@pattern-js/mod-identity 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +53 -0
  3. package/dist/admin-routes.d.ts +30 -0
  4. package/dist/admin-routes.d.ts.map +1 -0
  5. package/dist/admin-routes.js +52 -0
  6. package/dist/admin-routes.js.map +1 -0
  7. package/dist/auth-provider.d.ts +16 -0
  8. package/dist/auth-provider.d.ts.map +1 -0
  9. package/dist/auth-provider.js +47 -0
  10. package/dist/auth-provider.js.map +1 -0
  11. package/dist/cookies.d.ts +19 -0
  12. package/dist/cookies.d.ts.map +1 -0
  13. package/dist/cookies.js +44 -0
  14. package/dist/cookies.js.map +1 -0
  15. package/dist/csrf.d.ts +19 -0
  16. package/dist/csrf.d.ts.map +1 -0
  17. package/dist/csrf.js +39 -0
  18. package/dist/csrf.js.map +1 -0
  19. package/dist/deliver.d.ts +28 -0
  20. package/dist/deliver.d.ts.map +1 -0
  21. package/dist/deliver.js +36 -0
  22. package/dist/deliver.js.map +1 -0
  23. package/dist/frontend.d.ts +10 -0
  24. package/dist/frontend.d.ts.map +1 -0
  25. package/dist/frontend.js +139 -0
  26. package/dist/frontend.js.map +1 -0
  27. package/dist/index.d.ts +23 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +23 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/mod.d.ts +21 -0
  32. package/dist/mod.d.ts.map +1 -0
  33. package/dist/mod.js +97 -0
  34. package/dist/mod.js.map +1 -0
  35. package/dist/ops.d.ts +26 -0
  36. package/dist/ops.d.ts.map +1 -0
  37. package/dist/ops.js +379 -0
  38. package/dist/ops.js.map +1 -0
  39. package/dist/options.d.ts +53 -0
  40. package/dist/options.d.ts.map +1 -0
  41. package/dist/options.js +22 -0
  42. package/dist/options.js.map +1 -0
  43. package/dist/pages/bootstrap.d.ts +13 -0
  44. package/dist/pages/bootstrap.d.ts.map +1 -0
  45. package/dist/pages/bootstrap.js +23 -0
  46. package/dist/pages/bootstrap.js.map +1 -0
  47. package/dist/pages/html.d.ts +16 -0
  48. package/dist/pages/html.d.ts.map +1 -0
  49. package/dist/pages/html.js +72 -0
  50. package/dist/pages/html.js.map +1 -0
  51. package/dist/pages/login.d.ts +17 -0
  52. package/dist/pages/login.d.ts.map +1 -0
  53. package/dist/pages/login.js +43 -0
  54. package/dist/pages/login.js.map +1 -0
  55. package/dist/pages/welcome.d.ts +13 -0
  56. package/dist/pages/welcome.d.ts.map +1 -0
  57. package/dist/pages/welcome.js +19 -0
  58. package/dist/pages/welcome.js.map +1 -0
  59. package/dist/service-key.d.ts +12 -0
  60. package/dist/service-key.d.ts.map +1 -0
  61. package/dist/service-key.js +16 -0
  62. package/dist/service-key.js.map +1 -0
  63. package/dist/service.d.ts +133 -0
  64. package/dist/service.d.ts.map +1 -0
  65. package/dist/service.js +208 -0
  66. package/dist/service.js.map +1 -0
  67. package/dist/store/memory.d.ts +11 -0
  68. package/dist/store/memory.d.ts.map +1 -0
  69. package/dist/store/memory.js +0 -0
  70. package/dist/store/memory.js.map +1 -0
  71. package/dist/store/migrations.d.ts +21 -0
  72. package/dist/store/migrations.d.ts.map +1 -0
  73. package/dist/store/migrations.js +87 -0
  74. package/dist/store/migrations.js.map +1 -0
  75. package/dist/store/mutex.d.ts +13 -0
  76. package/dist/store/mutex.d.ts.map +1 -0
  77. package/dist/store/mutex.js +28 -0
  78. package/dist/store/mutex.js.map +1 -0
  79. package/dist/store/sqlite.d.ts +20 -0
  80. package/dist/store/sqlite.d.ts.map +1 -0
  81. package/dist/store/sqlite.js +254 -0
  82. package/dist/store/sqlite.js.map +1 -0
  83. package/dist/store/types.d.ts +135 -0
  84. package/dist/store/types.d.ts.map +1 -0
  85. package/dist/store/types.js +23 -0
  86. package/dist/store/types.js.map +1 -0
  87. package/dist/tokens.d.ts +16 -0
  88. package/dist/tokens.d.ts.map +1 -0
  89. package/dist/tokens.js +25 -0
  90. package/dist/tokens.js.map +1 -0
  91. package/dist/workflows.d.ts +14 -0
  92. package/dist/workflows.d.ts.map +1 -0
  93. package/dist/workflows.js +96 -0
  94. package/dist/workflows.js.map +1 -0
  95. package/docs/index.md +162 -0
  96. package/docs/ops/identity.bootstrap.page.md +5 -0
  97. package/docs/ops/identity.bootstrap.submit.md +5 -0
  98. package/docs/ops/identity.login.page.md +5 -0
  99. package/docs/ops/identity.logout.md +4 -0
  100. package/docs/ops/identity.sessions.list.md +5 -0
  101. package/docs/ops/identity.sessions.revoke.md +4 -0
  102. package/docs/ops/identity.settings.get.md +4 -0
  103. package/docs/ops/identity.settings.set.md +5 -0
  104. package/docs/ops/identity.token.callback.md +7 -0
  105. package/docs/ops/identity.users.get.md +5 -0
  106. package/docs/ops/identity.users.invite.md +7 -0
  107. package/docs/ops/identity.users.list.md +5 -0
  108. package/docs/ops/identity.users.loginLink.md +5 -0
  109. package/docs/ops/identity.users.revokeSessions.md +5 -0
  110. package/docs/ops/identity.users.runStats.md +7 -0
  111. package/docs/ops/identity.users.setRoles.md +5 -0
  112. package/docs/ops/identity.users.toggleDisabled.md +5 -0
  113. package/docs/ops/identity.welcome.page.md +5 -0
  114. package/docs/ops/identity.whoami.md +6 -0
  115. package/package.json +45 -0
@@ -0,0 +1,53 @@
1
+ /**
2
+ * @pattern-js/mod-identity — options & defaults.
3
+ *
4
+ * Sensible defaults so `pattern.config.json` can list the mod as a bare
5
+ * string; custom setups use a local wrapper mod exporting `identityMod({...})`
6
+ * (same convention as `adminMod`).
7
+ */
8
+ export interface IdentityOptions {
9
+ /** URL prefix for the auth routes (login, token callback, …). Default "/auth". */
10
+ mount?: string;
11
+ /** Session cookie name. Default "pattern_session". */
12
+ cookieName?: string;
13
+ /**
14
+ * SQLite database path, or "memory" for the in-process store. Default
15
+ * "./.pattern-data/identity.db" — gitignored; NEVER `.pattern/`, which is
16
+ * committed workflow storage and must not hold PII.
17
+ */
18
+ storage?: string;
19
+ /**
20
+ * Role → scopes map, compiled onto the principal at session resolution
21
+ * (so edits apply on the next request). Default { admin: ["admin"] }.
22
+ */
23
+ roles?: Record<string, string[]>;
24
+ /** Who may sign themselves up. Default "invite" (admin invites; closed). */
25
+ signup?: "open" | "invite";
26
+ /** Sliding session lifetime in ms. Default 30 days. */
27
+ sessionTtlMs?: number;
28
+ /** Min interval between sliding-touch writes per session. Default 60s. */
29
+ touchThrottleMs?: number;
30
+ /** Single-use token lifetime in ms. Default 15 minutes. */
31
+ tokenTtlMs?: number;
32
+ /**
33
+ * Set the cookie's Secure flag. Default false so http://localhost logins
34
+ * work out of the box — turn it on behind TLS in production.
35
+ */
36
+ cookieSecure?: boolean;
37
+ /** Role(s) granted to the first (bootstrap) user. Default ["admin"]. */
38
+ bootstrapRoles?: string[];
39
+ }
40
+ export interface ResolvedIdentityOptions {
41
+ mount: string;
42
+ cookieName: string;
43
+ storage: string;
44
+ roles: Record<string, string[]>;
45
+ signup: "open" | "invite";
46
+ sessionTtlMs: number;
47
+ touchThrottleMs: number;
48
+ tokenTtlMs: number;
49
+ cookieSecure: boolean;
50
+ bootstrapRoles: string[];
51
+ }
52
+ export declare function resolveOptions(options?: IdentityOptions): ResolvedIdentityOptions;
53
+ //# sourceMappingURL=options.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,eAAe;IAC9B,kFAAkF;IAClF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sDAAsD;IACtD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACjC,4EAA4E;IAC5E,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC3B,uDAAuD;IACvD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,0EAA0E;IAC1E,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,2DAA2D;IAC3D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,wEAAwE;IACxE,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAChC,MAAM,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,OAAO,CAAC;IACtB,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED,wBAAgB,cAAc,CAAC,OAAO,GAAE,eAAoB,GAAG,uBAAuB,CAarF"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @pattern-js/mod-identity — options & defaults.
3
+ *
4
+ * Sensible defaults so `pattern.config.json` can list the mod as a bare
5
+ * string; custom setups use a local wrapper mod exporting `identityMod({...})`
6
+ * (same convention as `adminMod`).
7
+ */
8
+ export function resolveOptions(options = {}) {
9
+ return {
10
+ mount: (options.mount ?? "/auth").replace(/\/$/, "") || "/auth",
11
+ cookieName: options.cookieName ?? "pattern_session",
12
+ storage: options.storage ?? "./.pattern-data/identity.db",
13
+ roles: options.roles ?? { admin: ["admin"] },
14
+ signup: options.signup ?? "invite",
15
+ sessionTtlMs: options.sessionTtlMs ?? 30 * 24 * 60 * 60 * 1000,
16
+ touchThrottleMs: options.touchThrottleMs ?? 60_000,
17
+ tokenTtlMs: options.tokenTtlMs ?? 15 * 60 * 1000,
18
+ cookieSecure: options.cookieSecure ?? false,
19
+ bootstrapRoles: options.bootstrapRoles ?? ["admin"],
20
+ };
21
+ }
22
+ //# sourceMappingURL=options.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"options.js","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAgDH,MAAM,UAAU,cAAc,CAAC,UAA2B,EAAE;IAC1D,OAAO;QACL,KAAK,EAAE,CAAC,OAAO,CAAC,KAAK,IAAI,OAAO,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,OAAO;QAC/D,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,iBAAiB;QACnD,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,6BAA6B;QACzD,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,EAAE,KAAK,EAAE,CAAC,OAAO,CAAC,EAAE;QAC5C,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,QAAQ;QAClC,YAAY,EAAE,OAAO,CAAC,YAAY,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI;QAC9D,eAAe,EAAE,OAAO,CAAC,eAAe,IAAI,MAAM;QAClD,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI;QAChD,YAAY,EAAE,OAAO,CAAC,YAAY,IAAI,KAAK;QAC3C,cAAc,EAAE,OAAO,CAAC,cAAc,IAAI,CAAC,OAAO,CAAC;KACpD,CAAC;AACJ,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @pattern-js/mod-identity — the bootstrap (first-admin) page.
3
+ *
4
+ * Reached via the one-time URL printed to the console on first boot. One
5
+ * email field; submitting consumes the bootstrap token and creates the
6
+ * first user with the bootstrap roles + a session.
7
+ */
8
+ export declare function renderBootstrapPage(opts: {
9
+ token: string;
10
+ mount: string;
11
+ error?: string;
12
+ }): string;
13
+ //# sourceMappingURL=bootstrap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bootstrap.d.ts","sourceRoot":"","sources":["../../src/pages/bootstrap.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAgBlG"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @pattern-js/mod-identity — the bootstrap (first-admin) page.
3
+ *
4
+ * Reached via the one-time URL printed to the console on first boot. One
5
+ * email field; submitting consumes the bootstrap token and creates the
6
+ * first user with the bootstrap roles + a session.
7
+ */
8
+ import { escapeHtml, errorBanner, layout } from "./html.js";
9
+ export function renderBootstrapPage(opts) {
10
+ return layout("Welcome to Pattern", `<h1>Create the first account</h1>
11
+ <p>This one-time link sets up the owner of this Pattern instance${opts.error ? "" : " — it becomes an admin"}.</p>
12
+ ${errorBanner(opts.error)}
13
+ <form method="post" action="${escapeHtml(opts.mount)}/bootstrap">
14
+ <label for="email">Your email</label>
15
+ <input id="email" name="email" type="email" required autocomplete="email" autofocus>
16
+ <label for="name">Name <span style="color:#5d6676">(optional)</span></label>
17
+ <input id="name" name="name" type="text" autocomplete="name">
18
+ <input type="hidden" name="t" value="${escapeHtml(opts.token)}">
19
+ <button type="submit">Create account &amp; sign in</button>
20
+ </form>
21
+ <p class="hint">Lost this link? Restart with an empty user store to mint a new one.</p>`);
22
+ }
23
+ //# sourceMappingURL=bootstrap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bootstrap.js","sourceRoot":"","sources":["../../src/pages/bootstrap.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAE5D,MAAM,UAAU,mBAAmB,CAAC,IAAsD;IACxF,OAAO,MAAM,CACX,oBAAoB,EACpB;kEAC8D,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,wBAAwB;EAC1G,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC;8BACK,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC;;;;;uCAKb,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC;;;wFAG2B,CACrF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * @pattern-js/mod-identity — hand-written HTML pages.
3
+ *
4
+ * No build step, no React: auth pages are tiny, must work before anything
5
+ * else does, and follow the admin placeholder's dark-glass aesthetic so the
6
+ * front door matches the house.
7
+ */
8
+ export declare function escapeHtml(s: string): string;
9
+ /**
10
+ * Only relative paths survive as a post-login destination — anything else
11
+ * (absolute URLs, protocol-relative `//`) is an open-redirect vector.
12
+ */
13
+ export declare function safeNextPath(next: unknown): string;
14
+ export declare function layout(title: string, body: string): string;
15
+ export declare function errorBanner(code: string | undefined): string;
16
+ //# sourceMappingURL=html.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../../src/pages/html.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAO5C;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAGlD;AAED,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAkC1D;AASD,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAI5D"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * @pattern-js/mod-identity — hand-written HTML pages.
3
+ *
4
+ * No build step, no React: auth pages are tiny, must work before anything
5
+ * else does, and follow the admin placeholder's dark-glass aesthetic so the
6
+ * front door matches the house.
7
+ */
8
+ export function escapeHtml(s) {
9
+ return s
10
+ .replaceAll("&", "&amp;")
11
+ .replaceAll("<", "&lt;")
12
+ .replaceAll(">", "&gt;")
13
+ .replaceAll('"', "&quot;")
14
+ .replaceAll("'", "&#39;");
15
+ }
16
+ /**
17
+ * Only relative paths survive as a post-login destination — anything else
18
+ * (absolute URLs, protocol-relative `//`) is an open-redirect vector.
19
+ */
20
+ export function safeNextPath(next) {
21
+ if (typeof next !== "string" || !next.startsWith("/") || next.startsWith("//"))
22
+ return "/";
23
+ return next;
24
+ }
25
+ export function layout(title, body) {
26
+ return `<!doctype html><html><head><meta charset="utf-8">
27
+ <meta name="viewport" content="width=device-width, initial-scale=1">
28
+ <title>${escapeHtml(title)}</title>
29
+ <style>
30
+ :root { color-scheme: dark; }
31
+ body { font: 16px/1.5 system-ui, sans-serif; margin: 0; min-height: 100vh;
32
+ display: grid; place-items: center; background: #0b0d12; color: #e6e9ef; }
33
+ .card { width: min(92vw, 420px); padding: 2rem 2.25rem; border-radius: 16px;
34
+ background: rgba(255,255,255,.04); border: 1px solid rgba(255,255,255,.08);
35
+ backdrop-filter: blur(12px); }
36
+ h1 { font-size: 1.25rem; margin: 0 0 .25rem; }
37
+ p { color: #9aa3b2; font-size: .9rem; margin: .25rem 0 1rem; }
38
+ label { display: block; font-size: .8rem; color: #9aa3b2; margin: .75rem 0 .25rem; }
39
+ input { width: 100%; box-sizing: border-box; padding: .6rem .75rem; border-radius: 10px;
40
+ border: 1px solid rgba(255,255,255,.12); background: rgba(0,0,0,.3);
41
+ color: #e6e9ef; font: inherit; }
42
+ input:focus { outline: none; border-color: #7cf; box-shadow: 0 0 0 2px rgba(119,204,255,.2); }
43
+ button, .btn { display: block; width: 100%; margin-top: 1rem; padding: .65rem .75rem;
44
+ border-radius: 10px; border: 1px solid rgba(119,204,255,.4); cursor: pointer;
45
+ background: rgba(119,204,255,.12); color: #7cf; font: inherit; font-weight: 600;
46
+ text-align: center; text-decoration: none; box-sizing: border-box; }
47
+ button:hover, .btn:hover { background: rgba(119,204,255,.2); }
48
+ .error { border: 1px solid rgba(255,99,132,.4); background: rgba(255,99,132,.08);
49
+ color: #ff8fa3; padding: .6rem .75rem; border-radius: 10px; font-size: .85rem;
50
+ margin-bottom: 1rem; }
51
+ .hint { font-size: .78rem; color: #5d6676; margin-top: 1.25rem; }
52
+ .brand { font-size: .75rem; letter-spacing: .14em; text-transform: uppercase;
53
+ color: #5d6676; margin-bottom: 1rem; }
54
+ .method + .method { border-top: 1px solid rgba(255,255,255,.08); margin-top: 1.25rem; padding-top: .5rem; }
55
+ </style></head>
56
+ <body><div class="card"><div class="brand">⌬ Pattern</div>
57
+ ${body}
58
+ </div></body></html>`;
59
+ }
60
+ const ERROR_MESSAGES = {
61
+ "invalid-token": "That link is invalid, expired, or was already used. Request a fresh one.",
62
+ "signup-closed": "Sign-ups are invite-only. Ask an admin to invite you.",
63
+ "account-disabled": "This account is disabled.",
64
+ "invalid-email": "That doesn't look like an email address.",
65
+ };
66
+ export function errorBanner(code) {
67
+ if (!code)
68
+ return "";
69
+ const message = ERROR_MESSAGES[code] ?? "Something went wrong. Try again.";
70
+ return `<div class="error">${escapeHtml(message)}</div>`;
71
+ }
72
+ //# sourceMappingURL=html.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html.js","sourceRoot":"","sources":["../../src/pages/html.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,UAAU,UAAU,CAAC,CAAS;IAClC,OAAO,CAAC;SACL,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC;SACxB,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC;SACvB,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC;SACvB,UAAU,CAAC,GAAG,EAAE,QAAQ,CAAC;SACzB,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AAC9B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,IAAa;IACxC,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,GAAG,CAAC;IAC3F,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,MAAM,CAAC,KAAa,EAAE,IAAY;IAChD,OAAO;;SAEA,UAAU,CAAC,KAAK,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA6BxB,IAAI;qBACe,CAAC;AACtB,CAAC;AAED,MAAM,cAAc,GAA2B;IAC7C,eAAe,EAAE,0EAA0E;IAC3F,eAAe,EAAE,uDAAuD;IACxE,kBAAkB,EAAE,2BAA2B;IAC/C,eAAe,EAAE,0CAA0C;CAC5D,CAAC;AAEF,MAAM,UAAU,WAAW,CAAC,IAAwB;IAClD,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,CAAC;IACrB,MAAM,OAAO,GAAG,cAAc,CAAC,IAAI,CAAC,IAAI,kCAAkC,CAAC;IAC3E,OAAO,sBAAsB,UAAU,CAAC,OAAO,CAAC,QAAQ,CAAC;AAC3D,CAAC"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @pattern-js/mod-identity — the login page.
3
+ *
4
+ * Rendered from the login-method registry, so it grows a section per
5
+ * provider mod (magic-link form, OIDC buttons, …) with zero page changes.
6
+ * `next` survives the round-trip as a hidden field / query param.
7
+ */
8
+ import type { LoginMethod } from "../service.js";
9
+ export declare function renderLoginPage(opts: {
10
+ methods: LoginMethod[];
11
+ next?: unknown;
12
+ error?: string;
13
+ notice?: string;
14
+ }): string;
15
+ /** Post-request page ("check your email / console"). */
16
+ export declare function renderSentPage(email: string): string;
17
+ //# sourceMappingURL=login.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/pages/login.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAGjD,wBAAgB,eAAe,CAAC,IAAI,EAAE;IACpC,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,MAAM,CAiBT;AAqBD,wDAAwD;AACxD,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOpD"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * @pattern-js/mod-identity — the login page.
3
+ *
4
+ * Rendered from the login-method registry, so it grows a section per
5
+ * provider mod (magic-link form, OIDC buttons, …) with zero page changes.
6
+ * `next` survives the round-trip as a hidden field / query param.
7
+ */
8
+ import { escapeHtml, errorBanner, layout, safeNextPath } from "./html.js";
9
+ export function renderLoginPage(opts) {
10
+ const next = safeNextPath(opts.next);
11
+ const sections = opts.methods.length
12
+ ? opts.methods.map((m) => renderMethod(m, next)).join("\n")
13
+ : `<p>No login methods are installed. Add one (e.g. <code>@pattern-js/mod-auth-magic-link</code>) to your mods.</p>`;
14
+ const notice = opts.notice ? `<p>${escapeHtml(opts.notice)}</p>` : "";
15
+ return layout("Sign in", `<h1>Sign in</h1>
16
+ <p>to continue${next !== "/" ? ` to <code>${escapeHtml(next)}</code>` : ""}</p>
17
+ ${errorBanner(opts.error)}
18
+ ${notice}
19
+ ${sections}`);
20
+ }
21
+ function renderMethod(method, next) {
22
+ if (method.kind === "redirect") {
23
+ const url = `${method.startUrl}${method.startUrl.includes("?") ? "&" : "?"}next=${encodeURIComponent(next)}`;
24
+ return `<div class="method"><a class="btn" href="${escapeHtml(url)}">${escapeHtml(method.label)}</a></div>`;
25
+ }
26
+ const fields = method.fields ?? [{ name: "email", label: "Email", type: "email" }];
27
+ const inputs = fields
28
+ .map((f) => `<label for="${escapeHtml(f.name)}">${escapeHtml(f.label)}</label>
29
+ <input id="${escapeHtml(f.name)}" name="${escapeHtml(f.name)}" type="${escapeHtml(f.type ?? "text")}" required autocomplete="${f.type === "email" ? "email" : "on"}">`)
30
+ .join("\n");
31
+ return `<div class="method"><form method="post" action="${escapeHtml(method.startUrl)}">
32
+ ${inputs}
33
+ <input type="hidden" name="next" value="${escapeHtml(next)}">
34
+ <button type="submit">${escapeHtml(method.label)}</button>
35
+ </form></div>`;
36
+ }
37
+ /** Post-request page ("check your email / console"). */
38
+ export function renderSentPage(email) {
39
+ return layout("Check your inbox", `<h1>Check your inbox</h1>
40
+ <p>If <strong>${escapeHtml(email)}</strong> has an account (or sign-ups are open), a sign-in link is on its way.</p>
41
+ <p class="hint">No email mod installed? The link was printed to the server console.</p>`);
42
+ }
43
+ //# sourceMappingURL=login.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/pages/login.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAE1E,MAAM,UAAU,eAAe,CAAC,IAK/B;IACC,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAErC,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM;QAClC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,YAAY,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;QAC3D,CAAC,CAAC,kHAAkH,CAAC;IAEvH,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IAEtE,OAAO,MAAM,CACX,SAAS,EACT;gBACY,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,aAAa,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE;EACxE,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC;EACvB,MAAM;EACN,QAAQ,EAAE,CACT,CAAC;AACJ,CAAC;AAED,SAAS,YAAY,CAAC,MAAmB,EAAE,IAAY;IACrD,IAAI,MAAM,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;QAC/B,MAAM,GAAG,GAAG,GAAG,MAAM,CAAC,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,QAAQ,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7G,OAAO,4CAA4C,UAAU,CAAC,GAAG,CAAC,KAAK,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC;IAC9G,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;IACnF,MAAM,MAAM,GAAG,MAAM;SAClB,GAAG,CACF,CAAC,CAAC,EAAE,EAAE,CAAC,eAAe,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC;aACzD,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,UAAU,CAAC,CAAC,CAAC,IAAI,IAAI,MAAM,CAAC,4BAA4B,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,IAAI,CACjK;SACA,IAAI,CAAC,IAAI,CAAC,CAAC;IACd,OAAO,mDAAmD,UAAU,CAAC,MAAM,CAAC,QAAQ,CAAC;EACrF,MAAM;0CACkC,UAAU,CAAC,IAAI,CAAC;wBAClC,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC;cAClC,CAAC;AACf,CAAC;AAED,wDAAwD;AACxD,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,OAAO,MAAM,CACX,kBAAkB,EAClB;gBACY,UAAU,CAAC,KAAK,CAAC;wFACuD,CACrF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @pattern-js/mod-identity — the post-login landing of last resort.
3
+ *
4
+ * Logins redirect to `next` → the app's advertised home (AUTH_HOME_URL — the
5
+ * admin registers its mount) → here. Exists so a headless app's first login
6
+ * never dead-ends on a 404: you see who you are and where to go next.
7
+ */
8
+ export declare function renderWelcomePage(opts: {
9
+ email: string;
10
+ name?: string | null;
11
+ mount: string;
12
+ }): string;
13
+ //# sourceMappingURL=welcome.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"welcome.d.ts","sourceRoot":"","sources":["../../src/pages/welcome.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAYtG"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @pattern-js/mod-identity — the post-login landing of last resort.
3
+ *
4
+ * Logins redirect to `next` → the app's advertised home (AUTH_HOME_URL — the
5
+ * admin registers its mount) → here. Exists so a headless app's first login
6
+ * never dead-ends on a 404: you see who you are and where to go next.
7
+ */
8
+ import { escapeHtml, layout } from "./html.js";
9
+ export function renderWelcomePage(opts) {
10
+ const who = opts.name ? `${opts.name} (${opts.email})` : opts.email;
11
+ return layout("Signed in", `<h1>You're in ✦</h1>
12
+ <p>Signed in as <strong>${escapeHtml(who)}</strong>.</p>
13
+ <p>This instance has no frontend registered, so there's nowhere to redirect you —
14
+ your session cookie is set and every protected route now answers to you.</p>
15
+ <form method="post" action="${escapeHtml(opts.mount)}/logout"><button type="submit">Sign out</button></form>
16
+ <p class="hint">Apps advertise a post-login home via the <code>AUTH_HOME_URL</code> service
17
+ (the admin mod does this automatically).</p>`);
18
+ }
19
+ //# sourceMappingURL=welcome.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"welcome.js","sourceRoot":"","sources":["../../src/pages/welcome.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAE/C,MAAM,UAAU,iBAAiB,CAAC,IAA4D;IAC5F,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC;IACpE,OAAO,MAAM,CACX,WAAW,EACX;0BACsB,UAAU,CAAC,GAAG,CAAC;;;8BAGX,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC;;6CAEP,CAC1C,CAAC;AACJ,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @pattern-js/mod-identity — service access for ops.
3
+ *
4
+ * The key itself (`IDENTITY_SERVICE`) lives in core so consumers that only
5
+ * need presence-detection (the admin's secure-by-default) never import this
6
+ * package. This module is the typed accessor for code that does.
7
+ */
8
+ import { IDENTITY_SERVICE, type OpContext } from "@pattern-js/core";
9
+ import type { IdentityService } from "./service.js";
10
+ export { IDENTITY_SERVICE };
11
+ export declare function identityService(ctx: OpContext): IdentityService;
12
+ //# sourceMappingURL=service-key.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-key.d.ts","sourceRoot":"","sources":["../src/service-key.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,gBAAgB,EAAE,KAAK,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACpE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEpD,OAAO,EAAE,gBAAgB,EAAE,CAAC;AAE5B,wBAAgB,eAAe,CAAC,GAAG,EAAE,SAAS,GAAG,eAAe,CAI/D"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * @pattern-js/mod-identity — service access for ops.
3
+ *
4
+ * The key itself (`IDENTITY_SERVICE`) lives in core so consumers that only
5
+ * need presence-detection (the admin's secure-by-default) never import this
6
+ * package. This module is the typed accessor for code that does.
7
+ */
8
+ import { IDENTITY_SERVICE } from "@pattern-js/core";
9
+ export { IDENTITY_SERVICE };
10
+ export function identityService(ctx) {
11
+ const svc = ctx.services[IDENTITY_SERVICE];
12
+ if (!svc)
13
+ throw new Error("identity service is not registered (install @pattern-js/mod-identity)");
14
+ return svc;
15
+ }
16
+ //# sourceMappingURL=service-key.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-key.js","sourceRoot":"","sources":["../src/service-key.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,gBAAgB,EAAkB,MAAM,kBAAkB,CAAC;AAGpE,OAAO,EAAE,gBAAgB,EAAE,CAAC;AAE5B,MAAM,UAAU,eAAe,CAAC,GAAc;IAC5C,MAAM,GAAG,GAAG,GAAG,CAAC,QAAQ,CAAC,gBAAgB,CAAgC,CAAC;IAC1E,IAAI,CAAC,GAAG;QAAE,MAAM,IAAI,KAAK,CAAC,uEAAuE,CAAC,CAAC;IACnG,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,133 @@
1
+ /**
2
+ * @pattern-js/mod-identity — the identity service (§9).
3
+ *
4
+ * Registered on the engine under core's `IDENTITY_SERVICE` key; consumed by
5
+ * this mod's ops, by provider mods (magic-link, oidc…) and by the admin's
6
+ * secure-by-default check. Bridges the two meanings of "auth provider":
7
+ * login methods prove who a human is (interactive flows, registered here);
8
+ * the session AuthProvider turns the resulting cookie back into a principal
9
+ * on every request.
10
+ *
11
+ * Scopes are compiled from roles **at resolve time**, so editing the role
12
+ * map applies on the next request — sessions store no scopes.
13
+ */
14
+ import type { ConnectionRegistry } from "@pattern-js/core";
15
+ import type { ResolvedIdentityOptions } from "./options.js";
16
+ import type { IdentityStores, SessionRow, TokenPurpose, TokenRow, UserRow } from "./store/types.js";
17
+ /** A way to log in, registered by provider mods in their `ready` hook. */
18
+ export interface LoginMethod {
19
+ id: string;
20
+ label: string;
21
+ /** "form" posts fields to startUrl; "redirect" navigates to startUrl. */
22
+ kind: "form" | "redirect";
23
+ startUrl: string;
24
+ /** Form field hint for the login page (default: a single email field). */
25
+ fields?: Array<{
26
+ name: string;
27
+ label: string;
28
+ type?: string;
29
+ }>;
30
+ }
31
+ export interface MintedSession {
32
+ sessionId: string;
33
+ /** The RAW cookie secret — only ever returned here, stored hashed. */
34
+ token: string;
35
+ expiresAt: number;
36
+ }
37
+ export interface ResolvedSession {
38
+ session: SessionRow;
39
+ user: UserRow;
40
+ /** Compiled from the user's roles via the role map. */
41
+ scopes: string[];
42
+ }
43
+ export interface IssuedToken {
44
+ /** The RAW single-use secret. */
45
+ token: string;
46
+ /** Path-only callback URL (`{mount}/token?t=…[&next=…]`); callers prepend an origin if needed. */
47
+ path: string;
48
+ expiresAt: number;
49
+ }
50
+ export interface FindOrCreateInput {
51
+ provider: string;
52
+ subject: string;
53
+ email: string;
54
+ name?: string;
55
+ /** True when arriving via invite/bootstrap token or open signup. */
56
+ allowCreate: boolean;
57
+ /** Roles granted on creation (invite/bootstrap). */
58
+ roles?: string[];
59
+ }
60
+ export interface IdentityService {
61
+ findOrCreateByIdentity(input: FindOrCreateInput): Promise<UserRow | null>;
62
+ getUser(id: string): Promise<UserRow | null>;
63
+ /** Lookup by (normalized) email — provider mods gate token issuance on this. */
64
+ findUserByEmail(email: string): Promise<UserRow | null>;
65
+ listUsers(): Promise<UserRow[]>;
66
+ /** CAS-retried. Revokes the user's sessions — privilege changes end sessions. */
67
+ setRoles(userId: string, roles: string[]): Promise<UserRow>;
68
+ /** CAS-retried. Disabling also revokes all sessions. */
69
+ setDisabled(userId: string, disabled: boolean): Promise<UserRow>;
70
+ mintSession(userId: string, meta?: {
71
+ userAgent?: string | null;
72
+ ip?: string | null;
73
+ }): Promise<MintedSession>;
74
+ resolveSessionByToken(rawToken: string, now?: number): Promise<ResolvedSession | null>;
75
+ revokeSession(sessionId: string): Promise<void>;
76
+ revokeAllForUser(userId: string): Promise<void>;
77
+ listSessions(userId?: string): Promise<SessionRow[]>;
78
+ issueToken(input: {
79
+ purpose: TokenPurpose;
80
+ email?: string;
81
+ ttlMs?: number;
82
+ data?: Record<string, unknown>;
83
+ }): Promise<IssuedToken>;
84
+ consumeToken(rawToken: string, purpose?: TokenPurpose): Promise<TokenRow | null>;
85
+ registerLoginMethod(method: LoginMethod): void;
86
+ loginMethods(): LoginMethod[];
87
+ scopesForRoles(roles: string[]): string[];
88
+ /** The EFFECTIVE signup policy — admin-toggleable at runtime. */
89
+ getSignup(): Promise<"open" | "invite">;
90
+ setSignup(mode: "open" | "invite"): Promise<void>;
91
+ readonly options: ResolvedIdentityOptions;
92
+ }
93
+ export declare class DefaultIdentityService implements IdentityService {
94
+ private readonly stores;
95
+ readonly options: ResolvedIdentityOptions;
96
+ /** The engine's connection registry — revocation closes live WS sockets. */
97
+ private readonly connections?;
98
+ private readonly methods;
99
+ private readonly mutex;
100
+ constructor(stores: IdentityStores, options: ResolvedIdentityOptions,
101
+ /** The engine's connection registry — revocation closes live WS sockets. */
102
+ connections?: ConnectionRegistry | undefined);
103
+ findOrCreateByIdentity(input: FindOrCreateInput): Promise<UserRow | null>;
104
+ getUser(id: string): Promise<UserRow | null>;
105
+ findUserByEmail(email: string): Promise<UserRow | null>;
106
+ listUsers(): Promise<UserRow[]>;
107
+ setRoles(userId: string, roles: string[]): Promise<UserRow>;
108
+ setDisabled(userId: string, disabled: boolean): Promise<UserRow>;
109
+ /** Read-modify-write with CAS retry under the per-user mutex. */
110
+ private casUpdateUser;
111
+ mintSession(userId: string, meta?: {
112
+ userAgent?: string | null;
113
+ ip?: string | null;
114
+ }): Promise<MintedSession>;
115
+ resolveSessionByToken(rawToken: string, now?: number): Promise<ResolvedSession | null>;
116
+ revokeSession(sessionId: string): Promise<void>;
117
+ revokeAllForUser(userId: string): Promise<void>;
118
+ private closeSessionSockets;
119
+ listSessions(userId?: string): Promise<SessionRow[]>;
120
+ issueToken(input: {
121
+ purpose: TokenPurpose;
122
+ email?: string;
123
+ ttlMs?: number;
124
+ data?: Record<string, unknown>;
125
+ }): Promise<IssuedToken>;
126
+ consumeToken(rawToken: string, purpose?: TokenPurpose): Promise<TokenRow | null>;
127
+ registerLoginMethod(method: LoginMethod): void;
128
+ loginMethods(): LoginMethod[];
129
+ scopesForRoles(roles: string[]): string[];
130
+ getSignup(): Promise<"open" | "invite">;
131
+ setSignup(mode: "open" | "invite"): Promise<void>;
132
+ }
133
+ //# sourceMappingURL=service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}