@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.
- package/LICENSE +21 -0
- package/README.md +53 -0
- package/dist/admin-routes.d.ts +30 -0
- package/dist/admin-routes.d.ts.map +1 -0
- package/dist/admin-routes.js +52 -0
- package/dist/admin-routes.js.map +1 -0
- package/dist/auth-provider.d.ts +16 -0
- package/dist/auth-provider.d.ts.map +1 -0
- package/dist/auth-provider.js +47 -0
- package/dist/auth-provider.js.map +1 -0
- package/dist/cookies.d.ts +19 -0
- package/dist/cookies.d.ts.map +1 -0
- package/dist/cookies.js +44 -0
- package/dist/cookies.js.map +1 -0
- package/dist/csrf.d.ts +19 -0
- package/dist/csrf.d.ts.map +1 -0
- package/dist/csrf.js +39 -0
- package/dist/csrf.js.map +1 -0
- package/dist/deliver.d.ts +28 -0
- package/dist/deliver.d.ts.map +1 -0
- package/dist/deliver.js +36 -0
- package/dist/deliver.js.map +1 -0
- package/dist/frontend.d.ts +10 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +139 -0
- package/dist/frontend.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/mod.d.ts +21 -0
- package/dist/mod.d.ts.map +1 -0
- package/dist/mod.js +97 -0
- package/dist/mod.js.map +1 -0
- package/dist/ops.d.ts +26 -0
- package/dist/ops.d.ts.map +1 -0
- package/dist/ops.js +379 -0
- package/dist/ops.js.map +1 -0
- package/dist/options.d.ts +53 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +22 -0
- package/dist/options.js.map +1 -0
- package/dist/pages/bootstrap.d.ts +13 -0
- package/dist/pages/bootstrap.d.ts.map +1 -0
- package/dist/pages/bootstrap.js +23 -0
- package/dist/pages/bootstrap.js.map +1 -0
- package/dist/pages/html.d.ts +16 -0
- package/dist/pages/html.d.ts.map +1 -0
- package/dist/pages/html.js +72 -0
- package/dist/pages/html.js.map +1 -0
- package/dist/pages/login.d.ts +17 -0
- package/dist/pages/login.d.ts.map +1 -0
- package/dist/pages/login.js +43 -0
- package/dist/pages/login.js.map +1 -0
- package/dist/pages/welcome.d.ts +13 -0
- package/dist/pages/welcome.d.ts.map +1 -0
- package/dist/pages/welcome.js +19 -0
- package/dist/pages/welcome.js.map +1 -0
- package/dist/service-key.d.ts +12 -0
- package/dist/service-key.d.ts.map +1 -0
- package/dist/service-key.js +16 -0
- package/dist/service-key.js.map +1 -0
- package/dist/service.d.ts +133 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +208 -0
- package/dist/service.js.map +1 -0
- package/dist/store/memory.d.ts +11 -0
- package/dist/store/memory.d.ts.map +1 -0
- package/dist/store/memory.js +0 -0
- package/dist/store/memory.js.map +1 -0
- package/dist/store/migrations.d.ts +21 -0
- package/dist/store/migrations.d.ts.map +1 -0
- package/dist/store/migrations.js +87 -0
- package/dist/store/migrations.js.map +1 -0
- package/dist/store/mutex.d.ts +13 -0
- package/dist/store/mutex.d.ts.map +1 -0
- package/dist/store/mutex.js +28 -0
- package/dist/store/mutex.js.map +1 -0
- package/dist/store/sqlite.d.ts +20 -0
- package/dist/store/sqlite.d.ts.map +1 -0
- package/dist/store/sqlite.js +254 -0
- package/dist/store/sqlite.js.map +1 -0
- package/dist/store/types.d.ts +135 -0
- package/dist/store/types.d.ts.map +1 -0
- package/dist/store/types.js +23 -0
- package/dist/store/types.js.map +1 -0
- package/dist/tokens.d.ts +16 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/tokens.js +25 -0
- package/dist/tokens.js.map +1 -0
- package/dist/workflows.d.ts +14 -0
- package/dist/workflows.d.ts.map +1 -0
- package/dist/workflows.js +96 -0
- package/dist/workflows.js.map +1 -0
- package/docs/index.md +162 -0
- package/docs/ops/identity.bootstrap.page.md +5 -0
- package/docs/ops/identity.bootstrap.submit.md +5 -0
- package/docs/ops/identity.login.page.md +5 -0
- package/docs/ops/identity.logout.md +4 -0
- package/docs/ops/identity.sessions.list.md +5 -0
- package/docs/ops/identity.sessions.revoke.md +4 -0
- package/docs/ops/identity.settings.get.md +4 -0
- package/docs/ops/identity.settings.set.md +5 -0
- package/docs/ops/identity.token.callback.md +7 -0
- package/docs/ops/identity.users.get.md +5 -0
- package/docs/ops/identity.users.invite.md +7 -0
- package/docs/ops/identity.users.list.md +5 -0
- package/docs/ops/identity.users.loginLink.md +5 -0
- package/docs/ops/identity.users.revokeSessions.md +5 -0
- package/docs/ops/identity.users.runStats.md +7 -0
- package/docs/ops/identity.users.setRoles.md +5 -0
- package/docs/ops/identity.users.toggleDisabled.md +5 -0
- package/docs/ops/identity.welcome.page.md +5 -0
- package/docs/ops/identity.whoami.md +6 -0
- package/package.json +45 -0
package/docs/index.md
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# Identity
|
|
2
|
+
|
|
3
|
+
`@pattern-js/mod-identity` is the identity kernel: users, revocable cookie
|
|
4
|
+
sessions, roles→scopes, a single-use token system, the login page, and the
|
|
5
|
+
admin's Access screens (Users / Invite / Sessions). Login *methods* are
|
|
6
|
+
separate mods that plug into it — `@pattern-js/mod-auth-magic-link` is the
|
|
7
|
+
reference.
|
|
8
|
+
|
|
9
|
+
```jsonc
|
|
10
|
+
{ "mods": ["@pattern-js/mod-identity", "@pattern-js/mod-auth-magic-link"] }
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## When to use it
|
|
14
|
+
|
|
15
|
+
Install it the moment you want **users, sessions, or roles** — a login page,
|
|
16
|
+
who-is-this-request, gated routes. It is opt-in: a project without it behaves
|
|
17
|
+
exactly as before. But installing it alongside `@pattern-js/mod-admin` **flips the
|
|
18
|
+
admin to secure-by-default** — `/admin` (API + SPA) starts requiring the
|
|
19
|
+
`admin` scope, and a logged-out browser is 302'd to the login page. Pass
|
|
20
|
+
`adminMod({ auth: false })` to keep the admin open anyway. If you only need a
|
|
21
|
+
machine-to-machine API key, you don't need this mod; reach for it when a
|
|
22
|
+
*human* has to prove who they are.
|
|
23
|
+
|
|
24
|
+
## Roles → scopes
|
|
25
|
+
|
|
26
|
+
Users carry **roles** (strings); the `roles` map compiles them into **scopes**
|
|
27
|
+
on the principal at **resolve time** — so editing the map applies on the *next*
|
|
28
|
+
request, and sessions store no scopes of their own. The default map is
|
|
29
|
+
`{ admin: ["admin"] }`. Adding application scopes is *just this map*:
|
|
30
|
+
|
|
31
|
+
```js
|
|
32
|
+
// mods/identity.mjs — a local wrapper mod
|
|
33
|
+
import { identityMod } from "@pattern-js/mod-identity";
|
|
34
|
+
export default identityMod({
|
|
35
|
+
roles: { admin: ["admin"], editor: ["edit", "read"], viewer: ["read"] },
|
|
36
|
+
});
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Now a user with role `editor` resolves to scopes `["edit","read"]`; gate a
|
|
40
|
+
route with `requireAuth: { scopes: ["edit"] }` and both `admin` (if you also
|
|
41
|
+
grant it `edit`) and `editor` users get through, `viewer` does not. The same
|
|
42
|
+
roles surface on the admin Users screen and are settable there
|
|
43
|
+
(`identity.users.setRoles` — which **ends the user's sessions**, because a
|
|
44
|
+
privilege change must re-login).
|
|
45
|
+
|
|
46
|
+
## Bootstrap on first boot
|
|
47
|
+
|
|
48
|
+
With an empty user store, the mod's `ready` hook mints a one-time **bootstrap
|
|
49
|
+
token** (valid 24h) and prints a setup link to the **server console**:
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
[pattern] ◆ No users yet — create the first admin with this one-time link (valid 24h):
|
|
53
|
+
[pattern] http://localhost:3000/auth/bootstrap?t=…
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Open it: `GET /auth/bootstrap?t=…` renders a form, the `POST` creates the first
|
|
57
|
+
user (roles `["admin"]` by default — `bootstrapRoles`) and signs them in. It's
|
|
58
|
+
the *same single-use-token primitive* as login and invites, so there's no
|
|
59
|
+
special-case admin password anywhere.
|
|
60
|
+
|
|
61
|
+
## Sessions
|
|
62
|
+
|
|
63
|
+
Opaque 256-bit secrets in an `HttpOnly; SameSite=Lax` cookie (`pattern_session`
|
|
64
|
+
by default); only the sha256 is stored. Sliding 30-day expiry with throttled
|
|
65
|
+
touches. Sessions are **revocable** — individually (`identity.sessions.revoke`),
|
|
66
|
+
per user (`identity.users.revokeSessions`), and automatically when a role
|
|
67
|
+
changes or a user is disabled. Set `cookieSecure: true` behind TLS in
|
|
68
|
+
production. CSRF protection lives *inside* the session provider: a cross-site
|
|
69
|
+
state-changing request simply doesn't authenticate (so it 401s with no token
|
|
70
|
+
machinery), while GETs always pass — the magic-link callback is a cross-site
|
|
71
|
+
top-level GET by nature.
|
|
72
|
+
|
|
73
|
+
## The `user` port
|
|
74
|
+
|
|
75
|
+
Host-bound triggers (`boundary.http.request`, `boundary.ws.*`) expose a `user`
|
|
76
|
+
output port — `{ id, provider, email?, name?, scopes, claims } | null` — so
|
|
77
|
+
the caller's identity is an *edge you can see*, not ambient context. Wire it
|
|
78
|
+
like any value:
|
|
79
|
+
|
|
80
|
+
```jsonc
|
|
81
|
+
{ "from": { "node": "in", "port": "user" }, "to": { "node": "listNotes", "port": "owner" } }
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
For an op deeper in the graph, `identity.whoami` and `ctx.principal` cover the
|
|
85
|
+
same ground.
|
|
86
|
+
|
|
87
|
+
## Gating a route with `requireAuth`
|
|
88
|
+
|
|
89
|
+
Enforcement is the **trigger's** job, on its config — core is untouched. Stamp
|
|
90
|
+
`requireAuth` on a `boundary.http.request` (or `boundary.ws.*`):
|
|
91
|
+
|
|
92
|
+
```jsonc
|
|
93
|
+
{ "op": "boundary.http.request",
|
|
94
|
+
"config": { "method": "POST", "path": "/api/publish",
|
|
95
|
+
"requireAuth": { "scopes": ["edit"] } } }
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Forms: `true` (any signed-in user), `{ "scopes": [...] }` (needs all listed
|
|
99
|
+
scopes), or the env-deferred `{ "env": "MY_SWITCH" }` (read per request, so you
|
|
100
|
+
can flip a surface open/closed without redeploying). The admin's own Access
|
|
101
|
+
routes stamp `{ scopes: ["admin"] }` on the boundary; the underlying
|
|
102
|
+
`identity.*` ops stay pure and are tagged `sensitivity: "privileged"`, so the
|
|
103
|
+
validator flags any route that exposes them without a gate.
|
|
104
|
+
|
|
105
|
+
## WebSockets
|
|
106
|
+
|
|
107
|
+
Auth happens **at upgrade** — the same cookie resolves the principal, the
|
|
108
|
+
trigger's `requireAuth` is enforced before the socket is accepted, and the
|
|
109
|
+
principal is fixed for the connection. Authenticated sockets auto-join two
|
|
110
|
+
rooms:
|
|
111
|
+
|
|
112
|
+
- `user:{id}` — broadcast to all of a user's devices,
|
|
113
|
+
- `session:{sessionId}` — **revoking the session closes its sockets** (code 4001).
|
|
114
|
+
|
|
115
|
+
## Tokens & delivery
|
|
116
|
+
|
|
117
|
+
Magic links, invites, and the bootstrap link all print to the **server
|
|
118
|
+
console** until you register a delivery workflow on the `identity.deliverToken`
|
|
119
|
+
hook (`payload: { email, url, purpose, delivered }`) — send the link by
|
|
120
|
+
email/SMS/chat and return `delivered: true`. No subscriber (or
|
|
121
|
+
`delivered: false`) falls back to the console, which **is** the zero-config dev
|
|
122
|
+
login. Tokens are single-use, short-TTL (15 min; invites 7 days), sha256 at
|
|
123
|
+
rest, consumed via CAS so replays fail closed. Purposes: `login`, `invite`
|
|
124
|
+
(carries roles), `bootstrap`. The `/auth/token` callback turns a consumed token
|
|
125
|
+
into a user + session per the **signup policy** — `invite` (default; unknown
|
|
126
|
+
emails refused) or `open` — a runtime setting toggled on the admin's Settings
|
|
127
|
+
page (the mod option only seeds it).
|
|
128
|
+
|
|
129
|
+
## Minimal config
|
|
130
|
+
|
|
131
|
+
Defaults work from the bare `"@pattern-js/mod-identity"` config entry. To
|
|
132
|
+
customize, export a local wrapper mod:
|
|
133
|
+
|
|
134
|
+
```js
|
|
135
|
+
// mods/identity.mjs
|
|
136
|
+
import { identityMod } from "@pattern-js/mod-identity";
|
|
137
|
+
export default identityMod({
|
|
138
|
+
signup: "open", // default "invite"
|
|
139
|
+
roles: { admin: ["admin"], editor: ["edit", "read"] },
|
|
140
|
+
cookieSecure: true, // REQUIRED behind TLS
|
|
141
|
+
storage: "./.pattern-data/identity.db", // or "memory"
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
`storage` is `node:sqlite` at `./.pattern-data/identity.db` by default —
|
|
146
|
+
**gitignored**; never put identity data in `.pattern/`, which is committed
|
|
147
|
+
workflow storage.
|
|
148
|
+
|
|
149
|
+
## Routes
|
|
150
|
+
|
|
151
|
+
| Route | What |
|
|
152
|
+
| --- | --- |
|
|
153
|
+
| `GET /auth/login` | Login page (a section per registered method) |
|
|
154
|
+
| `GET /auth/token?t=…` | Token callback → session cookie → redirect `next` |
|
|
155
|
+
| `POST /auth/logout` | Revoke the current session, clear the cookie |
|
|
156
|
+
| `GET /auth/whoami` | The current principal (JSON) |
|
|
157
|
+
| `GET /auth/welcome` | Post-login landing when no home is advertised |
|
|
158
|
+
| `GET/POST /auth/bootstrap` | First-admin setup (the one-time link) |
|
|
159
|
+
|
|
160
|
+
All public by design — the privileged surface is ops (`identity.users.*`,
|
|
161
|
+
`identity.sessions.*`, `identity.settings.*`), reached through their own
|
|
162
|
+
admin-scope-stamped routes under `/admin/api/identity/*`.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
Render the first-admin setup form, reached via the one-time link printed to the
|
|
2
|
+
console on first boot when the user store is empty. The GET half of a two-step
|
|
3
|
+
flow (this renders, `identity.bootstrap.submit` creates) — deliberately not a
|
|
4
|
+
one-click GET. An HTTP-shaped op backing `GET /auth/bootstrap`; a missing `t`
|
|
5
|
+
redirects to login. You never trigger this manually — boot prints the link.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
Consume the bootstrap token, create the first user with the bootstrap roles
|
|
2
|
+
(`["admin"]` by default), and sign them in. The POST half of the first-boot
|
|
3
|
+
flow backing `POST /auth/bootstrap`; it accepts a `bootstrap`-purpose token
|
|
4
|
+
only (never an upgraded login token), so it works exactly once on an empty
|
|
5
|
+
store. Re-renders the form with an error on a bad email.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
Render the login page, one section per login method registered by provider mods
|
|
2
|
+
(e.g. magic-link) in their `ready` hook — so an identity install with no
|
|
3
|
+
provider shows an empty page. An HTTP-shaped op: it emits `{ body, status }`,
|
|
4
|
+
not JSON. Backs `GET /auth/login`; you rarely wire it by hand. The `sent` and
|
|
5
|
+
`error` query params drive the post-submit and error states.
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
Revoke the current session (from `ctx.principal`'s `sessionId`) and clear the
|
|
2
|
+
cookie, then redirect to the login page. Backs `POST /auth/logout` — a POST, so
|
|
3
|
+
the CSRF guard makes forged cross-site logouts inert. Anonymous callers just
|
|
4
|
+
get the redirect (nothing to revoke).
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
List sessions newest-first with user email and a derived status
|
|
2
|
+
(active/expired/revoked); pass `userId` to scope to one user, omit it for all.
|
|
3
|
+
Backs the admin Sessions screen and a user's session sub-list. Privileged —
|
|
4
|
+
gate the trigger with the `admin` scope; revoke a row with
|
|
5
|
+
`identity.sessions.revoke`.
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
Read the effective signup policy (`"open"` or `"invite"`) — the runtime
|
|
2
|
+
setting, not the construction-time mod option, so it reflects whatever was last
|
|
3
|
+
toggled. Pairs with `identity.settings.set`; backs the admin Settings section.
|
|
4
|
+
Privileged — gate the trigger with the `admin` scope.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
Switch the signup policy between `"open"` and `"invite"` — persisted, so it
|
|
2
|
+
survives restarts and **overrides** the `signup` mod option (which only seeds
|
|
3
|
+
the initial value). This is the live switch that governs whether unknown emails
|
|
4
|
+
can self-register through `/auth/token` and `auth.magiclink.request`.
|
|
5
|
+
Privileged — gate the trigger with the `admin` scope.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
The security heart of login: consume a single-use `login`/`invite` token,
|
|
2
|
+
find-or-create the user per the **effective** signup policy, mint a session,
|
|
3
|
+
and redirect to `next` with the cookie set. An HTTP-shaped op backing
|
|
4
|
+
`GET /auth/token` — provider mods (magic-link, OIDC) issue tokens whose links
|
|
5
|
+
land here; they never mint sessions themselves. Bootstrap tokens are refused
|
|
6
|
+
here (they have their own flow), and a closed-signup unknown email redirects to
|
|
7
|
+
the login page with an error rather than creating an account.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
One user's profile shaped for the admin details page — email, name, roles, the
|
|
2
|
+
**resolved scopes** (via the role→scopes map), disabled flag, created time, and
|
|
3
|
+
active-session count. Use this for a single user; `identity.users.list` for the
|
|
4
|
+
table. Privileged — gate the trigger with the `admin` scope; throws if the user
|
|
5
|
+
isn't found.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Invite a user by email: mints a 7-day `invite` token (carrying the given
|
|
2
|
+
roles) and delivers it via the `identity.deliverToken` hook. Use this — not
|
|
3
|
+
`auth.magiclink.request` — when signup is `invite` (the default) and you need
|
|
4
|
+
to onboard someone who can't sign themselves up. When no delivery channel is
|
|
5
|
+
wired the result carries the link as `copy` (a copyable field) for the admin to
|
|
6
|
+
hand over manually; `roles` accepts an array or a comma string. Privileged —
|
|
7
|
+
gate the trigger with the `admin` scope.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
List every user with roles flattened to a comma string for table rendering —
|
|
2
|
+
backs the admin Users screen. Privileged and pure: it never checks scopes
|
|
3
|
+
itself, so a route or workflow wiring it MUST stamp `requireAuth: { scopes:
|
|
4
|
+
["admin"] }` on the trigger (the validator flags it otherwise). For one user's
|
|
5
|
+
detail use `identity.users.get`.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
Mint a single-use sign-in link for an existing user and return it as `copy`
|
|
2
|
+
(a copyable field) — for handing over manually when no email/SMS delivery is
|
|
3
|
+
wired. Use it to sign in an *existing* user; for someone new under invite-only
|
|
4
|
+
signup use `identity.users.invite` instead. Throws if the user is disabled
|
|
5
|
+
(re-enable first). Privileged — gate the trigger with the `admin` scope.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
Revoke every session of one user — logs them out on all devices and closes
|
|
2
|
+
their live WS sockets (code 4001) — without disabling the account, so they can
|
|
3
|
+
sign back in immediately. Use it for "sign out everywhere"; to also bar
|
|
4
|
+
re-entry use `identity.users.toggleDisabled`. Privileged — gate the trigger
|
|
5
|
+
with the `admin` scope.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Per-workflow run counts (runs, errors, avg ms, last run) for one user, read
|
|
2
|
+
from the admin's trace sink. Feature-detected: it returns an empty list when
|
|
3
|
+
`@pattern-js/mod-admin` isn't installed, and only covers the sink's **retained
|
|
4
|
+
window** (a bounded ring buffer), not all-time history — so treat the numbers
|
|
5
|
+
as recent-activity, not an audit log. Admin plumbing routes (`__`-prefixed,
|
|
6
|
+
`*.route.admin.*`) are filtered out. Privileged — gate the trigger with the
|
|
7
|
+
`admin` scope.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
Replace a user's roles, then **revoke all their sessions** — a privilege change
|
|
2
|
+
forces a re-login, so don't reach for this on a hot path expecting the user to
|
|
3
|
+
stay signed in. New scopes take effect from the role→scopes map on their next
|
|
4
|
+
session. `roles` accepts an array or a comma string; privileged — gate the
|
|
5
|
+
trigger with the `admin` scope.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
Flip a user between disabled and enabled — disabling also revokes all their
|
|
2
|
+
sessions and closes their sockets, so it's the lock-out switch. It reads the
|
|
3
|
+
current state and inverts it (no explicit on/off arg), so two concurrent calls
|
|
4
|
+
cancel out. Privileged — gate the trigger with the `admin` scope. A disabled
|
|
5
|
+
user can't be issued a login link until re-enabled.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
The post-login landing of last resort — shown only when no app advertised a
|
|
2
|
+
home under core's `AUTH_HOME_URL` (the admin registers its mount there). Renders
|
|
3
|
+
who-you-are plus a sign-out button; an HTTP-shaped op backing `GET
|
|
4
|
+
/auth/welcome` that redirects anonymous callers to the login page. If you have
|
|
5
|
+
an app, advertise its home and users never see this.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
Report the calling principal — `{ kind: "anonymous" }` when unauthenticated,
|
|
2
|
+
else `{ kind: "user", id, email, name, roles, scopes, sessionId }`. Pure and
|
|
3
|
+
unprivileged (it only ever sees the caller's own principal), so wire it into
|
|
4
|
+
any graph that needs to know who's running; for a host trigger prefer the
|
|
5
|
+
`user` port, and reach for this when the answer is needed deeper in the
|
|
6
|
+
workflow.
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pattern-js/mod-identity",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Users, sessions, roles and pluggable login methods for Pattern — the optional identity brick. Opaque revocable cookie sessions (CAS-backed, sqlite or in-memory), a single-use token kernel (login/invite/bootstrap), a login page, and admin screens.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"docs",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"zod": "^4.0.0",
|
|
22
|
+
"@pattern-js/runtime-node": "0.2.0",
|
|
23
|
+
"@pattern-js/core": "0.2.0"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/node": "^22.0.0",
|
|
27
|
+
"typescript": "^5.6.0",
|
|
28
|
+
"@pattern-js/runtime-node": "0.2.0"
|
|
29
|
+
},
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=22.5"
|
|
32
|
+
},
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/makkr-studio/pattern.git",
|
|
36
|
+
"directory": "packages/mod-identity"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://github.com/makkr-studio/pattern#readme",
|
|
39
|
+
"bugs": "https://github.com/makkr-studio/pattern/issues",
|
|
40
|
+
"author": "Makkr Studio (https://github.com/makkr-studio)",
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "tsc -p tsconfig.json",
|
|
43
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
44
|
+
}
|
|
45
|
+
}
|