@happyvertical/smrt-profiles 0.37.2 → 0.37.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunks/ApiKey-3DlNEc91.js +145 -0
- package/dist/chunks/ApiKey-3DlNEc91.js.map +1 -0
- package/dist/chunks/ApiKeyCollection-PwVs0qDb.js +83 -0
- package/dist/chunks/ApiKeyCollection-PwVs0qDb.js.map +1 -0
- package/dist/chunks/AuditLogCollection-CnIYU0aK.js +191 -0
- package/dist/chunks/AuditLogCollection-CnIYU0aK.js.map +1 -0
- package/dist/chunks/NostrIdentityCollection-CG6HGf6c.js +3267 -0
- package/dist/chunks/NostrIdentityCollection-CG6HGf6c.js.map +1 -0
- package/dist/chunks/OidcIdentityCollection-DiLneB7J.js +158 -0
- package/dist/chunks/OidcIdentityCollection-DiLneB7J.js.map +1 -0
- package/dist/chunks/ProfileAssetCollection-BY2Wbem3.js +93 -0
- package/dist/chunks/ProfileAssetCollection-BY2Wbem3.js.map +1 -0
- package/dist/chunks/ProfileCollection-DlhdOnDr.js +641 -0
- package/dist/chunks/ProfileCollection-DlhdOnDr.js.map +1 -0
- package/dist/chunks/ProfileMetadataCollection-CGcO4Iw_.js +105 -0
- package/dist/chunks/ProfileMetadataCollection-CGcO4Iw_.js.map +1 -0
- package/dist/chunks/ProfileMetafieldCollection-C8TX9HvN.js +142 -0
- package/dist/chunks/ProfileMetafieldCollection-C8TX9HvN.js.map +1 -0
- package/dist/chunks/ProfileRelationshipCollection-JnskbZTd.js +159 -0
- package/dist/chunks/ProfileRelationshipCollection-JnskbZTd.js.map +1 -0
- package/dist/chunks/ProfileRelationshipTermCollection-UopP7sja.js +105 -0
- package/dist/chunks/ProfileRelationshipTermCollection-UopP7sja.js.map +1 -0
- package/dist/chunks/ProfileRelationshipType-OMBn5cWR.js +98 -0
- package/dist/chunks/ProfileRelationshipType-OMBn5cWR.js.map +1 -0
- package/dist/chunks/ProfileRelationshipTypeCollection-CQu08kxF.js +54 -0
- package/dist/chunks/ProfileRelationshipTypeCollection-CQu08kxF.js.map +1 -0
- package/dist/index.js +690 -69
- package/dist/index.js.map +1 -1
- package/dist/manifest.json +2 -2
- package/dist/smrt-knowledge.json +9 -9
- package/dist/types.js +0 -2
- package/dist/utils.js +18 -36
- package/dist/utils.js.map +1 -1
- package/package.json +16 -16
- package/dist/chunks/ApiKey-B2LKEaP8.js +0 -143
- package/dist/chunks/ApiKey-B2LKEaP8.js.map +0 -1
- package/dist/chunks/ApiKeyCollection-B6Op817e.js +0 -91
- package/dist/chunks/ApiKeyCollection-B6Op817e.js.map +0 -1
- package/dist/chunks/AuditLogCollection-BYqCj0uE.js +0 -195
- package/dist/chunks/AuditLogCollection-BYqCj0uE.js.map +0 -1
- package/dist/chunks/NostrIdentityCollection-DadQBHWy.js +0 -3065
- package/dist/chunks/NostrIdentityCollection-DadQBHWy.js.map +0 -1
- package/dist/chunks/ProfileAssetCollection-CUM5QfuJ.js +0 -122
- package/dist/chunks/ProfileAssetCollection-CUM5QfuJ.js.map +0 -1
- package/dist/chunks/ProfileCollection-M2PK8bp-.js +0 -800
- package/dist/chunks/ProfileCollection-M2PK8bp-.js.map +0 -1
- package/dist/chunks/ProfileMetadataCollection-DEhmljMY.js +0 -120
- package/dist/chunks/ProfileMetadataCollection-DEhmljMY.js.map +0 -1
- package/dist/chunks/ProfileMetafieldCollection-DMKhSHXX.js +0 -184
- package/dist/chunks/ProfileMetafieldCollection-DMKhSHXX.js.map +0 -1
- package/dist/chunks/ProfileRelationshipCollection-_TsY7MHL.js +0 -189
- package/dist/chunks/ProfileRelationshipCollection-_TsY7MHL.js.map +0 -1
- package/dist/chunks/ProfileRelationshipTermCollection-CXem_qT-.js +0 -117
- package/dist/chunks/ProfileRelationshipTermCollection-CXem_qT-.js.map +0 -1
- package/dist/chunks/ProfileRelationshipType-BXBLldea.js +0 -103
- package/dist/chunks/ProfileRelationshipType-BXBLldea.js.map +0 -1
- package/dist/chunks/ProfileRelationshipTypeCollection-CF8YvLTV.js +0 -48
- package/dist/chunks/ProfileRelationshipTypeCollection-CF8YvLTV.js.map +0 -1
- package/dist/chunks/index-B9lXQy7N.js +0 -1029
- package/dist/chunks/index-B9lXQy7N.js.map +0 -1
- package/dist/types.js.map +0 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["profile","oidcIdentity"],"sources":["../src/__smrt-register__.ts","../src/models/MagicLinkToken.ts","../src/collections/MagicLinkTokenCollection.ts","../src/auth/magicLinkService.ts","../src/auth/nip05Handler.ts","../src/auth/resolveIdentity.ts","../src/collections/ProfileTypeCollection.ts","../src/models/ProfileTypes.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// `new URL('./manifest.json', import.meta.url)` resolves at runtime to the\n// manifest sitting next to this module's compiled output. Vite warns at build\n// time that it cannot pre-resolve the URL; that is the intended behavior —\n// the URL must resolve to dist/manifest.json at runtime, not be inlined.\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","/**\n * resolveIdentity - Resolves authentication context to a Profile\n *\n * Used by apps (blindmanpress.com) to resolve incoming auth to a Profile.\n * Modules (aedile, praeco) should receive the resolved profile from the app.\n *\n * Resolution order:\n * 1. API key header → ApiKey → Profile\n * 2. OIDC session → OidcIdentity → Profile\n * 3. Nostr auth → NostrIdentity → Profile\n * 4. Actor header (CI pass-through) → Profile lookup\n */\n\nimport type { SmrtObjectOptions } from '@happyvertical/smrt-core';\nimport { ApiKey } from '../models/ApiKey';\nimport { NostrIdentity } from '../models/NostrIdentity';\nimport { OidcIdentity } from '../models/OidcIdentity';\nimport type { Profile } from '../models/Profile';\nimport { type NostrEvent, verifyAuthEvent } from './nostrCrypto';\n\n/**\n * Context provided to resolveIdentity\n */\nexport interface AuthContext {\n /**\n * API key from X-API-Key header or similar\n */\n apiKey?: string | null;\n\n /**\n * OIDC session data (from @auth/sveltekit or similar)\n */\n oidcSession?: {\n sub?: string;\n iss?: string;\n email?: string;\n name?: string;\n } | null;\n\n /**\n * Actor identifier for CI pass-through identity\n * Usually the GitHub actor (username) who triggered the workflow\n */\n actor?: string | null;\n\n /**\n * Nostr authentication data (NIP-42 style)\n */\n nostrAuth?: {\n /** Signed Nostr event for authentication */\n event: NostrEvent;\n /** Expected challenge (to prevent replay attacks) */\n challenge: string;\n } | null;\n\n /**\n * Database/persistence options\n */\n db?: SmrtObjectOptions['db'];\n}\n\n/**\n * Result of identity resolution\n */\nexport interface ResolveIdentityResult {\n /**\n * The resolved profile, or null if not authenticated\n */\n profile: Profile | null;\n\n /**\n * How the identity was resolved\n */\n source: 'api_key' | 'oidc' | 'nostr' | 'actor' | 'none';\n\n /**\n * The API key record if authenticated via API key\n */\n apiKey?: ApiKey;\n\n /**\n * The OIDC identity record if authenticated via OIDC\n */\n oidcIdentity?: OidcIdentity;\n\n /**\n * The Nostr identity record if authenticated via Nostr\n */\n nostrIdentity?: NostrIdentity;\n}\n\n/**\n * Resolve authentication context to a Profile\n *\n * @param context - The authentication context from the request\n * @returns The resolved profile and metadata\n *\n * @example\n * ```typescript\n * // In SvelteKit hooks.server.ts\n * import { resolveIdentity } from '@happyvertical/smrt-profiles';\n *\n * const identityMiddleware: Handle = async ({ event, resolve }) => {\n * const session = await event.locals.auth();\n *\n * const { profile, source } = await resolveIdentity({\n * oidcSession: session,\n * apiKey: event.request.headers.get('X-API-Key'),\n * actor: event.request.headers.get('X-Actor'),\n * db: { type: 'postgres', url: DATABASE_URL },\n * });\n *\n * event.locals.profile = profile;\n * event.locals.authSource = source;\n *\n * return resolve(event);\n * };\n * ```\n */\nexport async function resolveIdentity(\n context: AuthContext,\n): Promise<ResolveIdentityResult> {\n const options: SmrtObjectOptions = { db: context.db };\n\n // 1. Check API key header first (highest priority for programmatic access)\n if (context.apiKey) {\n const apiKey = await ApiKey.verify(context.apiKey, options);\n if (apiKey) {\n const profile = await apiKey.getProfile();\n if (profile) {\n return {\n profile,\n source: 'api_key',\n apiKey,\n };\n }\n }\n }\n\n // 2. Check OIDC session (web users)\n if (context.oidcSession?.sub && context.oidcSession?.iss) {\n const oidcIdentity = await OidcIdentity.findBySubject(\n context.oidcSession.iss,\n context.oidcSession.sub,\n options,\n );\n\n if (oidcIdentity) {\n // Record usage\n await oidcIdentity.recordUsage();\n\n const profile = await oidcIdentity.getProfile();\n if (profile) {\n return {\n profile,\n source: 'oidc',\n oidcIdentity,\n };\n }\n }\n }\n\n // 3. Check Nostr auth (NIP-42 style signed event)\n if (context.nostrAuth?.event && context.nostrAuth?.challenge) {\n const { event, challenge } = context.nostrAuth;\n\n // Verify the auth event\n const verifyResult = verifyAuthEvent(event, challenge);\n if (verifyResult.valid) {\n // Look up identity by public key\n const nostrIdentity = await NostrIdentity.findByPubkey(\n event.pubkey,\n options,\n );\n\n if (nostrIdentity) {\n // Record usage\n await nostrIdentity.recordUsage();\n\n const profile = await nostrIdentity.getProfile();\n if (profile) {\n return {\n profile,\n source: 'nostr',\n nostrIdentity,\n };\n }\n }\n }\n }\n\n // 4. Check actor for CI pass-through (look up by metadata)\n if (context.actor) {\n const profile = await findProfileByExternalId(\n 'github',\n context.actor,\n options,\n );\n if (profile) {\n return {\n profile,\n source: 'actor',\n };\n }\n }\n\n // No authentication found\n return {\n profile: null,\n source: 'none',\n };\n}\n\n/**\n * Find a profile by external ID (e.g., GitHub username)\n *\n * This looks up profiles by their linked external identities or metadata.\n *\n * @param provider - The external provider (e.g., 'github')\n * @param externalId - The external identifier (e.g., GitHub username)\n * @param options - Database options\n * @returns The profile or null\n */\nasync function findProfileByExternalId(\n provider: string,\n externalId: string,\n options: SmrtObjectOptions,\n): Promise<Profile | null> {\n // First check OIDC identities (if they use the provider)\n const { OidcIdentityCollection } = await import(\n '../collections/OidcIdentityCollection'\n );\n\n const oidcCollection = await OidcIdentityCollection.create(options);\n const identities = await oidcCollection.findByProvider(provider);\n\n for (const identity of identities) {\n // Check if the subject matches the external ID\n if (\n identity.subject === externalId ||\n identity.email?.includes(externalId)\n ) {\n const profile = await identity.getProfile();\n if (profile) return profile;\n }\n }\n\n // Could also check profile metadata for external IDs\n // This would require a standardized metadata field like 'github_username'\n const { ProfileCollection } = await import(\n '../collections/ProfileCollection'\n );\n const profileCollection = await ProfileCollection.create(options);\n\n // Try to find by email that looks like a GitHub noreply\n const profiles = await profileCollection.list({\n where: {\n email: `${externalId}@users.noreply.github.com`,\n },\n limit: 1,\n });\n\n if (profiles.length > 0) {\n return profiles[0];\n }\n\n return null;\n}\n\n/**\n * Create a profile from OIDC claims if it doesn't exist\n *\n * Supports email-based account linking: if a user signs in with Google\n * and later with GitHub using the same email, they get the same profile.\n *\n * Resolution order:\n * 1. If OIDC identity (iss + sub) already exists → return linked profile\n * 2. If verified email provided, check if profile with same email exists → link new identity\n * 3. Otherwise, create new profile + identity\n *\n * Security considerations:\n * - Email-based linking only occurs when `email_verified` is true. This prevents\n * attackers from claiming unverified emails to hijack accounts.\n * - Linking is automatic and irreversible through this API. Multiple OIDC\n * identities from different providers sharing the same verified email will\n * be associated to the same Profile.\n * - If an OIDC provider does not supply an email, or the email changes later,\n * existing links are not automatically updated. New sign-ins without an email\n * or with a different email may result in a new Profile being created.\n * - This function trusts the OIDC provider to assert correct email_verified status.\n * Only use with trusted providers.\n *\n * @param claims - OIDC token claims\n * @param provider - Provider name (e.g., 'keycloak', 'google', 'github')\n * @param options - Database options\n * @returns The created or existing profile with linked OIDC identity\n */\nexport async function createProfileFromOidc(\n claims: {\n sub: string;\n iss: string;\n email?: string;\n email_verified?: boolean;\n name?: string;\n preferred_username?: string;\n },\n provider: string,\n options: SmrtObjectOptions,\n): Promise<{ profile: Profile; oidcIdentity: OidcIdentity; created: boolean }> {\n // 1. If OIDC identity already exists for this issuer+subject, return linked profile\n const existingIdentity = await OidcIdentity.findBySubject(\n claims.iss,\n claims.sub,\n options,\n );\n\n if (existingIdentity) {\n const profile = await existingIdentity.getProfile();\n if (profile) {\n // Update identity with latest claims\n if (claims.email) existingIdentity.email = claims.email;\n existingIdentity.lastUsedAt = new Date();\n await existingIdentity.save();\n\n return {\n profile,\n oidcIdentity: existingIdentity,\n created: false,\n };\n }\n }\n\n // 2. Email-based account linking: only link if email is verified (security)\n if (claims.email && claims.email_verified) {\n const { ProfileCollection } = await import(\n '../collections/ProfileCollection'\n );\n\n const profileCollection = await ProfileCollection.create(options);\n const existingProfile = await profileCollection.findByEmail(claims.email);\n\n if (existingProfile) {\n // Link new OIDC identity to existing profile (email-based linking)\n const oidcIdentity = await OidcIdentity.findOrCreate(\n existingProfile,\n {\n provider,\n issuer: claims.iss,\n subject: claims.sub,\n email: claims.email,\n },\n options,\n );\n\n return {\n profile: existingProfile,\n oidcIdentity,\n created: false,\n };\n }\n }\n\n // 3. Create new profile\n const { Person } = await import('../models/ProfileTypes');\n const { ProfileTypeCollection } = await import(\n '../collections/ProfileTypeCollection'\n );\n\n // Get or create the 'person' type\n const typeCollection = await ProfileTypeCollection.create(options);\n let personType = await typeCollection.getBySlug('person');\n\n if (!personType) {\n // Create the person type if it doesn't exist\n const { ProfileType } = await import('../models/ProfileType');\n personType = new ProfileType({\n ...options,\n slug: 'person',\n name: 'Person',\n description: 'Individual person profile',\n });\n await personType.initialize();\n await personType.save();\n }\n\n const profile = new Person({\n ...options,\n typeId: personType.id as string,\n email: claims.email || '',\n name: claims.name || claims.preferred_username || claims.sub,\n });\n await profile.initialize();\n await profile.save();\n\n // Link OIDC identity\n const oidcIdentity = await OidcIdentity.findOrCreate(\n profile,\n {\n provider,\n issuer: claims.iss,\n subject: claims.sub,\n email: claims.email,\n },\n options,\n );\n\n return {\n profile,\n oidcIdentity,\n created: true,\n };\n}\n\n/**\n * Create a profile from Nostr identity (used by magic link service)\n *\n * This is typically called internally by the magic link service,\n * but can be used directly if needed.\n *\n * @param email - Email address for the profile\n * @param nostrData - Encrypted Nostr keypair data\n * @param options - Database options\n * @returns The created profile with linked Nostr identity\n */\nexport async function createProfileFromNostr(\n email: string,\n nostrData: {\n pubkey: string;\n encryptedPrivkey: string;\n encryptionIv: string;\n encryptionTag: string;\n nip05Username?: string;\n },\n options: SmrtObjectOptions,\n): Promise<{\n profile: Profile;\n nostrIdentity: NostrIdentity;\n created: boolean;\n}> {\n const normalizedEmail = email.toLowerCase();\n\n // Check if Nostr identity already exists\n const existingIdentity = await NostrIdentity.findByEmail(\n normalizedEmail,\n options,\n );\n\n if (existingIdentity) {\n const profile = await existingIdentity.getProfile();\n if (profile) {\n return {\n profile,\n nostrIdentity: existingIdentity,\n created: false,\n };\n }\n }\n\n // Create new profile\n const { Person } = await import('../models/ProfileTypes');\n const { ProfileTypeCollection } = await import(\n '../collections/ProfileTypeCollection'\n );\n\n // Get or create the 'person' type\n const typeCollection = await ProfileTypeCollection.create(options);\n let personType = await typeCollection.getBySlug('person');\n\n if (!personType) {\n const { ProfileType } = await import('../models/ProfileType');\n personType = new ProfileType({\n ...options,\n slug: 'person',\n name: 'Person',\n description: 'Individual person profile',\n });\n await personType.initialize();\n await personType.save();\n }\n\n const profile = new Person({\n ...options,\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 // Create Nostr identity\n const nostrIdentity = new NostrIdentity({\n ...options,\n profileId: profile.id as string,\n pubkey: nostrData.pubkey,\n encryptedPrivkey: nostrData.encryptedPrivkey,\n encryptionIv: nostrData.encryptionIv,\n encryptionTag: nostrData.encryptionTag,\n email: normalizedEmail,\n nip05Username:\n nostrData.nip05Username?.toLowerCase() || normalizedEmail.split('@')[0],\n });\n await nostrIdentity.initialize();\n await nostrIdentity.save();\n\n return {\n profile,\n nostrIdentity,\n created: true,\n };\n}\n","/**\n * ProfileTypeCollection - Collection manager for ProfileType objects\n *\n * Provides querying for profile type lookup table.\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { ProfileType } from '../models/ProfileType';\n\nexport class ProfileTypeCollection extends SmrtCollection<ProfileType> {\n static readonly _itemClass = ProfileType;\n\n /**\n * Get profile type by slug\n *\n * @param slug - The slug to search for\n * @returns ProfileType instance or null\n */\n async getBySlug(slug: string): Promise<ProfileType | null> {\n return await this.get({ slug });\n }\n\n /**\n * Get or create a profile type by slug\n *\n * @param slug - The slug to search for\n * @param defaults - Default values if creating\n * @returns ProfileType instance\n */\n async getOrCreateBySlug(\n slug: string,\n defaults: { name: string; description?: string },\n ): Promise<ProfileType> {\n const existing = await this.getBySlug(slug);\n if (existing) return existing;\n\n const profileType = await this.create({ slug, ...defaults });\n await profileType.save();\n return profileType;\n }\n}\n","/**\n * Profile subclasses for STI (Single Table Inheritance)\n *\n * These classes inherit from Profile and automatically use the shared\n * profiles table with _meta_type discriminator.\n */\n\nimport { smrt } from '@happyvertical/smrt-core';\nimport { Profile, type ProfileOptions } from './Profile';\n\n/**\n * Person profile type\n *\n * Represents individual people/users.\n */\n@smrt({\n tableStrategy: 'sti',\n})\nexport class Person extends Profile {\n constructor(options: ProfileOptions = {}) {\n super(options);\n // Person-specific initialization can go here\n }\n}\n\n/**\n * Organization profile type\n *\n * Represents companies, groups, institutions, and other organizational entities.\n */\n@smrt({\n tableStrategy: 'sti',\n})\nexport class Organization extends Profile {\n constructor(options: ProfileOptions = {}) {\n super(options);\n // Organization-specific initialization can go here\n }\n}\n\n/**\n * Bot profile type\n *\n * Represents automated agents, bots, and AI entities.\n */\n@smrt({\n tableStrategy: 'sti',\n})\nexport class Bot extends Profile {\n constructor(options: ProfileOptions = {}) {\n super(options);\n // Bot-specific initialization can go here\n }\n}\n"],"mappings":"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"}
|
package/dist/manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": "1.0.0",
|
|
3
|
-
"timestamp":
|
|
3
|
+
"timestamp": 1782943608773,
|
|
4
4
|
"packageName": "@happyvertical/smrt-profiles",
|
|
5
|
-
"packageVersion": "0.37.
|
|
5
|
+
"packageVersion": "0.37.4",
|
|
6
6
|
"objects": {
|
|
7
7
|
"@happyvertical/smrt-profiles:ApiKeyCollection": {
|
|
8
8
|
"name": "apikeycollection",
|
package/dist/smrt-knowledge.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"generatedAt": "2026-07-
|
|
3
|
+
"generatedAt": "2026-07-01T22:06:52.193Z",
|
|
4
4
|
"packageName": "@happyvertical/smrt-profiles",
|
|
5
|
-
"packageVersion": "0.37.
|
|
5
|
+
"packageVersion": "0.37.4",
|
|
6
6
|
"sourceManifestPath": "dist/manifest.json",
|
|
7
7
|
"agentDocPath": "AGENTS.md",
|
|
8
8
|
"sourceHashes": {
|
|
9
|
-
"manifest": "
|
|
10
|
-
"packageJson": "
|
|
9
|
+
"manifest": "07d3c739725bd4ae68b3dd3f39966075487a3382990046dc347d0d441567bab0",
|
|
10
|
+
"packageJson": "f7c7cf08ae3a78d9d2f107ce2ee56137db4a826a870616d61be96373ab2cd3dd",
|
|
11
11
|
"agents": "3c18e895a2e065f761aabf31779c71a138f0a75261ab223464c3b2a5fbbe6b16"
|
|
12
12
|
},
|
|
13
13
|
"exports": [
|
|
@@ -26,15 +26,15 @@
|
|
|
26
26
|
"@happyvertical/smrt-tenancy": "workspace:*",
|
|
27
27
|
"@happyvertical/sql": "catalog:",
|
|
28
28
|
"@happyvertical/utils": "catalog:",
|
|
29
|
-
"@noble/curves": "^1.
|
|
29
|
+
"@noble/curves": "^1.9.7",
|
|
30
30
|
"bech32": "^2.0.0",
|
|
31
|
-
"@faker-js/faker": "^10.
|
|
31
|
+
"@faker-js/faker": "^10.5.0",
|
|
32
32
|
"@happyvertical/smrt-vitest": "workspace:*",
|
|
33
|
-
"@types/node": "
|
|
33
|
+
"@types/node": "24.13.2",
|
|
34
34
|
"fast-glob": "3.3.3",
|
|
35
35
|
"typescript": "^5.9.3",
|
|
36
|
-
"vite": "^
|
|
37
|
-
"vitest": "^4.
|
|
36
|
+
"vite": "^8.1.2",
|
|
37
|
+
"vitest": "^4.1.9"
|
|
38
38
|
},
|
|
39
39
|
"smrtDependencies": [
|
|
40
40
|
"@happyvertical/smrt-assets",
|
package/dist/types.js
CHANGED
package/dist/utils.js
CHANGED
|
@@ -1,46 +1,28 @@
|
|
|
1
|
+
//#region src/utils.ts
|
|
1
2
|
async function getProfileMetadata(profileId, options = {}) {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
return await metadataCollection.getMetadataObject(profileId);
|
|
3
|
+
const { ProfileMetadataCollection } = await import("./chunks/ProfileMetadataCollection-CGcO4Iw_.js").then((n) => n.n);
|
|
4
|
+
return await (await ProfileMetadataCollection.create(options)).getMetadataObject(profileId);
|
|
5
5
|
}
|
|
6
6
|
async function setProfileMetadata(profileId, metafieldSlug, value, options = {}) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
throw new Error(`Profile '${profileId}' not found`);
|
|
12
|
-
}
|
|
13
|
-
await profile.addMetadata(metafieldSlug, value);
|
|
7
|
+
const { ProfileCollection } = await import("./chunks/ProfileCollection-DlhdOnDr.js").then((n) => n.n);
|
|
8
|
+
const profile = await (await ProfileCollection.create(options)).get({ id: profileId });
|
|
9
|
+
if (!profile) throw new Error(`Profile '${profileId}' not found`);
|
|
10
|
+
await profile.addMetadata(metafieldSlug, value);
|
|
14
11
|
}
|
|
15
12
|
async function findProfilesByMeta(metafieldSlug, value, options = {}) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
throw new Error(`Metafield '${metafieldSlug}' not found`);
|
|
22
|
-
}
|
|
23
|
-
const metadataCollection = await ProfileMetadataCollection.create(options);
|
|
24
|
-
return await metadataCollection.findProfilesByMetadata(
|
|
25
|
-
metafield.id,
|
|
26
|
-
value
|
|
27
|
-
);
|
|
13
|
+
const { ProfileMetafieldCollection } = await import("./chunks/ProfileMetafieldCollection-C8TX9HvN.js").then((n) => n.n);
|
|
14
|
+
const { ProfileMetadataCollection } = await import("./chunks/ProfileMetadataCollection-CGcO4Iw_.js").then((n) => n.n);
|
|
15
|
+
const metafield = await (await ProfileMetafieldCollection.create(options)).getBySlug(metafieldSlug);
|
|
16
|
+
if (!metafield) throw new Error(`Metafield '${metafieldSlug}' not found`);
|
|
17
|
+
return await (await ProfileMetadataCollection.create(options)).findProfilesByMetadata(metafield.id, value);
|
|
28
18
|
}
|
|
29
19
|
async function createReciprocalRelationship(fromProfile, toProfile, relationshipSlug, contextProfile) {
|
|
30
|
-
|
|
31
|
-
toProfile,
|
|
32
|
-
relationshipSlug,
|
|
33
|
-
contextProfile
|
|
34
|
-
);
|
|
20
|
+
await fromProfile.addRelationship(toProfile, relationshipSlug, contextProfile);
|
|
35
21
|
}
|
|
36
22
|
async function validateMetadataValue(metafield, value) {
|
|
37
|
-
|
|
23
|
+
return await metafield.validateValue(value);
|
|
38
24
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
setProfileMetadata,
|
|
44
|
-
validateMetadataValue
|
|
45
|
-
};
|
|
46
|
-
//# sourceMappingURL=utils.js.map
|
|
25
|
+
//#endregion
|
|
26
|
+
export { createReciprocalRelationship, findProfilesByMeta, getProfileMetadata, setProfileMetadata, validateMetadataValue };
|
|
27
|
+
|
|
28
|
+
//# sourceMappingURL=utils.js.map
|
package/dist/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sources":["../src/utils.ts"],"sourcesContent":["/**\n * Utility functions for profile operations\n *\n * Provides standalone helper functions for metadata management,\n * relationship operations, and validation.\n *\n * @packageDocumentation\n */\n\n// Utility functions will be implemented as part of Phase 2\n// This file serves as a placeholder for the API structure\n\nimport type { SmrtObjectOptions } from '@happyvertical/smrt-core';\nimport type { Profile } from './models/Profile';\nimport type { ProfileMetafield } from './models/ProfileMetafield';\n\n/**\n * Retrieve all metadata for a profile as a key-value object\n *\n * @param profileId - The UUID of the profile\n * @param options - Database and configuration options\n * @returns Object with metafield slugs as keys and values as values\n */\nexport async function getProfileMetadata(\n profileId: string,\n options: SmrtObjectOptions = {},\n): Promise<Record<string, string>> {\n const { ProfileMetadataCollection } = await import(\n './collections/ProfileMetadataCollection'\n );\n\n const metadataCollection = await ProfileMetadataCollection.create(options);\n\n return await metadataCollection.getMetadataObject(profileId);\n}\n\n/**\n * Set a single metadata value for a profile\n *\n * @param profileId - The UUID of the profile\n * @param metafieldSlug - The slug of the metafield\n * @param value - The value to set\n * @param options - Database and configuration options\n */\nexport async function setProfileMetadata(\n profileId: string,\n metafieldSlug: string,\n value: unknown,\n options: SmrtObjectOptions = {},\n): Promise<void> {\n const { ProfileCollection } = await import('./collections/ProfileCollection');\n\n const profileCollection = await ProfileCollection.create(options);\n\n const profile = await profileCollection.get({ id: profileId });\n if (!profile) {\n throw new Error(`Profile '${profileId}' not found`);\n }\n\n await profile.addMetadata(metafieldSlug, value);\n}\n\n/**\n * Find profiles with a specific metadata key-value pair\n *\n * @param metafieldSlug - The slug of the metafield to search\n * @param value - The value to match\n * @param options - Database and configuration options\n * @returns Array of matching profile IDs\n */\nexport async function findProfilesByMeta(\n metafieldSlug: string,\n value: unknown,\n options: SmrtObjectOptions = {},\n): Promise<string[]> {\n const { ProfileMetafieldCollection } = await import(\n './collections/ProfileMetafieldCollection'\n );\n const { ProfileMetadataCollection } = await import(\n './collections/ProfileMetadataCollection'\n );\n\n // Get metafield by slug\n const metafieldCollection = await ProfileMetafieldCollection.create(options);\n\n const metafield = await metafieldCollection.getBySlug(metafieldSlug);\n if (!metafield) {\n throw new Error(`Metafield '${metafieldSlug}' not found`);\n }\n\n // Find all metadata with this metafield and value\n const metadataCollection = await ProfileMetadataCollection.create(options);\n\n return await metadataCollection.findProfilesByMetadata(\n metafield.id as string,\n value,\n );\n}\n\n/**\n * Create a reciprocal relationship between two profiles\n *\n * This is a convenience function that creates a relationship and automatically\n * handles the reciprocal relationship based on the relationship type configuration.\n *\n * @param fromProfile - The initiating profile\n * @param toProfile - The target profile\n * @param relationshipSlug - The type of relationship\n * @param contextProfile - Optional context profile for tertiary relationships\n */\nexport async function createReciprocalRelationship(\n fromProfile: Profile,\n toProfile: Profile,\n relationshipSlug: string,\n contextProfile?: Profile,\n): Promise<void> {\n // The Profile.addRelationship method already handles reciprocal relationships\n await fromProfile.addRelationship(\n toProfile,\n relationshipSlug,\n contextProfile,\n );\n}\n\n/**\n * Validate a metadata value against a metafield's validation schema\n *\n * @param metafield - The metafield with validation rules\n * @param value - The value to validate\n * @returns True if valid, throws error if invalid\n */\nexport async function validateMetadataValue(\n metafield: ProfileMetafield,\n value: unknown,\n): Promise<boolean> {\n // The ProfileMetafield.validateValue method already implements validation\n return await metafield.validateValue(value);\n}\n"],"
|
|
1
|
+
{"version":3,"file":"utils.js","names":[],"sources":["../src/utils.ts"],"sourcesContent":["/**\n * Utility functions for profile operations\n *\n * Provides standalone helper functions for metadata management,\n * relationship operations, and validation.\n *\n * @packageDocumentation\n */\n\n// Utility functions will be implemented as part of Phase 2\n// This file serves as a placeholder for the API structure\n\nimport type { SmrtObjectOptions } from '@happyvertical/smrt-core';\nimport type { Profile } from './models/Profile';\nimport type { ProfileMetafield } from './models/ProfileMetafield';\n\n/**\n * Retrieve all metadata for a profile as a key-value object\n *\n * @param profileId - The UUID of the profile\n * @param options - Database and configuration options\n * @returns Object with metafield slugs as keys and values as values\n */\nexport async function getProfileMetadata(\n profileId: string,\n options: SmrtObjectOptions = {},\n): Promise<Record<string, string>> {\n const { ProfileMetadataCollection } = await import(\n './collections/ProfileMetadataCollection'\n );\n\n const metadataCollection = await ProfileMetadataCollection.create(options);\n\n return await metadataCollection.getMetadataObject(profileId);\n}\n\n/**\n * Set a single metadata value for a profile\n *\n * @param profileId - The UUID of the profile\n * @param metafieldSlug - The slug of the metafield\n * @param value - The value to set\n * @param options - Database and configuration options\n */\nexport async function setProfileMetadata(\n profileId: string,\n metafieldSlug: string,\n value: unknown,\n options: SmrtObjectOptions = {},\n): Promise<void> {\n const { ProfileCollection } = await import('./collections/ProfileCollection');\n\n const profileCollection = await ProfileCollection.create(options);\n\n const profile = await profileCollection.get({ id: profileId });\n if (!profile) {\n throw new Error(`Profile '${profileId}' not found`);\n }\n\n await profile.addMetadata(metafieldSlug, value);\n}\n\n/**\n * Find profiles with a specific metadata key-value pair\n *\n * @param metafieldSlug - The slug of the metafield to search\n * @param value - The value to match\n * @param options - Database and configuration options\n * @returns Array of matching profile IDs\n */\nexport async function findProfilesByMeta(\n metafieldSlug: string,\n value: unknown,\n options: SmrtObjectOptions = {},\n): Promise<string[]> {\n const { ProfileMetafieldCollection } = await import(\n './collections/ProfileMetafieldCollection'\n );\n const { ProfileMetadataCollection } = await import(\n './collections/ProfileMetadataCollection'\n );\n\n // Get metafield by slug\n const metafieldCollection = await ProfileMetafieldCollection.create(options);\n\n const metafield = await metafieldCollection.getBySlug(metafieldSlug);\n if (!metafield) {\n throw new Error(`Metafield '${metafieldSlug}' not found`);\n }\n\n // Find all metadata with this metafield and value\n const metadataCollection = await ProfileMetadataCollection.create(options);\n\n return await metadataCollection.findProfilesByMetadata(\n metafield.id as string,\n value,\n );\n}\n\n/**\n * Create a reciprocal relationship between two profiles\n *\n * This is a convenience function that creates a relationship and automatically\n * handles the reciprocal relationship based on the relationship type configuration.\n *\n * @param fromProfile - The initiating profile\n * @param toProfile - The target profile\n * @param relationshipSlug - The type of relationship\n * @param contextProfile - Optional context profile for tertiary relationships\n */\nexport async function createReciprocalRelationship(\n fromProfile: Profile,\n toProfile: Profile,\n relationshipSlug: string,\n contextProfile?: Profile,\n): Promise<void> {\n // The Profile.addRelationship method already handles reciprocal relationships\n await fromProfile.addRelationship(\n toProfile,\n relationshipSlug,\n contextProfile,\n );\n}\n\n/**\n * Validate a metadata value against a metafield's validation schema\n *\n * @param metafield - The metafield with validation rules\n * @param value - The value to validate\n * @returns True if valid, throws error if invalid\n */\nexport async function validateMetadataValue(\n metafield: ProfileMetafield,\n value: unknown,\n): Promise<boolean> {\n // The ProfileMetafield.validateValue method already implements validation\n return await metafield.validateValue(value);\n}\n"],"mappings":";AAuBA,eAAsB,mBACpB,WACA,UAA6B,CAAC,GACG;CACjC,MAAM,EAAE,8BAA8B,MAAM,OAC1C,iDAAA,CAAA,MAAA,MAAA,EAAA,CAAA;CAKF,OAAO,OAAM,MAFoB,0BAA0B,OAAO,OAAO,EAAA,CAEzC,kBAAkB,SAAS;AAC7D;AAUA,eAAsB,mBACpB,WACA,eACA,OACA,UAA6B,CAAC,GACf;CACf,MAAM,EAAE,sBAAsB,MAAM,OAAO,yCAAA,CAAA,MAAA,MAAA,EAAA,CAAA;CAI3C,MAAM,UAAU,OAAM,MAFU,kBAAkB,OAAO,OAAO,EAAA,CAExB,IAAI,EAAE,IAAI,UAAU,CAAC;CAC7D,IAAI,CAAC,SACH,MAAM,IAAI,MAAM,YAAY,UAAS,YAAa;CAGpD,MAAM,QAAQ,YAAY,eAAe,KAAK;AAChD;AAUA,eAAsB,mBACpB,eACA,OACA,UAA6B,CAAC,GACX;CACnB,MAAM,EAAE,+BAA+B,MAAM,OAC3C,kDAAA,CAAA,MAAA,MAAA,EAAA,CAAA;CAEF,MAAM,EAAE,8BAA8B,MAAM,OAC1C,iDAAA,CAAA,MAAA,MAAA,EAAA,CAAA;CAMF,MAAM,YAAY,OAAM,MAFU,2BAA2B,OAAO,OAAO,EAAA,CAE/B,UAAU,aAAa;CACnE,IAAI,CAAC,WACH,MAAM,IAAI,MAAM,cAAc,cAAa,YAAa;CAM1D,OAAO,OAAM,MAFoB,0BAA0B,OAAO,OAAO,EAAA,CAEzC,uBAC9B,UAAU,IACV,KACF;AACF;AAaA,eAAsB,6BACpB,aACA,WACA,kBACA,gBACe;CAEf,MAAM,YAAY,gBAChB,WACA,kBACA,cACF;AACF;AASA,eAAsB,sBACpB,WACA,OACkB;CAElB,OAAO,MAAM,UAAU,cAAc,KAAK;AAC5C"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@happyvertical/smrt-profiles",
|
|
3
|
-
"version": "0.37.
|
|
3
|
+
"version": "0.37.4",
|
|
4
4
|
"description": "Profile management system with relationships, metadata, and reciprocal associations for SMRT framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -23,26 +23,26 @@
|
|
|
23
23
|
"./manifest.json": "./dist/manifest.json"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@happyvertical/ai": "^0.74.
|
|
27
|
-
"@happyvertical/files": "^0.74.
|
|
28
|
-
"@happyvertical/logger": "^0.74.
|
|
29
|
-
"@happyvertical/sql": "^0.74.
|
|
30
|
-
"@happyvertical/utils": "^0.74.
|
|
31
|
-
"@noble/curves": "^1.
|
|
26
|
+
"@happyvertical/ai": "^0.74.11",
|
|
27
|
+
"@happyvertical/files": "^0.74.11",
|
|
28
|
+
"@happyvertical/logger": "^0.74.11",
|
|
29
|
+
"@happyvertical/sql": "^0.74.11",
|
|
30
|
+
"@happyvertical/utils": "^0.74.11",
|
|
31
|
+
"@noble/curves": "^1.9.7",
|
|
32
32
|
"bech32": "^2.0.0",
|
|
33
|
-
"@happyvertical/smrt-assets": "0.37.
|
|
34
|
-
"@happyvertical/smrt-core": "0.37.
|
|
35
|
-
"@happyvertical/smrt-prompts": "0.37.
|
|
36
|
-
"@happyvertical/smrt-tenancy": "0.37.
|
|
33
|
+
"@happyvertical/smrt-assets": "0.37.4",
|
|
34
|
+
"@happyvertical/smrt-core": "0.37.4",
|
|
35
|
+
"@happyvertical/smrt-prompts": "0.37.4",
|
|
36
|
+
"@happyvertical/smrt-tenancy": "0.37.4"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@faker-js/faker": "^10.
|
|
40
|
-
"@types/node": "
|
|
39
|
+
"@faker-js/faker": "^10.5.0",
|
|
40
|
+
"@types/node": "24.13.2",
|
|
41
41
|
"fast-glob": "3.3.3",
|
|
42
42
|
"typescript": "^5.9.3",
|
|
43
|
-
"vite": "^
|
|
44
|
-
"vitest": "^4.
|
|
45
|
-
"@happyvertical/smrt-vitest": "0.37.
|
|
43
|
+
"vite": "^8.1.2",
|
|
44
|
+
"vitest": "^4.1.9",
|
|
45
|
+
"@happyvertical/smrt-vitest": "0.37.4"
|
|
46
46
|
},
|
|
47
47
|
"keywords": [
|
|
48
48
|
"ai",
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
import { createHash, randomBytes } from "node:crypto";
|
|
2
|
-
import { foreignKey, smrt, SmrtObject } from "@happyvertical/smrt-core";
|
|
3
|
-
var __defProp = Object.defineProperty;
|
|
4
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
-
var __decorateClass = (decorators, target, key, kind) => {
|
|
6
|
-
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
7
|
-
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
8
|
-
if (decorator = decorators[i])
|
|
9
|
-
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
10
|
-
if (kind && result) __defProp(target, key, result);
|
|
11
|
-
return result;
|
|
12
|
-
};
|
|
13
|
-
let ApiKey = class extends SmrtObject {
|
|
14
|
-
profileId;
|
|
15
|
-
/**
|
|
16
|
-
* SHA-256 hash of the API key
|
|
17
|
-
*/
|
|
18
|
-
keyHash = "";
|
|
19
|
-
/**
|
|
20
|
-
* First 8 characters of the key for identification (e.g., "sk_live_a1b2...")
|
|
21
|
-
*/
|
|
22
|
-
keyPrefix = "";
|
|
23
|
-
/**
|
|
24
|
-
* Human-readable name for the key (e.g., "CI Bot", "Local CLI")
|
|
25
|
-
*/
|
|
26
|
-
name = "";
|
|
27
|
-
/**
|
|
28
|
-
* Scopes/permissions for this key
|
|
29
|
-
*/
|
|
30
|
-
scopes = [];
|
|
31
|
-
/**
|
|
32
|
-
* Last time this key was used
|
|
33
|
-
*/
|
|
34
|
-
lastUsedAt = null;
|
|
35
|
-
/**
|
|
36
|
-
* When this key expires (null = never)
|
|
37
|
-
*/
|
|
38
|
-
expiresAt = null;
|
|
39
|
-
/**
|
|
40
|
-
* When this key was revoked (null = active)
|
|
41
|
-
*/
|
|
42
|
-
revokedAt = null;
|
|
43
|
-
constructor(options = {}) {
|
|
44
|
-
super(options);
|
|
45
|
-
if (options.profileId) this.profileId = options.profileId;
|
|
46
|
-
if (options.name) this.name = options.name;
|
|
47
|
-
if (options.scopes) this.scopes = options.scopes;
|
|
48
|
-
if (options.expiresAt !== void 0) this.expiresAt = options.expiresAt;
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* Get the linked Profile
|
|
52
|
-
*/
|
|
53
|
-
async getProfile() {
|
|
54
|
-
return await this.getRelated("profileId");
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Check if this key is valid (not expired, not revoked)
|
|
58
|
-
*/
|
|
59
|
-
isValid() {
|
|
60
|
-
if (this.revokedAt) return false;
|
|
61
|
-
if (this.expiresAt && this.expiresAt < /* @__PURE__ */ new Date()) return false;
|
|
62
|
-
return true;
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Check if this key has a specific scope
|
|
66
|
-
*/
|
|
67
|
-
hasScope(scope) {
|
|
68
|
-
return this.scopes.includes(scope) || this.scopes.includes("*");
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Revoke this key
|
|
72
|
-
*/
|
|
73
|
-
async revoke() {
|
|
74
|
-
this.revokedAt = /* @__PURE__ */ new Date();
|
|
75
|
-
await this.save();
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* Record usage of this key
|
|
79
|
-
*/
|
|
80
|
-
async recordUsage() {
|
|
81
|
-
this.lastUsedAt = /* @__PURE__ */ new Date();
|
|
82
|
-
await this.save();
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* Hash an API key
|
|
86
|
-
*/
|
|
87
|
-
static hashKey(key) {
|
|
88
|
-
return createHash("sha256").update(key).digest("hex");
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* Generate a new API key for a profile
|
|
92
|
-
*/
|
|
93
|
-
static async generate(profile, options) {
|
|
94
|
-
const randomPart = randomBytes(32).toString("hex");
|
|
95
|
-
const key = `sk_live_${randomPart}`;
|
|
96
|
-
const keyPrefix = key.substring(0, 16);
|
|
97
|
-
const keyHash = ApiKey.hashKey(key);
|
|
98
|
-
const apiKey = new ApiKey({
|
|
99
|
-
db: options.db || profile.options?.db,
|
|
100
|
-
profileId: profile.id,
|
|
101
|
-
name: options.name,
|
|
102
|
-
scopes: options.scopes || ["*"],
|
|
103
|
-
expiresAt: options.expiresAt ?? null
|
|
104
|
-
});
|
|
105
|
-
apiKey.keyHash = keyHash;
|
|
106
|
-
apiKey.keyPrefix = keyPrefix;
|
|
107
|
-
await apiKey.initialize();
|
|
108
|
-
await apiKey.save();
|
|
109
|
-
return { key, apiKey };
|
|
110
|
-
}
|
|
111
|
-
/**
|
|
112
|
-
* Verify an API key and return the ApiKey record if valid
|
|
113
|
-
*/
|
|
114
|
-
static async verify(key, options = {}) {
|
|
115
|
-
const keyHash = ApiKey.hashKey(key);
|
|
116
|
-
const { ApiKeyCollection } = await import("./ApiKeyCollection-B6Op817e.js");
|
|
117
|
-
const collection = await ApiKeyCollection.create(options);
|
|
118
|
-
const apiKey = await collection.findOne({
|
|
119
|
-
where: { keyHash }
|
|
120
|
-
});
|
|
121
|
-
if (!apiKey) return null;
|
|
122
|
-
if (!apiKey.isValid()) return null;
|
|
123
|
-
await apiKey.recordUsage();
|
|
124
|
-
return apiKey;
|
|
125
|
-
}
|
|
126
|
-
};
|
|
127
|
-
__decorateClass([
|
|
128
|
-
foreignKey("Profile", { required: true })
|
|
129
|
-
], ApiKey.prototype, "profileId", 2);
|
|
130
|
-
ApiKey = __decorateClass([
|
|
131
|
-
smrt({
|
|
132
|
-
tableName: "api_keys",
|
|
133
|
-
api: { include: ["list", "get"] },
|
|
134
|
-
mcp: { include: ["list", "get"] },
|
|
135
|
-
// create/revoke are admin operations invoked in-process via the CLI; only
|
|
136
|
-
// read-only list/get are exposed over HTTP.
|
|
137
|
-
cli: { include: ["list", "get", "create", "revoke"], skipApiCheck: true }
|
|
138
|
-
})
|
|
139
|
-
], ApiKey);
|
|
140
|
-
export {
|
|
141
|
-
ApiKey
|
|
142
|
-
};
|
|
143
|
-
//# sourceMappingURL=ApiKey-B2LKEaP8.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ApiKey-B2LKEaP8.js","sources":["../../src/models/ApiKey.ts"],"sourcesContent":["/**\n * ApiKey - API keys for CLI and CI authentication\n *\n * Provides secure API key management with hashing, scopes, and expiration.\n * Keys are stored as hashes - the plaintext is only returned on creation.\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 { Profile } from './Profile';\n\nexport interface ApiKeyOptions extends SmrtObjectOptions {\n profileId?: string;\n name?: string;\n scopes?: string[];\n expiresAt?: Date | null;\n}\n\nexport interface GenerateKeyResult {\n key: string;\n apiKey: ApiKey;\n}\n\n@smrt({\n tableName: 'api_keys',\n api: { include: ['list', 'get'] },\n mcp: { include: ['list', 'get'] },\n // create/revoke are admin operations invoked in-process via the CLI; only\n // read-only list/get are exposed over HTTP.\n cli: { include: ['list', 'get', 'create', 'revoke'], skipApiCheck: true },\n})\nexport class ApiKey extends SmrtObject {\n /**\n * Link to the Profile (Person, Organization, Bot)\n */\n @foreignKey('Profile', { required: true })\n profileId?: string;\n\n /**\n * SHA-256 hash of the API key\n */\n keyHash: string = '';\n\n /**\n * First 8 characters of the key for identification (e.g., \"sk_live_a1b2...\")\n */\n keyPrefix: string = '';\n\n /**\n * Human-readable name for the key (e.g., \"CI Bot\", \"Local CLI\")\n */\n name: string = '';\n\n /**\n * Scopes/permissions for this key\n */\n scopes: string[] = [];\n\n /**\n * Last time this key was used\n */\n lastUsedAt: Date | null = null;\n\n /**\n * When this key expires (null = never)\n */\n expiresAt: Date | null = null;\n\n /**\n * When this key was revoked (null = active)\n */\n revokedAt: Date | null = null;\n\n constructor(options: ApiKeyOptions = {}) {\n super(options);\n if (options.profileId) this.profileId = options.profileId;\n if (options.name) this.name = options.name;\n if (options.scopes) this.scopes = options.scopes;\n if (options.expiresAt !== undefined) this.expiresAt = options.expiresAt;\n }\n\n /**\n * Get the linked Profile\n */\n async getProfile(): Promise<Profile | null> {\n return (await this.getRelated('profileId')) as Profile | null;\n }\n\n /**\n * Check if this key is valid (not expired, not revoked)\n */\n isValid(): boolean {\n if (this.revokedAt) return false;\n if (this.expiresAt && this.expiresAt < new Date()) return false;\n return true;\n }\n\n /**\n * Check if this key has a specific scope\n */\n hasScope(scope: string): boolean {\n return this.scopes.includes(scope) || this.scopes.includes('*');\n }\n\n /**\n * Revoke this key\n */\n async revoke(): Promise<void> {\n this.revokedAt = new Date();\n await this.save();\n }\n\n /**\n * Record usage of this key\n */\n async recordUsage(): Promise<void> {\n this.lastUsedAt = new Date();\n await this.save();\n }\n\n /**\n * Hash an API key\n */\n static hashKey(key: string): string {\n return createHash('sha256').update(key).digest('hex');\n }\n\n /**\n * Generate a new API key for a profile\n */\n static async generate(\n profile: Profile,\n options: {\n name: string;\n scopes?: string[];\n expiresAt?: Date | null;\n db?: SmrtObjectOptions['db'];\n },\n ): Promise<GenerateKeyResult> {\n // Generate a random key: sk_live_<32 random bytes as hex>\n const randomPart = randomBytes(32).toString('hex');\n const key = `sk_live_${randomPart}`;\n const keyPrefix = key.substring(0, 16);\n const keyHash = ApiKey.hashKey(key);\n\n const apiKey = new ApiKey({\n db: options.db || profile.options?.db,\n profileId: profile.id as string,\n name: options.name,\n scopes: options.scopes || ['*'],\n expiresAt: options.expiresAt ?? null,\n });\n apiKey.keyHash = keyHash;\n apiKey.keyPrefix = keyPrefix;\n\n await apiKey.initialize();\n await apiKey.save();\n\n return { key, apiKey };\n }\n\n /**\n * Verify an API key and return the ApiKey record if valid\n */\n static async verify(\n key: string,\n options: SmrtObjectOptions = {},\n ): Promise<ApiKey | null> {\n const keyHash = ApiKey.hashKey(key);\n\n const { ApiKeyCollection } = await import(\n '../collections/ApiKeyCollection'\n );\n const collection = await ApiKeyCollection.create(options);\n\n const apiKey = await collection.findOne({\n where: { keyHash },\n });\n\n if (!apiKey) return null;\n if (!apiKey.isValid()) return null;\n\n // Record usage\n await apiKey.recordUsage();\n\n return apiKey;\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import { SmrtCollection } from "@happyvertical/smrt-core";
|
|
2
|
-
import { ApiKey } from "./ApiKey-B2LKEaP8.js";
|
|
3
|
-
class ApiKeyCollection extends SmrtCollection {
|
|
4
|
-
static _itemClass = ApiKey;
|
|
5
|
-
/**
|
|
6
|
-
* Find all keys for a profile
|
|
7
|
-
*/
|
|
8
|
-
async findByProfile(profileId) {
|
|
9
|
-
return await this.list({
|
|
10
|
-
where: { profileId }
|
|
11
|
-
});
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Find active (non-revoked, non-expired) keys for a profile
|
|
15
|
-
*/
|
|
16
|
-
async findActiveByProfile(profileId) {
|
|
17
|
-
const keys = await this.findByProfile(profileId);
|
|
18
|
-
return keys.filter((key) => key.isValid());
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Find key by hash
|
|
22
|
-
*/
|
|
23
|
-
async findByHash(keyHash) {
|
|
24
|
-
return await this.findOne({
|
|
25
|
-
where: { keyHash }
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Verify an API key string and return the record if valid
|
|
30
|
-
*/
|
|
31
|
-
async verify(key) {
|
|
32
|
-
const keyHash = ApiKey.hashKey(key);
|
|
33
|
-
const apiKey = await this.findByHash(keyHash);
|
|
34
|
-
if (!apiKey) return null;
|
|
35
|
-
if (!apiKey.isValid()) return null;
|
|
36
|
-
await apiKey.recordUsage();
|
|
37
|
-
return apiKey;
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Generate a new key for a profile
|
|
41
|
-
*/
|
|
42
|
-
async generateForProfile(profile, options) {
|
|
43
|
-
return await ApiKey.generate(profile, {
|
|
44
|
-
...options,
|
|
45
|
-
db: this.options.db
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Revoke all keys for a profile
|
|
50
|
-
*/
|
|
51
|
-
async revokeAllForProfile(profileId) {
|
|
52
|
-
const keys = await this.findActiveByProfile(profileId);
|
|
53
|
-
for (const key of keys) {
|
|
54
|
-
await key.revoke();
|
|
55
|
-
}
|
|
56
|
-
return keys.length;
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* Revoke a specific key by prefix
|
|
60
|
-
*/
|
|
61
|
-
async revokeByPrefix(keyPrefix) {
|
|
62
|
-
const key = await this.findOne({
|
|
63
|
-
where: { keyPrefix }
|
|
64
|
-
});
|
|
65
|
-
if (key && key.isValid()) {
|
|
66
|
-
await key.revoke();
|
|
67
|
-
return true;
|
|
68
|
-
}
|
|
69
|
-
return false;
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* Clean up expired keys (soft delete by setting revokedAt)
|
|
73
|
-
*/
|
|
74
|
-
async cleanupExpired() {
|
|
75
|
-
const now = /* @__PURE__ */ new Date();
|
|
76
|
-
const allKeys = await this.list({});
|
|
77
|
-
let cleaned = 0;
|
|
78
|
-
for (const key of allKeys) {
|
|
79
|
-
if (key.expiresAt && key.expiresAt < now && !key.revokedAt) {
|
|
80
|
-
key.revokedAt = now;
|
|
81
|
-
await key.save();
|
|
82
|
-
cleaned++;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
return cleaned;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
export {
|
|
89
|
-
ApiKeyCollection
|
|
90
|
-
};
|
|
91
|
-
//# sourceMappingURL=ApiKeyCollection-B6Op817e.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ApiKeyCollection-B6Op817e.js","sources":["../../src/collections/ApiKeyCollection.ts"],"sourcesContent":["/**\n * ApiKeyCollection - Collection for managing API keys\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { ApiKey, type GenerateKeyResult } from '../models/ApiKey';\nimport type { Profile } from '../models/Profile';\n\nexport class ApiKeyCollection extends SmrtCollection<ApiKey> {\n static readonly _itemClass = ApiKey;\n\n /**\n * Find all keys for a profile\n */\n async findByProfile(profileId: string): Promise<ApiKey[]> {\n return await this.list({\n where: { profileId },\n });\n }\n\n /**\n * Find active (non-revoked, non-expired) keys for a profile\n */\n async findActiveByProfile(profileId: string): Promise<ApiKey[]> {\n const keys = await this.findByProfile(profileId);\n return keys.filter((key) => key.isValid());\n }\n\n /**\n * Find key by hash\n */\n async findByHash(keyHash: string): Promise<ApiKey | null> {\n return await this.findOne({\n where: { keyHash },\n });\n }\n\n /**\n * Verify an API key string and return the record if valid\n */\n async verify(key: string): Promise<ApiKey | null> {\n const keyHash = ApiKey.hashKey(key);\n const apiKey = await this.findByHash(keyHash);\n\n if (!apiKey) return null;\n if (!apiKey.isValid()) return null;\n\n // Record usage\n await apiKey.recordUsage();\n\n return apiKey;\n }\n\n /**\n * Generate a new key for a profile\n */\n async generateForProfile(\n profile: Profile,\n options: {\n name: string;\n scopes?: string[];\n expiresAt?: Date | null;\n },\n ): Promise<GenerateKeyResult> {\n return await ApiKey.generate(profile, {\n ...options,\n db: this.options.db,\n });\n }\n\n /**\n * Revoke all keys for a profile\n */\n async revokeAllForProfile(profileId: string): Promise<number> {\n const keys = await this.findActiveByProfile(profileId);\n for (const key of keys) {\n await key.revoke();\n }\n return keys.length;\n }\n\n /**\n * Revoke a specific key by prefix\n */\n async revokeByPrefix(keyPrefix: string): Promise<boolean> {\n const key = await this.findOne({\n where: { keyPrefix },\n });\n\n if (key && key.isValid()) {\n await key.revoke();\n return true;\n }\n return false;\n }\n\n /**\n * Clean up expired keys (soft delete by setting revokedAt)\n */\n async cleanupExpired(): Promise<number> {\n const now = new Date();\n const allKeys = await this.list({});\n let cleaned = 0;\n\n for (const key of allKeys) {\n if (key.expiresAt && key.expiresAt < now && !key.revokedAt) {\n key.revokedAt = now;\n await key.save();\n cleaned++;\n }\n }\n\n return cleaned;\n }\n}\n"],"names":[],"mappings":";;AAQO,MAAM,yBAAyB,eAAuB;AAAA,EAC3D,OAAgB,aAAa;AAAA;AAAA;AAAA;AAAA,EAK7B,MAAM,cAAc,WAAsC;AACxD,WAAO,MAAM,KAAK,KAAK;AAAA,MACrB,OAAO,EAAE,UAAA;AAAA,IAAU,CACpB;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,oBAAoB,WAAsC;AAC9D,UAAM,OAAO,MAAM,KAAK,cAAc,SAAS;AAC/C,WAAO,KAAK,OAAO,CAAC,QAAQ,IAAI,SAAS;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,WAAW,SAAyC;AACxD,WAAO,MAAM,KAAK,QAAQ;AAAA,MACxB,OAAO,EAAE,QAAA;AAAA,IAAQ,CAClB;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAO,KAAqC;AAChD,UAAM,UAAU,OAAO,QAAQ,GAAG;AAClC,UAAM,SAAS,MAAM,KAAK,WAAW,OAAO;AAE5C,QAAI,CAAC,OAAQ,QAAO;AACpB,QAAI,CAAC,OAAO,QAAA,EAAW,QAAO;AAG9B,UAAM,OAAO,YAAA;AAEb,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,mBACJ,SACA,SAK4B;AAC5B,WAAO,MAAM,OAAO,SAAS,SAAS;AAAA,MACpC,GAAG;AAAA,MACH,IAAI,KAAK,QAAQ;AAAA,IAAA,CAClB;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,oBAAoB,WAAoC;AAC5D,UAAM,OAAO,MAAM,KAAK,oBAAoB,SAAS;AACrD,eAAW,OAAO,MAAM;AACtB,YAAM,IAAI,OAAA;AAAA,IACZ;AACA,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,eAAe,WAAqC;AACxD,UAAM,MAAM,MAAM,KAAK,QAAQ;AAAA,MAC7B,OAAO,EAAE,UAAA;AAAA,IAAU,CACpB;AAED,QAAI,OAAO,IAAI,WAAW;AACxB,YAAM,IAAI,OAAA;AACV,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,iBAAkC;AACtC,UAAM,0BAAU,KAAA;AAChB,UAAM,UAAU,MAAM,KAAK,KAAK,CAAA,CAAE;AAClC,QAAI,UAAU;AAEd,eAAW,OAAO,SAAS;AACzB,UAAI,IAAI,aAAa,IAAI,YAAY,OAAO,CAAC,IAAI,WAAW;AAC1D,YAAI,YAAY;AAChB,cAAM,IAAI,KAAA;AACV;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AACF;"}
|