@substrat-run/vertical-auth 0.8.0 → 0.9.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/README.md CHANGED
@@ -48,8 +48,22 @@ leak across tenants. It holds the provider-agnostic `sub → principal` mapping,
48
48
  `setPendingOwner` / `resolvePrincipal` are used under **every** provider, including the two
49
49
  OIDC ones where Better Auth stays dormant.
50
50
 
51
- That mapping is what makes first-run ownership work: the first authenticated subject claims
52
- the pending owner slot, and access is invite-only afterwards.
51
+ That mapping is what makes first-run ownership work. The owner seat is minted **empty** at
52
+ provision (the platform knows the principal it minted, not the login the tenant's issuer will
53
+ emit), and bound later by a verified subject — two ways:
54
+
55
+ - **First sign-in, inside a window.** For `FIRST_SIGN_IN_WINDOW_MS` (15 minutes) after provision,
56
+ the first authenticated subject claims the seat — the install flow, where the installer opens
57
+ the app seconds later. After that a plain sign-in binds nobody: an instance nobody opened (a CI
58
+ deploy, an issuer with open sign-up) is no longer a seat anyone can take, indefinitely.
59
+ - **A claim link.** The platform asks `mintOwnerClaim` under its secret and the dashboard hands
60
+ the installer a short-lived `/?claim=<token>` link; `claimOwner` binds the subject that
61
+ presents it. Only the token's hash is stored, and minting again retires the earlier link.
62
+ `mintOwnerClaimLink` does the token + hash + URL once, so a vertical's hook is a one-liner.
63
+
64
+ A closed window is not a lost desk: the seat stays pending (`needsSetup` keeps saying so, and
65
+ `ownerSeat` says *why* — open, closed, or already claimed) until a claim binds it. A re-provision
66
+ keeps the window it has and never re-opens a claimed seat. Access is invite-only afterwards.
53
67
 
54
68
  Also exported: `resolveCookieDomain`, which refuses to set a session cookie on a public suffix
55
69
  (guarded by [`@substrat-run/psl`](https://npmjs.com/package/@substrat-run/psl)).
@@ -1,6 +1,7 @@
1
1
  import { DurableObject } from 'cloudflare:workers';
2
2
  import type { AuthProvider } from './provider.js';
3
3
  import { type SiteRow } from './site-registry.js';
4
+ import { type OwnerSeat } from './owner-seat.js';
4
5
  export type IdentityDoEnv = Record<string, never>;
5
6
  export declare class IdentityDO extends DurableObject<IdentityDoEnv> {
6
7
  /** This tenant's Better Auth signing secret — generated in this DO, never shared, never a worker binding. */
@@ -56,10 +57,10 @@ export declare class IdentityDO extends DurableObject<IdentityDoEnv> {
56
57
  /** The DO's SQLite as the registry's minimal `exec` seam (see site-registry.ts). */
57
58
  private get registrySql();
58
59
  /**
59
- * Record the owner seat at provision. Writes BOTH the transient `pending_owner` (claimed +
60
- * consumed by the first login) and the durable `owner_of_record` (#332, never consumed) in one
61
- * call, so the vertical always retains who the owner is even after the seat has been claimed.
62
- * Idempotent — the reconciliation sweep re-runs it.
60
+ * Record the owner seat at provision (#925): the transient `pending_owner`, bounded by the
61
+ * first-sign-in window, and the durable `owner_of_record` (#332). Idempotent the way the
62
+ * platform needs — a re-run (reconcile, retry) keeps the seat's existing window and never
63
+ * re-opens a seat already claimed.
63
64
  */
64
65
  setPendingOwner(scopeId: string, principal: string): Promise<void>;
65
66
  /**
@@ -69,19 +70,34 @@ export declare class IdentityDO extends DurableObject<IdentityDoEnv> {
69
70
  */
70
71
  getOwnerOfRecord(scopeId: string): Promise<string | null>;
71
72
  /**
72
- * Is this scope awaiting first-run setup? True while its owner seat is unclaimed — the
73
- * instance is provisioned but no admin has signed in yet. The worker uses this to show a
74
- * "create the admin account" setup screen instead of a plain sign-in, and to keep open
75
- * sign-up allowed only during this window (after the owner claims, sign-up is invite-only).
73
+ * Is this scope awaiting first-run setup? True while its owner seat is unclaimed — whether or
74
+ * not a plain first sign-in can still claim it (`ownerSeat` says which). The worker uses this
75
+ * to answer `needs-setup` instead of a bare 401, so the SPA can say what to do.
76
76
  */
77
77
  needsSetup(scopeId: string): Promise<boolean>;
78
+ /** The seat as the platform sees it: claimed / unclaimed (window open or closed, link live or not) / unknown. */
79
+ ownerSeat(scopeId: string): Promise<OwnerSeat>;
78
80
  /**
79
81
  * Map a verified subject to a PrincipalId in this scope. If already bound, return it. If
80
- * not, and the scope's owner seat is unclaimed, CLAIM it: bind this subject to the owner
81
- * principal and consume the pending seat. Otherwise null — a valid login with no seat has
82
- * no access. Provider-agnostic: the subject may come from Better Auth or an OIDC issuer.
82
+ * not, and the scope's owner seat is unclaimed with its first-sign-in window still open,
83
+ * CLAIM it. Otherwise null — a valid login with no seat has no access, and after the
84
+ * window the seat is reached by claim link only. Provider-agnostic: the subject may come
85
+ * from Better Auth or an OIDC issuer.
83
86
  */
84
87
  resolvePrincipal(scopeId: string, sub: string): Promise<string | null>;
88
+ /**
89
+ * Mint a claim link for an unclaimed seat: the platform asks for this under its secret and
90
+ * the dashboard hands the installer the link. Only `tokenHash` reaches the DO; a new mint
91
+ * replaces the previous link. Null ⇒ the seat is already claimed (nothing to mint).
92
+ */
93
+ mintOwnerClaim(scopeId: string, tokenHash: string): Promise<{
94
+ expiresAt: string;
95
+ } | null>;
96
+ /**
97
+ * Claim the seat by link: bind this verified subject to the owner principal if the token's
98
+ * hash matches the live claim. Consumes the seat and the link. Null ⇒ invalid or expired.
99
+ */
100
+ claimOwner(scopeId: string, sub: string, tokenHash: string): Promise<string | null>;
85
101
  /**
86
102
  * Record a member invite: a pre-minted `principal` + `roleKey` the caller has already
87
103
  * granted at scope level, claimable by whoever presents the token whose hash is `tokenHash`.
@@ -135,7 +151,12 @@ export type IdentityStub = {
135
151
  setPendingOwner(scopeId: string, principal: string): Promise<void>;
136
152
  getOwnerOfRecord(scopeId: string): Promise<string | null>;
137
153
  needsSetup(scopeId: string): Promise<boolean>;
154
+ ownerSeat(scopeId: string): Promise<OwnerSeat>;
138
155
  resolvePrincipal(scopeId: string, sub: string): Promise<string | null>;
156
+ mintOwnerClaim(scopeId: string, tokenHash: string): Promise<{
157
+ expiresAt: string;
158
+ } | null>;
159
+ claimOwner(scopeId: string, sub: string, tokenHash: string): Promise<string | null>;
139
160
  createInvite(scopeId: string, principal: string, roleKey: string, email: string | null, tokenHash: string): Promise<void>;
140
161
  listInvites(scopeId: string): Promise<Array<{
141
162
  principal: string;
@@ -1 +1 @@
1
- {"version":3,"file":"identity-do.d.ts","sourceRoot":"","sources":["../src/identity-do.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"identity-do.d.ts","sourceRoot":"","sources":["../src/identity-do.ts"],"names":[],"mappings":"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"}
@@ -5,6 +5,7 @@ import { drizzle } from 'drizzle-orm/durable-sqlite';
5
5
  import * as schema from './auth-schema.js';
6
6
  import { resolveCookieDomain } from './cookie-domain.js';
7
7
  import { SITE_REGISTRY_DDL, recordSite as recordSiteRow, listSites as listSiteRows, resolveSiteScope as resolveSiteScopeRow, forgetSite as forgetSiteRow, } from './site-registry.js';
8
+ import { OWNER_SEAT_DDL, migrateOwnerSeat, recordOwnerSeat, ownerOfRecord as ownerOfRecordRow, needsSetup as needsSetupRow, ownerSeat as ownerSeatRow, resolvePrincipal as resolvePrincipalRow, mintOwnerClaim as mintOwnerClaimRow, claimOwner as claimOwnerRow, } from './owner-seat.js';
8
9
  /**
9
10
  * The per-tenant IDENTITY Durable Object — one per tenant, running its OWN Better Auth
10
11
  * over its OWN SQLite (Drizzle's Durable-Object driver, not a shared D1), and holding the
@@ -18,8 +19,9 @@ import { SITE_REGISTRY_DDL, recordSite as recordSiteRow, listSites as listSiteRo
18
19
  * Two roles, either or both used per deployment:
19
20
  * - `doAuthProvider` (below) exposes Better Auth here as an `AuthProvider` (the
20
21
  * `better-auth-do` config). With an OIDC provider instead, Better Auth stays dormant.
21
- * - `setPendingOwner` / `resolvePrincipal` are the identity directory — used under EVERY
22
- * provider, since the subject → principal mapping is provider-independent.
22
+ * - `setPendingOwner` / `resolvePrincipal` / `claimOwner` are the identity directory —
23
+ * used under EVERY provider, since the subject → principal mapping is provider-independent.
24
+ * The owner seat's rules (the bounded first sign-in, the claim link) are in owner-seat.ts.
23
25
  */
24
26
  const SCHEMA_STATEMENTS = [
25
27
  `CREATE TABLE IF NOT EXISTS user (
@@ -46,19 +48,15 @@ const SCHEMA_STATEMENTS = [
46
48
  created_at INTEGER NOT NULL DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)),
47
49
  updated_at INTEGER NOT NULL DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)))`,
48
50
  `CREATE INDEX IF NOT EXISTS verification_identifier_idx ON verification (identifier)`,
49
- // The PROVIDER-AGNOSTIC identity directory: a verified subject (`sub`) → the Substrat
50
- // PrincipalId it maps to, per scope (K-22 — the same login is a different principal in
51
- // each scope). Independent of WHICH provider verified the subject.
52
- `CREATE TABLE IF NOT EXISTS identity (scope_id TEXT NOT NULL, sub TEXT NOT NULL, principal TEXT NOT NULL, PRIMARY KEY (scope_id, sub))`,
53
- // The owner seat waiting to be claimed: set at provision, consumed by the first login.
54
- `CREATE TABLE IF NOT EXISTS pending_owner (scope_id TEXT PRIMARY KEY, principal TEXT NOT NULL)`,
55
- // The DURABLE owner of record: also set at provision, but NEVER consumed (#332). `pending_owner`
56
- // is gone the moment the owner first signs in, so it can't answer "who owns this scope" after
57
- // that. This can — it's the vertical's own memory of the owner, in the per-tenant IdentityDO
58
- // (a different DO from the scope's data DO), so it survives a scope-DO storage wipe. A builder-
59
- // triggered reconcile reads it to re-establish the owner's role grant without the platform
60
- // secret and without the CP having to persist an owner it doesn't otherwise track.
61
- `CREATE TABLE IF NOT EXISTS owner_of_record (scope_id TEXT PRIMARY KEY, principal TEXT NOT NULL)`,
51
+ // The PROVIDER-AGNOSTIC identity directory (`identity`: a verified `sub` → the Substrat
52
+ // PrincipalId it maps to, per scope — K-22, the same login is a different principal in each
53
+ // scope, independent of WHICH provider verified it) and the OWNER SEAT around it —
54
+ // `pending_owner` (claimed and consumed), `owner_of_record` (#332, never consumed: the
55
+ // vertical's own durable memory of the owner, in this per-tenant DO rather than the scope's
56
+ // data DO, so it survives a scope-DO wipe and a reconcile can re-grant from it) and
57
+ // `owner_claim` (the claim link's hash, #925). The tables and the rules over them live in
58
+ // `owner-seat.ts` so they are unit-tested without a DO; the methods below delegate there.
59
+ ...OWNER_SEAT_DDL,
62
60
  // Outstanding member invites (the post-setup join path). Each is a pre-minted principal +
63
61
  // role the admin already granted at scope level, waiting for a login to claim it by token.
64
62
  // Only the token's HASH is stored — the token itself lives in the accept link, never here.
@@ -89,6 +87,8 @@ export class IdentityDO extends DurableObject {
89
87
  ctx.blockConcurrencyWhile(async () => {
90
88
  for (const stmt of SCHEMA_STATEMENTS)
91
89
  ctx.storage.sql.exec(stmt);
90
+ // Columns the DDL cannot add to a table that already exists (#925's `claim_until`).
91
+ migrateOwnerSeat(ctx.storage.sql);
92
92
  // Load-or-generate this tenant's OWN signing secret. Each IdentityDO mints its own on
93
93
  // first init and persists it here, so the secret is per-tenant, never leaves the DO,
94
94
  // and needs no `wrangler secret put` (which would be one value shared across every
@@ -177,14 +177,13 @@ export class IdentityDO extends DurableObject {
177
177
  return this.ctx.storage.sql;
178
178
  }
179
179
  /**
180
- * Record the owner seat at provision. Writes BOTH the transient `pending_owner` (claimed +
181
- * consumed by the first login) and the durable `owner_of_record` (#332, never consumed) in one
182
- * call, so the vertical always retains who the owner is even after the seat has been claimed.
183
- * Idempotent — the reconciliation sweep re-runs it.
180
+ * Record the owner seat at provision (#925): the transient `pending_owner`, bounded by the
181
+ * first-sign-in window, and the durable `owner_of_record` (#332). Idempotent the way the
182
+ * platform needs — a re-run (reconcile, retry) keeps the seat's existing window and never
183
+ * re-opens a seat already claimed.
184
184
  */
185
185
  async setPendingOwner(scopeId, principal) {
186
- this.ctx.storage.sql.exec('INSERT OR REPLACE INTO pending_owner (scope_id, principal) VALUES (?, ?)', scopeId, principal);
187
- this.ctx.storage.sql.exec('INSERT OR REPLACE INTO owner_of_record (scope_id, principal) VALUES (?, ?)', scopeId, principal);
186
+ recordOwnerSeat(this.registrySql, scopeId, principal, Date.now());
188
187
  }
189
188
  /**
190
189
  * The scope's durable owner of record, or null if this scope was never provisioned through this
@@ -192,35 +191,44 @@ export class IdentityDO extends DurableObject {
192
191
  * owner their role after a scope-DO wipe left the scope enforcing against an empty tuple table.
193
192
  */
194
193
  async getOwnerOfRecord(scopeId) {
195
- const row = [...this.ctx.storage.sql.exec('SELECT principal FROM owner_of_record WHERE scope_id = ?', scopeId)][0];
196
- return row?.principal ?? null;
194
+ return ownerOfRecordRow(this.registrySql, scopeId);
197
195
  }
198
196
  /**
199
- * Is this scope awaiting first-run setup? True while its owner seat is unclaimed — the
200
- * instance is provisioned but no admin has signed in yet. The worker uses this to show a
201
- * "create the admin account" setup screen instead of a plain sign-in, and to keep open
202
- * sign-up allowed only during this window (after the owner claims, sign-up is invite-only).
197
+ * Is this scope awaiting first-run setup? True while its owner seat is unclaimed — whether or
198
+ * not a plain first sign-in can still claim it (`ownerSeat` says which). The worker uses this
199
+ * to answer `needs-setup` instead of a bare 401, so the SPA can say what to do.
203
200
  */
204
201
  async needsSetup(scopeId) {
205
- const pending = [...this.ctx.storage.sql.exec('SELECT 1 FROM pending_owner WHERE scope_id = ?', scopeId)][0];
206
- return pending !== undefined;
202
+ return needsSetupRow(this.registrySql, scopeId);
203
+ }
204
+ /** The seat as the platform sees it: claimed / unclaimed (window open or closed, link live or not) / unknown. */
205
+ async ownerSeat(scopeId) {
206
+ return ownerSeatRow(this.registrySql, scopeId, Date.now());
207
207
  }
208
208
  /**
209
209
  * Map a verified subject to a PrincipalId in this scope. If already bound, return it. If
210
- * not, and the scope's owner seat is unclaimed, CLAIM it: bind this subject to the owner
211
- * principal and consume the pending seat. Otherwise null — a valid login with no seat has
212
- * no access. Provider-agnostic: the subject may come from Better Auth or an OIDC issuer.
210
+ * not, and the scope's owner seat is unclaimed with its first-sign-in window still open,
211
+ * CLAIM it. Otherwise null — a valid login with no seat has no access, and after the
212
+ * window the seat is reached by claim link only. Provider-agnostic: the subject may come
213
+ * from Better Auth or an OIDC issuer.
213
214
  */
214
215
  async resolvePrincipal(scopeId, sub) {
215
- const bound = [...this.ctx.storage.sql.exec('SELECT principal FROM identity WHERE scope_id = ? AND sub = ?', scopeId, sub)][0];
216
- if (bound)
217
- return bound.principal;
218
- const pending = [...this.ctx.storage.sql.exec('SELECT principal FROM pending_owner WHERE scope_id = ?', scopeId)][0];
219
- if (!pending)
220
- return null;
221
- this.ctx.storage.sql.exec('INSERT OR REPLACE INTO identity (scope_id, sub, principal) VALUES (?, ?, ?)', scopeId, sub, pending.principal);
222
- this.ctx.storage.sql.exec('DELETE FROM pending_owner WHERE scope_id = ?', scopeId);
223
- return pending.principal;
216
+ return resolvePrincipalRow(this.registrySql, scopeId, sub, Date.now());
217
+ }
218
+ /**
219
+ * Mint a claim link for an unclaimed seat: the platform asks for this under its secret and
220
+ * the dashboard hands the installer the link. Only `tokenHash` reaches the DO; a new mint
221
+ * replaces the previous link. Null ⇒ the seat is already claimed (nothing to mint).
222
+ */
223
+ async mintOwnerClaim(scopeId, tokenHash) {
224
+ return mintOwnerClaimRow(this.registrySql, scopeId, tokenHash, Date.now());
225
+ }
226
+ /**
227
+ * Claim the seat by link: bind this verified subject to the owner principal if the token's
228
+ * hash matches the live claim. Consumes the seat and the link. Null ⇒ invalid or expired.
229
+ */
230
+ async claimOwner(scopeId, sub, tokenHash) {
231
+ return claimOwnerRow(this.registrySql, scopeId, sub, tokenHash, Date.now());
224
232
  }
225
233
  /**
226
234
  * Record a member invite: a pre-minted `principal` + `roleKey` the caller has already
@@ -1 +1 @@
1
- {"version":3,"file":"identity-do.js","sourceRoot":"","sources":["../src/identity-do.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"identity-do.js","sourceRoot":"","sources":["../src/identity-do.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -3,4 +3,6 @@ export { oidcAuthProvider, type OidcConfig } from './oidc.js';
3
3
  export { oidcRpAuthProvider, type OidcRpConfig } from './oidc-rp-provider.js';
4
4
  export { IdentityDO, doAuthProvider, type IdentityDoEnv, type IdentityStub } from './identity-do.js';
5
5
  export { resolveCookieDomain } from './cookie-domain.js';
6
+ export { FIRST_SIGN_IN_WINDOW_MS, OWNER_CLAIM_TTL_MS, type OwnerSeat } from './owner-seat.js';
7
+ export { mintOwnerClaimLink, ownerClaimPath, sha256Hex } from './owner-claim-link.js';
6
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC/D,OAAO,EAAE,gBAAgB,EAAE,KAAK,UAAU,EAAE,MAAM,WAAW,CAAC;AAC9D,OAAO,EAAE,kBAAkB,EAAE,KAAK,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC9E,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,KAAK,aAAa,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrG,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC/D,OAAO,EAAE,gBAAgB,EAAE,KAAK,UAAU,EAAE,MAAM,WAAW,CAAC;AAC9D,OAAO,EAAE,kBAAkB,EAAE,KAAK,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC9E,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,KAAK,aAAa,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrG,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,uBAAuB,EAAE,kBAAkB,EAAE,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC9F,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC"}
package/dist/index.js CHANGED
@@ -2,4 +2,6 @@ export { oidcAuthProvider } from './oidc.js';
2
2
  export { oidcRpAuthProvider } from './oidc-rp-provider.js';
3
3
  export { IdentityDO, doAuthProvider } from './identity-do.js';
4
4
  export { resolveCookieDomain } from './cookie-domain.js';
5
+ export { FIRST_SIGN_IN_WINDOW_MS, OWNER_CLAIM_TTL_MS } from './owner-seat.js';
6
+ export { mintOwnerClaimLink, ownerClaimPath, sha256Hex } from './owner-claim-link.js';
5
7
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAmB,MAAM,WAAW,CAAC;AAC9D,OAAO,EAAE,kBAAkB,EAAqB,MAAM,uBAAuB,CAAC;AAC9E,OAAO,EAAE,UAAU,EAAE,cAAc,EAAyC,MAAM,kBAAkB,CAAC;AACrG,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAmB,MAAM,WAAW,CAAC;AAC9D,OAAO,EAAE,kBAAkB,EAAqB,MAAM,uBAAuB,CAAC;AAC9E,OAAO,EAAE,UAAU,EAAE,cAAc,EAAyC,MAAM,kBAAkB,CAAC;AACrG,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,uBAAuB,EAAE,kBAAkB,EAAkB,MAAM,iBAAiB,CAAC;AAC9F,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * The claim-link half of the owner seat (#925), from the worker's side: mint a token,
3
+ * hand its HASH to the identity directory, and return the URL the installer opens. Written
4
+ * once here so each vertical's `mintOwnerClaim` hook is a one-liner, and so the token shape,
5
+ * the hash and the URL convention (`/?claim=<token>`, the SPA's counterpart to `?invite=`)
6
+ * are one fact rather than four copies.
7
+ */
8
+ /** SHA-256 hex (Web Crypto — the same call in workerd, node and browsers). */
9
+ export declare function sha256Hex(s: string): Promise<string>;
10
+ /** A long, URL-safe token: two UUIDs = 256 bits of entropy. Only its hash is ever stored. */
11
+ export declare function claimToken(): string;
12
+ /** The path a claim token rides on — what the SPA reads back as `?claim=`. */
13
+ export declare function ownerClaimPath(token: string): string;
14
+ /**
15
+ * Mint a claim link for a scope's unclaimed owner seat. `origin` is the instance's public
16
+ * origin — supplied by the platform, which owns the hostname directory; a `/internal` call
17
+ * reaches the worker through the dispatcher and carries no usable host of its own. Null ⇒
18
+ * the seat is already claimed (or unknown), and there is nothing to mint for.
19
+ */
20
+ export declare function mintOwnerClaimLink(directory: {
21
+ mintOwnerClaim(scopeId: string, tokenHash: string): Promise<{
22
+ expiresAt: string;
23
+ } | null>;
24
+ }, scopeId: string, origin: string): Promise<{
25
+ claimUrl: string;
26
+ expiresAt: string;
27
+ } | null>;
28
+ //# sourceMappingURL=owner-claim-link.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"owner-claim-link.d.ts","sourceRoot":"","sources":["../src/owner-claim-link.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,8EAA8E;AAC9E,wBAAsB,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAG1D;AAED,6FAA6F;AAC7F,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AAED,8EAA8E;AAC9E,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACtC,SAAS,EAAE;IAAE,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,CAAA;CAAE,EACxG,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,GACb,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAKzD"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * The claim-link half of the owner seat (#925), from the worker's side: mint a token,
3
+ * hand its HASH to the identity directory, and return the URL the installer opens. Written
4
+ * once here so each vertical's `mintOwnerClaim` hook is a one-liner, and so the token shape,
5
+ * the hash and the URL convention (`/?claim=<token>`, the SPA's counterpart to `?invite=`)
6
+ * are one fact rather than four copies.
7
+ */
8
+ /** SHA-256 hex (Web Crypto — the same call in workerd, node and browsers). */
9
+ export async function sha256Hex(s) {
10
+ const buf = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(s));
11
+ return [...new Uint8Array(buf)].map((b) => b.toString(16).padStart(2, '0')).join('');
12
+ }
13
+ /** A long, URL-safe token: two UUIDs = 256 bits of entropy. Only its hash is ever stored. */
14
+ export function claimToken() {
15
+ return (crypto.randomUUID() + crypto.randomUUID()).replace(/-/g, '');
16
+ }
17
+ /** The path a claim token rides on — what the SPA reads back as `?claim=`. */
18
+ export function ownerClaimPath(token) {
19
+ return `/?claim=${encodeURIComponent(token)}`;
20
+ }
21
+ /**
22
+ * Mint a claim link for a scope's unclaimed owner seat. `origin` is the instance's public
23
+ * origin — supplied by the platform, which owns the hostname directory; a `/internal` call
24
+ * reaches the worker through the dispatcher and carries no usable host of its own. Null ⇒
25
+ * the seat is already claimed (or unknown), and there is nothing to mint for.
26
+ */
27
+ export async function mintOwnerClaimLink(directory, scopeId, origin) {
28
+ const token = claimToken();
29
+ const minted = await directory.mintOwnerClaim(scopeId, await sha256Hex(token));
30
+ if (!minted)
31
+ return null;
32
+ return { claimUrl: `${origin.replace(/\/$/, '')}${ownerClaimPath(token)}`, expiresAt: minted.expiresAt };
33
+ }
34
+ //# sourceMappingURL=owner-claim-link.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"owner-claim-link.js","sourceRoot":"","sources":["../src/owner-claim-link.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,8EAA8E;AAC9E,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,CAAS;IACvC,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/E,OAAO,CAAC,GAAG,IAAI,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACvF,CAAC;AAED,6FAA6F;AAC7F,MAAM,UAAU,UAAU;IACxB,OAAO,CAAC,MAAM,CAAC,UAAU,EAAE,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;AACvE,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,OAAO,WAAW,kBAAkB,CAAC,KAAK,CAAC,EAAE,CAAC;AAChD,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,SAAwG,EACxG,OAAe,EACf,MAAc;IAEd,MAAM,KAAK,GAAG,UAAU,EAAE,CAAC;IAC3B,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,cAAc,CAAC,OAAO,EAAE,MAAM,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;IAC/E,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IACzB,OAAO,EAAE,QAAQ,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,cAAc,CAAC,KAAK,CAAC,EAAE,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,CAAC;AAC3G,CAAC"}
@@ -0,0 +1,106 @@
1
+ /**
2
+ * The OWNER SEAT — how a provisioned scope gets its first human (#925).
3
+ *
4
+ * At provision the platform mints a principal for the installer and hands it to the
5
+ * vertical as `owner`. It cannot hand over the login: the vertical authenticates at
6
+ * whatever issuer the tenant bound, and the platform does not know what `sub` that
7
+ * issuer will emit for this person. So the seat is minted EMPTY and bound later, by a
8
+ * verified subject arriving to claim it. Three ways in, and the first two are what
9
+ * closes the window this used to leave open:
10
+ *
11
+ * 1. **First sign-in, inside a window.** The install flow is "provision, then the
12
+ * installer opens the app and signs in" — seconds apart. That trust-on-first-use
13
+ * claim stays, but only for `FIRST_SIGN_IN_WINDOW_MS` after provision. Before, it
14
+ * was unbounded in time and audience: a CI-deployed instance whose issuer had open
15
+ * sign-up was a seat anyone could take, indefinitely, and nothing said so.
16
+ * 2. **A claim link.** After the window (or instead of it), the platform asks the
17
+ * vertical for a short-lived claim token under the platform secret, and the
18
+ * dashboard hands the installer the link. Only the token's HASH is stored here;
19
+ * the token rides one HTTP exchange and is never persisted anywhere.
20
+ * 3. **Reconcile never re-opens.** A re-provision (the platform's reconciliation
21
+ * sweep, a retry) keeps whatever window the seat already has, and a seat already
22
+ * claimed is left claimed — before, `INSERT OR REPLACE` re-minted the pending seat
23
+ * on every re-provision, so a sweep could hand a claimed desk's ownership to the
24
+ * next stranger to sign in.
25
+ *
26
+ * A CLOSED window is not a lost desk: `pending_owner` stays until a claim binds it, so
27
+ * `needsSetup` keeps reporting the truth and a claim link always works. What a closed
28
+ * window refuses is exactly the unbounded part — a stranger's plain sign-in.
29
+ *
30
+ * Plain functions over a minimal SQLite `exec` seam (the same shape as site-registry.ts)
31
+ * so the rules are unit-tested against a real SQLite without standing up a Durable Object.
32
+ * The IdentityDO's owner-seat methods delegate here.
33
+ */
34
+ import type { RegistrySql } from './site-registry.js';
35
+ /** How long after provision a plain first sign-in still claims the seat. */
36
+ export declare const FIRST_SIGN_IN_WINDOW_MS: number;
37
+ /** How long a minted claim link stays valid. */
38
+ export declare const OWNER_CLAIM_TTL_MS: number;
39
+ /**
40
+ * The tables. `identity` is the provider-agnostic directory (a verified `sub` → the
41
+ * PrincipalId it maps to, per scope — K-22, the same login is a different principal in
42
+ * each scope); it is also written by invites, which is why it lives here beside the seat
43
+ * rather than in the DO alone: claiming IS writing it.
44
+ */
45
+ export declare const OWNER_SEAT_DDL: readonly string[];
46
+ /**
47
+ * Bring a `pending_owner` table from before `claim_until` up to date. `CREATE TABLE IF NOT
48
+ * EXISTS` leaves an existing table alone, so a DO whose storage predates the column needs the
49
+ * one `ALTER`. Idempotent — run it after the DDL on every construction.
50
+ */
51
+ export declare function migrateOwnerSeat(sql: RegistrySql): void;
52
+ /** What the platform (and the vertical's own first-run screen) can see of the seat. */
53
+ export interface OwnerSeat {
54
+ /** `unknown` ⇒ this scope was never provisioned through this directory. */
55
+ state: 'claimed' | 'unclaimed' | 'unknown';
56
+ /** The owner of record — the principal the seat binds to. Null when unknown. */
57
+ owner: string | null;
58
+ /** While unclaimed: whether a plain first sign-in still claims it, and until when (ISO). */
59
+ firstSignIn: {
60
+ open: boolean;
61
+ until: string | null;
62
+ } | null;
63
+ /** While unclaimed: the outstanding claim link's expiry (ISO), or null when none is live. */
64
+ claimLink: {
65
+ expiresAt: string;
66
+ } | null;
67
+ }
68
+ /**
69
+ * Record the seat at provision — both the transient `pending_owner` and the durable
70
+ * `owner_of_record`. The FIRST write wins: a scope that already has an owner of record is
71
+ * left exactly as it is, whatever principal a re-run names. The platform re-runs provision
72
+ * on reconcile and retry with the owner it minted, so a same-owner re-run changes nothing
73
+ * either way; a DIFFERENT owner reaching a seat that is already recorded would otherwise
74
+ * re-point it — and re-open a claimed one for a stranger — which is the hole this closes.
75
+ * A pending seat keeps the window it was given rather than getting a fresh one per re-run.
76
+ */
77
+ export declare function recordOwnerSeat(sql: RegistrySql, scopeId: string, principal: string, now: number, windowMs?: number): void;
78
+ /** The scope's durable owner of record, or null if never provisioned through this directory. */
79
+ export declare function ownerOfRecord(sql: RegistrySql, scopeId: string): string | null;
80
+ /** Is the seat unclaimed? True whatever the window says — a closed window is still an empty seat. */
81
+ export declare function needsSetup(sql: RegistrySql, scopeId: string): boolean;
82
+ /** The seat as the platform sees it. */
83
+ export declare function ownerSeat(sql: RegistrySql, scopeId: string, now: number): OwnerSeat;
84
+ /**
85
+ * Map a verified subject to a principal in this scope. Bound ⇒ that principal. Unbound with
86
+ * the seat pending AND the first-sign-in window open ⇒ claim it. Otherwise null — a valid
87
+ * login with no seat has no access, and a closed window is "no seat" for THIS path (the
88
+ * claim link is the other). Provider-agnostic: the subject may come from Better Auth or an
89
+ * OIDC issuer.
90
+ */
91
+ export declare function resolvePrincipal(sql: RegistrySql, scopeId: string, sub: string, now: number): string | null;
92
+ /**
93
+ * Mint a claim link for a pending seat: store the token's hash with an expiry, replacing any
94
+ * earlier link (so minting again is also how one is revoked). Null ⇒ the seat is not pending
95
+ * — already claimed, or never provisioned here — and there is nothing to mint for.
96
+ */
97
+ export declare function mintOwnerClaim(sql: RegistrySql, scopeId: string, tokenHash: string, now: number, ttlMs?: number): {
98
+ expiresAt: string;
99
+ } | null;
100
+ /**
101
+ * Claim the seat by link: the presented token's hash must match the live claim, and the seat
102
+ * must still be pending. Binds the subject, consumes the seat and the link. Null ⇒ invalid,
103
+ * expired, already used, or nothing to claim — one answer, so a probe learns nothing.
104
+ */
105
+ export declare function claimOwner(sql: RegistrySql, scopeId: string, sub: string, tokenHash: string, now: number): string | null;
106
+ //# sourceMappingURL=owner-seat.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"owner-seat.d.ts","sourceRoot":"","sources":["../src/owner-seat.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEtD,4EAA4E;AAC5E,eAAO,MAAM,uBAAuB,QAAc,CAAC;AACnD,gDAAgD;AAChD,eAAO,MAAM,kBAAkB,QAAc,CAAC;AAE9C;;;;;GAKG;AACH,eAAO,MAAM,cAAc,EAAE,SAAS,MAAM,EAa3C,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,WAAW,GAAG,IAAI,CAKvD;AAED,uFAAuF;AACvF,MAAM,WAAW,SAAS;IACxB,2EAA2E;IAC3E,KAAK,EAAE,SAAS,GAAG,WAAW,GAAG,SAAS,CAAC;IAC3C,gFAAgF;IAChF,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,4FAA4F;IAC5F,WAAW,EAAE;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;IAC5D,6FAA6F;IAC7F,SAAS,EAAE;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CACzC;AAID;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,WAAW,EAChB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,QAAQ,GAAE,MAAgC,GACzC,IAAI,CASN;AAED,gGAAgG;AAChG,wBAAgB,aAAa,CAAC,GAAG,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAK9E;AAED,qGAAqG;AACrG,wBAAgB,UAAU,CAAC,GAAG,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAErE;AAuBD,wCAAwC;AACxC,wBAAgB,SAAS,CAAC,GAAG,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,CAenF;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAS3G;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,WAAW,EAChB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,KAAK,GAAE,MAA2B,GACjC;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAU9B;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMxH"}
@@ -0,0 +1,171 @@
1
+ /**
2
+ * The OWNER SEAT — how a provisioned scope gets its first human (#925).
3
+ *
4
+ * At provision the platform mints a principal for the installer and hands it to the
5
+ * vertical as `owner`. It cannot hand over the login: the vertical authenticates at
6
+ * whatever issuer the tenant bound, and the platform does not know what `sub` that
7
+ * issuer will emit for this person. So the seat is minted EMPTY and bound later, by a
8
+ * verified subject arriving to claim it. Three ways in, and the first two are what
9
+ * closes the window this used to leave open:
10
+ *
11
+ * 1. **First sign-in, inside a window.** The install flow is "provision, then the
12
+ * installer opens the app and signs in" — seconds apart. That trust-on-first-use
13
+ * claim stays, but only for `FIRST_SIGN_IN_WINDOW_MS` after provision. Before, it
14
+ * was unbounded in time and audience: a CI-deployed instance whose issuer had open
15
+ * sign-up was a seat anyone could take, indefinitely, and nothing said so.
16
+ * 2. **A claim link.** After the window (or instead of it), the platform asks the
17
+ * vertical for a short-lived claim token under the platform secret, and the
18
+ * dashboard hands the installer the link. Only the token's HASH is stored here;
19
+ * the token rides one HTTP exchange and is never persisted anywhere.
20
+ * 3. **Reconcile never re-opens.** A re-provision (the platform's reconciliation
21
+ * sweep, a retry) keeps whatever window the seat already has, and a seat already
22
+ * claimed is left claimed — before, `INSERT OR REPLACE` re-minted the pending seat
23
+ * on every re-provision, so a sweep could hand a claimed desk's ownership to the
24
+ * next stranger to sign in.
25
+ *
26
+ * A CLOSED window is not a lost desk: `pending_owner` stays until a claim binds it, so
27
+ * `needsSetup` keeps reporting the truth and a claim link always works. What a closed
28
+ * window refuses is exactly the unbounded part — a stranger's plain sign-in.
29
+ *
30
+ * Plain functions over a minimal SQLite `exec` seam (the same shape as site-registry.ts)
31
+ * so the rules are unit-tested against a real SQLite without standing up a Durable Object.
32
+ * The IdentityDO's owner-seat methods delegate here.
33
+ */
34
+ /** How long after provision a plain first sign-in still claims the seat. */
35
+ export const FIRST_SIGN_IN_WINDOW_MS = 15 * 60_000;
36
+ /** How long a minted claim link stays valid. */
37
+ export const OWNER_CLAIM_TTL_MS = 15 * 60_000;
38
+ /**
39
+ * The tables. `identity` is the provider-agnostic directory (a verified `sub` → the
40
+ * PrincipalId it maps to, per scope — K-22, the same login is a different principal in
41
+ * each scope); it is also written by invites, which is why it lives here beside the seat
42
+ * rather than in the DO alone: claiming IS writing it.
43
+ */
44
+ export const OWNER_SEAT_DDL = [
45
+ `CREATE TABLE IF NOT EXISTS identity (scope_id TEXT NOT NULL, sub TEXT NOT NULL, principal TEXT NOT NULL, PRIMARY KEY (scope_id, sub))`,
46
+ // The owner seat waiting to be claimed: set at provision, consumed by the claim. `claim_until`
47
+ // bounds the plain first-sign-in path (ms epoch); NULL — a row from before the column existed —
48
+ // reads as CLOSED, since a seat that sat unclaimed across an upgrade is exactly the case.
49
+ `CREATE TABLE IF NOT EXISTS pending_owner (scope_id TEXT PRIMARY KEY, principal TEXT NOT NULL, claim_until INTEGER)`,
50
+ // The DURABLE owner of record: also set at provision, but NEVER consumed (#332). `pending_owner`
51
+ // is gone the moment the owner claims, so it can't answer "who owns this scope" after that.
52
+ // This can — it survives a scope-DO storage wipe, and the reconcile path re-grants from it.
53
+ `CREATE TABLE IF NOT EXISTS owner_of_record (scope_id TEXT PRIMARY KEY, principal TEXT NOT NULL)`,
54
+ // One outstanding claim link per scope — the hash of its token and when it stops working.
55
+ // Minting again replaces it, so a leaked link is retired by minting a fresh one.
56
+ `CREATE TABLE IF NOT EXISTS owner_claim (scope_id TEXT PRIMARY KEY, token_hash TEXT NOT NULL, expires_at INTEGER NOT NULL)`,
57
+ ];
58
+ /**
59
+ * Bring a `pending_owner` table from before `claim_until` up to date. `CREATE TABLE IF NOT
60
+ * EXISTS` leaves an existing table alone, so a DO whose storage predates the column needs the
61
+ * one `ALTER`. Idempotent — run it after the DDL on every construction.
62
+ */
63
+ export function migrateOwnerSeat(sql) {
64
+ const columns = [...sql.exec('PRAGMA table_info(pending_owner)')].map((r) => r.name);
65
+ if (!columns.includes('claim_until')) {
66
+ sql.exec('ALTER TABLE pending_owner ADD COLUMN claim_until INTEGER');
67
+ }
68
+ }
69
+ const iso = (ms) => new Date(ms).toISOString();
70
+ /**
71
+ * Record the seat at provision — both the transient `pending_owner` and the durable
72
+ * `owner_of_record`. The FIRST write wins: a scope that already has an owner of record is
73
+ * left exactly as it is, whatever principal a re-run names. The platform re-runs provision
74
+ * on reconcile and retry with the owner it minted, so a same-owner re-run changes nothing
75
+ * either way; a DIFFERENT owner reaching a seat that is already recorded would otherwise
76
+ * re-point it — and re-open a claimed one for a stranger — which is the hole this closes.
77
+ * A pending seat keeps the window it was given rather than getting a fresh one per re-run.
78
+ */
79
+ export function recordOwnerSeat(sql, scopeId, principal, now, windowMs = FIRST_SIGN_IN_WINDOW_MS) {
80
+ if (ownerOfRecord(sql, scopeId) !== null)
81
+ return;
82
+ sql.exec('INSERT INTO owner_of_record (scope_id, principal) VALUES (?, ?)', scopeId, principal);
83
+ sql.exec('INSERT INTO pending_owner (scope_id, principal, claim_until) VALUES (?, ?, ?)', scopeId, principal, now + windowMs);
84
+ }
85
+ /** The scope's durable owner of record, or null if never provisioned through this directory. */
86
+ export function ownerOfRecord(sql, scopeId) {
87
+ const row = [...sql.exec('SELECT principal FROM owner_of_record WHERE scope_id = ?', scopeId)][0];
88
+ return row?.principal ?? null;
89
+ }
90
+ /** Is the seat unclaimed? True whatever the window says — a closed window is still an empty seat. */
91
+ export function needsSetup(sql, scopeId) {
92
+ return [...sql.exec('SELECT 1 FROM pending_owner WHERE scope_id = ?', scopeId)][0] !== undefined;
93
+ }
94
+ function pendingRow(sql, scopeId) {
95
+ return [...sql.exec('SELECT principal, claim_until FROM pending_owner WHERE scope_id = ?', scopeId)][0];
96
+ }
97
+ function liveClaim(sql, scopeId, now) {
98
+ const row = [...sql.exec('SELECT token_hash, expires_at FROM owner_claim WHERE scope_id = ?', scopeId)][0];
99
+ return row && row.expires_at > now ? row : undefined;
100
+ }
101
+ /** Bind a subject to the pending seat and consume it — the one write every claim path shares. */
102
+ function bindSeat(sql, scopeId, sub, principal) {
103
+ sql.exec('INSERT OR REPLACE INTO identity (scope_id, sub, principal) VALUES (?, ?, ?)', scopeId, sub, principal);
104
+ sql.exec('DELETE FROM pending_owner WHERE scope_id = ?', scopeId);
105
+ sql.exec('DELETE FROM owner_claim WHERE scope_id = ?', scopeId);
106
+ return principal;
107
+ }
108
+ /** The seat as the platform sees it. */
109
+ export function ownerSeat(sql, scopeId, now) {
110
+ const owner = ownerOfRecord(sql, scopeId);
111
+ if (!owner)
112
+ return { state: 'unknown', owner: null, firstSignIn: null, claimLink: null };
113
+ const pending = pendingRow(sql, scopeId);
114
+ if (!pending)
115
+ return { state: 'claimed', owner, firstSignIn: null, claimLink: null };
116
+ const claim = liveClaim(sql, scopeId, now);
117
+ return {
118
+ state: 'unclaimed',
119
+ owner,
120
+ firstSignIn: {
121
+ open: pending.claim_until !== null && pending.claim_until > now,
122
+ until: pending.claim_until === null ? null : iso(pending.claim_until),
123
+ },
124
+ claimLink: claim ? { expiresAt: iso(claim.expires_at) } : null,
125
+ };
126
+ }
127
+ /**
128
+ * Map a verified subject to a principal in this scope. Bound ⇒ that principal. Unbound with
129
+ * the seat pending AND the first-sign-in window open ⇒ claim it. Otherwise null — a valid
130
+ * login with no seat has no access, and a closed window is "no seat" for THIS path (the
131
+ * claim link is the other). Provider-agnostic: the subject may come from Better Auth or an
132
+ * OIDC issuer.
133
+ */
134
+ export function resolvePrincipal(sql, scopeId, sub, now) {
135
+ const bound = [...sql.exec('SELECT principal FROM identity WHERE scope_id = ? AND sub = ?', scopeId, sub)][0];
136
+ if (bound)
137
+ return bound.principal;
138
+ const pending = pendingRow(sql, scopeId);
139
+ if (!pending)
140
+ return null;
141
+ if (pending.claim_until === null || pending.claim_until <= now)
142
+ return null;
143
+ return bindSeat(sql, scopeId, sub, pending.principal);
144
+ }
145
+ /**
146
+ * Mint a claim link for a pending seat: store the token's hash with an expiry, replacing any
147
+ * earlier link (so minting again is also how one is revoked). Null ⇒ the seat is not pending
148
+ * — already claimed, or never provisioned here — and there is nothing to mint for.
149
+ */
150
+ export function mintOwnerClaim(sql, scopeId, tokenHash, now, ttlMs = OWNER_CLAIM_TTL_MS) {
151
+ if (!pendingRow(sql, scopeId))
152
+ return null;
153
+ const expiresAt = now + ttlMs;
154
+ sql.exec('INSERT OR REPLACE INTO owner_claim (scope_id, token_hash, expires_at) VALUES (?, ?, ?)', scopeId, tokenHash, expiresAt);
155
+ return { expiresAt: iso(expiresAt) };
156
+ }
157
+ /**
158
+ * Claim the seat by link: the presented token's hash must match the live claim, and the seat
159
+ * must still be pending. Binds the subject, consumes the seat and the link. Null ⇒ invalid,
160
+ * expired, already used, or nothing to claim — one answer, so a probe learns nothing.
161
+ */
162
+ export function claimOwner(sql, scopeId, sub, tokenHash, now) {
163
+ const pending = pendingRow(sql, scopeId);
164
+ if (!pending)
165
+ return null;
166
+ const claim = liveClaim(sql, scopeId, now);
167
+ if (!claim || claim.token_hash !== tokenHash)
168
+ return null;
169
+ return bindSeat(sql, scopeId, sub, pending.principal);
170
+ }
171
+ //# sourceMappingURL=owner-seat.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"owner-seat.js","sourceRoot":"","sources":["../src/owner-seat.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@substrat-run/vertical-auth",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Pluggable auth for Substrat verticals — the AuthProvider contract, an OIDC provider (Supabase/Auth0/AuthHero/Keycloak), a full relying-party login flow, and a per-tenant identity Durable Object (Better Auth + sub→principal directory). Workers-targeted; the vertical worker bundles it.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {
@@ -33,7 +33,7 @@
33
33
  "dist"
34
34
  ],
35
35
  "dependencies": {
36
- "better-auth": "^1.6.23",
36
+ "better-auth": "^1.7.1",
37
37
  "drizzle-orm": "^0.45.2",
38
38
  "jose": "^6.2.3",
39
39
  "@substrat-run/oidc-rp": "^0.5.1",