Fork me on GitHub
#graphql
<
2019-09-17
>
erwinrooijakkers14:09:06

How do I add an extra interceptor to Pedestal Lacinia?

erwinrooijakkers14:09:45

This does not work:

(def my-interceptor
  {:name ::my-interceptor
   :enter (fn [context]
            (log/info "hello!" context)
            context)})

(defn interceptors [compiled-schema]
  (let [options {}]
    (conj (pedestal/default-interceptors compiled-schema options)
          my-interceptor)))

(defrecord Server [schema-provider server port]
  component/Lifecycle
  (start [this]
    (let [compiled-schema (:schema schema-provider)]
      (assoc this :server (-> compiled-schema
                              (pedestal/service-map {:graphiql true
                                                     :port port
                                                     :interceptors (interceptors compiled-schema)})
                              http/create-server
                              http/start))))
  (stop [this]
    (http/stop server)
    (assoc this :server nil)))

nenadalm18:09:54

It should work as default-interceptors function returns vector of interceptors: https://github.com/walmartlabs/lacinia-pedestal/blob/2e7a7b82742c09a9f65ff30ecccb7c4e8f906799/src/com/walmartlabs/lacinia/pedestal.clj#L416 The thing is though that the last of the default interceptors probably adds response to the context: https://github.com/walmartlabs/lacinia-pedestal/blob/2e7a7b82742c09a9f65ff30ecccb7c4e8f906799/src/com/walmartlabs/lacinia/pedestal.clj#L354 which causes pedestal to omit rest of the interceptors: http://pedestal.io/reference/servlet-interceptor#_early_termination so your interceptor even if present is not called. If you want to add your own interceptor, you should add it before last default interceptor. You can use inject helper fn for that: https://github.com/walmartlabs/lacinia-pedestal/blob/2e7a7b82742c09a9f65ff30ecccb7c4e8f906799/src/com/walmartlabs/lacinia/pedestal.clj#L81 and yes. :request is available to field resolvers, thanks to this interceptor: https://github.com/walmartlabs/lacinia-pedestal/blob/2e7a7b82742c09a9f65ff30ecccb7c4e8f906799/src/com/walmartlabs/lacinia/pedestal.clj#L318 (you can create your own interceptor that will add more stuff into context and inject it after inject-app-context-interceptor)

erwinrooijakkers09:09:06

What does use of inject look like?

erwinrooijakkers09:09:33

Ah I see a test like this:

deftest inject-before
  (let [fred {:name :fred}
        barney {:name :barney}
        wilma {:name :wilma}]
    (is (= [fred wilma barney]
           (inject [fred barney] wilma :before :barney)))))

erwinrooijakkers09:09:24

And I should put it before :name :io.pedestal.http.impl.servlet-interceptor/ring-response

erwinrooijakkers14:09:11

Adding after inject-app-context does not provide data on the context…

erwinrooijakkers14:09:29

(defn- inject-roles-interceptor [interceptors]
  (pedestal/inject interceptors
                   roles-interceptor
                   :after
                   ::pedestal/inject-app-context))

erwinrooijakkers14:09:36

But the context is empty in the resolver

erwinrooijakkers14:09:57

(defn- use-roles-resolver
  [ds {:keys [columns]}]
  (fn [{:auth/keys [roles] :as context} data _]
    (log/info roles)
    (log/info context)
 ))

(defn- resolver-map
  [ds]
  {:query/component-basic (use-roles-resolver ds)})
Does not print the roles that are added by the interceptor…

erwinrooijakkers14:09:23

It does not have an added key

erwinrooijakkers14:09:30

Any idea why? Should I inject somehwere else?

nenadalm16:09:30

I didn't notice example interceptor from you, did I miss it or did you not post it? Did you add the required key into :lacinia-app-context inside of the context? Example of interceptor (untested) that adds key :custom-role-key inside context with value :some-value

(def roles-interceptor
  {:enter (fn [context]
    (assoc-in context [:lacinia-app-context :custom-role-key] :some-value})

erwinrooijakkers07:09:01

I added it to the context

erwinrooijakkers07:09:05

Not the lacinia-app-context

erwinrooijakkers07:09:11

Awesome thanks a lot

erwinrooijakkers08:09:48

Hmm that also does not work

erwinrooijakkers08:09:25

I now try replacing the full inject-app-context interceptor with my own code

erwinrooijakkers08:09:34

This also does not work:

(defn my-own-inject-app-context-interceptor
  "Adds a `:lacinia-app-context` key to the request, used when executing the query.
  The provided app-context map is augmented with the request map, as
  key `:request`.

  The interceptor extracts authentication information from the request and
  exposes that as the app-context's `:auth/roles` key."
  {:added "0.2.0"}
  [app-context]
  (interceptor
    {:name ::inject-app-context
     :enter (fn [context]
              (let [roles (get-roles context)]
                (assoc-in context [:request :lacinia-app-context]
                          (assoc app-context :request (:request context) :auth/roles roles)))}))

(defn- inject-roles-interceptor [interceptors]
  "Replace the default interceptor that creates the app context with one that also
  validates and retrieves the roles from the JWT and adds those to the
  app-context under the `:auth/roles` key."
  (pedestal/inject interceptors
                   my-own-inject-app-context-interceptor
                   :replace
                   ::pedestal/inject-app-context))

erwinrooijakkers08:09:40

Nothing available

erwinrooijakkers08:09:43

Not even the request then

erwinrooijakkers08:09:53

Ah maybe this one should begin with :request

erwinrooijakkers08:09:56

(def roles-interceptor
  {:enter (fn [context]
    (assoc-in context [:lacinia-app-context :custom-role-key] :some-value})

erwinrooijakkers08:09:03

So :request :lacinina-app-context

erwinrooijakkers15:09:31

Goal is to access the JWT token in the x-auth-token header of the HTTP request

erwinrooijakkers15:09:58

Ah I heard from a colleague that the request is also available in the context of a resolver

orestis19:09:58

Yep, under :request