@happyvertical/smrt-profiles 0.40.69 → 0.41.0

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/AGENTS.md +10 -5
  2. package/dist/chunks/{ApiKey-Br9NGBnr.js → ApiKey-CMm509C4.js} +5 -7
  3. package/dist/chunks/ApiKey-CMm509C4.js.map +1 -0
  4. package/dist/chunks/{ApiKeyCollection-CrTjdMz8.js → ApiKeyCollection-C6p7XgTz.js} +3 -3
  5. package/dist/chunks/{ApiKeyCollection-CrTjdMz8.js.map → ApiKeyCollection-C6p7XgTz.js.map} +1 -1
  6. package/dist/chunks/{AuditLogCollection-BBIEI6BD.js → AuditLogCollection-lJfGFdIQ.js} +2 -2
  7. package/dist/chunks/{AuditLogCollection-BBIEI6BD.js.map → AuditLogCollection-lJfGFdIQ.js.map} +1 -1
  8. package/dist/chunks/{NostrIdentity-DF_a_WZk.js → NostrIdentity-CgdM4ng0.js} +6 -8
  9. package/dist/chunks/{NostrIdentity-DF_a_WZk.js.map → NostrIdentity-CgdM4ng0.js.map} +1 -1
  10. package/dist/chunks/{NostrIdentityCollection-BRYUgt47.js → NostrIdentityCollection-BnHZOsUI.js} +2 -2
  11. package/dist/chunks/{NostrIdentityCollection-BRYUgt47.js.map → NostrIdentityCollection-BnHZOsUI.js.map} +1 -1
  12. package/dist/chunks/{OidcIdentity-CFbUlB_Z.js → OidcIdentity-DANnAbfU.js} +3 -3
  13. package/dist/chunks/{OidcIdentity-CFbUlB_Z.js.map → OidcIdentity-DANnAbfU.js.map} +1 -1
  14. package/dist/chunks/{OidcIdentityCollection-BLxSaqJT.js → OidcIdentityCollection-eSM4gzHL.js} +3 -3
  15. package/dist/chunks/{OidcIdentityCollection-BLxSaqJT.js.map → OidcIdentityCollection-eSM4gzHL.js.map} +1 -1
  16. package/dist/chunks/{ProfileAssetCollection-FhW8q7_1.js → ProfileAssetCollection-A_PpEaDq.js} +3 -3
  17. package/dist/chunks/{ProfileAssetCollection-FhW8q7_1.js.map → ProfileAssetCollection-A_PpEaDq.js.map} +1 -1
  18. package/dist/chunks/{ProfileCollection-DdJ3W4GG.js → ProfileCollection-BsKhR5Dp.js} +27 -35
  19. package/dist/chunks/{ProfileCollection-DdJ3W4GG.js.map → ProfileCollection-BsKhR5Dp.js.map} +1 -1
  20. package/dist/chunks/{ProfileMetadataCollection-loKVv_pj.js → ProfileMetadataCollection-Ceq-QV-1.js} +2 -2
  21. package/dist/chunks/{ProfileMetadataCollection-loKVv_pj.js.map → ProfileMetadataCollection-Ceq-QV-1.js.map} +1 -1
  22. package/dist/chunks/{ProfileMetafieldCollection-CACMY1hl.js → ProfileMetafieldCollection-CM374Dbh.js} +2 -2
  23. package/dist/chunks/{ProfileMetafieldCollection-CACMY1hl.js.map → ProfileMetafieldCollection-CM374Dbh.js.map} +1 -1
  24. package/dist/chunks/{ProfileRelationshipCollection-CzQKfvMy.js → ProfileRelationshipCollection-DuPcpzp1.js} +5 -5
  25. package/dist/chunks/{ProfileRelationshipCollection-CzQKfvMy.js.map → ProfileRelationshipCollection-DuPcpzp1.js.map} +1 -1
  26. package/dist/chunks/{ProfileRelationshipTermCollection-DzmU48P6.js → ProfileRelationshipTermCollection-Bw32vYB8.js} +2 -2
  27. package/dist/chunks/{ProfileRelationshipTermCollection-DzmU48P6.js.map → ProfileRelationshipTermCollection-Bw32vYB8.js.map} +1 -1
  28. package/dist/chunks/{ProfileRelationshipType-BjoaCt-u.js → ProfileRelationshipType-CZm2X1qY.js} +2 -2
  29. package/dist/chunks/{ProfileRelationshipType-BjoaCt-u.js.map → ProfileRelationshipType-CZm2X1qY.js.map} +1 -1
  30. package/dist/chunks/{ProfileRelationshipTypeCollection-Bh4xJ4-I.js → ProfileRelationshipTypeCollection-CtoeCpp_.js} +3 -3
  31. package/dist/chunks/{ProfileRelationshipTypeCollection-Bh4xJ4-I.js.map → ProfileRelationshipTypeCollection-CtoeCpp_.js.map} +1 -1
  32. package/dist/chunks/{oidcProvisioningPrimitives-C9XSQNZj.js → oidcProvisioningPrimitives-Bzv4VTcn.js} +23 -8
  33. package/dist/chunks/oidcProvisioningPrimitives-Bzv4VTcn.js.map +1 -0
  34. package/dist/chunks/{resolveIdentity-BpkxPkz7.js → resolveIdentity-DusCiNZo.js} +17 -17
  35. package/dist/chunks/{resolveIdentity-BpkxPkz7.js.map → resolveIdentity-DusCiNZo.js.map} +1 -1
  36. package/dist/index.d.ts +12 -3
  37. package/dist/index.js +24 -26
  38. package/dist/index.js.map +1 -1
  39. package/dist/internal/oidc-provisioning.js +2 -2
  40. package/dist/manifest.json +399 -215
  41. package/dist/oidc-provisioning.d.ts +13 -1
  42. package/dist/smrt-knowledge.json +40 -5
  43. package/dist/types.d.ts +8 -2
  44. package/dist/utils.d.ts +8 -2
  45. package/dist/utils.js +4 -4
  46. package/package.json +6 -6
  47. package/dist/chunks/ApiKey-Br9NGBnr.js.map +0 -1
  48. package/dist/chunks/oidcProvisioningPrimitives-C9XSQNZj.js.map +0 -1
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/__smrt-register__.ts","../src/models/MagicLinkToken.ts","../src/collections/MagicLinkTokenCollection.ts","../src/auth/magicLinkService.ts","../src/auth/nip05Handler.ts"],"sourcesContent":["/**\n * Self-registers this package's build-time manifest before any @smrt() decorator\n * in the package fires. Fixes issue #1132: in consumer runtimes (tsx, SvelteKit\n * SSR, plain `vite dev`) the decorator's synchronous manifest lookup previously\n * missed because no step populated the global manifest cache — classes got\n * registered with zero fields and `save()` / `toJSON()` silently dropped every\n * declared property.\n *\n * Import this module as the first statement in `src/index.ts` so its top-level\n * side effect runs ahead of any class module's @smrt() decorator.\n *\n * Silent no-op in dev/test, where the vitest plugin already populates manifests\n * via a different path. Only needs to succeed in the published dist output.\n *\n * @see https://github.com/happyvertical/smrt/issues/1132\n */\nimport { ObjectRegistry } from '@happyvertical/smrt-core';\n\n// During library builds, smrtPlugin replaces this entire module with generated\n// code that embeds the scanned manifest inline (#1506/#1507) — published dists\n// never resolve this URL, so downstream bundlers cannot break registration by\n// relocating the compiled module away from dist/manifest.json. The runtime\n// lookup below is the fallback for source-mode runs without that transform.\nObjectRegistry.registerPackageManifest(\n new URL('./manifest.json', import.meta.url),\n);\n","/**\n * MagicLinkToken - Temporary tokens for email verification\n *\n * Stores hashed magic link tokens with expiration. Tokens are single-use\n * and automatically invalidated after verification or expiration.\n */\n\nimport { createHash, randomBytes } from 'node:crypto';\nimport {\n foreignKey,\n SmrtObject,\n type SmrtObjectOptions,\n smrt,\n} from '@happyvertical/smrt-core';\nimport type { NostrIdentity } from './NostrIdentity';\n\nexport interface MagicLinkTokenOptions extends SmrtObjectOptions {\n nostrIdentityId?: string;\n tokenHash?: string;\n email?: string;\n expiresAt?: Date;\n usedAt?: Date | null;\n requestedFromIp?: string;\n createdAt?: Date;\n}\n\nexport interface GenerateTokenResult {\n /** The plaintext token (only available at creation time) */\n token: string;\n /** The MagicLinkToken record */\n magicLinkToken: MagicLinkToken;\n}\n\n/** Default token expiration: 15 minutes */\nconst DEFAULT_EXPIRATION_MINUTES = 15;\n\n@smrt({\n tableName: 'magic_link_tokens',\n api: { exclude: ['*'] }, // No API access - internal only\n mcp: { exclude: ['*'] },\n cli: { include: ['list'] }, // Admin visibility only\n})\nexport class MagicLinkToken extends SmrtObject {\n /**\n * Link to the NostrIdentity this token is for\n */\n @foreignKey('NostrIdentity', { required: true })\n nostrIdentityId?: string;\n\n /**\n * SHA-256 hash of the token (never store plaintext)\n */\n tokenHash: string = '';\n\n /**\n * Email this token was sent to\n */\n email: string = '';\n\n /**\n * When this token expires\n */\n expiresAt: Date = new Date(\n Date.now() + DEFAULT_EXPIRATION_MINUTES * 60 * 1000,\n );\n\n /**\n * When this token was used (null = unused)\n */\n usedAt: Date | null = null;\n\n /**\n * IP address that requested the token (for rate limiting)\n */\n requestedFromIp: string = '';\n\n /**\n * When this token was created (for rate limiting)\n */\n createdAt: Date = new Date();\n\n constructor(options: MagicLinkTokenOptions = {}) {\n super(options);\n if (options.nostrIdentityId) this.nostrIdentityId = options.nostrIdentityId;\n if (options.tokenHash) this.tokenHash = options.tokenHash;\n if (options.email) this.email = options.email;\n if (options.expiresAt) this.expiresAt = options.expiresAt;\n if (options.usedAt !== undefined) this.usedAt = options.usedAt;\n if (options.requestedFromIp) this.requestedFromIp = options.requestedFromIp;\n if (options.createdAt) this.createdAt = options.createdAt;\n }\n\n /**\n * Hash a token using SHA-256\n */\n static hashToken(token: string): string {\n return createHash('sha256').update(token).digest('hex');\n }\n\n /**\n * Generate a new magic link token\n * @param nostrIdentity - The identity this token is for\n * @param email - The email address to send to\n * @param options - Additional options\n */\n static async generate(\n nostrIdentity: NostrIdentity,\n email: string,\n options: {\n expiresInMinutes?: number;\n requestedFromIp?: string;\n db?: SmrtObjectOptions['db'];\n } = {},\n ): Promise<GenerateTokenResult> {\n // Generate 32 bytes of random data (256 bits of entropy)\n const tokenBytes = randomBytes(32);\n const token = tokenBytes.toString('base64url');\n const tokenHash = MagicLinkToken.hashToken(token);\n\n const expiresInMinutes =\n options.expiresInMinutes ?? DEFAULT_EXPIRATION_MINUTES;\n const expiresAt = new Date(Date.now() + expiresInMinutes * 60 * 1000);\n\n const magicLinkToken = new MagicLinkToken({\n db: options.db ?? nostrIdentity.options?.db,\n nostrIdentityId: nostrIdentity.id as string,\n tokenHash,\n email: email.toLowerCase(),\n expiresAt,\n requestedFromIp: options.requestedFromIp || '',\n });\n\n await magicLinkToken.initialize();\n await magicLinkToken.save();\n\n return { token, magicLinkToken };\n }\n\n /**\n * Verify a token and return the MagicLinkToken if valid\n * @param token - The plaintext token to verify\n * @param options - Database options\n * @returns The MagicLinkToken if valid, null otherwise\n */\n static async verify(\n token: string,\n options: SmrtObjectOptions = {},\n ): Promise<MagicLinkToken | null> {\n const tokenHash = MagicLinkToken.hashToken(token);\n\n const { MagicLinkTokenCollection } = await import(\n '../collections/MagicLinkTokenCollection'\n );\n const collection = await MagicLinkTokenCollection.create(options);\n\n const magicLinkToken = await collection.findOne({\n where: { tokenHash },\n });\n\n if (!magicLinkToken) {\n return null;\n }\n\n // Check if valid\n if (!magicLinkToken.isValid()) {\n return null;\n }\n\n return magicLinkToken;\n }\n\n /**\n * Check if this token is valid (not expired and not used)\n */\n isValid(): boolean {\n if (this.usedAt !== null) {\n return false;\n }\n if (new Date() > this.expiresAt) {\n return false;\n }\n return true;\n }\n\n /**\n * Check if this token has expired\n */\n isExpired(): boolean {\n return new Date() > this.expiresAt;\n }\n\n /**\n * Check if this token has been used\n */\n isUsed(): boolean {\n return this.usedAt !== null;\n }\n\n /**\n * Mark this token as used\n */\n async markUsed(): Promise<void> {\n this.usedAt = new Date();\n await this.save();\n }\n\n /**\n * Get the NostrIdentity this token is for\n */\n async getNostrIdentity(): Promise<NostrIdentity | null> {\n return (await this.getRelated('nostrIdentityId')) as NostrIdentity | null;\n }\n\n /**\n * Get time remaining until expiration in seconds\n */\n getTimeRemainingSeconds(): number {\n const now = new Date();\n const diff = this.expiresAt.getTime() - now.getTime();\n return Math.max(0, Math.floor(diff / 1000));\n }\n}\n","/**\n * MagicLinkTokenCollection - Collection for managing magic link tokens\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { MagicLinkToken } from '../models/MagicLinkToken';\n\nexport class MagicLinkTokenCollection extends SmrtCollection<MagicLinkToken> {\n static readonly _itemClass = MagicLinkToken;\n\n /**\n * Find token by hash\n */\n async findByTokenHash(tokenHash: string): Promise<MagicLinkToken | null> {\n return await this.findOne({\n where: { tokenHash },\n });\n }\n\n /**\n * Find active (non-expired, non-used) tokens for an email\n */\n async findActiveForEmail(email: string): Promise<MagicLinkToken[]> {\n const tokens = await this.list({\n where: { email: email.toLowerCase() },\n });\n return tokens.filter((token) => token.isValid());\n }\n\n /**\n * Find tokens for a NostrIdentity\n */\n async findByIdentity(nostrIdentityId: string): Promise<MagicLinkToken[]> {\n return await this.list({\n where: { nostrIdentityId },\n });\n }\n\n /**\n * Count tokens created for an email within a time window (for rate limiting)\n * @param email - Email address\n * @param withinMinutes - Time window in minutes\n */\n async countRecentByEmail(\n email: string,\n withinMinutes: number,\n ): Promise<number> {\n const cutoff = new Date(Date.now() - withinMinutes * 60 * 1000);\n const tokens = await this.list({\n where: { email: email.toLowerCase() },\n });\n\n // Filter tokens created after cutoff\n // Note: We use createdAt from SmrtObject base\n return tokens.filter((token) => {\n const createdAt = token.createdAt;\n return createdAt && new Date(createdAt) > cutoff;\n }).length;\n }\n\n /**\n * Count tokens created from an IP within a time window (for rate limiting)\n * @param ip - IP address\n * @param withinMinutes - Time window in minutes\n */\n async countRecentByIp(ip: string, withinMinutes: number): Promise<number> {\n const cutoff = new Date(Date.now() - withinMinutes * 60 * 1000);\n const tokens = await this.list({\n where: { requestedFromIp: ip },\n });\n\n // Filter tokens created after cutoff\n return tokens.filter((token) => {\n const createdAt = token.createdAt;\n return createdAt && new Date(createdAt) > cutoff;\n }).length;\n }\n\n /**\n * Verify a token and return it if valid\n */\n async verify(token: string): Promise<MagicLinkToken | null> {\n const tokenHash = MagicLinkToken.hashToken(token);\n const magicLinkToken = await this.findByTokenHash(tokenHash);\n\n if (!magicLinkToken) {\n return null;\n }\n\n if (!magicLinkToken.isValid()) {\n return null;\n }\n\n return magicLinkToken;\n }\n\n /**\n * Clean up expired tokens by deleting them\n * @returns Number of tokens deleted\n */\n async cleanupExpired(): Promise<number> {\n const now = new Date();\n const allTokens = await this.list({});\n let deleted = 0;\n\n for (const token of allTokens) {\n if (token.isExpired() || token.isUsed()) {\n await token.delete();\n deleted++;\n }\n }\n\n return deleted;\n }\n\n /**\n * Revoke all tokens for a NostrIdentity (e.g., when identity is deleted)\n */\n async revokeForIdentity(nostrIdentityId: string): Promise<number> {\n const tokens = await this.findByIdentity(nostrIdentityId);\n let revoked = 0;\n\n for (const token of tokens) {\n if (!token.isUsed()) {\n await token.markUsed(); // Mark as used to invalidate\n revoked++;\n }\n }\n\n return revoked;\n }\n\n /**\n * Check if rate limit is exceeded for email\n * @param email - Email address\n * @param maxPerHour - Maximum tokens allowed per hour (default: 5)\n */\n async isRateLimitedByEmail(\n email: string,\n maxPerHour: number = 5,\n ): Promise<boolean> {\n const count = await this.countRecentByEmail(email, 60);\n return count >= maxPerHour;\n }\n\n /**\n * Check if rate limit is exceeded for IP\n * @param ip - IP address\n * @param maxPerHour - Maximum tokens allowed per hour (default: 10)\n */\n async isRateLimitedByIp(\n ip: string,\n maxPerHour: number = 10,\n ): Promise<boolean> {\n const count = await this.countRecentByIp(ip, 60);\n return count >= maxPerHour;\n }\n}\n","/**\n * Magic Link Service\n *\n * Handles the magic link authentication flow including:\n * - Token generation and storage\n * - Email sending (via callback)\n * - Token verification\n * - Rate limiting checks\n */\n\nimport type { SmrtObjectOptions } from '@happyvertical/smrt-core';\nimport { MagicLinkTokenCollection } from '../collections/MagicLinkTokenCollection';\nimport { NostrIdentityCollection } from '../collections/NostrIdentityCollection';\nimport { MagicLinkToken } from '../models/MagicLinkToken';\nimport type { NostrIdentity } from '../models/NostrIdentity';\nimport type { Profile } from '../models/Profile';\nimport { encryptPrivkey, generateNostrKeypair } from './nostrCrypto';\n\nexport interface MagicLinkConfig {\n /** Base URL for magic links (e.g., \"https://example.com\") */\n baseUrl: string;\n /** Path for verification endpoint (default: \"/auth/verify\") */\n verifyPath?: string;\n /** Token expiration in minutes (default: 15) */\n expiresInMinutes?: number;\n /** Maximum tokens per email per hour (default: 5) */\n maxTokensPerEmailPerHour?: number;\n /** Maximum tokens per IP per hour (default: 10) */\n maxTokensPerIpPerHour?: number;\n /** Server master secret for encryption */\n masterSecret: string;\n /** Callback to send the email */\n sendEmail: (to: string, magicLink: string) => Promise<void>;\n /** Database options */\n db: SmrtObjectOptions['db'];\n}\n\nexport interface InitiateResult {\n success: boolean;\n /** The Nostr identity (existing or new) */\n nostrIdentity?: NostrIdentity;\n /** The profile (existing or new) */\n profile?: Profile;\n /** True if a new profile was created */\n created: boolean;\n /** Error message if success is false */\n error?: string;\n /** Error code for programmatic handling */\n errorCode?: 'RATE_LIMITED_EMAIL' | 'RATE_LIMITED_IP' | 'EMAIL_SEND_FAILED';\n}\n\nexport interface VerifyResult {\n success: boolean;\n /** The profile */\n profile?: Profile;\n /** The Nostr identity */\n nostrIdentity?: NostrIdentity;\n /** The decrypted keypair (only on success) */\n keypair?: {\n pubkey: string;\n privkey: string;\n npub: string;\n nsec: string;\n };\n /** Error message if success is false */\n error?: string;\n /** Error code for programmatic handling */\n errorCode?: 'INVALID_TOKEN' | 'EXPIRED_TOKEN' | 'USED_TOKEN' | 'NOT_FOUND';\n}\n\nexport interface MagicLinkService {\n /**\n * Initiate magic link flow - generates token and sends email\n */\n initiate(\n email: string,\n options?: {\n requestedFromIp?: string;\n nip05Username?: string;\n },\n ): Promise<InitiateResult>;\n\n /**\n * Verify a magic link token and return decrypted keypair\n */\n verify(token: string): Promise<VerifyResult>;\n}\n\n/**\n * Create a magic link service instance\n */\nexport function createMagicLinkService(\n config: MagicLinkConfig,\n): MagicLinkService {\n const {\n baseUrl,\n verifyPath = '/auth/verify',\n expiresInMinutes = 15,\n maxTokensPerEmailPerHour = 5,\n maxTokensPerIpPerHour = 10,\n masterSecret,\n sendEmail,\n db,\n } = config;\n\n return {\n async initiate(\n email: string,\n options: {\n requestedFromIp?: string;\n nip05Username?: string;\n } = {},\n ): Promise<InitiateResult> {\n const normalizedEmail = email.toLowerCase().trim();\n const { requestedFromIp, nip05Username } = options;\n\n // Create collections\n const identityCollection = await NostrIdentityCollection.create({ db });\n const tokenCollection = await MagicLinkTokenCollection.create({ db });\n\n // Rate limiting checks\n if (\n await tokenCollection.isRateLimitedByEmail(\n normalizedEmail,\n maxTokensPerEmailPerHour,\n )\n ) {\n return {\n success: false,\n created: false,\n error: 'Too many requests for this email. Please try again later.',\n errorCode: 'RATE_LIMITED_EMAIL',\n };\n }\n\n if (\n requestedFromIp &&\n (await tokenCollection.isRateLimitedByIp(\n requestedFromIp,\n maxTokensPerIpPerHour,\n ))\n ) {\n return {\n success: false,\n created: false,\n error: 'Too many requests from this IP. Please try again later.',\n errorCode: 'RATE_LIMITED_IP',\n };\n }\n\n // Check if identity exists\n let nostrIdentity = await identityCollection.findByEmail(normalizedEmail);\n let profile: Profile | null = null;\n let created = false;\n\n if (!nostrIdentity) {\n // Create new identity and profile\n const { Person } = await import('../models/ProfileTypes');\n const { ProfileTypeCollection } = await import(\n '../collections/ProfileTypeCollection'\n );\n\n // Profile.typeId is a required FK, so resolve (or seed) the canonical\n // 'person' type before creating the profile — otherwise `.save()`\n // fails required-field validation. Mirrors createProfileFromNostr /\n // createProfileFromOidc in resolveIdentity.ts.\n const typeCollection = await ProfileTypeCollection.create({ db });\n let personType = await typeCollection.getBySlug('person');\n if (!personType) {\n const { ProfileType } = await import('../models/ProfileType');\n personType = new ProfileType({\n db,\n slug: 'person',\n name: 'Person',\n description: 'Individual person profile',\n });\n await personType.initialize();\n await personType.save();\n }\n\n // Create a new profile for this user (Person is an STI subclass of Profile)\n profile = new Person({\n db,\n typeId: personType.id as string,\n email: normalizedEmail,\n name: normalizedEmail.split('@')[0], // Default name from email\n });\n await profile.initialize();\n await profile.save();\n\n // Generate keypair and encrypt\n const keypair = generateNostrKeypair();\n const encrypted = encryptPrivkey(keypair.privkey, masterSecret);\n\n // Create Nostr identity\n const { NostrIdentity: NostrIdentityClass } = await import(\n '../models/NostrIdentity'\n );\n nostrIdentity = new NostrIdentityClass({\n db,\n profileId: profile.id!,\n pubkey: keypair.pubkey,\n encryptedPrivkey: encrypted.ciphertext,\n encryptionIv: encrypted.iv,\n encryptionTag: encrypted.tag,\n email: normalizedEmail,\n nip05Username:\n nip05Username?.toLowerCase() || normalizedEmail.split('@')[0],\n });\n await nostrIdentity.initialize();\n await nostrIdentity.save();\n\n created = true;\n } else {\n profile = await nostrIdentity.getProfile();\n }\n\n // Generate magic link token\n const { token } = await MagicLinkToken.generate(\n nostrIdentity,\n normalizedEmail,\n {\n expiresInMinutes,\n requestedFromIp,\n db,\n },\n );\n\n // Build magic link URL\n const magicLink = `${baseUrl}${verifyPath}?token=${encodeURIComponent(token)}`;\n\n // Send email\n try {\n await sendEmail(normalizedEmail, magicLink);\n } catch (emailError) {\n return {\n success: false,\n created,\n nostrIdentity,\n profile: profile || undefined,\n error: 'Failed to send email. Please try again.',\n errorCode: 'EMAIL_SEND_FAILED',\n };\n }\n\n return {\n success: true,\n created,\n nostrIdentity,\n profile: profile || undefined,\n };\n },\n\n async verify(token: string): Promise<VerifyResult> {\n // Verify token\n const magicLinkToken = await MagicLinkToken.verify(token, { db });\n\n if (!magicLinkToken) {\n // Try to get more specific error\n const tokenHash = MagicLinkToken.hashToken(token);\n const tokenCollection = await MagicLinkTokenCollection.create({ db });\n const existingToken = await tokenCollection.findByTokenHash(tokenHash);\n\n if (!existingToken) {\n return {\n success: false,\n error: 'Invalid or expired token.',\n errorCode: 'NOT_FOUND',\n };\n }\n\n if (existingToken.isUsed()) {\n return {\n success: false,\n error: 'This link has already been used.',\n errorCode: 'USED_TOKEN',\n };\n }\n\n if (existingToken.isExpired()) {\n return {\n success: false,\n error: 'This link has expired. Please request a new one.',\n errorCode: 'EXPIRED_TOKEN',\n };\n }\n\n return {\n success: false,\n error: 'Invalid token.',\n errorCode: 'INVALID_TOKEN',\n };\n }\n\n // Mark token as used\n await magicLinkToken.markUsed();\n\n // Get the Nostr identity\n const nostrIdentity = await magicLinkToken.getNostrIdentity();\n if (!nostrIdentity) {\n return {\n success: false,\n error: 'Identity not found.',\n errorCode: 'NOT_FOUND',\n };\n }\n\n // Mark identity as verified\n await nostrIdentity.markVerified();\n\n // Get profile\n const profile = await nostrIdentity.getProfile();\n\n // Decrypt keypair\n const keypair = nostrIdentity.getKeypair(masterSecret);\n\n return {\n success: true,\n profile: profile || undefined,\n nostrIdentity,\n keypair,\n };\n },\n };\n}\n","/**\n * NIP-05 Handler\n *\n * Provides a helper for serving the /.well-known/nostr.json endpoint\n * for NIP-05 identity verification.\n *\n * @see https://github.com/nostr-protocol/nips/blob/master/05.md\n */\n\nimport type { SmrtObjectOptions } from '@happyvertical/smrt-core';\nimport {\n type Nip05Response,\n NostrIdentityCollection,\n} from '../collections/NostrIdentityCollection';\n\nexport interface Nip05HandlerConfig {\n /** Database options */\n db: SmrtObjectOptions['db'];\n /** Optional: Additional relays to include for all users */\n defaultRelays?: string[];\n /** Optional: Cache TTL in seconds (default: 300 = 5 minutes) */\n cacheTtlSeconds?: number;\n}\n\nexport interface Nip05Request {\n /** The 'name' query parameter from the request */\n name?: string | null;\n}\n\nexport interface Nip05HandlerResult {\n /** The NIP-05 response body */\n body: Nip05Response;\n /** Suggested headers for the response */\n headers: Record<string, string>;\n /** HTTP status code */\n status: number;\n}\n\n/**\n * Create a NIP-05 handler\n *\n * @example\n * // SvelteKit\n * import { createNip05Handler } from '@happyvertical/smrt-profiles';\n *\n * const handler = createNip05Handler({ db: { type: 'postgres', url: DATABASE_URL } });\n *\n * export async function GET({ url }) {\n * const result = await handler({ name: url.searchParams.get('name') });\n * return new Response(JSON.stringify(result.body), {\n * status: result.status,\n * headers: result.headers,\n * });\n * }\n *\n * @example\n * // Express\n * import { createNip05Handler } from '@happyvertical/smrt-profiles';\n *\n * const handler = createNip05Handler({ db: { type: 'postgres', url: DATABASE_URL } });\n *\n * app.get('/.well-known/nostr.json', async (req, res) => {\n * const result = await handler({ name: req.query.name });\n * res.status(result.status).set(result.headers).json(result.body);\n * });\n */\nexport function createNip05Handler(config: Nip05HandlerConfig) {\n const { db, defaultRelays = [], cacheTtlSeconds = 300 } = config;\n\n return async function handleNip05Request(\n request: Nip05Request,\n ): Promise<Nip05HandlerResult> {\n const { name } = request;\n\n const collection = await NostrIdentityCollection.create({ db });\n const response = await collection.getNip05Response(name || undefined);\n\n // Add default relays if configured\n if (defaultRelays.length > 0 && Object.keys(response.names).length > 0) {\n response.relays = {};\n for (const pubkey of Object.values(response.names)) {\n response.relays[pubkey] = defaultRelays;\n }\n }\n\n // If specific name requested but not found, return empty names\n if (name && Object.keys(response.names).length === 0) {\n return {\n body: { names: {} },\n headers: {\n 'Content-Type': 'application/json',\n 'Access-Control-Allow-Origin': '*',\n 'Cache-Control': `public, max-age=${cacheTtlSeconds}`,\n },\n status: 200, // NIP-05 spec says return 200 with empty names, not 404\n };\n }\n\n return {\n body: response,\n headers: {\n 'Content-Type': 'application/json',\n 'Access-Control-Allow-Origin': '*',\n 'Cache-Control': `public, max-age=${cacheTtlSeconds}`,\n },\n status: 200,\n };\n };\n}\n\n/**\n * Validate a NIP-05 identifier format\n * @param identifier - The identifier to validate (e.g., \"alice@example.com\")\n */\nexport function isValidNip05Identifier(identifier: string): boolean {\n // NIP-05 format: <local-part>@<domain>\n const parts = identifier.split('@');\n if (parts.length !== 2) {\n return false;\n }\n\n const [localPart, domain] = parts;\n\n // Local part: alphanumeric, _, -\n if (!/^[a-z0-9_-]+$/i.test(localPart)) {\n return false;\n }\n\n // Domain: basic domain validation\n if (!/^[a-z0-9.-]+\\.[a-z]{2,}$/i.test(domain)) {\n return false;\n }\n\n return true;\n}\n\n/**\n * Parse a NIP-05 identifier into its parts\n * @param identifier - The identifier to parse (e.g., \"alice@example.com\")\n */\nexport function parseNip05Identifier(identifier: string): {\n localPart: string;\n domain: string;\n} | null {\n if (!isValidNip05Identifier(identifier)) {\n return null;\n }\n\n const [localPart, domain] = identifier.split('@');\n return { localPart, domain };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/__smrt-register__.ts","../src/models/MagicLinkToken.ts","../src/collections/MagicLinkTokenCollection.ts","../src/auth/magicLinkService.ts","../src/auth/nip05Handler.ts"],"sourcesContent":["/**\n * Self-registers this package's build-time manifest before any @smrt() decorator\n * in the package fires. Fixes issue #1132: in consumer runtimes (tsx, SvelteKit\n * SSR, plain `vite dev`) the decorator's synchronous manifest lookup previously\n * missed because no step populated the global manifest cache — classes got\n * registered with zero fields and `save()` / `toJSON()` silently dropped every\n * declared property.\n *\n * Import this module as the first statement in `src/index.ts` so its top-level\n * side effect runs ahead of any class module's @smrt() decorator.\n *\n * Silent no-op in dev/test, where the vitest plugin already populates manifests\n * via a different path. Only needs to succeed in the published dist output.\n *\n * @see https://github.com/happyvertical/smrt/issues/1132\n */\nimport { ObjectRegistry } from '@happyvertical/smrt-core';\n\n// During library builds, smrtPlugin replaces this entire module with generated\n// code that embeds the scanned manifest inline (#1506/#1507) — published dists\n// never resolve this URL, so downstream bundlers cannot break registration by\n// relocating the compiled module away from dist/manifest.json. The runtime\n// lookup below is the fallback for source-mode runs without that transform.\nObjectRegistry.registerPackageManifest(\n new URL('./manifest.json', import.meta.url),\n);\n","/**\n * MagicLinkToken - Temporary tokens for email verification\n *\n * Stores hashed magic link tokens with expiration. Tokens are single-use\n * and automatically invalidated after verification or expiration.\n */\n\nimport { createHash, randomBytes } from 'node:crypto';\nimport {\n field,\n foreignKey,\n SmrtObject,\n type SmrtObjectOptions,\n smrt,\n} from '@happyvertical/smrt-core';\nimport type { NostrIdentity } from './NostrIdentity';\n\nexport interface MagicLinkTokenOptions extends SmrtObjectOptions {\n nostrIdentityId?: string;\n tokenHash?: string;\n email?: string;\n expiresAt?: Date;\n usedAt?: Date | null;\n requestedFromIp?: string;\n createdAt?: Date;\n}\n\nexport interface GenerateTokenResult {\n /** The plaintext token (only available at creation time) */\n token: string;\n /** The MagicLinkToken record */\n magicLinkToken: MagicLinkToken;\n}\n\n/** Default token expiration: 15 minutes */\nconst DEFAULT_EXPIRATION_MINUTES = 15;\n\n@smrt({\n tableName: 'magic_link_tokens',\n api: { exclude: ['*'] }, // No API access - internal only\n mcp: { exclude: ['*'] },\n cli: { include: ['list'] }, // Admin visibility only\n})\nexport class MagicLinkToken extends SmrtObject {\n /**\n * Link to the NostrIdentity this token is for\n */\n @foreignKey('NostrIdentity', { required: true })\n nostrIdentityId?: string;\n\n /**\n * SHA-256 hash of the token (never store plaintext).\n *\n * Indexed: `verify()` looks tokens up by this hash on every magic-link\n * click (#2364, epic #2382 finding A3).\n */\n @field({ indexed: true })\n tokenHash: string = '';\n\n /**\n * Email this token was sent to\n */\n email: string = '';\n\n /**\n * When this token expires\n */\n expiresAt: Date = new Date(\n Date.now() + DEFAULT_EXPIRATION_MINUTES * 60 * 1000,\n );\n\n /**\n * When this token was used (null = unused)\n */\n usedAt: Date | null = null;\n\n /**\n * IP address that requested the token (for rate limiting)\n */\n requestedFromIp: string = '';\n\n /**\n * When this token was created (for rate limiting)\n */\n createdAt: Date = new Date();\n\n constructor(options: MagicLinkTokenOptions = {}) {\n super(options);\n if (options.nostrIdentityId) this.nostrIdentityId = options.nostrIdentityId;\n if (options.tokenHash) this.tokenHash = options.tokenHash;\n if (options.email) this.email = options.email;\n if (options.expiresAt) this.expiresAt = options.expiresAt;\n if (options.usedAt !== undefined) this.usedAt = options.usedAt;\n if (options.requestedFromIp) this.requestedFromIp = options.requestedFromIp;\n if (options.createdAt) this.createdAt = options.createdAt;\n }\n\n /**\n * Hash a token using SHA-256\n */\n static hashToken(token: string): string {\n return createHash('sha256').update(token).digest('hex');\n }\n\n /**\n * Generate a new magic link token\n * @param nostrIdentity - The identity this token is for\n * @param email - The email address to send to\n * @param options - Additional options\n */\n static async generate(\n nostrIdentity: NostrIdentity,\n email: string,\n options: {\n expiresInMinutes?: number;\n requestedFromIp?: string;\n db?: SmrtObjectOptions['db'];\n } = {},\n ): Promise<GenerateTokenResult> {\n // Generate 32 bytes of random data (256 bits of entropy)\n const tokenBytes = randomBytes(32);\n const token = tokenBytes.toString('base64url');\n const tokenHash = MagicLinkToken.hashToken(token);\n\n const expiresInMinutes =\n options.expiresInMinutes ?? DEFAULT_EXPIRATION_MINUTES;\n const expiresAt = new Date(Date.now() + expiresInMinutes * 60 * 1000);\n\n const magicLinkToken = new MagicLinkToken({\n db: options.db ?? nostrIdentity.options?.db,\n nostrIdentityId: nostrIdentity.id as string,\n tokenHash,\n email: email.toLowerCase(),\n expiresAt,\n requestedFromIp: options.requestedFromIp || '',\n });\n\n await magicLinkToken.initialize();\n await magicLinkToken.save();\n\n return { token, magicLinkToken };\n }\n\n /**\n * Verify a token and return the MagicLinkToken if valid\n * @param token - The plaintext token to verify\n * @param options - Database options\n * @returns The MagicLinkToken if valid, null otherwise\n */\n static async verify(\n token: string,\n options: SmrtObjectOptions = {},\n ): Promise<MagicLinkToken | null> {\n const tokenHash = MagicLinkToken.hashToken(token);\n\n const { MagicLinkTokenCollection } = await import(\n '../collections/MagicLinkTokenCollection'\n );\n const collection = await MagicLinkTokenCollection.create(options);\n\n const magicLinkToken = await collection.findOne({\n where: { tokenHash },\n });\n\n if (!magicLinkToken) {\n return null;\n }\n\n // Check if valid\n if (!magicLinkToken.isValid()) {\n return null;\n }\n\n return magicLinkToken;\n }\n\n /**\n * Check if this token is valid (not expired and not used)\n */\n isValid(): boolean {\n if (this.usedAt !== null) {\n return false;\n }\n if (new Date() > this.expiresAt) {\n return false;\n }\n return true;\n }\n\n /**\n * Check if this token has expired\n */\n isExpired(): boolean {\n return new Date() > this.expiresAt;\n }\n\n /**\n * Check if this token has been used\n */\n isUsed(): boolean {\n return this.usedAt !== null;\n }\n\n /**\n * Mark this token as used\n */\n async markUsed(): Promise<void> {\n this.usedAt = new Date();\n await this.save();\n }\n\n /**\n * Get the NostrIdentity this token is for\n */\n async getNostrIdentity(): Promise<NostrIdentity | null> {\n return (await this.getRelated('nostrIdentityId')) as NostrIdentity | null;\n }\n\n /**\n * Get time remaining until expiration in seconds\n */\n getTimeRemainingSeconds(): number {\n const now = new Date();\n const diff = this.expiresAt.getTime() - now.getTime();\n return Math.max(0, Math.floor(diff / 1000));\n }\n}\n","/**\n * MagicLinkTokenCollection - Collection for managing magic link tokens\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { MagicLinkToken } from '../models/MagicLinkToken';\n\nexport class MagicLinkTokenCollection extends SmrtCollection<MagicLinkToken> {\n static readonly _itemClass = MagicLinkToken;\n\n /**\n * Find token by hash\n */\n async findByTokenHash(tokenHash: string): Promise<MagicLinkToken | null> {\n return await this.findOne({\n where: { tokenHash },\n });\n }\n\n /**\n * Find active (non-expired, non-used) tokens for an email\n */\n async findActiveForEmail(email: string): Promise<MagicLinkToken[]> {\n const tokens = await this.list({\n where: { email: email.toLowerCase() },\n });\n return tokens.filter((token) => token.isValid());\n }\n\n /**\n * Find tokens for a NostrIdentity\n */\n async findByIdentity(nostrIdentityId: string): Promise<MagicLinkToken[]> {\n return await this.list({\n where: { nostrIdentityId },\n });\n }\n\n /**\n * Count tokens created for an email within a time window (for rate limiting)\n * @param email - Email address\n * @param withinMinutes - Time window in minutes\n */\n async countRecentByEmail(\n email: string,\n withinMinutes: number,\n ): Promise<number> {\n const cutoff = new Date(Date.now() - withinMinutes * 60 * 1000);\n const tokens = await this.list({\n where: { email: email.toLowerCase() },\n });\n\n // Filter tokens created after cutoff\n // Note: We use createdAt from SmrtObject base\n return tokens.filter((token) => {\n const createdAt = token.createdAt;\n return createdAt && new Date(createdAt) > cutoff;\n }).length;\n }\n\n /**\n * Count tokens created from an IP within a time window (for rate limiting)\n * @param ip - IP address\n * @param withinMinutes - Time window in minutes\n */\n async countRecentByIp(ip: string, withinMinutes: number): Promise<number> {\n const cutoff = new Date(Date.now() - withinMinutes * 60 * 1000);\n const tokens = await this.list({\n where: { requestedFromIp: ip },\n });\n\n // Filter tokens created after cutoff\n return tokens.filter((token) => {\n const createdAt = token.createdAt;\n return createdAt && new Date(createdAt) > cutoff;\n }).length;\n }\n\n /**\n * Verify a token and return it if valid\n */\n async verify(token: string): Promise<MagicLinkToken | null> {\n const tokenHash = MagicLinkToken.hashToken(token);\n const magicLinkToken = await this.findByTokenHash(tokenHash);\n\n if (!magicLinkToken) {\n return null;\n }\n\n if (!magicLinkToken.isValid()) {\n return null;\n }\n\n return magicLinkToken;\n }\n\n /**\n * Clean up expired tokens by deleting them\n * @returns Number of tokens deleted\n */\n async cleanupExpired(): Promise<number> {\n const now = new Date();\n const allTokens = await this.list({});\n let deleted = 0;\n\n for (const token of allTokens) {\n if (token.isExpired() || token.isUsed()) {\n await token.delete();\n deleted++;\n }\n }\n\n return deleted;\n }\n\n /**\n * Revoke all tokens for a NostrIdentity (e.g., when identity is deleted)\n */\n async revokeForIdentity(nostrIdentityId: string): Promise<number> {\n const tokens = await this.findByIdentity(nostrIdentityId);\n let revoked = 0;\n\n for (const token of tokens) {\n if (!token.isUsed()) {\n await token.markUsed(); // Mark as used to invalidate\n revoked++;\n }\n }\n\n return revoked;\n }\n\n /**\n * Check if rate limit is exceeded for email\n * @param email - Email address\n * @param maxPerHour - Maximum tokens allowed per hour (default: 5)\n */\n async isRateLimitedByEmail(\n email: string,\n maxPerHour: number = 5,\n ): Promise<boolean> {\n const count = await this.countRecentByEmail(email, 60);\n return count >= maxPerHour;\n }\n\n /**\n * Check if rate limit is exceeded for IP\n * @param ip - IP address\n * @param maxPerHour - Maximum tokens allowed per hour (default: 10)\n */\n async isRateLimitedByIp(\n ip: string,\n maxPerHour: number = 10,\n ): Promise<boolean> {\n const count = await this.countRecentByIp(ip, 60);\n return count >= maxPerHour;\n }\n}\n","/**\n * Magic Link Service\n *\n * Handles the magic link authentication flow including:\n * - Token generation and storage\n * - Email sending (via callback)\n * - Token verification\n * - Rate limiting checks\n */\n\nimport type { SmrtObjectOptions } from '@happyvertical/smrt-core';\nimport { MagicLinkTokenCollection } from '../collections/MagicLinkTokenCollection';\nimport { NostrIdentityCollection } from '../collections/NostrIdentityCollection';\nimport { MagicLinkToken } from '../models/MagicLinkToken';\nimport type { NostrIdentity } from '../models/NostrIdentity';\nimport type { Profile } from '../models/Profile';\nimport { encryptPrivkey, generateNostrKeypair } from './nostrCrypto';\n\nexport interface MagicLinkConfig {\n /** Base URL for magic links (e.g., \"https://example.com\") */\n baseUrl: string;\n /** Path for verification endpoint (default: \"/auth/verify\") */\n verifyPath?: string;\n /** Token expiration in minutes (default: 15) */\n expiresInMinutes?: number;\n /** Maximum tokens per email per hour (default: 5) */\n maxTokensPerEmailPerHour?: number;\n /** Maximum tokens per IP per hour (default: 10) */\n maxTokensPerIpPerHour?: number;\n /** Server master secret for encryption */\n masterSecret: string;\n /** Callback to send the email */\n sendEmail: (to: string, magicLink: string) => Promise<void>;\n /** Database options */\n db: SmrtObjectOptions['db'];\n}\n\nexport interface InitiateResult {\n success: boolean;\n /** The Nostr identity (existing or new) */\n nostrIdentity?: NostrIdentity;\n /** The profile (existing or new) */\n profile?: Profile;\n /** True if a new profile was created */\n created: boolean;\n /** Error message if success is false */\n error?: string;\n /** Error code for programmatic handling */\n errorCode?: 'RATE_LIMITED_EMAIL' | 'RATE_LIMITED_IP' | 'EMAIL_SEND_FAILED';\n}\n\nexport interface VerifyResult {\n success: boolean;\n /** The profile */\n profile?: Profile;\n /** The Nostr identity */\n nostrIdentity?: NostrIdentity;\n /** The decrypted keypair (only on success) */\n keypair?: {\n pubkey: string;\n privkey: string;\n npub: string;\n nsec: string;\n };\n /** Error message if success is false */\n error?: string;\n /** Error code for programmatic handling */\n errorCode?: 'INVALID_TOKEN' | 'EXPIRED_TOKEN' | 'USED_TOKEN' | 'NOT_FOUND';\n}\n\nexport interface MagicLinkService {\n /**\n * Initiate magic link flow - generates token and sends email\n */\n initiate(\n email: string,\n options?: {\n requestedFromIp?: string;\n nip05Username?: string;\n },\n ): Promise<InitiateResult>;\n\n /**\n * Verify a magic link token and return decrypted keypair\n */\n verify(token: string): Promise<VerifyResult>;\n}\n\n/**\n * Create a magic link service instance\n */\nexport function createMagicLinkService(\n config: MagicLinkConfig,\n): MagicLinkService {\n const {\n baseUrl,\n verifyPath = '/auth/verify',\n expiresInMinutes = 15,\n maxTokensPerEmailPerHour = 5,\n maxTokensPerIpPerHour = 10,\n masterSecret,\n sendEmail,\n db,\n } = config;\n\n return {\n async initiate(\n email: string,\n options: {\n requestedFromIp?: string;\n nip05Username?: string;\n } = {},\n ): Promise<InitiateResult> {\n const normalizedEmail = email.toLowerCase().trim();\n const { requestedFromIp, nip05Username } = options;\n\n // Create collections\n const identityCollection = await NostrIdentityCollection.create({ db });\n const tokenCollection = await MagicLinkTokenCollection.create({ db });\n\n // Rate limiting checks\n if (\n await tokenCollection.isRateLimitedByEmail(\n normalizedEmail,\n maxTokensPerEmailPerHour,\n )\n ) {\n return {\n success: false,\n created: false,\n error: 'Too many requests for this email. Please try again later.',\n errorCode: 'RATE_LIMITED_EMAIL',\n };\n }\n\n if (\n requestedFromIp &&\n (await tokenCollection.isRateLimitedByIp(\n requestedFromIp,\n maxTokensPerIpPerHour,\n ))\n ) {\n return {\n success: false,\n created: false,\n error: 'Too many requests from this IP. Please try again later.',\n errorCode: 'RATE_LIMITED_IP',\n };\n }\n\n // Check if identity exists\n let nostrIdentity = await identityCollection.findByEmail(normalizedEmail);\n let profile: Profile | null = null;\n let created = false;\n\n if (!nostrIdentity) {\n // Create new identity and profile\n const { Person } = await import('../models/ProfileTypes');\n const { ProfileTypeCollection } = await import(\n '../collections/ProfileTypeCollection'\n );\n\n // Profile.typeId is a required FK, so resolve (or seed) the canonical\n // 'person' type before creating the profile — otherwise `.save()`\n // fails required-field validation. Mirrors createProfileFromNostr /\n // createProfileFromOidc in resolveIdentity.ts.\n const typeCollection = await ProfileTypeCollection.create({ db });\n let personType = await typeCollection.getBySlug('person');\n if (!personType) {\n const { ProfileType } = await import('../models/ProfileType');\n personType = new ProfileType({\n db,\n slug: 'person',\n name: 'Person',\n description: 'Individual person profile',\n });\n await personType.initialize();\n await personType.save();\n }\n\n // Create a new profile for this user (Person is an STI subclass of Profile)\n profile = new Person({\n db,\n typeId: personType.id as string,\n email: normalizedEmail,\n name: normalizedEmail.split('@')[0], // Default name from email\n });\n await profile.initialize();\n await profile.save();\n\n // Generate keypair and encrypt\n const keypair = generateNostrKeypair();\n const encrypted = encryptPrivkey(keypair.privkey, masterSecret);\n\n // Create Nostr identity\n const { NostrIdentity: NostrIdentityClass } = await import(\n '../models/NostrIdentity'\n );\n nostrIdentity = new NostrIdentityClass({\n db,\n profileId: profile.id!,\n pubkey: keypair.pubkey,\n encryptedPrivkey: encrypted.ciphertext,\n encryptionIv: encrypted.iv,\n encryptionTag: encrypted.tag,\n email: normalizedEmail,\n nip05Username:\n nip05Username?.toLowerCase() || normalizedEmail.split('@')[0],\n });\n await nostrIdentity.initialize();\n await nostrIdentity.save();\n\n created = true;\n } else {\n profile = await nostrIdentity.getProfile();\n }\n\n // Generate magic link token\n const { token } = await MagicLinkToken.generate(\n nostrIdentity,\n normalizedEmail,\n {\n expiresInMinutes,\n requestedFromIp,\n db,\n },\n );\n\n // Build magic link URL\n const magicLink = `${baseUrl}${verifyPath}?token=${encodeURIComponent(token)}`;\n\n // Send email\n try {\n await sendEmail(normalizedEmail, magicLink);\n } catch (emailError) {\n return {\n success: false,\n created,\n nostrIdentity,\n profile: profile || undefined,\n error: 'Failed to send email. Please try again.',\n errorCode: 'EMAIL_SEND_FAILED',\n };\n }\n\n return {\n success: true,\n created,\n nostrIdentity,\n profile: profile || undefined,\n };\n },\n\n async verify(token: string): Promise<VerifyResult> {\n // Verify token\n const magicLinkToken = await MagicLinkToken.verify(token, { db });\n\n if (!magicLinkToken) {\n // Try to get more specific error\n const tokenHash = MagicLinkToken.hashToken(token);\n const tokenCollection = await MagicLinkTokenCollection.create({ db });\n const existingToken = await tokenCollection.findByTokenHash(tokenHash);\n\n if (!existingToken) {\n return {\n success: false,\n error: 'Invalid or expired token.',\n errorCode: 'NOT_FOUND',\n };\n }\n\n if (existingToken.isUsed()) {\n return {\n success: false,\n error: 'This link has already been used.',\n errorCode: 'USED_TOKEN',\n };\n }\n\n if (existingToken.isExpired()) {\n return {\n success: false,\n error: 'This link has expired. Please request a new one.',\n errorCode: 'EXPIRED_TOKEN',\n };\n }\n\n return {\n success: false,\n error: 'Invalid token.',\n errorCode: 'INVALID_TOKEN',\n };\n }\n\n // Mark token as used\n await magicLinkToken.markUsed();\n\n // Get the Nostr identity\n const nostrIdentity = await magicLinkToken.getNostrIdentity();\n if (!nostrIdentity) {\n return {\n success: false,\n error: 'Identity not found.',\n errorCode: 'NOT_FOUND',\n };\n }\n\n // Mark identity as verified\n await nostrIdentity.markVerified();\n\n // Get profile\n const profile = await nostrIdentity.getProfile();\n\n // Decrypt keypair\n const keypair = nostrIdentity.getKeypair(masterSecret);\n\n return {\n success: true,\n profile: profile || undefined,\n nostrIdentity,\n keypair,\n };\n },\n };\n}\n","/**\n * NIP-05 Handler\n *\n * Provides a helper for serving the /.well-known/nostr.json endpoint\n * for NIP-05 identity verification.\n *\n * @see https://github.com/nostr-protocol/nips/blob/master/05.md\n */\n\nimport type { SmrtObjectOptions } from '@happyvertical/smrt-core';\nimport {\n type Nip05Response,\n NostrIdentityCollection,\n} from '../collections/NostrIdentityCollection';\n\nexport interface Nip05HandlerConfig {\n /** Database options */\n db: SmrtObjectOptions['db'];\n /** Optional: Additional relays to include for all users */\n defaultRelays?: string[];\n /** Optional: Cache TTL in seconds (default: 300 = 5 minutes) */\n cacheTtlSeconds?: number;\n}\n\nexport interface Nip05Request {\n /** The 'name' query parameter from the request */\n name?: string | null;\n}\n\nexport interface Nip05HandlerResult {\n /** The NIP-05 response body */\n body: Nip05Response;\n /** Suggested headers for the response */\n headers: Record<string, string>;\n /** HTTP status code */\n status: number;\n}\n\n/**\n * Create a NIP-05 handler\n *\n * @example\n * // SvelteKit\n * import { createNip05Handler } from '@happyvertical/smrt-profiles';\n *\n * const handler = createNip05Handler({ db: { type: 'postgres', url: DATABASE_URL } });\n *\n * export async function GET({ url }) {\n * const result = await handler({ name: url.searchParams.get('name') });\n * return new Response(JSON.stringify(result.body), {\n * status: result.status,\n * headers: result.headers,\n * });\n * }\n *\n * @example\n * // Express\n * import { createNip05Handler } from '@happyvertical/smrt-profiles';\n *\n * const handler = createNip05Handler({ db: { type: 'postgres', url: DATABASE_URL } });\n *\n * app.get('/.well-known/nostr.json', async (req, res) => {\n * const result = await handler({ name: req.query.name });\n * res.status(result.status).set(result.headers).json(result.body);\n * });\n */\nexport function createNip05Handler(config: Nip05HandlerConfig) {\n const { db, defaultRelays = [], cacheTtlSeconds = 300 } = config;\n\n return async function handleNip05Request(\n request: Nip05Request,\n ): Promise<Nip05HandlerResult> {\n const { name } = request;\n\n const collection = await NostrIdentityCollection.create({ db });\n const response = await collection.getNip05Response(name || undefined);\n\n // Add default relays if configured\n if (defaultRelays.length > 0 && Object.keys(response.names).length > 0) {\n response.relays = {};\n for (const pubkey of Object.values(response.names)) {\n response.relays[pubkey] = defaultRelays;\n }\n }\n\n // If specific name requested but not found, return empty names\n if (name && Object.keys(response.names).length === 0) {\n return {\n body: { names: {} },\n headers: {\n 'Content-Type': 'application/json',\n 'Access-Control-Allow-Origin': '*',\n 'Cache-Control': `public, max-age=${cacheTtlSeconds}`,\n },\n status: 200, // NIP-05 spec says return 200 with empty names, not 404\n };\n }\n\n return {\n body: response,\n headers: {\n 'Content-Type': 'application/json',\n 'Access-Control-Allow-Origin': '*',\n 'Cache-Control': `public, max-age=${cacheTtlSeconds}`,\n },\n status: 200,\n };\n };\n}\n\n/**\n * Validate a NIP-05 identifier format\n * @param identifier - The identifier to validate (e.g., \"alice@example.com\")\n */\nexport function isValidNip05Identifier(identifier: string): boolean {\n // NIP-05 format: <local-part>@<domain>\n const parts = identifier.split('@');\n if (parts.length !== 2) {\n return false;\n }\n\n const [localPart, domain] = parts;\n\n // Local part: alphanumeric, _, -\n if (!/^[a-z0-9_-]+$/i.test(localPart)) {\n return false;\n }\n\n // Domain: basic domain validation\n if (!/^[a-z0-9.-]+\\.[a-z]{2,}$/i.test(domain)) {\n return false;\n }\n\n return true;\n}\n\n/**\n * Parse a NIP-05 identifier into its parts\n * @param identifier - The identifier to parse (e.g., \"alice@example.com\")\n */\nexport function parseNip05Identifier(identifier: string): {\n localPart: string;\n domain: string;\n} | null {\n if (!isValidNip05Identifier(identifier)) {\n return null;\n }\n\n const [localPart, domain] = identifier.split('@');\n return { localPart, domain };\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
- import { d as coordinateOidcProvisioning, f as isOidcAbortedTransactionError, m as saveOidcRaceArbiter, p as isOidcProvisioningRaceConflict } from "../chunks/ProfileCollection-DdJ3W4GG.js";
2
- import { n as insertOidcProvisioningIdentity$1, t as createOidcProvisioningPerson$1 } from "../chunks/oidcProvisioningPrimitives-C9XSQNZj.js";
1
+ import { d as coordinateOidcProvisioning, f as isOidcAbortedTransactionError, m as saveOidcRaceArbiter, p as isOidcProvisioningRaceConflict } from "../chunks/ProfileCollection-BsKhR5Dp.js";
2
+ import { n as insertOidcProvisioningIdentity$1, t as createOidcProvisioningPerson$1 } from "../chunks/oidcProvisioningPrimitives-Bzv4VTcn.js";
3
3
  //#region src/internal/oidc-provisioning.ts
4
4
  async function createOidcProvisioningPerson(db, input) {
5
5
  return await createOidcProvisioningPerson$1(db, input);