@braedonsaunders/appkit-auth 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @braedonsaunders/appkit-auth
2
2
 
3
- Production authentication extracted from the complete reference runtime and generalized around explicit application inputs.
3
+ Production authentication generalized around explicit application inputs.
4
4
 
5
5
  ## What ships
6
6
 
package/invites.d.ts CHANGED
@@ -65,7 +65,7 @@ export type InviteService = {
65
65
  inspectInviteForUser: (rawGrant: string, sessionUserId: string) => Promise<InviteInspection>;
66
66
  };
67
67
  /**
68
- * The complete invitation grant flow extracted from the production runtime.
68
+ * The complete invitation grant flow.
69
69
  * Better Auth's one-time magic-link token proves mailbox possession; this
70
70
  * independently signed grant binds that verified callback to exactly one
71
71
  * pending membership generation.
package/invites.js CHANGED
@@ -3,7 +3,7 @@ const INVITE_GRANT_VERSION = 1;
3
3
  const DEFAULT_INVITE_TTL_SECONDS = 15 * 60;
4
4
  const MAX_GRANT_LENGTH = 4096;
5
5
  /**
6
- * The complete invitation grant flow extracted from the production runtime.
6
+ * The complete invitation grant flow.
7
7
  * Better Auth's one-time magic-link token proves mailbox possession; this
8
8
  * independently signed grant binds that verified callback to exactly one
9
9
  * pending membership generation.
package/invites.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"invites.js","sourceRoot":"","sources":["../src/invites.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHmac, hkdfSync, timingSafeEqual } from 'node:crypto'\n\nconst INVITE_GRANT_VERSION = 1\nconst DEFAULT_INVITE_TTL_SECONDS = 15 * 60\nconst MAX_GRANT_LENGTH = 4096\n\nexport type InviteGrantInput = {\n membershipId: string\n tenantId: string\n userId: string\n invitedAt: Date\n}\n\nexport type InviteGrantPayload = {\n v: typeof INVITE_GRANT_VERSION\n membershipId: string\n tenantId: string\n userId: string\n invitedAt: number\n issuedAt: number\n expiresAt: number\n}\n\nexport type InviteGrantVerification =\n | { ok: true; payload: InviteGrantPayload }\n | { ok: false; reason: 'invalid' | 'expired' }\n\nexport type InviteAccessState =\n | 'active'\n | 'pending'\n | 'suspended'\n | 'tenant_unavailable'\n | 'unverified'\n | 'invalid'\n | 'expired'\n\nexport type InviteRecord = {\n membershipId: string\n tenantId: string\n tenantName: string\n userId: string\n invitedAt: Date | null\n status: 'active' | 'invited' | 'suspended'\n emailVerified: boolean\n tenantStatus: 'active' | 'suspended' | 'archived'\n}\n\nexport type InviteInspection = {\n state: InviteAccessState\n tenantId: string | null\n tenantName: string | null\n}\n\nexport type InviteStore = {\n /** Read-only inspection. This must never activate a membership. */\n inspect: (membershipId: string) => Promise<InviteRecord | null>\n /**\n * Atomically lock, re-evaluate, activate, and audit a pending membership.\n * The store receives the already signature-verified grant and must return the\n * final state so concurrent or superseded links cannot double-accept.\n */\n accept: (payload: InviteGrantPayload, sessionUserId: string) => Promise<InviteAccessState>\n}\n\nexport type InviteServiceOptions = {\n secret: string\n store: InviteStore\n ttlSeconds?: number\n /** HKDF domain separation. Change this only when intentionally rotating formats. */\n signingContext?: string\n callbackPath?: string\n}\n\nexport type InviteService = {\n ttlSeconds: number\n createInviteGrant: (input: InviteGrantInput, now?: number) => string\n verifyInviteGrant: (raw: string, now?: number) => InviteGrantVerification\n inviteCallbackPath: (grant: string) => string\n inviteGrantFromCallbackURL: (callbackURL: unknown, baseURL: string) => string | null\n acceptInviteAfterMagicLink: (rawGrant: string, sessionUserId: string) => Promise<InviteAccessState>\n inspectInviteForUser: (rawGrant: string, sessionUserId: string) => Promise<InviteInspection>\n}\n\n/**\n * The complete invitation grant flow extracted from the production runtime.\n * Better Auth's one-time magic-link token proves mailbox possession; this\n * independently signed grant binds that verified callback to exactly one\n * pending membership generation.\n */\nexport function createInviteService(options: InviteServiceOptions): InviteService {\n if (options.secret.length < 32) throw new Error('Invitation signing secret must contain at least 32 characters.')\n const ttlSeconds = options.ttlSeconds ?? DEFAULT_INVITE_TTL_SECONDS\n if (!Number.isSafeInteger(ttlSeconds) || ttlSeconds < 60) {\n throw new Error('Invitation TTL must be a whole number of at least 60 seconds.')\n }\n const callbackPath = normalizeCallbackPath(options.callbackPath ?? '/invite/accept')\n const signingKey = Buffer.from(\n hkdfSync(\n 'sha256',\n Buffer.from(options.secret),\n Buffer.alloc(0),\n Buffer.from(options.signingContext ?? 'appkit.invite.v1'),\n 32,\n ),\n )\n const sign = (payload: string) => createHmac('sha256', signingKey).update(payload).digest()\n\n function createInviteGrant(input: InviteGrantInput, now = Date.now()): string {\n if (!Number.isSafeInteger(now) || !Number.isSafeInteger(input.invitedAt.getTime())) {\n throw new Error('Invalid invitation issuance time.')\n }\n for (const [name, value] of Object.entries({\n membershipId: input.membershipId,\n tenantId: input.tenantId,\n userId: input.userId,\n })) {\n if (!isBoundedString(value, name === 'userId' ? 200 : 100)) {\n throw new Error(`Invalid ${name}.`)\n }\n }\n const payload: InviteGrantPayload = {\n v: INVITE_GRANT_VERSION,\n membershipId: input.membershipId,\n tenantId: input.tenantId,\n userId: input.userId,\n invitedAt: input.invitedAt.getTime(),\n issuedAt: now,\n expiresAt: now + ttlSeconds * 1000,\n }\n const encoded = Buffer.from(JSON.stringify(payload)).toString('base64url')\n return `${encoded}.${sign(encoded).toString('base64url')}`\n }\n\n function verifyInviteGrant(raw: string, now = Date.now()): InviteGrantVerification {\n if (!raw || raw.length > MAX_GRANT_LENGTH || !Number.isSafeInteger(now)) {\n return { ok: false, reason: 'invalid' }\n }\n const separator = raw.lastIndexOf('.')\n if (separator <= 0 || separator === raw.length - 1) return { ok: false, reason: 'invalid' }\n const encoded = raw.slice(0, separator)\n const signature = raw.slice(separator + 1)\n let expected: Buffer\n let provided: Buffer\n try {\n expected = sign(encoded)\n provided = Buffer.from(signature, 'base64url')\n } catch {\n return { ok: false, reason: 'invalid' }\n }\n if (provided.length !== expected.length || !timingSafeEqual(provided, expected)) {\n return { ok: false, reason: 'invalid' }\n }\n let payload: InviteGrantPayload | null = null\n try {\n payload = parsePayload(JSON.parse(Buffer.from(encoded, 'base64url').toString('utf8')), ttlSeconds)\n } catch {\n return { ok: false, reason: 'invalid' }\n }\n if (!payload) return { ok: false, reason: 'invalid' }\n if (now < payload.issuedAt - 60_000 || now > payload.expiresAt) {\n return { ok: false, reason: 'expired' }\n }\n return { ok: true, payload }\n }\n\n function inviteCallbackPath(grant: string): string {\n return `${callbackPath}?${new URLSearchParams({ grant }).toString()}`\n }\n\n function inviteGrantFromCallbackURL(callbackURL: unknown, baseURL: string): string | null {\n if (typeof callbackURL !== 'string' || !callbackURL) return null\n try {\n const base = new URL(baseURL)\n const callback = new URL(callbackURL, base)\n if (callback.origin !== base.origin || callback.pathname !== callbackPath) return null\n return callback.searchParams.get('grant')\n } catch {\n return null\n }\n }\n\n async function acceptInviteAfterMagicLink(\n rawGrant: string,\n sessionUserId: string,\n ): Promise<InviteAccessState> {\n const verified = verifyInviteGrant(rawGrant)\n if (!verified.ok) return verified.reason\n return options.store.accept(verified.payload, sessionUserId)\n }\n\n async function inspectInviteForUser(\n rawGrant: string,\n sessionUserId: string,\n ): Promise<InviteInspection> {\n const verified = verifyInviteGrant(rawGrant)\n if (!verified.ok) return { state: verified.reason, tenantId: null, tenantName: null }\n const record = await options.store.inspect(verified.payload.membershipId)\n return {\n state: evaluateInviteAccess(verified.payload, sessionUserId, record),\n tenantId: record?.tenantId ?? null,\n tenantName: record?.tenantName ?? null,\n }\n }\n\n return {\n ttlSeconds,\n createInviteGrant,\n verifyInviteGrant,\n inviteCallbackPath,\n inviteGrantFromCallbackURL,\n acceptInviteAfterMagicLink,\n inspectInviteForUser,\n }\n}\n\n/** Pure state machine shared by memory and durable stores. */\nexport function evaluateInviteAccess(\n payload: InviteGrantPayload,\n sessionUserId: string,\n record: InviteRecord | null,\n): InviteAccessState {\n if (!record || sessionUserId !== payload.userId) return 'invalid'\n if (\n record.membershipId !== payload.membershipId ||\n record.tenantId !== payload.tenantId ||\n record.userId !== payload.userId ||\n record.invitedAt?.getTime() !== payload.invitedAt\n ) {\n return 'invalid'\n }\n if (record.tenantStatus !== 'active') return 'tenant_unavailable'\n if (record.status === 'suspended') return 'suspended'\n if (record.status === 'active') return 'active'\n if (!record.emailVerified) return 'unverified'\n return 'pending'\n}\n\n/** Every resend advances the generation, even within the same clock tick. */\nexport function nextInviteGenerationDate(previous: Date | null, now = new Date()): Date {\n const nowMs = now.getTime()\n const previousMs = previous?.getTime() ?? -1\n if (!Number.isSafeInteger(nowMs) || !Number.isSafeInteger(previousMs)) {\n throw new Error('Invalid invitation generation time.')\n }\n return new Date(Math.max(nowMs, previousMs + 1))\n}\n\nfunction parsePayload(value: unknown, ttlSeconds: number): InviteGrantPayload | null {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return null\n const payload = value as Partial<InviteGrantPayload>\n if (\n payload.v !== INVITE_GRANT_VERSION ||\n !isBoundedString(payload.membershipId, 100) ||\n !isBoundedString(payload.tenantId, 100) ||\n !isBoundedString(payload.userId, 200) ||\n !Number.isSafeInteger(payload.invitedAt) ||\n !Number.isSafeInteger(payload.issuedAt) ||\n !Number.isSafeInteger(payload.expiresAt) ||\n payload.expiresAt! <= payload.issuedAt! ||\n payload.expiresAt! - payload.issuedAt! > ttlSeconds * 1000\n ) {\n return null\n }\n return payload as InviteGrantPayload\n}\n\nfunction isBoundedString(value: unknown, max: number): value is string {\n return typeof value === 'string' && value.length > 0 && value.length <= max\n}\n\nfunction normalizeCallbackPath(value: string): string {\n try {\n const url = new URL(value, 'https://app.invalid')\n if (url.origin !== 'https://app.invalid' || !url.pathname.startsWith('/')) throw new Error()\n return url.pathname\n } catch {\n throw new Error('Invitation callback path must be an absolute same-origin path.')\n }\n}\n"]}
1
+ {"version":3,"file":"invites.js","sourceRoot":"","sources":["../src/invites.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHmac, hkdfSync, timingSafeEqual } from 'node:crypto'\n\nconst INVITE_GRANT_VERSION = 1\nconst DEFAULT_INVITE_TTL_SECONDS = 15 * 60\nconst MAX_GRANT_LENGTH = 4096\n\nexport type InviteGrantInput = {\n membershipId: string\n tenantId: string\n userId: string\n invitedAt: Date\n}\n\nexport type InviteGrantPayload = {\n v: typeof INVITE_GRANT_VERSION\n membershipId: string\n tenantId: string\n userId: string\n invitedAt: number\n issuedAt: number\n expiresAt: number\n}\n\nexport type InviteGrantVerification =\n | { ok: true; payload: InviteGrantPayload }\n | { ok: false; reason: 'invalid' | 'expired' }\n\nexport type InviteAccessState =\n | 'active'\n | 'pending'\n | 'suspended'\n | 'tenant_unavailable'\n | 'unverified'\n | 'invalid'\n | 'expired'\n\nexport type InviteRecord = {\n membershipId: string\n tenantId: string\n tenantName: string\n userId: string\n invitedAt: Date | null\n status: 'active' | 'invited' | 'suspended'\n emailVerified: boolean\n tenantStatus: 'active' | 'suspended' | 'archived'\n}\n\nexport type InviteInspection = {\n state: InviteAccessState\n tenantId: string | null\n tenantName: string | null\n}\n\nexport type InviteStore = {\n /** Read-only inspection. This must never activate a membership. */\n inspect: (membershipId: string) => Promise<InviteRecord | null>\n /**\n * Atomically lock, re-evaluate, activate, and audit a pending membership.\n * The store receives the already signature-verified grant and must return the\n * final state so concurrent or superseded links cannot double-accept.\n */\n accept: (payload: InviteGrantPayload, sessionUserId: string) => Promise<InviteAccessState>\n}\n\nexport type InviteServiceOptions = {\n secret: string\n store: InviteStore\n ttlSeconds?: number\n /** HKDF domain separation. Change this only when intentionally rotating formats. */\n signingContext?: string\n callbackPath?: string\n}\n\nexport type InviteService = {\n ttlSeconds: number\n createInviteGrant: (input: InviteGrantInput, now?: number) => string\n verifyInviteGrant: (raw: string, now?: number) => InviteGrantVerification\n inviteCallbackPath: (grant: string) => string\n inviteGrantFromCallbackURL: (callbackURL: unknown, baseURL: string) => string | null\n acceptInviteAfterMagicLink: (rawGrant: string, sessionUserId: string) => Promise<InviteAccessState>\n inspectInviteForUser: (rawGrant: string, sessionUserId: string) => Promise<InviteInspection>\n}\n\n/**\n * The complete invitation grant flow.\n * Better Auth's one-time magic-link token proves mailbox possession; this\n * independently signed grant binds that verified callback to exactly one\n * pending membership generation.\n */\nexport function createInviteService(options: InviteServiceOptions): InviteService {\n if (options.secret.length < 32) throw new Error('Invitation signing secret must contain at least 32 characters.')\n const ttlSeconds = options.ttlSeconds ?? DEFAULT_INVITE_TTL_SECONDS\n if (!Number.isSafeInteger(ttlSeconds) || ttlSeconds < 60) {\n throw new Error('Invitation TTL must be a whole number of at least 60 seconds.')\n }\n const callbackPath = normalizeCallbackPath(options.callbackPath ?? '/invite/accept')\n const signingKey = Buffer.from(\n hkdfSync(\n 'sha256',\n Buffer.from(options.secret),\n Buffer.alloc(0),\n Buffer.from(options.signingContext ?? 'appkit.invite.v1'),\n 32,\n ),\n )\n const sign = (payload: string) => createHmac('sha256', signingKey).update(payload).digest()\n\n function createInviteGrant(input: InviteGrantInput, now = Date.now()): string {\n if (!Number.isSafeInteger(now) || !Number.isSafeInteger(input.invitedAt.getTime())) {\n throw new Error('Invalid invitation issuance time.')\n }\n for (const [name, value] of Object.entries({\n membershipId: input.membershipId,\n tenantId: input.tenantId,\n userId: input.userId,\n })) {\n if (!isBoundedString(value, name === 'userId' ? 200 : 100)) {\n throw new Error(`Invalid ${name}.`)\n }\n }\n const payload: InviteGrantPayload = {\n v: INVITE_GRANT_VERSION,\n membershipId: input.membershipId,\n tenantId: input.tenantId,\n userId: input.userId,\n invitedAt: input.invitedAt.getTime(),\n issuedAt: now,\n expiresAt: now + ttlSeconds * 1000,\n }\n const encoded = Buffer.from(JSON.stringify(payload)).toString('base64url')\n return `${encoded}.${sign(encoded).toString('base64url')}`\n }\n\n function verifyInviteGrant(raw: string, now = Date.now()): InviteGrantVerification {\n if (!raw || raw.length > MAX_GRANT_LENGTH || !Number.isSafeInteger(now)) {\n return { ok: false, reason: 'invalid' }\n }\n const separator = raw.lastIndexOf('.')\n if (separator <= 0 || separator === raw.length - 1) return { ok: false, reason: 'invalid' }\n const encoded = raw.slice(0, separator)\n const signature = raw.slice(separator + 1)\n let expected: Buffer\n let provided: Buffer\n try {\n expected = sign(encoded)\n provided = Buffer.from(signature, 'base64url')\n } catch {\n return { ok: false, reason: 'invalid' }\n }\n if (provided.length !== expected.length || !timingSafeEqual(provided, expected)) {\n return { ok: false, reason: 'invalid' }\n }\n let payload: InviteGrantPayload | null = null\n try {\n payload = parsePayload(JSON.parse(Buffer.from(encoded, 'base64url').toString('utf8')), ttlSeconds)\n } catch {\n return { ok: false, reason: 'invalid' }\n }\n if (!payload) return { ok: false, reason: 'invalid' }\n if (now < payload.issuedAt - 60_000 || now > payload.expiresAt) {\n return { ok: false, reason: 'expired' }\n }\n return { ok: true, payload }\n }\n\n function inviteCallbackPath(grant: string): string {\n return `${callbackPath}?${new URLSearchParams({ grant }).toString()}`\n }\n\n function inviteGrantFromCallbackURL(callbackURL: unknown, baseURL: string): string | null {\n if (typeof callbackURL !== 'string' || !callbackURL) return null\n try {\n const base = new URL(baseURL)\n const callback = new URL(callbackURL, base)\n if (callback.origin !== base.origin || callback.pathname !== callbackPath) return null\n return callback.searchParams.get('grant')\n } catch {\n return null\n }\n }\n\n async function acceptInviteAfterMagicLink(\n rawGrant: string,\n sessionUserId: string,\n ): Promise<InviteAccessState> {\n const verified = verifyInviteGrant(rawGrant)\n if (!verified.ok) return verified.reason\n return options.store.accept(verified.payload, sessionUserId)\n }\n\n async function inspectInviteForUser(\n rawGrant: string,\n sessionUserId: string,\n ): Promise<InviteInspection> {\n const verified = verifyInviteGrant(rawGrant)\n if (!verified.ok) return { state: verified.reason, tenantId: null, tenantName: null }\n const record = await options.store.inspect(verified.payload.membershipId)\n return {\n state: evaluateInviteAccess(verified.payload, sessionUserId, record),\n tenantId: record?.tenantId ?? null,\n tenantName: record?.tenantName ?? null,\n }\n }\n\n return {\n ttlSeconds,\n createInviteGrant,\n verifyInviteGrant,\n inviteCallbackPath,\n inviteGrantFromCallbackURL,\n acceptInviteAfterMagicLink,\n inspectInviteForUser,\n }\n}\n\n/** Pure state machine shared by memory and durable stores. */\nexport function evaluateInviteAccess(\n payload: InviteGrantPayload,\n sessionUserId: string,\n record: InviteRecord | null,\n): InviteAccessState {\n if (!record || sessionUserId !== payload.userId) return 'invalid'\n if (\n record.membershipId !== payload.membershipId ||\n record.tenantId !== payload.tenantId ||\n record.userId !== payload.userId ||\n record.invitedAt?.getTime() !== payload.invitedAt\n ) {\n return 'invalid'\n }\n if (record.tenantStatus !== 'active') return 'tenant_unavailable'\n if (record.status === 'suspended') return 'suspended'\n if (record.status === 'active') return 'active'\n if (!record.emailVerified) return 'unverified'\n return 'pending'\n}\n\n/** Every resend advances the generation, even within the same clock tick. */\nexport function nextInviteGenerationDate(previous: Date | null, now = new Date()): Date {\n const nowMs = now.getTime()\n const previousMs = previous?.getTime() ?? -1\n if (!Number.isSafeInteger(nowMs) || !Number.isSafeInteger(previousMs)) {\n throw new Error('Invalid invitation generation time.')\n }\n return new Date(Math.max(nowMs, previousMs + 1))\n}\n\nfunction parsePayload(value: unknown, ttlSeconds: number): InviteGrantPayload | null {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return null\n const payload = value as Partial<InviteGrantPayload>\n if (\n payload.v !== INVITE_GRANT_VERSION ||\n !isBoundedString(payload.membershipId, 100) ||\n !isBoundedString(payload.tenantId, 100) ||\n !isBoundedString(payload.userId, 200) ||\n !Number.isSafeInteger(payload.invitedAt) ||\n !Number.isSafeInteger(payload.issuedAt) ||\n !Number.isSafeInteger(payload.expiresAt) ||\n payload.expiresAt! <= payload.issuedAt! ||\n payload.expiresAt! - payload.issuedAt! > ttlSeconds * 1000\n ) {\n return null\n }\n return payload as InviteGrantPayload\n}\n\nfunction isBoundedString(value: unknown, max: number): value is string {\n return typeof value === 'string' && value.length > 0 && value.length <= max\n}\n\nfunction normalizeCallbackPath(value: string): string {\n try {\n const url = new URL(value, 'https://app.invalid')\n if (url.origin !== 'https://app.invalid' || !url.pathname.startsWith('/')) throw new Error()\n return url.pathname\n } catch {\n throw new Error('Invitation callback path must be an absolute same-origin path.')\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@braedonsaunders/appkit-auth",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Production authentication: durable sessions, passwords, magic links, invitation grants, reset flows, React UI, and Postgres adapters.",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "type": "module",
@@ -51,7 +51,7 @@
51
51
  "types": "./index.d.ts",
52
52
  "dependencies": {
53
53
  "@braedonsaunders/appkit-db": "^0.2.0",
54
- "@braedonsaunders/appkit-ui": "^0.2.0",
54
+ "@braedonsaunders/appkit-ui": "^0.3.0",
55
55
  "better-auth": "^1.6.23",
56
56
  "drizzle-orm": "^0.45.2",
57
57
  "pg": "^8.22.0",
package/server.d.ts CHANGED
@@ -31,8 +31,8 @@ export type AppkitAuthOptions = {
31
31
  extend?: Omit<BetterAuthOptions, 'database' | 'baseURL' | 'secret' | 'appName' | 'trustedOrigins' | 'user' | 'session' | 'account' | 'verification' | 'emailAndPassword' | 'plugins' | 'hooks' | 'advanced'>;
32
32
  };
33
33
  /**
34
- * Complete persisted authentication runtime extracted from the production
35
- * reference: password sign-in/reset, hashed one-time magic links, invitation
34
+ * Complete persisted authentication runtime: password sign-in/reset, hashed
35
+ * one-time magic links, invitation
36
36
  * activation, durable sessions/accounts/verifications, client-compatible APIs,
37
37
  * and optional social-provider extension. Product delivery and domain effects
38
38
  * are injected.
package/server.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
package/server.js CHANGED
@@ -2,8 +2,8 @@ import { betterAuth } from 'better-auth';
2
2
  import { magicLink } from 'better-auth/plugins';
3
3
  import { renderMagicLinkEmail, renderPasswordResetEmail } from './mail.js';
4
4
  /**
5
- * Complete persisted authentication runtime extracted from the production
6
- * reference: password sign-in/reset, hashed one-time magic links, invitation
5
+ * Complete persisted authentication runtime: password sign-in/reset, hashed
6
+ * one-time magic links, invitation
7
7
  * activation, durable sessions/accounts/verifications, client-compatible APIs,
8
8
  * and optional social-provider extension. Product delivery and domain effects
9
9
  * are injected.
package/server.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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","sourcesContent":["import { betterAuth } from 'better-auth'\nimport { magicLink } from 'better-auth/plugins'\nimport type { AuthEmailRenderer, AuthEmailSender } from './mail'\nimport { renderMagicLinkEmail, renderPasswordResetEmail } from './mail'\nimport type { InviteService } from './invites'\n\ntype BetterAuthOptions = Parameters<typeof betterAuth>[0]\n\nexport type AppkitAuthOptions = {\n database: NonNullable<BetterAuthOptions['database']>\n baseURL: string\n secret: string\n appName: string\n sendEmail: AuthEmailSender\n renderEmail?: AuthEmailRenderer\n inviteService?: InviteService\n onInviteAcceptanceError?: (error: unknown) => void\n trustedOrigins?: BetterAuthOptions['trustedOrigins']\n session?: {\n expiresIn?: number\n updateAge?: number\n cookieCacheMaxAge?: number\n }\n password?: {\n disableSignUp?: boolean\n requireEmailVerification?: boolean\n minLength?: number\n resetTokenExpiresIn?: number\n }\n magicLink?: {\n expiresIn?: number\n disableSignUp?: boolean\n }\n /** Additional upstream options such as social providers and database hooks. */\n extend?: Omit<\n BetterAuthOptions,\n | 'database'\n | 'baseURL'\n | 'secret'\n | 'appName'\n | 'trustedOrigins'\n | 'user'\n | 'session'\n | 'account'\n | 'verification'\n | 'emailAndPassword'\n | 'plugins'\n | 'hooks'\n | 'advanced'\n >\n}\n\n/**\n * Complete persisted authentication runtime extracted from the production\n * reference: password sign-in/reset, hashed one-time magic links, invitation\n * activation, durable sessions/accounts/verifications, client-compatible APIs,\n * and optional social-provider extension. Product delivery and domain effects\n * are injected.\n */\nexport function createAppkitAuth(options: AppkitAuthOptions) {\n const baseURL = normalizeBaseURL(options.baseURL)\n const appName = options.appName.trim()\n if (!appName) throw new Error('Authentication appName is required.')\n if (options.secret.length < 32) {\n throw new Error('Authentication secret must contain at least 32 characters.')\n }\n const inviteTTL = options.inviteService?.ttlSeconds\n const magicTTL = options.magicLink?.expiresIn ?? inviteTTL ?? 15 * 60\n if (inviteTTL && magicTTL !== inviteTTL) {\n throw new Error('Magic-link and invitation grant expiration must match.')\n }\n\n return betterAuth({\n ...options.extend,\n database: options.database,\n baseURL,\n secret: options.secret,\n appName,\n user: {\n modelName: 'users',\n fields: {\n emailVerified: 'email_verified',\n createdAt: 'created_at',\n updatedAt: 'updated_at',\n },\n additionalFields: {\n timezone: {\n type: 'string',\n fieldName: 'timezone',\n required: true,\n defaultValue: 'UTC',\n },\n isActive: {\n type: 'boolean',\n fieldName: 'is_active',\n required: true,\n defaultValue: true,\n input: false,\n },\n isSuperAdmin: {\n type: 'boolean',\n fieldName: 'is_super_admin',\n required: true,\n defaultValue: false,\n input: false,\n },\n },\n },\n session: {\n modelName: 'sessions',\n fields: {\n userId: 'user_id',\n expiresAt: 'expires_at',\n ipAddress: 'ip_address',\n userAgent: 'user_agent',\n createdAt: 'created_at',\n updatedAt: 'updated_at',\n },\n additionalFields: {\n activeTenantId: { type: 'string', fieldName: 'active_tenant_id', required: false, input: false },\n impersonatingUserId: { type: 'string', fieldName: 'impersonating_user_id', required: false, input: false },\n impersonationTenantId: { type: 'string', fieldName: 'impersonation_tenant_id', required: false, input: false },\n impersonationStartedAt: { type: 'date', fieldName: 'impersonation_started_at', required: false, input: false },\n impersonationExpiresAt: { type: 'date', fieldName: 'impersonation_expires_at', required: false, input: false },\n impersonationReason: { type: 'string', fieldName: 'impersonation_reason', required: false, input: false },\n },\n expiresIn: options.session?.expiresIn ?? 60 * 60 * 24 * 30,\n updateAge: options.session?.updateAge ?? 60 * 60 * 24,\n cookieCache: {\n enabled: true,\n maxAge: options.session?.cookieCacheMaxAge ?? 60 * 5,\n },\n },\n account: {\n modelName: 'accounts',\n fields: {\n userId: 'user_id',\n accountId: 'account_id',\n providerId: 'provider_id',\n accessToken: 'access_token',\n refreshToken: 'refresh_token',\n idToken: 'id_token',\n accessTokenExpiresAt: 'access_token_expires_at',\n refreshTokenExpiresAt: 'refresh_token_expires_at',\n createdAt: 'created_at',\n updatedAt: 'updated_at',\n },\n encryptOAuthTokens: true,\n },\n verification: {\n modelName: 'verifications',\n fields: {\n expiresAt: 'expires_at',\n createdAt: 'created_at',\n updatedAt: 'updated_at',\n },\n },\n advanced: {\n database: { generateId: 'uuid' },\n },\n emailAndPassword: {\n enabled: true,\n disableSignUp: options.password?.disableSignUp ?? true,\n requireEmailVerification: options.password?.requireEmailVerification ?? false,\n minPasswordLength: options.password?.minLength ?? 8,\n autoSignIn: true,\n resetPasswordTokenExpiresIn: options.password?.resetTokenExpiresIn ?? 60 * 60,\n revokeSessionsOnPasswordReset: true,\n sendResetPassword: async ({ user, url }) => {\n const rendered = (options.renderEmail?.passwordReset ?? renderPasswordResetEmail)({\n appName,\n email: user.email,\n url,\n })\n await options.sendEmail({ kind: 'password-reset', to: user.email, ...rendered })\n },\n },\n plugins: [\n magicLink({\n disableSignUp: options.magicLink?.disableSignUp ?? true,\n expiresIn: magicTTL,\n storeToken: 'hashed',\n sendMagicLink: async ({ email, url, metadata }) => {\n const invitation = metadata?.flow === 'invite'\n const tenantName =\n typeof metadata?.tenantName === 'string' && metadata.tenantName.trim()\n ? metadata.tenantName.trim()\n : null\n const rendered = (options.renderEmail?.magicLink ?? renderMagicLinkEmail)({\n appName,\n email,\n url,\n invitation,\n tenantName,\n })\n await options.sendEmail({\n kind: invitation ? 'invitation' : 'magic-link',\n to: email,\n ...rendered,\n })\n },\n }),\n ],\n hooks: {\n after: async (rawContext) => {\n const context = rawContext as unknown as {\n path?: string\n query?: { callbackURL?: unknown }\n context: { newSession?: { user: { id: string } } | null }\n }\n if (context.path !== '/magic-link/verify' || !options.inviteService) return {}\n const sessionUserId = context.context.newSession?.user.id\n if (!sessionUserId) return {}\n const grant = options.inviteService.inviteGrantFromCallbackURL(\n context.query?.callbackURL,\n baseURL,\n )\n if (!grant) return {}\n // Preserve the valid session/callback if a downstream domain hook fails;\n // the membership remains unchanged and the callback can show recovery.\n await options.inviteService.acceptInviteAfterMagicLink(grant, sessionUserId).catch((error) => {\n if (options.onInviteAcceptanceError) options.onInviteAcceptanceError(error)\n else console.error('[appkit/auth] invitation acceptance failed', error)\n })\n return {}\n },\n },\n trustedOrigins: options.trustedOrigins ?? [baseURL],\n })\n}\n\nexport type AppkitAuth = ReturnType<typeof createAppkitAuth>\n\n/** Lazy singleton pattern used by server runtimes that must import safely at build time. */\nexport function createLazyAuth(factory: () => AppkitAuth): () => AppkitAuth {\n let instance: AppkitAuth | undefined\n return () => (instance ??= factory())\n}\n\nfunction normalizeBaseURL(value: string): string {\n const url = new URL(value)\n if (url.username || url.password || url.search || url.hash) {\n throw new Error('Authentication baseURL must be an origin without credentials, query, or hash.')\n }\n return url.origin\n}\n"]}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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","sourcesContent":["import { betterAuth } from 'better-auth'\nimport { magicLink } from 'better-auth/plugins'\nimport type { AuthEmailRenderer, AuthEmailSender } from './mail'\nimport { renderMagicLinkEmail, renderPasswordResetEmail } from './mail'\nimport type { InviteService } from './invites'\n\ntype BetterAuthOptions = Parameters<typeof betterAuth>[0]\n\nexport type AppkitAuthOptions = {\n database: NonNullable<BetterAuthOptions['database']>\n baseURL: string\n secret: string\n appName: string\n sendEmail: AuthEmailSender\n renderEmail?: AuthEmailRenderer\n inviteService?: InviteService\n onInviteAcceptanceError?: (error: unknown) => void\n trustedOrigins?: BetterAuthOptions['trustedOrigins']\n session?: {\n expiresIn?: number\n updateAge?: number\n cookieCacheMaxAge?: number\n }\n password?: {\n disableSignUp?: boolean\n requireEmailVerification?: boolean\n minLength?: number\n resetTokenExpiresIn?: number\n }\n magicLink?: {\n expiresIn?: number\n disableSignUp?: boolean\n }\n /** Additional upstream options such as social providers and database hooks. */\n extend?: Omit<\n BetterAuthOptions,\n | 'database'\n | 'baseURL'\n | 'secret'\n | 'appName'\n | 'trustedOrigins'\n | 'user'\n | 'session'\n | 'account'\n | 'verification'\n | 'emailAndPassword'\n | 'plugins'\n | 'hooks'\n | 'advanced'\n >\n}\n\n/**\n * Complete persisted authentication runtime: password sign-in/reset, hashed\n * one-time magic links, invitation\n * activation, durable sessions/accounts/verifications, client-compatible APIs,\n * and optional social-provider extension. Product delivery and domain effects\n * are injected.\n */\nexport function createAppkitAuth(options: AppkitAuthOptions) {\n const baseURL = normalizeBaseURL(options.baseURL)\n const appName = options.appName.trim()\n if (!appName) throw new Error('Authentication appName is required.')\n if (options.secret.length < 32) {\n throw new Error('Authentication secret must contain at least 32 characters.')\n }\n const inviteTTL = options.inviteService?.ttlSeconds\n const magicTTL = options.magicLink?.expiresIn ?? inviteTTL ?? 15 * 60\n if (inviteTTL && magicTTL !== inviteTTL) {\n throw new Error('Magic-link and invitation grant expiration must match.')\n }\n\n return betterAuth({\n ...options.extend,\n database: options.database,\n baseURL,\n secret: options.secret,\n appName,\n user: {\n modelName: 'users',\n fields: {\n emailVerified: 'email_verified',\n createdAt: 'created_at',\n updatedAt: 'updated_at',\n },\n additionalFields: {\n timezone: {\n type: 'string',\n fieldName: 'timezone',\n required: true,\n defaultValue: 'UTC',\n },\n isActive: {\n type: 'boolean',\n fieldName: 'is_active',\n required: true,\n defaultValue: true,\n input: false,\n },\n isSuperAdmin: {\n type: 'boolean',\n fieldName: 'is_super_admin',\n required: true,\n defaultValue: false,\n input: false,\n },\n },\n },\n session: {\n modelName: 'sessions',\n fields: {\n userId: 'user_id',\n expiresAt: 'expires_at',\n ipAddress: 'ip_address',\n userAgent: 'user_agent',\n createdAt: 'created_at',\n updatedAt: 'updated_at',\n },\n additionalFields: {\n activeTenantId: { type: 'string', fieldName: 'active_tenant_id', required: false, input: false },\n impersonatingUserId: { type: 'string', fieldName: 'impersonating_user_id', required: false, input: false },\n impersonationTenantId: { type: 'string', fieldName: 'impersonation_tenant_id', required: false, input: false },\n impersonationStartedAt: { type: 'date', fieldName: 'impersonation_started_at', required: false, input: false },\n impersonationExpiresAt: { type: 'date', fieldName: 'impersonation_expires_at', required: false, input: false },\n impersonationReason: { type: 'string', fieldName: 'impersonation_reason', required: false, input: false },\n },\n expiresIn: options.session?.expiresIn ?? 60 * 60 * 24 * 30,\n updateAge: options.session?.updateAge ?? 60 * 60 * 24,\n cookieCache: {\n enabled: true,\n maxAge: options.session?.cookieCacheMaxAge ?? 60 * 5,\n },\n },\n account: {\n modelName: 'accounts',\n fields: {\n userId: 'user_id',\n accountId: 'account_id',\n providerId: 'provider_id',\n accessToken: 'access_token',\n refreshToken: 'refresh_token',\n idToken: 'id_token',\n accessTokenExpiresAt: 'access_token_expires_at',\n refreshTokenExpiresAt: 'refresh_token_expires_at',\n createdAt: 'created_at',\n updatedAt: 'updated_at',\n },\n encryptOAuthTokens: true,\n },\n verification: {\n modelName: 'verifications',\n fields: {\n expiresAt: 'expires_at',\n createdAt: 'created_at',\n updatedAt: 'updated_at',\n },\n },\n advanced: {\n database: { generateId: 'uuid' },\n },\n emailAndPassword: {\n enabled: true,\n disableSignUp: options.password?.disableSignUp ?? true,\n requireEmailVerification: options.password?.requireEmailVerification ?? false,\n minPasswordLength: options.password?.minLength ?? 8,\n autoSignIn: true,\n resetPasswordTokenExpiresIn: options.password?.resetTokenExpiresIn ?? 60 * 60,\n revokeSessionsOnPasswordReset: true,\n sendResetPassword: async ({ user, url }) => {\n const rendered = (options.renderEmail?.passwordReset ?? renderPasswordResetEmail)({\n appName,\n email: user.email,\n url,\n })\n await options.sendEmail({ kind: 'password-reset', to: user.email, ...rendered })\n },\n },\n plugins: [\n magicLink({\n disableSignUp: options.magicLink?.disableSignUp ?? true,\n expiresIn: magicTTL,\n storeToken: 'hashed',\n sendMagicLink: async ({ email, url, metadata }) => {\n const invitation = metadata?.flow === 'invite'\n const tenantName =\n typeof metadata?.tenantName === 'string' && metadata.tenantName.trim()\n ? metadata.tenantName.trim()\n : null\n const rendered = (options.renderEmail?.magicLink ?? renderMagicLinkEmail)({\n appName,\n email,\n url,\n invitation,\n tenantName,\n })\n await options.sendEmail({\n kind: invitation ? 'invitation' : 'magic-link',\n to: email,\n ...rendered,\n })\n },\n }),\n ],\n hooks: {\n after: async (rawContext) => {\n const context = rawContext as unknown as {\n path?: string\n query?: { callbackURL?: unknown }\n context: { newSession?: { user: { id: string } } | null }\n }\n if (context.path !== '/magic-link/verify' || !options.inviteService) return {}\n const sessionUserId = context.context.newSession?.user.id\n if (!sessionUserId) return {}\n const grant = options.inviteService.inviteGrantFromCallbackURL(\n context.query?.callbackURL,\n baseURL,\n )\n if (!grant) return {}\n // Preserve the valid session/callback if a downstream domain hook fails;\n // the membership remains unchanged and the callback can show recovery.\n await options.inviteService.acceptInviteAfterMagicLink(grant, sessionUserId).catch((error) => {\n if (options.onInviteAcceptanceError) options.onInviteAcceptanceError(error)\n else console.error('[appkit/auth] invitation acceptance failed', error)\n })\n return {}\n },\n },\n trustedOrigins: options.trustedOrigins ?? [baseURL],\n })\n}\n\nexport type AppkitAuth = ReturnType<typeof createAppkitAuth>\n\n/** Lazy singleton pattern used by server runtimes that must import safely at build time. */\nexport function createLazyAuth(factory: () => AppkitAuth): () => AppkitAuth {\n let instance: AppkitAuth | undefined\n return () => (instance ??= factory())\n}\n\nfunction normalizeBaseURL(value: string): string {\n const url = new URL(value)\n if (url.username || url.password || url.search || url.hash) {\n throw new Error('Authentication baseURL must be an origin without credentials, query, or hash.')\n }\n return url.origin\n}\n"]}