@emdash-cms/auth 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/adapters/kysely.d.mts +62 -0
  2. package/dist/adapters/kysely.d.mts.map +1 -0
  3. package/dist/adapters/kysely.mjs +379 -0
  4. package/dist/adapters/kysely.mjs.map +1 -0
  5. package/dist/authenticate-D5UgaoTH.d.mts +124 -0
  6. package/dist/authenticate-D5UgaoTH.d.mts.map +1 -0
  7. package/dist/authenticate-j5GayLXB.mjs +373 -0
  8. package/dist/authenticate-j5GayLXB.mjs.map +1 -0
  9. package/dist/index.d.mts +444 -0
  10. package/dist/index.d.mts.map +1 -0
  11. package/dist/index.mjs +728 -0
  12. package/dist/index.mjs.map +1 -0
  13. package/dist/oauth/providers/github.d.mts +12 -0
  14. package/dist/oauth/providers/github.d.mts.map +1 -0
  15. package/dist/oauth/providers/github.mjs +55 -0
  16. package/dist/oauth/providers/github.mjs.map +1 -0
  17. package/dist/oauth/providers/google.d.mts +7 -0
  18. package/dist/oauth/providers/google.d.mts.map +1 -0
  19. package/dist/oauth/providers/google.mjs +38 -0
  20. package/dist/oauth/providers/google.mjs.map +1 -0
  21. package/dist/passkey/index.d.mts +2 -0
  22. package/dist/passkey/index.mjs +3 -0
  23. package/dist/types-Bu4irX9A.d.mts +35 -0
  24. package/dist/types-Bu4irX9A.d.mts.map +1 -0
  25. package/dist/types-CiSNpRI9.mjs +60 -0
  26. package/dist/types-CiSNpRI9.mjs.map +1 -0
  27. package/dist/types-HtRc90Wi.d.mts +208 -0
  28. package/dist/types-HtRc90Wi.d.mts.map +1 -0
  29. package/package.json +72 -0
  30. package/src/adapters/kysely.ts +715 -0
  31. package/src/config.ts +214 -0
  32. package/src/index.ts +135 -0
  33. package/src/invite.ts +205 -0
  34. package/src/magic-link/index.ts +150 -0
  35. package/src/oauth/consumer.ts +324 -0
  36. package/src/oauth/providers/github.ts +68 -0
  37. package/src/oauth/providers/google.ts +34 -0
  38. package/src/oauth/types.ts +36 -0
  39. package/src/passkey/authenticate.ts +183 -0
  40. package/src/passkey/index.ts +27 -0
  41. package/src/passkey/register.ts +232 -0
  42. package/src/passkey/types.ts +120 -0
  43. package/src/rbac.test.ts +141 -0
  44. package/src/rbac.ts +205 -0
  45. package/src/signup.ts +210 -0
  46. package/src/tokens.test.ts +141 -0
  47. package/src/tokens.ts +238 -0
  48. package/src/types.ts +352 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":["TOKEN_EXPIRY_MS","TOKEN_EXPIRY_MS","timingDelay","_authConfigSchema"],"sources":["../src/config.ts","../src/rbac.ts","../src/invite.ts","../src/magic-link/index.ts","../src/signup.ts","../src/oauth/consumer.ts","../src/index.ts"],"sourcesContent":["/**\n * Configuration schema for @emdashcms/auth\n */\n\nimport { z } from \"zod\";\n\nimport type { RoleName } from \"./types.js\";\n\n/** Matches http(s) scheme at start of URL */\nconst HTTP_SCHEME_RE = /^https?:\\/\\//i;\n\n/** Validates that a URL string uses http or https scheme. Rejects javascript:/data: URI XSS vectors. */\nconst httpUrl = z\n\t.string()\n\t.url()\n\t.refine((url) => HTTP_SCHEME_RE.test(url), \"URL must use http or https\");\n\n/**\n * OAuth provider configuration\n */\nconst oauthProviderSchema = z.object({\n\tclientId: z.string(),\n\tclientSecret: z.string(),\n});\n\n/**\n * Full auth configuration schema\n */\nexport const authConfigSchema = z.object({\n\t/**\n\t * Secret key for encrypting tokens and session data.\n\t * Generate with: `emdash auth secret`\n\t */\n\tsecret: z.string().min(32, \"Auth secret must be at least 32 characters\"),\n\n\t/**\n\t * Passkey (WebAuthn) configuration\n\t */\n\tpasskeys: z\n\t\t.object({\n\t\t\t/**\n\t\t\t * Relying party name shown to users during passkey registration\n\t\t\t */\n\t\t\trpName: z.string(),\n\t\t\t/**\n\t\t\t * Relying party ID (domain). Defaults to the hostname from baseUrl.\n\t\t\t */\n\t\t\trpId: z.string().optional(),\n\t\t})\n\t\t.optional(),\n\n\t/**\n\t * Self-signup configuration\n\t */\n\tselfSignup: z\n\t\t.object({\n\t\t\t/**\n\t\t\t * Email domains allowed to self-register\n\t\t\t */\n\t\t\tdomains: z.array(z.string()),\n\t\t\t/**\n\t\t\t * Default role for self-registered users\n\t\t\t */\n\t\t\tdefaultRole: z.enum([\"subscriber\", \"contributor\", \"author\"] as const).default(\"contributor\"),\n\t\t})\n\t\t.optional(),\n\n\t/**\n\t * OAuth provider configurations (for \"Login with X\")\n\t */\n\toauth: z\n\t\t.object({\n\t\t\tgithub: oauthProviderSchema.optional(),\n\t\t\tgoogle: oauthProviderSchema.optional(),\n\t\t})\n\t\t.optional(),\n\n\t/**\n\t * Configure EmDash as an OAuth provider\n\t */\n\tprovider: z\n\t\t.object({\n\t\t\tenabled: z.boolean(),\n\t\t\t/**\n\t\t\t * Issuer URL for OIDC. Defaults to site URL.\n\t\t\t */\n\t\t\tissuer: httpUrl.optional(),\n\t\t})\n\t\t.optional(),\n\n\t/**\n\t * Enterprise SSO configuration\n\t */\n\tsso: z\n\t\t.object({\n\t\t\tenabled: z.boolean(),\n\t\t})\n\t\t.optional(),\n\n\t/**\n\t * Session configuration\n\t */\n\tsession: z\n\t\t.object({\n\t\t\t/**\n\t\t\t * Session max age in seconds. Default: 30 days\n\t\t\t */\n\t\t\tmaxAge: z.number().default(30 * 24 * 60 * 60),\n\t\t\t/**\n\t\t\t * Extend session on activity. Default: true\n\t\t\t */\n\t\t\tsliding: z.boolean().default(true),\n\t\t})\n\t\t.optional(),\n});\n\nexport type AuthConfig = z.infer<typeof authConfigSchema>;\n\n/**\n * Validated and resolved auth configuration\n */\nexport interface ResolvedAuthConfig {\n\tsecret: string;\n\tbaseUrl: string;\n\tsiteName: string;\n\n\tpasskeys: {\n\t\trpName: string;\n\t\trpId: string;\n\t\torigin: string;\n\t};\n\n\tselfSignup?: {\n\t\tdomains: string[];\n\t\tdefaultRole: RoleName;\n\t};\n\n\toauth?: {\n\t\tgithub?: {\n\t\t\tclientId: string;\n\t\t\tclientSecret: string;\n\t\t};\n\t\tgoogle?: {\n\t\t\tclientId: string;\n\t\t\tclientSecret: string;\n\t\t};\n\t};\n\n\tprovider?: {\n\t\tenabled: boolean;\n\t\tissuer: string;\n\t};\n\n\tsso?: {\n\t\tenabled: boolean;\n\t};\n\n\tsession: {\n\t\tmaxAge: number;\n\t\tsliding: boolean;\n\t};\n}\n\nconst selfSignupRoleMap: Record<\"subscriber\" | \"contributor\" | \"author\", RoleName> = {\n\tsubscriber: \"SUBSCRIBER\",\n\tcontributor: \"CONTRIBUTOR\",\n\tauthor: \"AUTHOR\",\n};\n\n/**\n * Resolve auth configuration with defaults\n */\nexport function resolveConfig(\n\tconfig: AuthConfig,\n\tbaseUrl: string,\n\tsiteName: string,\n): ResolvedAuthConfig {\n\tconst url = new URL(baseUrl);\n\n\treturn {\n\t\tsecret: config.secret,\n\t\tbaseUrl,\n\t\tsiteName,\n\n\t\tpasskeys: {\n\t\t\trpName: config.passkeys?.rpName ?? siteName,\n\t\t\trpId: config.passkeys?.rpId ?? url.hostname,\n\t\t\torigin: url.origin,\n\t\t},\n\n\t\tselfSignup: config.selfSignup\n\t\t\t? {\n\t\t\t\t\tdomains: config.selfSignup.domains.map((d) => d.toLowerCase()),\n\t\t\t\t\tdefaultRole: selfSignupRoleMap[config.selfSignup.defaultRole],\n\t\t\t\t}\n\t\t\t: undefined,\n\n\t\toauth: config.oauth,\n\n\t\tprovider: config.provider\n\t\t\t? {\n\t\t\t\t\tenabled: config.provider.enabled,\n\t\t\t\t\tissuer: config.provider.issuer ?? baseUrl,\n\t\t\t\t}\n\t\t\t: undefined,\n\n\t\tsso: config.sso,\n\n\t\tsession: {\n\t\t\tmaxAge: config.session?.maxAge ?? 30 * 24 * 60 * 60,\n\t\t\tsliding: config.session?.sliding ?? true,\n\t\t},\n\t};\n}\n","/**\n * Role-Based Access Control\n */\n\nimport type { ApiTokenScope } from \"./tokens.js\";\nimport { Role, type RoleLevel } from \"./types.js\";\n\n/**\n * Permission definitions with minimum role required\n */\nexport const Permissions = {\n\t// Content\n\t\"content:read\": Role.SUBSCRIBER,\n\t\"content:create\": Role.CONTRIBUTOR,\n\t\"content:edit_own\": Role.AUTHOR,\n\t\"content:edit_any\": Role.EDITOR,\n\t\"content:delete_own\": Role.AUTHOR,\n\t\"content:delete_any\": Role.EDITOR,\n\t\"content:publish_own\": Role.AUTHOR,\n\t\"content:publish_any\": Role.EDITOR,\n\n\t// Media\n\t\"media:read\": Role.SUBSCRIBER,\n\t\"media:upload\": Role.CONTRIBUTOR,\n\t\"media:edit_own\": Role.AUTHOR,\n\t\"media:edit_any\": Role.EDITOR,\n\t\"media:delete_own\": Role.AUTHOR,\n\t\"media:delete_any\": Role.EDITOR,\n\n\t// Taxonomies\n\t\"taxonomies:read\": Role.SUBSCRIBER,\n\t\"taxonomies:manage\": Role.EDITOR,\n\n\t// Comments\n\t\"comments:read\": Role.SUBSCRIBER,\n\t\"comments:moderate\": Role.EDITOR,\n\t\"comments:delete\": Role.ADMIN,\n\t\"comments:settings\": Role.ADMIN,\n\n\t// Menus\n\t\"menus:read\": Role.SUBSCRIBER,\n\t\"menus:manage\": Role.EDITOR,\n\n\t// Widgets\n\t\"widgets:read\": Role.SUBSCRIBER,\n\t\"widgets:manage\": Role.EDITOR,\n\n\t// Sections\n\t\"sections:read\": Role.SUBSCRIBER,\n\t\"sections:manage\": Role.EDITOR,\n\n\t// Redirects\n\t\"redirects:read\": Role.EDITOR,\n\t\"redirects:manage\": Role.ADMIN,\n\n\t// Users\n\t\"users:read\": Role.ADMIN,\n\t\"users:invite\": Role.ADMIN,\n\t\"users:manage\": Role.ADMIN,\n\n\t// Settings\n\t\"settings:read\": Role.EDITOR,\n\t\"settings:manage\": Role.ADMIN,\n\n\t// Schema (content types)\n\t\"schema:read\": Role.EDITOR,\n\t\"schema:manage\": Role.ADMIN,\n\n\t// Plugins\n\t\"plugins:read\": Role.EDITOR,\n\t\"plugins:manage\": Role.ADMIN,\n\n\t// Import\n\t\"import:execute\": Role.ADMIN,\n\n\t// Search\n\t\"search:read\": Role.SUBSCRIBER,\n\t\"search:manage\": Role.ADMIN,\n\n\t// Auth\n\t\"auth:manage_own_credentials\": Role.SUBSCRIBER,\n\t\"auth:manage_connections\": Role.ADMIN,\n} as const;\n\nexport type Permission = keyof typeof Permissions;\n\n/**\n * Check if a user has a specific permission\n */\nexport function hasPermission(\n\tuser: { role: RoleLevel } | null | undefined,\n\tpermission: Permission,\n): boolean {\n\tif (!user) return false;\n\treturn user.role >= Permissions[permission];\n}\n\n/**\n * Require a permission, throwing if not met\n */\nexport function requirePermission(\n\tuser: { role: RoleLevel } | null | undefined,\n\tpermission: Permission,\n): asserts user is { role: RoleLevel } {\n\tif (!user) {\n\t\tthrow new PermissionError(\"unauthorized\", \"Authentication required\");\n\t}\n\tif (!hasPermission(user, permission)) {\n\t\tthrow new PermissionError(\"forbidden\", `Missing permission: ${permission}`);\n\t}\n}\n\n/**\n * Check if user can perform action on a resource they own\n */\nexport function canActOnOwn(\n\tuser: { role: RoleLevel; id: string } | null | undefined,\n\townerId: string,\n\townPermission: Permission,\n\tanyPermission: Permission,\n): boolean {\n\tif (!user) return false;\n\tif (user.id === ownerId) {\n\t\treturn hasPermission(user, ownPermission);\n\t}\n\treturn hasPermission(user, anyPermission);\n}\n\n/**\n * Require permission on a resource, checking ownership\n */\nexport function requirePermissionOnResource(\n\tuser: { role: RoleLevel; id: string } | null | undefined,\n\townerId: string,\n\townPermission: Permission,\n\tanyPermission: Permission,\n): asserts user is { role: RoleLevel; id: string } {\n\tif (!user) {\n\t\tthrow new PermissionError(\"unauthorized\", \"Authentication required\");\n\t}\n\tif (!canActOnOwn(user, ownerId, ownPermission, anyPermission)) {\n\t\tthrow new PermissionError(\"forbidden\", `Missing permission: ${anyPermission}`);\n\t}\n}\n\nexport class PermissionError extends Error {\n\tconstructor(\n\t\tpublic code: \"unauthorized\" | \"forbidden\",\n\t\tmessage: string,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"PermissionError\";\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// API Token Scope ↔ Role mapping\n//\n// Maps each API token scope to the minimum RBAC role required to hold it.\n// Used at token issuance time to clamp granted scopes to the user's role.\n// ---------------------------------------------------------------------------\n\n/**\n * Minimum role required for each API token scope.\n *\n * This is the authoritative mapping between the two authorization systems\n * (RBAC roles and API token scopes). When issuing a token, the granted\n * scopes must be intersected with the scopes allowed by the user's role.\n */\nconst SCOPE_MIN_ROLE: Record<ApiTokenScope, RoleLevel> = {\n\t\"content:read\": Role.SUBSCRIBER,\n\t\"content:write\": Role.CONTRIBUTOR,\n\t\"media:read\": Role.SUBSCRIBER,\n\t\"media:write\": Role.CONTRIBUTOR,\n\t\"schema:read\": Role.EDITOR,\n\t\"schema:write\": Role.ADMIN,\n\tadmin: Role.ADMIN,\n};\n\n/**\n * Return the maximum set of API token scopes a given role level may hold.\n *\n * Used at token issuance time (device flow, authorization code exchange)\n * to enforce: effective_scopes = requested_scopes ∩ scopesForRole(role).\n */\nexport function scopesForRole(role: RoleLevel): ApiTokenScope[] {\n\t// eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- Object.entries loses tuple types; SCOPE_MIN_ROLE keys are ApiTokenScope by construction\n\tconst entries = Object.entries(SCOPE_MIN_ROLE) as [ApiTokenScope, RoleLevel][];\n\treturn entries.reduce<ApiTokenScope[]>((acc, [scope, minRole]) => {\n\t\tif (role >= minRole) acc.push(scope);\n\t\treturn acc;\n\t}, []);\n}\n\n/**\n * Clamp a set of requested scopes to those permitted by a user's role.\n *\n * Returns the intersection of `requested` and the scopes the role allows.\n * This is the central policy enforcement point: effective permissions =\n * role permissions ∩ token scopes.\n */\nexport function clampScopes(requested: string[], role: RoleLevel): string[] {\n\tconst allowed = new Set<string>(scopesForRole(role));\n\treturn requested.filter((s) => allowed.has(s));\n}\n","/**\n * Invite system for new users\n */\n\nimport { generateTokenWithHash, hashToken } from \"./tokens.js\";\nimport type { AuthAdapter, RoleLevel, EmailMessage, User } from \"./types.js\";\n\n/** Escape HTML special characters to prevent injection in email templates */\nexport function escapeHtml(s: string): string {\n\treturn s\n\t\t.replaceAll(\"&\", \"&amp;\")\n\t\t.replaceAll(\"<\", \"&lt;\")\n\t\t.replaceAll(\">\", \"&gt;\")\n\t\t.replaceAll('\"', \"&quot;\");\n}\n\nconst TOKEN_EXPIRY_MS = 7 * 24 * 60 * 60 * 1000; // 7 days\n\n/** Function that sends an email (matches the EmailPipeline.send signature) */\nexport type EmailSendFn = (message: EmailMessage) => Promise<void>;\n\nexport interface InviteConfig {\n\tbaseUrl: string;\n\tsiteName: string;\n\t/** Optional email sender. When omitted, invite URL is returned without sending. */\n\temail?: EmailSendFn;\n}\n\n/** Result of creating an invite token (without sending email) */\nexport interface InviteTokenResult {\n\t/** The complete invite URL */\n\turl: string;\n\t/** The invite email address */\n\temail: string;\n}\n\n/**\n * Create an invite token and URL without sending email.\n *\n * Validates the user doesn't already exist, generates a token, stores it,\n * and returns the invite URL. Callers decide whether to send email or\n * display the URL as a copy-link fallback.\n */\nexport async function createInviteToken(\n\tconfig: Pick<InviteConfig, \"baseUrl\">,\n\tadapter: AuthAdapter,\n\temail: string,\n\trole: RoleLevel,\n\tinvitedBy: string,\n): Promise<InviteTokenResult> {\n\t// Check if user already exists\n\tconst existing = await adapter.getUserByEmail(email);\n\tif (existing) {\n\t\tthrow new InviteError(\"user_exists\", \"A user with this email already exists\");\n\t}\n\n\t// Generate token\n\tconst { token, hash } = generateTokenWithHash();\n\n\t// Store token\n\tawait adapter.createToken({\n\t\thash,\n\t\temail,\n\t\ttype: \"invite\",\n\t\trole,\n\t\tinvitedBy,\n\t\texpiresAt: new Date(Date.now() + TOKEN_EXPIRY_MS),\n\t});\n\n\t// Build invite URL\n\tconst url = new URL(\"/api/auth/invite/accept\", config.baseUrl);\n\turl.searchParams.set(\"token\", token);\n\n\treturn { url: url.toString(), email };\n}\n\n/**\n * Build the invite email message.\n */\nfunction buildInviteEmail(inviteUrl: string, email: string, siteName: string): EmailMessage {\n\tconst safeName = escapeHtml(siteName);\n\treturn {\n\t\tto: email,\n\t\tsubject: `You've been invited to ${siteName}`,\n\t\ttext: `You've been invited to join ${siteName}.\\n\\nClick this link to create your account:\\n${inviteUrl}\\n\\nThis link expires in 7 days.`,\n\t\thtml: `\n<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n</head>\n<body style=\"font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.5; color: #333; max-width: 600px; margin: 0 auto; padding: 20px;\">\n <h1 style=\"font-size: 24px; margin-bottom: 20px;\">You've been invited to ${safeName}</h1>\n <p>Click the button below to create your account:</p>\n <p style=\"margin: 30px 0;\">\n <a href=\"${inviteUrl}\" style=\"background-color: #0066cc; color: white; padding: 12px 24px; text-decoration: none; border-radius: 6px; display: inline-block;\">Accept Invite</a>\n </p>\n <p style=\"color: #666; font-size: 14px;\">This link expires in 7 days.</p>\n</body>\n</html>`,\n\t};\n}\n\n/**\n * Create and send an invite to a new user.\n *\n * When `config.email` is provided, sends the invite email.\n * When omitted, creates the token and returns the invite URL\n * without sending (for the copy-link fallback).\n */\nexport async function createInvite(\n\tconfig: InviteConfig,\n\tadapter: AuthAdapter,\n\temail: string,\n\trole: RoleLevel,\n\tinvitedBy: string,\n): Promise<InviteTokenResult> {\n\tconst result = await createInviteToken(config, adapter, email, role, invitedBy);\n\n\t// Send email if a sender is configured\n\tif (config.email) {\n\t\tconst message = buildInviteEmail(result.url, email, config.siteName);\n\t\tawait config.email(message);\n\t}\n\n\treturn result;\n}\n\n/**\n * Validate an invite token and return the invite data\n */\nexport async function validateInvite(\n\tadapter: AuthAdapter,\n\ttoken: string,\n): Promise<{ email: string; role: RoleLevel }> {\n\tconst hash = hashToken(token);\n\n\tconst authToken = await adapter.getToken(hash, \"invite\");\n\tif (!authToken) {\n\t\tthrow new InviteError(\"invalid_token\", \"Invalid or expired invite link\");\n\t}\n\n\tif (authToken.expiresAt < new Date()) {\n\t\tawait adapter.deleteToken(hash);\n\t\tthrow new InviteError(\"token_expired\", \"This invite has expired\");\n\t}\n\n\tif (!authToken.email || authToken.role === null) {\n\t\tthrow new InviteError(\"invalid_token\", \"Invalid invite data\");\n\t}\n\n\treturn {\n\t\temail: authToken.email,\n\t\trole: authToken.role,\n\t};\n}\n\n/**\n * Complete the invite process (after passkey registration)\n */\nexport async function completeInvite(\n\tadapter: AuthAdapter,\n\ttoken: string,\n\tuserData: {\n\t\tname?: string;\n\t\tavatarUrl?: string;\n\t},\n): Promise<User> {\n\tconst hash = hashToken(token);\n\n\t// Validate token one more time\n\tconst authToken = await adapter.getToken(hash, \"invite\");\n\tif (!authToken || authToken.expiresAt < new Date()) {\n\t\tthrow new InviteError(\"invalid_token\", \"Invalid or expired invite\");\n\t}\n\n\tif (!authToken.email || authToken.role === null) {\n\t\tthrow new InviteError(\"invalid_token\", \"Invalid invite data\");\n\t}\n\n\t// Delete token (single-use)\n\tawait adapter.deleteToken(hash);\n\n\t// Create user\n\tconst user = await adapter.createUser({\n\t\temail: authToken.email,\n\t\tname: userData.name,\n\t\tavatarUrl: userData.avatarUrl,\n\t\trole: authToken.role,\n\t\temailVerified: true, // Email verified by accepting invite\n\t});\n\n\treturn user;\n}\n\nexport class InviteError extends Error {\n\tconstructor(\n\t\tpublic code: \"invalid_token\" | \"token_expired\" | \"user_exists\",\n\t\tmessage: string,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"InviteError\";\n\t}\n}\n","/**\n * Magic link authentication\n */\n\nimport { escapeHtml } from \"../invite.js\";\nimport { generateTokenWithHash, hashToken } from \"../tokens.js\";\nimport type { AuthAdapter, User, EmailMessage } from \"../types.js\";\n\nconst TOKEN_EXPIRY_MS = 15 * 60 * 1000; // 15 minutes\n\n/** Function that sends an email (matches the EmailPipeline.send signature) */\nexport type EmailSendFn = (message: EmailMessage) => Promise<void>;\n\nexport interface MagicLinkConfig {\n\tbaseUrl: string;\n\tsiteName: string;\n\t/** Optional email sender. When omitted, magic links cannot be sent. */\n\temail?: EmailSendFn;\n}\n\n/**\n * Add artificial delay with jitter to prevent timing attacks.\n * Range approximates the time for token creation + email send.\n */\nasync function timingDelay(): Promise<void> {\n\tconst delay = 100 + Math.random() * 150; // 100-250ms\n\tawait new Promise((resolve) => setTimeout(resolve, delay));\n}\n\n/**\n * Send a magic link to a user's email.\n *\n * Requires `config.email` to be set. Throws if no email sender is configured.\n */\nexport async function sendMagicLink(\n\tconfig: MagicLinkConfig,\n\tadapter: AuthAdapter,\n\temail: string,\n\ttype: \"magic_link\" | \"recovery\" = \"magic_link\",\n): Promise<void> {\n\tif (!config.email) {\n\t\tthrow new MagicLinkError(\"email_not_configured\", \"Email is not configured\");\n\t}\n\n\t// Find user\n\tconst user = await adapter.getUserByEmail(email);\n\tif (!user) {\n\t\t// Don't reveal whether user exists - add delay to match successful path timing\n\t\tawait timingDelay();\n\t\treturn;\n\t}\n\n\t// Generate token\n\tconst { token, hash } = generateTokenWithHash();\n\n\t// Store token hash\n\tawait adapter.createToken({\n\t\thash,\n\t\tuserId: user.id,\n\t\temail: user.email,\n\t\ttype,\n\t\texpiresAt: new Date(Date.now() + TOKEN_EXPIRY_MS),\n\t});\n\n\t// Build magic link URL\n\tconst url = new URL(\"/api/auth/magic-link/verify\", config.baseUrl);\n\turl.searchParams.set(\"token\", token);\n\n\t// Send email\n\tconst safeName = escapeHtml(config.siteName);\n\tawait config.email({\n\t\tto: user.email,\n\t\tsubject: `Sign in to ${config.siteName}`,\n\t\ttext: `Click this link to sign in to ${config.siteName}:\\n\\n${url.toString()}\\n\\nThis link expires in 15 minutes.\\n\\nIf you didn't request this, you can safely ignore this email.`,\n\t\thtml: `\n<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n</head>\n<body style=\"font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.5; color: #333; max-width: 600px; margin: 0 auto; padding: 20px;\">\n <h1 style=\"font-size: 24px; margin-bottom: 20px;\">Sign in to ${safeName}</h1>\n <p>Click the button below to sign in:</p>\n <p style=\"margin: 30px 0;\">\n <a href=\"${url.toString()}\" style=\"background-color: #0066cc; color: white; padding: 12px 24px; text-decoration: none; border-radius: 6px; display: inline-block;\">Sign in</a>\n </p>\n <p style=\"color: #666; font-size: 14px;\">This link expires in 15 minutes.</p>\n <p style=\"color: #666; font-size: 14px;\">If you didn't request this, you can safely ignore this email.</p>\n</body>\n</html>`,\n\t});\n}\n\n/**\n * Verify a magic link token and return the user\n */\nexport async function verifyMagicLink(adapter: AuthAdapter, token: string): Promise<User> {\n\tconst hash = hashToken(token);\n\n\t// Find and validate token\n\tconst authToken = await adapter.getToken(hash, \"magic_link\");\n\tif (!authToken) {\n\t\t// Also check for recovery tokens\n\t\tconst recoveryToken = await adapter.getToken(hash, \"recovery\");\n\t\tif (!recoveryToken) {\n\t\t\tthrow new MagicLinkError(\"invalid_token\", \"Invalid or expired link\");\n\t\t}\n\t\treturn verifyTokenAndGetUser(adapter, recoveryToken, hash);\n\t}\n\n\treturn verifyTokenAndGetUser(adapter, authToken, hash);\n}\n\nasync function verifyTokenAndGetUser(\n\tadapter: AuthAdapter,\n\tauthToken: { userId: string | null; expiresAt: Date },\n\thash: string,\n): Promise<User> {\n\t// Check expiry\n\tif (authToken.expiresAt < new Date()) {\n\t\tawait adapter.deleteToken(hash);\n\t\tthrow new MagicLinkError(\"token_expired\", \"This link has expired\");\n\t}\n\n\t// Delete token (single-use)\n\tawait adapter.deleteToken(hash);\n\n\t// Get user\n\tif (!authToken.userId) {\n\t\tthrow new MagicLinkError(\"invalid_token\", \"Invalid token\");\n\t}\n\n\tconst user = await adapter.getUserById(authToken.userId);\n\tif (!user) {\n\t\tthrow new MagicLinkError(\"user_not_found\", \"User not found\");\n\t}\n\n\treturn user;\n}\n\nexport class MagicLinkError extends Error {\n\tconstructor(\n\t\tpublic code: \"invalid_token\" | \"token_expired\" | \"user_not_found\" | \"email_not_configured\",\n\t\tmessage: string,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"MagicLinkError\";\n\t}\n}\n","/**\n * Self-signup for allowed email domains\n */\n\nimport { escapeHtml } from \"./invite.js\";\nimport { generateTokenWithHash, hashToken } from \"./tokens.js\";\nimport type { AuthAdapter, RoleLevel, EmailMessage, User } from \"./types.js\";\n\nconst TOKEN_EXPIRY_MS = 15 * 60 * 1000; // 15 minutes\n\n/** Function that sends an email (matches the EmailPipeline.send signature) */\nexport type EmailSendFn = (message: EmailMessage) => Promise<void>;\n\n/**\n * Add artificial delay with jitter to prevent timing attacks.\n * Range approximates the time for token creation + email send.\n */\nasync function timingDelay(): Promise<void> {\n\tconst delay = 100 + Math.random() * 150; // 100-250ms\n\tawait new Promise((resolve) => setTimeout(resolve, delay));\n}\n\nexport interface SignupConfig {\n\tbaseUrl: string;\n\tsiteName: string;\n\t/** Optional email sender. When omitted, signup verification cannot be sent. */\n\temail?: EmailSendFn;\n}\n\n/**\n * Check if an email domain is allowed for self-signup\n */\nexport async function canSignup(\n\tadapter: AuthAdapter,\n\temail: string,\n): Promise<{ allowed: boolean; role: RoleLevel } | null> {\n\tconst domain = email.split(\"@\")[1]?.toLowerCase();\n\tif (!domain) return null;\n\n\tconst allowedDomain = await adapter.getAllowedDomain(domain);\n\tif (!allowedDomain || !allowedDomain.enabled) {\n\t\treturn null;\n\t}\n\n\treturn {\n\t\tallowed: true,\n\t\trole: allowedDomain.defaultRole,\n\t};\n}\n\n/**\n * Request self-signup (sends verification email).\n *\n * Requires `config.email` to be set. Throws if no email sender is configured.\n */\nexport async function requestSignup(\n\tconfig: SignupConfig,\n\tadapter: AuthAdapter,\n\temail: string,\n): Promise<void> {\n\tif (!config.email) {\n\t\tthrow new SignupError(\"email_not_configured\", \"Email is not configured\");\n\t}\n\n\t// Check if user already exists\n\tconst existing = await adapter.getUserByEmail(email);\n\tif (existing) {\n\t\t// Don't reveal that user exists - add delay to match successful path timing\n\t\tawait timingDelay();\n\t\treturn;\n\t}\n\n\t// Check if domain is allowed\n\tconst signup = await canSignup(adapter, email);\n\tif (!signup) {\n\t\t// Don't reveal that domain is not allowed - add delay to match successful path timing\n\t\tawait timingDelay();\n\t\treturn;\n\t}\n\n\t// Generate token\n\tconst { token, hash } = generateTokenWithHash();\n\n\t// Store token with role info\n\tawait adapter.createToken({\n\t\thash,\n\t\temail,\n\t\ttype: \"email_verify\",\n\t\trole: signup.role,\n\t\texpiresAt: new Date(Date.now() + TOKEN_EXPIRY_MS),\n\t});\n\n\t// Build verification URL\n\tconst url = new URL(\"/api/auth/signup/verify\", config.baseUrl);\n\turl.searchParams.set(\"token\", token);\n\n\t// Send email\n\tconst safeName = escapeHtml(config.siteName);\n\tawait config.email({\n\t\tto: email,\n\t\tsubject: `Verify your email for ${config.siteName}`,\n\t\ttext: `Click this link to verify your email and create your account:\\n\\n${url.toString()}\\n\\nThis link expires in 15 minutes.\\n\\nIf you didn't request this, you can safely ignore this email.`,\n\t\thtml: `\n<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n</head>\n<body style=\"font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.5; color: #333; max-width: 600px; margin: 0 auto; padding: 20px;\">\n <h1 style=\"font-size: 24px; margin-bottom: 20px;\">Verify your email</h1>\n <p>Click the button below to verify your email and create your ${safeName} account:</p>\n <p style=\"margin: 30px 0;\">\n <a href=\"${url.toString()}\" style=\"background-color: #0066cc; color: white; padding: 12px 24px; text-decoration: none; border-radius: 6px; display: inline-block;\">Verify Email</a>\n </p>\n <p style=\"color: #666; font-size: 14px;\">This link expires in 15 minutes.</p>\n <p style=\"color: #666; font-size: 14px;\">If you didn't request this, you can safely ignore this email.</p>\n</body>\n</html>`,\n\t});\n}\n\n/**\n * Validate a signup verification token\n */\nexport async function validateSignupToken(\n\tadapter: AuthAdapter,\n\ttoken: string,\n): Promise<{ email: string; role: RoleLevel }> {\n\tconst hash = hashToken(token);\n\n\tconst authToken = await adapter.getToken(hash, \"email_verify\");\n\tif (!authToken) {\n\t\tthrow new SignupError(\"invalid_token\", \"Invalid or expired verification link\");\n\t}\n\n\tif (authToken.expiresAt < new Date()) {\n\t\tawait adapter.deleteToken(hash);\n\t\tthrow new SignupError(\"token_expired\", \"This link has expired\");\n\t}\n\n\tif (!authToken.email || authToken.role === null) {\n\t\tthrow new SignupError(\"invalid_token\", \"Invalid token data\");\n\t}\n\n\treturn {\n\t\temail: authToken.email,\n\t\trole: authToken.role,\n\t};\n}\n\n/**\n * Complete signup process (after passkey registration)\n */\nexport async function completeSignup(\n\tadapter: AuthAdapter,\n\ttoken: string,\n\tuserData: {\n\t\tname?: string;\n\t\tavatarUrl?: string;\n\t},\n): Promise<User> {\n\tconst hash = hashToken(token);\n\n\t// Validate token one more time\n\tconst authToken = await adapter.getToken(hash, \"email_verify\");\n\tif (!authToken || authToken.expiresAt < new Date()) {\n\t\tthrow new SignupError(\"invalid_token\", \"Invalid or expired verification\");\n\t}\n\n\tif (!authToken.email || authToken.role === null) {\n\t\tthrow new SignupError(\"invalid_token\", \"Invalid token data\");\n\t}\n\n\t// Check user doesn't already exist\n\tconst existing = await adapter.getUserByEmail(authToken.email);\n\tif (existing) {\n\t\tawait adapter.deleteToken(hash);\n\t\tthrow new SignupError(\"user_exists\", \"An account with this email already exists\");\n\t}\n\n\t// Delete token (single-use)\n\tawait adapter.deleteToken(hash);\n\n\t// Create user\n\tconst user = await adapter.createUser({\n\t\temail: authToken.email,\n\t\tname: userData.name,\n\t\tavatarUrl: userData.avatarUrl,\n\t\trole: authToken.role,\n\t\temailVerified: true,\n\t});\n\n\treturn user;\n}\n\nexport class SignupError extends Error {\n\tconstructor(\n\t\tpublic code:\n\t\t\t| \"invalid_token\"\n\t\t\t| \"token_expired\"\n\t\t\t| \"user_exists\"\n\t\t\t| \"domain_not_allowed\"\n\t\t\t| \"email_not_configured\",\n\t\tmessage: string,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"SignupError\";\n\t}\n}\n","/**\n * OAuth consumer - \"Login with X\" functionality\n */\n\nimport { sha256 } from \"@oslojs/crypto/sha2\";\nimport { encodeBase64urlNoPadding } from \"@oslojs/encoding\";\nimport { z } from \"zod\";\n\nimport type { AuthAdapter, User, RoleLevel } from \"../types.js\";\nimport { github, fetchGitHubEmail } from \"./providers/github.js\";\nimport { google } from \"./providers/google.js\";\nimport type { OAuthProvider, OAuthConfig, OAuthProfile, OAuthState } from \"./types.js\";\n\nexport { github, google };\n\nexport interface OAuthConsumerConfig {\n\tbaseUrl: string;\n\tproviders: {\n\t\tgithub?: OAuthConfig;\n\t\tgoogle?: OAuthConfig;\n\t};\n\t/**\n\t * Check if self-signup is allowed for this email domain\n\t */\n\tcanSelfSignup?: (email: string) => Promise<{ allowed: boolean; role: RoleLevel } | null>;\n}\n\n/**\n * Generate an OAuth authorization URL\n */\nexport async function createAuthorizationUrl(\n\tconfig: OAuthConsumerConfig,\n\tproviderName: \"github\" | \"google\",\n\tstateStore: StateStore,\n): Promise<{ url: string; state: string }> {\n\tconst providerConfig = config.providers[providerName];\n\tif (!providerConfig) {\n\t\tthrow new Error(`OAuth provider ${providerName} not configured`);\n\t}\n\n\tconst provider = getProvider(providerName);\n\tconst state = generateState();\n\tconst redirectUri = `${config.baseUrl}/api/auth/oauth/${providerName}/callback`;\n\n\t// Generate PKCE code verifier for providers that support it\n\tconst codeVerifier = generateCodeVerifier();\n\tconst codeChallenge = await generateCodeChallenge(codeVerifier);\n\n\t// Store state for verification\n\tawait stateStore.set(state, {\n\t\tprovider: providerName,\n\t\tredirectUri,\n\t\tcodeVerifier,\n\t});\n\n\t// Build authorization URL\n\tconst url = new URL(provider.authorizeUrl);\n\turl.searchParams.set(\"client_id\", providerConfig.clientId);\n\turl.searchParams.set(\"redirect_uri\", redirectUri);\n\turl.searchParams.set(\"response_type\", \"code\");\n\turl.searchParams.set(\"scope\", provider.scopes.join(\" \"));\n\turl.searchParams.set(\"state\", state);\n\n\t// PKCE for all providers (GitHub has supported S256 since 2021)\n\turl.searchParams.set(\"code_challenge\", codeChallenge);\n\turl.searchParams.set(\"code_challenge_method\", \"S256\");\n\n\treturn { url: url.toString(), state };\n}\n\n/**\n * Handle OAuth callback\n */\nexport async function handleOAuthCallback(\n\tconfig: OAuthConsumerConfig,\n\tadapter: AuthAdapter,\n\tproviderName: \"github\" | \"google\",\n\tcode: string,\n\tstate: string,\n\tstateStore: StateStore,\n): Promise<User> {\n\tconst providerConfig = config.providers[providerName];\n\tif (!providerConfig) {\n\t\tthrow new Error(`OAuth provider ${providerName} not configured`);\n\t}\n\n\t// Verify state\n\tconst storedState = await stateStore.get(state);\n\tif (!storedState || storedState.provider !== providerName) {\n\t\tthrow new OAuthError(\"invalid_state\", \"Invalid OAuth state\");\n\t}\n\n\t// Delete state (single-use)\n\tawait stateStore.delete(state);\n\n\tconst provider = getProvider(providerName);\n\n\t// Exchange code for tokens\n\tconst tokens = await exchangeCode(\n\t\tprovider,\n\t\tproviderConfig,\n\t\tcode,\n\t\tstoredState.redirectUri,\n\t\tstoredState.codeVerifier,\n\t);\n\n\t// Fetch user profile\n\tconst profile = await fetchProfile(provider, tokens.accessToken, providerName);\n\n\t// Find or create user\n\treturn findOrCreateUser(config, adapter, providerName, profile);\n}\n\n/**\n * Exchange authorization code for tokens\n */\nasync function exchangeCode(\n\tprovider: OAuthProvider,\n\tconfig: OAuthConfig,\n\tcode: string,\n\tredirectUri: string,\n\tcodeVerifier?: string,\n): Promise<{ accessToken: string; idToken?: string }> {\n\tconst body = new URLSearchParams({\n\t\tgrant_type: \"authorization_code\",\n\t\tcode,\n\t\tredirect_uri: redirectUri,\n\t\tclient_id: config.clientId,\n\t\tclient_secret: config.clientSecret,\n\t});\n\n\tif (codeVerifier) {\n\t\tbody.set(\"code_verifier\", codeVerifier);\n\t}\n\n\tconst response = await fetch(provider.tokenUrl, {\n\t\tmethod: \"POST\",\n\t\theaders: {\n\t\t\t\"Content-Type\": \"application/x-www-form-urlencoded\",\n\t\t\tAccept: \"application/json\",\n\t\t},\n\t\tbody,\n\t});\n\n\tif (!response.ok) {\n\t\tconst error = await response.text();\n\t\tthrow new OAuthError(\"token_exchange_failed\", `Token exchange failed: ${error}`);\n\t}\n\n\tconst json: unknown = await response.json();\n\tconst data = z\n\t\t.object({\n\t\t\taccess_token: z.string(),\n\t\t\tid_token: z.string().optional(),\n\t\t})\n\t\t.parse(json);\n\n\treturn {\n\t\taccessToken: data.access_token,\n\t\tidToken: data.id_token,\n\t};\n}\n\n/**\n * Fetch user profile from OAuth provider\n */\nasync function fetchProfile(\n\tprovider: OAuthProvider,\n\taccessToken: string,\n\tproviderName: string,\n): Promise<OAuthProfile> {\n\tif (!provider.userInfoUrl) {\n\t\tthrow new Error(\"Provider does not have userinfo URL\");\n\t}\n\n\tconst response = await fetch(provider.userInfoUrl, {\n\t\theaders: {\n\t\t\tAuthorization: `Bearer ${accessToken}`,\n\t\t\tAccept: \"application/json\",\n\t\t},\n\t});\n\n\tif (!response.ok) {\n\t\tthrow new OAuthError(\"profile_fetch_failed\", `Failed to fetch profile: ${response.status}`);\n\t}\n\n\tconst data = await response.json();\n\tconst profile = provider.parseProfile(data);\n\n\t// GitHub may not return email in main profile\n\tif (providerName === \"github\" && !profile.email) {\n\t\tprofile.email = await fetchGitHubEmail(accessToken);\n\t}\n\n\treturn profile;\n}\n\n/**\n * Find existing user or create new one (with auto-linking)\n */\nasync function findOrCreateUser(\n\tconfig: OAuthConsumerConfig,\n\tadapter: AuthAdapter,\n\tproviderName: string,\n\tprofile: OAuthProfile,\n): Promise<User> {\n\t// Check if OAuth account already linked\n\tconst existingAccount = await adapter.getOAuthAccount(providerName, profile.id);\n\tif (existingAccount) {\n\t\tconst user = await adapter.getUserById(existingAccount.userId);\n\t\tif (!user) {\n\t\t\tthrow new OAuthError(\"user_not_found\", \"Linked user not found\");\n\t\t}\n\t\treturn user;\n\t}\n\n\t// Check if user with this email exists (auto-link)\n\t// Only auto-link when the provider has verified the email to prevent\n\t// account takeover via unverified email on a third-party provider\n\tconst existingUser = await adapter.getUserByEmail(profile.email);\n\tif (existingUser) {\n\t\tif (!profile.emailVerified) {\n\t\t\tthrow new OAuthError(\n\t\t\t\t\"signup_not_allowed\",\n\t\t\t\t\"Cannot link account: email not verified by provider\",\n\t\t\t);\n\t\t}\n\t\tawait adapter.createOAuthAccount({\n\t\t\tprovider: providerName,\n\t\t\tproviderAccountId: profile.id,\n\t\t\tuserId: existingUser.id,\n\t\t});\n\t\treturn existingUser;\n\t}\n\n\t// Check if self-signup is allowed\n\tif (config.canSelfSignup) {\n\t\tconst signup = await config.canSelfSignup(profile.email);\n\t\tif (signup?.allowed) {\n\t\t\t// Create new user\n\t\t\tconst user = await adapter.createUser({\n\t\t\t\temail: profile.email,\n\t\t\t\tname: profile.name,\n\t\t\t\tavatarUrl: profile.avatarUrl,\n\t\t\t\trole: signup.role,\n\t\t\t\temailVerified: profile.emailVerified,\n\t\t\t});\n\n\t\t\t// Link OAuth account\n\t\t\tawait adapter.createOAuthAccount({\n\t\t\t\tprovider: providerName,\n\t\t\t\tproviderAccountId: profile.id,\n\t\t\t\tuserId: user.id,\n\t\t\t});\n\n\t\t\treturn user;\n\t\t}\n\t}\n\n\tthrow new OAuthError(\"signup_not_allowed\", \"Self-signup not allowed for this email domain\");\n}\n\nfunction getProvider(name: \"github\" | \"google\"): OAuthProvider {\n\tswitch (name) {\n\t\tcase \"github\":\n\t\t\treturn github;\n\t\tcase \"google\":\n\t\t\treturn google;\n\t}\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/**\n * Generate a random state string for OAuth CSRF protection\n */\nfunction generateState(): string {\n\tconst bytes = new Uint8Array(32);\n\tcrypto.getRandomValues(bytes);\n\treturn encodeBase64urlNoPadding(bytes);\n}\n\nfunction generateCodeVerifier(): string {\n\tconst bytes = new Uint8Array(32);\n\tcrypto.getRandomValues(bytes);\n\treturn encodeBase64urlNoPadding(bytes);\n}\n\nasync function generateCodeChallenge(verifier: string): Promise<string> {\n\tconst bytes = new TextEncoder().encode(verifier);\n\tconst hash = sha256(bytes);\n\treturn encodeBase64urlNoPadding(hash);\n}\n\n// ============================================================================\n// State storage interface\n// ============================================================================\n\nexport interface StateStore {\n\tset(state: string, data: OAuthState): Promise<void>;\n\tget(state: string): Promise<OAuthState | null>;\n\tdelete(state: string): Promise<void>;\n}\n\n// ============================================================================\n// Errors\n// ============================================================================\n\nexport class OAuthError extends Error {\n\tconstructor(\n\t\tpublic code:\n\t\t\t| \"invalid_state\"\n\t\t\t| \"token_exchange_failed\"\n\t\t\t| \"profile_fetch_failed\"\n\t\t\t| \"user_not_found\"\n\t\t\t| \"signup_not_allowed\",\n\t\tmessage: string,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"OAuthError\";\n\t}\n}\n","/**\n * @emdashcms/auth - Passkey-first authentication for EmDash\n *\n * Email is now handled by the plugin email pipeline (see PLUGIN-EMAIL.md).\n * Auth functions accept an optional `email` send function instead of a\n * hardcoded adapter. The route layer bridges `emdash.email.send()` from\n * the pipeline into the auth functions.\n *\n * @example\n * ```ts\n * import { auth } from '@emdashcms/auth'\n *\n * export default defineConfig({\n * integrations: [\n * emdash({\n * auth: auth({\n * secret: import.meta.env.EMDASH_AUTH_SECRET,\n * passkeys: { rpName: 'My Site' },\n * }),\n * }),\n * ],\n * })\n * ```\n */\n\n// Types\nexport * from \"./types.js\";\n\n// Config\nimport { authConfigSchema as _authConfigSchema } from \"./config.js\";\nexport {\n\tauthConfigSchema,\n\tresolveConfig,\n\ttype AuthConfig,\n\ttype ResolvedAuthConfig,\n} from \"./config.js\";\n\n// RBAC\nexport {\n\tPermissions,\n\thasPermission,\n\trequirePermission,\n\tcanActOnOwn,\n\trequirePermissionOnResource,\n\tPermissionError,\n\tscopesForRole,\n\tclampScopes,\n\ttype Permission,\n} from \"./rbac.js\";\n\n// Tokens\nexport {\n\tgenerateToken,\n\thashToken,\n\tgenerateTokenWithHash,\n\tgenerateSessionId,\n\tgenerateAuthSecret,\n\tsecureCompare,\n\tencrypt,\n\tdecrypt,\n\t// Prefixed API tokens (ec_pat_, ec_oat_, ec_ort_)\n\tTOKEN_PREFIXES,\n\tgeneratePrefixedToken,\n\thashPrefixedToken,\n\t// Scopes\n\tVALID_SCOPES,\n\tvalidateScopes,\n\thasScope,\n\ttype ApiTokenScope,\n\t// PKCE\n\tcomputeS256Challenge,\n} from \"./tokens.js\";\n\n// Passkey\nexport * from \"./passkey/index.js\";\n\n// Magic Link\nexport {\n\tsendMagicLink,\n\tverifyMagicLink,\n\tMagicLinkError,\n\ttype MagicLinkConfig,\n} from \"./magic-link/index.js\";\n\n// Invite\nexport {\n\tcreateInvite,\n\tcreateInviteToken,\n\tvalidateInvite,\n\tcompleteInvite,\n\tInviteError,\n\tescapeHtml,\n\ttype InviteConfig,\n\ttype InviteTokenResult,\n\ttype EmailSendFn,\n} from \"./invite.js\";\n\n// Signup\nexport {\n\tcanSignup,\n\trequestSignup,\n\tvalidateSignupToken,\n\tcompleteSignup,\n\tSignupError,\n\ttype SignupConfig,\n} from \"./signup.js\";\n\n// OAuth\nexport {\n\tcreateAuthorizationUrl,\n\thandleOAuthCallback,\n\tOAuthError,\n\tgithub,\n\tgoogle,\n\ttype StateStore,\n\ttype OAuthConsumerConfig,\n} from \"./oauth/consumer.js\";\nexport type { OAuthProvider, OAuthConfig, OAuthProfile, OAuthState } from \"./oauth/types.js\";\n\n// Email types (implementations moved to plugin email pipeline)\nexport type { EmailAdapter, EmailMessage } from \"./types.js\";\n\n/**\n * Create an auth configuration\n *\n * This is a helper function that validates the config at runtime.\n */\nexport function auth(config: import(\"./config.js\").AuthConfig): import(\"./config.js\").AuthConfig {\n\t// Validate config\n\tconst result = _authConfigSchema.safeParse(config);\n\tif (!result.success) {\n\t\tthrow new Error(`Invalid auth config: ${result.error.message}`);\n\t}\n\treturn result.data;\n}\n"],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import { r as OAuthProvider } from "../../types-Bu4irX9A.mjs";
2
+
3
+ //#region src/oauth/providers/github.d.ts
4
+ declare const github: OAuthProvider;
5
+ /**
6
+ * Fetch the user's primary email from GitHub
7
+ * (needed because email may not be returned in the basic user endpoint)
8
+ */
9
+ declare function fetchGitHubEmail(accessToken: string): Promise<string>;
10
+ //#endregion
11
+ export { fetchGitHubEmail, github };
12
+ //# sourceMappingURL=github.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github.d.mts","names":[],"sources":["../../../src/oauth/providers/github.ts"],"mappings":";;;cAsBa,MAAA,EAAQ,aAAA;;;;AAuBrB;iBAAsB,gBAAA,CAAiB,WAAA,WAAsB,OAAA"}
@@ -0,0 +1,55 @@
1
+ import { z } from "zod";
2
+
3
+ //#region src/oauth/providers/github.ts
4
+ /**
5
+ * GitHub OAuth provider
6
+ */
7
+ const gitHubUserSchema = z.object({
8
+ id: z.number(),
9
+ login: z.string(),
10
+ name: z.string().nullable(),
11
+ email: z.string().nullable(),
12
+ avatar_url: z.string()
13
+ });
14
+ const gitHubEmailSchema = z.object({
15
+ email: z.string(),
16
+ primary: z.boolean(),
17
+ verified: z.boolean()
18
+ });
19
+ const github = {
20
+ name: "github",
21
+ authorizeUrl: "https://github.com/login/oauth/authorize",
22
+ tokenUrl: "https://github.com/login/oauth/access_token",
23
+ userInfoUrl: "https://api.github.com/user",
24
+ scopes: ["read:user", "user:email"],
25
+ parseProfile(data) {
26
+ const user = gitHubUserSchema.parse(data);
27
+ return {
28
+ id: String(user.id),
29
+ email: user.email || "",
30
+ name: user.name,
31
+ avatarUrl: user.avatar_url,
32
+ emailVerified: true
33
+ };
34
+ }
35
+ };
36
+ /**
37
+ * Fetch the user's primary email from GitHub
38
+ * (needed because email may not be returned in the basic user endpoint)
39
+ */
40
+ async function fetchGitHubEmail(accessToken) {
41
+ const response = await fetch("https://api.github.com/user/emails", { headers: {
42
+ Authorization: `Bearer ${accessToken}`,
43
+ Accept: "application/vnd.github+json",
44
+ "X-GitHub-Api-Version": "2022-11-28"
45
+ } });
46
+ if (!response.ok) throw new Error(`Failed to fetch GitHub emails: ${response.status}`);
47
+ const json = await response.json();
48
+ const primary = z.array(gitHubEmailSchema).parse(json).find((e) => e.primary && e.verified);
49
+ if (!primary) throw new Error("No verified primary email found on GitHub account");
50
+ return primary.email;
51
+ }
52
+
53
+ //#endregion
54
+ export { fetchGitHubEmail, github };
55
+ //# sourceMappingURL=github.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github.mjs","names":[],"sources":["../../../src/oauth/providers/github.ts"],"sourcesContent":["/**\n * GitHub OAuth provider\n */\n\nimport { z } from \"zod\";\n\nimport type { OAuthProvider, OAuthProfile } from \"../types.js\";\n\nconst gitHubUserSchema = z.object({\n\tid: z.number(),\n\tlogin: z.string(),\n\tname: z.string().nullable(),\n\temail: z.string().nullable(),\n\tavatar_url: z.string(),\n});\n\nconst gitHubEmailSchema = z.object({\n\temail: z.string(),\n\tprimary: z.boolean(),\n\tverified: z.boolean(),\n});\n\nexport const github: OAuthProvider = {\n\tname: \"github\",\n\tauthorizeUrl: \"https://github.com/login/oauth/authorize\",\n\ttokenUrl: \"https://github.com/login/oauth/access_token\",\n\tuserInfoUrl: \"https://api.github.com/user\",\n\tscopes: [\"read:user\", \"user:email\"],\n\n\tparseProfile(data: unknown): OAuthProfile {\n\t\tconst user = gitHubUserSchema.parse(data);\n\t\treturn {\n\t\t\tid: String(user.id),\n\t\t\temail: user.email || \"\", // Will be fetched separately if needed\n\t\t\tname: user.name,\n\t\t\tavatarUrl: user.avatar_url,\n\t\t\temailVerified: true, // GitHub verifies emails\n\t\t};\n\t},\n};\n\n/**\n * Fetch the user's primary email from GitHub\n * (needed because email may not be returned in the basic user endpoint)\n */\nexport async function fetchGitHubEmail(accessToken: string): Promise<string> {\n\tconst response = await fetch(\"https://api.github.com/user/emails\", {\n\t\theaders: {\n\t\t\tAuthorization: `Bearer ${accessToken}`,\n\t\t\tAccept: \"application/vnd.github+json\",\n\t\t\t\"X-GitHub-Api-Version\": \"2022-11-28\",\n\t\t},\n\t});\n\n\tif (!response.ok) {\n\t\tthrow new Error(`Failed to fetch GitHub emails: ${response.status}`);\n\t}\n\n\tconst json: unknown = await response.json();\n\tconst emails = z.array(gitHubEmailSchema).parse(json);\n\tconst primary = emails.find((e) => e.primary && e.verified);\n\n\tif (!primary) {\n\t\tthrow new Error(\"No verified primary email found on GitHub account\");\n\t}\n\n\treturn primary.email;\n}\n"],"mappings":";;;;;;AAQA,MAAM,mBAAmB,EAAE,OAAO;CACjC,IAAI,EAAE,QAAQ;CACd,OAAO,EAAE,QAAQ;CACjB,MAAM,EAAE,QAAQ,CAAC,UAAU;CAC3B,OAAO,EAAE,QAAQ,CAAC,UAAU;CAC5B,YAAY,EAAE,QAAQ;CACtB,CAAC;AAEF,MAAM,oBAAoB,EAAE,OAAO;CAClC,OAAO,EAAE,QAAQ;CACjB,SAAS,EAAE,SAAS;CACpB,UAAU,EAAE,SAAS;CACrB,CAAC;AAEF,MAAa,SAAwB;CACpC,MAAM;CACN,cAAc;CACd,UAAU;CACV,aAAa;CACb,QAAQ,CAAC,aAAa,aAAa;CAEnC,aAAa,MAA6B;EACzC,MAAM,OAAO,iBAAiB,MAAM,KAAK;AACzC,SAAO;GACN,IAAI,OAAO,KAAK,GAAG;GACnB,OAAO,KAAK,SAAS;GACrB,MAAM,KAAK;GACX,WAAW,KAAK;GAChB,eAAe;GACf;;CAEF;;;;;AAMD,eAAsB,iBAAiB,aAAsC;CAC5E,MAAM,WAAW,MAAM,MAAM,sCAAsC,EAClE,SAAS;EACR,eAAe,UAAU;EACzB,QAAQ;EACR,wBAAwB;EACxB,EACD,CAAC;AAEF,KAAI,CAAC,SAAS,GACb,OAAM,IAAI,MAAM,kCAAkC,SAAS,SAAS;CAGrE,MAAM,OAAgB,MAAM,SAAS,MAAM;CAE3C,MAAM,UADS,EAAE,MAAM,kBAAkB,CAAC,MAAM,KAAK,CAC9B,MAAM,MAAM,EAAE,WAAW,EAAE,SAAS;AAE3D,KAAI,CAAC,QACJ,OAAM,IAAI,MAAM,oDAAoD;AAGrE,QAAO,QAAQ"}
@@ -0,0 +1,7 @@
1
+ import { r as OAuthProvider } from "../../types-Bu4irX9A.mjs";
2
+
3
+ //#region src/oauth/providers/google.d.ts
4
+ declare const google: OAuthProvider;
5
+ //#endregion
6
+ export { google };
7
+ //# sourceMappingURL=google.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"google.d.mts","names":[],"sources":["../../../src/oauth/providers/google.ts"],"mappings":";;;cAgBa,MAAA,EAAQ,aAAA"}
@@ -0,0 +1,38 @@
1
+ import { z } from "zod";
2
+
3
+ //#region src/oauth/providers/google.ts
4
+ /**
5
+ * Google OAuth provider (using OIDC)
6
+ */
7
+ const googleUserSchema = z.object({
8
+ sub: z.string(),
9
+ email: z.string(),
10
+ email_verified: z.boolean(),
11
+ name: z.string(),
12
+ picture: z.string()
13
+ });
14
+ const google = {
15
+ name: "google",
16
+ authorizeUrl: "https://accounts.google.com/o/oauth2/v2/auth",
17
+ tokenUrl: "https://oauth2.googleapis.com/token",
18
+ userInfoUrl: "https://openidconnect.googleapis.com/v1/userinfo",
19
+ scopes: [
20
+ "openid",
21
+ "email",
22
+ "profile"
23
+ ],
24
+ parseProfile(data) {
25
+ const user = googleUserSchema.parse(data);
26
+ return {
27
+ id: user.sub,
28
+ email: user.email,
29
+ name: user.name,
30
+ avatarUrl: user.picture,
31
+ emailVerified: user.email_verified
32
+ };
33
+ }
34
+ };
35
+
36
+ //#endregion
37
+ export { google };
38
+ //# sourceMappingURL=google.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"google.mjs","names":[],"sources":["../../../src/oauth/providers/google.ts"],"sourcesContent":["/**\n * Google OAuth provider (using OIDC)\n */\n\nimport { z } from \"zod\";\n\nimport type { OAuthProvider, OAuthProfile } from \"../types.js\";\n\nconst googleUserSchema = z.object({\n\tsub: z.string(),\n\temail: z.string(),\n\temail_verified: z.boolean(),\n\tname: z.string(),\n\tpicture: z.string(),\n});\n\nexport const google: OAuthProvider = {\n\tname: \"google\",\n\tauthorizeUrl: \"https://accounts.google.com/o/oauth2/v2/auth\",\n\ttokenUrl: \"https://oauth2.googleapis.com/token\",\n\tuserInfoUrl: \"https://openidconnect.googleapis.com/v1/userinfo\",\n\tscopes: [\"openid\", \"email\", \"profile\"],\n\n\tparseProfile(data: unknown): OAuthProfile {\n\t\tconst user = googleUserSchema.parse(data);\n\t\treturn {\n\t\t\tid: user.sub,\n\t\t\temail: user.email,\n\t\t\tname: user.name,\n\t\t\tavatarUrl: user.picture,\n\t\t\temailVerified: user.email_verified,\n\t\t};\n\t},\n};\n"],"mappings":";;;;;;AAQA,MAAM,mBAAmB,EAAE,OAAO;CACjC,KAAK,EAAE,QAAQ;CACf,OAAO,EAAE,QAAQ;CACjB,gBAAgB,EAAE,SAAS;CAC3B,MAAM,EAAE,QAAQ;CAChB,SAAS,EAAE,QAAQ;CACnB,CAAC;AAEF,MAAa,SAAwB;CACpC,MAAM;CACN,cAAc;CACd,UAAU;CACV,aAAa;CACb,QAAQ;EAAC;EAAU;EAAS;EAAU;CAEtC,aAAa,MAA6B;EACzC,MAAM,OAAO,iBAAiB,MAAM,KAAK;AACzC,SAAO;GACN,IAAI,KAAK;GACT,OAAO,KAAK;GACZ,MAAM,KAAK;GACX,WAAW,KAAK;GAChB,eAAe,KAAK;GACpB;;CAEF"}
@@ -0,0 +1,2 @@
1
+ import { a as registerPasskey, c as AuthenticationResponse, d as PasskeyConfig, f as RegistrationOptions, h as VerifiedRegistration, i as generateRegistrationOptions, l as ChallengeData, m as VerifiedAuthentication, n as generateAuthenticationOptions, o as verifyRegistrationResponse, p as RegistrationResponse, r as verifyAuthenticationResponse, s as AuthenticationOptions, t as authenticateWithPasskey, u as ChallengeStore } from "../authenticate-D5UgaoTH.mjs";
2
+ export { type AuthenticationOptions, type AuthenticationResponse, type ChallengeData, type ChallengeStore, type PasskeyConfig, type RegistrationOptions, type RegistrationResponse, type VerifiedAuthentication, type VerifiedRegistration, authenticateWithPasskey, generateAuthenticationOptions, generateRegistrationOptions, registerPasskey, verifyAuthenticationResponse, verifyRegistrationResponse };
@@ -0,0 +1,3 @@
1
+ import { a as registerPasskey, i as generateRegistrationOptions, n as generateAuthenticationOptions, o as verifyRegistrationResponse, r as verifyAuthenticationResponse, t as authenticateWithPasskey } from "../authenticate-j5GayLXB.mjs";
2
+
3
+ export { authenticateWithPasskey, generateAuthenticationOptions, generateRegistrationOptions, registerPasskey, verifyAuthenticationResponse, verifyRegistrationResponse };
@@ -0,0 +1,35 @@
1
+ //#region src/oauth/types.d.ts
2
+ /**
3
+ * OAuth types
4
+ */
5
+ interface OAuthProfile {
6
+ id: string;
7
+ email: string;
8
+ name: string | null;
9
+ avatarUrl: string | null;
10
+ emailVerified: boolean;
11
+ }
12
+ interface OAuthProvider {
13
+ name: string;
14
+ authorizeUrl: string;
15
+ tokenUrl: string;
16
+ userInfoUrl?: string;
17
+ scopes: string[];
18
+ /**
19
+ * Parse the user profile from the provider's response
20
+ */
21
+ parseProfile(data: unknown): OAuthProfile;
22
+ }
23
+ interface OAuthConfig {
24
+ clientId: string;
25
+ clientSecret: string;
26
+ }
27
+ interface OAuthState {
28
+ provider: string;
29
+ redirectUri: string;
30
+ codeVerifier?: string;
31
+ nonce?: string;
32
+ }
33
+ //#endregion
34
+ export { OAuthState as i, OAuthProfile as n, OAuthProvider as r, OAuthConfig as t };
35
+ //# sourceMappingURL=types-Bu4irX9A.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types-Bu4irX9A.d.mts","names":[],"sources":["../src/oauth/types.ts"],"mappings":";;AAIA;;UAAiB,YAAA;EAChB,EAAA;EACA,KAAA;EACA,IAAA;EACA,SAAA;EACA,aAAA;AAAA;AAAA,UAGgB,aAAA;EAChB,IAAA;EACA,YAAA;EACA,QAAA;EACA,WAAA;EACA,MAAA;EAJA;;;EASA,YAAA,CAAa,IAAA,YAAgB,YAAA;AAAA;AAAA,UAGb,WAAA;EAChB,QAAA;EACA,YAAA;AAAA;AAAA,UAGgB,UAAA;EAChB,QAAA;EACA,WAAA;EACA,YAAA;EACA,KAAA;AAAA"}
@@ -0,0 +1,60 @@
1
+ //#region src/types.ts
2
+ /**
3
+ * Core types for @emdashcms/auth
4
+ */
5
+ const Role = {
6
+ SUBSCRIBER: 10,
7
+ CONTRIBUTOR: 20,
8
+ AUTHOR: 30,
9
+ EDITOR: 40,
10
+ ADMIN: 50
11
+ };
12
+ function roleFromLevel(level) {
13
+ const entry = Object.entries(Role).find(([, v]) => v === level);
14
+ if (!entry) return void 0;
15
+ const name = entry[0];
16
+ if (isRoleName(name)) return name;
17
+ }
18
+ function isRoleName(value) {
19
+ return value in Role;
20
+ }
21
+ const ROLE_LEVEL_MAP = new Map(Object.values(Role).map((v) => [v, v]));
22
+ function toRoleLevel(value) {
23
+ const level = ROLE_LEVEL_MAP.get(value);
24
+ if (level !== void 0) return level;
25
+ throw new Error(`Invalid role level: ${value}`);
26
+ }
27
+ const DEVICE_TYPE_MAP = {
28
+ singleDevice: "singleDevice",
29
+ multiDevice: "multiDevice"
30
+ };
31
+ function toDeviceType(value) {
32
+ const dt = DEVICE_TYPE_MAP[value];
33
+ if (dt !== void 0) return dt;
34
+ throw new Error(`Invalid device type: ${value}`);
35
+ }
36
+ const TOKEN_TYPE_MAP = {
37
+ magic_link: "magic_link",
38
+ email_verify: "email_verify",
39
+ invite: "invite",
40
+ recovery: "recovery"
41
+ };
42
+ function toTokenType(value) {
43
+ const tt = TOKEN_TYPE_MAP[value];
44
+ if (tt !== void 0) return tt;
45
+ throw new Error(`Invalid token type: ${value}`);
46
+ }
47
+ function roleToLevel(name) {
48
+ return Role[name];
49
+ }
50
+ var AuthError = class extends Error {
51
+ constructor(code, message) {
52
+ super(message ?? code);
53
+ this.code = code;
54
+ this.name = "AuthError";
55
+ }
56
+ };
57
+
58
+ //#endregion
59
+ export { toDeviceType as a, roleToLevel as i, Role as n, toRoleLevel as o, roleFromLevel as r, toTokenType as s, AuthError as t };
60
+ //# sourceMappingURL=types-CiSNpRI9.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types-CiSNpRI9.mjs","names":[],"sources":["../src/types.ts"],"sourcesContent":["/**\n * Core types for @emdashcms/auth\n */\n\n// ============================================================================\n// Roles & Permissions\n// ============================================================================\n\nexport const Role = {\n\tSUBSCRIBER: 10,\n\tCONTRIBUTOR: 20,\n\tAUTHOR: 30,\n\tEDITOR: 40,\n\tADMIN: 50,\n} as const;\n\nexport type RoleLevel = (typeof Role)[keyof typeof Role];\nexport type RoleName = keyof typeof Role;\n\nexport function roleFromLevel(level: number): RoleName | undefined {\n\tconst entry = Object.entries(Role).find(([, v]) => v === level);\n\tif (!entry) return undefined;\n\tconst name = entry[0];\n\tif (isRoleName(name)) return name;\n\treturn undefined;\n}\n\nfunction isRoleName(value: string): value is RoleName {\n\treturn value in Role;\n}\n\nconst ROLE_LEVEL_MAP = new Map<number, RoleLevel>(Object.values(Role).map((v) => [v, v]));\n\nexport function toRoleLevel(value: number): RoleLevel {\n\tconst level = ROLE_LEVEL_MAP.get(value);\n\tif (level !== undefined) return level;\n\tthrow new Error(`Invalid role level: ${value}`);\n}\n\nconst DEVICE_TYPE_MAP: Record<string, DeviceType | undefined> = {\n\tsingleDevice: \"singleDevice\",\n\tmultiDevice: \"multiDevice\",\n};\n\nexport function toDeviceType(value: string): DeviceType {\n\tconst dt = DEVICE_TYPE_MAP[value];\n\tif (dt !== undefined) return dt;\n\tthrow new Error(`Invalid device type: ${value}`);\n}\n\nconst TOKEN_TYPE_MAP: Record<string, TokenType | undefined> = {\n\tmagic_link: \"magic_link\",\n\temail_verify: \"email_verify\",\n\tinvite: \"invite\",\n\trecovery: \"recovery\",\n};\n\nexport function toTokenType(value: string): TokenType {\n\tconst tt = TOKEN_TYPE_MAP[value];\n\tif (tt !== undefined) return tt;\n\tthrow new Error(`Invalid token type: ${value}`);\n}\n\nexport function roleToLevel(name: RoleName): RoleLevel {\n\treturn Role[name];\n}\n\n// ============================================================================\n// User\n// ============================================================================\n\nexport interface User {\n\tid: string;\n\temail: string;\n\tname: string | null;\n\tavatarUrl: string | null;\n\trole: RoleLevel;\n\temailVerified: boolean;\n\tdisabled: boolean;\n\tdata: Record<string, unknown> | null;\n\tcreatedAt: Date;\n\tupdatedAt: Date;\n}\n\nexport interface NewUser {\n\temail: string;\n\tname?: string | null;\n\tavatarUrl?: string | null;\n\trole?: RoleLevel;\n\temailVerified?: boolean;\n\tdata?: Record<string, unknown> | null;\n}\n\nexport interface UpdateUser {\n\temail?: string;\n\tname?: string | null;\n\tavatarUrl?: string | null;\n\trole?: RoleLevel;\n\temailVerified?: boolean;\n\tdisabled?: boolean;\n\tdata?: Record<string, unknown> | null;\n}\n\n// ============================================================================\n// Credentials (Passkeys)\n// ============================================================================\n\nexport type AuthenticatorTransport = \"usb\" | \"nfc\" | \"ble\" | \"internal\" | \"hybrid\";\nexport type DeviceType = \"singleDevice\" | \"multiDevice\";\n\nexport interface Credential {\n\tid: string; // Base64url credential ID\n\tuserId: string;\n\tpublicKey: Uint8Array; // COSE public key\n\tcounter: number;\n\tdeviceType: DeviceType;\n\tbackedUp: boolean;\n\ttransports: AuthenticatorTransport[];\n\tname: string | null;\n\tcreatedAt: Date;\n\tlastUsedAt: Date;\n}\n\nexport interface NewCredential {\n\tid: string;\n\tuserId: string;\n\tpublicKey: Uint8Array;\n\tcounter: number;\n\tdeviceType: DeviceType;\n\tbackedUp: boolean;\n\ttransports: AuthenticatorTransport[];\n\tname?: string | null;\n}\n\n// ============================================================================\n// Sessions\n// ============================================================================\n\nexport interface Session {\n\tid: string;\n\tuserId: string;\n\texpiresAt: Date;\n\tipAddress: string | null;\n\tuserAgent: string | null;\n\tcreatedAt: Date;\n}\n\nexport interface SessionData {\n\tuserId: string;\n\texpiresAt: number; // Unix timestamp\n}\n\n// ============================================================================\n// Auth Tokens (magic links, invites, etc.)\n// ============================================================================\n\nexport type TokenType = \"magic_link\" | \"email_verify\" | \"invite\" | \"recovery\";\n\nexport interface AuthToken {\n\thash: string; // SHA-256 hash of the raw token\n\tuserId: string | null; // null for pre-user tokens (invite/signup)\n\temail: string | null; // For pre-user tokens\n\ttype: TokenType;\n\trole: RoleLevel | null; // For invites\n\tinvitedBy: string | null;\n\texpiresAt: Date;\n\tcreatedAt: Date;\n}\n\nexport interface NewAuthToken {\n\thash: string;\n\tuserId?: string | null;\n\temail?: string | null;\n\ttype: TokenType;\n\trole?: RoleLevel | null;\n\tinvitedBy?: string | null;\n\texpiresAt: Date;\n}\n\n// ============================================================================\n// OAuth Accounts\n// ============================================================================\n\nexport interface OAuthAccount {\n\tprovider: string;\n\tproviderAccountId: string;\n\tuserId: string;\n\tcreatedAt: Date;\n}\n\nexport interface NewOAuthAccount {\n\tprovider: string;\n\tproviderAccountId: string;\n\tuserId: string;\n}\n\n// ============================================================================\n// OAuth Connections (SSO config)\n// ============================================================================\n\nexport interface OAuthConnection {\n\tid: string;\n\tname: string;\n\tprovider: \"oidc\" | \"github\" | \"google\";\n\tclientId: string;\n\tclientSecretEnc: string; // Encrypted\n\tissuerUrl: string | null;\n\tconfig: Record<string, unknown> | null;\n\tenabled: boolean;\n\tcreatedAt: Date;\n}\n\n// ============================================================================\n// OAuth Clients (when EmDash is provider)\n// ============================================================================\n\nexport interface OAuthClient {\n\tid: string;\n\tname: string;\n\tsecretHash: string;\n\tredirectUris: string[];\n\tscopes: string[];\n\tcreatedAt: Date;\n}\n\n// ============================================================================\n// Allowed Domains (self-signup)\n// ============================================================================\n\nexport interface AllowedDomain {\n\tdomain: string;\n\tdefaultRole: RoleLevel;\n\tenabled: boolean;\n\tcreatedAt: Date;\n}\n\n// ============================================================================\n// User Listing Types (for admin UI)\n// ============================================================================\n\n/** Extended user with list view computed fields */\nexport interface UserListItem extends User {\n\tlastLogin: Date | null;\n\tcredentialCount: number;\n\toauthProviders: string[];\n}\n\n/** User with full details including related data */\nexport interface UserWithDetails {\n\tuser: User;\n\tcredentials: Credential[];\n\toauthAccounts: OAuthAccount[];\n\tlastLogin: Date | null;\n}\n\n// ============================================================================\n// Auth Adapter Interface\n// ============================================================================\n\nexport interface AuthAdapter {\n\t// Users\n\tgetUserById(id: string): Promise<User | null>;\n\tgetUserByEmail(email: string): Promise<User | null>;\n\tcreateUser(user: NewUser): Promise<User>;\n\tupdateUser(id: string, data: UpdateUser): Promise<void>;\n\tdeleteUser(id: string): Promise<void>;\n\tcountUsers(): Promise<number>;\n\n\t// User listing and details (for admin)\n\tgetUsers(options?: {\n\t\tsearch?: string;\n\t\trole?: number;\n\t\tcursor?: string;\n\t\tlimit?: number;\n\t}): Promise<{ items: UserListItem[]; nextCursor?: string }>;\n\tgetUserWithDetails(id: string): Promise<UserWithDetails | null>;\n\tcountAdmins(): Promise<number>;\n\n\t// Credentials\n\tgetCredentialById(id: string): Promise<Credential | null>;\n\tgetCredentialsByUserId(userId: string): Promise<Credential[]>;\n\tcreateCredential(credential: NewCredential): Promise<Credential>;\n\tupdateCredentialCounter(id: string, counter: number): Promise<void>;\n\tupdateCredentialName(id: string, name: string | null): Promise<void>;\n\tdeleteCredential(id: string): Promise<void>;\n\tcountCredentialsByUserId(userId: string): Promise<number>;\n\n\t// Auth Tokens\n\tcreateToken(token: NewAuthToken): Promise<void>;\n\tgetToken(hash: string, type: TokenType): Promise<AuthToken | null>;\n\tdeleteToken(hash: string): Promise<void>;\n\tdeleteExpiredTokens(): Promise<void>;\n\n\t// OAuth Accounts\n\tgetOAuthAccount(provider: string, providerAccountId: string): Promise<OAuthAccount | null>;\n\tgetOAuthAccountsByUserId(userId: string): Promise<OAuthAccount[]>;\n\tcreateOAuthAccount(account: NewOAuthAccount): Promise<OAuthAccount>;\n\tdeleteOAuthAccount(provider: string, providerAccountId: string): Promise<void>;\n\n\t// Allowed Domains\n\tgetAllowedDomain(domain: string): Promise<AllowedDomain | null>;\n\tgetAllowedDomains(): Promise<AllowedDomain[]>;\n\tcreateAllowedDomain(domain: string, defaultRole: RoleLevel): Promise<AllowedDomain>;\n\tupdateAllowedDomain(domain: string, enabled: boolean, defaultRole?: RoleLevel): Promise<void>;\n\tdeleteAllowedDomain(domain: string): Promise<void>;\n}\n\n// ============================================================================\n// Email Adapter Interface\n// ============================================================================\n\nexport interface EmailMessage {\n\tto: string;\n\tsubject: string;\n\ttext: string;\n\thtml?: string;\n}\n\nexport interface EmailAdapter {\n\tsend(message: EmailMessage): Promise<void>;\n}\n\n// ============================================================================\n// Auth Errors\n// ============================================================================\n\nexport class AuthError extends Error {\n\tconstructor(\n\t\tpublic code: AuthErrorCode,\n\t\tmessage?: string,\n\t) {\n\t\tsuper(message ?? code);\n\t\tthis.name = \"AuthError\";\n\t}\n}\n\nexport type AuthErrorCode =\n\t| \"invalid_credentials\"\n\t| \"invalid_token\"\n\t| \"token_expired\"\n\t| \"user_not_found\"\n\t| \"user_exists\"\n\t| \"credential_exists\"\n\t| \"max_credentials\"\n\t| \"email_not_verified\"\n\t| \"signup_not_allowed\"\n\t| \"domain_not_allowed\"\n\t| \"forbidden\"\n\t| \"unauthorized\"\n\t| \"rate_limited\"\n\t| \"invalid_request\"\n\t| \"internal_error\";\n"],"mappings":";;;;AAQA,MAAa,OAAO;CACnB,YAAY;CACZ,aAAa;CACb,QAAQ;CACR,QAAQ;CACR,OAAO;CACP;AAKD,SAAgB,cAAc,OAAqC;CAClE,MAAM,QAAQ,OAAO,QAAQ,KAAK,CAAC,MAAM,GAAG,OAAO,MAAM,MAAM;AAC/D,KAAI,CAAC,MAAO,QAAO;CACnB,MAAM,OAAO,MAAM;AACnB,KAAI,WAAW,KAAK,CAAE,QAAO;;AAI9B,SAAS,WAAW,OAAkC;AACrD,QAAO,SAAS;;AAGjB,MAAM,iBAAiB,IAAI,IAAuB,OAAO,OAAO,KAAK,CAAC,KAAK,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC;AAEzF,SAAgB,YAAY,OAA0B;CACrD,MAAM,QAAQ,eAAe,IAAI,MAAM;AACvC,KAAI,UAAU,OAAW,QAAO;AAChC,OAAM,IAAI,MAAM,uBAAuB,QAAQ;;AAGhD,MAAM,kBAA0D;CAC/D,cAAc;CACd,aAAa;CACb;AAED,SAAgB,aAAa,OAA2B;CACvD,MAAM,KAAK,gBAAgB;AAC3B,KAAI,OAAO,OAAW,QAAO;AAC7B,OAAM,IAAI,MAAM,wBAAwB,QAAQ;;AAGjD,MAAM,iBAAwD;CAC7D,YAAY;CACZ,cAAc;CACd,QAAQ;CACR,UAAU;CACV;AAED,SAAgB,YAAY,OAA0B;CACrD,MAAM,KAAK,eAAe;AAC1B,KAAI,OAAO,OAAW,QAAO;AAC7B,OAAM,IAAI,MAAM,uBAAuB,QAAQ;;AAGhD,SAAgB,YAAY,MAA2B;AACtD,QAAO,KAAK;;AAsQb,IAAa,YAAb,cAA+B,MAAM;CACpC,YACC,AAAO,MACP,SACC;AACD,QAAM,WAAW,KAAK;EAHf;AAIP,OAAK,OAAO"}
@@ -0,0 +1,208 @@
1
+ //#region src/types.d.ts
2
+ /**
3
+ * Core types for @emdashcms/auth
4
+ */
5
+ declare const Role: {
6
+ readonly SUBSCRIBER: 10;
7
+ readonly CONTRIBUTOR: 20;
8
+ readonly AUTHOR: 30;
9
+ readonly EDITOR: 40;
10
+ readonly ADMIN: 50;
11
+ };
12
+ type RoleLevel = (typeof Role)[keyof typeof Role];
13
+ type RoleName = keyof typeof Role;
14
+ declare function roleFromLevel(level: number): RoleName | undefined;
15
+ declare function toRoleLevel(value: number): RoleLevel;
16
+ declare function toDeviceType(value: string): DeviceType;
17
+ declare function toTokenType(value: string): TokenType;
18
+ declare function roleToLevel(name: RoleName): RoleLevel;
19
+ interface User {
20
+ id: string;
21
+ email: string;
22
+ name: string | null;
23
+ avatarUrl: string | null;
24
+ role: RoleLevel;
25
+ emailVerified: boolean;
26
+ disabled: boolean;
27
+ data: Record<string, unknown> | null;
28
+ createdAt: Date;
29
+ updatedAt: Date;
30
+ }
31
+ interface NewUser {
32
+ email: string;
33
+ name?: string | null;
34
+ avatarUrl?: string | null;
35
+ role?: RoleLevel;
36
+ emailVerified?: boolean;
37
+ data?: Record<string, unknown> | null;
38
+ }
39
+ interface UpdateUser {
40
+ email?: string;
41
+ name?: string | null;
42
+ avatarUrl?: string | null;
43
+ role?: RoleLevel;
44
+ emailVerified?: boolean;
45
+ disabled?: boolean;
46
+ data?: Record<string, unknown> | null;
47
+ }
48
+ type AuthenticatorTransport = "usb" | "nfc" | "ble" | "internal" | "hybrid";
49
+ type DeviceType = "singleDevice" | "multiDevice";
50
+ interface Credential {
51
+ id: string;
52
+ userId: string;
53
+ publicKey: Uint8Array;
54
+ counter: number;
55
+ deviceType: DeviceType;
56
+ backedUp: boolean;
57
+ transports: AuthenticatorTransport[];
58
+ name: string | null;
59
+ createdAt: Date;
60
+ lastUsedAt: Date;
61
+ }
62
+ interface NewCredential {
63
+ id: string;
64
+ userId: string;
65
+ publicKey: Uint8Array;
66
+ counter: number;
67
+ deviceType: DeviceType;
68
+ backedUp: boolean;
69
+ transports: AuthenticatorTransport[];
70
+ name?: string | null;
71
+ }
72
+ interface Session {
73
+ id: string;
74
+ userId: string;
75
+ expiresAt: Date;
76
+ ipAddress: string | null;
77
+ userAgent: string | null;
78
+ createdAt: Date;
79
+ }
80
+ interface SessionData {
81
+ userId: string;
82
+ expiresAt: number;
83
+ }
84
+ type TokenType = "magic_link" | "email_verify" | "invite" | "recovery";
85
+ interface AuthToken {
86
+ hash: string;
87
+ userId: string | null;
88
+ email: string | null;
89
+ type: TokenType;
90
+ role: RoleLevel | null;
91
+ invitedBy: string | null;
92
+ expiresAt: Date;
93
+ createdAt: Date;
94
+ }
95
+ interface NewAuthToken {
96
+ hash: string;
97
+ userId?: string | null;
98
+ email?: string | null;
99
+ type: TokenType;
100
+ role?: RoleLevel | null;
101
+ invitedBy?: string | null;
102
+ expiresAt: Date;
103
+ }
104
+ interface OAuthAccount {
105
+ provider: string;
106
+ providerAccountId: string;
107
+ userId: string;
108
+ createdAt: Date;
109
+ }
110
+ interface NewOAuthAccount {
111
+ provider: string;
112
+ providerAccountId: string;
113
+ userId: string;
114
+ }
115
+ interface OAuthConnection {
116
+ id: string;
117
+ name: string;
118
+ provider: "oidc" | "github" | "google";
119
+ clientId: string;
120
+ clientSecretEnc: string;
121
+ issuerUrl: string | null;
122
+ config: Record<string, unknown> | null;
123
+ enabled: boolean;
124
+ createdAt: Date;
125
+ }
126
+ interface OAuthClient {
127
+ id: string;
128
+ name: string;
129
+ secretHash: string;
130
+ redirectUris: string[];
131
+ scopes: string[];
132
+ createdAt: Date;
133
+ }
134
+ interface AllowedDomain {
135
+ domain: string;
136
+ defaultRole: RoleLevel;
137
+ enabled: boolean;
138
+ createdAt: Date;
139
+ }
140
+ /** Extended user with list view computed fields */
141
+ interface UserListItem extends User {
142
+ lastLogin: Date | null;
143
+ credentialCount: number;
144
+ oauthProviders: string[];
145
+ }
146
+ /** User with full details including related data */
147
+ interface UserWithDetails {
148
+ user: User;
149
+ credentials: Credential[];
150
+ oauthAccounts: OAuthAccount[];
151
+ lastLogin: Date | null;
152
+ }
153
+ interface AuthAdapter {
154
+ getUserById(id: string): Promise<User | null>;
155
+ getUserByEmail(email: string): Promise<User | null>;
156
+ createUser(user: NewUser): Promise<User>;
157
+ updateUser(id: string, data: UpdateUser): Promise<void>;
158
+ deleteUser(id: string): Promise<void>;
159
+ countUsers(): Promise<number>;
160
+ getUsers(options?: {
161
+ search?: string;
162
+ role?: number;
163
+ cursor?: string;
164
+ limit?: number;
165
+ }): Promise<{
166
+ items: UserListItem[];
167
+ nextCursor?: string;
168
+ }>;
169
+ getUserWithDetails(id: string): Promise<UserWithDetails | null>;
170
+ countAdmins(): Promise<number>;
171
+ getCredentialById(id: string): Promise<Credential | null>;
172
+ getCredentialsByUserId(userId: string): Promise<Credential[]>;
173
+ createCredential(credential: NewCredential): Promise<Credential>;
174
+ updateCredentialCounter(id: string, counter: number): Promise<void>;
175
+ updateCredentialName(id: string, name: string | null): Promise<void>;
176
+ deleteCredential(id: string): Promise<void>;
177
+ countCredentialsByUserId(userId: string): Promise<number>;
178
+ createToken(token: NewAuthToken): Promise<void>;
179
+ getToken(hash: string, type: TokenType): Promise<AuthToken | null>;
180
+ deleteToken(hash: string): Promise<void>;
181
+ deleteExpiredTokens(): Promise<void>;
182
+ getOAuthAccount(provider: string, providerAccountId: string): Promise<OAuthAccount | null>;
183
+ getOAuthAccountsByUserId(userId: string): Promise<OAuthAccount[]>;
184
+ createOAuthAccount(account: NewOAuthAccount): Promise<OAuthAccount>;
185
+ deleteOAuthAccount(provider: string, providerAccountId: string): Promise<void>;
186
+ getAllowedDomain(domain: string): Promise<AllowedDomain | null>;
187
+ getAllowedDomains(): Promise<AllowedDomain[]>;
188
+ createAllowedDomain(domain: string, defaultRole: RoleLevel): Promise<AllowedDomain>;
189
+ updateAllowedDomain(domain: string, enabled: boolean, defaultRole?: RoleLevel): Promise<void>;
190
+ deleteAllowedDomain(domain: string): Promise<void>;
191
+ }
192
+ interface EmailMessage {
193
+ to: string;
194
+ subject: string;
195
+ text: string;
196
+ html?: string;
197
+ }
198
+ interface EmailAdapter {
199
+ send(message: EmailMessage): Promise<void>;
200
+ }
201
+ declare class AuthError extends Error {
202
+ code: AuthErrorCode;
203
+ constructor(code: AuthErrorCode, message?: string);
204
+ }
205
+ type AuthErrorCode = "invalid_credentials" | "invalid_token" | "token_expired" | "user_not_found" | "user_exists" | "credential_exists" | "max_credentials" | "email_not_verified" | "signup_not_allowed" | "domain_not_allowed" | "forbidden" | "unauthorized" | "rate_limited" | "invalid_request" | "internal_error";
206
+ //#endregion
207
+ export { toDeviceType as A, TokenType as C, UserWithDetails as D, UserListItem as E, toTokenType as M, roleFromLevel as O, SessionData as S, User as T, OAuthConnection as _, AuthToken as a, RoleName as b, DeviceType as c, NewAuthToken as d, NewCredential as f, OAuthClient as g, OAuthAccount as h, AuthErrorCode as i, toRoleLevel as j, roleToLevel as k, EmailAdapter as l, NewUser as m, AuthAdapter as n, AuthenticatorTransport as o, NewOAuthAccount as p, AuthError as r, Credential as s, AllowedDomain as t, EmailMessage as u, Role as v, UpdateUser as w, Session as x, RoleLevel as y };
208
+ //# sourceMappingURL=types-HtRc90Wi.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types-HtRc90Wi.d.mts","names":[],"sources":["../src/types.ts"],"mappings":"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"}