Fork me on GitHub
#fulcro
<
2019-01-06
>
pauld21:01:02

I'm about to try to implement authentication and authorization in fulcro. If you were to do this today, would you implement it using a pathom plugin?

currentoor23:01:56

@pauld, @tony.kay and I are working on a project, we use a combination of java libraries, macros, and parser plugin i’ll make a quick gist

currentoor23:01:52

the user file shows how we encrypt/store the authentication data

currentoor23:01:56

the parser file shows how we inject user info into each parser action (every client request comes with an JWT token)

currentoor23:01:34

the mutation file shows how we specify authentication policies for different mutations

currentoor23:01:45

the policy file shows a simple example policy, but it can be anything, like checking permissions or access

currentoor23:01:33

for example here’s another policy

(defn wash? [db org-id wash-id]
  (let [wash (d/entity db [:wash/id wash-id])]
    (= org-id
      (-> wash
        :entity/firm
        :db/id))))

(defn wash-ownership [{:keys [env params] :as args}]
  (let [{:keys [db current/firm]} env

        {:keys [wash/id]} params
        org-id (:db/id firm)]
    (and (existence args)
      (wash? db org-id id))))

currentoor23:01:19

every one of our models has a reference to a firm and we can check for ownership based on whether a given model’s firm is the same firm as the user from the token

pauld23:01:40

@currentoor This is wonderful thanks! This is so helpful.

currentoor23:01:27

no worries, like i said it was a joint effort with @tony.kay

currentoor23:01:50

but this has been asked about a few times before

currentoor23:01:15

maintaining an example application is too much work, including it in fulcro is too specific

currentoor23:01:35

maybe i should write an article at some point with this example code

pauld23:01:38

well thanks to you both!

currentoor23:01:50

then we should share that

currentoor23:01:58

since this is something that comes up all the time

pauld23:01:36

yeah, it's usually one of the first things you want to figure out for any real project

currentoor23:01:41

oh and we have a similar macro for resolvers

(s/def ::value (s/cat
                 :value-name (fn [sym] (= sym 'value))
                 :value-args (fn [a] (and (vector? a) (= 2 (count a))))
                 :value-body (s/+ (constantly true))))

(s/def ::resolver-args (s/cat
                         :sym symbol?
                         :doc (s/? string?)
                         :output vector?
                         :policy (s/? (s/spec ::policy))
                         :value (s/? (s/spec ::value))))

(defmacro ^{:doc      "Defines a pathom resolver but with authorization. See pathom docs."
            :arglists '([sym docstring? output policy value])} defresolver
  [& args]
  (let [{:keys [sym output policy value]} (util/conform! ::resolver-args args)
        output  (first output)
        fqsym   (if (namespace sym)
                  sym
                  (symbol (name (ns-name *ns*)) (name sym)))
        policy  (:policy-fn policy)
        {:keys [value-args value-body]} value
        ex-msg  (str "Resolver " fqsym " unauthorized, " policy " violated")
        ex-body {:status 401}]
    `(ucv.server.parser/defresolver '~fqsym ~output
       (fn [env# params#]
         (if (~policy {:env env# :k '~fqsym :params params#})
           (let [~(first value-args) env#
                 ~(second value-args) params#]
             ~@value-body)
           (throw (ex-info ~ex-msg ~ex-body)))))))

pauld23:01:44

This will be great motivation for me to keep learning in the context of a real app.