@lunora/auth 1.0.0-alpha.50 → 1.0.0-alpha.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter.d.mts +4 -43
- package/dist/adapter.d.ts +4 -43
- package/dist/adapter.mjs +1 -1
- package/dist/index.d.mts +173 -4
- package/dist/index.d.ts +173 -4
- package/dist/index.mjs +1 -1
- package/dist/packem_shared/AUTH_DO_AUDIT_PATH-Cy_f9vvT.mjs +1 -0
- package/dist/packem_shared/adapter-RvDcm0Zy.mjs +1 -0
- package/dist/packem_shared/adapter.d-CQD49h5g.d.mts +76 -0
- package/dist/packem_shared/adapter.d-DiqyUMUb.d.ts +76 -0
- package/dist/packem_shared/authDoColumnAdditions-B8BRbdzn.mjs +1 -0
- package/dist/packem_shared/createDoAuthWiring-Dd6AloWo.mjs +1 -0
- package/package.json +2 -2
package/dist/adapter.d.mts
CHANGED
|
@@ -1,43 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
* A better-auth database adapter backed by an {@link AuthStore} — the bridge
|
|
6
|
-
* that routes better-auth's reads and writes through Lunora's data layer
|
|
7
|
-
* instead of better-auth's built-in D1/Kysely adapter. Pass the result as
|
|
8
|
-
* `createAuth({ database: lunoraAuthAdapter(store) })`; better-auth's
|
|
9
|
-
* `createAdapterFactory` handles id generation, default values, field-name
|
|
10
|
-
* mapping and output shaping, so this only translates the cleaned CRUD calls
|
|
11
|
-
* onto the store.
|
|
12
|
-
*
|
|
13
|
-
* ```ts
|
|
14
|
-
* const auth = createAuth({
|
|
15
|
-
* secret: env.AUTH_SECRET,
|
|
16
|
-
* emailAndPassword: { enabled: true },
|
|
17
|
-
* database: lunoraAuthAdapter(lunoraStore), // lunoraStore writes via ctx.db
|
|
18
|
-
* });
|
|
19
|
-
* ```
|
|
20
|
-
*
|
|
21
|
-
* Scope: the {@link AuthStore} interface is single-table CRUD. better-auth's
|
|
22
|
-
* relational `join` reads (an advanced opt-in) are not handled — pair the
|
|
23
|
-
* adapter with `disableJoins` or let better-auth fall back to per-table reads.
|
|
24
|
-
*/
|
|
25
|
-
declare const lunoraAuthAdapter: (store: AuthStore) => ReturnType<typeof createAdapterFactory>;
|
|
26
|
-
/**
|
|
27
|
-
* One-liner for the common case: a better-auth `database` backed by a Cloudflare
|
|
28
|
-
* D1 binding, via Lunora's SQL store — equivalent to
|
|
29
|
-
* `lunoraAuthAdapter(createSqlAuthStore(d1Executor(d1)))`.
|
|
30
|
-
*
|
|
31
|
-
* Prefer this over passing the raw `env.DB` as `database`. With raw D1,
|
|
32
|
-
* better-auth resolves its Kysely adapter through a runtime `await import(...)`
|
|
33
|
-
* inside `auth.$context`, and that dynamic import never settles under
|
|
34
|
-
* `@cloudflare/vite-plugin`'s worker runner — so it hangs *every* auth request
|
|
35
|
-
* in `pnpm dev` (a standalone `wrangler dev` or a deployed worker bundle it
|
|
36
|
-
* up-front, so they're unaffected — which makes the hang baffling to debug).
|
|
37
|
-
* This explicit adapter skips that import entirely, so dev and prod behave the
|
|
38
|
-
* same. The migration instance is the one exception — it wants raw `env.DB` so
|
|
39
|
-
* `ensureMigrated`'s Kysely migrator can create the tables (its `$context` is
|
|
40
|
-
* never resolved, so the hang doesn't apply there).
|
|
41
|
-
*/
|
|
42
|
-
declare const lunoraD1Adapter: (d1: Parameters<typeof d1Executor>[0]) => ReturnType<typeof lunoraAuthAdapter>;
|
|
43
|
-
export { lunoraAuthAdapter, lunoraD1Adapter };
|
|
1
|
+
import 'better-auth/adapters';
|
|
2
|
+
export { l as lunoraAuthAdapter, a as lunoraD1Adapter, b as lunoraDoAdapter } from "./packem_shared/adapter.d-CQD49h5g.mjs";
|
|
3
|
+
import "./sql-store.mjs";
|
|
4
|
+
import "./store.mjs";
|
package/dist/adapter.d.ts
CHANGED
|
@@ -1,43 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
* A better-auth database adapter backed by an {@link AuthStore} — the bridge
|
|
6
|
-
* that routes better-auth's reads and writes through Lunora's data layer
|
|
7
|
-
* instead of better-auth's built-in D1/Kysely adapter. Pass the result as
|
|
8
|
-
* `createAuth({ database: lunoraAuthAdapter(store) })`; better-auth's
|
|
9
|
-
* `createAdapterFactory` handles id generation, default values, field-name
|
|
10
|
-
* mapping and output shaping, so this only translates the cleaned CRUD calls
|
|
11
|
-
* onto the store.
|
|
12
|
-
*
|
|
13
|
-
* ```ts
|
|
14
|
-
* const auth = createAuth({
|
|
15
|
-
* secret: env.AUTH_SECRET,
|
|
16
|
-
* emailAndPassword: { enabled: true },
|
|
17
|
-
* database: lunoraAuthAdapter(lunoraStore), // lunoraStore writes via ctx.db
|
|
18
|
-
* });
|
|
19
|
-
* ```
|
|
20
|
-
*
|
|
21
|
-
* Scope: the {@link AuthStore} interface is single-table CRUD. better-auth's
|
|
22
|
-
* relational `join` reads (an advanced opt-in) are not handled — pair the
|
|
23
|
-
* adapter with `disableJoins` or let better-auth fall back to per-table reads.
|
|
24
|
-
*/
|
|
25
|
-
declare const lunoraAuthAdapter: (store: AuthStore) => ReturnType<typeof createAdapterFactory>;
|
|
26
|
-
/**
|
|
27
|
-
* One-liner for the common case: a better-auth `database` backed by a Cloudflare
|
|
28
|
-
* D1 binding, via Lunora's SQL store — equivalent to
|
|
29
|
-
* `lunoraAuthAdapter(createSqlAuthStore(d1Executor(d1)))`.
|
|
30
|
-
*
|
|
31
|
-
* Prefer this over passing the raw `env.DB` as `database`. With raw D1,
|
|
32
|
-
* better-auth resolves its Kysely adapter through a runtime `await import(...)`
|
|
33
|
-
* inside `auth.$context`, and that dynamic import never settles under
|
|
34
|
-
* `@cloudflare/vite-plugin`'s worker runner — so it hangs *every* auth request
|
|
35
|
-
* in `pnpm dev` (a standalone `wrangler dev` or a deployed worker bundle it
|
|
36
|
-
* up-front, so they're unaffected — which makes the hang baffling to debug).
|
|
37
|
-
* This explicit adapter skips that import entirely, so dev and prod behave the
|
|
38
|
-
* same. The migration instance is the one exception — it wants raw `env.DB` so
|
|
39
|
-
* `ensureMigrated`'s Kysely migrator can create the tables (its `$context` is
|
|
40
|
-
* never resolved, so the hang doesn't apply there).
|
|
41
|
-
*/
|
|
42
|
-
declare const lunoraD1Adapter: (d1: Parameters<typeof d1Executor>[0]) => ReturnType<typeof lunoraAuthAdapter>;
|
|
43
|
-
export { lunoraAuthAdapter, lunoraD1Adapter };
|
|
1
|
+
import 'better-auth/adapters';
|
|
2
|
+
export { l as lunoraAuthAdapter, a as lunoraD1Adapter, b as lunoraDoAdapter } from "./packem_shared/adapter.d-DiqyUMUb.js";
|
|
3
|
+
import "./sql-store.js";
|
|
4
|
+
import "./store.js";
|
package/dist/adapter.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
1
|
+
import"better-auth/adapters";import{s as p,l as e,w as l}from"./packem_shared/adapter-RvDcm0Zy.mjs";import"./sql-store.mjs";export{p as lunoraAuthAdapter,e as lunoraD1Adapter,l as lunoraDoAdapter};
|
package/dist/index.d.mts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
import { D as DoStorageLike } from "./packem_shared/adapter.d-CQD49h5g.mjs";
|
|
2
|
+
export { l as lunoraAuthAdapter, a as lunoraD1Adapter, b as lunoraDoAdapter } from "./packem_shared/adapter.d-CQD49h5g.mjs";
|
|
2
3
|
import { LunoraError } from '@lunora/errors';
|
|
3
4
|
import { L as LunoraAuth, a as LunoraAuthOptions } from "./packem_shared/create-auth.d-De6IOirt.mjs";
|
|
4
5
|
export { c as createAuth, r as resolveAuthOptions } from "./packem_shared/create-auth.d-De6IOirt.mjs";
|
|
5
|
-
import { AppendAuthAuditEntry, AppendAuthAuditOptions, AuthAuditEvent } from "./audit.mjs";
|
|
6
|
-
export { AUTH_AUDIT_TABLE, type AuthAuditEntry, type AuthAuditOutcome, type
|
|
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";
|
|
7
8
|
import { createAuthMiddleware } from 'better-auth/api';
|
|
8
9
|
import { SqlExecutor } from "./sql-store.mjs";
|
|
9
10
|
export { createSqlAuthStore, d1Executor } from "./sql-store.mjs";
|
|
@@ -563,6 +564,174 @@ declare const withAuthAudit: <Options extends {
|
|
|
563
564
|
after?: unknown;
|
|
564
565
|
};
|
|
565
566
|
}>(options: Options, config: AuthAuditHookConfig) => Options;
|
|
567
|
+
/**
|
|
568
|
+
* The Durable Object state slice this class needs — structural so unit tests can
|
|
569
|
+
* pass a double without depending on the workers runtime.
|
|
570
|
+
*/
|
|
571
|
+
interface AuthDoState {
|
|
572
|
+
storage: DoStorageLike;
|
|
573
|
+
}
|
|
574
|
+
/** Path the worker calls to resolve a request's identity. Not part of `/api/auth/*`. */
|
|
575
|
+
declare const RESOLVE_SESSION_PATH = "/__lunora/auth/session";
|
|
576
|
+
/**
|
|
577
|
+
* Path the worker calls to read the audit log. Also not part of `/api/auth/*`.
|
|
578
|
+
*
|
|
579
|
+
* The audit table lives in this object like every other auth table, so the worker
|
|
580
|
+
* cannot query it directly — same constraint as the session route, same shared secret.
|
|
581
|
+
*/
|
|
582
|
+
declare const READ_AUDIT_PATH = "/__lunora/auth/audit";
|
|
583
|
+
/** Header carrying the shared secret that authenticates the calling worker. */
|
|
584
|
+
declare const INTERNAL_SECRET_HEADER = "x-lunora-auth-do-secret";
|
|
585
|
+
/**
|
|
586
|
+
* Options for the auth DO, beyond the better-auth options themselves.
|
|
587
|
+
* @experimental
|
|
588
|
+
*/
|
|
589
|
+
interface AuthDoOptions {
|
|
590
|
+
/** Base path the auth routes are served under. Must match the worker's. */
|
|
591
|
+
basePath?: string;
|
|
592
|
+
/**
|
|
593
|
+
* Shared secret authenticating the worker on {@link RESOLVE_SESSION_PATH}.
|
|
594
|
+
*
|
|
595
|
+
* The DO binding is reachable from any worker bound to the namespace, so the
|
|
596
|
+
* binding alone is not an authorization boundary — same reasoning as
|
|
597
|
+
* `SessionDO`'s `SESSION_DO_SECRET`. When this is unset the internal route is
|
|
598
|
+
* refused outright rather than served unauthenticated: a missing secret is a
|
|
599
|
+
* misconfiguration, and answering identity questions to anyone is the one
|
|
600
|
+
* failure mode worth being loud about. `/api/auth/*` is unaffected — those
|
|
601
|
+
* routes carry their own credentials.
|
|
602
|
+
*/
|
|
603
|
+
internalSecret?: string;
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Base class for an app's auth Durable Object. Subclass it (or let codegen emit the
|
|
607
|
+
* subclass) and register the subclass in `wrangler.jsonc`.
|
|
608
|
+
*
|
|
609
|
+
* ```ts
|
|
610
|
+
* export class AuthDO extends LunoraAuthDO {
|
|
611
|
+
* public constructor(state: DurableObjectState, env: Env) {
|
|
612
|
+
* super(state, () => ({ secret: env.AUTH_SECRET, plugins: [scim({ … })] }), {
|
|
613
|
+
* internalSecret: env.AUTH_DO_SECRET,
|
|
614
|
+
* });
|
|
615
|
+
* }
|
|
616
|
+
* }
|
|
617
|
+
* ```
|
|
618
|
+
* @experimental
|
|
619
|
+
*/
|
|
620
|
+
declare class LunoraAuthDO {
|
|
621
|
+
#private;
|
|
622
|
+
/**
|
|
623
|
+
* @param state The Durable Object state — its `storage` becomes better-auth's database.
|
|
624
|
+
* @param optionsFactory Builds the better-auth options. Called once, lazily, on the first request.
|
|
625
|
+
* @param options Auth-DO specific options (base path, internal secret).
|
|
626
|
+
*/
|
|
627
|
+
constructor(state: AuthDoState, optionsFactory: () => LunoraAuthOptions, options?: AuthDoOptions);
|
|
628
|
+
/**
|
|
629
|
+
* Serve an auth request. Routes under `basePath` go to better-auth; the internal
|
|
630
|
+
* session route is handled here; anything else is a 404.
|
|
631
|
+
*/
|
|
632
|
+
fetch(request: Request): Promise<Response>;
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* The `CREATE TABLE` / `CREATE INDEX` statements for a better-auth config, in
|
|
636
|
+
* execution order (every table before any index).
|
|
637
|
+
*
|
|
638
|
+
* All statements are `IF NOT EXISTS`, so this is safe to run on every cold start —
|
|
639
|
+
* which is how `LunoraAuthDO` uses it. It creates; it never alters or drops, so a
|
|
640
|
+
* schema that has already diverged is left alone rather than half-migrated.
|
|
641
|
+
*
|
|
642
|
+
* Physical names throughout: `modelName` for tables, `fieldName ?? key` for columns,
|
|
643
|
+
* and better-auth's own resolved `columns` / `name` for indexes — so an index name
|
|
644
|
+
* here is the name better-auth's introspection expects to find.
|
|
645
|
+
* @param options The better-auth options the DO will run — the plugin list decides which tables exist.
|
|
646
|
+
* @returns SQL statements to execute in order.
|
|
647
|
+
* @experimental
|
|
648
|
+
*/
|
|
649
|
+
declare const authDoSchemaStatements: (options: LunoraAuthOptions) => string[];
|
|
650
|
+
/**
|
|
651
|
+
* `ALTER TABLE … ADD COLUMN` statements for columns the live schema is missing.
|
|
652
|
+
*
|
|
653
|
+
* ## Why this is needed at all
|
|
654
|
+
*
|
|
655
|
+
* {@link authDoSchemaStatements} is entirely `IF NOT EXISTS`, which makes it safe to
|
|
656
|
+
* re-run but blind to change: a *new table* appears on the next cold start, a *new
|
|
657
|
+
* column on an existing table* never does. That is not a hypothetical — enabling the
|
|
658
|
+
* `admin` plugin after first deploy adds `role` / `banned` / `banExpires` to `user`,
|
|
659
|
+
* and without this the object would keep serving a `user` table that cannot hold them.
|
|
660
|
+
*
|
|
661
|
+
* Additive only. Nothing here drops, renames, or retypes a column: a column that
|
|
662
|
+
* exists is left exactly as it is, so a schema someone has deliberately diverged is
|
|
663
|
+
* never "corrected" underneath them.
|
|
664
|
+
*
|
|
665
|
+
* ## What SQLite will not let us do
|
|
666
|
+
*
|
|
667
|
+
* `ADD COLUMN` cannot introduce a `NOT NULL` column without a default (existing rows
|
|
668
|
+
* would violate it immediately), and cannot introduce `UNIQUE`. So a required column
|
|
669
|
+
* with a static default is added with both; a required column *without* one is added
|
|
670
|
+
* **nullable**, which differs from what a fresh `CREATE TABLE` would produce. That is
|
|
671
|
+
* the deliberate trade: better-auth writes these fields on every insert, so nullable
|
|
672
|
+
* is harmless, whereas refusing to add the column at all would leave the object
|
|
673
|
+
* broken. Uniqueness is unaffected — better-auth expresses it as a separate index, and
|
|
674
|
+
* those are `IF NOT EXISTS`, so they are created by the statements above.
|
|
675
|
+
* @param options The better-auth options the DO runs, already resolved.
|
|
676
|
+
* @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).
|
|
677
|
+
* @returns SQL statements to execute in order; empty when the live schema is current.
|
|
678
|
+
* @experimental
|
|
679
|
+
*/
|
|
680
|
+
declare const authDoColumnAdditions: (options: LunoraAuthOptions, existingColumns: (table: string) => Iterable<string>) => string[];
|
|
681
|
+
/** The slice of a Durable Object namespace this needs — structural, so tests need no runtime. */
|
|
682
|
+
interface AuthNamespaceLike {
|
|
683
|
+
get: (id: unknown) => {
|
|
684
|
+
fetch: (request: Request) => Promise<Response>;
|
|
685
|
+
};
|
|
686
|
+
idFromName: (name: string) => unknown;
|
|
687
|
+
}
|
|
688
|
+
/** What {@link createDoAuthWiring} needs, already resolved against `env`. */
|
|
689
|
+
interface DoAuthWiringOptions {
|
|
690
|
+
/** Base path the auth routes are served under. Defaults to `/api/auth`. */
|
|
691
|
+
basePath?: string;
|
|
692
|
+
/**
|
|
693
|
+
* Shared secret presented on the object's internal session route. `undefined`
|
|
694
|
+
* means identity resolution fails closed — see {@link DoAuthWiring.resolveIdentity}.
|
|
695
|
+
*/
|
|
696
|
+
internalSecret: string | undefined;
|
|
697
|
+
/** The bound namespace, or `undefined` when the binding is absent from `env`. */
|
|
698
|
+
namespace: AuthNamespaceLike | undefined;
|
|
699
|
+
/**
|
|
700
|
+
* Name of the object instance holding the auth tables. Defaults to `"auth"`.
|
|
701
|
+
*
|
|
702
|
+
* One object owns the whole auth schema, so this exists to let an app pick the
|
|
703
|
+
* name (or run separate objects per deployment/tenant) rather than being pinned to
|
|
704
|
+
* a hardcoded one.
|
|
705
|
+
*/
|
|
706
|
+
objectName?: string;
|
|
707
|
+
}
|
|
708
|
+
/** The worker options DO-backed auth replaces. */
|
|
709
|
+
interface DoAuthWiring {
|
|
710
|
+
/**
|
|
711
|
+
* Reads the audit log out of the object, so the studio's audit feed works in DO
|
|
712
|
+
* mode. Answers an empty page rather than throwing when the object is unreachable
|
|
713
|
+
* or no secret is configured — an unavailable feed should read as empty, not 500
|
|
714
|
+
* the studio.
|
|
715
|
+
*/
|
|
716
|
+
auditReader: AuthAuditReader;
|
|
717
|
+
/** Forwards `/api/auth/*` to the object; `undefined` for anything else. */
|
|
718
|
+
authHandler: (request: Request) => Promise<Response | undefined>;
|
|
719
|
+
/** Resolves a request's identity by asking the object. `null` when anonymous, unreachable, or ungated. */
|
|
720
|
+
resolveIdentity: (request: Request) => Promise<null | {
|
|
721
|
+
userId: string;
|
|
722
|
+
}>;
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* Build the worker-side wiring for an auth Durable Object.
|
|
726
|
+
*
|
|
727
|
+
* Every failure path answers "not authenticated" rather than throwing: this runs on
|
|
728
|
+
* the request path for every request that touches `ctx.auth`, and a throw there would
|
|
729
|
+
* turn a misconfiguration into a 500 on traffic that has nothing to do with auth.
|
|
730
|
+
* @param options The resolved namespace, secret, and names.
|
|
731
|
+
* @returns The `authHandler` / `resolveIdentity` pair.
|
|
732
|
+
* @experimental
|
|
733
|
+
*/
|
|
734
|
+
declare const createDoAuthWiring: (options: DoAuthWiringOptions) => DoAuthWiring;
|
|
566
735
|
/** better-auth's `databaseHooks` shape, derived so a rename upstream fails to compile rather than silently mis-hooking. */
|
|
567
736
|
type DatabaseHooks = NonNullable<BetterAuthOptions["databaseHooks"]>;
|
|
568
737
|
/** Config for the signup gate hooks: the base {@link EmailGateConfig} plus an optional classification tap. */
|
|
@@ -697,4 +866,4 @@ declare const validateSessionPolicy: (policy: SessionPolicy) => SessionPolicy;
|
|
|
697
866
|
* with the same 60s cookie cache as `rolling`.
|
|
698
867
|
*/
|
|
699
868
|
declare const sessionPresets: Record<"longLived" | "rolling" | "strict", SessionPolicy>;
|
|
700
|
-
export { type AppendAuthAuditEntry, type AppendAuthAuditOptions, type AuthAccount, type AuthAdmin, type AuthAdminSession, type AuthAdminUser, type AuthAuditEvent, type AuthAuditHookConfig, type AuthCapabilities, type AuthConfigInfo, type AuthInvitation, type AuthMember, type AuthOrgRole, type AuthOrganization, type AuthPage, type AuthPasskey, type AuthTeam, type AuthTeamMember, type AuthTimestamp, type AuthUserFieldSpec, type CreateAuthAdminOptions, DEFAULT_AUTH_BASE_PATH, type EmailClassification, type EmailGateConfig, type EmailGateHookConfig, type ImpersonationResult, type ListUsersOptions, type LunoraAuth, LunoraAuthAdminError, type LunoraAuthOptions, type SessionPolicy, type SqlExecutor, authAuditHook, buildAuditEntry, compileMigrationsSql, createAuthAdmin, emailGateDatabaseHooks, ensureMigrated, eventForPath, handleAuthRequest, sessionPresets, validateSessionPolicy, withAuthAudit, withEmailGate };
|
|
869
|
+
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 };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
import { D as DoStorageLike } from "./packem_shared/adapter.d-DiqyUMUb.js";
|
|
2
|
+
export { l as lunoraAuthAdapter, a as lunoraD1Adapter, b as lunoraDoAdapter } from "./packem_shared/adapter.d-DiqyUMUb.js";
|
|
2
3
|
import { LunoraError } from '@lunora/errors';
|
|
3
4
|
import { L as LunoraAuth, a as LunoraAuthOptions } from "./packem_shared/create-auth.d-De6IOirt.js";
|
|
4
5
|
export { c as createAuth, r as resolveAuthOptions } from "./packem_shared/create-auth.d-De6IOirt.js";
|
|
5
|
-
import { AppendAuthAuditEntry, AppendAuthAuditOptions, AuthAuditEvent } from "./audit.js";
|
|
6
|
-
export { AUTH_AUDIT_TABLE, type AuthAuditEntry, type AuthAuditOutcome, type
|
|
6
|
+
import { AppendAuthAuditEntry, AppendAuthAuditOptions, AuthAuditEvent, AuthAuditReader } from "./audit.js";
|
|
7
|
+
export { AUTH_AUDIT_TABLE, type AuthAuditEntry, type AuthAuditOutcome, type ReadAuthAuditOptions, appendAuthAuditEntry, createAuthAuditReader, ensureAuthAuditTable, readAuthAuditLog } from "./audit.js";
|
|
7
8
|
import { createAuthMiddleware } from 'better-auth/api';
|
|
8
9
|
import { SqlExecutor } from "./sql-store.js";
|
|
9
10
|
export { createSqlAuthStore, d1Executor } from "./sql-store.js";
|
|
@@ -563,6 +564,174 @@ declare const withAuthAudit: <Options extends {
|
|
|
563
564
|
after?: unknown;
|
|
564
565
|
};
|
|
565
566
|
}>(options: Options, config: AuthAuditHookConfig) => Options;
|
|
567
|
+
/**
|
|
568
|
+
* The Durable Object state slice this class needs — structural so unit tests can
|
|
569
|
+
* pass a double without depending on the workers runtime.
|
|
570
|
+
*/
|
|
571
|
+
interface AuthDoState {
|
|
572
|
+
storage: DoStorageLike;
|
|
573
|
+
}
|
|
574
|
+
/** Path the worker calls to resolve a request's identity. Not part of `/api/auth/*`. */
|
|
575
|
+
declare const RESOLVE_SESSION_PATH = "/__lunora/auth/session";
|
|
576
|
+
/**
|
|
577
|
+
* Path the worker calls to read the audit log. Also not part of `/api/auth/*`.
|
|
578
|
+
*
|
|
579
|
+
* The audit table lives in this object like every other auth table, so the worker
|
|
580
|
+
* cannot query it directly — same constraint as the session route, same shared secret.
|
|
581
|
+
*/
|
|
582
|
+
declare const READ_AUDIT_PATH = "/__lunora/auth/audit";
|
|
583
|
+
/** Header carrying the shared secret that authenticates the calling worker. */
|
|
584
|
+
declare const INTERNAL_SECRET_HEADER = "x-lunora-auth-do-secret";
|
|
585
|
+
/**
|
|
586
|
+
* Options for the auth DO, beyond the better-auth options themselves.
|
|
587
|
+
* @experimental
|
|
588
|
+
*/
|
|
589
|
+
interface AuthDoOptions {
|
|
590
|
+
/** Base path the auth routes are served under. Must match the worker's. */
|
|
591
|
+
basePath?: string;
|
|
592
|
+
/**
|
|
593
|
+
* Shared secret authenticating the worker on {@link RESOLVE_SESSION_PATH}.
|
|
594
|
+
*
|
|
595
|
+
* The DO binding is reachable from any worker bound to the namespace, so the
|
|
596
|
+
* binding alone is not an authorization boundary — same reasoning as
|
|
597
|
+
* `SessionDO`'s `SESSION_DO_SECRET`. When this is unset the internal route is
|
|
598
|
+
* refused outright rather than served unauthenticated: a missing secret is a
|
|
599
|
+
* misconfiguration, and answering identity questions to anyone is the one
|
|
600
|
+
* failure mode worth being loud about. `/api/auth/*` is unaffected — those
|
|
601
|
+
* routes carry their own credentials.
|
|
602
|
+
*/
|
|
603
|
+
internalSecret?: string;
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Base class for an app's auth Durable Object. Subclass it (or let codegen emit the
|
|
607
|
+
* subclass) and register the subclass in `wrangler.jsonc`.
|
|
608
|
+
*
|
|
609
|
+
* ```ts
|
|
610
|
+
* export class AuthDO extends LunoraAuthDO {
|
|
611
|
+
* public constructor(state: DurableObjectState, env: Env) {
|
|
612
|
+
* super(state, () => ({ secret: env.AUTH_SECRET, plugins: [scim({ … })] }), {
|
|
613
|
+
* internalSecret: env.AUTH_DO_SECRET,
|
|
614
|
+
* });
|
|
615
|
+
* }
|
|
616
|
+
* }
|
|
617
|
+
* ```
|
|
618
|
+
* @experimental
|
|
619
|
+
*/
|
|
620
|
+
declare class LunoraAuthDO {
|
|
621
|
+
#private;
|
|
622
|
+
/**
|
|
623
|
+
* @param state The Durable Object state — its `storage` becomes better-auth's database.
|
|
624
|
+
* @param optionsFactory Builds the better-auth options. Called once, lazily, on the first request.
|
|
625
|
+
* @param options Auth-DO specific options (base path, internal secret).
|
|
626
|
+
*/
|
|
627
|
+
constructor(state: AuthDoState, optionsFactory: () => LunoraAuthOptions, options?: AuthDoOptions);
|
|
628
|
+
/**
|
|
629
|
+
* Serve an auth request. Routes under `basePath` go to better-auth; the internal
|
|
630
|
+
* session route is handled here; anything else is a 404.
|
|
631
|
+
*/
|
|
632
|
+
fetch(request: Request): Promise<Response>;
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* The `CREATE TABLE` / `CREATE INDEX` statements for a better-auth config, in
|
|
636
|
+
* execution order (every table before any index).
|
|
637
|
+
*
|
|
638
|
+
* All statements are `IF NOT EXISTS`, so this is safe to run on every cold start —
|
|
639
|
+
* which is how `LunoraAuthDO` uses it. It creates; it never alters or drops, so a
|
|
640
|
+
* schema that has already diverged is left alone rather than half-migrated.
|
|
641
|
+
*
|
|
642
|
+
* Physical names throughout: `modelName` for tables, `fieldName ?? key` for columns,
|
|
643
|
+
* and better-auth's own resolved `columns` / `name` for indexes — so an index name
|
|
644
|
+
* here is the name better-auth's introspection expects to find.
|
|
645
|
+
* @param options The better-auth options the DO will run — the plugin list decides which tables exist.
|
|
646
|
+
* @returns SQL statements to execute in order.
|
|
647
|
+
* @experimental
|
|
648
|
+
*/
|
|
649
|
+
declare const authDoSchemaStatements: (options: LunoraAuthOptions) => string[];
|
|
650
|
+
/**
|
|
651
|
+
* `ALTER TABLE … ADD COLUMN` statements for columns the live schema is missing.
|
|
652
|
+
*
|
|
653
|
+
* ## Why this is needed at all
|
|
654
|
+
*
|
|
655
|
+
* {@link authDoSchemaStatements} is entirely `IF NOT EXISTS`, which makes it safe to
|
|
656
|
+
* re-run but blind to change: a *new table* appears on the next cold start, a *new
|
|
657
|
+
* column on an existing table* never does. That is not a hypothetical — enabling the
|
|
658
|
+
* `admin` plugin after first deploy adds `role` / `banned` / `banExpires` to `user`,
|
|
659
|
+
* and without this the object would keep serving a `user` table that cannot hold them.
|
|
660
|
+
*
|
|
661
|
+
* Additive only. Nothing here drops, renames, or retypes a column: a column that
|
|
662
|
+
* exists is left exactly as it is, so a schema someone has deliberately diverged is
|
|
663
|
+
* never "corrected" underneath them.
|
|
664
|
+
*
|
|
665
|
+
* ## What SQLite will not let us do
|
|
666
|
+
*
|
|
667
|
+
* `ADD COLUMN` cannot introduce a `NOT NULL` column without a default (existing rows
|
|
668
|
+
* would violate it immediately), and cannot introduce `UNIQUE`. So a required column
|
|
669
|
+
* with a static default is added with both; a required column *without* one is added
|
|
670
|
+
* **nullable**, which differs from what a fresh `CREATE TABLE` would produce. That is
|
|
671
|
+
* the deliberate trade: better-auth writes these fields on every insert, so nullable
|
|
672
|
+
* is harmless, whereas refusing to add the column at all would leave the object
|
|
673
|
+
* broken. Uniqueness is unaffected — better-auth expresses it as a separate index, and
|
|
674
|
+
* those are `IF NOT EXISTS`, so they are created by the statements above.
|
|
675
|
+
* @param options The better-auth options the DO runs, already resolved.
|
|
676
|
+
* @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).
|
|
677
|
+
* @returns SQL statements to execute in order; empty when the live schema is current.
|
|
678
|
+
* @experimental
|
|
679
|
+
*/
|
|
680
|
+
declare const authDoColumnAdditions: (options: LunoraAuthOptions, existingColumns: (table: string) => Iterable<string>) => string[];
|
|
681
|
+
/** The slice of a Durable Object namespace this needs — structural, so tests need no runtime. */
|
|
682
|
+
interface AuthNamespaceLike {
|
|
683
|
+
get: (id: unknown) => {
|
|
684
|
+
fetch: (request: Request) => Promise<Response>;
|
|
685
|
+
};
|
|
686
|
+
idFromName: (name: string) => unknown;
|
|
687
|
+
}
|
|
688
|
+
/** What {@link createDoAuthWiring} needs, already resolved against `env`. */
|
|
689
|
+
interface DoAuthWiringOptions {
|
|
690
|
+
/** Base path the auth routes are served under. Defaults to `/api/auth`. */
|
|
691
|
+
basePath?: string;
|
|
692
|
+
/**
|
|
693
|
+
* Shared secret presented on the object's internal session route. `undefined`
|
|
694
|
+
* means identity resolution fails closed — see {@link DoAuthWiring.resolveIdentity}.
|
|
695
|
+
*/
|
|
696
|
+
internalSecret: string | undefined;
|
|
697
|
+
/** The bound namespace, or `undefined` when the binding is absent from `env`. */
|
|
698
|
+
namespace: AuthNamespaceLike | undefined;
|
|
699
|
+
/**
|
|
700
|
+
* Name of the object instance holding the auth tables. Defaults to `"auth"`.
|
|
701
|
+
*
|
|
702
|
+
* One object owns the whole auth schema, so this exists to let an app pick the
|
|
703
|
+
* name (or run separate objects per deployment/tenant) rather than being pinned to
|
|
704
|
+
* a hardcoded one.
|
|
705
|
+
*/
|
|
706
|
+
objectName?: string;
|
|
707
|
+
}
|
|
708
|
+
/** The worker options DO-backed auth replaces. */
|
|
709
|
+
interface DoAuthWiring {
|
|
710
|
+
/**
|
|
711
|
+
* Reads the audit log out of the object, so the studio's audit feed works in DO
|
|
712
|
+
* mode. Answers an empty page rather than throwing when the object is unreachable
|
|
713
|
+
* or no secret is configured — an unavailable feed should read as empty, not 500
|
|
714
|
+
* the studio.
|
|
715
|
+
*/
|
|
716
|
+
auditReader: AuthAuditReader;
|
|
717
|
+
/** Forwards `/api/auth/*` to the object; `undefined` for anything else. */
|
|
718
|
+
authHandler: (request: Request) => Promise<Response | undefined>;
|
|
719
|
+
/** Resolves a request's identity by asking the object. `null` when anonymous, unreachable, or ungated. */
|
|
720
|
+
resolveIdentity: (request: Request) => Promise<null | {
|
|
721
|
+
userId: string;
|
|
722
|
+
}>;
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* Build the worker-side wiring for an auth Durable Object.
|
|
726
|
+
*
|
|
727
|
+
* Every failure path answers "not authenticated" rather than throwing: this runs on
|
|
728
|
+
* the request path for every request that touches `ctx.auth`, and a throw there would
|
|
729
|
+
* turn a misconfiguration into a 500 on traffic that has nothing to do with auth.
|
|
730
|
+
* @param options The resolved namespace, secret, and names.
|
|
731
|
+
* @returns The `authHandler` / `resolveIdentity` pair.
|
|
732
|
+
* @experimental
|
|
733
|
+
*/
|
|
734
|
+
declare const createDoAuthWiring: (options: DoAuthWiringOptions) => DoAuthWiring;
|
|
566
735
|
/** better-auth's `databaseHooks` shape, derived so a rename upstream fails to compile rather than silently mis-hooking. */
|
|
567
736
|
type DatabaseHooks = NonNullable<BetterAuthOptions["databaseHooks"]>;
|
|
568
737
|
/** Config for the signup gate hooks: the base {@link EmailGateConfig} plus an optional classification tap. */
|
|
@@ -697,4 +866,4 @@ declare const validateSessionPolicy: (policy: SessionPolicy) => SessionPolicy;
|
|
|
697
866
|
* with the same 60s cookie cache as `rolling`.
|
|
698
867
|
*/
|
|
699
868
|
declare const sessionPresets: Record<"longLived" | "rolling" | "strict", SessionPolicy>;
|
|
700
|
-
export { type AppendAuthAuditEntry, type AppendAuthAuditOptions, type AuthAccount, type AuthAdmin, type AuthAdminSession, type AuthAdminUser, type AuthAuditEvent, type AuthAuditHookConfig, type AuthCapabilities, type AuthConfigInfo, type AuthInvitation, type AuthMember, type AuthOrgRole, type AuthOrganization, type AuthPage, type AuthPasskey, type AuthTeam, type AuthTeamMember, type AuthTimestamp, type AuthUserFieldSpec, type CreateAuthAdminOptions, DEFAULT_AUTH_BASE_PATH, type EmailClassification, type EmailGateConfig, type EmailGateHookConfig, type ImpersonationResult, type ListUsersOptions, type LunoraAuth, LunoraAuthAdminError, type LunoraAuthOptions, type SessionPolicy, type SqlExecutor, authAuditHook, buildAuditEntry, compileMigrationsSql, createAuthAdmin, emailGateDatabaseHooks, ensureMigrated, eventForPath, handleAuthRequest, sessionPresets, validateSessionPolicy, withAuthAudit, withEmailGate };
|
|
869
|
+
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 };
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{s as r,l as o,w as a}from"./packem_shared/adapter-RvDcm0Zy.mjs";import{LunoraAuthAdminError as u,createAuthAdmin as i}from"./packem_shared/LunoraAuthAdminError-CiHsF1qZ.mjs";import{AUTH_AUDIT_TABLE as m,appendAuthAuditEntry as l,createAuthAuditReader as d,ensureAuthAuditTable as h,readAuthAuditLog as E}from"./audit.mjs";import{authAuditHook as p,buildAuditEntry as T,eventForPath as f,withAuthAudit as _}from"./packem_shared/authAuditHook-Dx3sqf3G.mjs";import{READ_AUDIT_PATH as D,INTERNAL_SECRET_HEADER as S,RESOLVE_SESSION_PATH as H,LunoraAuthDO as c}from"./packem_shared/AUTH_DO_AUDIT_PATH-Cy_f9vvT.mjs";import{createAuth as L,resolveAuthOptions as P}from"./packem_shared/createAuth-DS6PL8Mb.mjs";import{authDoColumnAdditions as I,authDoSchemaStatements as O}from"./packem_shared/authDoColumnAdditions-B8BRbdzn.mjs";import{createDoAuthWiring as y}from"./packem_shared/createDoAuthWiring-Dd6AloWo.mjs";import{emailGateDatabaseHooks as g,withEmailGate as v}from"./packem_shared/emailGateDatabaseHooks-DzBD1Qoq.mjs";import{assertEmailAllowed as b,classifyEmail as q,emailGateMiddleware as C,loadEmailDomainLists as F}from"./email-guard.mjs";import{DEFAULT_AUTH_BASE_PATH as k,handleAuthRequest as B}from"./packem_shared/DEFAULT_AUTH_BASE_PATH-kIwlEt8i.mjs";import{LunoraAuthHeadersError as W,withAuthPlugins as Y}from"./middleware.mjs";import{compileMigrationsSql as z,ensureMigrated as J}from"./packem_shared/compileMigrationsSql-C-2ALo6c.mjs";import{default as Q}from"./schema.mjs";import{sessionPresets as Z,validateSessionPolicy as $}from"./packem_shared/sessionPresets-DpEFjXKV.mjs";import{createSqlAuthStore as te,d1Executor as re}from"./sql-store.mjs";import{createMemoryAuthStore as ae,matchesWhere as Ae}from"./store.mjs";import{TURNSTILE_VERIFY_ENDPOINT as ie,verifyTurnstile as se}from"./turnstile.mjs";import{verifyTurnstileMiddleware as le}from"./turnstile-middleware.mjs";export{m as AUTH_AUDIT_TABLE,D as AUTH_DO_AUDIT_PATH,S as AUTH_DO_SECRET_HEADER,H as AUTH_DO_SESSION_PATH,k as DEFAULT_AUTH_BASE_PATH,u as LunoraAuthAdminError,c as LunoraAuthDO,W as LunoraAuthHeadersError,ie as TURNSTILE_VERIFY_ENDPOINT,l as appendAuthAuditEntry,b as assertEmailAllowed,p as authAuditHook,I as authDoColumnAdditions,O as authDoSchemaStatements,Q as authTables,T as buildAuditEntry,q as classifyEmail,z as compileMigrationsSql,L as createAuth,i as createAuthAdmin,d as createAuthAuditReader,y as createDoAuthWiring,ae as createMemoryAuthStore,te as createSqlAuthStore,re as d1Executor,g as emailGateDatabaseHooks,C as emailGateMiddleware,h as ensureAuthAuditTable,J as ensureMigrated,f as eventForPath,B as handleAuthRequest,F as loadEmailDomainLists,r as lunoraAuthAdapter,o as lunoraD1Adapter,a as lunoraDoAdapter,Ae as matchesWhere,E as readAuthAuditLog,P as resolveAuthOptions,Z as sessionPresets,$ as validateSessionPolicy,se as verifyTurnstile,le as verifyTurnstileMiddleware,_ as withAuthAudit,Y as withAuthPlugins,v as withEmailGate};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{w as i,d as h}from"./adapter-RvDcm0Zy.mjs";import{ensureAuthAuditTable as u,createAuthAuditReader as c}from"../audit.mjs";import{resolveAuthOptions as l,createAuth as d}from"./createAuth-DS6PL8Mb.mjs";import{authDoSchemaStatements as f,authDoColumnAdditions as m}from"./authDoColumnAdditions-B8BRbdzn.mjs";import{handleAuthRequest as A}from"./DEFAULT_AUTH_BASE_PATH-kIwlEt8i.mjs";const p=(o,t)=>{const s=Math.max(o.length,t.length);let e=o.length^t.length;for(let r=0;r<s;r+=1){const a=r<o.length?o.charCodeAt(r):0,n=r<t.length?t.charCodeAt(r):0;e|=a^n}return e===0},R="/__lunora/auth/session",E="/__lunora/auth/audit",_="x-lunora-auth-do-secret";class j{#e;#r;#t;#s;#o=!1;constructor(t,s,e={}){this.#t=t.storage,this.#r=s,this.#e=e}#a(){if(this.#s!==void 0)return this.#s;const t=this.#r();if(!this.#o){const s=l(t);for(const e of f(s))[...this.#t.sql.exec(e)];for(const e of m(s,r=>this.#i(r)))[...this.#t.sql.exec(e)];this.#o=!0}return this.#s=d({...t,database:i(this.#t)}),this.#s}#i(t){return[...this.#t.sql.exec("SELECT name FROM pragma_table_info(?)",t)].map(s=>String(s.name))}async#h(t){if(!this.#n(t))return Response.json({error:"unauthorized"},{status:401});const s=h(this.#t);await u(s);const e=await t.json(),r=await c(s).read(e??{});return Response.json({entries:r})}#n(t){const{internalSecret:s}=this.#e;if(s===void 0||s==="")return!1;const e=t.headers.get(_);return e!==null&&p(e,s)}async#u(t){if(!this.#n(t))return Response.json({error:"unauthorized"},{status:401});const s=(await this.#a().api.getSession({headers:t.headers}))?.user.id;return Response.json(s===void 0?{}:{userId:s})}async fetch(t){const s=new URL(t.url);if(s.pathname===R)return this.#u(t);if(s.pathname===E)return this.#h(t);const e=this.#a();return await A(e,t,this.#e.basePath)??Response.json({error:"not an auth route"},{status:404})}}export{_ as INTERNAL_SECRET_HEADER,j as LunoraAuthDO,E as READ_AUDIT_PATH,R as RESOLVE_SESSION_PATH};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createAdapterFactory as l}from"better-auth/adapters";import{createSqlAuthStore as c,d1Executor as m}from"../sql-store.mjs";const i=e=>({all:(a,r)=>Promise.resolve([...e.sql.exec(a,...r)]),run:(a,r)=>([...e.sql.exec(a,...r)],Promise.resolve())}),u=e=>async a=>e.transaction(a),p=e=>e,o=e=>e??null,w=e=>e,y=e=>({consumeOne:async({model:a,where:r})=>o(await e.consumeOne(a,r)),count:async({model:a,where:r})=>e.count(a,r??[]),create:async({data:a,model:r})=>p(await e.create(r,a)),delete:async({model:a,where:r})=>{await e.remove(a,r)},deleteMany:async({model:a,where:r})=>e.remove(a,r),findMany:async({limit:a,model:r,offset:n,sortBy:t,where:s})=>w(await e.read(r,{limit:a,offset:n,sortBy:t,where:s??[]})),findOne:async({model:a,where:r})=>{const[n]=await e.read(a,{limit:1,where:r});return o(n)},incrementOne:async({increment:a,model:r,set:n,where:t})=>o(await e.incrementOne(r,t,a,n)),update:async({model:a,update:r,where:n})=>{const[t]=await e.update(a,n,r);return o(t)},updateMany:async({model:a,update:r,where:n})=>(await e.update(a,n,r)).length}),d=(e,a)=>{const r=t=>s=>l({adapter:()=>y(e),config:{adapterId:"lunora",adapterName:"Lunora Adapter",transaction:t(s),supportsBooleans:!1,supportsDates:!1,supportsJSON:!1,supportsNumericIds:!1}})(s),n=()=>!1;return r(a?t=>async s=>a(async()=>s(r(n)(t))):n)},O=e=>d(c(m(e))),x=e=>d(c(i(e)),u(e));export{i as d,O as l,d as s,x as w};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { createAdapterFactory } from 'better-auth/adapters';
|
|
2
|
+
import { d1Executor } from "../sql-store.mjs";
|
|
3
|
+
import { AuthStore } from "../store.mjs";
|
|
4
|
+
/**
|
|
5
|
+
* The slice of `DurableObjectStorage` this module uses.
|
|
6
|
+
*
|
|
7
|
+
* Structural on purpose: it keeps `@lunora/auth` free of a `@lunora/do` dependency
|
|
8
|
+
* and lets tests supply a double whose `transaction` has the same semantics.
|
|
9
|
+
*/
|
|
10
|
+
interface DoStorageLike {
|
|
11
|
+
/** Synchronous SQL over the object's SQLite. */
|
|
12
|
+
sql: {
|
|
13
|
+
exec: (query: string, ...bindings: unknown[]) => Iterable<Record<string, unknown>>;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* The platform's async transaction primitive. Everything the closure executes
|
|
17
|
+
* against `sql` joins the transaction — it is connection-scoped, so no handle is
|
|
18
|
+
* threaded through — and a throw rolls the whole thing back.
|
|
19
|
+
*/
|
|
20
|
+
transaction: <R>(closure: () => Promise<R>) => Promise<R>;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Runs `closure` atomically. Supplied by stores whose backend has real
|
|
24
|
+
* transactions — a Durable Object's storage does; D1 does not.
|
|
25
|
+
*/
|
|
26
|
+
type TransactionRunner = <R>(closure: () => Promise<R>) => Promise<R>;
|
|
27
|
+
/**
|
|
28
|
+
* A better-auth database adapter backed by an {@link AuthStore} — the bridge
|
|
29
|
+
* that routes better-auth's reads and writes through Lunora's data layer
|
|
30
|
+
* instead of better-auth's built-in D1/Kysely adapter. Pass the result as
|
|
31
|
+
* `createAuth({ database: lunoraAuthAdapter(store) })`; better-auth's
|
|
32
|
+
* `createAdapterFactory` handles id generation, default values, field-name
|
|
33
|
+
* mapping and output shaping, so this only translates the cleaned CRUD calls
|
|
34
|
+
* onto the store.
|
|
35
|
+
*
|
|
36
|
+
* ```ts
|
|
37
|
+
* const auth = createAuth({
|
|
38
|
+
* secret: env.AUTH_SECRET,
|
|
39
|
+
* emailAndPassword: { enabled: true },
|
|
40
|
+
* database: lunoraAuthAdapter(lunoraStore), // lunoraStore writes via ctx.db
|
|
41
|
+
* });
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* Scope: the {@link AuthStore} interface is single-table CRUD. better-auth's
|
|
45
|
+
* relational `join` reads (an advanced opt-in) are not handled — pair the
|
|
46
|
+
* adapter with `disableJoins` or let better-auth fall back to per-table reads.
|
|
47
|
+
*/
|
|
48
|
+
declare const lunoraAuthAdapter: (store: AuthStore, runInTransaction?: TransactionRunner) => ReturnType<typeof createAdapterFactory>;
|
|
49
|
+
/**
|
|
50
|
+
* One-liner for the common case: a better-auth `database` backed by a Cloudflare
|
|
51
|
+
* D1 binding, via Lunora's SQL store — equivalent to
|
|
52
|
+
* `lunoraAuthAdapter(createSqlAuthStore(d1Executor(d1)))`.
|
|
53
|
+
*
|
|
54
|
+
* Prefer this over passing the raw `env.DB` as `database`. With raw D1,
|
|
55
|
+
* better-auth resolves its Kysely adapter through a runtime `await import(...)`
|
|
56
|
+
* inside `auth.$context`, and that dynamic import never settles under
|
|
57
|
+
* `@cloudflare/vite-plugin`'s worker runner — so it hangs *every* auth request
|
|
58
|
+
* in `pnpm dev` (a standalone `wrangler dev` or a deployed worker bundle it
|
|
59
|
+
* up-front, so they're unaffected — which makes the hang baffling to debug).
|
|
60
|
+
* This explicit adapter skips that import entirely, so dev and prod behave the
|
|
61
|
+
* same. The migration instance is the one exception — it wants raw `env.DB` so
|
|
62
|
+
* `ensureMigrated`'s Kysely migrator can create the tables (its `$context` is
|
|
63
|
+
* never resolved, so the hang doesn't apply there).
|
|
64
|
+
*/
|
|
65
|
+
declare const lunoraD1Adapter: (d1: Parameters<typeof d1Executor>[0]) => ReturnType<typeof lunoraAuthAdapter>;
|
|
66
|
+
/**
|
|
67
|
+
* Prototype: a better-auth `database` backed by a Durable Object's own SQLite —
|
|
68
|
+
* `lunoraAuthAdapter(createSqlAuthStore(doExecutor(storage)), doTransactionRunner(storage))`.
|
|
69
|
+
*
|
|
70
|
+
* Unlike `lunoraD1Adapter` this one exposes real transactions, so plugins that
|
|
71
|
+
* demand them (`@better-auth/scim`) accept it. Read the trade-offs in `do-store.ts`
|
|
72
|
+
* before reaching for it: the auth tables then live inside a single Durable Object.
|
|
73
|
+
* @experimental
|
|
74
|
+
*/
|
|
75
|
+
declare const lunoraDoAdapter: (storage: DoStorageLike) => ReturnType<typeof lunoraAuthAdapter>;
|
|
76
|
+
export { DoStorageLike as D, lunoraD1Adapter as a, lunoraDoAdapter as b, lunoraAuthAdapter as l };
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { createAdapterFactory } from 'better-auth/adapters';
|
|
2
|
+
import { d1Executor } from "../sql-store.js";
|
|
3
|
+
import { AuthStore } from "../store.js";
|
|
4
|
+
/**
|
|
5
|
+
* The slice of `DurableObjectStorage` this module uses.
|
|
6
|
+
*
|
|
7
|
+
* Structural on purpose: it keeps `@lunora/auth` free of a `@lunora/do` dependency
|
|
8
|
+
* and lets tests supply a double whose `transaction` has the same semantics.
|
|
9
|
+
*/
|
|
10
|
+
interface DoStorageLike {
|
|
11
|
+
/** Synchronous SQL over the object's SQLite. */
|
|
12
|
+
sql: {
|
|
13
|
+
exec: (query: string, ...bindings: unknown[]) => Iterable<Record<string, unknown>>;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* The platform's async transaction primitive. Everything the closure executes
|
|
17
|
+
* against `sql` joins the transaction — it is connection-scoped, so no handle is
|
|
18
|
+
* threaded through — and a throw rolls the whole thing back.
|
|
19
|
+
*/
|
|
20
|
+
transaction: <R>(closure: () => Promise<R>) => Promise<R>;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Runs `closure` atomically. Supplied by stores whose backend has real
|
|
24
|
+
* transactions — a Durable Object's storage does; D1 does not.
|
|
25
|
+
*/
|
|
26
|
+
type TransactionRunner = <R>(closure: () => Promise<R>) => Promise<R>;
|
|
27
|
+
/**
|
|
28
|
+
* A better-auth database adapter backed by an {@link AuthStore} — the bridge
|
|
29
|
+
* that routes better-auth's reads and writes through Lunora's data layer
|
|
30
|
+
* instead of better-auth's built-in D1/Kysely adapter. Pass the result as
|
|
31
|
+
* `createAuth({ database: lunoraAuthAdapter(store) })`; better-auth's
|
|
32
|
+
* `createAdapterFactory` handles id generation, default values, field-name
|
|
33
|
+
* mapping and output shaping, so this only translates the cleaned CRUD calls
|
|
34
|
+
* onto the store.
|
|
35
|
+
*
|
|
36
|
+
* ```ts
|
|
37
|
+
* const auth = createAuth({
|
|
38
|
+
* secret: env.AUTH_SECRET,
|
|
39
|
+
* emailAndPassword: { enabled: true },
|
|
40
|
+
* database: lunoraAuthAdapter(lunoraStore), // lunoraStore writes via ctx.db
|
|
41
|
+
* });
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* Scope: the {@link AuthStore} interface is single-table CRUD. better-auth's
|
|
45
|
+
* relational `join` reads (an advanced opt-in) are not handled — pair the
|
|
46
|
+
* adapter with `disableJoins` or let better-auth fall back to per-table reads.
|
|
47
|
+
*/
|
|
48
|
+
declare const lunoraAuthAdapter: (store: AuthStore, runInTransaction?: TransactionRunner) => ReturnType<typeof createAdapterFactory>;
|
|
49
|
+
/**
|
|
50
|
+
* One-liner for the common case: a better-auth `database` backed by a Cloudflare
|
|
51
|
+
* D1 binding, via Lunora's SQL store — equivalent to
|
|
52
|
+
* `lunoraAuthAdapter(createSqlAuthStore(d1Executor(d1)))`.
|
|
53
|
+
*
|
|
54
|
+
* Prefer this over passing the raw `env.DB` as `database`. With raw D1,
|
|
55
|
+
* better-auth resolves its Kysely adapter through a runtime `await import(...)`
|
|
56
|
+
* inside `auth.$context`, and that dynamic import never settles under
|
|
57
|
+
* `@cloudflare/vite-plugin`'s worker runner — so it hangs *every* auth request
|
|
58
|
+
* in `pnpm dev` (a standalone `wrangler dev` or a deployed worker bundle it
|
|
59
|
+
* up-front, so they're unaffected — which makes the hang baffling to debug).
|
|
60
|
+
* This explicit adapter skips that import entirely, so dev and prod behave the
|
|
61
|
+
* same. The migration instance is the one exception — it wants raw `env.DB` so
|
|
62
|
+
* `ensureMigrated`'s Kysely migrator can create the tables (its `$context` is
|
|
63
|
+
* never resolved, so the hang doesn't apply there).
|
|
64
|
+
*/
|
|
65
|
+
declare const lunoraD1Adapter: (d1: Parameters<typeof d1Executor>[0]) => ReturnType<typeof lunoraAuthAdapter>;
|
|
66
|
+
/**
|
|
67
|
+
* Prototype: a better-auth `database` backed by a Durable Object's own SQLite —
|
|
68
|
+
* `lunoraAuthAdapter(createSqlAuthStore(doExecutor(storage)), doTransactionRunner(storage))`.
|
|
69
|
+
*
|
|
70
|
+
* Unlike `lunoraD1Adapter` this one exposes real transactions, so plugins that
|
|
71
|
+
* demand them (`@better-auth/scim`) accept it. Read the trade-offs in `do-store.ts`
|
|
72
|
+
* before reaching for it: the auth tables then live inside a single Durable Object.
|
|
73
|
+
* @experimental
|
|
74
|
+
*/
|
|
75
|
+
declare const lunoraDoAdapter: (storage: DoStorageLike) => ReturnType<typeof lunoraAuthAdapter>;
|
|
76
|
+
export { DoStorageLike as D, lunoraD1Adapter as a, lunoraDoAdapter as b, lunoraAuthAdapter as l };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{getAuthTablesWithResolvedIndexes as f,getDatabaseFieldIndexName as N}from"@better-auth/core/db/internal";const s=n=>`"${n.replaceAll('"','""')}"`,c=(n,e)=>{if(n==="id"||e.references?.field==="id")return"text";const{type:t}=e;if(Array.isArray(t))return"text";switch(t){case"boolean":return"integer";case"date":return"date";case"number":return"bigint"in e&&e.bigint===!0?"bigint":"integer";default:return"text"}},b=n=>{const{defaultValue:e,type:t,unique:r}=n;if(!(r===!0&&n.required===!1)&&!(e==null||typeof e=="function")){if(t==="boolean")return e===!0?"1":"0";if(t==="number"&&typeof e=="number"&&Number.isFinite(e))return String(e);if(t==="string"&&typeof e=="string")return`'${e.replaceAll("'","''")}'`}},p=(n,e)=>{const t=[];for(const[r,o]of Object.entries(e)){const i=o.unique===!0;if(!i&&o.index!==!0)continue;const l=o.fieldName??r,u=N(n,l,i);t.push(`CREATE ${i?"UNIQUE ":""}INDEX IF NOT EXISTS ${s(u)} ON ${s(n)} (${s(l)})`)}return t},E=(n,e)=>{const t=[s(n),c(n,e)],r=b(e);return e.required!==!1&&r!==void 0&&t.push("NOT NULL"),r!==void 0&&t.push(`DEFAULT ${r}`),t.join(" ")},T=(n,e)=>{const t=[s(n),c(n,e)];return e.required!==!1&&t.push("NOT NULL"),t.join(" ")},A=n=>{const{indexesByTable:e,tables:t}=f(n),r=[],o=[];for(const i of Object.values(t)){if(i.disableMigrations===!0)continue;const l=[`${s("id")} text NOT NULL PRIMARY KEY`,...Object.entries(i.fields).map(([u,a])=>T(a.fieldName??u,a))];r.push(`CREATE TABLE IF NOT EXISTS ${s(i.modelName)} (${l.join(", ")})`),o.push(...p(i.modelName,i.fields));for(const u of e.get(i.modelName)??[]){const a=u.unique===!0?"UNIQUE ":"",d=u.columns.map(m=>s(m)).join(", ");o.push(`CREATE ${a}INDEX IF NOT EXISTS ${s(u.name)} ON ${s(i.modelName)} (${d})`)}}return[...r,...o]},$=(n,e)=>{const{tables:t}=f(n),r=[];for(const o of Object.values(t)){if(o.disableMigrations===!0)continue;const i=new Set(e(o.modelName));if(i.size!==0)for(const[l,u]of Object.entries(o.fields)){const a=u.fieldName??l;i.has(a)||r.push(`ALTER TABLE ${s(o.modelName)} ADD COLUMN ${E(a,u)}`)}}return r};export{$ as authDoColumnAdditions,A as authDoSchemaStatements};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{INTERNAL_SECRET_HEADER as u,RESOLVE_SESSION_PATH as f,READ_AUDIT_PATH as w}from"./AUTH_DO_AUDIT_PATH-Cy_f9vvT.mjs";import{DEFAULT_AUTH_BASE_PATH as A}from"./DEFAULT_AUTH_BASE_PATH-kIwlEt8i.mjs";const m=c=>{const{basePath:d=A,internalSecret:a,namespace:i,objectName:h="auth"}=c,o=()=>{if(i)return i.get(i.idFromName(h))},l=async(t,e,s)=>{if(!a)return;const n=o();if(!n)return;const r=await n.fetch(new Request(new URL(t,e),{body:JSON.stringify(s),headers:{"content-type":"application/json",[u]:a},method:"POST"}));return r.ok?r:void 0};return{auditReader:{read:async t=>{const e=await l(w,"https://auth-do.invalid",t);return e?(await e.json())?.entries??[]:[]}},authHandler:async t=>{if(new URL(t.url).pathname.startsWith(d))return o()?.fetch(t)},resolveIdentity:async t=>{if(!a)return null;const e=o();if(!e)return null;const s=new Headers(t.headers);s.set(u,a);const n=await e.fetch(new Request(new URL(f,t.url),{headers:s}));if(!n.ok)return null;const r=await n.json();return r?.userId?{userId:r.userId}:null}}};export{m as createDoAuthWiring};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunora/auth",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.51",
|
|
4
4
|
"description": "Auth for Lunora — a thin better-auth wrapper: email/password, OAuth, plugins, D1-backed",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"auth",
|
|
@@ -104,7 +104,7 @@
|
|
|
104
104
|
"@better-auth/passkey": "1.7.0-rc.2",
|
|
105
105
|
"@better-auth/scim": "1.7.0-rc.2",
|
|
106
106
|
"@lunora/errors": "1.0.0-alpha.8",
|
|
107
|
-
"@lunora/server": "1.0.0-alpha.
|
|
107
|
+
"@lunora/server": "1.0.0-alpha.45",
|
|
108
108
|
"@lunora/values": "1.0.0-alpha.11",
|
|
109
109
|
"@visulima/disposable-email-domains": "1.0.1",
|
|
110
110
|
"@visulima/email-verifier": "1.0.1",
|