@lunora/auth 1.0.0-alpha.22 → 1.0.0-alpha.221

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 (87) hide show
  1. package/LICENSE.md +32 -0
  2. package/README.md +101 -11
  3. package/dist/adapter.d.mts +4 -43
  4. package/dist/adapter.d.ts +4 -43
  5. package/dist/adapter.mjs +1 -48
  6. package/dist/audit.d.mts +142 -0
  7. package/dist/audit.d.ts +142 -0
  8. package/dist/audit.mjs +12 -0
  9. package/dist/cimd-node.d.mts +1 -0
  10. package/dist/cimd-node.d.ts +1 -0
  11. package/dist/cimd-node.mjs +1 -0
  12. package/dist/cimd-workers.d.mts +32 -0
  13. package/dist/cimd-workers.d.ts +32 -0
  14. package/dist/cimd-workers.mjs +1 -0
  15. package/dist/email-guard.d.mts +143 -0
  16. package/dist/email-guard.d.ts +143 -0
  17. package/dist/email-guard.mjs +1 -0
  18. package/dist/index.d.mts +738 -179
  19. package/dist/index.d.ts +738 -179
  20. package/dist/index.mjs +1 -12
  21. package/dist/middleware.d.mts +165 -155
  22. package/dist/middleware.d.ts +165 -155
  23. package/dist/middleware.mjs +1 -56
  24. package/dist/migrate.d.mts +47 -0
  25. package/dist/migrate.d.ts +47 -0
  26. package/dist/migrate.mjs +1 -0
  27. package/dist/packem_shared/AUTH_DO_AUDIT_PATH-DNM3G5GI.mjs +1 -0
  28. package/dist/packem_shared/DEFAULT_AUTH_BASE_PATH-DneiLGpv.mjs +1 -0
  29. package/dist/packem_shared/LunoraAuthAdminError-uhKG1MgQ.mjs +1 -0
  30. package/dist/packem_shared/adapter-CBZ17jKi.mjs +2 -0
  31. package/dist/packem_shared/adapter.d-CElAPJ1U.d.ts +88 -0
  32. package/dist/packem_shared/adapter.d-CaPwF2v3.d.mts +88 -0
  33. package/dist/packem_shared/auth-do-nKx0bUTe.mjs +1 -0
  34. package/dist/packem_shared/authAuditHook-DPTKdjBe.mjs +1 -0
  35. package/dist/packem_shared/authDiscoveryPaths-B14lM14u.mjs +1 -0
  36. package/dist/packem_shared/authDoColumnAdditions-UwF3Rl41.mjs +1 -0
  37. package/dist/packem_shared/create-auth.d-D1CKOFWc.d.mts +142 -0
  38. package/dist/packem_shared/create-auth.d-D1CKOFWc.d.ts +142 -0
  39. package/dist/packem_shared/createAuth-B-Pq7nmN.mjs +1 -0
  40. package/dist/packem_shared/createDoAuthWiring-az8FcbYw.mjs +1 -0
  41. package/dist/packem_shared/createLunoraAuthClient-DRcFNwp9.mjs +1 -0
  42. package/dist/packem_shared/createSignUpInvitation-DHZL9fNl.mjs +1 -0
  43. package/dist/packem_shared/emailGateDatabaseHooks-BYLUpiN_.mjs +1 -0
  44. package/dist/packem_shared/invite-only.d-CsPJWAwM.d.ts +126 -0
  45. package/dist/packem_shared/invite-only.d-DKyHHkkQ.d.mts +126 -0
  46. package/dist/packem_shared/legacyIssuerCleanupStatements-D9Kf8OD1.mjs +1 -0
  47. package/dist/packem_shared/mcp-CgX1hTOv.mjs +1 -0
  48. package/dist/packem_shared/on-cloudflare-edge-Dx6gFjBO.mjs +1 -0
  49. package/dist/packem_shared/quote-identifier-CGiYFBvY.mjs +1 -0
  50. package/dist/packem_shared/sessionPresets-C867Mlo4.mjs +1 -0
  51. package/dist/packem_shared/sql-store-uMubPYI9.mjs +1 -0
  52. package/dist/packem_shared/uiConfig-B8pN6pks.mjs +1 -0
  53. package/dist/plugins-client.d.mts +250 -1
  54. package/dist/plugins-client.d.ts +250 -1
  55. package/dist/plugins-client.mjs +1 -2
  56. package/dist/plugins-enterprise-client.d.mts +13 -0
  57. package/dist/plugins-enterprise-client.d.ts +13 -0
  58. package/dist/plugins-enterprise-client.mjs +1 -0
  59. package/dist/plugins-enterprise.d.mts +1668 -0
  60. package/dist/plugins-enterprise.d.ts +1668 -0
  61. package/dist/plugins-enterprise.mjs +1 -0
  62. package/dist/plugins.d.mts +167 -2
  63. package/dist/plugins.d.ts +167 -2
  64. package/dist/plugins.mjs +1 -22
  65. package/dist/schema.d.mts +39 -39
  66. package/dist/schema.d.ts +39 -39
  67. package/dist/schema.mjs +1 -62
  68. package/dist/sql-store.d.mts +43 -28
  69. package/dist/sql-store.d.ts +43 -28
  70. package/dist/sql-store.mjs +1 -184
  71. package/dist/store.d.mts +65 -47
  72. package/dist/store.d.ts +65 -47
  73. package/dist/store.mjs +1 -183
  74. package/dist/turnstile-middleware.d.mts +73 -55
  75. package/dist/turnstile-middleware.d.ts +73 -55
  76. package/dist/turnstile-middleware.mjs +1 -45
  77. package/dist/turnstile.d.mts +42 -59
  78. package/dist/turnstile.d.ts +42 -59
  79. package/dist/turnstile.mjs +1 -61
  80. package/package.json +53 -6
  81. package/dist/packem_shared/DEFAULT_AUTH_BASE_PATH-DjcUWEQl.mjs +0 -11
  82. package/dist/packem_shared/LunoraAuthAdminError-D4L7n6gN.mjs +0 -510
  83. package/dist/packem_shared/compileMigrationsSql-B9bj-mJv.mjs +0 -29
  84. package/dist/packem_shared/create-auth.d-Mwhb4gSc.d.mts +0 -128
  85. package/dist/packem_shared/create-auth.d-Mwhb4gSc.d.ts +0 -128
  86. package/dist/packem_shared/createAuth-BVMMllTm.mjs +0 -87
  87. package/dist/packem_shared/sessionPresets-Dwwd74_J.mjs +0 -38
@@ -1,12 +1,12 @@
1
1
  import { LunoraError } from '@lunora/errors';
2
- import { L as LunoraAuth } from "./packem_shared/create-auth.d-Mwhb4gSc.js";
2
+ import { L as LunoraAuth } from "./packem_shared/create-auth.d-D1CKOFWc.js";
3
3
  import 'better-auth';
4
4
  /**
5
- * Structural mirror of `@lunora/server`'s `MiddlewareNext` — the continuation
6
- * callback handed to a middleware. Repeated here so this package does not take
7
- * a runtime dependency on `@lunora/server`; the types are assignable from the
8
- * fully-typed builder.
9
- */
5
+ * Structural mirror of `@lunora/server`'s `MiddlewareNext` — the continuation
6
+ * callback handed to a middleware. Repeated here so this package does not take
7
+ * a runtime dependency on `@lunora/server`; the types are assignable from the
8
+ * fully-typed builder.
9
+ */
10
10
  interface MiddlewareNext<ContextIn> {
11
11
  (): Promise<ContextIn>;
12
12
  <Extension extends Record<string, unknown>>(options: {
@@ -14,174 +14,184 @@ interface MiddlewareNext<ContextIn> {
14
14
  }): Promise<ContextIn & Extension>;
15
15
  }
16
16
  /**
17
- * Thrown by the runtime header guard when a privileged `ctx.authApi.*` endpoint
18
- * is invoked without a `headers` property on its argument object. Carries the
19
- * offending `method` name so callers can pinpoint the bad call site, and points
20
- * at the explicit escape hatches.
21
- *
22
- * This is the runtime sibling of the static `auth_api_call_without_headers`
23
- * advisor lint — both treat a header-less `ctx.authApi.*` call as an
24
- * authorization bypass, so a call that trips the lint also trips this guard.
25
- */
17
+ * Thrown by the runtime header guard when a privileged `ctx.authApi.*` endpoint
18
+ * is invoked without a `headers` property on its argument object. Carries the
19
+ * offending `method` name so callers can pinpoint the bad call site, and points
20
+ * at the explicit escape hatches.
21
+ *
22
+ * This is the runtime sibling of the static `auth_api_call_without_headers`
23
+ * advisor lint — both treat a header-less `ctx.authApi.*` call as an
24
+ * authorization bypass, so a call that trips the lint also trips this guard.
25
+ */
26
26
  declare class LunoraAuthHeadersError extends LunoraError {
27
- /** The `ctx.authApi.&lt;method>` that was called without `headers`. */
27
+ /** The `ctx.authApi.<method>` that was called without `headers`. */
28
28
  readonly method: string;
29
29
  constructor(method: string);
30
30
  }
31
31
  /**
32
- * Options for {@link withAuthPlugins}.
33
- */
32
+ * Options for {@link withAuthPlugins}.
33
+ */
34
34
  interface WithAuthPluginsOptions {
35
35
  /**
36
- * Whether to install the runtime header guard around `ctx.authApi`.
37
- *
38
- * **Defaults to `true` — the safe default.** When enabled, every
39
- * `ctx.authApi.&lt;method>(…)` call that omits `headers` throws
40
- * {@link LunoraAuthHeadersError} instead of silently running with full
41
- * server-to-server privileges. For a deliberate, per-call unauthenticated
42
- * invocation, use the explicit `ctx.authApi.withoutHeaders()` escape hatch
43
- * rather than disabling the guard wholesale.
44
- *
45
- * Set to `false` only when you have audited every `ctx.authApi.*` call site
46
- * and accept responsibility for passing headers yourself. This is the loud,
47
- * all-or-nothing opt-out; prefer `withoutHeaders()` for one-offs.
48
- */
36
+ * Whether to install the runtime header guard around `ctx.authApi`.
37
+ *
38
+ * **Defaults to `true` — the safe default.** When enabled, every
39
+ * `ctx.authApi.<method>(…)` call that omits `headers` throws
40
+ * {@link LunoraAuthHeadersError} instead of silently running with full
41
+ * server-to-server privileges. For a deliberate, per-call unauthenticated
42
+ * invocation, use the explicit `ctx.authApi.withoutHeaders()` escape hatch
43
+ * rather than disabling the guard wholesale.
44
+ *
45
+ * Set to `false` only when you have audited every `ctx.authApi.*` call site
46
+ * and accept responsibility for passing headers yourself. This is the loud,
47
+ * all-or-nothing opt-out; prefer `withoutHeaders()` for one-offs.
48
+ */
49
49
  enforceHeaders?: boolean;
50
50
  }
51
51
  /**
52
- * The Lunora context extension this middleware installs. It does *not* replace
53
- * `ctx.auth` (the identity-only surface populated by the runtime — `userId` and
54
- * `getIdentity()`); instead it adds a sibling `ctx.authApi` that points at the
55
- * full better-auth plugin API surface.
56
- *
57
- * The shape of `authApi` is the shape of the better-auth instance's `api` —
58
- * a flat record of endpoint functions like `createOrganization`, `banUser`,
59
- * `listMembers`, … contributed by whichever plugins are configured on the auth
60
- * instance. Because the type is `Auth["api"]` and `LunoraAuth` is generic over
61
- * the auth instance, callers get end-to-end inference: the endpoints they see
62
- * are exactly the ones their auth instance loaded.
63
- */
52
+ * The Lunora context extension this middleware installs. It does *not* replace
53
+ * `ctx.auth` (the identity-only surface populated by the runtime — `userId` and
54
+ * `getIdentity()`); instead it adds a sibling `ctx.authApi` that points at the
55
+ * full better-auth plugin API surface.
56
+ *
57
+ * The shape of `authApi` is the shape of the better-auth instance's `api` —
58
+ * a flat record of endpoint functions like `createOrganization`, `banUser`,
59
+ * `listMembers`, … contributed by whichever plugins are configured on the auth
60
+ * instance. Because the type is `Auth["api"]` and `LunoraAuth` is generic over
61
+ * the auth instance, callers get end-to-end inference: the endpoints they see
62
+ * are exactly the ones their auth instance loaded.
63
+ */
64
64
  interface LunoraAuthApiContext<Auth extends LunoraAuth> {
65
65
  /**
66
- * The better-auth endpoint surface — every endpoint contributed by every
67
- * plugin configured on the auth instance, ready to call directly:
68
- *
69
- * ```ts
70
- * await ctx.authApi.createOrganization({ body: { name: "Acme" }, headers });
71
- * await ctx.authApi.banUser({ body: { userId: "u_1" }, headers });
72
- * ```
73
- *
74
- * # ⚠️ SECURITY: privileged surface — you MUST pass `headers`
75
- *
76
- * This is the **full, privileged** better-auth API (`auth.api`) — it
77
- * includes admin/management endpoints such as `banUser`, `setRole`,
78
- * impersonation, `createOrganization`, `removeMember`, … better-auth
79
- * authorizes these calls from the caller's session in the `headers` you
80
- * pass. Invoked **without** `headers`, better-auth treats the call as a
81
- * trusted server-side invocation and **bypasses session authorization
82
- * entirely** — any procedure that can reach `ctx.authApi` could then ban a
83
- * user, escalate a role, or read another tenant's data.
84
- *
85
- * To stop that bypass at runtime, `withAuthPlugins` installs a guard around
86
- * `ctx.authApi` by default: a header-less call to any endpoint throws
87
- * {@link LunoraAuthHeadersError} rather than running with full privileges.
88
- * The same `auth_api_call_without_headers` advisor lint catches it
89
- * statically; the guard is the runtime backstop for the cases the lint
90
- * can't see (dynamic method names, indirected calls). For the rare,
91
- * deliberate unauthenticated server-to-server call, opt out explicitly with
92
- * `ctx.authApi.withoutHeaders().&lt;method>(…)`.
93
- *
94
- * Lunora's procedure context carries only the resolved identity, not the
95
- * raw inbound `Headers`, so this middleware CANNOT pre-bind them for you.
96
- * Therefore: **thread the inbound `Headers` into every `ctx.authApi.*`
97
- * call** (typically from an HTTP action — see {@link withAuthPlugins}). A
98
- * header-less call is an authorization bypass, not a convenience.
99
- */
66
+ * The better-auth endpoint surface — every endpoint contributed by every
67
+ * plugin configured on the auth instance, ready to call directly:
68
+ *
69
+ * ```ts
70
+ * await ctx.authApi.createOrganization({ body: { name: "Acme" }, headers });
71
+ * await ctx.authApi.banUser({ body: { userId: "u_1" }, headers });
72
+ * ```
73
+ *
74
+ * # ⚠️ SECURITY: privileged surface — you MUST pass `headers`
75
+ *
76
+ * This is the **full, privileged** better-auth API (`auth.api`) — it
77
+ * includes admin/management endpoints such as `banUser`, `setRole`,
78
+ * impersonation, `createOrganization`, `removeMember`, … better-auth
79
+ * authorizes these calls from the caller's session in the `headers` you
80
+ * pass. Invoked **without** `headers`, better-auth treats the call as a
81
+ * trusted server-side invocation and **bypasses session authorization
82
+ * entirely** — any procedure that can reach `ctx.authApi` could then ban a
83
+ * user, escalate a role, or read another tenant's data.
84
+ *
85
+ * To stop that bypass at runtime, `withAuthPlugins` installs a guard around
86
+ * `ctx.authApi` by default: a header-less call to any endpoint throws
87
+ * {@link LunoraAuthHeadersError} rather than running with full privileges.
88
+ * The same `auth_api_call_without_headers` advisor lint catches it
89
+ * statically; the guard is the runtime backstop for the cases the lint
90
+ * can't see (dynamic method names, indirected calls). For the rare,
91
+ * deliberate unauthenticated server-to-server call, opt out explicitly with
92
+ * `ctx.authApi.withoutHeaders().<method>(…)`.
93
+ *
94
+ * Lunora's procedure context carries only the resolved identity, not the
95
+ * raw inbound `Headers`, so this middleware CANNOT pre-bind them for you.
96
+ * Therefore: **thread the inbound `Headers` into every `ctx.authApi.*`
97
+ * call** (typically from an HTTP action — see {@link withAuthPlugins}). A
98
+ * header-less call is an authorization bypass, not a convenience.
99
+ */
100
100
  readonly authApi: {
101
101
  /**
102
- * Explicit, loud escape hatch from the runtime header guard. Returns
103
- * the raw, **unguarded** `auth.api` surface — every endpoint reached
104
- * through it runs as a trusted server-to-server call with session
105
- * authorization skipped.
106
- *
107
- * ```ts
108
- * // A scheduled job with no inbound request that must create the
109
- * // system org. Audited and intentional:
110
- * await ctx.authApi.withoutHeaders().createOrganization({ body: { name } });
111
- * ```
112
- *
113
- * Use only for deliberate, audited unauthenticated calls. For ordinary
114
- * request-driven calls, pass `headers` so authorization is enforced.
115
- */
102
+ * Explicit, loud escape hatch from the runtime header guard. Returns
103
+ * the raw, **unguarded** `auth.api` surface — every endpoint reached
104
+ * through it runs as a trusted server-to-server call with session
105
+ * authorization skipped.
106
+ *
107
+ * ```ts
108
+ * // A scheduled job with no inbound request that must create the
109
+ * // system org. Audited and intentional:
110
+ * await ctx.authApi.withoutHeaders().createOrganization({ body: { name } });
111
+ * ```
112
+ *
113
+ * Use only for deliberate, audited unauthenticated calls. For ordinary
114
+ * request-driven calls, pass `headers` so authorization is enforced.
115
+ */
116
116
  withoutHeaders: () => Auth["api"];
117
117
  } & Auth["api"];
118
118
  }
119
119
  /**
120
- * Build a Lunora middleware that mounts a better-auth instance's plugin API
121
- * onto `ctx.authApi`. Compose it with `.use(...)` once per builder and every
122
- * downstream handler gets typed access to the plugin endpoints — no more
123
- * importing the auth instance directly from every query/mutation file.
124
- *
125
- * # ⚠️ SECURITY: headers are load-bearing for authorization
126
- *
127
- * `ctx.authApi` is the **full privileged** better-auth surface (`auth.api`):
128
- * `banUser`, `setRole`, impersonation, `createOrganization`, `removeMember`,
129
- * and so on. better-auth authorizes these from the caller's session carried in
130
- * the `headers` you pass. **Called without `headers`, better-auth treats the
131
- * invocation as a trusted server-side call and skips session authorization
132
- * altogether** — so a header-less `ctx.authApi.banUser(...)` from any procedure
133
- * runs with full privileges regardless of who the caller is. This is an
134
- * authorization bypass, not just a missing convenience.
135
- *
136
- * To make that bypass fail loudly instead of silently, this middleware wraps
137
- * `ctx.authApi` in a **runtime header guard by default**: any endpoint called
138
- * without a `headers` property throws {@link LunoraAuthHeadersError}. The guard
139
- * mirrors the static `auth_api_call_without_headers` advisor lint exactly, so
140
- * the two agree on what counts as a header-bearing call.
141
- *
142
- * - **Default (safe):** `withAuthPlugins(auth)` — header-less calls throw.
143
- * - **Per-call opt-out (preferred):** `ctx.authApi.withoutHeaders().banUser(…)`
144
- * for a deliberate, audited unauthenticated server-to-server call.
145
- * - **Whole-middleware opt-out (loud):** `withAuthPlugins(auth, { enforceHeaders: false })`
146
- * disables the guard entirely; only do this once every call site is audited.
147
- *
148
- * Lunora's procedure context does not currently carry the raw request headers
149
- * (only the resolved identity — see `AuthState` in `@lunora/server`), so
150
- * this middleware **cannot** pre-bind headers for you and does **not** do so.
151
- * You MUST pass the inbound `Headers` explicitly into **every** `ctx.authApi.*`
152
- * call, from a transport that has them — typically an HTTP action:
153
- *
154
- * ```ts
155
- * // lunora/orgs.ts
156
- * import { httpAction } from "@lunora/server";
157
- * import { withAuthPlugins } from "@lunora/auth/middleware";
158
- * import { auth } from "./auth.js";
159
- *
160
- * export const createOrg = httpAction(async (ctx, request) => {
161
- * const { name } = await request.json();
162
- *
163
- * // ctx.authApi is installed by withAuthPlugins(auth) on the builder.
164
- * const org = await ctx.authApi.createOrganization({
165
- * body: { name },
166
- * headers: request.headers,
167
- * });
168
- *
169
- * return Response.json(org);
170
- * });
171
- * ```
172
- *
173
- * For internal server-to-server calls where there is no inbound request
174
- * (e.g. a scheduled job that creates the system org), opt out explicitly with
175
- * `ctx.authApi.withoutHeaders()` and authenticate with whatever bearer token
176
- * your auth instance is configured to honour.
177
- */
120
+ * Build a Lunora middleware that mounts a better-auth instance's plugin API
121
+ * onto `ctx.authApi`. Compose it with `.use(...)` once per builder and every
122
+ * downstream handler gets typed access to the plugin endpoints — no more
123
+ * importing the auth instance directly from every query/mutation file.
124
+ *
125
+ * # ⚠️ SECURITY: headers are load-bearing for authorization
126
+ *
127
+ * `ctx.authApi` is the **full privileged** better-auth surface (`auth.api`):
128
+ * `banUser`, `setRole`, impersonation, `createOrganization`, `removeMember`,
129
+ * and so on. better-auth authorizes these from the caller's session carried in
130
+ * the `headers` you pass. **Called without `headers`, better-auth treats the
131
+ * invocation as a trusted server-side call and skips session authorization
132
+ * altogether** — so a header-less `ctx.authApi.banUser(...)` from any procedure
133
+ * runs with full privileges regardless of who the caller is. This is an
134
+ * authorization bypass, not just a missing convenience.
135
+ *
136
+ * To make that bypass fail loudly instead of silently, this middleware wraps
137
+ * `ctx.authApi` in a **runtime header guard by default**: any endpoint called
138
+ * without a `headers` property throws {@link LunoraAuthHeadersError}. The guard
139
+ * mirrors the static `auth_api_call_without_headers` advisor lint exactly, so
140
+ * the two agree on what counts as a header-bearing call.
141
+ *
142
+ * - **Default (safe):** `withAuthPlugins(auth)` — header-less calls throw.
143
+ * - **Per-call opt-out (preferred):** `ctx.authApi.withoutHeaders().banUser(…)`
144
+ * for a deliberate, audited unauthenticated server-to-server call.
145
+ * - **Whole-middleware opt-out (loud):** `withAuthPlugins(auth, { enforceHeaders: false })`
146
+ * disables the guard entirely; only do this once every call site is audited.
147
+ *
148
+ * Lunora's procedure context does not currently carry the raw request headers
149
+ * (only the resolved identity — see `AuthState` in `@lunora/server`), so
150
+ * this middleware **cannot** pre-bind headers for you and does **not** do so.
151
+ * You MUST pass `Headers` explicitly into **every** `ctx.authApi.*` call. A
152
+ * procedure has no inbound `Headers` object, so rebuild one from what the
153
+ * transport forwarded in `args`:
154
+ *
155
+ * ```ts
156
+ * // lunora/orgs.ts
157
+ * export const createOrg = mutation
158
+ * .input({ cookie: v.string(), name: v.string() })
159
+ * .use(withAuthPlugins(auth))
160
+ * .mutation(async ({ args, ctx }) =>
161
+ * ctx.authApi.createOrganization({
162
+ * body: { name: args.name },
163
+ * // Rebuilt from the transport — the middleware cannot pre-bind them.
164
+ * headers: new Headers({ cookie: args.cookie }),
165
+ * }),
166
+ * );
167
+ * ```
168
+ *
169
+ * `ctx.authApi` is a PROCEDURE-context surface — this middleware installs it
170
+ * through the builder's `.use()` chain, which `httpAction` / `httpRoute` do not
171
+ * have (see `HttpActionCtx` in `@lunora/server`). An HTTP action already holds
172
+ * the real inbound `Headers`, so call the auth instance there directly:
173
+ *
174
+ * ```ts
175
+ * export const createOrgHttp = httpAction(async (ctx, request) => {
176
+ * const { name } = await request.json();
177
+ * const org = await auth.api.createOrganization({ body: { name }, headers: request.headers });
178
+ *
179
+ * return Response.json(org);
180
+ * });
181
+ * ```
182
+ *
183
+ * For internal server-to-server calls where there is no inbound request
184
+ * (e.g. a scheduled job that creates the system org), opt out explicitly with
185
+ * `ctx.authApi.withoutHeaders()` and authenticate with whatever bearer token
186
+ * your auth instance is configured to honour.
187
+ */
178
188
  /**
179
- * Shape of the middleware {@link withAuthPlugins} returns: a callable generic
180
- * over the incoming ctx so chaining `.use(...)` preserves whatever ctx fields
181
- * the upstream middleware already installed. Lives as its own interface
182
- * because TypeScript doesn't allow declaring `const fn: &lt;CtxIn>() => ...` —
183
- * the generic must live on a callable type alias or interface.
184
- */
189
+ * Shape of the middleware {@link withAuthPlugins} returns: a callable generic
190
+ * over the incoming ctx so chaining `.use(...)` preserves whatever ctx fields
191
+ * the upstream middleware already installed. Lives as its own interface
192
+ * because TypeScript doesn't allow declaring `const fn: <CtxIn>() => ...` —
193
+ * the generic must live on a callable type alias or interface.
194
+ */
185
195
  type WithAuthPluginsMiddleware<Auth extends LunoraAuth> = <ContextIn>(options: {
186
196
  ctx: ContextIn;
187
197
  next: MiddlewareNext<ContextIn>;
@@ -1,56 +1 @@
1
- import { LunoraError } from '@lunora/errors';
2
-
3
- const callHasHeaders = (argument) => {
4
- if (argument === void 0) {
5
- return false;
6
- }
7
- if (typeof argument !== "object" || argument === null) {
8
- return true;
9
- }
10
- const { headers } = argument;
11
- return headers !== void 0 && headers !== null;
12
- };
13
- const guardAuthApi = (api) => {
14
- const withoutHeaders = () => api;
15
- return /* @__PURE__ */ new Proxy(api, {
16
- // eslint-disable-next-line sonarjs/function-return-type -- a Proxy `get` trap is intrinsically polymorphic: it returns the synthetic `withoutHeaders`, the guarded endpoint wrapper, or any passthrough property value
17
- get(target, property, receiver) {
18
- if (property === "withoutHeaders" && !(property in target)) {
19
- return withoutHeaders;
20
- }
21
- const value = Reflect.get(target, property, receiver);
22
- if (typeof value !== "function" || typeof property !== "string") {
23
- return value;
24
- }
25
- const method = property;
26
- return (...arguments_) => {
27
- if (!callHasHeaders(arguments_[0])) {
28
- return Promise.reject(new LunoraAuthHeadersError(method));
29
- }
30
- return Reflect.apply(value, target, arguments_);
31
- };
32
- }
33
- });
34
- };
35
- class LunoraAuthHeadersError extends LunoraError {
36
- /** The `ctx.authApi.&lt;method>` that was called without `headers`. */
37
- method;
38
- constructor(method) {
39
- super(
40
- "AUTH_HEADERS_MISSING",
41
- `@lunora/auth: ctx.authApi.${method}(…) was called without \`headers\`. better-auth treats a header-less call as a trusted server-to-server invocation and skips session authorization entirely — an authorization bypass. Pass the inbound request headers: ctx.authApi.${method}({ body, headers: request.headers }). If you genuinely intend an unauthenticated server-to-server call, opt out explicitly via ctx.authApi.withoutHeaders().<method>(…), or disable the guard for the whole middleware with withAuthPlugins(auth, { enforceHeaders: false }).`,
42
- { name: "LunoraAuthHeadersError" }
43
- );
44
- this.method = method;
45
- }
46
- }
47
- const withAuthPlugins = (auth, options = {}) => {
48
- const enforceHeaders = options.enforceHeaders ?? true;
49
- const authApi = enforceHeaders ? guardAuthApi(auth.api) : auth.api;
50
- return async ({ next }) => {
51
- const extended = await next({ ctx: { authApi } });
52
- return extended;
53
- };
54
- };
55
-
56
- export { LunoraAuthHeadersError, withAuthPlugins };
1
+ import{LunoraError as d}from"@lunora/errors";const h=e=>{if(e===void 0)return!1;if(typeof e!="object"||e===null)return!0;const{headers:t}=e;return t!=null},c=(e,t)=>{const n=()=>e;return new Proxy(e,{get(s,r,o){if(r==="withoutHeaders"&&!(r in s))return n;const a=Reflect.get(s,r,o);if(typeof a!="function"||typeof r!="string")return a;const i=r;return(...u)=>t&&!h(u[0])?Promise.reject(new l(i)):Reflect.apply(a,s,u)}})};class l extends d{method;constructor(t){super("AUTH_HEADERS_MISSING",`@lunora/auth: ctx.authApi.${t}(…) was called without \`headers\`. better-auth treats a header-less call as a trusted server-to-server invocation and skips session authorization entirely — an authorization bypass. Pass the inbound request headers: ctx.authApi.${t}({ body, headers: request.headers }). If you genuinely intend an unauthenticated server-to-server call, opt out explicitly via ctx.authApi.withoutHeaders().<method>(…), or disable the guard for the whole middleware with withAuthPlugins(auth, { enforceHeaders: false }).`,{name:"LunoraAuthHeadersError"}),this.method=t}}const w=(e,t={})=>{const n=t.enforceHeaders??!0,s=c(e.api,n);return async({next:r})=>await r({ctx:{authApi:s}})};export{l as LunoraAuthHeadersError,w as withAuthPlugins};
@@ -0,0 +1,47 @@
1
+ import { a as LunoraAuthOptions, L as LunoraAuth } from "./packem_shared/create-auth.d-D1CKOFWc.mjs";
2
+ import 'better-auth';
3
+ /**
4
+ * Apply better-auth's required schema (`user`, `session`, `account`,
5
+ * `verification`) to the configured database. Idempotent — better-auth
6
+ * diffs the existing schema and only runs the missing DDL.
7
+ *
8
+ * Cached per `options` reference so the diff cost (one PRAGMA-style sweep
9
+ * per table) doesn't fire on every request. In Cloudflare Workers the same
10
+ * `env.DB` binding is reused across invocations within a single isolate, so
11
+ * caching against the options object — which captures the binding — is
12
+ * sufficient.
13
+ *
14
+ * For production you should prefer pre-applying the schema at deploy time
15
+ * via `compileMigrationsSql` + `wrangler d1 execute`; this helper exists for
16
+ * dev/playground and small deployments.
17
+ */
18
+ declare const ensureMigrated: (auth: LunoraAuth | {
19
+ options: LunoraAuthOptions;
20
+ }) => Promise<void>;
21
+ /**
22
+ * Compile better-auth's migrations to a single SQL string. Useful for
23
+ * `wrangler d1 execute --file -` in CI so the deploy step applies the schema
24
+ * before the first user request.
25
+ *
26
+ * Compiles from the SAME resolved options `createAuth` runs with (via
27
+ * `resolveAuthOptions`), not the raw caller options — so the schema includes the
28
+ * `rateLimit` table the worker's default-on durable limiter writes to. Compiling
29
+ * from the raw options would omit it, and the running worker would then write to
30
+ * a table the migration never created.
31
+ *
32
+ * **Upgrading a database created by an older `@lunora/auth`.** This compiles DDL without
33
+ * reading the database, so it cannot know whether the `account.issuer` column better-auth
34
+ * 1.7.0 required and 1.7.3 reverted is present — and SQLite has no `DROP COLUMN IF EXISTS`
35
+ * to make that moot. `ensureMigrated` handles it automatically; on this path, run
36
+ * `legacyIssuerCleanupStatements()` once against a database that still has the column.
37
+ * Leaving it in place fails every sign-up with `NOT NULL constraint failed: account.issuer`.
38
+ */
39
+ declare const compileMigrationsSql: (options: LunoraAuthOptions) => Promise<string>;
40
+ /**
41
+ * The migration-only auth instance: the same validated, hardened options as `createAuth`, but
42
+ * through the full `better-auth` entry so `database` may be the raw D1 binding the Kysely
43
+ * migrator drives. Hand it to {@link ensureMigrated}; never serve requests from it. Lives here
44
+ * so the migrator's Kysely edge stays out of the runtime bundle.
45
+ */
46
+ declare const createMigrationAuth: (options: LunoraAuthOptions) => LunoraAuth;
47
+ export { compileMigrationsSql, createMigrationAuth, ensureMigrated };
@@ -0,0 +1,47 @@
1
+ import { a as LunoraAuthOptions, L as LunoraAuth } from "./packem_shared/create-auth.d-D1CKOFWc.js";
2
+ import 'better-auth';
3
+ /**
4
+ * Apply better-auth's required schema (`user`, `session`, `account`,
5
+ * `verification`) to the configured database. Idempotent — better-auth
6
+ * diffs the existing schema and only runs the missing DDL.
7
+ *
8
+ * Cached per `options` reference so the diff cost (one PRAGMA-style sweep
9
+ * per table) doesn't fire on every request. In Cloudflare Workers the same
10
+ * `env.DB` binding is reused across invocations within a single isolate, so
11
+ * caching against the options object — which captures the binding — is
12
+ * sufficient.
13
+ *
14
+ * For production you should prefer pre-applying the schema at deploy time
15
+ * via `compileMigrationsSql` + `wrangler d1 execute`; this helper exists for
16
+ * dev/playground and small deployments.
17
+ */
18
+ declare const ensureMigrated: (auth: LunoraAuth | {
19
+ options: LunoraAuthOptions;
20
+ }) => Promise<void>;
21
+ /**
22
+ * Compile better-auth's migrations to a single SQL string. Useful for
23
+ * `wrangler d1 execute --file -` in CI so the deploy step applies the schema
24
+ * before the first user request.
25
+ *
26
+ * Compiles from the SAME resolved options `createAuth` runs with (via
27
+ * `resolveAuthOptions`), not the raw caller options — so the schema includes the
28
+ * `rateLimit` table the worker's default-on durable limiter writes to. Compiling
29
+ * from the raw options would omit it, and the running worker would then write to
30
+ * a table the migration never created.
31
+ *
32
+ * **Upgrading a database created by an older `@lunora/auth`.** This compiles DDL without
33
+ * reading the database, so it cannot know whether the `account.issuer` column better-auth
34
+ * 1.7.0 required and 1.7.3 reverted is present — and SQLite has no `DROP COLUMN IF EXISTS`
35
+ * to make that moot. `ensureMigrated` handles it automatically; on this path, run
36
+ * `legacyIssuerCleanupStatements()` once against a database that still has the column.
37
+ * Leaving it in place fails every sign-up with `NOT NULL constraint failed: account.issuer`.
38
+ */
39
+ declare const compileMigrationsSql: (options: LunoraAuthOptions) => Promise<string>;
40
+ /**
41
+ * The migration-only auth instance: the same validated, hardened options as `createAuth`, but
42
+ * through the full `better-auth` entry so `database` may be the raw D1 binding the Kysely
43
+ * migrator drives. Hand it to {@link ensureMigrated}; never serve requests from it. Lives here
44
+ * so the migrator's Kysely edge stays out of the runtime bundle.
45
+ */
46
+ declare const createMigrationAuth: (options: LunoraAuthOptions) => LunoraAuth;
47
+ export { compileMigrationsSql, createMigrationAuth, ensureMigrated };
@@ -0,0 +1 @@
1
+ import{getAuthTablesWithResolvedIndexes as w,invalidateSchemaChecks as y}from"@better-auth/core/db/internal";import{LunoraError as R}from"@lunora/errors";import{betterAuth as _}from"better-auth";import{getMigrations as l}from"better-auth/db/migration";import{q as c}from"./packem_shared/quote-identifier-CGiYFBvY.mjs";import{resolveAuthOptions as A,prepareAuthOptions as M}from"./packem_shared/createAuth-B-Pq7nmN.mjs";import{schemaDeclaresIssuer as x,legacyIssuerCleanupStatements as T,indexesReferencingIssuer as N}from"./packem_shared/legacyIssuerCleanupStatements-D9Kf8OD1.mjs";const C=/pragma_index_list\s*\(/iu,D=/sqlite_master/iu,S=/^["`[]|["\]`]$/gu,O=/\s+/u,P=/create\s+unique\s+index/iu,L=/\bwhere\b/iu,U=t=>C.test(t)&&D.test(t),q=t=>(t.trim().split(O)[0]??"").replaceAll(S,"").trim(),v=t=>{const e=t.indexOf("(");if(e===-1)return{columns:[],tail:""};let s=0;for(let n=e;n<t.length;n+=1){const a=t[n];if(a==="(")s+=1;else if(a===")"&&(s-=1,s===0))return{columns:t.slice(e+1,n).split(",").map(o=>q(o)).filter(o=>o!==""),tail:t.slice(n+1)}}return{columns:[],tail:""}},W=async t=>{const e=t.prepare("SELECT name, tbl_name, sql FROM sqlite_master WHERE type = 'index'"),{results:s}=await e.all(),n=[];for(const a of s??[]){const o=a.name,r=a.tbl_name,i=a.sql;if(typeof o!="string"||typeof r!="string")continue;if(typeof i!="string"){n.push({columnPosition:0,indexName:o,isPartial:0,isUnique:1,tableName:r});continue}const d=P.test(i)?1:0,{columns:h,tail:b}=v(i),E=L.test(b)?1:0;for(const[g,I]of h.entries())n.push({columnName:I,columnPosition:g,indexName:o,isPartial:E,isUnique:d,tableName:r})}return n},F=t=>{const e=async()=>{const n=await W(t);return{meta:{},results:n,success:!0}},s={all:e,bind:()=>s,run:e};return s},H=t=>new Proxy(t,{get(e,s,n){if(s==="prepare")return o=>U(o)?F(e):e.prepare(o);const a=Reflect.get(e,s,n);return typeof a=="function"?a.bind(e):a}}),m=t=>typeof t=="object"&&t!==null&&typeof t.prepare=="function"&&typeof t.batch=="function",p=t=>{const{database:e}=t;if(!(e&&typeof e!="function"))throw new R("AUTH_MIGRATOR_UNSUPPORTED",e?"@lunora/auth: this auth instance's `database` is a custom adapter, which better-auth's migrator cannot drive.":"@lunora/auth: this auth instance has no `database`, so better-auth's migrator has nothing to introspect.")},f=t=>m(t.database)?{...t,database:H(t.database)}:t,$=async(t,e)=>{const s=c("t");try{return await t.prepare(`SELECT ${s}.${c("issuer")} FROM ${c(e)} AS ${s} LIMIT 0`).run(),!0}catch{return!1}},Q=async t=>{const{database:e}=t,{tables:s}=w(t),{account:n}=s;if(n!==void 0&&!x(Object.entries(n.fields).map(([a,o])=>o.fieldName??a))&&m(e)&&await $(e,n.modelName))try{const{results:a}=await e.prepare("SELECT name, sql FROM sqlite_master WHERE type = 'index' AND tbl_name = ?").bind(n.modelName).all();await e.batch(T(n.modelName,N(a)).map(o=>e.prepare(o)))}catch(a){console.error("@lunora/auth: could not drop the reverted `account.issuer` column; if it is still present, sign-ups will fail until it is removed.",a)}},j=t=>{const{database:e}=t;typeof e=="object"&&e!==null&&y(e)},u=new WeakMap,J=async t=>{const{options:e}=t;p(e);const s=u.get(e);if(s){await s;return}const n=(async()=>{const{runMigrations:a}=await l(f(e));await a(),await Q(e),j(e)})();u.set(e,n);try{await n}catch(a){throw u.delete(e),a}},V=async t=>{const e=A(t);p(e);const{compileMigrations:s}=await l(f(e));return s()},Z=t=>_(M(t));export{V as compileMigrationsSql,Z as createMigrationAuth,J as ensureMigrated};
@@ -0,0 +1 @@
1
+ import{I as R,L as _,R as S,a as T}from"./auth-do-nKx0bUTe.mjs";import"./adapter-CBZ17jKi.mjs";import"../audit.mjs";import"./createAuth-B-Pq7nmN.mjs";import"./authDiscoveryPaths-B14lM14u.mjs";import"./authDoColumnAdditions-UwF3Rl41.mjs";import"./DEFAULT_AUTH_BASE_PATH-DneiLGpv.mjs";import"./legacyIssuerCleanupStatements-D9Kf8OD1.mjs";export{R as INTERNAL_SECRET_HEADER,_ as LunoraAuthDO,S as READ_AUDIT_PATH,T as RESOLVE_SESSION_PATH};
@@ -0,0 +1 @@
1
+ const e="/api/auth",u=(n,t)=>{const r=t.endsWith("/")?t.slice(0,-1):t;return n===r||n.startsWith(`${r}/`)},o=async(n,t,r=e)=>{const s=new URL(t.url);if(u(s.pathname,r))return n.handler(t)};export{e as DEFAULT_AUTH_BASE_PATH,o as handleAuthRequest,u as isAuthRoutePath};
@@ -0,0 +1 @@
1
+ import{LunoraError as M}from"@lunora/errors";import{getAuthTables as b}from"better-auth/db";import{revokeSignUpInvitation as U,createSignUpInvitation as R}from"./createSignUpInvitation-DHZL9fNl.mjs";class f extends M{constructor(c,h){super(h,c,{name:"LunoraAuthAdminError"})}}const N=50,D=500,O=3600,E=O*24,T=100*365*24*60*60,z=2880*60*1e3,k=[{field:"userId",model:"member"},{field:"userId",model:"teamMember"},{field:"userId",model:"passkey"},{field:"userId",model:"twoFactor"},{field:"userId",model:"oauthAccessToken"},{field:"userId",model:"oauthRefreshToken"},{field:"userId",model:"oauthConsent"},{field:"userId",model:"deviceCode"},{field:"userId",model:"walletAddress"},{field:"inviterId",model:"invitation"}],L=new Set(["accessToken","backupCodes","idToken","password","publicKey","refreshToken","secret","token","tokenHash"]),C=o=>Math.min(Math.max(Math.trunc(o??N),1),D),P=o=>Math.max(0,Math.trunc(o??0)),u=o=>{const c={};for(const[h,l]of Object.entries(o))L.has(h)||(c[h]=l instanceof Date?l.getTime():l);return c},g=o=>Array.isArray(o)?o.join(","):o,S=o=>o.toLowerCase().replaceAll(/[^\da-z]+/g,"-").replaceAll(/^-|-$/g,""),B=new Set(["banExpires","banned","banReason","createdAt","email","emailVerified","id","name","role","updatedAt"]),F={displayUsername:"username",phoneNumber:"phone-number",phoneNumberVerified:"phone-number",username:"username"},G=o=>o==="boolean"||o==="date"||o==="number"?o:"string",V=o=>{const c=[];for(const[h,l]of Object.entries(o))l.input===!1||l.references!==void 0||B.has(h)||c.push({name:h,plugin:F[h],required:l.required===!0,type:G(l.type),unique:l.unique===!0});return c},I=o=>typeof o=="string"&&o.includes("owner"),_=async(o,c)=>{const l=(await o.adapter.findMany({model:"member",where:[{field:"userId",value:c}]})).filter(p=>I(p.role)&&typeof p.organizationId=="string").map(p=>p.organizationId);if(l.length===0)return;const A=await Promise.all(l.map(async p=>o.adapter.findMany({model:"member",where:[{field:"organizationId",value:p}]}))),n=l.find((p,v)=>!A[v]?.some(e=>e.userId!==c&&I(e.role)));if(n!==void 0)throw new f(`user is the last owner of organization ${n} — transfer ownership or delete the organization first`,"LAST_ORGANIZATION_OWNER")},j=o=>{if(o instanceof f)return o;const c=o,h=c?.body?.code??c?.code??"AUTH_ADMIN_ERROR",l=c?.body?.message??c?.message??"auth admin operation failed";return new f(l,h)},W=(o,c={})=>{const h=o.$context,l=c.features??{},A=e=>{const a=new Set((e.plugins??[]).map(i=>i.id)),t=i=>a.has(i);return{accounts:l.accounts??!0,admin:l.admin??t("admin"),inviteOnly:l.inviteOnly??t("lunora-invite-only"),organization:l.organization??t("organization"),passkey:l.passkey??t("passkey"),twoFactor:l.twoFactor??t("two-factor")}},n=async e=>{try{return await e(await h)}catch(a){throw j(a)}},p=e=>u(e),v=async(e,a,t)=>{const i=t.where&&t.where.length>0?t.where:void 0,[r,s]=await Promise.all([e.adapter.findMany({limit:C(t.limit),model:a,offset:P(t.offset),sortBy:t.sortBy,where:i}),e.adapter.count({model:a,where:i})]);return{rows:r.map(d=>u(d)),total:s}};return{banUser:({expiresInSeconds:e,reason:a,userId:t})=>n(async i=>{let r=null;if(e!==void 0){if(!Number.isInteger(e)||e<=0)throw new f("expiresInSeconds must be a positive finite integer","INVALID_BAN_SECONDS");const d=Math.min(e,T);r=new Date(Date.now()+d*1e3)}const s=await i.internalAdapter.updateUser(t,{banExpires:r,banned:!0,banReason:a??"No reason"});return await i.internalAdapter.deleteUserSessions(t),p(s)}),cancelInvitation:({invitationId:e})=>n(async a=>{await a.adapter.delete({model:"invitation",where:[{field:"id",value:e}]})}),capabilities:()=>n(e=>Promise.resolve(A(e.options))),addMember:({organizationId:e,role:a,userId:t})=>n(async i=>{const r=await i.adapter.create({data:{createdAt:new Date,organizationId:e,role:a===void 0||a===""?"member":a,userId:t},model:"member"});return u(r)}),addTeamMember:({teamId:e,userId:a})=>n(async t=>{const i=await t.adapter.create({data:{createdAt:new Date,teamId:e,userId:a},model:"teamMember"});return u(i)}),config:()=>n(e=>{const a=e.options,t=A(a),i=new Set((a.plugins??[]).map(m=>m.id)),r=b(a),s=a.session??{},d=a.rateLimit??{};return Promise.resolve({capabilities:t,emailAndPassword:a.emailAndPassword?.enabled??!1,organization:{enabled:t.organization,roles:!!r.organizationRole,teams:!!r.team},plugins:[...i].toSorted((m,w)=>m.localeCompare(w)),rateLimit:{enabled:d.enabled??!1,max:d.max,window:d.window},session:{cookieCache:s.cookieCache?.enabled,expiresIn:s.expiresIn,freshAge:s.freshAge,updateAge:s.updateAge},socialProviders:Object.keys(a.socialProviders??{}).toSorted((m,w)=>m.localeCompare(w)),userFields:V(r.user?.fields??{})})}),createOrganization:({logo:e,metadata:a,name:t,ownerId:i,slug:r})=>n(async s=>{const d=S(r!==void 0&&r!==""?r:t);if(d==="")throw new f("could not derive a slug from the organization name","ORG_SLUG_INVALID");if(await s.adapter.findOne({model:"organization",where:[{field:"slug",value:d}]}))throw new f("an organization with this slug already exists","ORG_SLUG_TAKEN");const w=await s.adapter.create({data:{createdAt:new Date,logo:e===void 0||e===""?void 0:e,metadata:a===void 0?void 0:JSON.stringify(a),name:t,slug:d},model:"organization"});return i!==void 0&&i!==""&&await s.adapter.create({data:{createdAt:new Date,organizationId:w.id,role:"owner",userId:i},model:"member"}),u(w)}),createOrgRole:({organizationId:e,permission:a,role:t})=>n(async i=>{const r=await i.adapter.create({data:{createdAt:new Date,organizationId:e,permission:JSON.stringify(a),role:t},model:"organizationRole"});return u(r)}),createTeam:({name:e,organizationId:a})=>n(async t=>{const i=await t.adapter.create({data:{createdAt:new Date,name:e,organizationId:a},model:"team"});return u(i)}),deleteOrganization:({organizationId:e})=>n(async a=>{const t=b(a.options);if(await a.adapter.deleteMany({model:"member",where:[{field:"organizationId",value:e}]}),await a.adapter.deleteMany({model:"invitation",where:[{field:"organizationId",value:e}]}),t.team){const i=await a.adapter.findMany({model:"team",where:[{field:"organizationId",value:e}]});for(const r of i)await a.adapter.deleteMany({model:"teamMember",where:[{field:"teamId",value:r.id}]});await a.adapter.deleteMany({model:"team",where:[{field:"organizationId",value:e}]})}t.organizationRole&&await a.adapter.deleteMany({model:"organizationRole",where:[{field:"organizationId",value:e}]}),await a.adapter.delete({model:"organization",where:[{field:"id",value:e}]})}),deleteOrgRole:({roleId:e})=>n(async a=>{await a.adapter.delete({model:"organizationRole",where:[{field:"id",value:e}]})}),inviteMember:({email:e,inviterId:a,organizationId:t,role:i})=>n(async r=>{let s=a;if(s===void 0||s===""){const m=await r.adapter.findMany({model:"member",where:[{field:"organizationId",value:t}]});s=(m.find(y=>typeof y.role=="string"&&y.role.includes("owner"))??m[0])?.userId}if(s===void 0||s==="")throw new f("provide an inviter — the organization has no members to attribute the invitation to","INVITER_REQUIRED");const d=await r.adapter.create({data:{createdAt:new Date,email:e.toLowerCase(),expiresAt:new Date(Date.now()+z),inviterId:s,organizationId:t,role:i===void 0||i===""?"member":i,status:"pending"},model:"invitation"});return u(d)}),listOrgRoles:({limit:e,offset:a,organizationId:t})=>n(i=>v(i,"organizationRole",{limit:e,offset:a,sortBy:{direction:"desc",field:"createdAt"},where:[{field:"organizationId",value:t}]})),listTeamMembers:({limit:e,offset:a,teamId:t})=>n(i=>v(i,"teamMember",{limit:e,offset:a,where:[{field:"teamId",value:t}]})),listTeams:({limit:e,offset:a,organizationId:t})=>n(i=>v(i,"team",{limit:e,offset:a,sortBy:{direction:"desc",field:"createdAt"},where:[{field:"organizationId",value:t}]})),removeTeam:({teamId:e})=>n(async a=>{await a.adapter.deleteMany({model:"teamMember",where:[{field:"teamId",value:e}]}),await a.adapter.delete({model:"team",where:[{field:"id",value:e}]})}),removeTeamMember:({teamMemberId:e})=>n(async a=>{await a.adapter.delete({model:"teamMember",where:[{field:"id",value:e}]})}),updateMemberRole:({memberId:e,role:a})=>n(async t=>{const i=await t.adapter.update({model:"member",update:{role:g(a)},where:[{field:"id",value:e}]});return u(i??{id:e,role:g(a)})}),updateOrganization:({logo:e,metadata:a,name:t,organizationId:i,slug:r})=>n(async s=>{const d={};if(t!==void 0&&(d.name=t),r!==void 0&&r!==""&&(d.slug=S(r)),e!==void 0&&(d.logo=e===""?void 0:e),a!==void 0&&(d.metadata=JSON.stringify(a)),Object.keys(d).length===0)return u({id:i});const m=await s.adapter.update({model:"organization",update:d,where:[{field:"id",value:i}]});return u(m??{id:i})}),updateOrgRole:({permission:e,roleId:a})=>n(async t=>{const i=await t.adapter.update({model:"organizationRole",update:{permission:JSON.stringify(e),updatedAt:new Date},where:[{field:"id",value:a}]});return u(i??{id:a,permission:JSON.stringify(e)})}),updateTeam:({name:e,teamId:a})=>n(async t=>{const i=await t.adapter.update({model:"team",update:{name:e,updatedAt:new Date},where:[{field:"id",value:a}]});return u(i??{id:a,name:e})}),createUser:({data:e,email:a,name:t,password:i,role:r})=>n(async s=>{const d=a.toLowerCase();if(await s.internalAdapter.findUserByEmail(d))throw new f("a user with this email already exists","USER_ALREADY_EXISTS");const m={...e};delete m.isAnonymous;const w=await s.internalAdapter.createUser({email:d,name:t,role:r===void 0?void 0:g(r),...m},{method:"admin"});if(i!==void 0&&i!==""){const y=await s.password.hash(i);await s.internalAdapter.linkAccount({accountId:w.id,password:y,providerId:"credential",userId:w.id})}return p(w)}),deletePasskey:({passkeyId:e})=>n(async a=>{await a.adapter.delete({model:"passkey",where:[{field:"id",value:e}]})}),disableTwoFactor:({userId:e})=>n(async a=>{await a.adapter.deleteMany({model:"twoFactor",where:[{field:"userId",value:e}]}),await a.internalAdapter.updateUser(e,{twoFactorEnabled:!1})}),impersonateUser:({userId:e})=>n(async a=>{const t=await a.internalAdapter.findUserById(e);if(!t)throw new f("user not found","USER_NOT_FOUND");const i=c.impersonationSeconds;let r=O;if(i!==void 0){if(!Number.isInteger(i)||!Number.isFinite(i)||i<=0)throw new f("impersonationSeconds must be a positive finite integer","INVALID_IMPERSONATION_SECONDS");r=Math.min(i,E)}const s=new Date(Date.now()+r*1e3),d=await a.internalAdapter.createSession(e,!0,{expiresAt:s,impersonatedBy:c.impersonatedBy??e},!0);return{expiresAt:d.expiresAt instanceof Date?d.expiresAt.getTime():s.getTime(),token:d.token,user:p(t)}}),listAccounts:({userId:e})=>n(async a=>(await a.adapter.findMany({model:"account",where:[{field:"userId",value:e}]})).map(i=>u(i))),listInvitations:({limit:e,offset:a,organizationId:t})=>n(i=>v(i,"invitation",{limit:e,offset:a,where:[{field:"organizationId",value:t}]})),createSignUpInvitation:({email:e,expiresInSeconds:a,invitedBy:t})=>n(async()=>{const i=await R(o,{email:e,expiresInSeconds:a,invitedBy:t});return{...u({...i}),token:i.token}}),listSignUpInvitations:({limit:e,offset:a})=>n(t=>v(t,"signUpInvitation",{limit:e,offset:a,sortBy:{direction:"desc",field:"createdAt"}})),revokeSignUpInvitation:({email:e})=>n(async()=>U(o,{email:e})),listMembers:({limit:e,offset:a,organizationId:t})=>n(i=>v(i,"member",{limit:e,offset:a,sortBy:{direction:"desc",field:"createdAt"},where:[{field:"organizationId",value:t}]})),listOrganizations:({limit:e,offset:a})=>n(t=>v(t,"organization",{limit:e,offset:a,sortBy:{direction:"desc",field:"createdAt"}})),listPasskeys:({userId:e})=>n(async a=>(await a.adapter.findMany({model:"passkey",where:[{field:"userId",value:e}]})).map(i=>u(i))),listSessions:({limit:e,offset:a,userId:t})=>n(i=>v(i,"session",{limit:e,offset:a,sortBy:{direction:"desc",field:"createdAt"},where:t===void 0||t===""?void 0:[{field:"userId",value:t}]})),listUsers:({filterField:e,filterValue:a,limit:t,offset:i,search:r,searchField:s,sortBy:d,sortDirection:m})=>n(w=>{const y=[];return r!==void 0&&r!==""&&y.push({field:s??"email",operator:"contains",value:r}),a!==void 0&&y.push({field:e??"email",operator:"eq",value:a}),v(w,"user",{limit:t,offset:i,sortBy:{direction:m??"desc",field:d??"createdAt"},where:y})}),removeMember:({memberId:e})=>n(async a=>{await a.adapter.delete({model:"member",where:[{field:"id",value:e}]})}),removeUser:({userId:e})=>n(async a=>{const t=b(a.options);t.member&&await _(a,e),await Promise.all(k.filter(({model:i})=>t[i]).map(({field:i,model:r})=>a.adapter.deleteMany({model:r,where:[{field:i,value:e}]}))),await a.internalAdapter.deleteUserSessions(e),await a.internalAdapter.deleteUser(e)}),revokeUserSession:({sessionId:e})=>n(async a=>{const t=await a.adapter.findOne({model:"session",where:[{field:"id",value:e}]});t?.token&&await a.internalAdapter.deleteSession(t.token)}),revokeUserSessions:({userId:e})=>n(async a=>{await a.internalAdapter.deleteUserSessions(e)}),setRole:({role:e,userId:a})=>n(async t=>{const i=await t.internalAdapter.updateUser(a,{role:g(e)});return p(i)}),setUserPassword:({newPassword:e,userId:a})=>n(async t=>{const i=t.password.config.minPasswordLength,r=t.password.config.maxPasswordLength;if(e.length<i)throw new f(`password must be at least ${i.toString()} characters`,"PASSWORD_TOO_SHORT");if(e.length>r)throw new f(`password must be at most ${r.toString()} characters`,"PASSWORD_TOO_LONG");if(!await t.internalAdapter.findUserById(a))throw new f("user not found","USER_NOT_FOUND");const d=(await t.internalAdapter.findAccounts(a)).some(w=>w.providerId==="credential");if(!d&&t.options.emailAndPassword?.enabled!==!0)throw new f("email/password sign-in is disabled for this deployment","EMAIL_PASSWORD_DISABLED");const m=await t.password.hash(e);d?await t.internalAdapter.updatePassword(a,m):await t.internalAdapter.linkAccount({accountId:a,password:m,providerId:"credential",userId:a})}),unbanUser:({userId:e})=>n(async a=>{const t=await a.internalAdapter.updateUser(e,{banExpires:null,banned:!1,banReason:null});return p(t)}),unlinkAccount:({accountId:e,userId:a})=>n(async t=>{await t.adapter.delete({model:"account",where:[{field:"id",value:e},{connector:"AND",field:"userId",value:a}]})}),updateUser:({data:e,userId:a})=>n(async t=>{const i=await t.internalAdapter.updateUser(a,e);return p(i)})}};export{f as LunoraAuthAdminError,W as createAuthAdmin};
@@ -0,0 +1,2 @@
1
+ import{createAdapterFactory as m}from"better-auth/adapters";import{checksSchema as p,registerSchemaCheck as f,createSchemaCheck as h,getExpectedSchema as y,diffSchema as b,formatSchemaFinding as w}from"@better-auth/core/db/internal";import{q as S}from"./quote-identifier-CGiYFBvY.mjs";import{C as g,d as x,c as u}from"./sql-store-uMubPYI9.mjs";const A=e=>({all:(a,t)=>Promise.resolve([...e.sql.exec(a,...t)]),run:(a,t)=>([...e.sql.exec(a,...t)],Promise.resolve())}),O=e=>async a=>e.transaction(a),M=e=>({parameters:[],sql:`PRAGMA table_info(${S(e)})`}),q=e=>({parameters:[e],sql:"SELECT * FROM pragma_table_info(?)"}),I=e=>{const a=String(e.name),t=e.dflt_value;return{hasDefault:t!=null||a===g,name:a,nullable:Number(e.notnull??0)===0}},v=async(e,a,t)=>{const{parameters:n,sql:r}=a(t),s=await e.all(r,n);if(s.length!==0)return{columns:s.map(o=>I(o)),name:t}},C=e=>{const a=["@lunora/auth: the auth tables do not match the better-auth options this app runs.",...e.map(t=>` - ${w(t,"database")}`),"Fix the schema, not the adapter. better-auth's own tables are created by `ensureMigrated(...)` or by","`compileMigrationsSql(...)` at deploy time; tables you declared yourself come from `authTables(options)`","in `lunora/schema.ts` plus `lunora migrate`. `npx auth migrate` does not apply to a Lunora app."];console.error(a.join(`
2
+ `))},T=async(e,a,t)=>{const n=y(e),r=Object.entries(n).filter(([,c])=>!c.disableMigrations).map(([c])=>c),s=await Promise.all(r.map(async c=>v(a,t,c))),o=b(n,s.filter(c=>c!==void 0));return o.length>0&&C(o),o},i=(e,a)=>t=>{const n=e(t);if(p(t)){const{database:r,executor:s,tableInfo:o}=a;f(n,h(async()=>T(t,s,o),"database",r))}return n},E=e=>e,l=e=>e??null,N=e=>e,R=e=>({consumeOne:async({model:a,where:t})=>l(await e.consumeOne(a,t)),count:async({model:a,where:t})=>e.count(a,t??[]),create:async({data:a,model:t})=>E(await e.create(t,a)),delete:async({model:a,where:t})=>{await e.remove(a,t)},deleteMany:async({model:a,where:t})=>e.remove(a,t),findMany:async({limit:a,model:t,offset:n,sortBy:r,where:s})=>N(await e.read(t,{limit:a,offset:n,sortBy:r,where:s??[]})),findOne:async({model:a,where:t})=>{const[n]=await e.read(a,{limit:1,where:t});return l(n)},incrementOne:async({increment:a,model:t,set:n,where:r})=>l(await e.incrementOne(t,r,a,n)),update:async({model:a,update:t,where:n})=>{const[r]=await e.update(a,n,t);return l(r)},updateMany:async({model:a,update:t,where:n})=>(await e.update(a,n,t)).length}),d=(e,a)=>{const t=r=>s=>m({adapter:()=>R(e),config:{adapterId:"lunora",adapterName:"Lunora Adapter",transaction:r(s),supportsBooleans:!1,supportsDates:!1,supportsJSON:!1,supportsNumericIds:!1}})(s),n=()=>!1;return t(a?r=>async s=>a(async()=>s(t(n)(r))):n)},k=e=>{const a=x(e);return i(d(u(a)),{database:e,executor:a,tableInfo:M})},L=e=>{const a=A(e);return i(d(u(a),O(e)),{database:e,executor:a,tableInfo:q})};export{k as a,L as b,A as d,d as l};