;; identity/api.sx — the identity service facade. ;; ;; `identity:start()` spawns one coordinator process that owns a token ;; table and a session registry and ties them together. It exposes the ;; whole-domain operations the architecture sketch names: ;; ;; login(Svc, Subject, Client, Scope[, Ttl]) -> {ok, SessionId, Token} ;; verify(Svc, Token) -> {active, Subject, Client, Scope} | {inactive} ;; revoke(Svc, Token) -> ok (revokes the token; real, immediate) ;; logout(Svc, SessionId) -> ok (tombstones + deregisters a session) ;; session_status(Svc, Sid) -> active | expired | revoked | gone ;; ;; The coordinator is also the Owner the sessions notify on idle timeout, ;; so an expired session deregisters itself from the directory — the ;; timeout is the only liveness driver; nothing sweeps. ;; ;; Delegation boundary: verify/2 answers IDENTITY only — who the token ;; belongs to and what scope was granted. It deliberately does NOT answer ;; \"may they do X\"; that question belongs to acl-on-sx, which keys off the ;; {active, Subject, Client, Scope} this returns. (define identity-api-source "-module(identity).\n\n start() ->\n spawn(fun () ->\n TokReg = identity_tokens:start(),\n SessReg = identity_registry:start(),\n loop(TokReg, SessReg, 1)\n end).\n\n login(Svc, Subject, Client, Scope) ->\n login(Svc, Subject, Client, Scope, infinity).\n\n login(Svc, Subject, Client, Scope, Ttl) ->\n Svc ! {login, Subject, Client, Scope, Ttl, self()},\n receive {identity_reply, R} -> R end.\n\n verify(Svc, Token) ->\n Svc ! {verify, Token, self()},\n receive {identity_reply, R} -> R end.\n\n revoke(Svc, Token) ->\n Svc ! {revoke, Token, self()},\n receive {identity_reply, R} -> R end.\n\n logout(Svc, SessionId) ->\n Svc ! {logout, SessionId, self()},\n receive {identity_reply, R} -> R end.\n\n session_status(Svc, SessionId) ->\n Svc ! {session_status, SessionId, self()},\n receive {identity_reply, R} -> R end.\n\n loop(TokReg, SessReg, NextId) ->\n receive\n {login, Subject, Client, Scope, Ttl, From} ->\n SessionId = NextId,\n Self = self(),\n S = identity_session:start(SessionId, Subject, Client, Self, Ttl),\n identity_registry:register(SessReg, SessionId, Subject, Client, S),\n {ok, Token} = identity_tokens:issue(TokReg, Subject, Client, Scope),\n From ! {identity_reply, {ok, SessionId, Token}},\n loop(TokReg, SessReg, NextId + 1);\n {verify, Token, From} ->\n From ! {identity_reply, identity_tokens:introspect(TokReg, Token)},\n loop(TokReg, SessReg, NextId);\n {revoke, Token, From} ->\n identity_tokens:revoke(TokReg, Token),\n From ! {identity_reply, ok},\n loop(TokReg, SessReg, NextId);\n {logout, SessionId, From} ->\n case identity_registry:whereis_session(SessReg, SessionId) of\n {ok, Pid} -> identity_session:revoke(Pid);\n {error, _} -> ok\n end,\n identity_registry:deregister(SessReg, SessionId),\n From ! {identity_reply, ok},\n loop(TokReg, SessReg, NextId);\n {session_status, SessionId, From} ->\n R = case identity_registry:whereis_session(SessReg, SessionId) of\n {ok, Pid} ->\n case identity_session:lookup(Pid) of\n {ok, {_, _, _, St}} -> St;\n {error, St} -> St\n end;\n {error, _} -> gone\n end,\n From ! {identity_reply, R},\n loop(TokReg, SessReg, NextId);\n {session_expired, SessionId} ->\n identity_registry:deregister(SessReg, SessionId),\n loop(TokReg, SessReg, NextId)\n end.") (define identity-load-api! (fn () (erlang-load-module identity-api-source))) (define identity-load-all! (fn () (identity-load-session!) (identity-load-token!) (identity-load-registry!) (identity-load-api!)))