@happyvertical/smrt-profiles 0.35.2 → 0.35.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.
@@ -1 +1 @@
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 as any).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
+ {"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 +1 @@
1
- {"version":3,"file":"AuditLogCollection-BYqCj0uE.js","sources":["../../src/models/AuditLog.ts","../../src/collections/AuditLogCollection.ts"],"sourcesContent":["/**\n * AuditLog - Tracks all actions for full audit trail\n *\n * Records who did what, when, and how (web, CLI, CI, webhook, MCP).\n * Supports pass-through identity for CI workflows.\n */\n\nimport {\n foreignKey,\n SmrtObject,\n type SmrtObjectOptions,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport type { Profile } from './Profile';\n\nexport type AuditSource = 'web' | 'cli' | 'ci' | 'webhook' | 'mcp';\n\nexport interface AuditLogOptions extends SmrtObjectOptions {\n profileId?: string;\n action?: string;\n resourceType?: string;\n resourceId?: string;\n source?: AuditSource;\n metadata?: Record<string, any>;\n onBehalfOfId?: string | null;\n tenantId?: string | null;\n}\n\n@TenantScoped({ mode: 'optional', allowSuperAdminBypass: true })\n@smrt({\n tableName: 'audit_logs',\n api: { include: ['list', 'get'] },\n mcp: { include: ['list', 'get'] },\n cli: { include: ['list'] },\n})\nexport class AuditLog extends SmrtObject {\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * The profile who performed the action\n */\n @foreignKey('Profile', { required: true })\n profileId?: string;\n\n /**\n * Action performed (e.g., 'issue.update', 'feedback.incorporate')\n */\n action: string = '';\n\n /**\n * Type of resource affected (e.g., 'Issue', 'Repository')\n */\n resourceType: string = '';\n\n /**\n * ID of the affected resource\n */\n resourceId: string = '';\n\n /**\n * Source of the action\n */\n source: AuditSource = 'web';\n\n /**\n * Additional context (CI run ID, request ID, etc.)\n */\n metadata: Record<string, any> = {};\n\n /**\n * For pass-through identity in CI - the actual person who triggered\n */\n @foreignKey('Profile', { nullable: true })\n onBehalfOfId?: string | null;\n\n constructor(options: AuditLogOptions = {}) {\n super(options);\n if (options.profileId) this.profileId = options.profileId;\n if (options.action) this.action = options.action;\n if (options.resourceType) this.resourceType = options.resourceType;\n if (options.resourceId) this.resourceId = options.resourceId;\n if (options.source) this.source = options.source;\n if (options.metadata) this.metadata = options.metadata;\n if (options.onBehalfOfId !== undefined)\n this.onBehalfOfId = options.onBehalfOfId;\n }\n\n /**\n * Get the profile who performed the action\n */\n async getProfile(): Promise<Profile | null> {\n return (await this.getRelated('profileId')) as Profile | null;\n }\n\n /**\n * Get the profile on whose behalf the action was performed (if any)\n */\n async getOnBehalfOf(): Promise<Profile | null> {\n if (!this.onBehalfOfId) return null;\n return (await this.getRelated('onBehalfOfId')) as Profile | null;\n }\n\n /**\n * Get the effective actor (onBehalfOf if set, otherwise profile)\n */\n async getEffectiveActor(): Promise<Profile | null> {\n const onBehalfOf = await this.getOnBehalfOf();\n if (onBehalfOf) return onBehalfOf;\n return await this.getProfile();\n }\n\n /**\n * Create an audit log entry\n */\n static async record(\n options: AuditLogOptions & {\n profile: Profile;\n onBehalfOf?: Profile | null;\n },\n ): Promise<AuditLog> {\n const log = new AuditLog({\n ...options,\n profileId: options.profile.id as string,\n onBehalfOfId: options.onBehalfOf?.id as string | undefined,\n });\n await log.initialize();\n await log.save();\n return log;\n }\n}\n","/**\n * AuditLogCollection - Collection for querying audit logs\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { AuditLog, type AuditSource } from '../models/AuditLog';\nimport type { Profile } from '../models/Profile';\n\nexport class AuditLogCollection extends SmrtCollection<AuditLog> {\n static readonly _itemClass = AuditLog;\n\n /**\n * Find logs for a profile (actions they performed)\n */\n async findByProfile(profileId: string): Promise<AuditLog[]> {\n return await this.list({\n where: { profileId },\n orderBy: 'createdAt DESC',\n });\n }\n\n /**\n * Find logs for a resource\n */\n async findByResource(\n resourceType: string,\n resourceId: string,\n ): Promise<AuditLog[]> {\n return await this.list({\n where: { resourceType, resourceId },\n orderBy: 'createdAt DESC',\n });\n }\n\n /**\n * Find logs by action type\n */\n async findByAction(action: string): Promise<AuditLog[]> {\n return await this.list({\n where: { action },\n orderBy: 'createdAt DESC',\n });\n }\n\n /**\n * Find logs by source\n */\n async findBySource(source: AuditSource): Promise<AuditLog[]> {\n return await this.list({\n where: { source },\n orderBy: 'createdAt DESC',\n });\n }\n\n /**\n * Find logs where actions were performed on behalf of someone\n */\n async findOnBehalfOf(profileId: string): Promise<AuditLog[]> {\n return await this.list({\n where: { onBehalfOfId: profileId },\n orderBy: 'createdAt DESC',\n });\n }\n\n /**\n * Record a new audit log entry\n */\n async record(options: {\n profile: Profile;\n action: string;\n resourceType: string;\n resourceId: string;\n source?: AuditSource;\n metadata?: Record<string, any>;\n onBehalfOf?: Profile | null;\n }): Promise<AuditLog> {\n const log = new AuditLog({\n ...this.options,\n profileId: options.profile.id as string,\n action: options.action,\n resourceType: options.resourceType,\n resourceId: options.resourceId,\n source: options.source || 'web',\n metadata: options.metadata || {},\n onBehalfOfId: options.onBehalfOf?.id as string | undefined,\n });\n await log.initialize();\n await log.save();\n return log;\n }\n\n /**\n * Get recent activity for a profile\n */\n async getRecentActivity(\n profileId: string,\n limit: number = 50,\n ): Promise<AuditLog[]> {\n return await this.list({\n where: { profileId },\n orderBy: 'createdAt DESC',\n limit,\n });\n }\n\n /**\n * Get activity timeline for a resource\n */\n async getResourceTimeline(\n resourceType: string,\n resourceId: string,\n ): Promise<AuditLog[]> {\n return await this.list({\n where: { resourceType, resourceId },\n orderBy: 'createdAt ASC',\n });\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"AuditLogCollection-BYqCj0uE.js","sources":["../../src/models/AuditLog.ts","../../src/collections/AuditLogCollection.ts"],"sourcesContent":["/**\n * AuditLog - Tracks all actions for full audit trail\n *\n * Records who did what, when, and how (web, CLI, CI, webhook, MCP).\n * Supports pass-through identity for CI workflows.\n */\n\nimport {\n foreignKey,\n SmrtObject,\n type SmrtObjectOptions,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport type { Profile } from './Profile';\n\nexport type AuditSource = 'web' | 'cli' | 'ci' | 'webhook' | 'mcp';\n\nexport interface AuditLogOptions extends SmrtObjectOptions {\n profileId?: string;\n action?: string;\n resourceType?: string;\n resourceId?: string;\n source?: AuditSource;\n metadata?: Record<string, unknown>;\n onBehalfOfId?: string | null;\n tenantId?: string | null;\n}\n\n@TenantScoped({ mode: 'optional', allowSuperAdminBypass: true })\n@smrt({\n tableName: 'audit_logs',\n api: { include: ['list', 'get'] },\n mcp: { include: ['list', 'get'] },\n cli: { include: ['list'] },\n})\nexport class AuditLog extends SmrtObject {\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * The profile who performed the action\n */\n @foreignKey('Profile', { required: true })\n profileId?: string;\n\n /**\n * Action performed (e.g., 'issue.update', 'feedback.incorporate')\n */\n action: string = '';\n\n /**\n * Type of resource affected (e.g., 'Issue', 'Repository')\n */\n resourceType: string = '';\n\n /**\n * ID of the affected resource\n */\n resourceId: string = '';\n\n /**\n * Source of the action\n */\n source: AuditSource = 'web';\n\n /**\n * Additional context (CI run ID, request ID, etc.)\n */\n metadata: Record<string, unknown> = {};\n\n /**\n * For pass-through identity in CI - the actual person who triggered\n */\n @foreignKey('Profile', { nullable: true })\n onBehalfOfId?: string | null;\n\n constructor(options: AuditLogOptions = {}) {\n super(options);\n if (options.profileId) this.profileId = options.profileId;\n if (options.action) this.action = options.action;\n if (options.resourceType) this.resourceType = options.resourceType;\n if (options.resourceId) this.resourceId = options.resourceId;\n if (options.source) this.source = options.source;\n if (options.metadata) this.metadata = options.metadata;\n if (options.onBehalfOfId !== undefined)\n this.onBehalfOfId = options.onBehalfOfId;\n }\n\n /**\n * Get the profile who performed the action\n */\n async getProfile(): Promise<Profile | null> {\n return (await this.getRelated('profileId')) as Profile | null;\n }\n\n /**\n * Get the profile on whose behalf the action was performed (if any)\n */\n async getOnBehalfOf(): Promise<Profile | null> {\n if (!this.onBehalfOfId) return null;\n return (await this.getRelated('onBehalfOfId')) as Profile | null;\n }\n\n /**\n * Get the effective actor (onBehalfOf if set, otherwise profile)\n */\n async getEffectiveActor(): Promise<Profile | null> {\n const onBehalfOf = await this.getOnBehalfOf();\n if (onBehalfOf) return onBehalfOf;\n return await this.getProfile();\n }\n\n /**\n * Create an audit log entry\n */\n static async record(\n options: AuditLogOptions & {\n profile: Profile;\n onBehalfOf?: Profile | null;\n },\n ): Promise<AuditLog> {\n const log = new AuditLog({\n ...options,\n profileId: options.profile.id as string,\n onBehalfOfId: options.onBehalfOf?.id as string | undefined,\n });\n await log.initialize();\n await log.save();\n return log;\n }\n}\n","/**\n * AuditLogCollection - Collection for querying audit logs\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { AuditLog, type AuditSource } from '../models/AuditLog';\nimport type { Profile } from '../models/Profile';\n\nexport class AuditLogCollection extends SmrtCollection<AuditLog> {\n static readonly _itemClass = AuditLog;\n\n /**\n * Find logs for a profile (actions they performed)\n */\n async findByProfile(profileId: string): Promise<AuditLog[]> {\n return await this.list({\n where: { profileId },\n orderBy: 'createdAt DESC',\n });\n }\n\n /**\n * Find logs for a resource\n */\n async findByResource(\n resourceType: string,\n resourceId: string,\n ): Promise<AuditLog[]> {\n return await this.list({\n where: { resourceType, resourceId },\n orderBy: 'createdAt DESC',\n });\n }\n\n /**\n * Find logs by action type\n */\n async findByAction(action: string): Promise<AuditLog[]> {\n return await this.list({\n where: { action },\n orderBy: 'createdAt DESC',\n });\n }\n\n /**\n * Find logs by source\n */\n async findBySource(source: AuditSource): Promise<AuditLog[]> {\n return await this.list({\n where: { source },\n orderBy: 'createdAt DESC',\n });\n }\n\n /**\n * Find logs where actions were performed on behalf of someone\n */\n async findOnBehalfOf(profileId: string): Promise<AuditLog[]> {\n return await this.list({\n where: { onBehalfOfId: profileId },\n orderBy: 'createdAt DESC',\n });\n }\n\n /**\n * Record a new audit log entry\n */\n async record(options: {\n profile: Profile;\n action: string;\n resourceType: string;\n resourceId: string;\n source?: AuditSource;\n metadata?: Record<string, unknown>;\n onBehalfOf?: Profile | null;\n }): Promise<AuditLog> {\n const log = new AuditLog({\n ...this.options,\n profileId: options.profile.id as string,\n action: options.action,\n resourceType: options.resourceType,\n resourceId: options.resourceId,\n source: options.source || 'web',\n metadata: options.metadata || {},\n onBehalfOfId: options.onBehalfOf?.id as string | undefined,\n });\n await log.initialize();\n await log.save();\n return log;\n }\n\n /**\n * Get recent activity for a profile\n */\n async getRecentActivity(\n profileId: string,\n limit: number = 50,\n ): Promise<AuditLog[]> {\n return await this.list({\n where: { profileId },\n orderBy: 'createdAt DESC',\n limit,\n });\n }\n\n /**\n * Get activity timeline for a resource\n */\n async getResourceTimeline(\n resourceType: string,\n resourceId: string,\n ): Promise<AuditLog[]> {\n return await this.list({\n where: { resourceType, resourceId },\n orderBy: 'createdAt ASC',\n });\n }\n}\n"],"names":[],"mappings":"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"}