Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 40s
oauth.sx now owns a client registry (loop/6) with register_client and the client_credentials grant. A confidential client authenticates and gets a token acting on its own behalf (subject = the client), no refresh token (§4.4.3). A public client is unauthorized_client; any auth failure (unknown client or wrong secret) is invalid_client — no client-existence oracle (§5.2). identity-load-oauth! now pulls its deps. New tests/grants.sx. 158/158. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
97 lines
4.7 KiB
Plaintext
97 lines
4.7 KiB
Plaintext
;; identity/tests/grants.sx — the client-credentials grant (RFC 6749
|
|
;; §4.4): a confidential client authenticates and gets a token acting on
|
|
;; its own behalf — no end-user, no refresh token (§4.4.3). Public clients
|
|
;; cannot use it.
|
|
|
|
(define id-grants-test-count 0)
|
|
(define id-grants-test-pass 0)
|
|
(define id-grants-test-fails (list))
|
|
|
|
(define
|
|
id-grants-test
|
|
(fn
|
|
(name actual expected)
|
|
(set! id-grants-test-count (+ id-grants-test-count 1))
|
|
(if
|
|
(= actual expected)
|
|
(set! id-grants-test-pass (+ id-grants-test-pass 1))
|
|
(append! id-grants-test-fails {:name name :expected expected :actual actual}))))
|
|
|
|
(define idg-ev erlang-eval-ast)
|
|
(define idgnm (fn (v) (get v :name)))
|
|
|
|
(identity-load-oauth!)
|
|
|
|
;; ── confidential client-credentials happy path ───────────────────
|
|
|
|
(id-grants-test
|
|
"a confidential client obtains a working token"
|
|
(idgnm
|
|
(idg-ev
|
|
"O = identity_oauth:start(),\n identity_oauth:register_client(O, svc, confidential, sk, [uri1]),\n {ok, T} = identity_oauth:client_credentials(O, svc, sk, batch),\n case identity_oauth:introspect(O, T) of\n {active, _, _, _} -> active;\n {inactive} -> inactive\n end"))
|
|
"active")
|
|
|
|
(id-grants-test
|
|
"the client-credentials token's subject is the client itself"
|
|
(idgnm
|
|
(idg-ev
|
|
"O = identity_oauth:start(),\n identity_oauth:register_client(O, svc, confidential, sk, [uri1]),\n {ok, T} = identity_oauth:client_credentials(O, svc, sk, batch),\n case identity_oauth:introspect(O, T) of\n {active, Subject, _, _} -> Subject\n end"))
|
|
"svc")
|
|
|
|
(id-grants-test
|
|
"the client-credentials token carries the requested scope"
|
|
(idgnm
|
|
(idg-ev
|
|
"O = identity_oauth:start(),\n identity_oauth:register_client(O, svc, confidential, sk, [uri1]),\n {ok, T} = identity_oauth:client_credentials(O, svc, sk, reports),\n case identity_oauth:introspect(O, T) of\n {active, _, _, Scope} -> Scope\n end"))
|
|
"reports")
|
|
|
|
(id-grants-test
|
|
"client-credentials issues no refresh token (single value)"
|
|
(idgnm
|
|
(idg-ev
|
|
"O = identity_oauth:start(),\n identity_oauth:register_client(O, svc, confidential, sk, [uri1]),\n case identity_oauth:client_credentials(O, svc, sk, batch) of\n {ok, _, _} -> pair;\n {ok, _} -> single;\n {error, W} -> W\n end"))
|
|
"single")
|
|
|
|
;; ── authentication failures ──────────────────────────────────────
|
|
|
|
(id-grants-test
|
|
"a wrong client secret is invalid_client"
|
|
(idgnm
|
|
(idg-ev
|
|
"O = identity_oauth:start(),\n identity_oauth:register_client(O, svc, confidential, sk, [uri1]),\n case identity_oauth:client_credentials(O, svc, wrong, batch) of\n {ok, _} -> issued;\n {error, W} -> W\n end"))
|
|
"invalid_client")
|
|
|
|
(id-grants-test
|
|
"a public client cannot use client-credentials"
|
|
(idgnm
|
|
(idg-ev
|
|
"O = identity_oauth:start(),\n identity_oauth:register_client(O, spa, public, none, [uri1]),\n case identity_oauth:client_credentials(O, spa, none, batch) of\n {ok, _} -> issued;\n {error, W} -> W\n end"))
|
|
"unauthorized_client")
|
|
|
|
(id-grants-test
|
|
"an unregistered client cannot use client-credentials"
|
|
(idgnm
|
|
(idg-ev
|
|
"O = identity_oauth:start(),\n case identity_oauth:client_credentials(O, ghost, x, batch) of\n {ok, _} -> issued;\n {error, W} -> W\n end"))
|
|
"invalid_client")
|
|
|
|
;; ── independence + real revocation for client tokens ─────────────
|
|
|
|
(id-grants-test
|
|
"two confidential clients get independent tokens"
|
|
(idgnm
|
|
(idg-ev
|
|
"O = identity_oauth:start(),\n identity_oauth:register_client(O, svc1, confidential, k1, [uri1]),\n identity_oauth:register_client(O, svc2, confidential, k2, [uri1]),\n {ok, _T1} = identity_oauth:client_credentials(O, svc1, k1, batch),\n {ok, T2} = identity_oauth:client_credentials(O, svc2, k2, batch),\n case identity_oauth:introspect(O, T2) of\n {active, Subject, _, _} -> Subject\n end"))
|
|
"svc2")
|
|
|
|
(id-grants-test
|
|
"a client-credentials token can be revoked"
|
|
(idgnm
|
|
(idg-ev
|
|
"O = identity_oauth:start(),\n identity_oauth:register_client(O, svc, confidential, sk, [uri1]),\n {ok, T} = identity_oauth:client_credentials(O, svc, sk, batch),\n identity_oauth:revoke(O, T),\n case identity_oauth:introspect(O, T) of\n {active, _, _, _} -> still_valid;\n {inactive} -> inactive\n end"))
|
|
"inactive")
|
|
|
|
(define
|
|
id-grants-test-summary
|
|
(str "grants " id-grants-test-pass "/" id-grants-test-count))
|