mirror of
https://github.com/metosin/reitit.git
synced 2025-12-17 08:21:11 +00:00
I find the clarity of this example important because the implementation of (ring/get-match) c23f591283/modules/reitit-ring/src/reitit/ring.cljc (L309) doesn't explain what's going on here.
I find the destructing of the qualified keyword `::roles` unnecessary to the example. To this end I propose using an unqualified keyword.
Additionally there is a collision between the name of the required rolls to access the route and the name of the roles held by the user making the request. To this end I propose having the roles held by the user named `:my-roles`
1.5 KiB
1.5 KiB
Dynamic Extensions
ring-handler injects the Match into a request and it can be extracted at runtime with reitit.ring/get-match. This can be used to build ad-hoc extensions to the system.
Example middleware to guard routes based on user roles:
(require '[reitit.ring :as ring])
(require '[clojure.set :as set])
(defn wrap-enforce-roles [handler]
(fn [{:keys [my-roles] :as request}]
(let [required (some-> request (ring/get-match) :data ::roles)]
(if (and (seq required) (not (set/subset? required my-roles)))
{:status 403, :body "forbidden"}
(handler request)))))
Mounted to an app via router data (affecting all routes):
(def handler (constantly {:status 200, :body "ok"}))
(def app
(ring/ring-handler
(ring/router
[["/api"
["/ping" handler]
["/admin" {::roles #{:admin}}
["/ping" handler]]]]
{:data {:middleware [wrap-enforce-roles]}})))
Anonymous access to public route:
(app {:request-method :get, :uri "/api/ping"})
; {:status 200, :body "ok"}
Anonymous access to guarded route:
(app {:request-method :get, :uri "/api/admin/ping"})
; {:status 403, :body "forbidden"}
Authorized access to guarded route:
(app {:request-method :get, :uri "/api/admin/ping", :my-roles #{:admin}})
; {:status 200, :body "ok"}
Dynamic extensions are nice, but we can do much better. See data-driven middleware and compiling routes.