@lunora/auth 1.0.0-alpha.7 → 1.0.0-alpha.71

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 (69) hide show
  1. package/LICENSE.md +38 -0
  2. package/README.md +56 -1
  3. package/dist/adapter.d.mts +4 -43
  4. package/dist/adapter.d.ts +4 -43
  5. package/dist/adapter.mjs +1 -47
  6. package/dist/audit.d.mts +146 -0
  7. package/dist/audit.d.ts +146 -0
  8. package/dist/audit.mjs +12 -0
  9. package/dist/email-guard.d.mts +122 -0
  10. package/dist/email-guard.d.ts +122 -0
  11. package/dist/email-guard.mjs +1 -0
  12. package/dist/index.d.mts +698 -147
  13. package/dist/index.d.ts +698 -147
  14. package/dist/index.mjs +1 -12
  15. package/dist/middleware.d.mts +157 -156
  16. package/dist/middleware.d.ts +157 -156
  17. package/dist/middleware.mjs +1 -53
  18. package/dist/packem_shared/AUTH_DO_AUDIT_PATH-C4897amZ.mjs +1 -0
  19. package/dist/packem_shared/DEFAULT_AUTH_BASE_PATH-kIwlEt8i.mjs +1 -0
  20. package/dist/packem_shared/LunoraAuthAdminError-CiHsF1qZ.mjs +1 -0
  21. package/dist/packem_shared/adapter-RvDcm0Zy.mjs +1 -0
  22. package/dist/packem_shared/adapter.d-2a61HAY2.d.ts +76 -0
  23. package/dist/packem_shared/adapter.d-DrD3bb1u.d.mts +76 -0
  24. package/dist/packem_shared/authAuditHook-DG_ZNO53.mjs +1 -0
  25. package/dist/packem_shared/authDoColumnAdditions-B8BRbdzn.mjs +1 -0
  26. package/dist/packem_shared/compileMigrationsSql-BcvcHAqo.mjs +1 -0
  27. package/dist/packem_shared/create-auth.d-De6IOirt.d.mts +128 -0
  28. package/dist/packem_shared/create-auth.d-De6IOirt.d.ts +128 -0
  29. package/dist/packem_shared/createAuth-DRtd4q6u.mjs +1 -0
  30. package/dist/packem_shared/createDoAuthWiring-acnXUGZr.mjs +1 -0
  31. package/dist/packem_shared/createLunoraAuthClient-CedinxXU.mjs +1 -0
  32. package/dist/packem_shared/emailGateDatabaseHooks-DzBD1Qoq.mjs +1 -0
  33. package/dist/packem_shared/sessionPresets-DpEFjXKV.mjs +1 -0
  34. package/dist/packem_shared/uiConfig-BrYEFK3O.mjs +1 -0
  35. package/dist/plugins-client.d.mts +68 -1
  36. package/dist/plugins-client.d.ts +68 -1
  37. package/dist/plugins-client.mjs +1 -2
  38. package/dist/plugins-enterprise-client.d.mts +9 -0
  39. package/dist/plugins-enterprise-client.d.ts +9 -0
  40. package/dist/plugins-enterprise-client.mjs +1 -0
  41. package/dist/plugins-enterprise.d.mts +1576 -0
  42. package/dist/plugins-enterprise.d.ts +1576 -0
  43. package/dist/plugins-enterprise.mjs +1 -0
  44. package/dist/plugins.d.mts +83 -2
  45. package/dist/plugins.d.ts +83 -2
  46. package/dist/plugins.mjs +1 -22
  47. package/dist/schema.d.mts +39 -39
  48. package/dist/schema.d.ts +39 -39
  49. package/dist/schema.mjs +1 -62
  50. package/dist/sql-store.d.mts +28 -28
  51. package/dist/sql-store.d.ts +28 -28
  52. package/dist/sql-store.mjs +1 -162
  53. package/dist/store.d.mts +49 -31
  54. package/dist/store.d.ts +49 -31
  55. package/dist/store.mjs +1 -170
  56. package/dist/turnstile-middleware.d.mts +55 -55
  57. package/dist/turnstile-middleware.d.ts +55 -55
  58. package/dist/turnstile-middleware.mjs +1 -45
  59. package/dist/turnstile.d.mts +42 -59
  60. package/dist/turnstile.d.ts +42 -59
  61. package/dist/turnstile.mjs +1 -61
  62. package/package.json +38 -5
  63. package/dist/packem_shared/DEFAULT_AUTH_BASE_PATH-DjcUWEQl.mjs +0 -11
  64. package/dist/packem_shared/LunoraAuthAdminError-BxrfEeA_.mjs +0 -249
  65. package/dist/packem_shared/compileMigrationsSql-wZH3oXDu.mjs +0 -28
  66. package/dist/packem_shared/create-auth.d-M36jwG_Y.d.mts +0 -58
  67. package/dist/packem_shared/create-auth.d-M36jwG_Y.d.ts +0 -58
  68. package/dist/packem_shared/createAuth-B-tvsvQU.mjs +0 -56
  69. package/dist/packem_shared/sessionPresets-B95rXrd8.mjs +0 -35
package/dist/index.d.mts CHANGED
@@ -1,27 +1,35 @@
1
- export { lunoraAuthAdapter, lunoraD1Adapter } from "./adapter.mjs";
2
- import { L as LunoraAuth, a as LunoraAuthOptions } from "./packem_shared/create-auth.d-M36jwG_Y.mjs";
3
- export { c as createAuth } from "./packem_shared/create-auth.d-M36jwG_Y.mjs";
1
+ import { D as DoStorageLike } from "./packem_shared/adapter.d-DrD3bb1u.mjs";
2
+ export { l as lunoraAuthAdapter, a as lunoraD1Adapter, b as lunoraDoAdapter } from "./packem_shared/adapter.d-DrD3bb1u.mjs";
3
+ import { LunoraError } from '@lunora/errors';
4
+ import { L as LunoraAuth, a as LunoraAuthOptions } from "./packem_shared/create-auth.d-De6IOirt.mjs";
5
+ export { c as createAuth, r as resolveAuthOptions } from "./packem_shared/create-auth.d-De6IOirt.mjs";
6
+ import { AppendAuthAuditEntry, AppendAuthAuditOptions, AuthAuditEvent, AuthAuditReader } from "./audit.mjs";
7
+ export { AUTH_AUDIT_TABLE, type AuthAuditEntry, type AuthAuditOutcome, type ReadAuthAuditOptions, appendAuthAuditEntry, createAuthAuditReader, ensureAuthAuditTable, readAuthAuditLog } from "./audit.mjs";
8
+ import { createAuthMiddleware } from 'better-auth/api';
9
+ import { SqlExecutor } from "./sql-store.mjs";
10
+ export { createSqlAuthStore, d1Executor } from "./sql-store.mjs";
11
+ import { BetterAuthOptions } from 'better-auth';
12
+ import { EmailClassification, EmailGateConfig } from "./email-guard.mjs";
13
+ export { type EmailClass, type EmailGateMiddlewareOptions, assertEmailAllowed, classifyEmail, emailGateMiddleware, loadEmailDomainLists } from "./email-guard.mjs";
4
14
  export { type LunoraAuthApiContext, LunoraAuthHeadersError, type WithAuthPluginsMiddleware, type WithAuthPluginsOptions, withAuthPlugins } from "./middleware.mjs";
5
15
  export { default as authTables } from "./schema.mjs";
6
- import { BetterAuthOptions } from 'better-auth';
7
- export { type SqlExecutor, createSqlAuthStore, d1Executor } from "./sql-store.mjs";
8
16
  export { type AuthQuery, type AuthRow, type AuthStore, type AuthWhereClause, createMemoryAuthStore, matchesWhere } from "./store.mjs";
9
17
  export { type FetchLike, TURNSTILE_VERIFY_ENDPOINT, type TurnstileVerifyResult, type VerifyTurnstileOptions, verifyTurnstile } from "./turnstile.mjs";
10
18
  export { type VerifyTurnstileMiddlewareOptions, verifyTurnstileMiddleware } from "./turnstile-middleware.mjs";
11
19
  import 'better-auth/adapters';
12
20
  import '@lunora/server';
13
21
  /**
14
- * A timestamp as it leaves the admin API: epoch-ms (better-auth stores `Date`s,
15
- * which we normalize on output) or `null` when the column is unset.
16
- */
22
+ * A timestamp as it leaves the admin API: epoch-ms (better-auth stores `Date`s,
23
+ * which we normalize on output) or `null` when the column is unset.
24
+ */
17
25
  type AuthTimestamp = null | number;
18
26
  /**
19
- * One user row as the admin API surfaces it. The fixed keys mirror better-auth's
20
- * core `user` table plus the `admin()` plugin columns (`role`/`banned`/…); the
21
- * index signature carries any app-defined `user.additionalFields` so callers
22
- * (the studio) can render them generically. Password material never lives on
23
- * this row (it's in the `account` table) and is never returned.
24
- */
27
+ * One user row as the admin API surfaces it. The fixed keys mirror better-auth's
28
+ * core `user` table plus the `admin()` plugin columns (`role`/`banned`/…); the
29
+ * index signature carries any app-defined `user.additionalFields` so callers
30
+ * (the studio) can render them generically. Password material never lives on
31
+ * this row (it's in the `account` table) and is never returned.
32
+ */
25
33
  interface AuthAdminUser {
26
34
  [key: string]: unknown;
27
35
  banExpires?: AuthTimestamp;
@@ -37,12 +45,12 @@ interface AuthAdminUser {
37
45
  updatedAt?: AuthTimestamp;
38
46
  }
39
47
  /**
40
- * One session row as the admin API surfaces it. Mirrors better-auth's `session`
41
- * table; `impersonatedBy` is set when the session was minted by
42
- * {@link AuthAdmin.impersonateUser}. The signing `token` is stripped — it's a
43
- * bearer credential, and the only place we hand one back is the explicit
44
- * impersonation flow.
45
- */
48
+ * One session row as the admin API surfaces it. Mirrors better-auth's `session`
49
+ * table; `impersonatedBy` is set when the session was minted by
50
+ * {@link AuthAdmin.impersonateUser}. The signing `token` is stripped — it's a
51
+ * bearer credential, and the only place we hand one back is the explicit
52
+ * impersonation flow.
53
+ */
46
54
  interface AuthAdminSession {
47
55
  [key: string]: unknown;
48
56
  createdAt?: AuthTimestamp;
@@ -90,6 +98,35 @@ interface AuthInvitation {
90
98
  role?: null | string;
91
99
  status?: null | string;
92
100
  }
101
+ /** One team row (from the `organization` plugin with `teams.enabled`). */
102
+ interface AuthTeam {
103
+ [key: string]: unknown;
104
+ createdAt?: AuthTimestamp;
105
+ id: string;
106
+ name?: null | string;
107
+ organizationId: string;
108
+ }
109
+ /** One team-membership row (teams). */
110
+ interface AuthTeamMember {
111
+ [key: string]: unknown;
112
+ createdAt?: AuthTimestamp;
113
+ id: string;
114
+ teamId: string;
115
+ userId: string;
116
+ }
117
+ /**
118
+ * One custom organization role (from the organization plugin's dynamic
119
+ * access-control). `permission` is a JSON string of a `resource → actions[]` map
120
+ * as stored; the studio parses it for display/editing.
121
+ */
122
+ interface AuthOrgRole {
123
+ [key: string]: unknown;
124
+ createdAt?: AuthTimestamp;
125
+ id: string;
126
+ organizationId: string;
127
+ permission?: null | string;
128
+ role?: null | string;
129
+ }
93
130
  /** One registered passkey. Credential secrets (`publicKey`) are stripped. */
94
131
  interface AuthPasskey {
95
132
  [key: string]: unknown;
@@ -105,12 +142,12 @@ interface AuthPage<T> {
105
142
  total: number;
106
143
  }
107
144
  /**
108
- * Which admin surfaces a given auth instance supports, derived from the enabled
109
- * better-auth plugins (and any {@link CreateAuthAdminOptions.features} overrides).
110
- * The studio calls {@link AuthAdmin.capabilities} once and renders only the
111
- * panels whose capability is `true` — so a deployment that doesn't enable, say,
112
- * the `organization` plugin never shows an Organizations section.
113
- */
145
+ * Which admin surfaces a given auth instance supports, derived from the enabled
146
+ * better-auth plugins (and any {@link CreateAuthAdminOptions.features} overrides).
147
+ * The studio calls {@link AuthAdmin.capabilities} once and renders only the
148
+ * panels whose capability is `true` — so a deployment that doesn't enable, say,
149
+ * the `organization` plugin never shows an Organizations section.
150
+ */
114
151
  interface AuthCapabilities {
115
152
  /** Linked-account browsing/unlinking — core (the `account` table always exists). */
116
153
  accounts: boolean;
@@ -123,6 +160,64 @@ interface AuthCapabilities {
123
160
  /** The `two-factor` plugin: per-user 2FA status / disable. */
124
161
  twoFactor: boolean;
125
162
  }
163
+ /**
164
+ * One app/plugin-defined field the create-user form should render, derived from
165
+ * the merged better-auth `user` table (core + plugin + `additionalFields`). Only
166
+ * user-settable columns are surfaced — server-managed flags (`input: false`),
167
+ * foreign keys (`references`), and the core columns the form already handles
168
+ * (`email`/`name`/`role`/ban state/…) are filtered out upstream.
169
+ */
170
+ interface AuthUserFieldSpec {
171
+ /** Logical field name (the key passed back in `createUser`'s `data`). */
172
+ name: string;
173
+ /** Best-effort plugin id the field originates from (`username`, `phone-number`, …); `undefined` for app `additionalFields`. */
174
+ plugin?: string;
175
+ required: boolean;
176
+ /** Coarse input kind the studio maps to a control (checkbox / number / date / text). */
177
+ type: "boolean" | "date" | "number" | "string";
178
+ unique: boolean;
179
+ }
180
+ /**
181
+ * A rich, read-only description of the deployment's auth configuration for the
182
+ * studio's config panel and dynamic create-user form. Unlike
183
+ * {@link AuthCapabilities} (five booleans that gate panels), this exposes *what*
184
+ * is configured — enabled plugins, email/password + social sign-in, the
185
+ * user-settable fields, organization sub-features (teams / custom roles), and
186
+ * the session + rate-limit policy — without ever leaking a secret.
187
+ */
188
+ interface AuthConfigInfo {
189
+ /** The same capability booleans {@link AuthAdmin.capabilities} returns, embedded so a single call drives the whole panel. */
190
+ capabilities: AuthCapabilities;
191
+ /** Whether email + password sign-in is enabled. */
192
+ emailAndPassword: boolean;
193
+ /** Organization plugin sub-features. */
194
+ organization: {
195
+ enabled: boolean;
196
+ /** Custom roles / dynamic access control (`organizationRole` table present). */
197
+ roles: boolean;
198
+ /** Teams (`team` table present). */
199
+ teams: boolean;
200
+ };
201
+ /** Enabled better-auth plugin ids, sorted. */
202
+ plugins: string[];
203
+ /** Rate-limit policy (window is in seconds). */
204
+ rateLimit: {
205
+ enabled: boolean;
206
+ max?: number;
207
+ window?: number;
208
+ };
209
+ /** Session policy (all durations in seconds). */
210
+ session: {
211
+ cookieCache?: boolean;
212
+ expiresIn?: number;
213
+ freshAge?: number;
214
+ updateAge?: number;
215
+ };
216
+ /** Configured social/OAuth provider ids, sorted. */
217
+ socialProviders: string[];
218
+ /** User-settable extra fields for the create-user form (plugin + app `additionalFields`). */
219
+ userFields: AuthUserFieldSpec[];
220
+ }
126
221
  /** A scalar value usable in an adapter `where` clause / filter. */
127
222
  type WhereValue = boolean | number | string;
128
223
  /** Filtering / paging options for {@link AuthAdmin.listUsers}. */
@@ -149,15 +244,26 @@ interface ImpersonationResult {
149
244
  user: AuthAdminUser;
150
245
  }
151
246
  /**
152
- * The full read + write surface the studio's auth dashboard drives, backed by
153
- * better-auth's tables. Returned by {@link createAuthAdmin}. The runtime accepts
154
- * a structurally-compatible object as its `authAdmin` option and exposes each
155
- * method behind an admin-token-gated endpoint. Methods whose backing plugin is
156
- * absent still exist (they're not conditionally omitted) but the studio gates
157
- * them on {@link AuthAdmin.capabilities}; calling one for an unconfigured plugin
158
- * surfaces the underlying adapter error.
159
- */
247
+ * The full read + write surface the studio's auth dashboard drives, backed by
248
+ * better-auth's tables. Returned by {@link createAuthAdmin}. The runtime accepts
249
+ * a structurally-compatible object as its `authAdmin` option and exposes each
250
+ * method behind an admin-token-gated endpoint. Methods whose backing plugin is
251
+ * absent still exist (they're not conditionally omitted) but the studio gates
252
+ * them on {@link AuthAdmin.capabilities}; calling one for an unconfigured plugin
253
+ * surfaces the underlying adapter error.
254
+ */
160
255
  interface AuthAdmin {
256
+ /** Directly add an existing user as an org member (server-side, no invitation/acceptance). */
257
+ addMember: (input: {
258
+ organizationId: string;
259
+ role?: string;
260
+ userId: string;
261
+ }) => Promise<AuthMember>;
262
+ /** Add a user to a team. */
263
+ addTeamMember: (input: {
264
+ teamId: string;
265
+ userId: string;
266
+ }) => Promise<AuthTeamMember>;
161
267
  banUser: (input: {
162
268
  expiresInSeconds?: number;
163
269
  reason?: string;
@@ -167,6 +273,27 @@ interface AuthAdmin {
167
273
  invitationId: string;
168
274
  }) => Promise<void>;
169
275
  capabilities: () => Promise<AuthCapabilities>;
276
+ /** Rich, read-only description of the auth configuration (plugins, fields, session policy, …). */
277
+ config: () => Promise<AuthConfigInfo>;
278
+ /** Create an organization; optionally seed an `owner` member for `ownerId`. */
279
+ createOrganization: (input: {
280
+ logo?: string;
281
+ metadata?: Record<string, unknown>;
282
+ name: string;
283
+ ownerId?: string;
284
+ slug?: string;
285
+ }) => Promise<AuthOrganization>;
286
+ /** Create a custom org role with a permission grant (a `resource → actions[]` map). */
287
+ createOrgRole: (input: {
288
+ organizationId: string;
289
+ permission: Record<string, string[]>;
290
+ role: string;
291
+ }) => Promise<AuthOrgRole>;
292
+ /** Create a team under an organization. */
293
+ createTeam: (input: {
294
+ name: string;
295
+ organizationId: string;
296
+ }) => Promise<AuthTeam>;
170
297
  createUser: (input: {
171
298
  data?: Record<string, unknown>;
172
299
  email: string;
@@ -174,6 +301,14 @@ interface AuthAdmin {
174
301
  password?: string;
175
302
  role?: string | string[];
176
303
  }) => Promise<AuthAdminUser>;
304
+ /** Delete an organization and cascade-delete its members, invitations, teams, and custom roles. */
305
+ deleteOrganization: (input: {
306
+ organizationId: string;
307
+ }) => Promise<void>;
308
+ /** Delete a custom org role. */
309
+ deleteOrgRole: (input: {
310
+ roleId: string;
311
+ }) => Promise<void>;
177
312
  deletePasskey: (input: {
178
313
  passkeyId: string;
179
314
  }) => Promise<void>;
@@ -183,6 +318,13 @@ interface AuthAdmin {
183
318
  impersonateUser: (input: {
184
319
  userId: string;
185
320
  }) => Promise<ImpersonationResult>;
321
+ /** Create a pending email invitation to an org (no acceptance side effects). */
322
+ inviteMember: (input: {
323
+ email: string;
324
+ inviterId?: string;
325
+ organizationId: string;
326
+ role?: string;
327
+ }) => Promise<AuthInvitation>;
186
328
  listAccounts: (input: {
187
329
  userId: string;
188
330
  }) => Promise<AuthAccount[]>;
@@ -200,6 +342,12 @@ interface AuthAdmin {
200
342
  limit?: number;
201
343
  offset?: number;
202
344
  }) => Promise<AuthPage<AuthOrganization>>;
345
+ /** List an org's custom roles. */
346
+ listOrgRoles: (options: {
347
+ limit?: number;
348
+ offset?: number;
349
+ organizationId: string;
350
+ }) => Promise<AuthPage<AuthOrgRole>>;
203
351
  listPasskeys: (input: {
204
352
  userId: string;
205
353
  }) => Promise<AuthPasskey[]>;
@@ -208,10 +356,30 @@ interface AuthAdmin {
208
356
  offset?: number;
209
357
  userId?: string;
210
358
  }) => Promise<AuthPage<AuthAdminSession>>;
359
+ /** List a team's members. */
360
+ listTeamMembers: (options: {
361
+ limit?: number;
362
+ offset?: number;
363
+ teamId: string;
364
+ }) => Promise<AuthPage<AuthTeamMember>>;
365
+ /** List an org's teams. */
366
+ listTeams: (options: {
367
+ limit?: number;
368
+ offset?: number;
369
+ organizationId: string;
370
+ }) => Promise<AuthPage<AuthTeam>>;
211
371
  listUsers: (options: ListUsersOptions) => Promise<AuthPage<AuthAdminUser>>;
212
372
  removeMember: (input: {
213
373
  memberId: string;
214
374
  }) => Promise<void>;
375
+ /** Delete a team and its memberships. */
376
+ removeTeam: (input: {
377
+ teamId: string;
378
+ }) => Promise<void>;
379
+ /** Remove a member from a team. */
380
+ removeTeamMember: (input: {
381
+ teamMemberId: string;
382
+ }) => Promise<void>;
215
383
  removeUser: (input: {
216
384
  userId: string;
217
385
  }) => Promise<void>;
@@ -236,6 +404,29 @@ interface AuthAdmin {
236
404
  accountId: string;
237
405
  userId: string;
238
406
  }) => Promise<void>;
407
+ /** Change a member's role. */
408
+ updateMemberRole: (input: {
409
+ memberId: string;
410
+ role: string | string[];
411
+ }) => Promise<AuthMember>;
412
+ /** Update an organization's name/slug/logo/metadata. */
413
+ updateOrganization: (input: {
414
+ logo?: string;
415
+ metadata?: Record<string, unknown>;
416
+ name?: string;
417
+ organizationId: string;
418
+ slug?: string;
419
+ }) => Promise<AuthOrganization>;
420
+ /** Replace a custom org role's permission grant. */
421
+ updateOrgRole: (input: {
422
+ permission: Record<string, string[]>;
423
+ roleId: string;
424
+ }) => Promise<AuthOrgRole>;
425
+ /** Rename a team. */
426
+ updateTeam: (input: {
427
+ name: string;
428
+ teamId: string;
429
+ }) => Promise<AuthTeam>;
239
430
  updateUser: (input: {
240
431
  data: Record<string, unknown>;
241
432
  userId: string;
@@ -244,143 +435,503 @@ interface AuthAdmin {
244
435
  /** Options for {@link createAuthAdmin}. */
245
436
  interface CreateAuthAdminOptions {
246
437
  /**
247
- * Force individual {@link AuthCapabilities} on or off regardless of which
248
- * plugins are detected — e.g. `{ impersonate: false }`-style opt-outs by
249
- * setting `admin: false`, or hiding linked accounts with `accounts: false`.
250
- * A capability is reported only when both its plugin is enabled *and* its
251
- * override isn't `false`.
252
- */
438
+ * Force individual {@link AuthCapabilities} on or off regardless of which
439
+ * plugins are detected — e.g. `{ impersonate: false }`-style opt-outs by
440
+ * setting `admin: false`, or hiding linked accounts with `accounts: false`.
441
+ * A capability is reported only when both its plugin is enabled *and* its
442
+ * override isn't `false`.
443
+ */
253
444
  features?: Partial<AuthCapabilities>;
254
445
  /**
255
- * User id recorded as the impersonator on sessions minted by
256
- * {@link AuthAdmin.impersonateUser}. Defaults to the impersonated user's own
257
- * id (a self-reference) since the trusted admin plane has no acting user.
258
- */
446
+ * User id recorded as the impersonator on sessions minted by
447
+ * {@link AuthAdmin.impersonateUser}. Defaults to the impersonated user's own
448
+ * id (a self-reference) since the trusted admin plane has no acting user.
449
+ */
259
450
  impersonatedBy?: string;
260
451
  /**
261
- * How long (in seconds) an impersonation session lives. Must be a positive
262
- * finite integer. Capped at 24 × {@link DEFAULT_IMPERSONATION_SECONDS}
263
- * (86 400 s / 24 h). Defaults to {@link DEFAULT_IMPERSONATION_SECONDS}
264
- * (3 600 s / 1 h).
265
- */
452
+ * How long (in seconds) an impersonation session lives. Must be a positive
453
+ * finite integer. Capped at 24 × {@link DEFAULT_IMPERSONATION_SECONDS}
454
+ * (86 400 s / 24 h). Defaults to {@link DEFAULT_IMPERSONATION_SECONDS}
455
+ * (3 600 s / 1 h).
456
+ */
266
457
  impersonationSeconds?: number;
267
458
  }
268
459
  /**
269
- * A normalized failure from an admin operation. better-auth throws `APIError`s
270
- * carrying a `body.code` (e.g. `USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL`); we
271
- * surface that `code` so the runtime can map it onto an HTTP status and the
272
- * studio can show a meaningful message instead of a generic 500.
273
- */
274
- declare class LunoraAuthAdminError extends Error {
275
- readonly code: string;
460
+ * A normalized failure from an admin operation. better-auth throws `APIError`s
461
+ * carrying a `body.code` (e.g. `USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL`); we
462
+ * surface that `code` so the runtime can map it onto an HTTP status and the
463
+ * studio can show a meaningful message instead of a generic 500.
464
+ */
465
+ declare class LunoraAuthAdminError extends LunoraError {
276
466
  constructor(message: string, code: string);
277
467
  }
278
468
  /**
279
- * Build the studio's auth user-management plane on top of better-auth.
280
- *
281
- * Pass the result as the runtime's `authAdmin` option; the runtime exposes each
282
- * method behind an admin-token-gated `/_lunora/admin/auth/*` endpoint. The set
283
- * of usable surfaces is reported by {@link AuthAdmin.capabilities} — derived
284
- * from the enabled better-auth plugins, so enabling `admin()`, `organization()`,
285
- * `twoFactor()`, or the passkey plugin in the auth config is what lights up the
286
- * matching dashboard panels.
287
- *
288
- * **Trust model — important.** These operations talk to better-auth's
289
- * `internalAdapter` (and `adapter`/password hasher) **directly**, deliberately
290
- * bypassing the plugins' own endpoints, which require the caller to hold an
291
- * admin-role session. That session check is the wrong gate here: the runtime
292
- * already authorizes every call with `LUNORA_ADMIN_TOKEN`, so this helper acts
293
- * as a trusted server-side operator. It is therefore not an end-user-callable
294
- * API — never expose it on a path that isn't admin-token gated.
295
- *
296
- * `auth.$context` is a promise (better-auth resolves the adapter, password
297
- * config, etc. lazily); we memoize it so the first call pays the cost once.
298
- */
469
+ * Build the studio's auth user-management plane on top of better-auth.
470
+ *
471
+ * Pass the result as the runtime's `authAdmin` option; the runtime exposes each
472
+ * method behind an admin-token-gated `/_lunora/admin/auth/*` endpoint. The set
473
+ * of usable surfaces is reported by {@link AuthAdmin.capabilities} — derived
474
+ * from the enabled better-auth plugins, so enabling `admin()`, `organization()`,
475
+ * `twoFactor()`, or the passkey plugin in the auth config is what lights up the
476
+ * matching dashboard panels.
477
+ *
478
+ * **Trust model — important.** These operations talk to better-auth's
479
+ * `internalAdapter` (and `adapter`/password hasher) **directly**, deliberately
480
+ * bypassing the plugins' own endpoints, which require the caller to hold an
481
+ * admin-role session. That session check is the wrong gate here: the runtime
482
+ * already authorizes every call with `LUNORA_ADMIN_TOKEN`, so this helper acts
483
+ * as a trusted server-side operator. It is therefore not an end-user-callable
484
+ * API — never expose it on a path that isn't admin-token gated.
485
+ *
486
+ * `auth.$context` is a promise (better-auth resolves the adapter, password
487
+ * config, etc. lazily); we memoize it so the first call pays the cost once.
488
+ */
299
489
  declare const createAuthAdmin: (auth: LunoraAuth, options?: CreateAuthAdminOptions) => AuthAdmin;
490
+ /** Configuration for {@link authAuditHook}. */
491
+ interface AuthAuditHookConfig extends AppendAuthAuditOptions {
492
+ /**
493
+ * Where to persist the trail — the same {@link SqlExecutor} seam better-auth's
494
+ * store rides (`d1Executor(env.DB)`), so events land in the auth D1 database.
495
+ */
496
+ executor: SqlExecutor;
497
+ /**
498
+ * Optional export tap (pairs with SIEM forwarding): called with each redacted
499
+ * entry right after it is persisted, so a deployment can fan events out to an
500
+ * external sink. Rejections/throws are swallowed so forwarding can't break an
501
+ * auth request.
502
+ */
503
+ onRecord?: (entry: AppendAuthAuditEntry) => Promise<void> | void;
504
+ }
505
+ /**
506
+ * Structural view of the fields we read off better-auth's after-hook context —
507
+ * kept loose to avoid coupling to internal types.
508
+ *
509
+ * `body` is the parsed request body better-auth's middleware context exposes
510
+ * as `ctx.body` — pinned present and populated (e.g. `.email`) in the after-hook
511
+ * by `__tests__/audit-hooks.behaviour.test.ts` (plan 280 S0).
512
+ */
513
+ interface AuditHookContext {
514
+ body?: Record<string, unknown>;
515
+ context?: {
516
+ newSession?: {
517
+ session?: {
518
+ userId?: string;
519
+ };
520
+ user?: {
521
+ email?: string;
522
+ id?: string;
523
+ };
524
+ } | null;
525
+ returned?: unknown;
526
+ session?: {
527
+ session?: {
528
+ userId?: string;
529
+ };
530
+ user?: {
531
+ email?: string;
532
+ id?: string;
533
+ };
534
+ } | null;
535
+ };
536
+ headers?: Headers;
537
+ path?: string;
538
+ request?: Request;
539
+ }
540
+ /**
541
+ * Map a better-auth endpoint path to the security event it represents, or
542
+ * `undefined` for endpoints not worth auditing (session reads, config, …). Match
543
+ * is by suffix so a caller `basePath` prefix (`/api/auth`) never affects it.
544
+ *
545
+ * Sign-in is split by what the endpoint actually DOES (plan 280 §4):
546
+ *
547
+ * - `/sign-in/social`, `/sign-in/magic-link` only DISPATCH — the first mints a
548
+ * provider redirect URL, the second sends an email. Nobody is authenticated
549
+ * yet, so these are `sign-in-initiated`, not `sign-in`.
550
+ * - `/callback/:id` (social + generic-oauth), `/magic-link/verify`, and every
551
+ * `/two-factor/verify-*` (`verify-totp` / `verify-otp` / `verify-backup-code`
552
+ * — all three complete a challenged sign-in the same way) are where a
553
+ * session actually gets issued, so they join credential sign-ins
554
+ * (`/sign-in/email`, `/sign-in/username`, `/sign-in/phone-number`, …) as
555
+ * plain `sign-in`. They were NOT recorded at all before this change.
556
+ *
557
+ * `/oauth2/callback/*` is deliberately NOT matched here: it does not exist as
558
+ * an endpoint in the pinned `1.7.0-rc.2` (checked against the installed
559
+ * `better-auth` and `@better-auth/*` dist — generic-oauth reuses the core
560
+ * `/callback/:id` endpoint, it does not register its own path). `@better-auth/sso`
561
+ * does expose its own `/sso/callback/:providerId`, but `@lunora/auth`'s
562
+ * `plugins.ts` does not currently re-export `sso` — recorded as a gap for a
563
+ * follow-up (plan 280 §9 Q1), not matched here since it is unreachable through
564
+ * this package today.
565
+ */
566
+ declare const eventForPath: (path: string) => AuthAuditEvent | undefined;
567
+ /**
568
+ * Build the entry a given after-hook context should record, or `undefined` when
569
+ * the path is not an audited security event. Exported for direct unit testing of
570
+ * the classification/extraction without spinning up better-auth.
571
+ *
572
+ * Does NOT attempt to distinguish a 2FA-challenged credential sign-in from a
573
+ * fully successful one (a `sign-in-challenged` event, as an earlier design for
574
+ * this change proposed) — pinned in `__tests__/audit-hooks.behaviour.test.ts`
575
+ * (plan 280 S0): better-auth runs the APP's own `hooks.after` BEFORE the
576
+ * `twoFactor` plugin's own after-hook that rewrites the response to
577
+ * `{ twoFactorRedirect: true }` and nulls `ctx.context.newSession`. By the time
578
+ * THIS hook runs, `context.returned`/`context.newSession` still reflect the
579
+ * pre-interception, fully-successful sign-in — there is nothing here to detect
580
+ * the challenge from. Distinguishing it would need a different seam (e.g. a
581
+ * plugin-ordered-after-`twoFactor` hook, or reading `twoFactor`'s own
582
+ * database state) and is left to a follow-up.
583
+ */
584
+ declare const buildAuditEntry: (context: AuditHookContext, now?: number) => AppendAuthAuditEntry | undefined;
585
+ /**
586
+ * Create the better-auth `hooks.after` middleware that records the auth/security
587
+ * audit trail. Assign it to `hooks.after` (or compose via {@link withAuthAudit}).
588
+ *
589
+ * ```ts
590
+ * const auth = createAuth({
591
+ * secret: env.AUTH_SECRET,
592
+ * database: lunoraD1Adapter(env.DB),
593
+ * hooks: { after: authAuditHook({ executor: d1Executor(env.DB), retention: 100_000 }) },
594
+ * });
595
+ * ```
596
+ *
597
+ * ## Behaviour change (plan 280) — `onRecord`/SIEM consumers keyed on `event` strings, read this
598
+ *
599
+ * | Endpoint | Before | After |
600
+ * | -------------------------------------------------- | ------------------- | --------------------- |
601
+ * | `/sign-in/email` (and other credential sign-ins) | `sign-in` | `sign-in` (unchanged) |
602
+ * | `/sign-in/social` | `sign-in` | `sign-in-initiated` |
603
+ * | `/sign-in/magic-link` | `sign-in` | `sign-in-initiated` |
604
+ * | `/callback/:id` (social + generic-oauth) | _(not recorded)_ | `sign-in` |
605
+ * | `/magic-link/verify` | _(not recorded)_ | `sign-in` |
606
+ * | `/two-factor/verify-totp` / `-otp` / `-backup-code`| _(not recorded)_ | `sign-in` |
607
+ *
608
+ * A caller matching on `event === "sign-in"` now sees FEWER events for
609
+ * `/sign-in/social` and `/sign-in/magic-link` (they never actually authenticated
610
+ * anyone) and MORE events for the four previously-unrecorded completion
611
+ * endpoints — the net effect is a more truthful count, not a strictly larger or
612
+ * smaller one. `sign-in-initiated` is a new event name (open `AuthAuditEvent`
613
+ * union, so no wire/type break, but SIEM rules enumerating event names should
614
+ * add it). A failed sign-in now also carries `targetEmail` (the attempted
615
+ * address/username) when the request body supplied one — see
616
+ * {@link AppendAuthAuditEntry.targetEmail} — so credential-stuffing attempts can
617
+ * be grouped by target even though they never produce an `actorEmail`.
618
+ *
619
+ * NOT changed: `/sign-in/email` under an active 2FA challenge still records
620
+ * plain `sign-in` / `success` (not a distinct `sign-in-challenged` event) — see
621
+ * {@link buildAuditEntry}'s docblock for why that distinction turned out not to
622
+ * be buildable from this hook.
623
+ */
624
+ declare const authAuditHook: (config: AuthAuditHookConfig) => ReturnType<typeof createAuthMiddleware>;
625
+ /**
626
+ * Merge the audit `hooks.after` middleware into a better-auth options object,
627
+ * composing with any `hooks.after` the caller already set (theirs runs first,
628
+ * then the audit record). Returns a new options object.
629
+ */
630
+ declare const withAuthAudit: <Options extends {
631
+ hooks?: {
632
+ after?: unknown;
633
+ };
634
+ }>(options: Options, config: AuthAuditHookConfig) => Options;
635
+ /**
636
+ * The Durable Object state slice this class needs — structural so unit tests can
637
+ * pass a double without depending on the workers runtime.
638
+ */
639
+ interface AuthDoState {
640
+ storage: DoStorageLike;
641
+ }
642
+ /** Path the worker calls to resolve a request's identity. Not part of `/api/auth/*`. */
643
+ declare const RESOLVE_SESSION_PATH = "/__lunora/auth/session";
644
+ /**
645
+ * Path the worker calls to read the audit log. Also not part of `/api/auth/*`.
646
+ *
647
+ * The audit table lives in this object like every other auth table, so the worker
648
+ * cannot query it directly — same constraint as the session route, same shared secret.
649
+ */
650
+ declare const READ_AUDIT_PATH = "/__lunora/auth/audit";
651
+ /** Header carrying the shared secret that authenticates the calling worker. */
652
+ declare const INTERNAL_SECRET_HEADER = "x-lunora-auth-do-secret";
653
+ /**
654
+ * Options for the auth DO, beyond the better-auth options themselves.
655
+ * @experimental
656
+ */
657
+ interface AuthDoOptions {
658
+ /** Base path the auth routes are served under. Must match the worker's. */
659
+ basePath?: string;
660
+ /**
661
+ * Shared secret authenticating the worker on {@link RESOLVE_SESSION_PATH}.
662
+ *
663
+ * The DO binding is reachable from any worker bound to the namespace, so the
664
+ * binding alone is not an authorization boundary — same reasoning as
665
+ * `SessionDO`'s `SESSION_DO_SECRET`. When this is unset the internal route is
666
+ * refused outright rather than served unauthenticated: a missing secret is a
667
+ * misconfiguration, and answering identity questions to anyone is the one
668
+ * failure mode worth being loud about. `/api/auth/*` is unaffected — those
669
+ * routes carry their own credentials.
670
+ */
671
+ internalSecret?: string;
672
+ }
673
+ /**
674
+ * Base class for an app's auth Durable Object. Subclass it (or let codegen emit the
675
+ * subclass) and register the subclass in `wrangler.jsonc`.
676
+ *
677
+ * ```ts
678
+ * export class AuthDO extends LunoraAuthDO {
679
+ * public constructor(state: DurableObjectState, env: Env) {
680
+ * super(state, () => ({ secret: env.AUTH_SECRET, plugins: [scim({ … })] }), {
681
+ * internalSecret: env.AUTH_DO_SECRET,
682
+ * });
683
+ * }
684
+ * }
685
+ * ```
686
+ * @experimental
687
+ */
688
+ declare class LunoraAuthDO {
689
+ #private;
690
+ /**
691
+ * @param state The Durable Object state — its `storage` becomes better-auth's database.
692
+ * @param optionsFactory Builds the better-auth options. Called once, lazily, on the first request.
693
+ * @param options Auth-DO specific options (base path, internal secret).
694
+ */
695
+ constructor(state: AuthDoState, optionsFactory: () => LunoraAuthOptions, options?: AuthDoOptions);
696
+ /**
697
+ * Serve an auth request. Routes under `basePath` go to better-auth; the internal
698
+ * session route is handled here; anything else is a 404.
699
+ */
700
+ fetch(request: Request): Promise<Response>;
701
+ }
702
+ /**
703
+ * The `CREATE TABLE` / `CREATE INDEX` statements for a better-auth config, in
704
+ * execution order (every table before any index).
705
+ *
706
+ * All statements are `IF NOT EXISTS`, so this is safe to run on every cold start —
707
+ * which is how `LunoraAuthDO` uses it. It creates; it never alters or drops, so a
708
+ * schema that has already diverged is left alone rather than half-migrated.
709
+ *
710
+ * Physical names throughout: `modelName` for tables, `fieldName ?? key` for columns,
711
+ * and better-auth's own resolved `columns` / `name` for indexes — so an index name
712
+ * here is the name better-auth's introspection expects to find.
713
+ * @param options The better-auth options the DO will run — the plugin list decides which tables exist.
714
+ * @returns SQL statements to execute in order.
715
+ * @experimental
716
+ */
717
+ declare const authDoSchemaStatements: (options: LunoraAuthOptions) => string[];
718
+ /**
719
+ * `ALTER TABLE … ADD COLUMN` statements for columns the live schema is missing.
720
+ *
721
+ * ## Why this is needed at all
722
+ *
723
+ * {@link authDoSchemaStatements} is entirely `IF NOT EXISTS`, which makes it safe to
724
+ * re-run but blind to change: a *new table* appears on the next cold start, a *new
725
+ * column on an existing table* never does. That is not a hypothetical — enabling the
726
+ * `admin` plugin after first deploy adds `role` / `banned` / `banExpires` to `user`,
727
+ * and without this the object would keep serving a `user` table that cannot hold them.
728
+ *
729
+ * Additive only. Nothing here drops, renames, or retypes a column: a column that
730
+ * exists is left exactly as it is, so a schema someone has deliberately diverged is
731
+ * never "corrected" underneath them.
732
+ *
733
+ * ## What SQLite will not let us do
734
+ *
735
+ * `ADD COLUMN` cannot introduce a `NOT NULL` column without a default (existing rows
736
+ * would violate it immediately), and cannot introduce `UNIQUE`. So a required column
737
+ * with a static default is added with both; a required column *without* one is added
738
+ * **nullable**, which differs from what a fresh `CREATE TABLE` would produce. That is
739
+ * the deliberate trade: better-auth writes these fields on every insert, so nullable
740
+ * is harmless, whereas refusing to add the column at all would leave the object
741
+ * broken. Uniqueness is unaffected — better-auth expresses it as a separate index, and
742
+ * those are `IF NOT EXISTS`, so they are created by the statements above.
743
+ * @param options The better-auth options the DO runs, already resolved.
744
+ * @param existingColumns Physical column names currently present on a table; empty/absent for a table that does not exist yet (it will be created instead).
745
+ * @returns SQL statements to execute in order; empty when the live schema is current.
746
+ * @experimental
747
+ */
748
+ declare const authDoColumnAdditions: (options: LunoraAuthOptions, existingColumns: (table: string) => Iterable<string>) => string[];
749
+ /** The slice of a Durable Object namespace this needs — structural, so tests need no runtime. */
750
+ interface AuthNamespaceLike {
751
+ get: (id: unknown) => {
752
+ fetch: (request: Request) => Promise<Response>;
753
+ };
754
+ idFromName: (name: string) => unknown;
755
+ }
756
+ /** What {@link createDoAuthWiring} needs, already resolved against `env`. */
757
+ interface DoAuthWiringOptions {
758
+ /** Base path the auth routes are served under. Defaults to `/api/auth`. */
759
+ basePath?: string;
760
+ /**
761
+ * Shared secret presented on the object's internal session route. `undefined`
762
+ * means identity resolution fails closed — see {@link DoAuthWiring.resolveIdentity}.
763
+ */
764
+ internalSecret: string | undefined;
765
+ /** The bound namespace, or `undefined` when the binding is absent from `env`. */
766
+ namespace: AuthNamespaceLike | undefined;
767
+ /**
768
+ * Name of the object instance holding the auth tables. Defaults to `"auth"`.
769
+ *
770
+ * One object owns the whole auth schema, so this exists to let an app pick the
771
+ * name (or run separate objects per deployment/tenant) rather than being pinned to
772
+ * a hardcoded one.
773
+ */
774
+ objectName?: string;
775
+ }
776
+ /** The worker options DO-backed auth replaces. */
777
+ interface DoAuthWiring {
778
+ /**
779
+ * Reads the audit log out of the object, so the studio's audit feed works in DO
780
+ * mode. Answers an empty page rather than throwing when the object is unreachable
781
+ * or no secret is configured — an unavailable feed should read as empty, not 500
782
+ * the studio.
783
+ */
784
+ auditReader: AuthAuditReader;
785
+ /** Forwards `/api/auth/*` to the object; `undefined` for anything else. */
786
+ authHandler: (request: Request) => Promise<Response | undefined>;
787
+ /** Resolves a request's identity by asking the object. `null` when anonymous, unreachable, or ungated. */
788
+ resolveIdentity: (request: Request) => Promise<null | {
789
+ userId: string;
790
+ }>;
791
+ }
792
+ /**
793
+ * Build the worker-side wiring for an auth Durable Object.
794
+ *
795
+ * Every failure path answers "not authenticated" rather than throwing: this runs on
796
+ * the request path for every request that touches `ctx.auth`, and a throw there would
797
+ * turn a misconfiguration into a 500 on traffic that has nothing to do with auth.
798
+ * @param options The resolved namespace, secret, and names.
799
+ * @returns The `authHandler` / `resolveIdentity` pair.
800
+ * @experimental
801
+ */
802
+ declare const createDoAuthWiring: (options: DoAuthWiringOptions) => DoAuthWiring;
803
+ /** better-auth's `databaseHooks` shape, derived so a rename upstream fails to compile rather than silently mis-hooking. */
804
+ type DatabaseHooks = NonNullable<BetterAuthOptions["databaseHooks"]>;
805
+ /** Config for the signup gate hooks: the base {@link EmailGateConfig} plus an optional classification tap. */
806
+ interface EmailGateHookConfig extends EmailGateConfig {
807
+ /**
808
+ * Called with the resolved classification once the gate passes, so app policy
809
+ * can react to `free` vs `business` at signup (e.g. tag the account). Never
810
+ * fires when the gate rejects. `context` is better-auth's endpoint context
811
+ * (`null` outside a request, e.g. an internal create).
812
+ */
813
+ onClassify?: (classification: EmailClassification, user: Record<string, unknown>, context: unknown) => void;
814
+ }
815
+ /**
816
+ * Produce a `databaseHooks` fragment that gates better-auth's native signup on
817
+ * the email-domain policy. Spread it into `createAuth({ databaseHooks: … })`, or
818
+ * use {@link withEmailGate} to merge it (composing with any existing
819
+ * `user.create.before`).
820
+ *
821
+ * ```ts
822
+ * const auth = createAuth({
823
+ * secret: env.AUTH_SECRET,
824
+ * database: lunoraD1Adapter(env.DB),
825
+ * databaseHooks: emailGateDatabaseHooks({ blockDisposable: true }),
826
+ * });
827
+ * ```
828
+ */
829
+ declare const emailGateDatabaseHooks: (config?: EmailGateHookConfig) => DatabaseHooks;
830
+ /**
831
+ * Merge the email-domain signup gate into an existing better-auth options object,
832
+ * preserving any `databaseHooks` the caller already set. If they already declared
833
+ * a `user.create.before`, the gate runs first (rejecting disposable signups
834
+ * before their hook sees them), then theirs runs on the (possibly rewritten) user.
835
+ *
836
+ * ```ts
837
+ * const auth = createAuth(withEmailGate({ secret, database }, { blockDisposable: true }));
838
+ * ```
839
+ */
840
+ declare const withEmailGate: (options: BetterAuthOptions, config?: EmailGateHookConfig) => BetterAuthOptions;
300
841
  /**
301
- * Default basePath used by better-auth's client + handler. Override via the
302
- * second argument if you mount the auth routes somewhere else.
303
- */
842
+ * Default basePath used by better-auth's client + handler. Override via the
843
+ * second argument if you mount the auth routes somewhere else.
844
+ */
304
845
  declare const DEFAULT_AUTH_BASE_PATH: string;
305
846
  /**
306
- * Route an inbound `Request` to better-auth if the path falls under
307
- * `basePath`; otherwise return `null` so the caller can continue dispatching.
308
- *
309
- * Better-auth handles arbitrarily nested paths (`/api/auth/sign-in/email`,
310
- * `/api/auth/callback/github`, …), so we use prefix matching instead of the
311
- * exact-path map `createWorker` consumes for top-level routes.
312
- */
847
+ * Route an inbound `Request` to better-auth if the path falls under
848
+ * `basePath`; otherwise return `null` so the caller can continue dispatching.
849
+ *
850
+ * Better-auth handles arbitrarily nested paths (`/api/auth/sign-in/email`,
851
+ * `/api/auth/callback/github`, …), so we use prefix matching instead of the
852
+ * exact-path map `createWorker` consumes for top-level routes.
853
+ */
313
854
  declare const handleAuthRequest: (auth: LunoraAuth, request: Request, basePath?: string) => Promise<Response | undefined>;
314
855
  /**
315
- * Apply better-auth's required schema (`user`, `session`, `account`,
316
- * `verification`) to the configured database. Idempotent — better-auth
317
- * diffs the existing schema and only runs the missing DDL.
318
- *
319
- * Cached per `options` reference so the diff cost (one PRAGMA-style sweep
320
- * per table) doesn't fire on every request. In Cloudflare Workers the same
321
- * `env.DB` binding is reused across invocations within a single isolate, so
322
- * caching against the options object — which captures the binding — is
323
- * sufficient.
324
- *
325
- * For production you should prefer pre-applying the schema at deploy time
326
- * via `compileMigrationsSql` + `wrangler d1 execute`; this helper exists for
327
- * dev/playground and small deployments.
328
- */
856
+ * Apply better-auth's required schema (`user`, `session`, `account`,
857
+ * `verification`) to the configured database. Idempotent — better-auth
858
+ * diffs the existing schema and only runs the missing DDL.
859
+ *
860
+ * Cached per `options` reference so the diff cost (one PRAGMA-style sweep
861
+ * per table) doesn't fire on every request. In Cloudflare Workers the same
862
+ * `env.DB` binding is reused across invocations within a single isolate, so
863
+ * caching against the options object — which captures the binding — is
864
+ * sufficient.
865
+ *
866
+ * For production you should prefer pre-applying the schema at deploy time
867
+ * via `compileMigrationsSql` + `wrangler d1 execute`; this helper exists for
868
+ * dev/playground and small deployments.
869
+ */
329
870
  declare const ensureMigrated: (auth: LunoraAuth | {
330
871
  options: LunoraAuthOptions;
331
872
  }) => Promise<void>;
332
873
  /**
333
- * Compile better-auth's migrations to a single SQL string. Useful for
334
- * `wrangler d1 execute --file -` in CI so the deploy step applies the schema
335
- * before the first user request.
336
- */
874
+ * Compile better-auth's migrations to a single SQL string. Useful for
875
+ * `wrangler d1 execute --file -` in CI so the deploy step applies the schema
876
+ * before the first user request.
877
+ *
878
+ * Compiles from the SAME resolved options `createAuth` runs with (via
879
+ * `resolveAuthOptions`), not the raw caller options — so the schema includes the
880
+ * `rateLimit` table the worker's default-on durable limiter writes to. Compiling
881
+ * from the raw options would omit it, and the running worker would then write to
882
+ * a table the migration never created.
883
+ */
337
884
  declare const compileMigrationsSql: (options: LunoraAuthOptions) => Promise<string>;
338
885
  /**
339
- * Lunora-friendly view over better-auth's `session` option.
340
- *
341
- * This is a typed alias for better-auth's own `session` shape — Lunora stays a
342
- * thin wrapper, so we don't reimplement the fields, we just give them a named,
343
- * documented home so callers get autocomplete without reaching into
344
- * `BetterAuthOptions`. The most relevant fields for session rotation / richer
345
- * policies are `expiresIn` (absolute session lifetime, in seconds),
346
- * `updateAge` (rolling-rotation interval, in seconds — how often an active
347
- * session's expiry is pushed forward; `0` rotates on every use),
348
- * `disableSessionRefresh` (turn rolling rotation off entirely so sessions
349
- * expire at the absolute `expiresIn` regardless of activity), `freshAge`
350
- * (freshness window, in seconds, for sensitive operations like account
351
- * deletion; `0` treats every session as fresh — not recommended), and
352
- * `cookieCache` (opt-in signed-cookie session cache to skip DB reads).
353
- *
354
- * See better-auth's `session` option for the full field list.
355
- */
886
+ * Lunora-friendly view over better-auth's `session` option.
887
+ *
888
+ * This is a typed alias for better-auth's own `session` shape — Lunora stays a
889
+ * thin wrapper, so we don't reimplement the fields, we just give them a named,
890
+ * documented home so callers get autocomplete without reaching into
891
+ * `BetterAuthOptions`. The most relevant fields for session rotation / richer
892
+ * policies are `expiresIn` (absolute session lifetime, in seconds),
893
+ * `updateAge` (rolling-rotation interval, in seconds — how often an active
894
+ * session's expiry is pushed forward; `0` rotates on every use),
895
+ * `disableSessionRefresh` (turn rolling rotation off entirely so sessions
896
+ * expire at the absolute `expiresIn` regardless of activity), `freshAge`
897
+ * (freshness window, in seconds, for sensitive operations like account
898
+ * deletion; `0` treats every session as fresh — not recommended), and
899
+ * `cookieCache` (opt-in signed-cookie session cache to skip DB reads).
900
+ *
901
+ * See better-auth's `session` option for the full field list.
902
+ */
356
903
  type SessionPolicy = NonNullable<BetterAuthOptions["session"]>;
357
904
  /**
358
- * Validate a {@link SessionPolicy} and return it unchanged (pass-through).
359
- *
360
- * better-auth happily accepts the `session` block verbatim, so the only job
361
- * here is to catch obviously-broken numeric inputs at construction time —
362
- * negative or non-finite durations — rather than letting them produce
363
- * surprising cookie expiries at runtime. Field names mirror better-auth's
364
- * `session` option exactly so the validated object forwards 1:1.
365
- */
905
+ * Validate a {@link SessionPolicy} and return it unchanged (pass-through).
906
+ *
907
+ * better-auth happily accepts the `session` block verbatim, so the only job
908
+ * here is to catch obviously-broken numeric inputs at construction time —
909
+ * negative or non-finite durations — rather than letting them produce
910
+ * surprising cookie expiries at runtime. Field names mirror better-auth's
911
+ * `session` option exactly so the validated object forwards 1:1.
912
+ */
366
913
  declare const validateSessionPolicy: (policy: SessionPolicy) => SessionPolicy;
367
914
  /**
368
- * Ready-made {@link SessionPolicy} presets covering the common rotation /
369
- * expiry trade-offs. Spread or override fields as needed:
370
- *
371
- * ```ts
372
- * import { createAuth, sessionPresets } from "@lunora/auth";
373
- *
374
- * export const auth = createAuth({
375
- * secret: env.AUTH_SECRET,
376
- * database: env.DB,
377
- * session: { ...sessionPresets.rolling, freshAge: 60 * 5 },
378
- * });
379
- * ```
380
- *
381
- * - `rolling` — balanced default: 7-day absolute expiry, rotated once per day.
382
- * - `strict` short, security-sensitive: 1-hour expiry, 15-minute rotation.
383
- * - `longLived` — low-friction consumer apps: 30-day expiry, daily rotation.
384
- */
915
+ * Ready-made {@link SessionPolicy} presets covering the common rotation /
916
+ * expiry trade-offs. Spread or override fields as needed:
917
+ *
918
+ * ```ts
919
+ * import { createAuth, sessionPresets } from "@lunora/auth";
920
+ *
921
+ * export const auth = createAuth({
922
+ * secret: env.AUTH_SECRET,
923
+ * database: env.DB,
924
+ * session: { ...sessionPresets.rolling, freshAge: 60 * 5 },
925
+ * });
926
+ * ```
927
+ *
928
+ * - `rolling` — balanced default: 7-day absolute expiry, rotated once per day,
929
+ * with a 60s signed-cookie session cache so bursts of authenticated calls
930
+ * skip the per-request DB session read.
931
+ * - `strict` — short, security-sensitive: 1-hour expiry, 15-minute rotation,
932
+ * cookie cache **off** (fast revocation / short freshness is the whole point).
933
+ * - `longLived` — low-friction consumer apps: 30-day expiry, daily rotation,
934
+ * with the same 60s cookie cache as `rolling`.
935
+ */
385
936
  declare const sessionPresets: Record<"longLived" | "rolling" | "strict", SessionPolicy>;
386
- export { type AuthAccount, type AuthAdmin, type AuthAdminSession, type AuthAdminUser, type AuthCapabilities, type AuthInvitation, type AuthMember, type AuthOrganization, type AuthPage, type AuthPasskey, type AuthTimestamp, type CreateAuthAdminOptions, DEFAULT_AUTH_BASE_PATH, type ImpersonationResult, type ListUsersOptions, type LunoraAuth, LunoraAuthAdminError, type LunoraAuthOptions, type SessionPolicy, compileMigrationsSql, createAuthAdmin, ensureMigrated, handleAuthRequest, sessionPresets, validateSessionPolicy };
937
+ export { READ_AUDIT_PATH as AUTH_DO_AUDIT_PATH, INTERNAL_SECRET_HEADER as AUTH_DO_SECRET_HEADER, RESOLVE_SESSION_PATH as AUTH_DO_SESSION_PATH, type AppendAuthAuditEntry, type AppendAuthAuditOptions, type AuthAccount, type AuthAdmin, type AuthAdminSession, type AuthAdminUser, type AuthAuditEvent, type AuthAuditHookConfig, type AuthAuditReader, type AuthCapabilities, type AuthConfigInfo, type AuthDoOptions, type AuthDoState, type AuthInvitation, type AuthMember, type AuthNamespaceLike, type AuthOrgRole, type AuthOrganization, type AuthPage, type AuthPasskey, type AuthTeam, type AuthTeamMember, type AuthTimestamp, type AuthUserFieldSpec, type CreateAuthAdminOptions, DEFAULT_AUTH_BASE_PATH, type DoAuthWiring, type DoAuthWiringOptions, type EmailClassification, type EmailGateConfig, type EmailGateHookConfig, type ImpersonationResult, type ListUsersOptions, type LunoraAuth, LunoraAuthAdminError, LunoraAuthDO, type LunoraAuthOptions, type SessionPolicy, type SqlExecutor, authAuditHook, authDoColumnAdditions, authDoSchemaStatements, buildAuditEntry, compileMigrationsSql, createAuthAdmin, createDoAuthWiring, emailGateDatabaseHooks, ensureMigrated, eventForPath, handleAuthRequest, sessionPresets, validateSessionPolicy, withAuthAudit, withEmailGate };