@happyvertical/smrt-secrets 0.40.7 → 0.40.8
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/{SecretService-Bg9_o1fL.js → SecretService-CrkpsGf8.js} +2 -2
- package/dist/chunks/{SecretService-Bg9_o1fL.js.map → SecretService-CrkpsGf8.js.map} +1 -1
- package/dist/chunks/{TenantKey-BLPjcOfk.js → TenantKey-DIyrgxt3.js} +2 -2
- package/dist/chunks/{TenantKey-BLPjcOfk.js.map → TenantKey-DIyrgxt3.js.map} +1 -1
- package/dist/index.js +2 -2
- package/dist/manifest.json +2 -2
- package/dist/models/index.js +1 -1
- package/dist/services/SecretService.js +1 -1
- package/dist/smrt-knowledge.json +4 -4
- package/package.json +4 -4
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as createAuditEntry, n as Secret, r as SecretAuditLog, t as TenantKey } from "./TenantKey-
|
|
1
|
+
import { i as createAuditEntry, n as Secret, r as SecretAuditLog, t as TenantKey } from "./TenantKey-DIyrgxt3.js";
|
|
2
2
|
import { SmrtCollection } from "@happyvertical/smrt-core";
|
|
3
3
|
import { AMKUnavailableError, DecryptionError, EncryptionError, EnvelopeEncryption, TenantKeyMissingError, getSecretStore } from "@happyvertical/secrets";
|
|
4
4
|
import { loadEnvConfig } from "@happyvertical/utils";
|
|
@@ -1190,4 +1190,4 @@ var SecretService = class SecretService {
|
|
|
1190
1190
|
//#endregion
|
|
1191
1191
|
export { SecretAuditLogCollection as a, SecretCollection as i, SecretService as n, TenantKeyCollection as r, SecretKeyDriftError as t };
|
|
1192
1192
|
|
|
1193
|
-
//# sourceMappingURL=SecretService-
|
|
1193
|
+
//# sourceMappingURL=SecretService-CrkpsGf8.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SecretService-Bg9_o1fL.js","names":[],"sources":["../../src/collections/SecretAuditLogCollection.ts","../../src/collections/SecretCollection.ts","../../src/collections/TenantKeyCollection.ts","../../../../node_modules/.pnpm/@happyvertical+logger@0.80.2_@sentry+node@10.63.0_@opentelemetry+core@2.7.0_@opentelemetry+api@1.9.1__/node_modules/@happyvertical/logger/dist/index.js","../../src/services/SecretService.ts"],"sourcesContent":["/**\n * SecretAuditLogCollection - Collection manager for SecretAuditLog objects\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport {\n type SecretAuditAction,\n SecretAuditLog,\n type SecretAuditResult,\n} from '../models/SecretAuditLog.js';\n\n/**\n * Options for listing audit logs\n */\nexport interface ListAuditLogsOptions {\n /**\n * Scope results to a single tenant's audit trail. Tenant-facing callers\n * (e.g. SecretService.getAuditLogs) must always set this — audit rows\n * reference secret names and must not leak across tenants (issue #1501).\n * Omit only for cross-tenant compliance tooling running under\n * withSuperAdminBypass().\n */\n tenantId?: string;\n /** Filter by secret name */\n secretName?: string;\n /** Filter by user ID */\n userId?: string;\n /** Filter by action type */\n action?: SecretAuditAction;\n /** Filter by result */\n result?: SecretAuditResult;\n /** Filter by date range start */\n since?: Date;\n /** Filter by date range end */\n until?: Date;\n /** Maximum number of results */\n limit?: number;\n /** Offset for pagination */\n offset?: number;\n}\n\n/**\n * Collection for managing SecretAuditLog objects\n */\nexport class SecretAuditLogCollection extends SmrtCollection<SecretAuditLog> {\n static readonly _itemClass = SecretAuditLog;\n\n /**\n * List audit logs with filtering options\n */\n async listLogs(\n options: ListAuditLogsOptions = {},\n ): Promise<SecretAuditLog[]> {\n const where: Record<string, unknown> = {};\n\n if (options.tenantId) {\n where.tenantId = options.tenantId;\n }\n\n if (options.secretName) {\n where.secretName = options.secretName;\n }\n\n if (options.userId) {\n where.userId = options.userId;\n }\n\n if (options.action) {\n where.action = options.action;\n }\n\n if (options.result) {\n where.result = options.result;\n }\n\n if (options.since) {\n where['created_at >'] = options.since.toISOString();\n }\n\n if (options.until) {\n where['created_at <'] = options.until.toISOString();\n }\n\n return this.list({\n where,\n limit: options.limit ?? 100,\n offset: options.offset,\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Get audit logs for a specific secret.\n *\n * @param tenantId - Scope to this tenant's audit trail, or `null` for a\n * cross-tenant compliance query (which must run under\n * `withSuperAdminBypass()` from `@happyvertical/smrt-tenancy`). Audit rows\n * reference secret names and must not leak across tenants (#1503).\n */\n async getSecretHistory(\n tenantId: string | null,\n secretName: string,\n limit: number = 50,\n ): Promise<SecretAuditLog[]> {\n return this.listLogs({\n tenantId: tenantId ?? undefined,\n secretName,\n limit,\n });\n }\n\n /**\n * Get audit logs for a specific user.\n *\n * @param tenantId - Scope to this tenant's audit trail, or `null` for a\n * cross-tenant compliance query under `withSuperAdminBypass()` (#1503).\n */\n async getUserActivity(\n tenantId: string | null,\n userId: string,\n limit: number = 50,\n ): Promise<SecretAuditLog[]> {\n return this.listLogs({ tenantId: tenantId ?? undefined, userId, limit });\n }\n\n /**\n * Get recent failures.\n *\n * @param tenantId - Scope to this tenant's audit trail, or `null` for a\n * cross-tenant compliance query under `withSuperAdminBypass()` (#1503).\n */\n async getRecentFailures(\n tenantId: string | null,\n limit: number = 20,\n ): Promise<SecretAuditLog[]> {\n return this.listLogs({\n tenantId: tenantId ?? undefined,\n result: 'failure',\n limit,\n });\n }\n\n /**\n * Get recent denied access attempts.\n *\n * @param tenantId - Scope to this tenant's audit trail, or `null` for a\n * cross-tenant compliance query under `withSuperAdminBypass()` (#1503).\n */\n async getRecentDenials(\n tenantId: string | null,\n limit: number = 20,\n ): Promise<SecretAuditLog[]> {\n return this.listLogs({\n tenantId: tenantId ?? undefined,\n result: 'denied',\n limit,\n });\n }\n\n /**\n * Count operations by action type.\n *\n * @param tenantId - Scope to this tenant's audit trail, or `null` for a\n * cross-tenant compliance count under `withSuperAdminBypass()` (#1503).\n */\n async countByAction(\n tenantId: string | null,\n since?: Date,\n ): Promise<Record<SecretAuditAction, number>> {\n const logs = await this.listLogs({\n tenantId: tenantId ?? undefined,\n since,\n limit: 10000,\n });\n\n const counts: Record<SecretAuditAction, number> = {\n create: 0,\n read: 0,\n update: 0,\n delete: 0,\n rotate_key: 0,\n disable: 0,\n enable: 0,\n expire: 0,\n };\n\n for (const log of logs) {\n counts[log.action]++;\n }\n\n return counts;\n }\n\n /**\n * Count operations by result.\n *\n * @param tenantId - Scope to this tenant's audit trail, or `null` for a\n * cross-tenant compliance count under `withSuperAdminBypass()` (#1503).\n */\n async countByResult(\n tenantId: string | null,\n since?: Date,\n ): Promise<Record<SecretAuditResult, number>> {\n const logs = await this.listLogs({\n tenantId: tenantId ?? undefined,\n since,\n limit: 10000,\n });\n\n const counts: Record<SecretAuditResult, number> = {\n success: 0,\n failure: 0,\n denied: 0,\n };\n\n for (const log of logs) {\n counts[log.result]++;\n }\n\n return counts;\n }\n\n /**\n * Delete old audit logs\n * @param olderThanDays Delete logs older than this many days\n */\n async cleanup(olderThanDays: number = 365): Promise<number> {\n const cutoffDate = new Date();\n cutoffDate.setDate(cutoffDate.getDate() - olderThanDays);\n\n const oldLogs = await this.list({\n where: {\n 'created_at <': cutoffDate.toISOString(),\n },\n });\n\n let count = 0;\n for (const log of oldLogs) {\n await log.delete();\n count++;\n }\n\n return count;\n }\n}\n","/**\n * SecretCollection - Collection manager for Secret objects\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { Secret, type SecretStatus } from '../models/Secret.js';\n\n/**\n * Options for listing secrets\n */\nexport interface ListSecretsOptions {\n /** Filter by category */\n category?: string;\n /** Filter by status */\n status?: SecretStatus;\n /** Include expired secrets */\n includeExpired?: boolean;\n /** Maximum number of results */\n limit?: number;\n /** Offset for pagination */\n offset?: number;\n}\n\n/**\n * Collection for managing Secret objects\n *\n * All lookups take an explicit `tenantId` and scope on the authoritative\n * `tenant_id` column. Scoping must NOT rely on the tenancy interceptor\n * (which may be disabled in the host application) and must NOT use the\n * `context` column: `context = tenantId` is only a convention applied on\n * the create path, so pre-convention rows may have a divergent `context`.\n * See https://github.com/happyvertical/smrt/issues/1501\n */\nexport class SecretCollection extends SmrtCollection<Secret> {\n static readonly _itemClass = Secret;\n\n /**\n * Find a secret by name within the given tenant\n */\n async findByName(tenantId: string, name: string): Promise<Secret | null> {\n return this.get({ name, tenantId });\n }\n\n /**\n * List secrets for a tenant with filtering options\n */\n async listSecrets(\n tenantId: string,\n options: ListSecretsOptions = {},\n ): Promise<Secret[]> {\n const where: Record<string, unknown> = { tenantId };\n\n if (options.category) {\n where.category = options.category;\n }\n\n if (options.status) {\n where.status = options.status;\n }\n\n const secrets = await this.list({\n where,\n limit: options.limit,\n offset: options.offset,\n orderBy: 'name ASC',\n });\n\n // Filter out expired secrets unless explicitly included\n if (!options.includeExpired) {\n return secrets.filter((secret) => !secret.isExpired());\n }\n\n return secrets;\n }\n\n /**\n * List all active secrets for a tenant\n */\n async listActive(tenantId: string): Promise<Secret[]> {\n return this.listSecrets(tenantId, { status: 'active' });\n }\n\n /**\n * List a tenant's secrets by category\n */\n async listByCategory(tenantId: string, category: string): Promise<Secret[]> {\n return this.listSecrets(tenantId, { category, status: 'active' });\n }\n\n /**\n * List a tenant's secrets that need attention (expired or about to expire)\n */\n async listExpiring(\n tenantId: string,\n daysAhead: number = 30,\n ): Promise<Secret[]> {\n const futureDate = new Date();\n futureDate.setDate(futureDate.getDate() + daysAhead);\n\n const secrets = await this.list({\n where: {\n tenantId,\n status: 'active',\n 'expiresAt !=': null,\n 'expiresAt <': futureDate.toISOString(),\n },\n orderBy: 'expiresAt ASC',\n });\n\n return secrets;\n }\n\n /**\n * Get categories used in a tenant's secrets\n */\n async getCategories(tenantId: string): Promise<string[]> {\n const secrets = await this.list({ where: { tenantId } });\n const categories = new Set(secrets.map((s) => s.category).filter(Boolean));\n return Array.from(categories).sort();\n }\n\n /**\n * Count a tenant's secrets by status\n */\n async countByStatus(tenantId: string): Promise<Record<SecretStatus, number>> {\n const secrets = await this.list({ where: { tenantId } });\n\n const counts: Record<SecretStatus, number> = {\n active: 0,\n disabled: 0,\n expired: 0,\n };\n\n for (const secret of secrets) {\n if (secret.isExpired()) {\n counts.expired++;\n } else {\n counts[secret.status]++;\n }\n }\n\n return counts;\n }\n\n /**\n * Delete a tenant's secret by name\n */\n async deleteByName(tenantId: string, name: string): Promise<boolean> {\n const secret = await this.findByName(tenantId, name);\n if (!secret) return false;\n\n await secret.delete();\n return true;\n }\n}\n","/**\n * TenantKeyCollection - Collection manager for TenantKey objects\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { TenantKey, type TenantKeyStatus } from '../models/TenantKey.js';\n\n/**\n * Collection for managing TenantKey objects\n */\nexport class TenantKeyCollection extends SmrtCollection<TenantKey> {\n static readonly _itemClass = TenantKey;\n\n /**\n * Get the active key for a tenant\n */\n async getActiveKey(tenantId: string): Promise<TenantKey | null> {\n return this.get({\n tenantId,\n status: 'active',\n });\n }\n\n /**\n * List all key versions for a tenant\n */\n async listKeyVersions(tenantId: string): Promise<TenantKey[]> {\n return this.list({\n where: { tenantId },\n orderBy: 'version DESC',\n });\n }\n\n /**\n * Get a specific key version for a tenant\n */\n async getKeyVersion(\n tenantId: string,\n version: number,\n ): Promise<TenantKey | null> {\n return this.get({\n tenantId,\n version,\n });\n }\n\n /**\n * Find keys that need rotation\n */\n async findKeysNeedingRotation(): Promise<TenantKey[]> {\n const now = new Date();\n\n return this.list({\n where: {\n status: 'active',\n 'rotateAfter !=': null,\n 'rotateAfter <': now.toISOString(),\n },\n orderBy: 'rotateAfter ASC',\n });\n }\n\n /**\n * List all active keys across all tenants\n */\n async listAllActiveKeys(): Promise<TenantKey[]> {\n return this.list({\n where: { status: 'active' },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Count keys by status\n */\n async countByStatus(): Promise<Record<TenantKeyStatus, number>> {\n const keys = await this.list({});\n\n const counts: Record<TenantKeyStatus, number> = {\n active: 0,\n rotating: 0,\n retired: 0,\n compromised: 0,\n };\n\n for (const key of keys) {\n counts[key.status]++;\n }\n\n return counts;\n }\n\n /**\n * Mark a key as compromised (should trigger re-encryption)\n */\n async markCompromised(tenantId: string, keyId: string): Promise<boolean> {\n const key = await this.get({\n id: keyId,\n tenantId,\n });\n\n if (!key) return false;\n\n key.markCompromised();\n await key.save();\n return true;\n }\n\n /**\n * Delete old retired keys that are no longer needed\n * @param olderThanDays Delete keys retired more than this many days ago\n */\n async cleanupRetiredKeys(olderThanDays: number = 90): Promise<number> {\n const cutoffDate = new Date();\n cutoffDate.setDate(cutoffDate.getDate() - olderThanDays);\n\n const oldKeys = await this.list({\n where: {\n status: 'retired',\n 'retiredAt <': cutoffDate.toISOString(),\n },\n });\n\n let count = 0;\n for (const key of oldKeys) {\n await key.delete();\n count++;\n }\n\n return count;\n }\n}\n","import { loadEnvConfig } from \"@happyvertical/utils\";\n//#region src/adapter.ts\n/**\n* Logger Adapter - Converts signals to structured log messages\n*\n* Transforms signals from the SMRT framework into structured log entries.\n* Each signal type is mapped to an appropriate log level:\n* - start → debug\n* - step → debug\n* - end → info\n* - error → error\n*\n* @example\n* ```typescript\n* const logger = new ConsoleLogger('info');\n* const adapter = new LoggerAdapter(logger);\n* signalBus.register(adapter);\n* ```\n*/\nvar LoggerAdapter = class {\n\tlogger;\n\tconstructor(logger) {\n\t\tthis.logger = logger;\n\t}\n\t/**\n\t* Handle a signal and log appropriately\n\t*\n\t* @param signal - Signal to log\n\t*/\n\tasync handle(signal) {\n\t\tconst context = {\n\t\t\tid: signal.id,\n\t\t\tobjectId: signal.objectId,\n\t\t\tclassName: signal.className,\n\t\t\tmethod: signal.method,\n\t\t\ttimestamp: signal.timestamp\n\t\t};\n\t\tif (signal.duration !== void 0) context.duration = signal.duration;\n\t\tif (signal.metadata) context.metadata = signal.metadata;\n\t\tswitch (signal.type) {\n\t\t\tcase \"start\":\n\t\t\t\tthis.logger.debug(`${signal.className}.${signal.method}() started`, context);\n\t\t\t\tbreak;\n\t\t\tcase \"step\":\n\t\t\t\tthis.logger.debug(`${signal.className}.${signal.method}() step: ${signal.step || \"unknown\"}`, context);\n\t\t\t\tbreak;\n\t\t\tcase \"end\":\n\t\t\t\tthis.logger.info(`${signal.className}.${signal.method}() completed in ${signal.duration}ms`, {\n\t\t\t\t\t...context,\n\t\t\t\t\tresult: signal.result !== void 0 ? \"present\" : \"none\"\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t\tcase \"error\":\n\t\t\t\tthis.logger.error(`${signal.className}.${signal.method}() failed: ${signal.error?.message || \"Unknown error\"}`, {\n\t\t\t\t\t...context,\n\t\t\t\t\terror: signal.error ? {\n\t\t\t\t\t\tmessage: signal.error.message,\n\t\t\t\t\t\tname: signal.error.name,\n\t\t\t\t\t\tstack: signal.error.stack\n\t\t\t\t\t} : void 0\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t}\n\t}\n};\n//#endregion\n//#region src/console.ts\n/**\n* Console-based logger with level filtering\n*\n* Logs are written to console with appropriate severity levels.\n* Messages are only output if they meet the configured log level threshold.\n*\n* @example\n* ```typescript\n* const logger = new ConsoleLogger('info');\n* logger.debug('Debug message'); // Not output (below 'info')\n* logger.info('Info message'); // Output\n* logger.error('Error message'); // Output\n* ```\n*/\nvar ConsoleLogger = class ConsoleLogger {\n\tlevel;\n\tstatic LEVELS = [\n\t\t\"debug\",\n\t\t\"info\",\n\t\t\"warn\",\n\t\t\"error\"\n\t];\n\tconstructor(level = \"info\") {\n\t\tthis.level = level;\n\t}\n\t/**\n\t* Check if a log level should be output\n\t*\n\t* @param level - Log level to check\n\t* @returns True if level meets threshold\n\t*/\n\tshouldLog(level) {\n\t\tconst currentIndex = ConsoleLogger.LEVELS.indexOf(this.level);\n\t\treturn ConsoleLogger.LEVELS.indexOf(level) >= currentIndex;\n\t}\n\t/**\n\t* Format context for console output\n\t*\n\t* @param context - Structured metadata\n\t* @returns Formatted context string\n\t*/\n\tformatContext(context) {\n\t\tif (!context || Object.keys(context).length === 0) return \"\";\n\t\treturn ` ${JSON.stringify(context)}`;\n\t}\n\tdebug(message, context) {\n\t\tif (this.shouldLog(\"debug\")) console.debug(`[DEBUG] ${message}${this.formatContext(context)}`);\n\t}\n\tinfo(message, context) {\n\t\tif (this.shouldLog(\"info\")) console.info(`[INFO] ${message}${this.formatContext(context)}`);\n\t}\n\twarn(message, context) {\n\t\tif (this.shouldLog(\"warn\")) console.warn(`[WARN] ${message}${this.formatContext(context)}`);\n\t}\n\terror(message, context) {\n\t\tif (this.shouldLog(\"error\")) console.error(`[ERROR] ${message}${this.formatContext(context)}`);\n\t}\n};\n//#endregion\n//#region src/index.ts\n/**\n* No-op logger that discards all log messages\n*\n* Used when logging is disabled (config: false)\n*/\nvar NoopLogger = class {\n\tdebug(_message, _context) {}\n\tinfo(_message, _context) {}\n\twarn(_message, _context) {}\n\terror(_message, _context) {}\n};\n/**\n* Create a logger from configuration\n*\n* Supports environment variable configuration via HAVE_LOGGER_LEVEL.\n* User-provided options take precedence over environment variables.\n*\n* @param config - Logger configuration (boolean or object)\n* @returns Configured logger instance\n*\n* @example\n* ```typescript\n* // Console logger with 'info' level (default)\n* const logger1 = createLogger(true);\n*\n* // Console logger with level from HAVE_LOGGER_LEVEL env var\n* process.env.HAVE_LOGGER_LEVEL = 'debug';\n* const logger2 = createLogger(true); // Uses 'debug' from env\n*\n* // No-op logger (all log calls are discarded)\n* const logger3 = createLogger(false);\n*\n* // Console logger with 'debug' level (overrides env)\n* const logger4 = createLogger({ level: 'debug' });\n*\n* // Custom log level (user options take precedence)\n* process.env.HAVE_LOGGER_LEVEL = 'info';\n* const logger5 = createLogger({ level: 'warn' }); // Uses 'warn' not 'info'\n* ```\n*/\nfunction createLogger(config) {\n\tif (typeof config === \"boolean\") {\n\t\tif (!config) return new NoopLogger();\n\t\treturn new ConsoleLogger(loadEnvConfig({}, {\n\t\t\tpackageName: \"logger\",\n\t\t\tschema: { level: \"string\" }\n\t\t}).level || \"info\");\n\t}\n\treturn new ConsoleLogger(loadEnvConfig(config, {\n\t\tpackageName: \"logger\",\n\t\tschema: { level: \"string\" }\n\t}).level || \"info\");\n}\n/** @internal */\nvar PACKAGE_VERSION_INITIALIZED = true;\n//#endregion\nexport { ConsoleLogger, LoggerAdapter, PACKAGE_VERSION_INITIALIZED, createLogger };\n\n//# sourceMappingURL=index.js.map","/**\n * SecretService - High-level API for per-tenant secret management\n * @packageDocumentation\n */\n\n// Self-register this package's manifest for consumers that import via this\n// subpath without the main entry. See src/__smrt-register__.ts (issue #1132).\nimport '../__smrt-register__.js';\n\nimport { createLogger } from '@happyvertical/logger';\nimport {\n AMKUnavailableError,\n DecryptionError,\n type EncryptedEnvelope,\n EncryptionError,\n EnvelopeEncryption,\n getSecretStore,\n type SecretStore,\n TenantKeyMissingError,\n} from '@happyvertical/secrets';\nimport {\n getCurrentTenant,\n requireTenantId,\n withTenant,\n} from '@happyvertical/smrt-tenancy';\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport { SecretAuditLogCollection } from '../collections/SecretAuditLogCollection.js';\nimport { SecretCollection } from '../collections/SecretCollection.js';\nimport { TenantKeyCollection } from '../collections/TenantKeyCollection.js';\nimport type { Secret } from '../models/Secret.js';\nimport {\n createAuditEntry,\n type SecretAuditAction,\n type SecretAuditLog,\n} from '../models/SecretAuditLog.js';\nimport type { TenantKey } from '../models/TenantKey.js';\n\nconst logger = createLogger({ level: 'info' });\n\n/**\n * Options for creating a SecretService\n */\nexport interface SecretServiceOptions {\n /** Database connection */\n db: DatabaseInterface;\n /** Environment variable containing the AMK (64 hex chars) */\n amkEnvVar?: string;\n /** AMK key identifier */\n amkKeyId?: string;\n /** Enable audit logging (default: true) */\n auditEnabled?: boolean;\n}\n\n/**\n * Options for storing a secret\n */\nexport interface StoreSecretOptions {\n /** Human-readable description */\n description?: string;\n /** Category for organization */\n category?: string;\n /** Optional expiration date */\n expiresAt?: Date;\n /** Additional metadata */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Result of retrieving a secret\n */\nexport interface RetrievedSecret {\n /** The decrypted secret value */\n value: string;\n /** Secret metadata */\n name: string;\n description: string;\n category: string;\n expiresAt: Date | null;\n createdAt: Date;\n lastAccessedAt: Date | null;\n accessCount: number;\n metadata: Record<string, unknown>;\n}\n\nexport type SecretKeyDriftIssueSeverity = 'info' | 'warning' | 'error';\n\nexport type SecretKeyDriftIssueCode =\n | 'amk_unavailable'\n | 'active_secrets_without_usable_active_key'\n | 'missing_active_tenant_encryption_key'\n | 'multiple_active_tenant_encryption_keys'\n | 'active_tenant_encryption_key_amk_mismatch'\n | 'active_tenant_encryption_key_unwrap_failed'\n | 'secret_envelope_invalid_json'\n | 'secret_envelope_invalid_wrapped_key'\n | 'secret_envelope_missing_tenant_encryption_key'\n | 'secret_envelope_unwrap_failed'\n | 'smrt_tenant_keys_query_failed'\n | 'smrt_tenant_keys_not_mirrored';\n\nexport type SecretKeyDriftRepairAction =\n | 'delete-unrecoverable-secret'\n | 'delete-unusable-tenant-encryption-key'\n | 'store-fresh-secret-value'\n | 'none';\n\nexport interface SecretKeyDriftIssue {\n code: SecretKeyDriftIssueCode;\n severity: SecretKeyDriftIssueSeverity;\n message: string;\n repairAction: SecretKeyDriftRepairAction;\n secretId?: string;\n secretName?: string;\n keyId?: string;\n sourceTable?: 'secrets' | 'tenant_encryption_keys' | 'tenant_keys';\n details?: Record<string, string | number | boolean | null>;\n}\n\nexport interface DiagnoseTenantSecretKeyDriftOptions {\n /**\n * Limit secret-envelope checks to these names. Tenant key checks still run.\n */\n secretNames?: string[];\n}\n\nexport interface SecretKeyDriftReport {\n tenantId: string;\n checkedAt: Date;\n ok: boolean;\n summary: {\n activeSecretCount: number;\n tenantEncryptionKeyCount: number;\n activeTenantEncryptionKeyCount: number;\n usableActiveTenantEncryptionKeyCount: number;\n smrtTenantKeyCount: number;\n activeSmrtTenantKeyCount: number;\n };\n issues: SecretKeyDriftIssue[];\n}\n\nexport interface RepairTenantSecretKeyDriftOptions\n extends DiagnoseTenantSecretKeyDriftOptions {\n /**\n * Preview affected rows without deleting anything.\n */\n dryRun?: boolean;\n /**\n * Required for destructive repair. This deletes encrypted values/key rows\n * that cannot be used with the currently configured AMK.\n */\n confirmDeleteUnrecoverableData?: boolean;\n}\n\nexport interface SecretKeyDriftRepairResult {\n tenantId: string;\n dryRun: boolean;\n issuesBefore: SecretKeyDriftIssue[];\n remainingIssues: SecretKeyDriftIssue[];\n wouldDeleteSecrets: number;\n wouldDeleteTenantEncryptionKeys: number;\n deletedSecrets: number;\n deletedTenantEncryptionKeys: number;\n secretNames: string[];\n tenantEncryptionKeyIds: string[];\n}\n\nexport class SecretKeyDriftError extends Error {\n readonly code = 'SECRET_KEY_DRIFT';\n readonly tenantId: string;\n readonly report: SecretKeyDriftReport;\n readonly cause?: Error;\n\n constructor(\n message: string,\n tenantId: string,\n report: SecretKeyDriftReport,\n cause?: Error,\n ) {\n super(message);\n this.name = 'SecretKeyDriftError';\n this.tenantId = tenantId;\n this.report = report;\n this.cause = cause;\n }\n}\n\ninterface TenantEncryptionKeyRow {\n id: string;\n tenant_id: string;\n wrapped_key: string;\n amk_key_id: string;\n status: string;\n version: number;\n rotate_after: string | null;\n retired_at: string | null;\n created_at: string;\n updated_at: string;\n}\n\ninterface SecretDiagnosisRow {\n id: string;\n name: string;\n encrypted_value: string;\n status: string;\n tenant_id: string;\n}\n\ninterface WrappedKeyCheck {\n usable: boolean;\n error?: string;\n fingerprint?: string;\n}\n\ninterface SmrtTenantKeyDiagnosisRows {\n keys: TenantKey[];\n error?: Error;\n}\n\ntype TransactionCapableDatabase = DatabaseInterface & {\n transaction?: <T>(\n callback: (tx: DatabaseInterface) => Promise<T>,\n ) => Promise<T>;\n};\n\n/**\n * SecretService provides high-level operations for managing per-tenant secrets.\n *\n * It integrates with:\n * - `@happyvertical/secrets` for envelope encryption\n * - `@happyvertical/smrt-tenancy` for tenant context\n * - Audit logging for compliance\n *\n * @example\n * ```typescript\n * import { SecretService } from '@happyvertical/smrt-secrets';\n * import { withTenant } from '@happyvertical/smrt-tenancy';\n *\n * const service = await SecretService.create({ db });\n *\n * await withTenant({ tenantId: 'tenant-123' }, async () => {\n * // Store a secret\n * await service.store('stripe-api-key', 'sk_live_xxx', {\n * category: 'api-keys',\n * description: 'Stripe production API key'\n * });\n *\n * // Retrieve the secret\n * const secret = await service.retrieve('stripe-api-key');\n * console.log(secret.value); // 'sk_live_xxx'\n *\n * // List secret names (without values)\n * const secrets = await service.list();\n *\n * // Rotate tenant's encryption key\n * await service.rotateKey();\n *\n * // Delete a secret\n * await service.delete('stripe-api-key');\n * });\n * ```\n */\nexport class SecretService {\n private db: DatabaseInterface;\n private secretStore: SecretStore;\n private secrets: SecretCollection;\n private tenantKeys: TenantKeyCollection;\n private auditLogs: SecretAuditLogCollection;\n private auditEnabled: boolean;\n private amkEnvVar: string;\n private amkKeyId: string;\n\n private constructor(\n db: DatabaseInterface,\n secretStore: SecretStore,\n secrets: SecretCollection,\n tenantKeys: TenantKeyCollection,\n auditLogs: SecretAuditLogCollection,\n auditEnabled: boolean,\n amkEnvVar: string,\n amkKeyId: string,\n ) {\n this.db = db;\n this.secretStore = secretStore;\n this.secrets = secrets;\n this.tenantKeys = tenantKeys;\n this.auditLogs = auditLogs;\n this.auditEnabled = auditEnabled;\n this.amkEnvVar = amkEnvVar;\n this.amkKeyId = amkKeyId;\n }\n\n /**\n * Create a new SecretService instance\n */\n static async create(options: SecretServiceOptions): Promise<SecretService> {\n const {\n db,\n amkEnvVar = 'SMRT_SECRET_MASTER_KEY',\n amkKeyId = 'smrt-amk-v1',\n auditEnabled = true,\n } = options;\n\n // Create the underlying secret store\n const secretStore = await getSecretStore({\n type: 'database',\n db,\n amk: {\n provider: 'env',\n keyEnvVar: amkEnvVar,\n keyId: amkKeyId,\n },\n });\n\n // Create collections - pass db directly (DatabaseConfig accepts DatabaseInterface)\n const baseOptions = { db };\n const secrets = await SecretCollection.create(baseOptions);\n const tenantKeys = await TenantKeyCollection.create(baseOptions);\n const auditLogs = await SecretAuditLogCollection.create(baseOptions);\n\n return new SecretService(\n db,\n secretStore,\n secrets,\n tenantKeys,\n auditLogs,\n auditEnabled,\n amkEnvVar,\n amkKeyId,\n );\n }\n\n /**\n * Store a secret for the current tenant\n */\n async store(\n name: string,\n value: string,\n options: StoreSecretOptions = {},\n ): Promise<Secret> {\n const tenantId = requireTenantId();\n const userId = this.getCurrentUserId();\n\n // Track whether this is an update to use correct audit action on error\n let isUpdate = false;\n\n try {\n // Check if secret already exists for THIS tenant (issue #1501: an\n // unscoped lookup here used to find another tenant's same-named row and\n // clobber it with an envelope encrypted under the caller's TDEK).\n let existing = await this.secrets.findByName(tenantId, name);\n // Defense-in-depth: even if a lookup regression ever returns a foreign\n // row again, never save over it — fall through to the create path,\n // which is tenant-scoped via the (slug, context=tenantId) upsert key.\n if (existing && existing.tenantId !== tenantId) {\n existing = null;\n }\n isUpdate = existing !== null;\n\n // Encrypt the value\n const envelope = await this.secretStore.encrypt(tenantId, name, value, {\n metadata: options.metadata\n ? this.serializeMetadata(options.metadata)\n : undefined,\n });\n\n if (existing) {\n // Update existing secret\n existing.encryptedValue = JSON.stringify(envelope);\n existing.description = options.description ?? existing.description;\n existing.category = options.category ?? existing.category;\n existing.expiresAt = options.expiresAt ?? existing.expiresAt;\n existing.metadata = options.metadata ?? existing.metadata;\n await existing.save();\n\n await this.audit(\n existing.id ?? null,\n name,\n userId,\n 'update',\n 'success',\n );\n return existing;\n }\n\n // Create new secret\n // Set context to tenantId for per-tenant uniqueness\n // The UPSERT uses (slug, context) as conflict columns, so different tenants\n // can have secrets with the same name\n const secret = await this.secrets.create({\n name,\n description: options.description ?? '',\n category: options.category ?? '',\n encryptedValue: JSON.stringify(envelope),\n keyVersion: 1,\n status: 'active',\n expiresAt: options.expiresAt ?? null,\n metadata: options.metadata ?? {},\n context: tenantId, // Per-tenant uniqueness\n tenantId,\n });\n\n await this.audit(secret.id ?? null, name, userId, 'create', 'success');\n return secret;\n } catch (error) {\n const classifiedError = await this.classifyTenantKeyFailure(\n tenantId,\n name,\n error,\n );\n await this.audit(\n null,\n name,\n userId,\n isUpdate ? 'update' : 'create',\n 'failure',\n {\n error: classifiedError.message,\n },\n );\n throw classifiedError;\n }\n }\n\n /**\n * Store a secret for a specific tenant.\n *\n * This is useful for integrations that already resolved tenant ownership but\n * may be running outside the application's ambient tenant context.\n */\n async storeForTenant(\n tenantId: string,\n name: string,\n value: string,\n options: StoreSecretOptions = {},\n ): Promise<Secret> {\n return withTenant({ tenantId }, () => this.store(name, value, options));\n }\n\n /**\n * Retrieve a secret for the current tenant\n */\n async retrieve(name: string): Promise<RetrievedSecret> {\n const tenantId = requireTenantId();\n const userId = this.getCurrentUserId();\n\n // Track whether we've already audited to avoid double-auditing\n let audited = false;\n\n try {\n const secret = await this.secrets.findByName(tenantId, name);\n // Ownership check before any decrypt attempt (issue #1501): a row owned\n // by another tenant must behave exactly like \"not found\", never surface\n // as a cross-tenant decrypt failure.\n if (!secret || secret.tenantId !== tenantId) {\n await this.audit(null, name, userId, 'read', 'failure', {\n error: 'Secret not found',\n });\n audited = true;\n throw new Error(`Secret '${name}' not found`);\n }\n\n if (!secret.isUsable()) {\n const reason = secret.isExpired()\n ? 'Secret expired'\n : 'Secret disabled';\n await this.audit(secret.id ?? null, name, userId, 'read', 'failure', {\n error: reason,\n });\n audited = true;\n throw new Error(reason);\n }\n\n // Decrypt the value\n const envelope: EncryptedEnvelope = JSON.parse(secret.encryptedValue);\n const decrypted = await this.secretStore.decrypt(tenantId, envelope);\n\n // Access tracking is operational telemetry. Retrieval should still\n // succeed if the decrypted value is available but this write fails.\n const previousLastAccessedAt = secret.lastAccessedAt;\n const previousAccessCount = secret.accessCount;\n try {\n secret.recordAccess();\n await secret.save();\n } catch (trackingError) {\n secret.lastAccessedAt = previousLastAccessedAt;\n secret.accessCount = previousAccessCount;\n logger.error('Failed to update secret access tracking', {\n error: trackingError,\n });\n }\n\n await this.audit(secret.id ?? null, name, userId, 'read', 'success');\n\n return {\n value: decrypted.value,\n name: secret.name,\n description: secret.description,\n category: secret.category,\n expiresAt: secret.expiresAt,\n createdAt: secret.created_at ?? new Date(),\n lastAccessedAt: secret.lastAccessedAt,\n accessCount: secret.accessCount,\n metadata: secret.metadata,\n };\n } catch (error) {\n const classifiedError = await this.classifyTenantKeyFailure(\n tenantId,\n name,\n error,\n );\n // Only audit if we haven't already audited this error\n if (!audited) {\n await this.audit(null, name, userId, 'read', 'failure', {\n error: classifiedError.message,\n });\n }\n throw classifiedError;\n }\n }\n\n /**\n * Retrieve a secret for a specific tenant.\n */\n async retrieveForTenant(\n tenantId: string,\n name: string,\n ): Promise<RetrievedSecret> {\n return withTenant({ tenantId }, () => this.retrieve(name));\n }\n\n /**\n * Diagnose tenant secret/key drift without exposing decrypted values.\n */\n async diagnoseTenantSecretKeyDrift(\n tenantId: string,\n options: DiagnoseTenantSecretKeyDriftOptions = {},\n ): Promise<SecretKeyDriftReport> {\n const activeSecrets = await this.listActiveSecretRowsForDiagnosis(\n tenantId,\n options.secretNames,\n );\n const tenantEncryptionKeys =\n await this.listTenantEncryptionKeyRows(tenantId);\n const smrtTenantKeyRows =\n await this.listSmrtTenantKeysForDiagnosis(tenantId);\n const smrtTenantKeys = smrtTenantKeyRows.keys;\n const issues: SecretKeyDriftIssue[] = [];\n\n const activeTenantEncryptionKeys = tenantEncryptionKeys.filter(\n (key) => key.status === 'active',\n );\n const activeSmrtTenantKeys = smrtTenantKeys.filter(\n (key) => key.status === 'active',\n );\n const amk = this.getConfiguredAmkForDiagnosis();\n\n if (!amk.usable) {\n issues.push({\n code: 'amk_unavailable',\n severity: 'error',\n message: amk.error ?? `AMK ${this.amkEnvVar} is unavailable`,\n repairAction: 'none',\n details: {\n amkEnvVar: this.amkEnvVar,\n amkKeyId: this.amkKeyId,\n },\n });\n }\n\n if (activeSecrets.length > 0 && activeTenantEncryptionKeys.length === 0) {\n issues.push({\n code: 'missing_active_tenant_encryption_key',\n severity: 'error',\n message:\n 'Active tenant secrets exist, but tenant_encryption_keys has no active key for encryption.',\n repairAction: 'store-fresh-secret-value',\n sourceTable: 'tenant_encryption_keys',\n details: {\n activeSecretCount: activeSecrets.length,\n },\n });\n }\n\n if (smrtTenantKeyRows.error) {\n issues.push({\n code: 'smrt_tenant_keys_query_failed',\n severity: 'error',\n message:\n 'Unable to query SMRT tenant_keys while diagnosing tenant secret key drift.',\n repairAction: 'none',\n sourceTable: 'tenant_keys',\n details: {\n error: smrtTenantKeyRows.error.message,\n },\n });\n }\n\n if (activeTenantEncryptionKeys.length > 1) {\n issues.push({\n code: 'multiple_active_tenant_encryption_keys',\n severity: 'error',\n message:\n 'tenant_encryption_keys has multiple active keys for this tenant; encryption may use an arbitrary active key.',\n repairAction: 'none',\n sourceTable: 'tenant_encryption_keys',\n details: {\n activeTenantEncryptionKeyCount: activeTenantEncryptionKeys.length,\n },\n });\n }\n\n const activeKeyChecks: WrappedKeyCheck[] = [];\n\n for (const key of tenantEncryptionKeys) {\n const check = amk.value\n ? this.checkWrappedKey(key.wrapped_key, amk.value)\n : { usable: false, error: amk.error };\n\n if (key.status === 'active') {\n activeKeyChecks.push(check);\n }\n\n if (key.status === 'active' && key.amk_key_id !== this.amkKeyId) {\n issues.push({\n code: 'active_tenant_encryption_key_amk_mismatch',\n severity: 'warning',\n message:\n 'Active tenant_encryption_keys row was wrapped by a different AMK key id than this SecretService is configured to use.',\n repairAction: 'none',\n keyId: key.id,\n sourceTable: 'tenant_encryption_keys',\n details: {\n rowAmkKeyId: key.amk_key_id,\n configuredAmkKeyId: this.amkKeyId,\n },\n });\n }\n\n if (key.status === 'active' && !check.usable && amk.value) {\n issues.push({\n code: 'active_tenant_encryption_key_unwrap_failed',\n severity: 'error',\n message:\n 'Active tenant_encryption_keys row cannot be unwrapped by the currently configured AMK.',\n repairAction: 'delete-unusable-tenant-encryption-key',\n keyId: key.id,\n sourceTable: 'tenant_encryption_keys',\n details: {\n version: key.version,\n error: check.error ?? null,\n },\n });\n }\n }\n\n const usableActiveTenantEncryptionKeyCount = activeKeyChecks.filter(\n (check) => check.usable,\n ).length;\n\n if (\n activeSecrets.length > 0 &&\n usableActiveTenantEncryptionKeyCount === 0\n ) {\n issues.push({\n code: 'active_secrets_without_usable_active_key',\n severity: 'error',\n message:\n 'Active secrets exist, but no active tenant_encryption_keys row can be used with the current AMK.',\n repairAction: 'store-fresh-secret-value',\n sourceTable: 'tenant_encryption_keys',\n details: {\n activeSecretCount: activeSecrets.length,\n activeTenantEncryptionKeyCount: activeTenantEncryptionKeys.length,\n },\n });\n }\n\n const keyFingerprintToRow = new Map<string, TenantEncryptionKeyRow>();\n for (const key of tenantEncryptionKeys) {\n const fingerprint = this.getWrappedKeyFingerprint(key.wrapped_key);\n if (fingerprint) {\n keyFingerprintToRow.set(fingerprint, key);\n }\n }\n\n for (const secret of activeSecrets) {\n const envelope = this.parseSecretEnvelopeForDiagnosis(secret, issues);\n if (!envelope) continue;\n\n const envelopeFingerprint = this.getWrappedKeyFingerprint(\n envelope.wrappedKey,\n );\n const envelopeCheck = amk.value\n ? this.checkWrappedKey(envelope.wrappedKey, amk.value)\n : {\n usable: false,\n error: amk.error,\n fingerprint: envelopeFingerprint,\n };\n\n if (!envelopeCheck.fingerprint) {\n issues.push({\n code: 'secret_envelope_invalid_wrapped_key',\n severity: 'error',\n message:\n 'Secret encryptedValue contains an invalid wrapped key format.',\n repairAction: 'delete-unrecoverable-secret',\n secretId: secret.id,\n secretName: secret.name,\n sourceTable: 'secrets',\n details: {\n error: envelopeCheck.error ?? null,\n },\n });\n continue;\n }\n\n const matchingKey = keyFingerprintToRow.get(envelopeCheck.fingerprint);\n if (!matchingKey) {\n issues.push({\n code: 'secret_envelope_missing_tenant_encryption_key',\n severity: 'error',\n message:\n 'Secret envelope does not match any tenant_encryption_keys row for this tenant.',\n repairAction: !amk.value\n ? 'none'\n : envelopeCheck.usable\n ? 'none'\n : 'delete-unrecoverable-secret',\n secretId: secret.id,\n secretName: secret.name,\n sourceTable: 'secrets',\n });\n }\n\n if (!envelopeCheck.usable && amk.value) {\n issues.push({\n code: 'secret_envelope_unwrap_failed',\n severity: 'error',\n message:\n 'Secret envelope cannot be unwrapped by the currently configured AMK.',\n repairAction: 'delete-unrecoverable-secret',\n secretId: secret.id,\n secretName: secret.name,\n keyId: matchingKey?.id,\n sourceTable: 'secrets',\n details: {\n error: envelopeCheck.error ?? null,\n },\n });\n }\n }\n\n if (\n activeSecrets.length > 0 &&\n tenantEncryptionKeys.length > 0 &&\n smrtTenantKeys.length === 0 &&\n !smrtTenantKeyRows.error\n ) {\n issues.push({\n code: 'smrt_tenant_keys_not_mirrored',\n severity: 'info',\n message:\n 'SMRT tenant_keys has no rows for this tenant, while the lower-level tenant_encryption_keys table does. SecretService uses tenant_encryption_keys for encryption.',\n repairAction: 'none',\n sourceTable: 'tenant_keys',\n });\n }\n\n return {\n tenantId,\n checkedAt: new Date(),\n ok: !issues.some((issue) => issue.severity === 'error'),\n summary: {\n activeSecretCount: activeSecrets.length,\n tenantEncryptionKeyCount: tenantEncryptionKeys.length,\n activeTenantEncryptionKeyCount: activeTenantEncryptionKeys.length,\n usableActiveTenantEncryptionKeyCount,\n smrtTenantKeyCount: smrtTenantKeys.length,\n activeSmrtTenantKeyCount: activeSmrtTenantKeys.length,\n },\n issues,\n };\n }\n\n /**\n * Diagnose drift for the current tenant context.\n */\n async diagnoseCurrentTenantSecretKeyDrift(\n options: DiagnoseTenantSecretKeyDriftOptions = {},\n ): Promise<SecretKeyDriftReport> {\n return this.diagnoseTenantSecretKeyDrift(requireTenantId(), options);\n }\n\n /**\n * Delete unrecoverable secret/key rows identified by diagnosis.\n *\n * This never attempts to recover or expose secret values. Use dryRun first\n * to preview destructive changes.\n */\n async repairTenantSecretKeyDrift(\n tenantId: string,\n options: RepairTenantSecretKeyDriftOptions = {},\n ): Promise<SecretKeyDriftRepairResult> {\n const dryRun = options.dryRun ?? false;\n const before = await this.diagnoseTenantSecretKeyDrift(tenantId, options);\n const secretIds = new Set<string>();\n const secretNames = new Map<string, string>();\n const tenantEncryptionKeyIds = new Set<string>();\n\n for (const issue of before.issues) {\n if (\n issue.repairAction === 'delete-unrecoverable-secret' &&\n issue.secretId\n ) {\n secretIds.add(issue.secretId);\n if (issue.secretName) {\n secretNames.set(issue.secretId, issue.secretName);\n }\n }\n\n if (\n issue.repairAction === 'delete-unusable-tenant-encryption-key' &&\n issue.keyId\n ) {\n tenantEncryptionKeyIds.add(issue.keyId);\n }\n }\n\n const wouldDeleteSecrets = secretIds.size;\n const wouldDeleteTenantEncryptionKeys = tenantEncryptionKeyIds.size;\n const wouldDeleteUnrecoverableData =\n wouldDeleteSecrets + wouldDeleteTenantEncryptionKeys > 0;\n\n if (\n !dryRun &&\n wouldDeleteUnrecoverableData &&\n !options.confirmDeleteUnrecoverableData\n ) {\n throw new Error(\n 'repairTenantSecretKeyDrift requires confirmDeleteUnrecoverableData: true before deleting encrypted secrets or tenant key rows.',\n );\n }\n\n let deletedSecrets = 0;\n let deletedTenantEncryptionKeys = 0;\n\n if (!dryRun) {\n const runDeletes = async (db: DatabaseInterface) => {\n const deletedSecretRows = await this.deleteRowsByIds(\n 'secrets',\n tenantId,\n secretIds,\n db,\n );\n const deletedTenantEncryptionKeyRows = await this.deleteRowsByIds(\n 'tenant_encryption_keys',\n tenantId,\n tenantEncryptionKeyIds,\n db,\n );\n return {\n deletedSecrets: deletedSecretRows,\n deletedTenantEncryptionKeys: deletedTenantEncryptionKeyRows,\n };\n };\n const txDb = this.db as TransactionCapableDatabase;\n const deleteResult =\n typeof txDb.transaction === 'function'\n ? await txDb.transaction((db) => runDeletes(db))\n : await runDeletes(this.db);\n\n deletedSecrets = deleteResult.deletedSecrets;\n deletedTenantEncryptionKeys = deleteResult.deletedTenantEncryptionKeys;\n await this.auditSecretDriftRepairDeletes(\n tenantId,\n secretIds,\n secretNames,\n );\n }\n\n const after = dryRun\n ? before\n : await this.diagnoseTenantSecretKeyDrift(tenantId, options);\n\n return {\n tenantId,\n dryRun,\n issuesBefore: before.issues,\n remainingIssues: after.issues,\n wouldDeleteSecrets,\n wouldDeleteTenantEncryptionKeys,\n deletedSecrets,\n deletedTenantEncryptionKeys,\n secretNames: Array.from(secretNames.values()).sort(),\n tenantEncryptionKeyIds: Array.from(tenantEncryptionKeyIds).sort(),\n };\n }\n\n /**\n * List secrets for the current tenant (names only, not values)\n */\n async list(options: { category?: string } = {}): Promise<Secret[]> {\n return this.secrets.listSecrets(requireTenantId(), {\n category: options.category,\n status: 'active',\n });\n }\n\n /**\n * Delete a secret\n */\n async delete(name: string): Promise<boolean> {\n const tenantId = requireTenantId();\n const userId = this.getCurrentUserId();\n\n const secret = await this.secrets.findByName(tenantId, name);\n // Ownership check (issue #1501): never delete another tenant's row\n if (!secret || secret.tenantId !== tenantId) {\n return false;\n }\n\n try {\n await secret.delete();\n await this.audit(secret.id ?? null, name, userId, 'delete', 'success');\n return true;\n } catch (error) {\n await this.audit(secret.id ?? null, name, userId, 'delete', 'failure', {\n error: (error as Error).message,\n });\n throw error;\n }\n }\n\n /**\n * Disable a secret (soft delete)\n */\n async disable(name: string): Promise<boolean> {\n const tenantId = requireTenantId();\n const userId = this.getCurrentUserId();\n\n const secret = await this.secrets.findByName(tenantId, name);\n // Ownership check (issue #1501): never mutate another tenant's row\n if (!secret || secret.tenantId !== tenantId) {\n return false;\n }\n\n secret.disable();\n await secret.save();\n await this.audit(secret.id ?? null, name, userId, 'disable', 'success');\n return true;\n }\n\n /**\n * Enable a disabled secret\n */\n async enable(name: string): Promise<boolean> {\n const tenantId = requireTenantId();\n const userId = this.getCurrentUserId();\n\n const secret = await this.secrets.findByName(tenantId, name);\n // Ownership check (issue #1501): never mutate another tenant's row\n if (!secret || secret.tenantId !== tenantId) {\n return false;\n }\n\n secret.enable();\n await secret.save();\n await this.audit(secret.id ?? null, name, userId, 'enable', 'success');\n return true;\n }\n\n /**\n * Rotate the tenant's encryption key\n *\n * This creates a new TDEK and marks the old one as retired.\n * Existing secrets remain encrypted with the old key and can still\n * be decrypted (the old key is kept in retired state).\n *\n * For full re-encryption, call reencryptAll() after rotation.\n */\n async rotateKey(): Promise<void> {\n const tenantId = requireTenantId();\n const userId = this.getCurrentUserId();\n\n try {\n await this.secretStore.rotateTenantKey(tenantId);\n await this.audit(null, '', userId, 'rotate_key', 'success', {\n tenantId,\n });\n } catch (error) {\n await this.audit(null, '', userId, 'rotate_key', 'failure', {\n tenantId,\n error: (error as Error).message,\n });\n throw error;\n }\n }\n\n /**\n * Re-encrypt all secrets with the current active key\n *\n * Call this after key rotation to ensure all secrets use the new key.\n * This is optional but recommended for security.\n */\n async reencryptAll(): Promise<{ success: number; failed: number }> {\n const tenantId = requireTenantId();\n const userId = this.getCurrentUserId();\n\n // Scope to the current tenant (issue #1501): re-encryption must never\n // touch (or attempt to decrypt) other tenants' rows.\n const secrets = await this.secrets.list({ where: { tenantId } });\n let success = 0;\n let failed = 0;\n\n for (const secret of secrets) {\n try {\n // Decrypt with old key\n const envelope: EncryptedEnvelope = JSON.parse(secret.encryptedValue);\n const decrypted = await this.secretStore.decrypt(tenantId, envelope);\n\n // Re-encrypt with new key\n const newEnvelope = await this.secretStore.encrypt(\n tenantId,\n secret.name,\n decrypted.value,\n );\n\n secret.encryptedValue = JSON.stringify(newEnvelope);\n await secret.save();\n\n success++;\n } catch (error) {\n failed++;\n await this.audit(\n secret.id ?? null,\n secret.name,\n userId,\n 'update',\n 'failure',\n {\n action: 'reencrypt',\n error: (error as Error).message,\n },\n );\n }\n }\n\n return { success, failed };\n }\n\n /**\n * Get audit logs for the current tenant\n */\n async getAuditLogs(\n options: { secretName?: string; limit?: number } = {},\n ): Promise<SecretAuditLog[]> {\n return this.auditLogs.listLogs({\n // Scope to the current tenant (issue #1501): audit logs reference\n // secret names and must not leak across tenants.\n tenantId: requireTenantId(),\n secretName: options.secretName,\n limit: options.limit ?? 100,\n });\n }\n\n /**\n * Get secret categories for the current tenant\n */\n async getCategories(): Promise<string[]> {\n return this.secrets.getCategories(requireTenantId());\n }\n\n /**\n * Check if a secret exists for the current tenant\n */\n async exists(name: string): Promise<boolean> {\n const tenantId = requireTenantId();\n const secret = await this.secrets.findByName(tenantId, name);\n return secret !== null && secret.tenantId === tenantId;\n }\n\n // Private methods\n\n private async listActiveSecretRowsForDiagnosis(\n tenantId: string,\n secretNames?: string[],\n ): Promise<SecretDiagnosisRow[]> {\n const params: unknown[] = [tenantId];\n let nameFilter = '';\n\n if (secretNames && secretNames.length > 0) {\n const placeholders = secretNames.map(() => '?').join(', ');\n nameFilter = ` AND name IN (${placeholders})`;\n params.push(...secretNames);\n }\n\n const result = await this.db.query(\n `\n SELECT id, name, encrypted_value, status, tenant_id\n FROM \"secrets\"\n WHERE tenant_id = ? AND status = 'active'${nameFilter}\n ORDER BY name ASC\n `,\n ...params,\n );\n\n return this.rowsFromResult<SecretDiagnosisRow>(result);\n }\n\n private async listTenantEncryptionKeyRows(\n tenantId: string,\n ): Promise<TenantEncryptionKeyRow[]> {\n const result = await this.db.query(\n `\n SELECT id, tenant_id, wrapped_key, amk_key_id, status, version,\n rotate_after, retired_at, created_at, updated_at\n FROM \"tenant_encryption_keys\"\n WHERE tenant_id = ?\n ORDER BY version DESC\n `,\n tenantId,\n );\n\n return this.rowsFromResult<TenantEncryptionKeyRow>(result);\n }\n\n private async listSmrtTenantKeysForDiagnosis(\n tenantId: string,\n ): Promise<SmrtTenantKeyDiagnosisRows> {\n try {\n return { keys: await this.tenantKeys.listKeyVersions(tenantId) };\n } catch (error) {\n return { keys: [], error: this.toError(error) };\n }\n }\n\n private getConfiguredAmkForDiagnosis():\n | { usable: true; value: Buffer }\n | { usable: false; error: string; value?: undefined } {\n const keyHex = process.env[this.amkEnvVar];\n if (!keyHex) {\n return {\n usable: false,\n error: `Application Master Key not found in environment variable: ${this.amkEnvVar}`,\n };\n }\n\n try {\n return {\n usable: true,\n value: EnvelopeEncryption.parseHexKey(keyHex),\n };\n } catch (error) {\n return {\n usable: false,\n error: `Invalid AMK in ${this.amkEnvVar}: ${\n this.toError(error).message\n }`,\n };\n }\n }\n\n private checkWrappedKey(wrappedKey: string, amk: Buffer): WrappedKeyCheck {\n const fingerprint = this.getWrappedKeyFingerprint(wrappedKey);\n\n try {\n const parsed = EnvelopeEncryption.parseWrappedKey(wrappedKey);\n const dataKey = EnvelopeEncryption.unwrapKey(\n parsed.wrappedKey,\n parsed.iv,\n parsed.authTag,\n amk,\n );\n dataKey.fill(0);\n return { usable: true, fingerprint };\n } catch (error) {\n return {\n usable: false,\n fingerprint,\n error: this.toError(error).message,\n };\n }\n }\n\n private getWrappedKeyFingerprint(wrappedKey: string): string | undefined {\n try {\n return EnvelopeEncryption.parseWrappedKey(wrappedKey).wrappedKey;\n } catch {\n return undefined;\n }\n }\n\n private parseSecretEnvelopeForDiagnosis(\n secret: SecretDiagnosisRow,\n issues: SecretKeyDriftIssue[],\n ): EncryptedEnvelope | null {\n try {\n return JSON.parse(secret.encrypted_value) as EncryptedEnvelope;\n } catch (error) {\n issues.push({\n code: 'secret_envelope_invalid_json',\n severity: 'error',\n message: 'Secret encryptedValue is not valid EncryptedEnvelope JSON.',\n repairAction: 'delete-unrecoverable-secret',\n secretId: secret.id,\n secretName: secret.name,\n sourceTable: 'secrets',\n details: {\n error: this.toError(error).message,\n },\n });\n return null;\n }\n }\n\n private async deleteRowsByIds(\n tableName: 'secrets' | 'tenant_encryption_keys',\n tenantId: string,\n ids: Set<string>,\n db: DatabaseInterface = this.db,\n ): Promise<number> {\n if (ids.size === 0) return 0;\n\n const idList = Array.from(ids);\n const placeholders = idList.map(() => '?').join(', ');\n const result = await db.query(\n `DELETE FROM \"${tableName}\" WHERE tenant_id = ? AND id IN (${placeholders})`,\n tenantId,\n ...idList,\n );\n\n return typeof result.rowCount === 'number'\n ? result.rowCount\n : idList.length;\n }\n\n private async auditSecretDriftRepairDeletes(\n tenantId: string,\n secretIds: Set<string>,\n secretNames: Map<string, string>,\n ): Promise<void> {\n if (secretIds.size === 0) return;\n\n const userId = this.getCurrentUserId();\n await withTenant({ tenantId }, async () => {\n for (const secretId of secretIds) {\n await this.audit(\n secretId,\n secretNames.get(secretId) ?? '',\n userId,\n 'delete',\n 'success',\n {\n action: 'repairTenantSecretKeyDrift',\n reason: 'unrecoverable-secret-key-drift',\n },\n );\n }\n });\n }\n\n private rowsFromResult<T>(result: unknown): T[] {\n if (Array.isArray(result)) return result as T[];\n if (\n result &&\n typeof result === 'object' &&\n Array.isArray((result as { rows?: unknown }).rows)\n ) {\n return (result as { rows: T[] }).rows;\n }\n return [];\n }\n\n private async classifyTenantKeyFailure(\n tenantId: string,\n secretName: string,\n error: unknown,\n ): Promise<Error> {\n const normalized = this.toError(error);\n if (!this.shouldClassifyTenantKeyFailure(normalized)) {\n return normalized;\n }\n\n try {\n const report = await this.diagnoseTenantSecretKeyDrift(tenantId, {\n secretNames: [secretName],\n });\n const errorCodes = report.issues\n .filter((issue) => issue.severity === 'error')\n .map((issue) => issue.code);\n\n if (errorCodes.length === 0) {\n return normalized;\n }\n\n return new SecretKeyDriftError(\n `Secret '${secretName}' for tenant '${tenantId}' failed because secret key drift was detected: ${[\n ...new Set(errorCodes),\n ].join(\n ', ',\n )}. Run diagnoseTenantSecretKeyDrift() for details and repairTenantSecretKeyDrift() for explicit cleanup of unrecoverable rows.`,\n tenantId,\n report,\n normalized,\n );\n } catch {\n return normalized;\n }\n }\n\n private shouldClassifyTenantKeyFailure(error: Error): boolean {\n const code = this.getSecretErrorCode(error);\n if (error instanceof AMKUnavailableError || code === 'AMK_UNAVAILABLE') {\n return false;\n }\n\n return (\n error instanceof TenantKeyMissingError ||\n error instanceof EncryptionError ||\n error instanceof DecryptionError ||\n code === 'TENANT_KEY_MISSING' ||\n code === 'ENCRYPTION_FAILED' ||\n code === 'DECRYPTION_FAILED'\n );\n }\n\n private getSecretErrorCode(error: Error): string | undefined {\n const code = (error as { code?: unknown }).code;\n return typeof code === 'string' ? code : undefined;\n }\n\n private toError(error: unknown): Error {\n return error instanceof Error ? error : new Error(String(error));\n }\n\n private getCurrentUserId(): string {\n const ctx = getCurrentTenant();\n return ctx?.userId ?? 'system';\n }\n\n private async audit(\n secretId: string | null,\n secretName: string,\n userId: string,\n action: SecretAuditAction,\n result: 'success' | 'failure' | 'denied',\n details?: Record<string, unknown>,\n ): Promise<void> {\n if (!this.auditEnabled) return;\n\n try {\n const tenantId = getCurrentTenant()?.tenantId ?? null;\n const log = await this.auditLogs.create(\n createAuditEntry({\n secretId,\n secretName,\n userId,\n action,\n result,\n details,\n tenantId,\n }),\n );\n await log.save();\n } catch (error) {\n // Don't throw on audit failure - log and continue\n logger.error('Failed to write audit log', { error });\n }\n }\n\n private serializeMetadata(\n metadata: Record<string, unknown>,\n ): Record<string, string> {\n const result: Record<string, string> = {};\n for (const [key, value] of Object.entries(metadata)) {\n result[key] = typeof value === 'string' ? value : JSON.stringify(value);\n }\n return result;\n }\n}\n"],"x_google_ignoreList":[3],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SecretService-CrkpsGf8.js","names":[],"sources":["../../src/collections/SecretAuditLogCollection.ts","../../src/collections/SecretCollection.ts","../../src/collections/TenantKeyCollection.ts","../../../../node_modules/.pnpm/@happyvertical+logger@0.80.2_@sentry+node@10.63.0_@opentelemetry+core@2.7.0_@opentelemetry+api@1.9.1__/node_modules/@happyvertical/logger/dist/index.js","../../src/services/SecretService.ts"],"sourcesContent":["/**\n * SecretAuditLogCollection - Collection manager for SecretAuditLog objects\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport {\n type SecretAuditAction,\n SecretAuditLog,\n type SecretAuditResult,\n} from '../models/SecretAuditLog.js';\n\n/**\n * Options for listing audit logs\n */\nexport interface ListAuditLogsOptions {\n /**\n * Scope results to a single tenant's audit trail. Tenant-facing callers\n * (e.g. SecretService.getAuditLogs) must always set this — audit rows\n * reference secret names and must not leak across tenants (issue #1501).\n * Omit only for cross-tenant compliance tooling running under\n * withSuperAdminBypass().\n */\n tenantId?: string;\n /** Filter by secret name */\n secretName?: string;\n /** Filter by user ID */\n userId?: string;\n /** Filter by action type */\n action?: SecretAuditAction;\n /** Filter by result */\n result?: SecretAuditResult;\n /** Filter by date range start */\n since?: Date;\n /** Filter by date range end */\n until?: Date;\n /** Maximum number of results */\n limit?: number;\n /** Offset for pagination */\n offset?: number;\n}\n\n/**\n * Collection for managing SecretAuditLog objects\n */\nexport class SecretAuditLogCollection extends SmrtCollection<SecretAuditLog> {\n static readonly _itemClass = SecretAuditLog;\n\n /**\n * List audit logs with filtering options\n */\n async listLogs(\n options: ListAuditLogsOptions = {},\n ): Promise<SecretAuditLog[]> {\n const where: Record<string, unknown> = {};\n\n if (options.tenantId) {\n where.tenantId = options.tenantId;\n }\n\n if (options.secretName) {\n where.secretName = options.secretName;\n }\n\n if (options.userId) {\n where.userId = options.userId;\n }\n\n if (options.action) {\n where.action = options.action;\n }\n\n if (options.result) {\n where.result = options.result;\n }\n\n if (options.since) {\n where['created_at >'] = options.since.toISOString();\n }\n\n if (options.until) {\n where['created_at <'] = options.until.toISOString();\n }\n\n return this.list({\n where,\n limit: options.limit ?? 100,\n offset: options.offset,\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Get audit logs for a specific secret.\n *\n * @param tenantId - Scope to this tenant's audit trail, or `null` for a\n * cross-tenant compliance query (which must run under\n * `withSuperAdminBypass()` from `@happyvertical/smrt-tenancy`). Audit rows\n * reference secret names and must not leak across tenants (#1503).\n */\n async getSecretHistory(\n tenantId: string | null,\n secretName: string,\n limit: number = 50,\n ): Promise<SecretAuditLog[]> {\n return this.listLogs({\n tenantId: tenantId ?? undefined,\n secretName,\n limit,\n });\n }\n\n /**\n * Get audit logs for a specific user.\n *\n * @param tenantId - Scope to this tenant's audit trail, or `null` for a\n * cross-tenant compliance query under `withSuperAdminBypass()` (#1503).\n */\n async getUserActivity(\n tenantId: string | null,\n userId: string,\n limit: number = 50,\n ): Promise<SecretAuditLog[]> {\n return this.listLogs({ tenantId: tenantId ?? undefined, userId, limit });\n }\n\n /**\n * Get recent failures.\n *\n * @param tenantId - Scope to this tenant's audit trail, or `null` for a\n * cross-tenant compliance query under `withSuperAdminBypass()` (#1503).\n */\n async getRecentFailures(\n tenantId: string | null,\n limit: number = 20,\n ): Promise<SecretAuditLog[]> {\n return this.listLogs({\n tenantId: tenantId ?? undefined,\n result: 'failure',\n limit,\n });\n }\n\n /**\n * Get recent denied access attempts.\n *\n * @param tenantId - Scope to this tenant's audit trail, or `null` for a\n * cross-tenant compliance query under `withSuperAdminBypass()` (#1503).\n */\n async getRecentDenials(\n tenantId: string | null,\n limit: number = 20,\n ): Promise<SecretAuditLog[]> {\n return this.listLogs({\n tenantId: tenantId ?? undefined,\n result: 'denied',\n limit,\n });\n }\n\n /**\n * Count operations by action type.\n *\n * @param tenantId - Scope to this tenant's audit trail, or `null` for a\n * cross-tenant compliance count under `withSuperAdminBypass()` (#1503).\n */\n async countByAction(\n tenantId: string | null,\n since?: Date,\n ): Promise<Record<SecretAuditAction, number>> {\n const logs = await this.listLogs({\n tenantId: tenantId ?? undefined,\n since,\n limit: 10000,\n });\n\n const counts: Record<SecretAuditAction, number> = {\n create: 0,\n read: 0,\n update: 0,\n delete: 0,\n rotate_key: 0,\n disable: 0,\n enable: 0,\n expire: 0,\n };\n\n for (const log of logs) {\n counts[log.action]++;\n }\n\n return counts;\n }\n\n /**\n * Count operations by result.\n *\n * @param tenantId - Scope to this tenant's audit trail, or `null` for a\n * cross-tenant compliance count under `withSuperAdminBypass()` (#1503).\n */\n async countByResult(\n tenantId: string | null,\n since?: Date,\n ): Promise<Record<SecretAuditResult, number>> {\n const logs = await this.listLogs({\n tenantId: tenantId ?? undefined,\n since,\n limit: 10000,\n });\n\n const counts: Record<SecretAuditResult, number> = {\n success: 0,\n failure: 0,\n denied: 0,\n };\n\n for (const log of logs) {\n counts[log.result]++;\n }\n\n return counts;\n }\n\n /**\n * Delete old audit logs\n * @param olderThanDays Delete logs older than this many days\n */\n async cleanup(olderThanDays: number = 365): Promise<number> {\n const cutoffDate = new Date();\n cutoffDate.setDate(cutoffDate.getDate() - olderThanDays);\n\n const oldLogs = await this.list({\n where: {\n 'created_at <': cutoffDate.toISOString(),\n },\n });\n\n let count = 0;\n for (const log of oldLogs) {\n await log.delete();\n count++;\n }\n\n return count;\n }\n}\n","/**\n * SecretCollection - Collection manager for Secret objects\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { Secret, type SecretStatus } from '../models/Secret.js';\n\n/**\n * Options for listing secrets\n */\nexport interface ListSecretsOptions {\n /** Filter by category */\n category?: string;\n /** Filter by status */\n status?: SecretStatus;\n /** Include expired secrets */\n includeExpired?: boolean;\n /** Maximum number of results */\n limit?: number;\n /** Offset for pagination */\n offset?: number;\n}\n\n/**\n * Collection for managing Secret objects\n *\n * All lookups take an explicit `tenantId` and scope on the authoritative\n * `tenant_id` column. Scoping must NOT rely on the tenancy interceptor\n * (which may be disabled in the host application) and must NOT use the\n * `context` column: `context = tenantId` is only a convention applied on\n * the create path, so pre-convention rows may have a divergent `context`.\n * See https://github.com/happyvertical/smrt/issues/1501\n */\nexport class SecretCollection extends SmrtCollection<Secret> {\n static readonly _itemClass = Secret;\n\n /**\n * Find a secret by name within the given tenant\n */\n async findByName(tenantId: string, name: string): Promise<Secret | null> {\n return this.get({ name, tenantId });\n }\n\n /**\n * List secrets for a tenant with filtering options\n */\n async listSecrets(\n tenantId: string,\n options: ListSecretsOptions = {},\n ): Promise<Secret[]> {\n const where: Record<string, unknown> = { tenantId };\n\n if (options.category) {\n where.category = options.category;\n }\n\n if (options.status) {\n where.status = options.status;\n }\n\n const secrets = await this.list({\n where,\n limit: options.limit,\n offset: options.offset,\n orderBy: 'name ASC',\n });\n\n // Filter out expired secrets unless explicitly included\n if (!options.includeExpired) {\n return secrets.filter((secret) => !secret.isExpired());\n }\n\n return secrets;\n }\n\n /**\n * List all active secrets for a tenant\n */\n async listActive(tenantId: string): Promise<Secret[]> {\n return this.listSecrets(tenantId, { status: 'active' });\n }\n\n /**\n * List a tenant's secrets by category\n */\n async listByCategory(tenantId: string, category: string): Promise<Secret[]> {\n return this.listSecrets(tenantId, { category, status: 'active' });\n }\n\n /**\n * List a tenant's secrets that need attention (expired or about to expire)\n */\n async listExpiring(\n tenantId: string,\n daysAhead: number = 30,\n ): Promise<Secret[]> {\n const futureDate = new Date();\n futureDate.setDate(futureDate.getDate() + daysAhead);\n\n const secrets = await this.list({\n where: {\n tenantId,\n status: 'active',\n 'expiresAt !=': null,\n 'expiresAt <': futureDate.toISOString(),\n },\n orderBy: 'expiresAt ASC',\n });\n\n return secrets;\n }\n\n /**\n * Get categories used in a tenant's secrets\n */\n async getCategories(tenantId: string): Promise<string[]> {\n const secrets = await this.list({ where: { tenantId } });\n const categories = new Set(secrets.map((s) => s.category).filter(Boolean));\n return Array.from(categories).sort();\n }\n\n /**\n * Count a tenant's secrets by status\n */\n async countByStatus(tenantId: string): Promise<Record<SecretStatus, number>> {\n const secrets = await this.list({ where: { tenantId } });\n\n const counts: Record<SecretStatus, number> = {\n active: 0,\n disabled: 0,\n expired: 0,\n };\n\n for (const secret of secrets) {\n if (secret.isExpired()) {\n counts.expired++;\n } else {\n counts[secret.status]++;\n }\n }\n\n return counts;\n }\n\n /**\n * Delete a tenant's secret by name\n */\n async deleteByName(tenantId: string, name: string): Promise<boolean> {\n const secret = await this.findByName(tenantId, name);\n if (!secret) return false;\n\n await secret.delete();\n return true;\n }\n}\n","/**\n * TenantKeyCollection - Collection manager for TenantKey objects\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { TenantKey, type TenantKeyStatus } from '../models/TenantKey.js';\n\n/**\n * Collection for managing TenantKey objects\n */\nexport class TenantKeyCollection extends SmrtCollection<TenantKey> {\n static readonly _itemClass = TenantKey;\n\n /**\n * Get the active key for a tenant\n */\n async getActiveKey(tenantId: string): Promise<TenantKey | null> {\n return this.get({\n tenantId,\n status: 'active',\n });\n }\n\n /**\n * List all key versions for a tenant\n */\n async listKeyVersions(tenantId: string): Promise<TenantKey[]> {\n return this.list({\n where: { tenantId },\n orderBy: 'version DESC',\n });\n }\n\n /**\n * Get a specific key version for a tenant\n */\n async getKeyVersion(\n tenantId: string,\n version: number,\n ): Promise<TenantKey | null> {\n return this.get({\n tenantId,\n version,\n });\n }\n\n /**\n * Find keys that need rotation\n */\n async findKeysNeedingRotation(): Promise<TenantKey[]> {\n const now = new Date();\n\n return this.list({\n where: {\n status: 'active',\n 'rotateAfter !=': null,\n 'rotateAfter <': now.toISOString(),\n },\n orderBy: 'rotateAfter ASC',\n });\n }\n\n /**\n * List all active keys across all tenants\n */\n async listAllActiveKeys(): Promise<TenantKey[]> {\n return this.list({\n where: { status: 'active' },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Count keys by status\n */\n async countByStatus(): Promise<Record<TenantKeyStatus, number>> {\n const keys = await this.list({});\n\n const counts: Record<TenantKeyStatus, number> = {\n active: 0,\n rotating: 0,\n retired: 0,\n compromised: 0,\n };\n\n for (const key of keys) {\n counts[key.status]++;\n }\n\n return counts;\n }\n\n /**\n * Mark a key as compromised (should trigger re-encryption)\n */\n async markCompromised(tenantId: string, keyId: string): Promise<boolean> {\n const key = await this.get({\n id: keyId,\n tenantId,\n });\n\n if (!key) return false;\n\n key.markCompromised();\n await key.save();\n return true;\n }\n\n /**\n * Delete old retired keys that are no longer needed\n * @param olderThanDays Delete keys retired more than this many days ago\n */\n async cleanupRetiredKeys(olderThanDays: number = 90): Promise<number> {\n const cutoffDate = new Date();\n cutoffDate.setDate(cutoffDate.getDate() - olderThanDays);\n\n const oldKeys = await this.list({\n where: {\n status: 'retired',\n 'retiredAt <': cutoffDate.toISOString(),\n },\n });\n\n let count = 0;\n for (const key of oldKeys) {\n await key.delete();\n count++;\n }\n\n return count;\n }\n}\n","import { loadEnvConfig } from \"@happyvertical/utils\";\n//#region src/adapter.ts\n/**\n* Logger Adapter - Converts signals to structured log messages\n*\n* Transforms signals from the SMRT framework into structured log entries.\n* Each signal type is mapped to an appropriate log level:\n* - start → debug\n* - step → debug\n* - end → info\n* - error → error\n*\n* @example\n* ```typescript\n* const logger = new ConsoleLogger('info');\n* const adapter = new LoggerAdapter(logger);\n* signalBus.register(adapter);\n* ```\n*/\nvar LoggerAdapter = class {\n\tlogger;\n\tconstructor(logger) {\n\t\tthis.logger = logger;\n\t}\n\t/**\n\t* Handle a signal and log appropriately\n\t*\n\t* @param signal - Signal to log\n\t*/\n\tasync handle(signal) {\n\t\tconst context = {\n\t\t\tid: signal.id,\n\t\t\tobjectId: signal.objectId,\n\t\t\tclassName: signal.className,\n\t\t\tmethod: signal.method,\n\t\t\ttimestamp: signal.timestamp\n\t\t};\n\t\tif (signal.duration !== void 0) context.duration = signal.duration;\n\t\tif (signal.metadata) context.metadata = signal.metadata;\n\t\tswitch (signal.type) {\n\t\t\tcase \"start\":\n\t\t\t\tthis.logger.debug(`${signal.className}.${signal.method}() started`, context);\n\t\t\t\tbreak;\n\t\t\tcase \"step\":\n\t\t\t\tthis.logger.debug(`${signal.className}.${signal.method}() step: ${signal.step || \"unknown\"}`, context);\n\t\t\t\tbreak;\n\t\t\tcase \"end\":\n\t\t\t\tthis.logger.info(`${signal.className}.${signal.method}() completed in ${signal.duration}ms`, {\n\t\t\t\t\t...context,\n\t\t\t\t\tresult: signal.result !== void 0 ? \"present\" : \"none\"\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t\tcase \"error\":\n\t\t\t\tthis.logger.error(`${signal.className}.${signal.method}() failed: ${signal.error?.message || \"Unknown error\"}`, {\n\t\t\t\t\t...context,\n\t\t\t\t\terror: signal.error ? {\n\t\t\t\t\t\tmessage: signal.error.message,\n\t\t\t\t\t\tname: signal.error.name,\n\t\t\t\t\t\tstack: signal.error.stack\n\t\t\t\t\t} : void 0\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t}\n\t}\n};\n//#endregion\n//#region src/console.ts\n/**\n* Console-based logger with level filtering\n*\n* Logs are written to console with appropriate severity levels.\n* Messages are only output if they meet the configured log level threshold.\n*\n* @example\n* ```typescript\n* const logger = new ConsoleLogger('info');\n* logger.debug('Debug message'); // Not output (below 'info')\n* logger.info('Info message'); // Output\n* logger.error('Error message'); // Output\n* ```\n*/\nvar ConsoleLogger = class ConsoleLogger {\n\tlevel;\n\tstatic LEVELS = [\n\t\t\"debug\",\n\t\t\"info\",\n\t\t\"warn\",\n\t\t\"error\"\n\t];\n\tconstructor(level = \"info\") {\n\t\tthis.level = level;\n\t}\n\t/**\n\t* Check if a log level should be output\n\t*\n\t* @param level - Log level to check\n\t* @returns True if level meets threshold\n\t*/\n\tshouldLog(level) {\n\t\tconst currentIndex = ConsoleLogger.LEVELS.indexOf(this.level);\n\t\treturn ConsoleLogger.LEVELS.indexOf(level) >= currentIndex;\n\t}\n\t/**\n\t* Format context for console output\n\t*\n\t* @param context - Structured metadata\n\t* @returns Formatted context string\n\t*/\n\tformatContext(context) {\n\t\tif (!context || Object.keys(context).length === 0) return \"\";\n\t\treturn ` ${JSON.stringify(context)}`;\n\t}\n\tdebug(message, context) {\n\t\tif (this.shouldLog(\"debug\")) console.debug(`[DEBUG] ${message}${this.formatContext(context)}`);\n\t}\n\tinfo(message, context) {\n\t\tif (this.shouldLog(\"info\")) console.info(`[INFO] ${message}${this.formatContext(context)}`);\n\t}\n\twarn(message, context) {\n\t\tif (this.shouldLog(\"warn\")) console.warn(`[WARN] ${message}${this.formatContext(context)}`);\n\t}\n\terror(message, context) {\n\t\tif (this.shouldLog(\"error\")) console.error(`[ERROR] ${message}${this.formatContext(context)}`);\n\t}\n};\n//#endregion\n//#region src/index.ts\n/**\n* No-op logger that discards all log messages\n*\n* Used when logging is disabled (config: false)\n*/\nvar NoopLogger = class {\n\tdebug(_message, _context) {}\n\tinfo(_message, _context) {}\n\twarn(_message, _context) {}\n\terror(_message, _context) {}\n};\n/**\n* Create a logger from configuration\n*\n* Supports environment variable configuration via HAVE_LOGGER_LEVEL.\n* User-provided options take precedence over environment variables.\n*\n* @param config - Logger configuration (boolean or object)\n* @returns Configured logger instance\n*\n* @example\n* ```typescript\n* // Console logger with 'info' level (default)\n* const logger1 = createLogger(true);\n*\n* // Console logger with level from HAVE_LOGGER_LEVEL env var\n* process.env.HAVE_LOGGER_LEVEL = 'debug';\n* const logger2 = createLogger(true); // Uses 'debug' from env\n*\n* // No-op logger (all log calls are discarded)\n* const logger3 = createLogger(false);\n*\n* // Console logger with 'debug' level (overrides env)\n* const logger4 = createLogger({ level: 'debug' });\n*\n* // Custom log level (user options take precedence)\n* process.env.HAVE_LOGGER_LEVEL = 'info';\n* const logger5 = createLogger({ level: 'warn' }); // Uses 'warn' not 'info'\n* ```\n*/\nfunction createLogger(config) {\n\tif (typeof config === \"boolean\") {\n\t\tif (!config) return new NoopLogger();\n\t\treturn new ConsoleLogger(loadEnvConfig({}, {\n\t\t\tpackageName: \"logger\",\n\t\t\tschema: { level: \"string\" }\n\t\t}).level || \"info\");\n\t}\n\treturn new ConsoleLogger(loadEnvConfig(config, {\n\t\tpackageName: \"logger\",\n\t\tschema: { level: \"string\" }\n\t}).level || \"info\");\n}\n/** @internal */\nvar PACKAGE_VERSION_INITIALIZED = true;\n//#endregion\nexport { ConsoleLogger, LoggerAdapter, PACKAGE_VERSION_INITIALIZED, createLogger };\n\n//# sourceMappingURL=index.js.map","/**\n * SecretService - High-level API for per-tenant secret management\n * @packageDocumentation\n */\n\n// Self-register this package's manifest for consumers that import via this\n// subpath without the main entry. See src/__smrt-register__.ts (issue #1132).\nimport '../__smrt-register__.js';\n\nimport { createLogger } from '@happyvertical/logger';\nimport {\n AMKUnavailableError,\n DecryptionError,\n type EncryptedEnvelope,\n EncryptionError,\n EnvelopeEncryption,\n getSecretStore,\n type SecretStore,\n TenantKeyMissingError,\n} from '@happyvertical/secrets';\nimport {\n getCurrentTenant,\n requireTenantId,\n withTenant,\n} from '@happyvertical/smrt-tenancy';\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport { SecretAuditLogCollection } from '../collections/SecretAuditLogCollection.js';\nimport { SecretCollection } from '../collections/SecretCollection.js';\nimport { TenantKeyCollection } from '../collections/TenantKeyCollection.js';\nimport type { Secret } from '../models/Secret.js';\nimport {\n createAuditEntry,\n type SecretAuditAction,\n type SecretAuditLog,\n} from '../models/SecretAuditLog.js';\nimport type { TenantKey } from '../models/TenantKey.js';\n\nconst logger = createLogger({ level: 'info' });\n\n/**\n * Options for creating a SecretService\n */\nexport interface SecretServiceOptions {\n /** Database connection */\n db: DatabaseInterface;\n /** Environment variable containing the AMK (64 hex chars) */\n amkEnvVar?: string;\n /** AMK key identifier */\n amkKeyId?: string;\n /** Enable audit logging (default: true) */\n auditEnabled?: boolean;\n}\n\n/**\n * Options for storing a secret\n */\nexport interface StoreSecretOptions {\n /** Human-readable description */\n description?: string;\n /** Category for organization */\n category?: string;\n /** Optional expiration date */\n expiresAt?: Date;\n /** Additional metadata */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Result of retrieving a secret\n */\nexport interface RetrievedSecret {\n /** The decrypted secret value */\n value: string;\n /** Secret metadata */\n name: string;\n description: string;\n category: string;\n expiresAt: Date | null;\n createdAt: Date;\n lastAccessedAt: Date | null;\n accessCount: number;\n metadata: Record<string, unknown>;\n}\n\nexport type SecretKeyDriftIssueSeverity = 'info' | 'warning' | 'error';\n\nexport type SecretKeyDriftIssueCode =\n | 'amk_unavailable'\n | 'active_secrets_without_usable_active_key'\n | 'missing_active_tenant_encryption_key'\n | 'multiple_active_tenant_encryption_keys'\n | 'active_tenant_encryption_key_amk_mismatch'\n | 'active_tenant_encryption_key_unwrap_failed'\n | 'secret_envelope_invalid_json'\n | 'secret_envelope_invalid_wrapped_key'\n | 'secret_envelope_missing_tenant_encryption_key'\n | 'secret_envelope_unwrap_failed'\n | 'smrt_tenant_keys_query_failed'\n | 'smrt_tenant_keys_not_mirrored';\n\nexport type SecretKeyDriftRepairAction =\n | 'delete-unrecoverable-secret'\n | 'delete-unusable-tenant-encryption-key'\n | 'store-fresh-secret-value'\n | 'none';\n\nexport interface SecretKeyDriftIssue {\n code: SecretKeyDriftIssueCode;\n severity: SecretKeyDriftIssueSeverity;\n message: string;\n repairAction: SecretKeyDriftRepairAction;\n secretId?: string;\n secretName?: string;\n keyId?: string;\n sourceTable?: 'secrets' | 'tenant_encryption_keys' | 'tenant_keys';\n details?: Record<string, string | number | boolean | null>;\n}\n\nexport interface DiagnoseTenantSecretKeyDriftOptions {\n /**\n * Limit secret-envelope checks to these names. Tenant key checks still run.\n */\n secretNames?: string[];\n}\n\nexport interface SecretKeyDriftReport {\n tenantId: string;\n checkedAt: Date;\n ok: boolean;\n summary: {\n activeSecretCount: number;\n tenantEncryptionKeyCount: number;\n activeTenantEncryptionKeyCount: number;\n usableActiveTenantEncryptionKeyCount: number;\n smrtTenantKeyCount: number;\n activeSmrtTenantKeyCount: number;\n };\n issues: SecretKeyDriftIssue[];\n}\n\nexport interface RepairTenantSecretKeyDriftOptions\n extends DiagnoseTenantSecretKeyDriftOptions {\n /**\n * Preview affected rows without deleting anything.\n */\n dryRun?: boolean;\n /**\n * Required for destructive repair. This deletes encrypted values/key rows\n * that cannot be used with the currently configured AMK.\n */\n confirmDeleteUnrecoverableData?: boolean;\n}\n\nexport interface SecretKeyDriftRepairResult {\n tenantId: string;\n dryRun: boolean;\n issuesBefore: SecretKeyDriftIssue[];\n remainingIssues: SecretKeyDriftIssue[];\n wouldDeleteSecrets: number;\n wouldDeleteTenantEncryptionKeys: number;\n deletedSecrets: number;\n deletedTenantEncryptionKeys: number;\n secretNames: string[];\n tenantEncryptionKeyIds: string[];\n}\n\nexport class SecretKeyDriftError extends Error {\n readonly code = 'SECRET_KEY_DRIFT';\n readonly tenantId: string;\n readonly report: SecretKeyDriftReport;\n readonly cause?: Error;\n\n constructor(\n message: string,\n tenantId: string,\n report: SecretKeyDriftReport,\n cause?: Error,\n ) {\n super(message);\n this.name = 'SecretKeyDriftError';\n this.tenantId = tenantId;\n this.report = report;\n this.cause = cause;\n }\n}\n\ninterface TenantEncryptionKeyRow {\n id: string;\n tenant_id: string;\n wrapped_key: string;\n amk_key_id: string;\n status: string;\n version: number;\n rotate_after: string | null;\n retired_at: string | null;\n created_at: string;\n updated_at: string;\n}\n\ninterface SecretDiagnosisRow {\n id: string;\n name: string;\n encrypted_value: string;\n status: string;\n tenant_id: string;\n}\n\ninterface WrappedKeyCheck {\n usable: boolean;\n error?: string;\n fingerprint?: string;\n}\n\ninterface SmrtTenantKeyDiagnosisRows {\n keys: TenantKey[];\n error?: Error;\n}\n\ntype TransactionCapableDatabase = DatabaseInterface & {\n transaction?: <T>(\n callback: (tx: DatabaseInterface) => Promise<T>,\n ) => Promise<T>;\n};\n\n/**\n * SecretService provides high-level operations for managing per-tenant secrets.\n *\n * It integrates with:\n * - `@happyvertical/secrets` for envelope encryption\n * - `@happyvertical/smrt-tenancy` for tenant context\n * - Audit logging for compliance\n *\n * @example\n * ```typescript\n * import { SecretService } from '@happyvertical/smrt-secrets';\n * import { withTenant } from '@happyvertical/smrt-tenancy';\n *\n * const service = await SecretService.create({ db });\n *\n * await withTenant({ tenantId: 'tenant-123' }, async () => {\n * // Store a secret\n * await service.store('stripe-api-key', 'sk_live_xxx', {\n * category: 'api-keys',\n * description: 'Stripe production API key'\n * });\n *\n * // Retrieve the secret\n * const secret = await service.retrieve('stripe-api-key');\n * console.log(secret.value); // 'sk_live_xxx'\n *\n * // List secret names (without values)\n * const secrets = await service.list();\n *\n * // Rotate tenant's encryption key\n * await service.rotateKey();\n *\n * // Delete a secret\n * await service.delete('stripe-api-key');\n * });\n * ```\n */\nexport class SecretService {\n private db: DatabaseInterface;\n private secretStore: SecretStore;\n private secrets: SecretCollection;\n private tenantKeys: TenantKeyCollection;\n private auditLogs: SecretAuditLogCollection;\n private auditEnabled: boolean;\n private amkEnvVar: string;\n private amkKeyId: string;\n\n private constructor(\n db: DatabaseInterface,\n secretStore: SecretStore,\n secrets: SecretCollection,\n tenantKeys: TenantKeyCollection,\n auditLogs: SecretAuditLogCollection,\n auditEnabled: boolean,\n amkEnvVar: string,\n amkKeyId: string,\n ) {\n this.db = db;\n this.secretStore = secretStore;\n this.secrets = secrets;\n this.tenantKeys = tenantKeys;\n this.auditLogs = auditLogs;\n this.auditEnabled = auditEnabled;\n this.amkEnvVar = amkEnvVar;\n this.amkKeyId = amkKeyId;\n }\n\n /**\n * Create a new SecretService instance\n */\n static async create(options: SecretServiceOptions): Promise<SecretService> {\n const {\n db,\n amkEnvVar = 'SMRT_SECRET_MASTER_KEY',\n amkKeyId = 'smrt-amk-v1',\n auditEnabled = true,\n } = options;\n\n // Create the underlying secret store\n const secretStore = await getSecretStore({\n type: 'database',\n db,\n amk: {\n provider: 'env',\n keyEnvVar: amkEnvVar,\n keyId: amkKeyId,\n },\n });\n\n // Create collections - pass db directly (DatabaseConfig accepts DatabaseInterface)\n const baseOptions = { db };\n const secrets = await SecretCollection.create(baseOptions);\n const tenantKeys = await TenantKeyCollection.create(baseOptions);\n const auditLogs = await SecretAuditLogCollection.create(baseOptions);\n\n return new SecretService(\n db,\n secretStore,\n secrets,\n tenantKeys,\n auditLogs,\n auditEnabled,\n amkEnvVar,\n amkKeyId,\n );\n }\n\n /**\n * Store a secret for the current tenant\n */\n async store(\n name: string,\n value: string,\n options: StoreSecretOptions = {},\n ): Promise<Secret> {\n const tenantId = requireTenantId();\n const userId = this.getCurrentUserId();\n\n // Track whether this is an update to use correct audit action on error\n let isUpdate = false;\n\n try {\n // Check if secret already exists for THIS tenant (issue #1501: an\n // unscoped lookup here used to find another tenant's same-named row and\n // clobber it with an envelope encrypted under the caller's TDEK).\n let existing = await this.secrets.findByName(tenantId, name);\n // Defense-in-depth: even if a lookup regression ever returns a foreign\n // row again, never save over it — fall through to the create path,\n // which is tenant-scoped via the (slug, context=tenantId) upsert key.\n if (existing && existing.tenantId !== tenantId) {\n existing = null;\n }\n isUpdate = existing !== null;\n\n // Encrypt the value\n const envelope = await this.secretStore.encrypt(tenantId, name, value, {\n metadata: options.metadata\n ? this.serializeMetadata(options.metadata)\n : undefined,\n });\n\n if (existing) {\n // Update existing secret\n existing.encryptedValue = JSON.stringify(envelope);\n existing.description = options.description ?? existing.description;\n existing.category = options.category ?? existing.category;\n existing.expiresAt = options.expiresAt ?? existing.expiresAt;\n existing.metadata = options.metadata ?? existing.metadata;\n await existing.save();\n\n await this.audit(\n existing.id ?? null,\n name,\n userId,\n 'update',\n 'success',\n );\n return existing;\n }\n\n // Create new secret\n // Set context to tenantId for per-tenant uniqueness\n // The UPSERT uses (slug, context) as conflict columns, so different tenants\n // can have secrets with the same name\n const secret = await this.secrets.create({\n name,\n description: options.description ?? '',\n category: options.category ?? '',\n encryptedValue: JSON.stringify(envelope),\n keyVersion: 1,\n status: 'active',\n expiresAt: options.expiresAt ?? null,\n metadata: options.metadata ?? {},\n context: tenantId, // Per-tenant uniqueness\n tenantId,\n });\n\n await this.audit(secret.id ?? null, name, userId, 'create', 'success');\n return secret;\n } catch (error) {\n const classifiedError = await this.classifyTenantKeyFailure(\n tenantId,\n name,\n error,\n );\n await this.audit(\n null,\n name,\n userId,\n isUpdate ? 'update' : 'create',\n 'failure',\n {\n error: classifiedError.message,\n },\n );\n throw classifiedError;\n }\n }\n\n /**\n * Store a secret for a specific tenant.\n *\n * This is useful for integrations that already resolved tenant ownership but\n * may be running outside the application's ambient tenant context.\n */\n async storeForTenant(\n tenantId: string,\n name: string,\n value: string,\n options: StoreSecretOptions = {},\n ): Promise<Secret> {\n return withTenant({ tenantId }, () => this.store(name, value, options));\n }\n\n /**\n * Retrieve a secret for the current tenant\n */\n async retrieve(name: string): Promise<RetrievedSecret> {\n const tenantId = requireTenantId();\n const userId = this.getCurrentUserId();\n\n // Track whether we've already audited to avoid double-auditing\n let audited = false;\n\n try {\n const secret = await this.secrets.findByName(tenantId, name);\n // Ownership check before any decrypt attempt (issue #1501): a row owned\n // by another tenant must behave exactly like \"not found\", never surface\n // as a cross-tenant decrypt failure.\n if (!secret || secret.tenantId !== tenantId) {\n await this.audit(null, name, userId, 'read', 'failure', {\n error: 'Secret not found',\n });\n audited = true;\n throw new Error(`Secret '${name}' not found`);\n }\n\n if (!secret.isUsable()) {\n const reason = secret.isExpired()\n ? 'Secret expired'\n : 'Secret disabled';\n await this.audit(secret.id ?? null, name, userId, 'read', 'failure', {\n error: reason,\n });\n audited = true;\n throw new Error(reason);\n }\n\n // Decrypt the value\n const envelope: EncryptedEnvelope = JSON.parse(secret.encryptedValue);\n const decrypted = await this.secretStore.decrypt(tenantId, envelope);\n\n // Access tracking is operational telemetry. Retrieval should still\n // succeed if the decrypted value is available but this write fails.\n const previousLastAccessedAt = secret.lastAccessedAt;\n const previousAccessCount = secret.accessCount;\n try {\n secret.recordAccess();\n await secret.save();\n } catch (trackingError) {\n secret.lastAccessedAt = previousLastAccessedAt;\n secret.accessCount = previousAccessCount;\n logger.error('Failed to update secret access tracking', {\n error: trackingError,\n });\n }\n\n await this.audit(secret.id ?? null, name, userId, 'read', 'success');\n\n return {\n value: decrypted.value,\n name: secret.name,\n description: secret.description,\n category: secret.category,\n expiresAt: secret.expiresAt,\n createdAt: secret.created_at ?? new Date(),\n lastAccessedAt: secret.lastAccessedAt,\n accessCount: secret.accessCount,\n metadata: secret.metadata,\n };\n } catch (error) {\n const classifiedError = await this.classifyTenantKeyFailure(\n tenantId,\n name,\n error,\n );\n // Only audit if we haven't already audited this error\n if (!audited) {\n await this.audit(null, name, userId, 'read', 'failure', {\n error: classifiedError.message,\n });\n }\n throw classifiedError;\n }\n }\n\n /**\n * Retrieve a secret for a specific tenant.\n */\n async retrieveForTenant(\n tenantId: string,\n name: string,\n ): Promise<RetrievedSecret> {\n return withTenant({ tenantId }, () => this.retrieve(name));\n }\n\n /**\n * Diagnose tenant secret/key drift without exposing decrypted values.\n */\n async diagnoseTenantSecretKeyDrift(\n tenantId: string,\n options: DiagnoseTenantSecretKeyDriftOptions = {},\n ): Promise<SecretKeyDriftReport> {\n const activeSecrets = await this.listActiveSecretRowsForDiagnosis(\n tenantId,\n options.secretNames,\n );\n const tenantEncryptionKeys =\n await this.listTenantEncryptionKeyRows(tenantId);\n const smrtTenantKeyRows =\n await this.listSmrtTenantKeysForDiagnosis(tenantId);\n const smrtTenantKeys = smrtTenantKeyRows.keys;\n const issues: SecretKeyDriftIssue[] = [];\n\n const activeTenantEncryptionKeys = tenantEncryptionKeys.filter(\n (key) => key.status === 'active',\n );\n const activeSmrtTenantKeys = smrtTenantKeys.filter(\n (key) => key.status === 'active',\n );\n const amk = this.getConfiguredAmkForDiagnosis();\n\n if (!amk.usable) {\n issues.push({\n code: 'amk_unavailable',\n severity: 'error',\n message: amk.error ?? `AMK ${this.amkEnvVar} is unavailable`,\n repairAction: 'none',\n details: {\n amkEnvVar: this.amkEnvVar,\n amkKeyId: this.amkKeyId,\n },\n });\n }\n\n if (activeSecrets.length > 0 && activeTenantEncryptionKeys.length === 0) {\n issues.push({\n code: 'missing_active_tenant_encryption_key',\n severity: 'error',\n message:\n 'Active tenant secrets exist, but tenant_encryption_keys has no active key for encryption.',\n repairAction: 'store-fresh-secret-value',\n sourceTable: 'tenant_encryption_keys',\n details: {\n activeSecretCount: activeSecrets.length,\n },\n });\n }\n\n if (smrtTenantKeyRows.error) {\n issues.push({\n code: 'smrt_tenant_keys_query_failed',\n severity: 'error',\n message:\n 'Unable to query SMRT tenant_keys while diagnosing tenant secret key drift.',\n repairAction: 'none',\n sourceTable: 'tenant_keys',\n details: {\n error: smrtTenantKeyRows.error.message,\n },\n });\n }\n\n if (activeTenantEncryptionKeys.length > 1) {\n issues.push({\n code: 'multiple_active_tenant_encryption_keys',\n severity: 'error',\n message:\n 'tenant_encryption_keys has multiple active keys for this tenant; encryption may use an arbitrary active key.',\n repairAction: 'none',\n sourceTable: 'tenant_encryption_keys',\n details: {\n activeTenantEncryptionKeyCount: activeTenantEncryptionKeys.length,\n },\n });\n }\n\n const activeKeyChecks: WrappedKeyCheck[] = [];\n\n for (const key of tenantEncryptionKeys) {\n const check = amk.value\n ? this.checkWrappedKey(key.wrapped_key, amk.value)\n : { usable: false, error: amk.error };\n\n if (key.status === 'active') {\n activeKeyChecks.push(check);\n }\n\n if (key.status === 'active' && key.amk_key_id !== this.amkKeyId) {\n issues.push({\n code: 'active_tenant_encryption_key_amk_mismatch',\n severity: 'warning',\n message:\n 'Active tenant_encryption_keys row was wrapped by a different AMK key id than this SecretService is configured to use.',\n repairAction: 'none',\n keyId: key.id,\n sourceTable: 'tenant_encryption_keys',\n details: {\n rowAmkKeyId: key.amk_key_id,\n configuredAmkKeyId: this.amkKeyId,\n },\n });\n }\n\n if (key.status === 'active' && !check.usable && amk.value) {\n issues.push({\n code: 'active_tenant_encryption_key_unwrap_failed',\n severity: 'error',\n message:\n 'Active tenant_encryption_keys row cannot be unwrapped by the currently configured AMK.',\n repairAction: 'delete-unusable-tenant-encryption-key',\n keyId: key.id,\n sourceTable: 'tenant_encryption_keys',\n details: {\n version: key.version,\n error: check.error ?? null,\n },\n });\n }\n }\n\n const usableActiveTenantEncryptionKeyCount = activeKeyChecks.filter(\n (check) => check.usable,\n ).length;\n\n if (\n activeSecrets.length > 0 &&\n usableActiveTenantEncryptionKeyCount === 0\n ) {\n issues.push({\n code: 'active_secrets_without_usable_active_key',\n severity: 'error',\n message:\n 'Active secrets exist, but no active tenant_encryption_keys row can be used with the current AMK.',\n repairAction: 'store-fresh-secret-value',\n sourceTable: 'tenant_encryption_keys',\n details: {\n activeSecretCount: activeSecrets.length,\n activeTenantEncryptionKeyCount: activeTenantEncryptionKeys.length,\n },\n });\n }\n\n const keyFingerprintToRow = new Map<string, TenantEncryptionKeyRow>();\n for (const key of tenantEncryptionKeys) {\n const fingerprint = this.getWrappedKeyFingerprint(key.wrapped_key);\n if (fingerprint) {\n keyFingerprintToRow.set(fingerprint, key);\n }\n }\n\n for (const secret of activeSecrets) {\n const envelope = this.parseSecretEnvelopeForDiagnosis(secret, issues);\n if (!envelope) continue;\n\n const envelopeFingerprint = this.getWrappedKeyFingerprint(\n envelope.wrappedKey,\n );\n const envelopeCheck = amk.value\n ? this.checkWrappedKey(envelope.wrappedKey, amk.value)\n : {\n usable: false,\n error: amk.error,\n fingerprint: envelopeFingerprint,\n };\n\n if (!envelopeCheck.fingerprint) {\n issues.push({\n code: 'secret_envelope_invalid_wrapped_key',\n severity: 'error',\n message:\n 'Secret encryptedValue contains an invalid wrapped key format.',\n repairAction: 'delete-unrecoverable-secret',\n secretId: secret.id,\n secretName: secret.name,\n sourceTable: 'secrets',\n details: {\n error: envelopeCheck.error ?? null,\n },\n });\n continue;\n }\n\n const matchingKey = keyFingerprintToRow.get(envelopeCheck.fingerprint);\n if (!matchingKey) {\n issues.push({\n code: 'secret_envelope_missing_tenant_encryption_key',\n severity: 'error',\n message:\n 'Secret envelope does not match any tenant_encryption_keys row for this tenant.',\n repairAction: !amk.value\n ? 'none'\n : envelopeCheck.usable\n ? 'none'\n : 'delete-unrecoverable-secret',\n secretId: secret.id,\n secretName: secret.name,\n sourceTable: 'secrets',\n });\n }\n\n if (!envelopeCheck.usable && amk.value) {\n issues.push({\n code: 'secret_envelope_unwrap_failed',\n severity: 'error',\n message:\n 'Secret envelope cannot be unwrapped by the currently configured AMK.',\n repairAction: 'delete-unrecoverable-secret',\n secretId: secret.id,\n secretName: secret.name,\n keyId: matchingKey?.id,\n sourceTable: 'secrets',\n details: {\n error: envelopeCheck.error ?? null,\n },\n });\n }\n }\n\n if (\n activeSecrets.length > 0 &&\n tenantEncryptionKeys.length > 0 &&\n smrtTenantKeys.length === 0 &&\n !smrtTenantKeyRows.error\n ) {\n issues.push({\n code: 'smrt_tenant_keys_not_mirrored',\n severity: 'info',\n message:\n 'SMRT tenant_keys has no rows for this tenant, while the lower-level tenant_encryption_keys table does. SecretService uses tenant_encryption_keys for encryption.',\n repairAction: 'none',\n sourceTable: 'tenant_keys',\n });\n }\n\n return {\n tenantId,\n checkedAt: new Date(),\n ok: !issues.some((issue) => issue.severity === 'error'),\n summary: {\n activeSecretCount: activeSecrets.length,\n tenantEncryptionKeyCount: tenantEncryptionKeys.length,\n activeTenantEncryptionKeyCount: activeTenantEncryptionKeys.length,\n usableActiveTenantEncryptionKeyCount,\n smrtTenantKeyCount: smrtTenantKeys.length,\n activeSmrtTenantKeyCount: activeSmrtTenantKeys.length,\n },\n issues,\n };\n }\n\n /**\n * Diagnose drift for the current tenant context.\n */\n async diagnoseCurrentTenantSecretKeyDrift(\n options: DiagnoseTenantSecretKeyDriftOptions = {},\n ): Promise<SecretKeyDriftReport> {\n return this.diagnoseTenantSecretKeyDrift(requireTenantId(), options);\n }\n\n /**\n * Delete unrecoverable secret/key rows identified by diagnosis.\n *\n * This never attempts to recover or expose secret values. Use dryRun first\n * to preview destructive changes.\n */\n async repairTenantSecretKeyDrift(\n tenantId: string,\n options: RepairTenantSecretKeyDriftOptions = {},\n ): Promise<SecretKeyDriftRepairResult> {\n const dryRun = options.dryRun ?? false;\n const before = await this.diagnoseTenantSecretKeyDrift(tenantId, options);\n const secretIds = new Set<string>();\n const secretNames = new Map<string, string>();\n const tenantEncryptionKeyIds = new Set<string>();\n\n for (const issue of before.issues) {\n if (\n issue.repairAction === 'delete-unrecoverable-secret' &&\n issue.secretId\n ) {\n secretIds.add(issue.secretId);\n if (issue.secretName) {\n secretNames.set(issue.secretId, issue.secretName);\n }\n }\n\n if (\n issue.repairAction === 'delete-unusable-tenant-encryption-key' &&\n issue.keyId\n ) {\n tenantEncryptionKeyIds.add(issue.keyId);\n }\n }\n\n const wouldDeleteSecrets = secretIds.size;\n const wouldDeleteTenantEncryptionKeys = tenantEncryptionKeyIds.size;\n const wouldDeleteUnrecoverableData =\n wouldDeleteSecrets + wouldDeleteTenantEncryptionKeys > 0;\n\n if (\n !dryRun &&\n wouldDeleteUnrecoverableData &&\n !options.confirmDeleteUnrecoverableData\n ) {\n throw new Error(\n 'repairTenantSecretKeyDrift requires confirmDeleteUnrecoverableData: true before deleting encrypted secrets or tenant key rows.',\n );\n }\n\n let deletedSecrets = 0;\n let deletedTenantEncryptionKeys = 0;\n\n if (!dryRun) {\n const runDeletes = async (db: DatabaseInterface) => {\n const deletedSecretRows = await this.deleteRowsByIds(\n 'secrets',\n tenantId,\n secretIds,\n db,\n );\n const deletedTenantEncryptionKeyRows = await this.deleteRowsByIds(\n 'tenant_encryption_keys',\n tenantId,\n tenantEncryptionKeyIds,\n db,\n );\n return {\n deletedSecrets: deletedSecretRows,\n deletedTenantEncryptionKeys: deletedTenantEncryptionKeyRows,\n };\n };\n const txDb = this.db as TransactionCapableDatabase;\n const deleteResult =\n typeof txDb.transaction === 'function'\n ? await txDb.transaction((db) => runDeletes(db))\n : await runDeletes(this.db);\n\n deletedSecrets = deleteResult.deletedSecrets;\n deletedTenantEncryptionKeys = deleteResult.deletedTenantEncryptionKeys;\n await this.auditSecretDriftRepairDeletes(\n tenantId,\n secretIds,\n secretNames,\n );\n }\n\n const after = dryRun\n ? before\n : await this.diagnoseTenantSecretKeyDrift(tenantId, options);\n\n return {\n tenantId,\n dryRun,\n issuesBefore: before.issues,\n remainingIssues: after.issues,\n wouldDeleteSecrets,\n wouldDeleteTenantEncryptionKeys,\n deletedSecrets,\n deletedTenantEncryptionKeys,\n secretNames: Array.from(secretNames.values()).sort(),\n tenantEncryptionKeyIds: Array.from(tenantEncryptionKeyIds).sort(),\n };\n }\n\n /**\n * List secrets for the current tenant (names only, not values)\n */\n async list(options: { category?: string } = {}): Promise<Secret[]> {\n return this.secrets.listSecrets(requireTenantId(), {\n category: options.category,\n status: 'active',\n });\n }\n\n /**\n * Delete a secret\n */\n async delete(name: string): Promise<boolean> {\n const tenantId = requireTenantId();\n const userId = this.getCurrentUserId();\n\n const secret = await this.secrets.findByName(tenantId, name);\n // Ownership check (issue #1501): never delete another tenant's row\n if (!secret || secret.tenantId !== tenantId) {\n return false;\n }\n\n try {\n await secret.delete();\n await this.audit(secret.id ?? null, name, userId, 'delete', 'success');\n return true;\n } catch (error) {\n await this.audit(secret.id ?? null, name, userId, 'delete', 'failure', {\n error: (error as Error).message,\n });\n throw error;\n }\n }\n\n /**\n * Disable a secret (soft delete)\n */\n async disable(name: string): Promise<boolean> {\n const tenantId = requireTenantId();\n const userId = this.getCurrentUserId();\n\n const secret = await this.secrets.findByName(tenantId, name);\n // Ownership check (issue #1501): never mutate another tenant's row\n if (!secret || secret.tenantId !== tenantId) {\n return false;\n }\n\n secret.disable();\n await secret.save();\n await this.audit(secret.id ?? null, name, userId, 'disable', 'success');\n return true;\n }\n\n /**\n * Enable a disabled secret\n */\n async enable(name: string): Promise<boolean> {\n const tenantId = requireTenantId();\n const userId = this.getCurrentUserId();\n\n const secret = await this.secrets.findByName(tenantId, name);\n // Ownership check (issue #1501): never mutate another tenant's row\n if (!secret || secret.tenantId !== tenantId) {\n return false;\n }\n\n secret.enable();\n await secret.save();\n await this.audit(secret.id ?? null, name, userId, 'enable', 'success');\n return true;\n }\n\n /**\n * Rotate the tenant's encryption key\n *\n * This creates a new TDEK and marks the old one as retired.\n * Existing secrets remain encrypted with the old key and can still\n * be decrypted (the old key is kept in retired state).\n *\n * For full re-encryption, call reencryptAll() after rotation.\n */\n async rotateKey(): Promise<void> {\n const tenantId = requireTenantId();\n const userId = this.getCurrentUserId();\n\n try {\n await this.secretStore.rotateTenantKey(tenantId);\n await this.audit(null, '', userId, 'rotate_key', 'success', {\n tenantId,\n });\n } catch (error) {\n await this.audit(null, '', userId, 'rotate_key', 'failure', {\n tenantId,\n error: (error as Error).message,\n });\n throw error;\n }\n }\n\n /**\n * Re-encrypt all secrets with the current active key\n *\n * Call this after key rotation to ensure all secrets use the new key.\n * This is optional but recommended for security.\n */\n async reencryptAll(): Promise<{ success: number; failed: number }> {\n const tenantId = requireTenantId();\n const userId = this.getCurrentUserId();\n\n // Scope to the current tenant (issue #1501): re-encryption must never\n // touch (or attempt to decrypt) other tenants' rows.\n const secrets = await this.secrets.list({ where: { tenantId } });\n let success = 0;\n let failed = 0;\n\n for (const secret of secrets) {\n try {\n // Decrypt with old key\n const envelope: EncryptedEnvelope = JSON.parse(secret.encryptedValue);\n const decrypted = await this.secretStore.decrypt(tenantId, envelope);\n\n // Re-encrypt with new key\n const newEnvelope = await this.secretStore.encrypt(\n tenantId,\n secret.name,\n decrypted.value,\n );\n\n secret.encryptedValue = JSON.stringify(newEnvelope);\n await secret.save();\n\n success++;\n } catch (error) {\n failed++;\n await this.audit(\n secret.id ?? null,\n secret.name,\n userId,\n 'update',\n 'failure',\n {\n action: 'reencrypt',\n error: (error as Error).message,\n },\n );\n }\n }\n\n return { success, failed };\n }\n\n /**\n * Get audit logs for the current tenant\n */\n async getAuditLogs(\n options: { secretName?: string; limit?: number } = {},\n ): Promise<SecretAuditLog[]> {\n return this.auditLogs.listLogs({\n // Scope to the current tenant (issue #1501): audit logs reference\n // secret names and must not leak across tenants.\n tenantId: requireTenantId(),\n secretName: options.secretName,\n limit: options.limit ?? 100,\n });\n }\n\n /**\n * Get secret categories for the current tenant\n */\n async getCategories(): Promise<string[]> {\n return this.secrets.getCategories(requireTenantId());\n }\n\n /**\n * Check if a secret exists for the current tenant\n */\n async exists(name: string): Promise<boolean> {\n const tenantId = requireTenantId();\n const secret = await this.secrets.findByName(tenantId, name);\n return secret !== null && secret.tenantId === tenantId;\n }\n\n // Private methods\n\n private async listActiveSecretRowsForDiagnosis(\n tenantId: string,\n secretNames?: string[],\n ): Promise<SecretDiagnosisRow[]> {\n const params: unknown[] = [tenantId];\n let nameFilter = '';\n\n if (secretNames && secretNames.length > 0) {\n const placeholders = secretNames.map(() => '?').join(', ');\n nameFilter = ` AND name IN (${placeholders})`;\n params.push(...secretNames);\n }\n\n const result = await this.db.query(\n `\n SELECT id, name, encrypted_value, status, tenant_id\n FROM \"secrets\"\n WHERE tenant_id = ? AND status = 'active'${nameFilter}\n ORDER BY name ASC\n `,\n ...params,\n );\n\n return this.rowsFromResult<SecretDiagnosisRow>(result);\n }\n\n private async listTenantEncryptionKeyRows(\n tenantId: string,\n ): Promise<TenantEncryptionKeyRow[]> {\n const result = await this.db.query(\n `\n SELECT id, tenant_id, wrapped_key, amk_key_id, status, version,\n rotate_after, retired_at, created_at, updated_at\n FROM \"tenant_encryption_keys\"\n WHERE tenant_id = ?\n ORDER BY version DESC\n `,\n tenantId,\n );\n\n return this.rowsFromResult<TenantEncryptionKeyRow>(result);\n }\n\n private async listSmrtTenantKeysForDiagnosis(\n tenantId: string,\n ): Promise<SmrtTenantKeyDiagnosisRows> {\n try {\n return { keys: await this.tenantKeys.listKeyVersions(tenantId) };\n } catch (error) {\n return { keys: [], error: this.toError(error) };\n }\n }\n\n private getConfiguredAmkForDiagnosis():\n | { usable: true; value: Buffer }\n | { usable: false; error: string; value?: undefined } {\n const keyHex = process.env[this.amkEnvVar];\n if (!keyHex) {\n return {\n usable: false,\n error: `Application Master Key not found in environment variable: ${this.amkEnvVar}`,\n };\n }\n\n try {\n return {\n usable: true,\n value: EnvelopeEncryption.parseHexKey(keyHex),\n };\n } catch (error) {\n return {\n usable: false,\n error: `Invalid AMK in ${this.amkEnvVar}: ${\n this.toError(error).message\n }`,\n };\n }\n }\n\n private checkWrappedKey(wrappedKey: string, amk: Buffer): WrappedKeyCheck {\n const fingerprint = this.getWrappedKeyFingerprint(wrappedKey);\n\n try {\n const parsed = EnvelopeEncryption.parseWrappedKey(wrappedKey);\n const dataKey = EnvelopeEncryption.unwrapKey(\n parsed.wrappedKey,\n parsed.iv,\n parsed.authTag,\n amk,\n );\n dataKey.fill(0);\n return { usable: true, fingerprint };\n } catch (error) {\n return {\n usable: false,\n fingerprint,\n error: this.toError(error).message,\n };\n }\n }\n\n private getWrappedKeyFingerprint(wrappedKey: string): string | undefined {\n try {\n return EnvelopeEncryption.parseWrappedKey(wrappedKey).wrappedKey;\n } catch {\n return undefined;\n }\n }\n\n private parseSecretEnvelopeForDiagnosis(\n secret: SecretDiagnosisRow,\n issues: SecretKeyDriftIssue[],\n ): EncryptedEnvelope | null {\n try {\n return JSON.parse(secret.encrypted_value) as EncryptedEnvelope;\n } catch (error) {\n issues.push({\n code: 'secret_envelope_invalid_json',\n severity: 'error',\n message: 'Secret encryptedValue is not valid EncryptedEnvelope JSON.',\n repairAction: 'delete-unrecoverable-secret',\n secretId: secret.id,\n secretName: secret.name,\n sourceTable: 'secrets',\n details: {\n error: this.toError(error).message,\n },\n });\n return null;\n }\n }\n\n private async deleteRowsByIds(\n tableName: 'secrets' | 'tenant_encryption_keys',\n tenantId: string,\n ids: Set<string>,\n db: DatabaseInterface = this.db,\n ): Promise<number> {\n if (ids.size === 0) return 0;\n\n const idList = Array.from(ids);\n const placeholders = idList.map(() => '?').join(', ');\n const result = await db.query(\n `DELETE FROM \"${tableName}\" WHERE tenant_id = ? AND id IN (${placeholders})`,\n tenantId,\n ...idList,\n );\n\n return typeof result.rowCount === 'number'\n ? result.rowCount\n : idList.length;\n }\n\n private async auditSecretDriftRepairDeletes(\n tenantId: string,\n secretIds: Set<string>,\n secretNames: Map<string, string>,\n ): Promise<void> {\n if (secretIds.size === 0) return;\n\n const userId = this.getCurrentUserId();\n await withTenant({ tenantId }, async () => {\n for (const secretId of secretIds) {\n await this.audit(\n secretId,\n secretNames.get(secretId) ?? '',\n userId,\n 'delete',\n 'success',\n {\n action: 'repairTenantSecretKeyDrift',\n reason: 'unrecoverable-secret-key-drift',\n },\n );\n }\n });\n }\n\n private rowsFromResult<T>(result: unknown): T[] {\n if (Array.isArray(result)) return result as T[];\n if (\n result &&\n typeof result === 'object' &&\n Array.isArray((result as { rows?: unknown }).rows)\n ) {\n return (result as { rows: T[] }).rows;\n }\n return [];\n }\n\n private async classifyTenantKeyFailure(\n tenantId: string,\n secretName: string,\n error: unknown,\n ): Promise<Error> {\n const normalized = this.toError(error);\n if (!this.shouldClassifyTenantKeyFailure(normalized)) {\n return normalized;\n }\n\n try {\n const report = await this.diagnoseTenantSecretKeyDrift(tenantId, {\n secretNames: [secretName],\n });\n const errorCodes = report.issues\n .filter((issue) => issue.severity === 'error')\n .map((issue) => issue.code);\n\n if (errorCodes.length === 0) {\n return normalized;\n }\n\n return new SecretKeyDriftError(\n `Secret '${secretName}' for tenant '${tenantId}' failed because secret key drift was detected: ${[\n ...new Set(errorCodes),\n ].join(\n ', ',\n )}. Run diagnoseTenantSecretKeyDrift() for details and repairTenantSecretKeyDrift() for explicit cleanup of unrecoverable rows.`,\n tenantId,\n report,\n normalized,\n );\n } catch {\n return normalized;\n }\n }\n\n private shouldClassifyTenantKeyFailure(error: Error): boolean {\n const code = this.getSecretErrorCode(error);\n if (error instanceof AMKUnavailableError || code === 'AMK_UNAVAILABLE') {\n return false;\n }\n\n return (\n error instanceof TenantKeyMissingError ||\n error instanceof EncryptionError ||\n error instanceof DecryptionError ||\n code === 'TENANT_KEY_MISSING' ||\n code === 'ENCRYPTION_FAILED' ||\n code === 'DECRYPTION_FAILED'\n );\n }\n\n private getSecretErrorCode(error: Error): string | undefined {\n const code = (error as { code?: unknown }).code;\n return typeof code === 'string' ? code : undefined;\n }\n\n private toError(error: unknown): Error {\n return error instanceof Error ? error : new Error(String(error));\n }\n\n private getCurrentUserId(): string {\n const ctx = getCurrentTenant();\n return ctx?.userId ?? 'system';\n }\n\n private async audit(\n secretId: string | null,\n secretName: string,\n userId: string,\n action: SecretAuditAction,\n result: 'success' | 'failure' | 'denied',\n details?: Record<string, unknown>,\n ): Promise<void> {\n if (!this.auditEnabled) return;\n\n try {\n const tenantId = getCurrentTenant()?.tenantId ?? null;\n const log = await this.auditLogs.create(\n createAuditEntry({\n secretId,\n secretName,\n userId,\n action,\n result,\n details,\n tenantId,\n }),\n );\n await log.save();\n } catch (error) {\n // Don't throw on audit failure - log and continue\n logger.error('Failed to write audit log', { error });\n }\n }\n\n private serializeMetadata(\n metadata: Record<string, unknown>,\n ): Record<string, string> {\n const result: Record<string, string> = {};\n for (const [key, value] of Object.entries(metadata)) {\n result[key] = typeof value === 'string' ? value : JSON.stringify(value);\n }\n return result;\n }\n}\n"],"x_google_ignoreList":[3],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ObjectRegistry, SmrtObject, crossPackageRef, foreignKey, smrt } from "@happyvertical/smrt-core";
|
|
2
2
|
//#region src/__smrt-register__.ts
|
|
3
|
-
ObjectRegistry.registerPackageManifest(JSON.parse("{\"version\":\"1.0.0\",\"timestamp\":1784269675052,\"packageName\":\"@happyvertical/smrt-secrets\",\"packageVersion\":\"0.40.7\",\"objects\":{\"@happyvertical/smrt-secrets:SecretAuditLogCollection\":{\"name\":\"secretauditlogcollection\",\"className\":\"SecretAuditLogCollection\",\"qualifiedName\":\"@happyvertical/smrt-secrets:SecretAuditLogCollection\",\"collection\":\"secretauditlogs\",\"filePath\":\"/home/runner/_work/smrt/smrt/packages/secrets/src/collections/SecretAuditLogCollection.ts\",\"packageName\":\"@happyvertical/smrt-secrets\",\"fields\":{},\"methods\":{\"listLogs\":{\"name\":\"listLogs\",\"async\":true,\"parameters\":[{\"name\":\"options\",\"type\":\"ListAuditLogsOptions\",\"optional\":true}],\"returnType\":\"Promise<SecretAuditLog[]>\",\"isStatic\":false,\"isPublic\":true},\"getSecretHistory\":{\"name\":\"getSecretHistory\",\"async\":true,\"parameters\":[{\"name\":\"tenantId\",\"type\":\"string | null\",\"optional\":false},{\"name\":\"secretName\",\"type\":\"string\",\"optional\":false},{\"name\":\"limit\",\"type\":\"number\",\"optional\":true}],\"returnType\":\"Promise<SecretAuditLog[]>\",\"isStatic\":false,\"isPublic\":true},\"getUserActivity\":{\"name\":\"getUserActivity\",\"async\":true,\"parameters\":[{\"name\":\"tenantId\",\"type\":\"string | null\",\"optional\":false},{\"name\":\"userId\",\"type\":\"string\",\"optional\":false},{\"name\":\"limit\",\"type\":\"number\",\"optional\":true}],\"returnType\":\"Promise<SecretAuditLog[]>\",\"isStatic\":false,\"isPublic\":true},\"getRecentFailures\":{\"name\":\"getRecentFailures\",\"async\":true,\"parameters\":[{\"name\":\"tenantId\",\"type\":\"string | null\",\"optional\":false},{\"name\":\"limit\",\"type\":\"number\",\"optional\":true}],\"returnType\":\"Promise<SecretAuditLog[]>\",\"isStatic\":false,\"isPublic\":true},\"getRecentDenials\":{\"name\":\"getRecentDenials\",\"async\":true,\"parameters\":[{\"name\":\"tenantId\",\"type\":\"string | null\",\"optional\":false},{\"name\":\"limit\",\"type\":\"number\",\"optional\":true}],\"returnType\":\"Promise<SecretAuditLog[]>\",\"isStatic\":false,\"isPublic\":true},\"countByAction\":{\"name\":\"countByAction\",\"async\":true,\"parameters\":[{\"name\":\"tenantId\",\"type\":\"string | null\",\"optional\":false},{\"name\":\"since\",\"type\":\"Date\",\"optional\":true}],\"returnType\":\"Promise<Record<SecretAuditAction, number>>\",\"isStatic\":false,\"isPublic\":true},\"countByResult\":{\"name\":\"countByResult\",\"async\":true,\"parameters\":[{\"name\":\"tenantId\",\"type\":\"string | null\",\"optional\":false},{\"name\":\"since\",\"type\":\"Date\",\"optional\":true}],\"returnType\":\"Promise<Record<SecretAuditResult, number>>\",\"isStatic\":false,\"isPublic\":true},\"cleanup\":{\"name\":\"cleanup\",\"async\":true,\"parameters\":[{\"name\":\"olderThanDays\",\"type\":\"number\",\"optional\":true}],\"returnType\":\"Promise<number>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"SecretAuditLog\",\"exportName\":\"SecretAuditLogCollection\",\"collectionExportName\":\"SecretAuditLogCollectionCollection\",\"schema\":{\"tableName\":\"secret_audit_log_collections\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"secret_audit_log_collections\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"}},\"indexes\":[{\"name\":\"secret_audit_log_collections_id_idx\",\"columns\":[\"id\"]},{\"name\":\"secret_audit_log_collections_slug_context_idx\",\"columns\":[\"slug\",\"context\"],\"unique\":true}],\"version\":\"7bb61130\"}},\"@happyvertical/smrt-secrets:SecretCollection\":{\"name\":\"secretcollection\",\"className\":\"SecretCollection\",\"qualifiedName\":\"@happyvertical/smrt-secrets:SecretCollection\",\"collection\":\"secrets\",\"filePath\":\"/home/runner/_work/smrt/smrt/packages/secrets/src/collections/SecretCollection.ts\",\"packageName\":\"@happyvertical/smrt-secrets\",\"fields\":{},\"methods\":{\"findByName\":{\"name\":\"findByName\",\"async\":true,\"parameters\":[{\"name\":\"tenantId\",\"type\":\"string\",\"optional\":false},{\"name\":\"name\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<Secret | null>\",\"isStatic\":false,\"isPublic\":true},\"listSecrets\":{\"name\":\"listSecrets\",\"async\":true,\"parameters\":[{\"name\":\"tenantId\",\"type\":\"string\",\"optional\":false},{\"name\":\"options\",\"type\":\"ListSecretsOptions\",\"optional\":true}],\"returnType\":\"Promise<Secret[]>\",\"isStatic\":false,\"isPublic\":true},\"listActive\":{\"name\":\"listActive\",\"async\":true,\"parameters\":[{\"name\":\"tenantId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<Secret[]>\",\"isStatic\":false,\"isPublic\":true},\"listByCategory\":{\"name\":\"listByCategory\",\"async\":true,\"parameters\":[{\"name\":\"tenantId\",\"type\":\"string\",\"optional\":false},{\"name\":\"category\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<Secret[]>\",\"isStatic\":false,\"isPublic\":true},\"listExpiring\":{\"name\":\"listExpiring\",\"async\":true,\"parameters\":[{\"name\":\"tenantId\",\"type\":\"string\",\"optional\":false},{\"name\":\"daysAhead\",\"type\":\"number\",\"optional\":true}],\"returnType\":\"Promise<Secret[]>\",\"isStatic\":false,\"isPublic\":true},\"getCategories\":{\"name\":\"getCategories\",\"async\":true,\"parameters\":[{\"name\":\"tenantId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<string[]>\",\"isStatic\":false,\"isPublic\":true},\"countByStatus\":{\"name\":\"countByStatus\",\"async\":true,\"parameters\":[{\"name\":\"tenantId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<Record<SecretStatus, number>>\",\"isStatic\":false,\"isPublic\":true},\"deleteByName\":{\"name\":\"deleteByName\",\"async\":true,\"parameters\":[{\"name\":\"tenantId\",\"type\":\"string\",\"optional\":false},{\"name\":\"name\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<boolean>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"Secret\",\"exportName\":\"SecretCollection\",\"collectionExportName\":\"SecretCollectionCollection\",\"schema\":{\"tableName\":\"secret_collections\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"secret_collections\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"}},\"indexes\":[{\"name\":\"secret_collections_id_idx\",\"columns\":[\"id\"]},{\"name\":\"secret_collections_slug_context_idx\",\"columns\":[\"slug\",\"context\"],\"unique\":true}],\"version\":\"5f18a721\"}},\"@happyvertical/smrt-secrets:TenantKeyCollection\":{\"name\":\"tenantkeycollection\",\"className\":\"TenantKeyCollection\",\"qualifiedName\":\"@happyvertical/smrt-secrets:TenantKeyCollection\",\"collection\":\"tenantkeys\",\"filePath\":\"/home/runner/_work/smrt/smrt/packages/secrets/src/collections/TenantKeyCollection.ts\",\"packageName\":\"@happyvertical/smrt-secrets\",\"fields\":{},\"methods\":{\"getActiveKey\":{\"name\":\"getActiveKey\",\"async\":true,\"parameters\":[{\"name\":\"tenantId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<TenantKey | null>\",\"isStatic\":false,\"isPublic\":true},\"listKeyVersions\":{\"name\":\"listKeyVersions\",\"async\":true,\"parameters\":[{\"name\":\"tenantId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<TenantKey[]>\",\"isStatic\":false,\"isPublic\":true},\"getKeyVersion\":{\"name\":\"getKeyVersion\",\"async\":true,\"parameters\":[{\"name\":\"tenantId\",\"type\":\"string\",\"optional\":false},{\"name\":\"version\",\"type\":\"number\",\"optional\":false}],\"returnType\":\"Promise<TenantKey | null>\",\"isStatic\":false,\"isPublic\":true},\"findKeysNeedingRotation\":{\"name\":\"findKeysNeedingRotation\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<TenantKey[]>\",\"isStatic\":false,\"isPublic\":true},\"listAllActiveKeys\":{\"name\":\"listAllActiveKeys\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<TenantKey[]>\",\"isStatic\":false,\"isPublic\":true},\"countByStatus\":{\"name\":\"countByStatus\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<Record<TenantKeyStatus, number>>\",\"isStatic\":false,\"isPublic\":true},\"markCompromised\":{\"name\":\"markCompromised\",\"async\":true,\"parameters\":[{\"name\":\"tenantId\",\"type\":\"string\",\"optional\":false},{\"name\":\"keyId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<boolean>\",\"isStatic\":false,\"isPublic\":true},\"cleanupRetiredKeys\":{\"name\":\"cleanupRetiredKeys\",\"async\":true,\"parameters\":[{\"name\":\"olderThanDays\",\"type\":\"number\",\"optional\":true}],\"returnType\":\"Promise<number>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"TenantKey\",\"exportName\":\"TenantKeyCollection\",\"collectionExportName\":\"TenantKeyCollectionCollection\",\"schema\":{\"tableName\":\"tenant_key_collections\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"tenant_key_collections\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"}},\"indexes\":[{\"name\":\"tenant_key_collections_id_idx\",\"columns\":[\"id\"]},{\"name\":\"tenant_key_collections_slug_context_idx\",\"columns\":[\"slug\",\"context\"],\"unique\":true}],\"version\":\"26616d13\"}},\"@happyvertical/smrt-secrets:Secret\":{\"name\":\"secret\",\"className\":\"Secret\",\"qualifiedName\":\"@happyvertical/smrt-secrets:Secret\",\"collection\":\"secrets\",\"filePath\":\"/home/runner/_work/smrt/smrt/packages/secrets/src/models/Secret.ts\",\"packageName\":\"@happyvertical/smrt-secrets\",\"fields\":{\"tenantId\":{\"type\":\"text\",\"required\":false,\"default\":\"\",\"_meta\":{\"sqlType\":\"UUID\",\"__tenancy\":{\"isTenantIdField\":true,\"mode\":\"required\",\"field\":\"tenantId\",\"autoFilter\":true,\"autoPopulate\":true,\"allowSuperAdminBypass\":false}}},\"name\":{\"type\":\"text\",\"required\":false,\"default\":\"\"},\"description\":{\"type\":\"text\",\"required\":false,\"default\":\"\"},\"category\":{\"type\":\"text\",\"required\":false,\"default\":\"\"},\"encryptedValue\":{\"type\":\"text\",\"required\":false,\"default\":\"\"},\"keyVersion\":{\"type\":\"integer\",\"required\":false,\"default\":1},\"status\":{\"type\":\"text\",\"required\":false,\"default\":\"active\"},\"expiresAt\":{\"type\":\"datetime\",\"required\":false},\"lastAccessedAt\":{\"type\":\"datetime\",\"required\":false},\"accessCount\":{\"type\":\"integer\",\"required\":false,\"default\":0},\"metadata\":{\"type\":\"json\",\"required\":false,\"default\":{}}},\"methods\":{\"isActive\":{\"name\":\"isActive\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"isExpired\":{\"name\":\"isExpired\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"isUsable\":{\"name\":\"isUsable\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"recordAccess\":{\"name\":\"recordAccess\",\"async\":false,\"parameters\":[],\"returnType\":\"void\",\"isStatic\":false,\"isPublic\":true},\"disable\":{\"name\":\"disable\",\"async\":false,\"parameters\":[],\"returnType\":\"void\",\"isStatic\":false,\"isPublic\":true},\"enable\":{\"name\":\"enable\",\"async\":false,\"parameters\":[],\"returnType\":\"void\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tenantScoped\":true,\"api\":{\"include\":[]},\"mcp\":{\"include\":[]},\"cli\":{\"include\":[\"list\"],\"skipApiCheck\":true}},\"extends\":\"SmrtObject\",\"exportName\":\"Secret\",\"collectionExportName\":\"SecretCollection\",\"schema\":{\"tableName\":\"secrets\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"secrets\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID,\\n \\\"name\\\" TEXT DEFAULT '',\\n \\\"description\\\" TEXT DEFAULT '',\\n \\\"category\\\" TEXT DEFAULT '',\\n \\\"encrypted_value\\\" TEXT DEFAULT '',\\n \\\"key_version\\\" INTEGER DEFAULT 1,\\n \\\"status\\\" TEXT DEFAULT 'active',\\n \\\"expires_at\\\" TIMESTAMP,\\n \\\"last_accessed_at\\\" TIMESTAMP,\\n \\\"access_count\\\" INTEGER DEFAULT 0,\\n \\\"metadata\\\" JSON DEFAULT '{}'\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":false,\"unique\":false},\"name\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"\"},\"description\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"\"},\"category\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"\"},\"encrypted_value\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"\"},\"key_version\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false,\"default\":1},\"status\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"active\"},\"expires_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false},\"last_accessed_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false},\"access_count\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false,\"default\":0},\"metadata\":{\"type\":\"JSON\",\"notNull\":false,\"unique\":false,\"default\":{}}},\"indexes\":[{\"name\":\"secrets_id_idx\",\"columns\":[\"id\"]},{\"name\":\"secrets_slug_context_idx\",\"columns\":[\"slug\",\"context\"],\"unique\":true}],\"version\":\"6298a980\"}},\"@happyvertical/smrt-secrets:SecretAuditLog\":{\"name\":\"secretauditlog\",\"className\":\"SecretAuditLog\",\"qualifiedName\":\"@happyvertical/smrt-secrets:SecretAuditLog\",\"collection\":\"secretauditlogs\",\"filePath\":\"/home/runner/_work/smrt/smrt/packages/secrets/src/models/SecretAuditLog.ts\",\"packageName\":\"@happyvertical/smrt-secrets\",\"fields\":{\"tenantId\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"sqlType\":\"UUID\",\"__tenancy\":{\"isTenantIdField\":true,\"mode\":\"required\",\"field\":\"tenantId\",\"autoFilter\":true,\"autoPopulate\":true,\"allowSuperAdminBypass\":false}}},\"secretId\":{\"type\":\"foreignKey\",\"required\":false,\"related\":\"Secret\"},\"secretName\":{\"type\":\"text\",\"required\":false,\"default\":\"\"},\"userId\":{\"type\":\"crossPackageRef\",\"required\":false,\"related\":\"@happyvertical/smrt-users:User\"},\"action\":{\"type\":\"text\",\"required\":false,\"default\":\"read\"},\"result\":{\"type\":\"text\",\"required\":false,\"default\":\"success\"},\"ipAddress\":{\"type\":\"text\",\"required\":false,\"default\":\"\"},\"userAgent\":{\"type\":\"text\",\"required\":false,\"default\":\"\"},\"details\":{\"type\":\"json\",\"required\":false,\"default\":{}}},\"methods\":{\"isSuccess\":{\"name\":\"isSuccess\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"isFailure\":{\"name\":\"isFailure\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"isDenied\":{\"name\":\"isDenied\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"isReadAction\":{\"name\":\"isReadAction\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"isWriteAction\":{\"name\":\"isWriteAction\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"isKeyOperation\":{\"name\":\"isKeyOperation\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tenantScoped\":true,\"api\":{\"include\":[]},\"mcp\":{\"include\":[]},\"cli\":{\"include\":[\"list\"],\"skipApiCheck\":true}},\"extends\":\"SmrtObject\",\"exportName\":\"SecretAuditLog\",\"collectionExportName\":\"SecretAuditLogCollection\",\"schema\":{\"tableName\":\"secret_audit_logs\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"secret_audit_logs\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID,\\n \\\"secret_id\\\" UUID,\\n \\\"secret_name\\\" TEXT DEFAULT '',\\n \\\"user_id\\\" UUID,\\n \\\"action\\\" TEXT DEFAULT 'read',\\n \\\"result\\\" TEXT DEFAULT 'success',\\n \\\"ip_address\\\" TEXT DEFAULT '',\\n \\\"user_agent\\\" TEXT DEFAULT '',\\n \\\"details\\\" JSON DEFAULT '{}'\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":false,\"unique\":false},\"secret_id\":{\"type\":\"UUID\",\"referenceKind\":\"foreignKey\",\"notNull\":false,\"unique\":false},\"secret_name\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"\"},\"user_id\":{\"type\":\"UUID\",\"referenceKind\":\"crossPackageRef\",\"notNull\":false,\"unique\":false},\"action\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"read\"},\"result\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"success\"},\"ip_address\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"\"},\"user_agent\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"\"},\"details\":{\"type\":\"JSON\",\"notNull\":false,\"unique\":false,\"default\":{}}},\"indexes\":[{\"name\":\"secret_audit_logs_id_idx\",\"columns\":[\"id\"]},{\"name\":\"secret_audit_logs_slug_context_idx\",\"columns\":[\"slug\",\"context\"],\"unique\":true}],\"version\":\"af0d679d\"}},\"@happyvertical/smrt-secrets:TenantKey\":{\"name\":\"tenantkey\",\"className\":\"TenantKey\",\"qualifiedName\":\"@happyvertical/smrt-secrets:TenantKey\",\"collection\":\"tenantkeys\",\"filePath\":\"/home/runner/_work/smrt/smrt/packages/secrets/src/models/TenantKey.ts\",\"packageName\":\"@happyvertical/smrt-secrets\",\"fields\":{\"tenantId\":{\"type\":\"text\",\"required\":false,\"default\":\"\"},\"wrappedKey\":{\"type\":\"text\",\"required\":false,\"default\":\"\"},\"amkKeyId\":{\"type\":\"text\",\"required\":false,\"default\":\"\"},\"status\":{\"type\":\"text\",\"required\":false,\"default\":\"active\"},\"version\":{\"type\":\"integer\",\"required\":false,\"default\":1},\"rotateAfter\":{\"type\":\"datetime\",\"required\":false},\"retiredAt\":{\"type\":\"datetime\",\"required\":false}},\"methods\":{\"isActive\":{\"name\":\"isActive\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"needsRotation\":{\"name\":\"needsRotation\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"isRetired\":{\"name\":\"isRetired\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"isCompromised\":{\"name\":\"isCompromised\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"canDecrypt\":{\"name\":\"canDecrypt\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"canEncrypt\":{\"name\":\"canEncrypt\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"retire\":{\"name\":\"retire\",\"async\":false,\"parameters\":[],\"returnType\":\"void\",\"isStatic\":false,\"isPublic\":true},\"markCompromised\":{\"name\":\"markCompromised\",\"async\":false,\"parameters\":[],\"returnType\":\"void\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"api\":{\"include\":[]},\"mcp\":{\"include\":[]},\"cli\":{\"include\":[\"list\",\"get\"],\"skipApiCheck\":true}},\"extends\":\"SmrtObject\",\"exportName\":\"TenantKey\",\"collectionExportName\":\"TenantKeyCollection\",\"schema\":{\"tableName\":\"tenant_keys\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"tenant_keys\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" TEXT DEFAULT '',\\n \\\"wrapped_key\\\" TEXT DEFAULT '',\\n \\\"amk_key_id\\\" TEXT DEFAULT '',\\n \\\"status\\\" TEXT DEFAULT 'active',\\n \\\"version\\\" INTEGER DEFAULT 1,\\n \\\"rotate_after\\\" TIMESTAMP,\\n \\\"retired_at\\\" TIMESTAMP\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"\"},\"wrapped_key\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"\"},\"amk_key_id\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"\"},\"status\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"active\"},\"version\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false,\"default\":1},\"rotate_after\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false},\"retired_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false}},\"indexes\":[{\"name\":\"tenant_keys_id_idx\",\"columns\":[\"id\"]},{\"name\":\"tenant_keys_slug_context_idx\",\"columns\":[\"slug\",\"context\"],\"unique\":true}],\"version\":\"79fd7e46\"}}},\"moduleType\":\"smrt\",\"smrtDependencies\":[\"@happyvertical/smrt-core\",\"@happyvertical/smrt-tenancy\"]}"));
|
|
3
|
+
ObjectRegistry.registerPackageManifest(JSON.parse("{\"version\":\"1.0.0\",\"timestamp\":1784314455311,\"packageName\":\"@happyvertical/smrt-secrets\",\"packageVersion\":\"0.40.8\",\"objects\":{\"@happyvertical/smrt-secrets:SecretAuditLogCollection\":{\"name\":\"secretauditlogcollection\",\"className\":\"SecretAuditLogCollection\",\"qualifiedName\":\"@happyvertical/smrt-secrets:SecretAuditLogCollection\",\"collection\":\"secretauditlogs\",\"filePath\":\"/home/runner/_work/smrt/smrt/packages/secrets/src/collections/SecretAuditLogCollection.ts\",\"packageName\":\"@happyvertical/smrt-secrets\",\"fields\":{},\"methods\":{\"listLogs\":{\"name\":\"listLogs\",\"async\":true,\"parameters\":[{\"name\":\"options\",\"type\":\"ListAuditLogsOptions\",\"optional\":true}],\"returnType\":\"Promise<SecretAuditLog[]>\",\"isStatic\":false,\"isPublic\":true},\"getSecretHistory\":{\"name\":\"getSecretHistory\",\"async\":true,\"parameters\":[{\"name\":\"tenantId\",\"type\":\"string | null\",\"optional\":false},{\"name\":\"secretName\",\"type\":\"string\",\"optional\":false},{\"name\":\"limit\",\"type\":\"number\",\"optional\":true}],\"returnType\":\"Promise<SecretAuditLog[]>\",\"isStatic\":false,\"isPublic\":true},\"getUserActivity\":{\"name\":\"getUserActivity\",\"async\":true,\"parameters\":[{\"name\":\"tenantId\",\"type\":\"string | null\",\"optional\":false},{\"name\":\"userId\",\"type\":\"string\",\"optional\":false},{\"name\":\"limit\",\"type\":\"number\",\"optional\":true}],\"returnType\":\"Promise<SecretAuditLog[]>\",\"isStatic\":false,\"isPublic\":true},\"getRecentFailures\":{\"name\":\"getRecentFailures\",\"async\":true,\"parameters\":[{\"name\":\"tenantId\",\"type\":\"string | null\",\"optional\":false},{\"name\":\"limit\",\"type\":\"number\",\"optional\":true}],\"returnType\":\"Promise<SecretAuditLog[]>\",\"isStatic\":false,\"isPublic\":true},\"getRecentDenials\":{\"name\":\"getRecentDenials\",\"async\":true,\"parameters\":[{\"name\":\"tenantId\",\"type\":\"string | null\",\"optional\":false},{\"name\":\"limit\",\"type\":\"number\",\"optional\":true}],\"returnType\":\"Promise<SecretAuditLog[]>\",\"isStatic\":false,\"isPublic\":true},\"countByAction\":{\"name\":\"countByAction\",\"async\":true,\"parameters\":[{\"name\":\"tenantId\",\"type\":\"string | null\",\"optional\":false},{\"name\":\"since\",\"type\":\"Date\",\"optional\":true}],\"returnType\":\"Promise<Record<SecretAuditAction, number>>\",\"isStatic\":false,\"isPublic\":true},\"countByResult\":{\"name\":\"countByResult\",\"async\":true,\"parameters\":[{\"name\":\"tenantId\",\"type\":\"string | null\",\"optional\":false},{\"name\":\"since\",\"type\":\"Date\",\"optional\":true}],\"returnType\":\"Promise<Record<SecretAuditResult, number>>\",\"isStatic\":false,\"isPublic\":true},\"cleanup\":{\"name\":\"cleanup\",\"async\":true,\"parameters\":[{\"name\":\"olderThanDays\",\"type\":\"number\",\"optional\":true}],\"returnType\":\"Promise<number>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"SecretAuditLog\",\"exportName\":\"SecretAuditLogCollection\",\"collectionExportName\":\"SecretAuditLogCollectionCollection\",\"schema\":{\"tableName\":\"secret_audit_log_collections\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"secret_audit_log_collections\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"}},\"indexes\":[{\"name\":\"secret_audit_log_collections_id_idx\",\"columns\":[\"id\"]},{\"name\":\"secret_audit_log_collections_slug_context_idx\",\"columns\":[\"slug\",\"context\"],\"unique\":true}],\"version\":\"7bb61130\"}},\"@happyvertical/smrt-secrets:SecretCollection\":{\"name\":\"secretcollection\",\"className\":\"SecretCollection\",\"qualifiedName\":\"@happyvertical/smrt-secrets:SecretCollection\",\"collection\":\"secrets\",\"filePath\":\"/home/runner/_work/smrt/smrt/packages/secrets/src/collections/SecretCollection.ts\",\"packageName\":\"@happyvertical/smrt-secrets\",\"fields\":{},\"methods\":{\"findByName\":{\"name\":\"findByName\",\"async\":true,\"parameters\":[{\"name\":\"tenantId\",\"type\":\"string\",\"optional\":false},{\"name\":\"name\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<Secret | null>\",\"isStatic\":false,\"isPublic\":true},\"listSecrets\":{\"name\":\"listSecrets\",\"async\":true,\"parameters\":[{\"name\":\"tenantId\",\"type\":\"string\",\"optional\":false},{\"name\":\"options\",\"type\":\"ListSecretsOptions\",\"optional\":true}],\"returnType\":\"Promise<Secret[]>\",\"isStatic\":false,\"isPublic\":true},\"listActive\":{\"name\":\"listActive\",\"async\":true,\"parameters\":[{\"name\":\"tenantId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<Secret[]>\",\"isStatic\":false,\"isPublic\":true},\"listByCategory\":{\"name\":\"listByCategory\",\"async\":true,\"parameters\":[{\"name\":\"tenantId\",\"type\":\"string\",\"optional\":false},{\"name\":\"category\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<Secret[]>\",\"isStatic\":false,\"isPublic\":true},\"listExpiring\":{\"name\":\"listExpiring\",\"async\":true,\"parameters\":[{\"name\":\"tenantId\",\"type\":\"string\",\"optional\":false},{\"name\":\"daysAhead\",\"type\":\"number\",\"optional\":true}],\"returnType\":\"Promise<Secret[]>\",\"isStatic\":false,\"isPublic\":true},\"getCategories\":{\"name\":\"getCategories\",\"async\":true,\"parameters\":[{\"name\":\"tenantId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<string[]>\",\"isStatic\":false,\"isPublic\":true},\"countByStatus\":{\"name\":\"countByStatus\",\"async\":true,\"parameters\":[{\"name\":\"tenantId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<Record<SecretStatus, number>>\",\"isStatic\":false,\"isPublic\":true},\"deleteByName\":{\"name\":\"deleteByName\",\"async\":true,\"parameters\":[{\"name\":\"tenantId\",\"type\":\"string\",\"optional\":false},{\"name\":\"name\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<boolean>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"Secret\",\"exportName\":\"SecretCollection\",\"collectionExportName\":\"SecretCollectionCollection\",\"schema\":{\"tableName\":\"secret_collections\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"secret_collections\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"}},\"indexes\":[{\"name\":\"secret_collections_id_idx\",\"columns\":[\"id\"]},{\"name\":\"secret_collections_slug_context_idx\",\"columns\":[\"slug\",\"context\"],\"unique\":true}],\"version\":\"5f18a721\"}},\"@happyvertical/smrt-secrets:TenantKeyCollection\":{\"name\":\"tenantkeycollection\",\"className\":\"TenantKeyCollection\",\"qualifiedName\":\"@happyvertical/smrt-secrets:TenantKeyCollection\",\"collection\":\"tenantkeys\",\"filePath\":\"/home/runner/_work/smrt/smrt/packages/secrets/src/collections/TenantKeyCollection.ts\",\"packageName\":\"@happyvertical/smrt-secrets\",\"fields\":{},\"methods\":{\"getActiveKey\":{\"name\":\"getActiveKey\",\"async\":true,\"parameters\":[{\"name\":\"tenantId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<TenantKey | null>\",\"isStatic\":false,\"isPublic\":true},\"listKeyVersions\":{\"name\":\"listKeyVersions\",\"async\":true,\"parameters\":[{\"name\":\"tenantId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<TenantKey[]>\",\"isStatic\":false,\"isPublic\":true},\"getKeyVersion\":{\"name\":\"getKeyVersion\",\"async\":true,\"parameters\":[{\"name\":\"tenantId\",\"type\":\"string\",\"optional\":false},{\"name\":\"version\",\"type\":\"number\",\"optional\":false}],\"returnType\":\"Promise<TenantKey | null>\",\"isStatic\":false,\"isPublic\":true},\"findKeysNeedingRotation\":{\"name\":\"findKeysNeedingRotation\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<TenantKey[]>\",\"isStatic\":false,\"isPublic\":true},\"listAllActiveKeys\":{\"name\":\"listAllActiveKeys\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<TenantKey[]>\",\"isStatic\":false,\"isPublic\":true},\"countByStatus\":{\"name\":\"countByStatus\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<Record<TenantKeyStatus, number>>\",\"isStatic\":false,\"isPublic\":true},\"markCompromised\":{\"name\":\"markCompromised\",\"async\":true,\"parameters\":[{\"name\":\"tenantId\",\"type\":\"string\",\"optional\":false},{\"name\":\"keyId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<boolean>\",\"isStatic\":false,\"isPublic\":true},\"cleanupRetiredKeys\":{\"name\":\"cleanupRetiredKeys\",\"async\":true,\"parameters\":[{\"name\":\"olderThanDays\",\"type\":\"number\",\"optional\":true}],\"returnType\":\"Promise<number>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"TenantKey\",\"exportName\":\"TenantKeyCollection\",\"collectionExportName\":\"TenantKeyCollectionCollection\",\"schema\":{\"tableName\":\"tenant_key_collections\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"tenant_key_collections\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"}},\"indexes\":[{\"name\":\"tenant_key_collections_id_idx\",\"columns\":[\"id\"]},{\"name\":\"tenant_key_collections_slug_context_idx\",\"columns\":[\"slug\",\"context\"],\"unique\":true}],\"version\":\"26616d13\"}},\"@happyvertical/smrt-secrets:Secret\":{\"name\":\"secret\",\"className\":\"Secret\",\"qualifiedName\":\"@happyvertical/smrt-secrets:Secret\",\"collection\":\"secrets\",\"filePath\":\"/home/runner/_work/smrt/smrt/packages/secrets/src/models/Secret.ts\",\"packageName\":\"@happyvertical/smrt-secrets\",\"fields\":{\"tenantId\":{\"type\":\"text\",\"required\":false,\"default\":\"\",\"_meta\":{\"sqlType\":\"UUID\",\"__tenancy\":{\"isTenantIdField\":true,\"mode\":\"required\",\"field\":\"tenantId\",\"autoFilter\":true,\"autoPopulate\":true,\"allowSuperAdminBypass\":false}}},\"name\":{\"type\":\"text\",\"required\":false,\"default\":\"\"},\"description\":{\"type\":\"text\",\"required\":false,\"default\":\"\"},\"category\":{\"type\":\"text\",\"required\":false,\"default\":\"\"},\"encryptedValue\":{\"type\":\"text\",\"required\":false,\"default\":\"\"},\"keyVersion\":{\"type\":\"integer\",\"required\":false,\"default\":1},\"status\":{\"type\":\"text\",\"required\":false,\"default\":\"active\"},\"expiresAt\":{\"type\":\"datetime\",\"required\":false},\"lastAccessedAt\":{\"type\":\"datetime\",\"required\":false},\"accessCount\":{\"type\":\"integer\",\"required\":false,\"default\":0},\"metadata\":{\"type\":\"json\",\"required\":false,\"default\":{}}},\"methods\":{\"isActive\":{\"name\":\"isActive\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"isExpired\":{\"name\":\"isExpired\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"isUsable\":{\"name\":\"isUsable\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"recordAccess\":{\"name\":\"recordAccess\",\"async\":false,\"parameters\":[],\"returnType\":\"void\",\"isStatic\":false,\"isPublic\":true},\"disable\":{\"name\":\"disable\",\"async\":false,\"parameters\":[],\"returnType\":\"void\",\"isStatic\":false,\"isPublic\":true},\"enable\":{\"name\":\"enable\",\"async\":false,\"parameters\":[],\"returnType\":\"void\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tenantScoped\":true,\"api\":{\"include\":[]},\"mcp\":{\"include\":[]},\"cli\":{\"include\":[\"list\"],\"skipApiCheck\":true}},\"extends\":\"SmrtObject\",\"exportName\":\"Secret\",\"collectionExportName\":\"SecretCollection\",\"schema\":{\"tableName\":\"secrets\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"secrets\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID,\\n \\\"name\\\" TEXT DEFAULT '',\\n \\\"description\\\" TEXT DEFAULT '',\\n \\\"category\\\" TEXT DEFAULT '',\\n \\\"encrypted_value\\\" TEXT DEFAULT '',\\n \\\"key_version\\\" INTEGER DEFAULT 1,\\n \\\"status\\\" TEXT DEFAULT 'active',\\n \\\"expires_at\\\" TIMESTAMP,\\n \\\"last_accessed_at\\\" TIMESTAMP,\\n \\\"access_count\\\" INTEGER DEFAULT 0,\\n \\\"metadata\\\" JSON DEFAULT '{}'\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":false,\"unique\":false},\"name\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"\"},\"description\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"\"},\"category\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"\"},\"encrypted_value\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"\"},\"key_version\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false,\"default\":1},\"status\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"active\"},\"expires_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false},\"last_accessed_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false},\"access_count\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false,\"default\":0},\"metadata\":{\"type\":\"JSON\",\"notNull\":false,\"unique\":false,\"default\":{}}},\"indexes\":[{\"name\":\"secrets_id_idx\",\"columns\":[\"id\"]},{\"name\":\"secrets_slug_context_idx\",\"columns\":[\"slug\",\"context\"],\"unique\":true}],\"version\":\"6298a980\"}},\"@happyvertical/smrt-secrets:SecretAuditLog\":{\"name\":\"secretauditlog\",\"className\":\"SecretAuditLog\",\"qualifiedName\":\"@happyvertical/smrt-secrets:SecretAuditLog\",\"collection\":\"secretauditlogs\",\"filePath\":\"/home/runner/_work/smrt/smrt/packages/secrets/src/models/SecretAuditLog.ts\",\"packageName\":\"@happyvertical/smrt-secrets\",\"fields\":{\"tenantId\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"sqlType\":\"UUID\",\"__tenancy\":{\"isTenantIdField\":true,\"mode\":\"required\",\"field\":\"tenantId\",\"autoFilter\":true,\"autoPopulate\":true,\"allowSuperAdminBypass\":false}}},\"secretId\":{\"type\":\"foreignKey\",\"required\":false,\"related\":\"Secret\"},\"secretName\":{\"type\":\"text\",\"required\":false,\"default\":\"\"},\"userId\":{\"type\":\"crossPackageRef\",\"required\":false,\"related\":\"@happyvertical/smrt-users:User\"},\"action\":{\"type\":\"text\",\"required\":false,\"default\":\"read\"},\"result\":{\"type\":\"text\",\"required\":false,\"default\":\"success\"},\"ipAddress\":{\"type\":\"text\",\"required\":false,\"default\":\"\"},\"userAgent\":{\"type\":\"text\",\"required\":false,\"default\":\"\"},\"details\":{\"type\":\"json\",\"required\":false,\"default\":{}}},\"methods\":{\"isSuccess\":{\"name\":\"isSuccess\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"isFailure\":{\"name\":\"isFailure\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"isDenied\":{\"name\":\"isDenied\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"isReadAction\":{\"name\":\"isReadAction\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"isWriteAction\":{\"name\":\"isWriteAction\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"isKeyOperation\":{\"name\":\"isKeyOperation\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tenantScoped\":true,\"api\":{\"include\":[]},\"mcp\":{\"include\":[]},\"cli\":{\"include\":[\"list\"],\"skipApiCheck\":true}},\"extends\":\"SmrtObject\",\"exportName\":\"SecretAuditLog\",\"collectionExportName\":\"SecretAuditLogCollection\",\"schema\":{\"tableName\":\"secret_audit_logs\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"secret_audit_logs\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID,\\n \\\"secret_id\\\" UUID,\\n \\\"secret_name\\\" TEXT DEFAULT '',\\n \\\"user_id\\\" UUID,\\n \\\"action\\\" TEXT DEFAULT 'read',\\n \\\"result\\\" TEXT DEFAULT 'success',\\n \\\"ip_address\\\" TEXT DEFAULT '',\\n \\\"user_agent\\\" TEXT DEFAULT '',\\n \\\"details\\\" JSON DEFAULT '{}'\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":false,\"unique\":false},\"secret_id\":{\"type\":\"UUID\",\"referenceKind\":\"foreignKey\",\"notNull\":false,\"unique\":false},\"secret_name\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"\"},\"user_id\":{\"type\":\"UUID\",\"referenceKind\":\"crossPackageRef\",\"notNull\":false,\"unique\":false},\"action\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"read\"},\"result\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"success\"},\"ip_address\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"\"},\"user_agent\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"\"},\"details\":{\"type\":\"JSON\",\"notNull\":false,\"unique\":false,\"default\":{}}},\"indexes\":[{\"name\":\"secret_audit_logs_id_idx\",\"columns\":[\"id\"]},{\"name\":\"secret_audit_logs_slug_context_idx\",\"columns\":[\"slug\",\"context\"],\"unique\":true}],\"version\":\"af0d679d\"}},\"@happyvertical/smrt-secrets:TenantKey\":{\"name\":\"tenantkey\",\"className\":\"TenantKey\",\"qualifiedName\":\"@happyvertical/smrt-secrets:TenantKey\",\"collection\":\"tenantkeys\",\"filePath\":\"/home/runner/_work/smrt/smrt/packages/secrets/src/models/TenantKey.ts\",\"packageName\":\"@happyvertical/smrt-secrets\",\"fields\":{\"tenantId\":{\"type\":\"text\",\"required\":false,\"default\":\"\"},\"wrappedKey\":{\"type\":\"text\",\"required\":false,\"default\":\"\"},\"amkKeyId\":{\"type\":\"text\",\"required\":false,\"default\":\"\"},\"status\":{\"type\":\"text\",\"required\":false,\"default\":\"active\"},\"version\":{\"type\":\"integer\",\"required\":false,\"default\":1},\"rotateAfter\":{\"type\":\"datetime\",\"required\":false},\"retiredAt\":{\"type\":\"datetime\",\"required\":false}},\"methods\":{\"isActive\":{\"name\":\"isActive\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"needsRotation\":{\"name\":\"needsRotation\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"isRetired\":{\"name\":\"isRetired\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"isCompromised\":{\"name\":\"isCompromised\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"canDecrypt\":{\"name\":\"canDecrypt\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"canEncrypt\":{\"name\":\"canEncrypt\",\"async\":false,\"parameters\":[],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"retire\":{\"name\":\"retire\",\"async\":false,\"parameters\":[],\"returnType\":\"void\",\"isStatic\":false,\"isPublic\":true},\"markCompromised\":{\"name\":\"markCompromised\",\"async\":false,\"parameters\":[],\"returnType\":\"void\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"api\":{\"include\":[]},\"mcp\":{\"include\":[]},\"cli\":{\"include\":[\"list\",\"get\"],\"skipApiCheck\":true}},\"extends\":\"SmrtObject\",\"exportName\":\"TenantKey\",\"collectionExportName\":\"TenantKeyCollection\",\"schema\":{\"tableName\":\"tenant_keys\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"tenant_keys\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" TEXT DEFAULT '',\\n \\\"wrapped_key\\\" TEXT DEFAULT '',\\n \\\"amk_key_id\\\" TEXT DEFAULT '',\\n \\\"status\\\" TEXT DEFAULT 'active',\\n \\\"version\\\" INTEGER DEFAULT 1,\\n \\\"rotate_after\\\" TIMESTAMP,\\n \\\"retired_at\\\" TIMESTAMP\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"\"},\"wrapped_key\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"\"},\"amk_key_id\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"\"},\"status\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"active\"},\"version\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false,\"default\":1},\"rotate_after\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false},\"retired_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false}},\"indexes\":[{\"name\":\"tenant_keys_id_idx\",\"columns\":[\"id\"]},{\"name\":\"tenant_keys_slug_context_idx\",\"columns\":[\"slug\",\"context\"],\"unique\":true}],\"version\":\"79fd7e46\"}}},\"moduleType\":\"smrt\",\"smrtDependencies\":[\"@happyvertical/smrt-core\",\"@happyvertical/smrt-tenancy\"]}"));
|
|
4
4
|
//#endregion
|
|
5
5
|
//#region \0@oxc-project+runtime@0.138.0/helpers/esm/decorate.js
|
|
6
6
|
function __decorate(decorators, target, key, desc) {
|
|
@@ -347,4 +347,4 @@ TenantKey = __decorate([smrt({
|
|
|
347
347
|
//#endregion
|
|
348
348
|
export { createAuditEntry as i, Secret as n, SecretAuditLog as r, TenantKey as t };
|
|
349
349
|
|
|
350
|
-
//# sourceMappingURL=TenantKey-
|
|
350
|
+
//# sourceMappingURL=TenantKey-DIyrgxt3.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TenantKey-BLPjcOfk.js","names":[],"sources":["../../src/__smrt-register__.ts","../../src/models/SecretAuditLog.ts","../../src/models/Secret.ts","../../src/models/TenantKey.ts"],"sourcesContent":["/**\n * Self-registers this package's build-time manifest before any @smrt() decorator\n * in the package fires. Fixes issue #1132: in consumer runtimes (tsx, SvelteKit\n * SSR, plain `vite dev`) the decorator's synchronous manifest lookup previously\n * missed because no step populated the global manifest cache — classes got\n * registered with zero fields and `save()` / `toJSON()` silently dropped every\n * declared property.\n *\n * Import this module as the first statement in `src/index.ts` so its top-level\n * side effect runs ahead of any class module's @smrt() decorator.\n *\n * Silent no-op in dev/test, where the vitest plugin already populates manifests\n * via a different path. Only needs to succeed in the published dist output.\n *\n * @see https://github.com/happyvertical/smrt/issues/1132\n */\nimport { ObjectRegistry } from '@happyvertical/smrt-core';\n\n// During library builds, smrtPlugin replaces this entire module with generated\n// code that embeds the scanned manifest inline (#1506/#1507) — published dists\n// never resolve this URL, so downstream bundlers cannot break registration by\n// relocating the compiled module away from dist/manifest.json. The runtime\n// lookup below is the fallback for source-mode runs without that transform.\nObjectRegistry.registerPackageManifest(\n new URL('./manifest.json', import.meta.url),\n);\n","/**\n * SecretAuditLog model - Audit trail for secret operations\n * @packageDocumentation\n */\n\nimport type {\n SmrtCreateInput,\n SmrtObjectOptions,\n} from '@happyvertical/smrt-core';\nimport {\n crossPackageRef,\n foreignKey,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\n\n/**\n * Secret audit action types\n */\nexport type SecretAuditAction =\n | 'create'\n | 'read'\n | 'update'\n | 'delete'\n | 'rotate_key'\n | 'disable'\n | 'enable'\n | 'expire';\n\n/**\n * Audit result types\n */\nexport type SecretAuditResult = 'success' | 'failure' | 'denied';\n\n/**\n * Constructor options for {@link SecretAuditLog}. Each field is optional and\n * mirrors a persisted column.\n */\nexport interface SecretAuditLogOptions extends SmrtObjectOptions {\n tenantId?: string | null;\n secretId?: string | null;\n secretName?: string;\n userId?: string | null;\n action?: SecretAuditAction;\n result?: SecretAuditResult;\n ipAddress?: string;\n userAgent?: string;\n details?: Record<string, unknown>;\n}\n\n/**\n * SecretAuditLog records all operations on secrets for compliance\n * and security monitoring.\n *\n * Every secret operation (create, read, update, delete, key rotation)\n * is logged with the user, action, result, and relevant details.\n *\n * **Retention**: Audit logs should be retained according to your\n * compliance requirements (typically 1-7 years).\n *\n * @example\n * ```typescript\n * // Query recent audit logs\n * const logs = await auditLogs.list({\n * where: { tenantId: 'tenant-123' },\n * orderBy: 'created_at DESC',\n * limit: 100\n * });\n *\n * // Filter by action\n * const reads = await auditLogs.list({\n * where: {\n * tenantId: 'tenant-123',\n * action: 'read'\n * }\n * });\n *\n * // Filter by secret name\n * const apiKeyLogs = await auditLogs.list({\n * where: {\n * tenantId: 'tenant-123',\n * secretName: 'stripe-api-key'\n * }\n * });\n * ```\n */\n// Intentional exception to standards.md §7 (`@TenantScoped({ mode: 'optional' })`):\n// `SecretAuditLog` uses the inline `tenantScoped: true` form on `@smrt()` rather than\n// the dedicated `@TenantScoped` decorator. Audit-trail queries are read-mostly and run\n// in mixed contexts — under a tenant for tenant-scoped reports, and (prospectively)\n// under super-admin bypass for compliance review. Cross-tenant audit queries should\n// be wrapped in `withSuperAdminBypass()` from `@happyvertical/smrt-tenancy` at the\n// call site; this package has no such call sites today, so the pattern is\n// prescriptive guidance for compliance tooling rather than current practice. See\n// `packages/secrets/CLAUDE.md` \"Known exceptions to monorepo standards\" for context.\n@smrt({\n tenantScoped: true,\n api: { include: [] }, // No API exposure\n mcp: { include: [] }, // No MCP exposure\n // CLI runs in-process for compliance review; HTTP exposure is intentionally\n // excluded, so skipApiCheck acknowledges the cli.include / api.include divergence.\n cli: { include: ['list'], skipApiCheck: true },\n})\nexport class SecretAuditLog extends SmrtObject {\n /**\n * Tenant associated with the audited secret operation.\n */\n tenantId: string | null = null;\n\n /**\n * ID of the secret (may be null for deleted secrets)\n */\n @foreignKey('Secret')\n secretId: string | null = null;\n\n /**\n * Name of the secret at the time of the operation\n */\n secretName: string = '';\n\n /**\n * ID of the user who performed the action, or `null` for system-initiated\n * operations with no authenticated user. Stored as a native `uuid` column on\n * Postgres, so a non-UUID actor sentinel must never reach it —\n * {@link createAuditEntry} normalizes the `'system'` sentinel to null (#1444).\n */\n @crossPackageRef('@happyvertical/smrt-users:User')\n userId: string | null = null;\n\n /**\n * The action that was performed\n */\n action: SecretAuditAction = 'read';\n\n /**\n * Result of the operation\n */\n result: SecretAuditResult = 'success';\n\n /**\n * IP address of the client (if available)\n */\n ipAddress: string = '';\n\n /**\n * User agent string (if available)\n */\n userAgent: string = '';\n\n /**\n * Additional context about the operation\n */\n details: Record<string, unknown> = {};\n\n constructor(options: SecretAuditLogOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) {\n this.tenantId = options.tenantId;\n }\n if (options.secretId !== undefined) this.secretId = options.secretId;\n if (options.secretName !== undefined) this.secretName = options.secretName;\n if (options.userId !== undefined) this.userId = options.userId;\n if (options.action !== undefined) this.action = options.action;\n if (options.result !== undefined) this.result = options.result;\n if (options.ipAddress !== undefined) this.ipAddress = options.ipAddress;\n if (options.userAgent !== undefined) this.userAgent = options.userAgent;\n if (options.details !== undefined) this.details = options.details;\n }\n\n /**\n * Check if this was a successful operation\n */\n isSuccess(): boolean {\n return this.result === 'success';\n }\n\n /**\n * Check if this was a failed operation\n */\n isFailure(): boolean {\n return this.result === 'failure';\n }\n\n /**\n * Check if this was a denied operation (permission denied)\n */\n isDenied(): boolean {\n return this.result === 'denied';\n }\n\n /**\n * Check if this is a read operation\n */\n isReadAction(): boolean {\n return this.action === 'read';\n }\n\n /**\n * Check if this is a write operation (create, update, delete)\n */\n isWriteAction(): boolean {\n return ['create', 'update', 'delete'].includes(this.action);\n }\n\n /**\n * Check if this is a key operation\n */\n isKeyOperation(): boolean {\n return this.action === 'rotate_key';\n }\n}\n\n/**\n * Create an audit log entry for a secret operation\n */\nexport function createAuditEntry(params: {\n secretId?: string | null;\n secretName: string;\n tenantId?: string | null;\n userId?: string | null;\n action: SecretAuditAction;\n result: SecretAuditResult;\n ipAddress?: string;\n userAgent?: string;\n details?: Record<string, unknown>;\n}): SmrtCreateInput<SecretAuditLog> {\n const tenantId =\n params.tenantId === undefined || params.tenantId === 'system'\n ? null\n : params.tenantId;\n\n // The `userId` column is a native `uuid` on Postgres. A missing actor and the\n // `'system'`/empty sentinels are not valid UUIDs, so normalize them all to\n // null — a system-initiated operation has no authenticated user. Callers may\n // pass `null`/`undefined` directly to express that (#1444).\n const userId =\n params.userId == null || params.userId === 'system' || params.userId === ''\n ? null\n : params.userId;\n\n const entry: SmrtCreateInput<SecretAuditLog> = {\n secretId: params.secretId ?? null,\n secretName: params.secretName,\n userId,\n action: params.action,\n result: params.result,\n ipAddress: params.ipAddress ?? '',\n userAgent: params.userAgent ?? '',\n details: params.details ?? {},\n tenantId,\n };\n return entry;\n}\n","/**\n * Secret model - Tenant-scoped encrypted secrets storage\n * @packageDocumentation\n */\n\nimport type { SmrtObjectOptions } from '@happyvertical/smrt-core';\nimport { SmrtObject, smrt } from '@happyvertical/smrt-core';\n\n/**\n * Secret status values\n */\nexport type SecretStatus = 'active' | 'disabled' | 'expired';\n\n/**\n * Constructor options for {@link Secret}. Each field is optional and mirrors a\n * persisted column; date fields also accept the serialized forms accepted by\n * `new Date()` so hydrated rows coerce cleanly.\n */\nexport interface SecretOptions extends SmrtObjectOptions {\n tenantId?: string;\n name?: string;\n description?: string;\n category?: string;\n encryptedValue?: string;\n keyVersion?: number;\n status?: SecretStatus;\n expiresAt?: Date | string | number | null;\n lastAccessedAt?: Date | string | number | null;\n accessCount?: number;\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Secret represents an encrypted value stored per-tenant.\n *\n * Secrets are tenant-scoped and use envelope encryption:\n * - Each tenant has their own Data Encryption Key (TDEK)\n * - The TDEK is wrapped by the Application Master Key (AMK)\n * - Secret values are encrypted by the unwrapped TDEK\n *\n * **Security**: This model deliberately excludes API and MCP exposure\n * to prevent accidental secret leakage. Secrets are only accessible\n * via CLI commands or direct service calls.\n *\n * @example\n * ```typescript\n * import { SecretService } from '@happyvertical/smrt-secrets';\n *\n * const service = await SecretService.create({ db });\n *\n * await withTenant({ tenantId: 'tenant-123' }, async () => {\n * // Store a secret\n * await service.store('api-key', 'sk_live_xxx', { category: 'stripe' });\n *\n * // Retrieve (auto-decrypts)\n * const apiKey = await service.retrieve('api-key');\n * });\n * ```\n */\n// Intentional exception to standards.md §7 (`@TenantScoped({ mode: 'optional' })`):\n// `Secret` uses the inline `tenantScoped: true` form on `@smrt()` rather than the\n// dedicated `@TenantScoped` decorator from `@happyvertical/smrt-tenancy`. The boolean\n// form gives us required-mode tenant scoping without depending on the tenancy package\n// at the model layer, while `SecretService` performs manual scoping by setting\n// `context = tenantId` on each row. The `(slug, context)` upsert key derived from the\n// base `SmrtObject` fields is what gives different tenants isolated namespaces for\n// secret names — switching to the decorator without changing the upsert key would\n// surface false-positive name collisions across tenants. See\n// `packages/secrets/CLAUDE.md` \"Known exceptions to monorepo standards\" for context.\n@smrt({\n tenantScoped: true,\n // NO API or MCP exposure for security\n api: { include: [] },\n mcp: { include: [] },\n // CLI runs in-process; secrets must never be reachable over HTTP, so\n // skipApiCheck acknowledges the cli.include / api.include divergence.\n cli: { include: ['list'], skipApiCheck: true }, // Only list names, not values\n})\nexport class Secret extends SmrtObject {\n /**\n * Tenant that owns this secret. Also stored in context for per-tenant name uniqueness.\n */\n tenantId: string = '';\n\n /**\n * Unique name for the secret within the tenant\n */\n name: string = '';\n\n /**\n * Human-readable description\n */\n description: string = '';\n\n /**\n * Category for organization (e.g., 'database', 'api-key', 'oauth')\n */\n category: string = '';\n\n /**\n * JSON-encoded EncryptedEnvelope from @happyvertical/secrets\n */\n encryptedValue: string = '';\n\n /**\n * Version of the tenant key used to encrypt this secret\n */\n keyVersion: number = 1;\n\n /**\n * Current status of the secret\n */\n status: SecretStatus = 'active';\n\n /**\n * Optional expiration date\n */\n expiresAt: Date | null = null;\n\n /**\n * Last time this secret was accessed (decrypted)\n */\n lastAccessedAt: Date | null = null;\n\n /**\n * Number of times this secret has been accessed\n */\n accessCount: number = 0;\n\n /**\n * Additional metadata stored with the secret\n */\n metadata: Record<string, unknown> = {};\n\n constructor(options: SecretOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.name !== undefined) this.name = options.name;\n if (options.description !== undefined)\n this.description = options.description;\n if (options.category !== undefined) this.category = options.category;\n if (options.encryptedValue !== undefined)\n this.encryptedValue = options.encryptedValue;\n if (options.keyVersion !== undefined) this.keyVersion = options.keyVersion;\n if (options.status !== undefined) this.status = options.status;\n if (options.expiresAt !== undefined) {\n this.expiresAt =\n options.expiresAt instanceof Date\n ? options.expiresAt\n : options.expiresAt\n ? new Date(options.expiresAt)\n : null;\n }\n if (options.lastAccessedAt !== undefined) {\n this.lastAccessedAt =\n options.lastAccessedAt instanceof Date\n ? options.lastAccessedAt\n : options.lastAccessedAt\n ? new Date(options.lastAccessedAt)\n : null;\n }\n if (options.accessCount !== undefined)\n this.accessCount = options.accessCount;\n if (options.metadata !== undefined) this.metadata = options.metadata;\n }\n\n /**\n * Check if the secret is currently active\n */\n isActive(): boolean {\n return this.status === 'active';\n }\n\n /**\n * Check if the secret has expired\n */\n isExpired(): boolean {\n if (!this.expiresAt) return false;\n return new Date() >= this.expiresAt;\n }\n\n /**\n * Check if the secret can be used (active and not expired)\n */\n isUsable(): boolean {\n return this.isActive() && !this.isExpired();\n }\n\n /**\n * Record an access to this secret\n */\n recordAccess(): void {\n this.lastAccessedAt = new Date();\n this.accessCount += 1;\n }\n\n /**\n * Disable the secret\n */\n disable(): void {\n this.status = 'disabled';\n }\n\n /**\n * Enable the secret\n */\n enable(): void {\n this.status = 'active';\n }\n}\n","/**\n * TenantKey model - Tracks per-tenant Data Encryption Keys (TDEKs)\n * @packageDocumentation\n */\n\nimport type { SmrtObjectOptions } from '@happyvertical/smrt-core';\nimport { SmrtObject, smrt } from '@happyvertical/smrt-core';\n\n/**\n * Key status values\n */\nexport type TenantKeyStatus = 'active' | 'rotating' | 'retired' | 'compromised';\n\n/**\n * Constructor options for {@link TenantKey}. Each field is optional and mirrors\n * a persisted column; date fields also accept the serialized forms accepted by\n * `new Date()` so hydrated rows coerce cleanly.\n */\nexport interface TenantKeyOptions extends SmrtObjectOptions {\n tenantId?: string;\n wrappedKey?: string;\n amkKeyId?: string;\n status?: TenantKeyStatus;\n version?: number;\n rotateAfter?: Date | string | number | null;\n retiredAt?: Date | string | number | null;\n}\n\n/**\n * TenantKey tracks the per-tenant Data Encryption Keys (TDEKs).\n *\n * Each tenant has one or more TDEKs stored in wrapped form. The wrapped key\n * can only be decrypted using the Application Master Key (AMK).\n *\n * **Key Lifecycle**:\n * 1. `active` - Current key used for encryption\n * 2. `rotating` - Transitional state during rotation\n * 3. `retired` - Old key kept for decryption of existing secrets\n * 4. `compromised` - Key marked as compromised, should not be used\n *\n * **Note**: This model is NOT tenant-scoped itself because it tracks\n * keys FOR tenants, not secrets owned BY tenants.\n *\n * @example\n * ```typescript\n * // Get active key for a tenant\n * const key = await tenantKeys.get({\n * tenantId: 'tenant-123',\n * status: 'active'\n * });\n *\n * // List all key versions for a tenant\n * const versions = await tenantKeys.list({\n * where: { tenantId: 'tenant-123' },\n * orderBy: 'version DESC'\n * });\n * ```\n */\n// Intentional exception to standards.md §7 (`@TenantScoped({ mode: 'optional' })`):\n// `TenantKey` is deliberately NOT tenant-scoped — neither via the decorator nor via\n// `tenantScoped: true` on `@smrt()`. The row carries a `tenantId` column because each\n// TDEK belongs to a tenant, but the model itself must remain queryable across tenants\n// (e.g. cross-tenant key-rotation tooling, AMK rewrap jobs, super-admin auditing).\n// Adding the tenancy interceptor here would silently filter out keys the rotation\n// service needs to inspect. See `packages/secrets/CLAUDE.md` \"Known exceptions to\n// monorepo standards\" for the full rationale.\n@smrt({\n // NOT tenant-scoped - this model tracks keys FOR tenants\n api: { include: [] }, // No API exposure\n mcp: { include: [] }, // No MCP exposure\n // CLI runs in-process for key-rotation tooling and audit; HTTP exposure is\n // intentionally excluded, so skipApiCheck acknowledges the divergence.\n cli: { include: ['list', 'get'], skipApiCheck: true },\n})\nexport class TenantKey extends SmrtObject {\n /**\n * Tenant ID this key belongs to\n */\n tenantId: string = '';\n\n /**\n * Wrapped key data (format: wrappedKey:iv:authTag)\n */\n wrappedKey: string = '';\n\n /**\n * ID of the AMK used to wrap this key\n */\n amkKeyId: string = '';\n\n /**\n * Current status of the key\n */\n status: TenantKeyStatus = 'active';\n\n /**\n * Version number (increments on rotation)\n */\n version: number = 1;\n\n /**\n * Recommended rotation date\n */\n rotateAfter: Date | null = null;\n\n /**\n * When the key was retired (if applicable)\n */\n retiredAt: Date | null = null;\n\n constructor(options: TenantKeyOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.wrappedKey !== undefined) this.wrappedKey = options.wrappedKey;\n if (options.amkKeyId !== undefined) this.amkKeyId = options.amkKeyId;\n if (options.status !== undefined) this.status = options.status;\n if (options.version !== undefined) this.version = options.version;\n if (options.rotateAfter !== undefined) {\n this.rotateAfter =\n options.rotateAfter instanceof Date\n ? options.rotateAfter\n : options.rotateAfter\n ? new Date(options.rotateAfter)\n : null;\n }\n if (options.retiredAt !== undefined) {\n this.retiredAt =\n options.retiredAt instanceof Date\n ? options.retiredAt\n : options.retiredAt\n ? new Date(options.retiredAt)\n : null;\n }\n }\n\n /**\n * Check if this key is currently active\n */\n isActive(): boolean {\n return this.status === 'active';\n }\n\n /**\n * Check if this key needs rotation\n */\n needsRotation(): boolean {\n if (!this.rotateAfter) return false;\n return new Date() >= this.rotateAfter;\n }\n\n /**\n * Check if this key is retired\n */\n isRetired(): boolean {\n return this.status === 'retired';\n }\n\n /**\n * Check if this key is compromised\n */\n isCompromised(): boolean {\n return this.status === 'compromised';\n }\n\n /**\n * Check if this key can be used for decryption\n * (active or retired keys can decrypt)\n */\n canDecrypt(): boolean {\n return this.status === 'active' || this.status === 'retired';\n }\n\n /**\n * Check if this key can be used for encryption\n * (only active keys should encrypt)\n */\n canEncrypt(): boolean {\n return this.status === 'active';\n }\n\n /**\n * Mark this key as retired\n */\n retire(): void {\n this.status = 'retired';\n this.retiredAt = new Date();\n }\n\n /**\n * Mark this key as compromised\n */\n markCompromised(): void {\n this.status = 'compromised';\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TenantKey-DIyrgxt3.js","names":[],"sources":["../../src/__smrt-register__.ts","../../src/models/SecretAuditLog.ts","../../src/models/Secret.ts","../../src/models/TenantKey.ts"],"sourcesContent":["/**\n * Self-registers this package's build-time manifest before any @smrt() decorator\n * in the package fires. Fixes issue #1132: in consumer runtimes (tsx, SvelteKit\n * SSR, plain `vite dev`) the decorator's synchronous manifest lookup previously\n * missed because no step populated the global manifest cache — classes got\n * registered with zero fields and `save()` / `toJSON()` silently dropped every\n * declared property.\n *\n * Import this module as the first statement in `src/index.ts` so its top-level\n * side effect runs ahead of any class module's @smrt() decorator.\n *\n * Silent no-op in dev/test, where the vitest plugin already populates manifests\n * via a different path. Only needs to succeed in the published dist output.\n *\n * @see https://github.com/happyvertical/smrt/issues/1132\n */\nimport { ObjectRegistry } from '@happyvertical/smrt-core';\n\n// During library builds, smrtPlugin replaces this entire module with generated\n// code that embeds the scanned manifest inline (#1506/#1507) — published dists\n// never resolve this URL, so downstream bundlers cannot break registration by\n// relocating the compiled module away from dist/manifest.json. The runtime\n// lookup below is the fallback for source-mode runs without that transform.\nObjectRegistry.registerPackageManifest(\n new URL('./manifest.json', import.meta.url),\n);\n","/**\n * SecretAuditLog model - Audit trail for secret operations\n * @packageDocumentation\n */\n\nimport type {\n SmrtCreateInput,\n SmrtObjectOptions,\n} from '@happyvertical/smrt-core';\nimport {\n crossPackageRef,\n foreignKey,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\n\n/**\n * Secret audit action types\n */\nexport type SecretAuditAction =\n | 'create'\n | 'read'\n | 'update'\n | 'delete'\n | 'rotate_key'\n | 'disable'\n | 'enable'\n | 'expire';\n\n/**\n * Audit result types\n */\nexport type SecretAuditResult = 'success' | 'failure' | 'denied';\n\n/**\n * Constructor options for {@link SecretAuditLog}. Each field is optional and\n * mirrors a persisted column.\n */\nexport interface SecretAuditLogOptions extends SmrtObjectOptions {\n tenantId?: string | null;\n secretId?: string | null;\n secretName?: string;\n userId?: string | null;\n action?: SecretAuditAction;\n result?: SecretAuditResult;\n ipAddress?: string;\n userAgent?: string;\n details?: Record<string, unknown>;\n}\n\n/**\n * SecretAuditLog records all operations on secrets for compliance\n * and security monitoring.\n *\n * Every secret operation (create, read, update, delete, key rotation)\n * is logged with the user, action, result, and relevant details.\n *\n * **Retention**: Audit logs should be retained according to your\n * compliance requirements (typically 1-7 years).\n *\n * @example\n * ```typescript\n * // Query recent audit logs\n * const logs = await auditLogs.list({\n * where: { tenantId: 'tenant-123' },\n * orderBy: 'created_at DESC',\n * limit: 100\n * });\n *\n * // Filter by action\n * const reads = await auditLogs.list({\n * where: {\n * tenantId: 'tenant-123',\n * action: 'read'\n * }\n * });\n *\n * // Filter by secret name\n * const apiKeyLogs = await auditLogs.list({\n * where: {\n * tenantId: 'tenant-123',\n * secretName: 'stripe-api-key'\n * }\n * });\n * ```\n */\n// Intentional exception to standards.md §7 (`@TenantScoped({ mode: 'optional' })`):\n// `SecretAuditLog` uses the inline `tenantScoped: true` form on `@smrt()` rather than\n// the dedicated `@TenantScoped` decorator. Audit-trail queries are read-mostly and run\n// in mixed contexts — under a tenant for tenant-scoped reports, and (prospectively)\n// under super-admin bypass for compliance review. Cross-tenant audit queries should\n// be wrapped in `withSuperAdminBypass()` from `@happyvertical/smrt-tenancy` at the\n// call site; this package has no such call sites today, so the pattern is\n// prescriptive guidance for compliance tooling rather than current practice. See\n// `packages/secrets/CLAUDE.md` \"Known exceptions to monorepo standards\" for context.\n@smrt({\n tenantScoped: true,\n api: { include: [] }, // No API exposure\n mcp: { include: [] }, // No MCP exposure\n // CLI runs in-process for compliance review; HTTP exposure is intentionally\n // excluded, so skipApiCheck acknowledges the cli.include / api.include divergence.\n cli: { include: ['list'], skipApiCheck: true },\n})\nexport class SecretAuditLog extends SmrtObject {\n /**\n * Tenant associated with the audited secret operation.\n */\n tenantId: string | null = null;\n\n /**\n * ID of the secret (may be null for deleted secrets)\n */\n @foreignKey('Secret')\n secretId: string | null = null;\n\n /**\n * Name of the secret at the time of the operation\n */\n secretName: string = '';\n\n /**\n * ID of the user who performed the action, or `null` for system-initiated\n * operations with no authenticated user. Stored as a native `uuid` column on\n * Postgres, so a non-UUID actor sentinel must never reach it —\n * {@link createAuditEntry} normalizes the `'system'` sentinel to null (#1444).\n */\n @crossPackageRef('@happyvertical/smrt-users:User')\n userId: string | null = null;\n\n /**\n * The action that was performed\n */\n action: SecretAuditAction = 'read';\n\n /**\n * Result of the operation\n */\n result: SecretAuditResult = 'success';\n\n /**\n * IP address of the client (if available)\n */\n ipAddress: string = '';\n\n /**\n * User agent string (if available)\n */\n userAgent: string = '';\n\n /**\n * Additional context about the operation\n */\n details: Record<string, unknown> = {};\n\n constructor(options: SecretAuditLogOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) {\n this.tenantId = options.tenantId;\n }\n if (options.secretId !== undefined) this.secretId = options.secretId;\n if (options.secretName !== undefined) this.secretName = options.secretName;\n if (options.userId !== undefined) this.userId = options.userId;\n if (options.action !== undefined) this.action = options.action;\n if (options.result !== undefined) this.result = options.result;\n if (options.ipAddress !== undefined) this.ipAddress = options.ipAddress;\n if (options.userAgent !== undefined) this.userAgent = options.userAgent;\n if (options.details !== undefined) this.details = options.details;\n }\n\n /**\n * Check if this was a successful operation\n */\n isSuccess(): boolean {\n return this.result === 'success';\n }\n\n /**\n * Check if this was a failed operation\n */\n isFailure(): boolean {\n return this.result === 'failure';\n }\n\n /**\n * Check if this was a denied operation (permission denied)\n */\n isDenied(): boolean {\n return this.result === 'denied';\n }\n\n /**\n * Check if this is a read operation\n */\n isReadAction(): boolean {\n return this.action === 'read';\n }\n\n /**\n * Check if this is a write operation (create, update, delete)\n */\n isWriteAction(): boolean {\n return ['create', 'update', 'delete'].includes(this.action);\n }\n\n /**\n * Check if this is a key operation\n */\n isKeyOperation(): boolean {\n return this.action === 'rotate_key';\n }\n}\n\n/**\n * Create an audit log entry for a secret operation\n */\nexport function createAuditEntry(params: {\n secretId?: string | null;\n secretName: string;\n tenantId?: string | null;\n userId?: string | null;\n action: SecretAuditAction;\n result: SecretAuditResult;\n ipAddress?: string;\n userAgent?: string;\n details?: Record<string, unknown>;\n}): SmrtCreateInput<SecretAuditLog> {\n const tenantId =\n params.tenantId === undefined || params.tenantId === 'system'\n ? null\n : params.tenantId;\n\n // The `userId` column is a native `uuid` on Postgres. A missing actor and the\n // `'system'`/empty sentinels are not valid UUIDs, so normalize them all to\n // null — a system-initiated operation has no authenticated user. Callers may\n // pass `null`/`undefined` directly to express that (#1444).\n const userId =\n params.userId == null || params.userId === 'system' || params.userId === ''\n ? null\n : params.userId;\n\n const entry: SmrtCreateInput<SecretAuditLog> = {\n secretId: params.secretId ?? null,\n secretName: params.secretName,\n userId,\n action: params.action,\n result: params.result,\n ipAddress: params.ipAddress ?? '',\n userAgent: params.userAgent ?? '',\n details: params.details ?? {},\n tenantId,\n };\n return entry;\n}\n","/**\n * Secret model - Tenant-scoped encrypted secrets storage\n * @packageDocumentation\n */\n\nimport type { SmrtObjectOptions } from '@happyvertical/smrt-core';\nimport { SmrtObject, smrt } from '@happyvertical/smrt-core';\n\n/**\n * Secret status values\n */\nexport type SecretStatus = 'active' | 'disabled' | 'expired';\n\n/**\n * Constructor options for {@link Secret}. Each field is optional and mirrors a\n * persisted column; date fields also accept the serialized forms accepted by\n * `new Date()` so hydrated rows coerce cleanly.\n */\nexport interface SecretOptions extends SmrtObjectOptions {\n tenantId?: string;\n name?: string;\n description?: string;\n category?: string;\n encryptedValue?: string;\n keyVersion?: number;\n status?: SecretStatus;\n expiresAt?: Date | string | number | null;\n lastAccessedAt?: Date | string | number | null;\n accessCount?: number;\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Secret represents an encrypted value stored per-tenant.\n *\n * Secrets are tenant-scoped and use envelope encryption:\n * - Each tenant has their own Data Encryption Key (TDEK)\n * - The TDEK is wrapped by the Application Master Key (AMK)\n * - Secret values are encrypted by the unwrapped TDEK\n *\n * **Security**: This model deliberately excludes API and MCP exposure\n * to prevent accidental secret leakage. Secrets are only accessible\n * via CLI commands or direct service calls.\n *\n * @example\n * ```typescript\n * import { SecretService } from '@happyvertical/smrt-secrets';\n *\n * const service = await SecretService.create({ db });\n *\n * await withTenant({ tenantId: 'tenant-123' }, async () => {\n * // Store a secret\n * await service.store('api-key', 'sk_live_xxx', { category: 'stripe' });\n *\n * // Retrieve (auto-decrypts)\n * const apiKey = await service.retrieve('api-key');\n * });\n * ```\n */\n// Intentional exception to standards.md §7 (`@TenantScoped({ mode: 'optional' })`):\n// `Secret` uses the inline `tenantScoped: true` form on `@smrt()` rather than the\n// dedicated `@TenantScoped` decorator from `@happyvertical/smrt-tenancy`. The boolean\n// form gives us required-mode tenant scoping without depending on the tenancy package\n// at the model layer, while `SecretService` performs manual scoping by setting\n// `context = tenantId` on each row. The `(slug, context)` upsert key derived from the\n// base `SmrtObject` fields is what gives different tenants isolated namespaces for\n// secret names — switching to the decorator without changing the upsert key would\n// surface false-positive name collisions across tenants. See\n// `packages/secrets/CLAUDE.md` \"Known exceptions to monorepo standards\" for context.\n@smrt({\n tenantScoped: true,\n // NO API or MCP exposure for security\n api: { include: [] },\n mcp: { include: [] },\n // CLI runs in-process; secrets must never be reachable over HTTP, so\n // skipApiCheck acknowledges the cli.include / api.include divergence.\n cli: { include: ['list'], skipApiCheck: true }, // Only list names, not values\n})\nexport class Secret extends SmrtObject {\n /**\n * Tenant that owns this secret. Also stored in context for per-tenant name uniqueness.\n */\n tenantId: string = '';\n\n /**\n * Unique name for the secret within the tenant\n */\n name: string = '';\n\n /**\n * Human-readable description\n */\n description: string = '';\n\n /**\n * Category for organization (e.g., 'database', 'api-key', 'oauth')\n */\n category: string = '';\n\n /**\n * JSON-encoded EncryptedEnvelope from @happyvertical/secrets\n */\n encryptedValue: string = '';\n\n /**\n * Version of the tenant key used to encrypt this secret\n */\n keyVersion: number = 1;\n\n /**\n * Current status of the secret\n */\n status: SecretStatus = 'active';\n\n /**\n * Optional expiration date\n */\n expiresAt: Date | null = null;\n\n /**\n * Last time this secret was accessed (decrypted)\n */\n lastAccessedAt: Date | null = null;\n\n /**\n * Number of times this secret has been accessed\n */\n accessCount: number = 0;\n\n /**\n * Additional metadata stored with the secret\n */\n metadata: Record<string, unknown> = {};\n\n constructor(options: SecretOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.name !== undefined) this.name = options.name;\n if (options.description !== undefined)\n this.description = options.description;\n if (options.category !== undefined) this.category = options.category;\n if (options.encryptedValue !== undefined)\n this.encryptedValue = options.encryptedValue;\n if (options.keyVersion !== undefined) this.keyVersion = options.keyVersion;\n if (options.status !== undefined) this.status = options.status;\n if (options.expiresAt !== undefined) {\n this.expiresAt =\n options.expiresAt instanceof Date\n ? options.expiresAt\n : options.expiresAt\n ? new Date(options.expiresAt)\n : null;\n }\n if (options.lastAccessedAt !== undefined) {\n this.lastAccessedAt =\n options.lastAccessedAt instanceof Date\n ? options.lastAccessedAt\n : options.lastAccessedAt\n ? new Date(options.lastAccessedAt)\n : null;\n }\n if (options.accessCount !== undefined)\n this.accessCount = options.accessCount;\n if (options.metadata !== undefined) this.metadata = options.metadata;\n }\n\n /**\n * Check if the secret is currently active\n */\n isActive(): boolean {\n return this.status === 'active';\n }\n\n /**\n * Check if the secret has expired\n */\n isExpired(): boolean {\n if (!this.expiresAt) return false;\n return new Date() >= this.expiresAt;\n }\n\n /**\n * Check if the secret can be used (active and not expired)\n */\n isUsable(): boolean {\n return this.isActive() && !this.isExpired();\n }\n\n /**\n * Record an access to this secret\n */\n recordAccess(): void {\n this.lastAccessedAt = new Date();\n this.accessCount += 1;\n }\n\n /**\n * Disable the secret\n */\n disable(): void {\n this.status = 'disabled';\n }\n\n /**\n * Enable the secret\n */\n enable(): void {\n this.status = 'active';\n }\n}\n","/**\n * TenantKey model - Tracks per-tenant Data Encryption Keys (TDEKs)\n * @packageDocumentation\n */\n\nimport type { SmrtObjectOptions } from '@happyvertical/smrt-core';\nimport { SmrtObject, smrt } from '@happyvertical/smrt-core';\n\n/**\n * Key status values\n */\nexport type TenantKeyStatus = 'active' | 'rotating' | 'retired' | 'compromised';\n\n/**\n * Constructor options for {@link TenantKey}. Each field is optional and mirrors\n * a persisted column; date fields also accept the serialized forms accepted by\n * `new Date()` so hydrated rows coerce cleanly.\n */\nexport interface TenantKeyOptions extends SmrtObjectOptions {\n tenantId?: string;\n wrappedKey?: string;\n amkKeyId?: string;\n status?: TenantKeyStatus;\n version?: number;\n rotateAfter?: Date | string | number | null;\n retiredAt?: Date | string | number | null;\n}\n\n/**\n * TenantKey tracks the per-tenant Data Encryption Keys (TDEKs).\n *\n * Each tenant has one or more TDEKs stored in wrapped form. The wrapped key\n * can only be decrypted using the Application Master Key (AMK).\n *\n * **Key Lifecycle**:\n * 1. `active` - Current key used for encryption\n * 2. `rotating` - Transitional state during rotation\n * 3. `retired` - Old key kept for decryption of existing secrets\n * 4. `compromised` - Key marked as compromised, should not be used\n *\n * **Note**: This model is NOT tenant-scoped itself because it tracks\n * keys FOR tenants, not secrets owned BY tenants.\n *\n * @example\n * ```typescript\n * // Get active key for a tenant\n * const key = await tenantKeys.get({\n * tenantId: 'tenant-123',\n * status: 'active'\n * });\n *\n * // List all key versions for a tenant\n * const versions = await tenantKeys.list({\n * where: { tenantId: 'tenant-123' },\n * orderBy: 'version DESC'\n * });\n * ```\n */\n// Intentional exception to standards.md §7 (`@TenantScoped({ mode: 'optional' })`):\n// `TenantKey` is deliberately NOT tenant-scoped — neither via the decorator nor via\n// `tenantScoped: true` on `@smrt()`. The row carries a `tenantId` column because each\n// TDEK belongs to a tenant, but the model itself must remain queryable across tenants\n// (e.g. cross-tenant key-rotation tooling, AMK rewrap jobs, super-admin auditing).\n// Adding the tenancy interceptor here would silently filter out keys the rotation\n// service needs to inspect. See `packages/secrets/CLAUDE.md` \"Known exceptions to\n// monorepo standards\" for the full rationale.\n@smrt({\n // NOT tenant-scoped - this model tracks keys FOR tenants\n api: { include: [] }, // No API exposure\n mcp: { include: [] }, // No MCP exposure\n // CLI runs in-process for key-rotation tooling and audit; HTTP exposure is\n // intentionally excluded, so skipApiCheck acknowledges the divergence.\n cli: { include: ['list', 'get'], skipApiCheck: true },\n})\nexport class TenantKey extends SmrtObject {\n /**\n * Tenant ID this key belongs to\n */\n tenantId: string = '';\n\n /**\n * Wrapped key data (format: wrappedKey:iv:authTag)\n */\n wrappedKey: string = '';\n\n /**\n * ID of the AMK used to wrap this key\n */\n amkKeyId: string = '';\n\n /**\n * Current status of the key\n */\n status: TenantKeyStatus = 'active';\n\n /**\n * Version number (increments on rotation)\n */\n version: number = 1;\n\n /**\n * Recommended rotation date\n */\n rotateAfter: Date | null = null;\n\n /**\n * When the key was retired (if applicable)\n */\n retiredAt: Date | null = null;\n\n constructor(options: TenantKeyOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.wrappedKey !== undefined) this.wrappedKey = options.wrappedKey;\n if (options.amkKeyId !== undefined) this.amkKeyId = options.amkKeyId;\n if (options.status !== undefined) this.status = options.status;\n if (options.version !== undefined) this.version = options.version;\n if (options.rotateAfter !== undefined) {\n this.rotateAfter =\n options.rotateAfter instanceof Date\n ? options.rotateAfter\n : options.rotateAfter\n ? new Date(options.rotateAfter)\n : null;\n }\n if (options.retiredAt !== undefined) {\n this.retiredAt =\n options.retiredAt instanceof Date\n ? options.retiredAt\n : options.retiredAt\n ? new Date(options.retiredAt)\n : null;\n }\n }\n\n /**\n * Check if this key is currently active\n */\n isActive(): boolean {\n return this.status === 'active';\n }\n\n /**\n * Check if this key needs rotation\n */\n needsRotation(): boolean {\n if (!this.rotateAfter) return false;\n return new Date() >= this.rotateAfter;\n }\n\n /**\n * Check if this key is retired\n */\n isRetired(): boolean {\n return this.status === 'retired';\n }\n\n /**\n * Check if this key is compromised\n */\n isCompromised(): boolean {\n return this.status === 'compromised';\n }\n\n /**\n * Check if this key can be used for decryption\n * (active or retired keys can decrypt)\n */\n canDecrypt(): boolean {\n return this.status === 'active' || this.status === 'retired';\n }\n\n /**\n * Check if this key can be used for encryption\n * (only active keys should encrypt)\n */\n canEncrypt(): boolean {\n return this.status === 'active';\n }\n\n /**\n * Mark this key as retired\n */\n retire(): void {\n this.status = 'retired';\n this.retiredAt = new Date();\n }\n\n /**\n * Mark this key as compromised\n */\n markCompromised(): void {\n this.status = 'compromised';\n }\n}\n"],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { i as createAuditEntry, n as Secret, r as SecretAuditLog, t as TenantKey } from "./chunks/TenantKey-
|
|
2
|
-
import { a as SecretAuditLogCollection, i as SecretCollection, n as SecretService, r as TenantKeyCollection, t as SecretKeyDriftError } from "./chunks/SecretService-
|
|
1
|
+
import { i as createAuditEntry, n as Secret, r as SecretAuditLog, t as TenantKey } from "./chunks/TenantKey-DIyrgxt3.js";
|
|
2
|
+
import { a as SecretAuditLogCollection, i as SecretCollection, n as SecretService, r as TenantKeyCollection, t as SecretKeyDriftError } from "./chunks/SecretService-CrkpsGf8.js";
|
|
3
3
|
import { AMKUnavailableError, DecryptionError, EncryptionError, InvalidKeyFormatError, KeyNotFoundError, KeyRotationError, SecretError, StoreNotInitializedError, TenantKeyMissingError } from "@happyvertical/secrets";
|
|
4
4
|
//#region src/index.ts
|
|
5
5
|
/** @internal */
|
package/dist/manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": "1.0.0",
|
|
3
|
-
"timestamp":
|
|
3
|
+
"timestamp": 1784314455311,
|
|
4
4
|
"packageName": "@happyvertical/smrt-secrets",
|
|
5
|
-
"packageVersion": "0.40.
|
|
5
|
+
"packageVersion": "0.40.8",
|
|
6
6
|
"objects": {
|
|
7
7
|
"@happyvertical/smrt-secrets:SecretAuditLogCollection": {
|
|
8
8
|
"name": "secretauditlogcollection",
|
package/dist/models/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { i as createAuditEntry, n as Secret, r as SecretAuditLog, t as TenantKey } from "../chunks/TenantKey-
|
|
1
|
+
import { i as createAuditEntry, n as Secret, r as SecretAuditLog, t as TenantKey } from "../chunks/TenantKey-DIyrgxt3.js";
|
|
2
2
|
export { Secret, SecretAuditLog, TenantKey, createAuditEntry };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as SecretService, t as SecretKeyDriftError } from "../chunks/SecretService-
|
|
1
|
+
import { n as SecretService, t as SecretKeyDriftError } from "../chunks/SecretService-CrkpsGf8.js";
|
|
2
2
|
export { SecretKeyDriftError, SecretService };
|
package/dist/smrt-knowledge.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"generatedAt": "2026-07-
|
|
3
|
+
"generatedAt": "2026-07-17T18:54:18.444Z",
|
|
4
4
|
"packageName": "@happyvertical/smrt-secrets",
|
|
5
|
-
"packageVersion": "0.40.
|
|
5
|
+
"packageVersion": "0.40.8",
|
|
6
6
|
"sourceManifestPath": "dist/manifest.json",
|
|
7
7
|
"agentDocPath": "AGENTS.md",
|
|
8
8
|
"sourceHashes": {
|
|
9
|
-
"manifest": "
|
|
10
|
-
"packageJson": "
|
|
9
|
+
"manifest": "2e5f270d7f38dcd7331c34ba5baa0d7491f37f2ae5a80d717a449ccc3fb3ad16",
|
|
10
|
+
"packageJson": "a749f18dd28cdca0dd66b7e2f811f6c350249b8f80e48a148c46ee3f5ca5be44",
|
|
11
11
|
"agents": "c2be546ffbe79a0df95401c10a60e23f4f31cb8232cb1349fcdb7e5682852d4a"
|
|
12
12
|
},
|
|
13
13
|
"exports": [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@happyvertical/smrt-secrets",
|
|
3
|
-
"version": "0.40.
|
|
3
|
+
"version": "0.40.8",
|
|
4
4
|
"description": "Per-tenant secret management with envelope encryption for SMRT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -31,15 +31,15 @@
|
|
|
31
31
|
"@happyvertical/secrets": "^0.80.2",
|
|
32
32
|
"@happyvertical/sql": "^0.80.2",
|
|
33
33
|
"@happyvertical/utils": "^0.80.2",
|
|
34
|
-
"@happyvertical/smrt-
|
|
35
|
-
"@happyvertical/smrt-
|
|
34
|
+
"@happyvertical/smrt-tenancy": "0.40.8",
|
|
35
|
+
"@happyvertical/smrt-core": "0.40.8"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/node": "24.13.2",
|
|
39
39
|
"typescript": "5.9.3",
|
|
40
40
|
"vite": "8.1.4",
|
|
41
41
|
"vitest": "4.1.10",
|
|
42
|
-
"@happyvertical/smrt-vitest": "0.40.
|
|
42
|
+
"@happyvertical/smrt-vitest": "0.40.8"
|
|
43
43
|
},
|
|
44
44
|
"keywords": [
|
|
45
45
|
"smrt",
|