cljs / src / ird / auth.cljs
500 words
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
(ns ird.auth
  "Device login flow and API key management. The device endpoints work
  anonymously; the API key management endpoints require an account JWT (API
  keys cannot manage API keys). Every function returns a channel (see
  ird.async)."
  (:require [cljs.core.async :as async]
            [ird.async :refer [go-try <?]]
            [ird.errors :as err]
            [ird.node :as node]
            [ird.transport :as transport]))

(defn device-start
  "POST /auth/device/start — Start a device login. Anonymous allowed.
  Delivers the response map with :device_code, :user_code,
  :verification_uri, :verification_uri_complete, :interval, :expires_in."
  [client]
  (transport/request client :post "/auth/device/start"))

(defn device-poll
  "POST /auth/device/poll — Poll a device login. Anonymous allowed.
  Delivers the response map with :approved."
  [client device-code]
  (transport/request client :post "/auth/device/poll"
                     {:body {:device_code device-code}}))

(defn device-complete
  "POST /auth/device/complete — Complete a device login. Anonymous allowed.
  Delivers the response map with :access_token, :refresh_token, :user."
  [client device-code confirmation-code]
  (transport/request client :post "/auth/device/complete"
                     {:body {:device_code device-code
                             :confirmation_code confirmation-code}}))

(defn list-api-keys
  "GET /auth/api-keys — List API keys (account JWT only)."
  [client]
  (transport/request client :get "/auth/api-keys"))

(defn create-api-key
  "POST /auth/api-keys — Create an API key (account JWT only).
  BODY is a plain map with :name."
  [client body]
  (transport/request client :post "/auth/api-keys" {:body body}))

(defn revoke-api-key
  "POST /auth/api-keys/{id}/revoke — Revoke an API key (account JWT only)."
  [client id]
  (transport/request client :post ["auth" "api-keys" id "revoke"]))

(defn device-login
  "Convenience: run the whole device login flow.

  Starts a device login, hands the start response to ON-PROMPT (default:
  prints the verification URL and user code) so a person can approve it in a
  browser, then polls until approved and completes the login with the
  confirmation code from CONFIRM-FN (a fn of the start response returning a
  channel of the code shown to the user after approval; default prompts on
  stdin).

  OPTS:
    :on-prompt   fn of the start response; default prints instructions.
    :confirm-fn  fn of the start response returning a channel of the
                 confirmation code; default reads a line from stdin.
    :interval-ms poll interval; defaults to the server-suggested :interval
                 (seconds) from the start response.
    :timeout-ms  overall deadline; defaults to the server's :expires_in.

  Delivers the completion map {:access_token :refresh_token :user}; feed
  :access_token to ird.client/make-client as :api-key. Delivers an
  :ird/timeout error when the login is not approved in time."
  ([client] (device-login client nil))
  ([client {:keys [on-prompt confirm-fn interval-ms timeout-ms]}]
   (let [on-prompt (or on-prompt
                       (fn [{:keys [verification_uri_complete verification_uri user_code]}]
                         (js/console.log "To sign in, open:" (or verification_uri_complete verification_uri))
                         (js/console.log "Code:" user_code)))
         confirm-fn (or confirm-fn
                        (fn [_] (node/question "Enter the confirmation code shown after approving: ")))]
     (go-try
       (let [start (<? (device-start client))
             ;; While the user hasn't approved, the API answers with
             ;; 400 authorization-pending; 429 slow-down asks for a longer
             ;; interval (poll-until's backoff grows it). Anything else
             ;; (401 expired, 403 denied) is terminal and delivered.
             poll (fn []
                    (go-try
                      (let [resp (async/<! (device-poll client (:device_code start)))]
                        (if (and (err/api-error? resp)
                                 (contains? #{400 429} (:status (ex-data resp))))
                          {:approved false}
                          resp))))]
         (on-prompt start)
         (<? (transport/poll-until poll :approved
                                   {:interval-ms (or interval-ms (* 1000 (or (:interval start) 5)))
                                    :timeout-ms (or timeout-ms (* 1000 (or (:expires_in start) 600)))
                                    :message "Device login timed out before approval"
                                    :sleep-fn (:sleep-fn client)}))
         (<? (device-complete client (:device_code start) (<? (confirm-fn start)))))))))

static mirror of HEAD · about · clone: git clone https://ird.cod-sursa.ro/cljs.git