|
| 1 | +(ns parts.frontend.api.http |
| 2 | + "Low level functions for interacting with the backend." |
| 3 | + (:require |
| 4 | + [cljs-http.client :as http-client] |
| 5 | + [cljs.core.async :refer [<! go]] |
| 6 | + [parts.frontend.api.utils :as utils])) |
| 7 | + |
| 8 | +(defn- add-auth-header |
| 9 | + "Add the Authorization header to REQ if OPTS includes :auth-header; if not |
| 10 | + leave REQ untouched." |
| 11 | + [req opts] |
| 12 | + (if-let [header (:auth-header opts)] |
| 13 | + (assoc-in req [:headers "Authorization"] header) |
| 14 | + req)) |
| 15 | + |
| 16 | +;; These functions wrap the cljs-http requests. |
| 17 | +;; They should not be used directly. |
| 18 | +(defn- raw-GET [endpoint params & [opts]] |
| 19 | + (go (<! (http-client/get (str "/api" endpoint) |
| 20 | + (-> {:query-params params |
| 21 | + :accept :transit+json} |
| 22 | + (add-auth-header opts)))))) |
| 23 | + |
| 24 | +(defn- raw-POST [endpoint data & [opts]] |
| 25 | + (go (<! (http-client/post (str "/api" endpoint) |
| 26 | + (-> {:transit-params data |
| 27 | + :accept :transit+json} |
| 28 | + (add-auth-header opts)))))) |
| 29 | + |
| 30 | +(defn- raw-PUT [endpoint data & [opts]] |
| 31 | + (go (<! (http-client/put (str "/api" endpoint) |
| 32 | + (-> {:transit-params data |
| 33 | + :accept :transit+json} |
| 34 | + (add-auth-header opts)))))) |
| 35 | + |
| 36 | +(defn- raw-PATCH [endpoint data & [opts]] |
| 37 | + (go (<! (http-client/patch (str "/api" endpoint) |
| 38 | + (-> {:transit-params data |
| 39 | + :accept :transit+json} |
| 40 | + (add-auth-header opts)))))) |
| 41 | + |
| 42 | +(defn- raw-DELETE [endpoint & [opts]] |
| 43 | + (go (<! (http-client/delete (str "/api" endpoint) |
| 44 | + (-> {:accept :transit+json} |
| 45 | + (add-auth-header opts)))))) |
| 46 | + |
| 47 | +(defn wrap-auth |
| 48 | + "Transparently handles auth token lifecycle for HTTP requests. |
| 49 | +
|
| 50 | + Wraps HTTP handlers with JWT auth machinery that: |
| 51 | + 1. Augments outbound reqs with auth headers (when token exists) |
| 52 | + 2. Intercepts 401s and attempts token refresh |
| 53 | + 3. Retries failed reqs with fresh credentials |
| 54 | +
|
| 55 | + OPTIONS: |
| 56 | + :skip-auth - bypasses ALL auth logic (both header injection and refresh)" |
| 57 | + [handler] |
| 58 | + (fn [endpoint params & [opts]] |
| 59 | + (go |
| 60 | + (let [skip-auth? (:skip-auth opts) |
| 61 | + tokens (utils/get-tokens) |
| 62 | + auth-header (utils/auth-header tokens) |
| 63 | + auth-opts (if (and (not skip-auth?) auth-header) |
| 64 | + (assoc opts :auth-header auth-header) |
| 65 | + opts) |
| 66 | + resp (<! (handler endpoint params auth-opts))] |
| 67 | + (if (and (= 401 (:status resp)) |
| 68 | + (not skip-auth?) |
| 69 | + (:refresh_token tokens)) |
| 70 | + (let [refresh-resp (<! (raw-POST "/auth/refresh" |
| 71 | + {:refresh_token (:refresh_token tokens)}))] |
| 72 | + (if (= 200 (:status refresh-resp)) |
| 73 | + (do |
| 74 | + (utils/save-tokens (:body refresh-resp)) |
| 75 | + (let [new-tokens (utils/get-tokens) |
| 76 | + new-auth-header (utils/auth-header new-tokens) |
| 77 | + new-params (assoc-in params [:headers "Authorization"] new-auth-header)] |
| 78 | + (<! (handler endpoint new-params)))) |
| 79 | + resp)) |
| 80 | + resp))))) |
| 81 | + |
| 82 | +;; Wrapping the raw HTTP handlers in the auth middleware |
| 83 | +;; Pass {:skip-auth true} to ignore authorization flows. |
| 84 | +(def GET (wrap-auth raw-GET)) |
| 85 | +(def POST (wrap-auth raw-POST)) |
| 86 | +(def PUT (wrap-auth raw-PUT)) |
| 87 | +(def PATCH (wrap-auth raw-PATCH)) |
| 88 | +(def DELETE (wrap-auth raw-DELETE)) |
0 commit comments