@metametadata there is also a simple router in kekkonen.ring
, see https://github.com/metosin/kekkonen/blob/e52031da4a07a8f219475d113a77202d8bf86084/src/kekkonen/ring.clj#L257-L274
if you have static assets, you can do
(require '[kekkonen.ring :as r])
(def my-api (api …))
(def app
(r/routes
my-api
(r/match “/” #{:get} my-index-page)
(r/match “/public” my-resources)))
cool, thanks!
While playing with the lib I also struggled to understand the difference between using interceptors vs custom :user metadata fields in handlers - they both only modify the context, right?.. E.g. auth is implemented as user metadata function:
(defnk ^:command reset-counter!
"reset the counter. Just for admins."
{::roles #{:admin}} ; <-------- meta
[counter]
(success (reset! counter 0)))
but adding file upload support is implemented using an interceptor:
(defnk ^:command upload
"Upload a file to a server"
{:interceptors [[upload/multipart-params]]} ; <-------- interceptor
[[:state file]
[:request [:multipart-params upload :- upload/TempFileUpload]]]
(reset! file upload)
(success (dissoc upload :tempfile)))
It was also confusing why returning empty context from the user meta function results in not showing the handler in Swagger UI and (less surprising but still not obvious) returning 404 on xhr request to the unauthorised action:
(defn require-roles [required]
(fn [context]
(let [roles (-> context :user :roles)]
(if (seq (set/intersection roles required)) ; <-- can return nil context
context))))
in the end, there are only interceptors & handlers. The user-meta is an extra layer on top of interceptors.
at dispatcher creation time, all keys in the handler/namespace meta are transformed into interceptors. So, to use ::roles
, one needs to register a meta-hander, which takes the value #{:admin}
and returns an interceptor for it.
so all the user-meta keys need a key => value => Interceptor
function registered into the dispatcher. otherwise, a creation-time error is given.
Kekkonen eats it’s own dogfood: the :interceptors
is just a pre-registered user-meta-key.
https://github.com/metosin/kekkonen/blob/master/src/kekkonen/core.clj#L584 & https://github.com/metosin/kekkonen/blob/master/src/kekkonen/core.clj#L370-L374
That gives nice creation-time guarantees. For example, we have been using a require-role
interceptor, and if there is a predefined set of roles, we can enforce those at creation-time.
so, you can't write ::roles #{:bogus}
if there is no such role. Same for feature flags etc. Fail early.
thank you! I think I'm starting to get it now so the handler can be actually rewritten as this
(defnk ^:command reset-counter!
"reset the counter. Just for admins."
{:interceptors [security/api-key-authenticator
(security/require-roles #{:admin})]
}
[counter]
(success (reset! counter 0)))
but I guess it makes the handler less "portable" now because security/api-key-authenticator assumes there's a Ring layer
this is originally a snippet from https://github.com/metosin/kekkonen-sample btw
yes, Kekkonen doesn’t make it hard to mix the pure-clojure handlers from ring-ones. But one can push all the ring-spesific stuff into the ring-handler.
so, read from the request there and push results into context under some other key.
right
for example, we use :user
key to store info of the user. The handlers (or namespaces) use interceptors that just assert that it has right data. Might be a good idea to remote the :request
from the normal handlers totally. would make it easier to keep ‘em ring-free.
(defnk ^:command reset-counter!
"reset the counter. Just for admins."
{:interceptors [security/api-key-authenticator
[security/require-roles #{:admin}]}}}
[counter]
(success (reset! counter 0)))
that :interceptor
-form is most data-oriented.
that's nice, I think I saw smt like this in compojure-api middlewares
yes, the syntax is from Duct.
so it looks like that api.admin/reset-counter!
handler's meta is calculated on forming Swagger UI
as well as on the GET /kekkonen/handlers
but somehow not on GET /kekkonen/handler
for the api.admin/reset-counter!
(I've put logs into securoty
interceptor functions)
seems so. Hmm. Issue or PR welcome!
About the nil
contexts… could be changed if there is a better way. Thought it was safe not to see any apis that are not meant to be seen, but that’s bad in development.
there is the kekkonen.interceptor/terminate
that can be called on the context to kill the request processing (the pedestal way).
ideas welcome.
Got it, I'll prob add an issue
kekkonen.interceptor/terminate
works almost the same as failure!
- i.e. the action is visible in Swagger UI but throws an error status code (404 and 400 respectively)
yeah, I'm not sure I see much point in hiding the handlers in Swagger - it's like the security through obscurity 🙂
on the other hand, if API's Swagger UI is available to the public on purpose then it makes sense to hide some stuff..