diff --git a/frontend/resources/templates/challenge.mustache b/frontend/resources/templates/challenge.mustache
new file mode 100644
index 000000000..16bba9b6a
--- /dev/null
+++ b/frontend/resources/templates/challenge.mustache
@@ -0,0 +1,18 @@
+
+
+
+
+ Penpot - Challenge
+
+
+
+
+
+
+
diff --git a/frontend/scripts/_helpers.js b/frontend/scripts/_helpers.js
index 0e284111d..6e2bd2765 100644
--- a/frontend/scripts/_helpers.js
+++ b/frontend/scripts/_helpers.js
@@ -333,6 +333,13 @@ async function generateTemplates() {
await fs.writeFile("./resources/public/index.html", content);
+ content = await renderTemplate(
+ "resources/templates/challenge.mustache",
+ {},
+ partials,
+ );
+ await fs.writeFile("./resources/public/challenge.html", content);
+
content = await renderTemplate("resources/templates/preview-body.mustache", {
manifest: manifest,
translations: JSON.stringify(translations),
diff --git a/frontend/src/app/main/data/users.cljs b/frontend/src/app/main/data/users.cljs
index 375119931..bd27cddb8 100644
--- a/frontend/src/app/main/data/users.cljs
+++ b/frontend/src/app/main/data/users.cljs
@@ -137,13 +137,24 @@
(when (not= previous-email email)
(set-current-team! nil)))))))
+(defn- on-fetch-profile-exception
+ [cause]
+ (let [data (ex-data cause)]
+ (if (and (= :authorization (:type data))
+ (= :challenge-required (:code data)))
+ (let [path (rt/get-current-path)
+ href (str "/challenge.html?redirect=" path)]
+ (rx/of (rt/nav-raw href)))
+ (rx/throw cause))))
+
(defn fetch-profile
[]
(ptk/reify ::fetch-profile
ptk/WatchEvent
(watch [_ _ _]
(->> (rp/cmd! :get-profile)
- (rx/map profile-fetched)))))
+ (rx/map profile-fetched)
+ (rx/catch on-fetch-profile-exception)))))
;; --- EVENT: login
diff --git a/frontend/src/app/main/repo.cljs b/frontend/src/app/main/repo.cljs
index b19edf933..77d4de012 100644
--- a/frontend/src/app/main/repo.cljs
+++ b/frontend/src/app/main/repo.cljs
@@ -17,7 +17,7 @@
[cuerdas.core :as str]))
(defn handle-response
- [{:keys [status body] :as response}]
+ [{:keys [status body headers] :as response}]
(cond
(= 204 status)
;; We need to send "something" so the streams listening downstream can act
@@ -40,6 +40,13 @@
{:type :validation
:code :request-body-too-large}))
+ (and (= status 403)
+ (or (= "cloudflare" (get headers "server"))
+ (= "challenge" (get headers "cf-mitigated"))))
+ (rx/throw (ex-info "http error"
+ {:type :authorization
+ :code :challenge-required}))
+
(and (>= status 400) (map? body))
(rx/throw (ex-info "http error" body))
@@ -48,6 +55,7 @@
(ex-info "http error"
{:type :unexpected-error
:status status
+ :headers headers
:data body}))))
(def default-options
diff --git a/frontend/src/app/util/router.cljs b/frontend/src/app/util/router.cljs
index c4d541cfd..cb17f1a80 100644
--- a/frontend/src/app/util/router.cljs
+++ b/frontend/src/app/util/router.cljs
@@ -13,8 +13,10 @@
[app.main.data.events :as ev]
[app.util.browser-history :as bhistory]
[app.util.dom :as dom]
+ [app.util.globals :as globals]
[app.util.timers :as ts]
[beicon.v2.core :as rx]
+ [cuerdas.core :as str]
[goog.events :as e]
[potok.v2.core :as ptk]
[reitit.core :as r]))
@@ -143,6 +145,20 @@
(= (.-hostname location) (:host referrer)))
(nav-back))))
+(defn nav-raw
+ [href]
+ (ptk/reify ::nav-raw
+ ptk/EffectEvent
+ (effect [_ _ _]
+ (set! (.-href globals/location) href))))
+
+(defn get-current-path
+ []
+ (let [hash (.-hash globals/location)]
+ (if (str/starts-with? hash "#")
+ (subs hash 1)
+ hash)))
+
;; --- History API
(defn initialize-history