Files
rose-ash/lib/identity/token.sx
giles ac63501266
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 33s
identity: opaque grant-backed tokens — issue/introspect/revoke (9 tests)
Token table is a process; the token is an opaque make_ref carrying no
information. introspect() is a live table lookup every time, so
revocation is real (RFC 7009 §2): a revoked token reads {inactive} on
the next introspection with no validity window. Reply shapes follow
RFC 7662 §2.2 ({active, Subject, Client, Scope} / {inactive}).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 23:48:30 +00:00

24 lines
2.6 KiB
Plaintext

;; identity/token.sx — opaque, grant-backed tokens (RFC 7662 / 7009).
;;
;; The token table is a process; the token itself is an opaque handle
;; (make_ref) that carries NO information. introspect(Token) is a live
;; lookup against the table every time — the token is never decoded.
;; Because every introspection consults the live table, revocation is
;; real: a revoked token reads inactive on the very next introspection,
;; with no window where it still validates (RFC 7009 §2).
;;
;; introspect replies model RFC 7662 §2.2:
;; {active, Subject, Client, Scope} — token is currently valid
;; {inactive} — unknown OR revoked; never says why
;;
;; Authorization is NOT decided here. {active, ...} states WHO and WHAT
;; was granted; whether that subject may do a thing is acl's question.
(define
identity-token-source
"-module(identity_tokens).\n\n start() ->\n spawn(fun () -> loop([]) end).\n\n issue(Reg, Subject, Client, Scope) ->\n Reg ! {issue, Subject, Client, Scope, self()},\n receive {token_reply, R} -> R end.\n\n introspect(Reg, Token) ->\n Reg ! {introspect, Token, self()},\n receive {token_reply, R} -> R end.\n\n revoke(Reg, Token) ->\n Reg ! {revoke, Token, self()},\n receive {token_reply, R} -> R end.\n\n stop(Reg) ->\n Reg ! {stop, self()},\n receive {token_reply, R} -> R end.\n\n loop(Tokens) ->\n receive\n {issue, Subject, Client, Scope, From} ->\n Token = make_ref(),\n From ! {token_reply, {ok, Token}},\n loop([{Token, {Subject, Client, Scope, active}} | Tokens]);\n {introspect, Token, From} ->\n From ! {token_reply, find(Token, Tokens)},\n loop(Tokens);\n {revoke, Token, From} ->\n From ! {token_reply, ok},\n loop(revoke_token(Token, Tokens));\n {stop, From} ->\n From ! {token_reply, ok}\n end.\n\n find(_, []) -> {inactive};\n find(Token, [{T, {Subject, Client, Scope, active}} | Rest]) ->\n case T =:= Token of\n true -> {active, Subject, Client, Scope};\n false -> find(Token, Rest)\n end;\n find(Token, [{T, {_, _, _, revoked}} | Rest]) ->\n case T =:= Token of\n true -> {inactive};\n false -> find(Token, Rest)\n end.\n\n revoke_token(_, []) -> [];\n revoke_token(Token, [{T, {Su, Cl, Sc, St}} | Rest]) ->\n case T =:= Token of\n true -> [{T, {Su, Cl, Sc, revoked}} | Rest];\n false -> [{T, {Su, Cl, Sc, St}} | revoke_token(Token, Rest)]\n end.")
(define
identity-load-token!
(fn () (erlang-load-module identity-token-source)))