;; identity/api.sx — the unified identity service facade.
;;
;; `identity:start()` spawns one coordinator that owns the whole domain:
;; an audit ledger, a grant-backed token table (wired to that ledger), a
;; session registry, and a membership registry. It exposes the
;; whole-domain operations through one door:
;;
;;   login / verify / revoke / logout / session_status   (sessions + tokens)
;;   sessions(Subject) / logout_all(Subject)             (subject-wide mgmt)
;;   grants(Subject) / revoke_app(Subject, Client)       (apps with access)
;;   history(Subject)                                     (audit ledger)
;;   enroll / member_status / member_project              (membership)
;;
;; Account-security surface, per subject: list where you're logged in
;; (sessions) and log out everywhere (logout_all); list which apps have
;; access (grants) and disconnect one (revoke_app); see what happened
;; (history). Every grant transition is audited. verify answers IDENTITY
;; only; membership projection reports WHAT a subject is for an app;
;; whether either may do a thing is acl's call.

(define
  identity-api-source
  "-module(identity).\n\n   start() ->\n     spawn(fun () ->\n       Audit = identity_audit:start(),\n       TokReg = identity_tokens:start(Audit),\n       SessReg = identity_registry:start(),\n       Members = identity_membership:start(),\n       loop(TokReg, SessReg, Audit, Members, 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   logout_all(Svc, Subject) ->\n     Svc ! {logout_all, Subject, self()},\n     receive {identity_reply, R} -> R end.\n\n   sessions(Svc, Subject) ->\n     Svc ! {sessions, Subject, self()},\n     receive {identity_reply, R} -> R end.\n\n   grants(Svc, Subject) ->\n     Svc ! {grants, Subject, self()},\n     receive {identity_reply, R} -> R end.\n\n   revoke_app(Svc, Subject, Client) ->\n     Svc ! {revoke_app, Subject, Client, 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   history(Svc, Subject) ->\n     Svc ! {history, Subject, self()},\n     receive {identity_reply, R} -> R end.\n\n   enroll(Svc, Subject, Tier) ->\n     Svc ! {enroll, Subject, Tier, self()},\n     receive {identity_reply, R} -> R end.\n\n   member_status(Svc, Subject) ->\n     Svc ! {member_status, Subject, self()},\n     receive {identity_reply, R} -> R end.\n\n   member_project(Svc, Subject, App) ->\n     Svc ! {member_project, Subject, App, self()},\n     receive {identity_reply, R} -> R end.\n\n   loop(TokReg, SessReg, Audit, Members, 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         identity_audit:record(Audit, Subject, login),\n         {ok, Token} = identity_tokens:issue(TokReg, Subject, Client, Scope),\n         From ! {identity_reply, {ok, SessionId, Token}},\n         loop(TokReg, SessReg, Audit, Members, NextId + 1);\n       {verify, Token, From} ->\n         From ! {identity_reply, identity_tokens:introspect(TokReg, Token)},\n         loop(TokReg, SessReg, Audit, Members, NextId);\n       {revoke, Token, From} ->\n         identity_tokens:revoke(TokReg, Token),\n         From ! {identity_reply, ok},\n         loop(TokReg, SessReg, Audit, Members, NextId);\n       {logout, SessionId, From} ->\n         case identity_registry:whereis_session(SessReg, SessionId) of\n           {ok, Pid} ->\n             audit_logout(Audit, Pid),\n             identity_session:revoke(Pid);\n           {error, _} -> ok\n         end,\n         identity_registry:deregister(SessReg, SessionId),\n         From ! {identity_reply, ok},\n         loop(TokReg, SessReg, Audit, Members, NextId);\n       {logout_all, Subject, From} ->\n         case identity_registry:sessions_for(SessReg, Subject) of\n           {ok, Ids} -> logout_each(SessReg, Audit, Ids)\n         end,\n         From ! {identity_reply, ok},\n         loop(TokReg, SessReg, Audit, Members, NextId);\n       {sessions, Subject, From} ->\n         case identity_registry:sessions_for(SessReg, Subject) of\n           {ok, Ids} -> From ! {identity_reply, Ids}\n         end,\n         loop(TokReg, SessReg, Audit, Members, NextId);\n       {grants, Subject, From} ->\n         From ! {identity_reply, identity_tokens:grants_for(TokReg, Subject)},\n         loop(TokReg, SessReg, Audit, Members, NextId);\n       {revoke_app, Subject, Client, From} ->\n         identity_tokens:revoke_app(TokReg, Subject, Client),\n         From ! {identity_reply, ok},\n         loop(TokReg, SessReg, Audit, Members, 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, Audit, Members, NextId);\n       {history, Subject, From} ->\n         From ! {identity_reply, identity_audit:actions(Audit, Subject)},\n         loop(TokReg, SessReg, Audit, Members, NextId);\n       {enroll, Subject, Tier, From} ->\n         identity_membership:request(Members, Subject, Tier),\n         identity_membership:approve(Members, Subject),\n         From ! {identity_reply, ok},\n         loop(TokReg, SessReg, Audit, Members, NextId);\n       {member_status, Subject, From} ->\n         From ! {identity_reply, identity_membership:status(Members, Subject)},\n         loop(TokReg, SessReg, Audit, Members, NextId);\n       {member_project, Subject, App, From} ->\n         From ! {identity_reply, identity_membership:project(Members, Subject, App)},\n         loop(TokReg, SessReg, Audit, Members, NextId);\n       {session_expired, SessionId} ->\n         identity_registry:deregister(SessReg, SessionId),\n         loop(TokReg, SessReg, Audit, Members, NextId)\n     end.\n\n   logout_each(_, _, []) -> ok;\n   logout_each(SessReg, Audit, [Sid | Rest]) ->\n     case identity_registry:whereis_session(SessReg, Sid) of\n       {ok, Pid} ->\n         audit_logout(Audit, Pid),\n         identity_session:revoke(Pid);\n       {error, _} -> ok\n     end,\n     identity_registry:deregister(SessReg, Sid),\n     logout_each(SessReg, Audit, Rest).\n\n   audit_logout(Audit, Pid) ->\n     case identity_session:lookup(Pid) of\n       {ok, {_, Subject, _, _}} -> identity_audit:record(Audit, Subject, logout);\n       {error, _} -> ok\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-audit!)
    (identity-load-membership!)
    (identity-load-api!)))
