Fork me on GitHub
#kekkonen
<
2016-12-19
>
ikitommi07:12:43

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)))

metametadata14:12:06

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))))

ikitommi15:12:15

in the end, there are only interceptors & handlers. The user-meta is an extra layer on top of interceptors.

ikitommi15:12:54

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.

ikitommi15:12:04

so all the user-meta keys need a key => value => Interceptor function registered into the dispatcher. otherwise, a creation-time error is given.

ikitommi15:12:38

Kekkonen eats it’s own dogfood: the :interceptors is just a pre-registered user-meta-key.

ikitommi15:12:21

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.

ikitommi15:12:04

so, you can't write ::roles #{:bogus} if there is no such role. Same for feature flags etc. Fail early.

metametadata15:12:26

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)))

metametadata15:12:39

but I guess it makes the handler less "portable" now because security/api-key-authenticator assumes there's a Ring layer

ikitommi15:12:30

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.

ikitommi15:12:02

so, read from the request there and push results into context under some other key.

ikitommi15:12:16

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.

ikitommi15:12:58

(defnk ^:command reset-counter!
  "reset the counter. Just for admins."
  {:interceptors [security/api-key-authenticator
                  [security/require-roles #{:admin}]}}}
  [counter]
  (success (reset! counter 0)))

ikitommi15:12:37

that :interceptor-form is most data-oriented.

metametadata15:12:09

that's nice, I think I saw smt like this in compojure-api middlewares

ikitommi15:12:28

yes, the syntax is from Duct.

metametadata15:12:19

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!

metametadata15:12:02

(I've put logs into securoty interceptor functions)

ikitommi16:12:59

seems so. Hmm. Issue or PR welcome!

ikitommi16:12:32

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.

ikitommi16:12:37

there is the kekkonen.interceptor/terminate that can be called on the context to kill the request processing (the pedestal way).

ikitommi16:12:41

ideas welcome.

metametadata16:12:30

Got it, I'll prob add an issue

metametadata16:12:58

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)

metametadata16:12:15

yeah, I'm not sure I see much point in hiding the handlers in Swagger - it's like the security through obscurity 🙂

metametadata16:12:32

on the other hand, if API's Swagger UI is available to the public on purpose then it makes sense to hide some stuff..