Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 22s
The token registry holds a logical clock (advance/now; the substrate has no wall clock). Grants carry a Ttl; each access token carries an Expires (Now-at-issue + Ttl, or infinity); introspect returns inactive once Now reaches it. Refresh mints a fresh short-lived access token — short access tokens, long refresh tokens. issue/4 and issue_grant/4 default to infinity so all prior behaviour is unchanged. New tests/expiry.sx. token loop/6. 138/138. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
93 lines
4.7 KiB
Plaintext
93 lines
4.7 KiB
Plaintext
;; identity/tests/expiry.sx — access-token expiry on a logical clock
|
|
;; (RFC 6749 §4.2.2 expires_in). `advance` stands in for time passing;
|
|
;; introspect returns inactive once the clock reaches a token's expiry.
|
|
;; Refresh mints a fresh short-lived access token — the point of refresh.
|
|
|
|
(define id-expiry-test-count 0)
|
|
(define id-expiry-test-pass 0)
|
|
(define id-expiry-test-fails (list))
|
|
|
|
(define
|
|
id-expiry-test
|
|
(fn
|
|
(name actual expected)
|
|
(set! id-expiry-test-count (+ id-expiry-test-count 1))
|
|
(if
|
|
(= actual expected)
|
|
(set! id-expiry-test-pass (+ id-expiry-test-pass 1))
|
|
(append! id-expiry-test-fails {:name name :expected expected :actual actual}))))
|
|
|
|
(define ide-ev erlang-eval-ast)
|
|
(define idenm (fn (v) (get v :name)))
|
|
|
|
(identity-load-token!)
|
|
|
|
;; ── within TTL is active; past TTL is inactive ───────────────────
|
|
|
|
(id-expiry-test
|
|
"a token within its TTL is active"
|
|
(idenm
|
|
(ide-ev
|
|
"R = identity_tokens:start(),\n {ok, T} = identity_tokens:issue(R, alice, web, read, 100),\n identity_tokens:advance(R, 50),\n case identity_tokens:introspect(R, T) of\n {active, _, _, _} -> active;\n {inactive} -> inactive\n end"))
|
|
"active")
|
|
|
|
(id-expiry-test
|
|
"a token at its TTL boundary is expired"
|
|
(idenm
|
|
(ide-ev
|
|
"R = identity_tokens:start(),\n {ok, T} = identity_tokens:issue(R, alice, web, read, 100),\n identity_tokens:advance(R, 100),\n case identity_tokens:introspect(R, T) of\n {active, _, _, _} -> active;\n {inactive} -> inactive\n end"))
|
|
"inactive")
|
|
|
|
(id-expiry-test
|
|
"a token just before its TTL is still active"
|
|
(idenm
|
|
(ide-ev
|
|
"R = identity_tokens:start(),\n {ok, T} = identity_tokens:issue(R, alice, web, read, 100),\n identity_tokens:advance(R, 99),\n case identity_tokens:introspect(R, T) of\n {active, _, _, _} -> active;\n {inactive} -> inactive\n end"))
|
|
"active")
|
|
|
|
;; ── no TTL (infinity) never expires ──────────────────────────────
|
|
|
|
(id-expiry-test
|
|
"a token issued without a TTL never expires"
|
|
(idenm
|
|
(ide-ev
|
|
"R = identity_tokens:start(),\n {ok, T} = identity_tokens:issue(R, alice, web, read),\n identity_tokens:advance(R, 100000),\n case identity_tokens:introspect(R, T) of\n {active, _, _, _} -> active;\n {inactive} -> inactive\n end"))
|
|
"active")
|
|
|
|
;; ── refresh mints a fresh short-lived token ──────────────────────
|
|
|
|
(id-expiry-test
|
|
"refresh renews access after the old token expired"
|
|
(idenm
|
|
(ide-ev
|
|
"R = identity_tokens:start(),\n {ok, A, Rt} = identity_tokens:issue_grant(R, alice, web, read, 100),\n identity_tokens:advance(R, 100),\n inactive = case identity_tokens:introspect(R, A) of\n {active, _, _, _} -> active; {inactive} -> inactive end,\n {ok, A2, _R2} = identity_tokens:refresh(R, Rt),\n case identity_tokens:introspect(R, A2) of\n {active, _, _, _} -> renewed;\n {inactive} -> inactive\n end"))
|
|
"renewed")
|
|
|
|
(id-expiry-test
|
|
"the renewed token also expires after its own TTL"
|
|
(idenm
|
|
(ide-ev
|
|
"R = identity_tokens:start(),\n {ok, _A, Rt} = identity_tokens:issue_grant(R, alice, web, read, 100),\n identity_tokens:advance(R, 100),\n {ok, A2, _R2} = identity_tokens:refresh(R, Rt),\n identity_tokens:advance(R, 100),\n case identity_tokens:introspect(R, A2) of\n {active, _, _, _} -> active;\n {inactive} -> inactive\n end"))
|
|
"inactive")
|
|
|
|
;; ── the logical clock ────────────────────────────────────────────
|
|
|
|
(id-expiry-test
|
|
"the clock starts at zero and advances"
|
|
(ide-ev
|
|
"R = identity_tokens:start(),\n identity_tokens:advance(R, 7),\n identity_tokens:advance(R, 35),\n identity_tokens:now(R)")
|
|
42)
|
|
|
|
;; ── expiry composes with revocation ──────────────────────────────
|
|
|
|
(id-expiry-test
|
|
"an expired token is also inactive after revoke (no contradiction)"
|
|
(idenm
|
|
(ide-ev
|
|
"R = identity_tokens:start(),\n {ok, T} = identity_tokens:issue(R, alice, web, read, 100),\n identity_tokens:advance(R, 200),\n identity_tokens:revoke(R, T),\n case identity_tokens:introspect(R, T) of\n {active, _, _, _} -> active;\n {inactive} -> inactive\n end"))
|
|
"inactive")
|
|
|
|
(define
|
|
id-expiry-test-summary
|
|
(str "expiry " id-expiry-test-pass "/" id-expiry-test-count))
|