clj / src / ird / auth.clj
481 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
 99
100
101
(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)."
  (:require
   [ird.transport :as transport]))

(defn device-start
  "POST /auth/device/start — Start a device login. Anonymous allowed.
  Returns the parsed 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.
  Returns the parsed 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.
  Returns the parsed 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.
  Returns the parsed response map."
  [client]
  (transport/request client :get "/auth/api-keys"))

(defn create-api-key
  "POST /auth/api-keys — Create an API key.
  BODY is a plain map with :name.
  Returns the parsed response map."
  [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.
  Returns the parsed response map."
  [client id]
  (transport/request client :post (str "/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
  CONFIRMATION-FN (a function of the start response returning the
  confirmation code shown to the user after approval; default prompts on
  stdin via read-line).

  OPTS:
    :on-prompt       fn of the start response; default prints instructions.
    :confirmation-fn fn of the start response returning 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.

  Returns the completion map {:access_token :refresh_token :user}; feed
  :access_token to ird.client/make-client as :api-key. Throws :ird/timeout
  when the login is not approved in time."
  ([client] (device-login client nil))
  ([client {:keys [on-prompt confirmation-fn interval-ms timeout-ms]}]
   (let [start (device-start client)
         on-prompt (or on-prompt
                       (fn [{:keys [verification_uri_complete verification_uri user_code]}]
                         (println "To sign in, open:" (or verification_uri_complete verification_uri))
                         (println "Code:" user_code)))
         confirmation-fn (or confirmation-fn
                             (fn [_]
                               (println "Enter the confirmation code shown after approving:")
                               (read-line)))
         interval-ms (or interval-ms (* 1000 (or (:interval start) 5)))
         timeout-ms (or timeout-ms (* 1000 (or (:expires_in start) 600)))
         ;; 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 rethrown.
         poll (fn []
                (try
                  (device-poll client (:device_code start))
                  (catch clojure.lang.ExceptionInfo e
                    (let [{:keys [type status]} (ex-data e)]
                      (if (and (= :ird/api-error type)
                               (contains? #{400 429} status))
                        {:approved false}
                        (throw e))))))]
     (on-prompt start)
     (transport/poll-until poll :approved
                           {:interval-ms interval-ms
                            :timeout-ms timeout-ms
                            :message "Device login timed out before approval"})
     (device-complete client (:device_code start) (confirmation-fn start)))))

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