Add support for fragment parameters in the reitit-frontend module

We have to process the fragment parameters due to the fact that the authorization server returns a callback in the following format:
`https://example.com/oauth/google/callback#access_token=foo&refresh_token=bar&provider_token=baz&token_type=bearer&expires_in=3600`

Links:
- https://www.rfc-editor.org/rfc/rfc6749#section-4.2
- https://www.rfc-editor.org/rfc/rfc6749#section-4.2.2
This commit is contained in:
Ilshat Sultanov 2022-06-15 22:49:13 +05:00
parent 3dff4c84aa
commit 25a051b003
No known key found for this signature in database
GPG key ID: 33B1D8EF26616886
3 changed files with 54 additions and 12 deletions

View file

@ -39,7 +39,8 @@
:body (->ParameterCoercion :body-params :body false false) :body (->ParameterCoercion :body-params :body false false)
:form (->ParameterCoercion :form-params :string true true) :form (->ParameterCoercion :form-params :string true true)
:header (->ParameterCoercion :headers :string true true) :header (->ParameterCoercion :headers :string true true)
:path (->ParameterCoercion :path-params :string true true)}) :path (->ParameterCoercion :path-params :string true true)
:fragment (->ParameterCoercion :fragment-params :string true true)})
(defn ^:no-doc request-coercion-failed! [result coercion value in request] (defn ^:no-doc request-coercion-failed! [result coercion value in request]
(throw (throw

View file

@ -1,5 +1,6 @@
(ns reitit.frontend (ns reitit.frontend
(:require [clojure.set :as set] (:require [clojure.set :as set]
[clojure.string :as str]
[reitit.coercion :as coercion] [reitit.coercion :as coercion]
[reitit.core :as r]) [reitit.core :as r])
(:import goog.Uri (:import goog.Uri
@ -20,6 +21,19 @@
(map (juxt keyword #(query-param q %))) (map (juxt keyword #(query-param q %)))
(into {})))) (into {}))))
(defn fragment-params
"Given goog.Uri, read fragment parameters into Clojure map."
[^Uri uri]
(let [fp (.getFragment uri)]
(if-not (seq fp)
{}
(into {}
(comp
(map #(str/split % #"="))
(map (fn [[k v]]
[(keyword k) v])))
(str/split fp #"&")))))
(defn match-by-path (defn match-by-path
"Given routing tree and current path, return match with possibly "Given routing tree and current path, return match with possibly
coerced parameters. Return nil if no match found. coerced parameters. Return nil if no match found.
@ -37,12 +51,14 @@
coercion/coerce!)] coercion/coerce!)]
(if-let [match (r/match-by-path router (.getPath uri))] (if-let [match (r/match-by-path router (.getPath uri))]
(let [q (query-params uri) (let [q (query-params uri)
match (assoc match :query-params q) fp (fragment-params uri)
match (assoc match :query-params q :fragment-params fp)
;; Return uncoerced values if coercion is not enabled - so ;; Return uncoerced values if coercion is not enabled - so
;; that tha parameters are always accessible from same property. ;; that tha parameters are always accessible from same property.
parameters (or (coerce! match) parameters (or (coerce! match)
{:path (:path-params match) {:path (:path-params match)
:query q})] :query q
:fragment fp})]
(assoc match :parameters parameters)))))) (assoc match :parameters parameters))))))
(defn match-by-name (defn match-by-name

View file

@ -21,9 +21,11 @@
:data {:name ::frontpage} :data {:name ::frontpage}
:path-params {} :path-params {}
:query-params {} :query-params {}
:fragment-params {}
:path "/" :path "/"
:parameters {:query {} :parameters {:query {}
:path {}}}) :path {}
:fragment {}}})
(rf/match-by-path router "/"))) (rf/match-by-path router "/")))
(is (= "/" (is (= "/"
@ -34,9 +36,11 @@
:data {:name ::foo} :data {:name ::foo}
:path-params {} :path-params {}
:query-params {} :query-params {}
:fragment-params {}
:path "/foo" :path "/foo"
:parameters {:query {} :parameters {:query {}
:path {}}}) :path {}
:fragment {}}})
(rf/match-by-path router "/foo"))) (rf/match-by-path router "/foo")))
(is (= (r/map->Match (is (= (r/map->Match
@ -44,9 +48,11 @@
:data {:name ::foo} :data {:name ::foo}
:path-params {} :path-params {}
:query-params {:mode ["foo", "bar"]} :query-params {:mode ["foo", "bar"]}
:fragment-params {}
:path "/foo" :path "/foo"
:parameters {:query {:mode ["foo", "bar"]} :parameters {:query {:mode ["foo", "bar"]}
:path {}}}) :path {}
:fragment {}}})
(rf/match-by-path router "/foo?mode=foo&mode=bar"))) (rf/match-by-path router "/foo?mode=foo&mode=bar")))
(is (= "/foo" (is (= "/foo"
@ -64,7 +70,12 @@
(let [router (r/router ["/" (let [router (r/router ["/"
[":id" {:name ::foo [":id" {:name ::foo
:parameters {:path {:id s/Int} :parameters {:path {:id s/Int}
:query {(s/optional-key :mode) s/Keyword}}}]] :query {(s/optional-key :mode) s/Keyword}
:fragment {(s/optional-key :access_token) s/Str
(s/optional-key :refresh_token) s/Str
(s/optional-key :expires_in) s/Int
(s/optional-key :provider_token) s/Str
(s/optional-key :token_type) s/Str}}}]]
{:compile rc/compile-request-coercers {:compile rc/compile-request-coercers
:data {:coercion rsc/coercion}})] :data {:coercion rsc/coercion}})]
@ -72,9 +83,11 @@
{:template "/:id" {:template "/:id"
:path-params {:id "5"} :path-params {:id "5"}
:query-params {} :query-params {}
:fragment-params {}
:path "/5" :path "/5"
:parameters {:query {} :parameters {:query {}
:path {:id 5}}}) :path {:id 5}
:fragment {}}})
(m (rf/match-by-path router "/5")))) (m (rf/match-by-path router "/5"))))
(is (= "/5" (is (= "/5"
@ -98,23 +111,35 @@
{:template "/:id" {:template "/:id"
:path-params {:id "5"} :path-params {:id "5"}
:query-params {:mode "foo"} :query-params {:mode "foo"}
:fragment-params {}
:path "/5" :path "/5"
:parameters {:path {:id 5} :parameters {:path {:id 5}
:query {:mode :foo}}}) :query {:mode :foo}
:fragment {}}})
(m (rf/match-by-path router "/5?mode=foo")))) (m (rf/match-by-path router "/5?mode=foo"))))
(is (= "/5?mode=foo" (is (= "/5?mode=foo"
(r/match->path (rf/match-by-name router ::foo {:id 5}) {:mode :foo})))) (r/match->path (rf/match-by-name router ::foo {:id 5}) {:mode :foo}))))
(testing "fragment is ignored" (testing "fragment is read"
(is (= (r/map->Match (is (= (r/map->Match
{:template "/:id" {:template "/:id"
:path-params {:id "5"} :path-params {:id "5"}
:query-params {:mode "foo"} :query-params {:mode "foo"}
:fragment-params {:access_token "foo"
:refresh_token "bar"
:provider_token "baz"
:token_type "bearer"
:expires_in "3600"}
:path "/5" :path "/5"
:parameters {:path {:id 5} :parameters {:path {:id 5}
:query {:mode :foo}}}) :query {:mode :foo}
(m (rf/match-by-path router "/5?mode=foo#fragment"))))) :fragment {:access_token "foo"
:refresh_token "bar"
:provider_token "baz"
:token_type "bearer"
:expires_in 3600}}})
(m (rf/match-by-path router "/5?mode=foo#access_token=foo&refresh_token=bar&provider_token=baz&token_type=bearer&expires_in=3600")))))
(testing "console warning about missing params" (testing "console warning about missing params"
(is (= [{:type :warn (is (= [{:type :warn