@happyvertical/smrt-users 0.31.0 → 0.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/AGENTS.md +40 -15
  2. package/dist/chunks/{TerminalAuthService-DoAMQ_yn.js → TerminalAuthService-D5VVPG9e.js} +247 -89
  3. package/dist/chunks/TerminalAuthService-D5VVPG9e.js.map +1 -0
  4. package/dist/chunks/{index-DkoYIvIu.js → index-CitgZk-4.js} +10 -10
  5. package/dist/chunks/{index-DkoYIvIu.js.map → index-CitgZk-4.js.map} +1 -1
  6. package/dist/collections/GroupMemberCollection.d.ts +9 -0
  7. package/dist/collections/GroupMemberCollection.d.ts.map +1 -1
  8. package/dist/collections/SessionCollection.d.ts.map +1 -1
  9. package/dist/index.d.ts +1 -1
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +38 -100
  12. package/dist/index.js.map +1 -1
  13. package/dist/manifest.json +2 -2
  14. package/dist/services/PermissionResolver.d.ts +24 -3
  15. package/dist/services/PermissionResolver.d.ts.map +1 -1
  16. package/dist/services/SessionService.d.ts +42 -5
  17. package/dist/services/SessionService.d.ts.map +1 -1
  18. package/dist/services/index.d.ts +1 -1
  19. package/dist/services/index.d.ts.map +1 -1
  20. package/dist/smrt-knowledge.json +6 -6
  21. package/dist/svelte/components/InviteUserModal.svelte +72 -169
  22. package/dist/svelte/components/InviteUserModal.svelte.d.ts.map +1 -1
  23. package/dist/svelte/components/UserCard.svelte +2 -1
  24. package/dist/svelte/components/UserCard.svelte.d.ts.map +1 -1
  25. package/dist/svelte/components/UserForm.svelte +11 -4
  26. package/dist/svelte/components/UserForm.svelte.d.ts.map +1 -1
  27. package/dist/svelte/components/UserMenu.svelte +100 -25
  28. package/dist/svelte/components/UserMenu.svelte.d.ts +5 -4
  29. package/dist/svelte/components/UserMenu.svelte.d.ts.map +1 -1
  30. package/dist/svelte/components/__tests__/InviteUserModal.test.js +11 -0
  31. package/dist/svelte/components/__tests__/UserMenu.test.js +45 -0
  32. package/dist/svelte/components/__tests__/UserStatus.test.js +36 -0
  33. package/dist/sveltekit/index.d.ts +17 -1
  34. package/dist/sveltekit/index.d.ts.map +1 -1
  35. package/dist/sveltekit.js +37 -9
  36. package/dist/sveltekit.js.map +1 -1
  37. package/package.json +8 -8
  38. package/dist/chunks/TerminalAuthService-DoAMQ_yn.js.map +0 -1
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/models/Group.ts","../src/collections/GroupCollection.ts","../src/models/MagicLinkToken.ts","../src/collections/MagicLinkTokenCollection.ts","../src/models/Role.ts","../src/collections/RoleCollection.ts","../src/services/MagicLinkService.ts","../src/services/PermissionCatalogService.ts","../src/services/PostgresPermissionPolicies.ts","../src/services/TenantService.ts"],"sourcesContent":["/**\n * Group model - team within a tenant\n * @packageDocumentation\n */\n\nimport { foreignKey, SmrtObject, smrt } from '@happyvertical/smrt-core';\n\n/**\n * Group represents a team or department within a tenant.\n *\n * Groups can have roles assigned to them via GroupRole.\n * Users in a group inherit permissions from the group's roles.\n *\n * @example\n * ```typescript\n * const editorsGroup = await groups.create({\n * tenantId: tenant.id,\n * name: 'Editorial Team',\n * slug: 'editorial-team',\n * description: 'Content editors and writers'\n * });\n * await editorsGroup.save();\n * ```\n */\n@smrt({\n // #1400: read-only generated surface — RBAC/identity writes go through\n // permission-gated services, not auth-only generated CRUD.\n api: { include: ['list', 'get'] },\n mcp: { include: ['list', 'get'] },\n cli: true,\n})\nexport class Group extends SmrtObject {\n /**\n * Foreign key to Tenant\n */\n @foreignKey('Tenant', { required: true })\n tenantId?: string;\n\n /**\n * Display name for the group\n */\n name: string = '';\n\n /**\n * Description of the group\n */\n description: string = '';\n\n constructor(options: any = {}) {\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 }\n}\n","/**\n * GroupCollection - Collection manager for Group objects\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { Group } from '../models/Group.js';\n\n/**\n * Collection for managing Group objects\n */\nexport class GroupCollection extends SmrtCollection<Group> {\n static readonly _itemClass = Group;\n\n /**\n * Find all groups in a tenant\n */\n async findByTenant(tenantId: string): Promise<Group[]> {\n return await this.list({\n where: { tenantId },\n orderBy: 'name ASC',\n });\n }\n\n /**\n * Find group by slug within a tenant\n */\n async findBySlug(slug: string, tenantId: string): Promise<Group | null> {\n const results = await this.list({\n where: { slug, tenantId },\n limit: 1,\n });\n return results.length > 0 ? results[0] : null;\n }\n}\n","/**\n * MagicLinkToken model - replay protection for magic link authentication\n *\n * Stores nonces embedded in magic link JWTs to prevent replay attacks.\n * Each nonce can only be used once within its expiry window.\n *\n * @packageDocumentation\n */\n\nimport { field, SmrtObject, smrt } from '@happyvertical/smrt-core';\n\n/**\n * Default magic link token expiry: 10 minutes in seconds\n */\nexport const DEFAULT_TOKEN_EXPIRY_SECONDS = 10 * 60;\n\n@smrt({\n tableName: 'users_magic_link_tokens',\n // Magic link tokens are security-sensitive — no public API\n api: { include: [] },\n mcp: { include: [] },\n cli: true,\n})\nexport class UsersMagicLinkToken extends SmrtObject {\n /** Unique nonce embedded in the signed JWT */\n @field({ required: true, unique: true })\n nonce: string = '';\n\n /** Email address this token was generated for */\n email: string = '';\n\n /** Whether this token has been used */\n used: boolean = false;\n\n /** When this token expires */\n expiresAt: Date = new Date(Date.now() + DEFAULT_TOKEN_EXPIRY_SECONDS * 1000);\n\n constructor(options: any = {}) {\n super(options);\n if (options.nonce !== undefined) this.nonce = options.nonce;\n if (options.email !== undefined) this.email = options.email;\n if (options.used !== undefined) this.used = options.used;\n if (options.expiresAt !== undefined) {\n this.expiresAt =\n options.expiresAt instanceof Date\n ? options.expiresAt\n : new Date(options.expiresAt);\n }\n }\n\n /** Check if the token has expired */\n isExpired(): boolean {\n return new Date() > this.expiresAt;\n }\n\n /** Check if the token is still valid (unused and not expired) */\n isValid(): boolean {\n return !this.used && !this.isExpired();\n }\n}\n\nexport { UsersMagicLinkToken as MagicLinkToken };\n","/**\n * MagicLinkTokenCollection - Collection manager for MagicLinkToken objects\n *\n * Provides nonce lookup and single-use enforcement for magic link authentication.\n *\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { UsersMagicLinkToken } from '../models/MagicLinkToken.js';\n\n/**\n * Collection for managing smrt-users magic link token records\n */\nexport class UsersMagicLinkTokenCollection extends SmrtCollection<UsersMagicLinkToken> {\n static readonly _itemClass = UsersMagicLinkToken;\n\n /**\n * Find a token by its nonce\n */\n async findByNonce(nonce: string): Promise<UsersMagicLinkToken | null> {\n return this.findOne({\n where: { nonce },\n }) as Promise<UsersMagicLinkToken | null>;\n }\n\n /**\n * Atomically mark a token as used (single-use enforcement).\n *\n * Returns true if the nonce was successfully claimed (transitioned from\n * unused to used). Returns false if the nonce was already used, expired,\n * or doesn't exist — preventing race conditions in concurrent verify() calls.\n */\n async markUsed(nonce: string): Promise<boolean> {\n const now = new Date().toISOString();\n const { rowCount } = await this.db.query(\n `UPDATE ${this.tableName}\n SET used = ?, updated_at = ?\n WHERE nonce = ? AND used = ? AND expires_at > ?`,\n true,\n now,\n nonce,\n false,\n now,\n );\n\n return rowCount > 0;\n }\n\n /**\n * Delete expired tokens (cleanup job)\n */\n async deleteExpired(): Promise<number> {\n const now = new Date();\n const tokens = await this.list({\n where: {\n 'expiresAt <': now.toISOString(),\n },\n });\n\n let count = 0;\n for (const token of tokens) {\n await token.delete();\n count++;\n }\n\n return count;\n }\n}\n\nexport { UsersMagicLinkTokenCollection as MagicLinkTokenCollection };\n","/**\n * Role model - permission template\n * @packageDocumentation\n */\n\nimport { foreignKey, SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport type { Role as RoleContract } from '@happyvertical/smrt-types';\n\n/**\n * Role represents a permission template that can be assigned to users.\n *\n * Roles can be:\n * - System roles (tenantId = null): Available to all tenants, cannot be modified\n * - Custom roles (tenantId set): Specific to a tenant, can be customized\n *\n * @example\n * ```typescript\n * // System role (tenantId = null)\n * const adminRole = await roles.create({\n * slug: 'admin',\n * name: 'Administrator',\n * isSystem: true\n * });\n *\n * // Custom tenant role\n * const editorRole = await roles.create({\n * tenantId: tenant.id,\n * slug: 'editor',\n * name: 'Editor',\n * description: 'Can edit content'\n * });\n * ```\n */\n@smrt({\n // #1400: read-only generated surface — RBAC/identity writes go through\n // permission-gated services, not auth-only generated CRUD.\n api: { include: ['list', 'get'] },\n mcp: { include: ['list', 'get'] },\n cli: true,\n})\nexport class Role extends SmrtObject implements RoleContract {\n /**\n * Foreign key to Tenant\n * null = system role available to all tenants\n */\n @foreignKey('Tenant', { nullable: true })\n tenantId?: string | null;\n\n /**\n * Display name for the role\n */\n name: string = '';\n\n /**\n * Description of the role\n */\n description: string = '';\n\n /**\n * Whether this is a system role (cannot be deleted)\n */\n isSystem: boolean = false;\n\n constructor(options: any = {}) {\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.isSystem !== undefined) this.isSystem = options.isSystem;\n }\n\n /**\n * Check if this is a system-wide role\n */\n isSystemRole(): boolean {\n return this.tenantId === null || this.tenantId === undefined;\n }\n\n /**\n * Check if this is a tenant-specific role\n */\n isTenantRole(): boolean {\n return this.tenantId !== null && this.tenantId !== undefined;\n }\n\n /**\n * Check if this role can be deleted.\n * System roles (isSystem = true) cannot be deleted.\n * @returns true if the role can be deleted\n */\n canDelete(): boolean {\n return !this.isSystem;\n }\n\n /**\n * Delete guard - prevents deletion of system roles.\n * Override the delete method to check isSystem flag first.\n */\n async delete(): Promise<void> {\n if (this.isSystem) {\n throw new Error(\n `Cannot delete system role '${this.slug}'. System roles are protected.`,\n );\n }\n return super.delete();\n }\n}\n","/**\n * RoleCollection - Collection manager for Role objects\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { Role } from '../models/Role.js';\nimport { DEFAULT_ROLES } from '../types/index.js';\n\n/**\n * Collection for managing Role objects\n */\nexport class RoleCollection extends SmrtCollection<Role> {\n static readonly _itemClass = Role;\n\n /**\n * Find all system roles (tenantId is null)\n */\n async findSystemRoles(): Promise<Role[]> {\n // Query for roles where tenantId is null\n return await this.query(\n `SELECT * FROM ${this.tableName} WHERE tenant_id IS NULL ORDER BY name ASC`,\n );\n }\n\n /**\n * Find roles available for a tenant (system + tenant-specific)\n */\n async findByTenant(tenantId: string): Promise<Role[]> {\n return await this.query(\n `SELECT * FROM ${this.tableName}\n WHERE tenant_id IS NULL OR tenant_id = ?\n ORDER BY is_system DESC, name ASC`,\n [tenantId],\n );\n }\n\n /**\n * Find tenant-specific roles only\n */\n async findTenantRoles(tenantId: string): Promise<Role[]> {\n return await this.list({\n where: { tenantId },\n orderBy: 'name ASC',\n });\n }\n\n /**\n * Find role by slug within a tenant context\n */\n async findBySlug(slug: string, tenantId?: string): Promise<Role | null> {\n // First check tenant-specific role\n if (tenantId) {\n const tenantRoles = await this.list({\n where: { slug, tenantId },\n limit: 1,\n });\n if (tenantRoles.length > 0) {\n return tenantRoles[0];\n }\n }\n\n // Fall back to system role\n const systemRoles = await this.query(\n `SELECT * FROM ${this.tableName} WHERE slug = ? AND tenant_id IS NULL LIMIT 1`,\n [slug],\n );\n return systemRoles.length > 0 ? systemRoles[0] : null;\n }\n\n /**\n * Seed default system roles\n */\n async seedSystemRoles(): Promise<Role[]> {\n const roles: Role[] = [];\n\n for (const roleDef of DEFAULT_ROLES) {\n // Check if role already exists\n const existing = await this.findBySlug(roleDef.slug);\n if (existing) {\n roles.push(existing);\n continue;\n }\n\n // Create new system role\n const role = await this.create({\n slug: roleDef.slug,\n name: roleDef.name,\n description: roleDef.description,\n tenantId: null,\n isSystem: true,\n });\n await role.save();\n roles.push(role);\n }\n\n return roles;\n }\n}\n","/**\n * MagicLinkService - Passwordless email authentication via signed magic link tokens\n *\n * This service handles token generation and verification for magic link\n * authentication. It is framework-agnostic: it returns tokens and verification\n * results, leaving email delivery and cookie management to the caller.\n *\n * Flow:\n * 1. User enters email → generate(email) → token + expiresAt\n * 2. Caller sends email with link containing the token\n * 3. User clicks link → verify(token) → { email, nonce }\n * 4. Caller creates session (via SessionService or createSessionCookie)\n *\n * @packageDocumentation\n *\n * @example\n * ```typescript\n * const magicLink = await MagicLinkService.create({\n * db: { type: 'postgres', url: process.env.DATABASE_URL },\n * secret: process.env.AUTH_SECRET,\n * });\n *\n * // Generate a magic link token\n * const { token, expiresAt } = await magicLink.generate('user@example.com');\n *\n * // Verify a magic link token (single-use)\n * const { email, nonce } = await magicLink.verify(token);\n * ```\n */\n\nimport type { SmrtClassOptions } from '@happyvertical/smrt-core';\nimport { UsersMagicLinkTokenCollection } from '../collections/MagicLinkTokenCollection.js';\nimport { DEFAULT_TOKEN_EXPIRY_SECONDS } from '../models/MagicLinkToken.js';\nimport { isValidEmail, normalizeEmail } from '../models/User.js';\n\n/**\n * Options for MagicLinkService\n */\nexport interface MagicLinkServiceOptions extends SmrtClassOptions {\n /** Secret used to derive the HMAC signing key (required) */\n secret: string;\n /** Token expiry in seconds (default: 10 minutes) */\n tokenExpiry?: number;\n /** JWT issuer claim (default: 'smrt:magiclink') */\n issuer?: string;\n}\n\n/**\n * Result of generating a magic link token\n */\nexport interface MagicLinkResult {\n /** The signed JWT token */\n token: string;\n /** When the token expires */\n expiresAt: Date;\n}\n\n/**\n * Result of verifying a magic link token\n */\nexport interface MagicLinkVerifyResult {\n /** The email address from the token */\n email: string;\n /** The nonce (for correlation/logging) */\n nonce: string;\n}\n\n/**\n * Error class for magic link authentication failures\n */\nexport class MagicLinkError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'MagicLinkError';\n }\n}\n\n/**\n * MagicLinkService provides passwordless authentication via email magic links.\n *\n * Tokens are HMAC-signed JWTs with embedded nonces for replay protection.\n * The service is framework-agnostic — it does not send emails or set cookies.\n */\nexport class MagicLinkService {\n private tokenCollection!: UsersMagicLinkTokenCollection;\n private signingKey: Uint8Array | null = null;\n private readonly secret: string;\n private readonly tokenExpiry: number;\n private readonly issuer: string;\n private readonly options: MagicLinkServiceOptions;\n\n constructor(options: MagicLinkServiceOptions) {\n if (!options.secret) {\n throw new Error('MagicLinkService requires a secret for token signing');\n }\n this.secret = options.secret;\n this.tokenExpiry = options.tokenExpiry ?? DEFAULT_TOKEN_EXPIRY_SECONDS;\n this.issuer = options.issuer ?? 'smrt:magiclink';\n this.options = options;\n }\n\n /**\n * Initialize collections\n */\n async initialize(): Promise<void> {\n this.tokenCollection = (await (UsersMagicLinkTokenCollection as any).create(\n this.options,\n )) as UsersMagicLinkTokenCollection;\n }\n\n /**\n * Derive the HMAC signing key from the secret\n */\n private async getSigningKey(): Promise<Uint8Array> {\n if (this.signingKey) return this.signingKey;\n\n const encoder = new TextEncoder();\n const data = encoder.encode(`magiclink:${this.secret}`);\n const hashBuffer = await crypto.subtle.digest('SHA-256', data);\n this.signingKey = new Uint8Array(hashBuffer);\n\n return this.signingKey;\n }\n\n /**\n * Generate a magic link token for the given email.\n *\n * Stores a nonce in the database for replay protection.\n * The caller is responsible for emailing the token to the user.\n */\n async generate(email: string): Promise<MagicLinkResult> {\n // Dynamic import to avoid eagerly loading jose at module init\n const { SignJWT } = await import('jose');\n\n const key = await this.getSigningKey();\n const nonce = crypto.randomUUID();\n const normalizedEmail = normalizeEmail(email);\n\n if (!isValidEmail(normalizedEmail)) {\n throw new MagicLinkError('Invalid email address');\n }\n\n const expiresAt = new Date(Date.now() + this.tokenExpiry * 1000);\n\n // Store nonce for replay protection\n await this.tokenCollection.create({\n nonce,\n email: normalizedEmail,\n used: false,\n expiresAt,\n });\n\n // Sign the token\n const token = await new SignJWT({\n email: normalizedEmail,\n nonce,\n })\n .setProtectedHeader({ alg: 'HS256' })\n .setIssuedAt()\n .setExpirationTime(`${this.tokenExpiry}s`)\n .setIssuer(this.issuer)\n .sign(key);\n\n return { token, expiresAt };\n }\n\n /**\n * Verify a magic link token.\n *\n * Checks JWT signature, expiry, and that the nonce hasn't been used.\n * Marks the nonce as used on success (single-use enforcement).\n *\n * @throws {MagicLinkError} If the token is invalid, expired, or already used\n */\n async verify(token: string): Promise<MagicLinkVerifyResult> {\n const { jwtVerify, errors } = await import('jose');\n\n const key = await this.getSigningKey();\n\n // Verify JWT signature and expiry\n let payload: Record<string, unknown>;\n try {\n const result = await jwtVerify(token, key, {\n issuer: this.issuer,\n });\n payload = result.payload as Record<string, unknown>;\n } catch (err) {\n if (err instanceof errors.JWTExpired) {\n throw new MagicLinkError('Token has expired');\n }\n throw new MagicLinkError('Invalid token');\n }\n\n const email = payload.email;\n const nonce = payload.nonce;\n\n // Explicit type validation — JWT payload values are unknown\n if (typeof email !== 'string' || typeof nonce !== 'string') {\n throw new MagicLinkError('Invalid token payload');\n }\n\n // Atomically claim the nonce (single-use enforcement) via a single\n // conditional UPDATE. Only one concurrent verify() call can flip the\n // row from used=false to used=true before the DB expires the nonce.\n const claimed = await this.tokenCollection.markUsed(nonce);\n\n if (!claimed) {\n throw new MagicLinkError('Token has already been used or has expired');\n }\n\n return { email: normalizeEmail(email), nonce };\n }\n\n /**\n * Clean up expired tokens (run periodically)\n */\n async cleanupExpiredTokens(): Promise<number> {\n return this.tokenCollection.deleteExpired();\n }\n\n /**\n * Static factory method\n */\n static async create(\n options: MagicLinkServiceOptions,\n ): Promise<MagicLinkService> {\n const service = new MagicLinkService(options);\n await service.initialize();\n return service;\n }\n}\n","/**\n * Manifest-derived permission cataloging and syncing\n * @packageDocumentation\n */\n\nimport { getPackageConfig } from '@happyvertical/smrt-config';\nimport {\n findManifestEntryByQualifiedName,\n ObjectRegistry,\n type SmartObjectDefinition,\n type SmrtClassOptions,\n} from '@happyvertical/smrt-core';\nimport { PermissionCollection } from '../collections/PermissionCollection.js';\nimport {\n isValidPermissionSlug,\n parsePermissionSlug,\n} from '../models/Permission.js';\n\nexport type PermissionCatalogSource = 'manifest' | 'config' | 'runtime';\n\nexport type PostgresPermissionAction =\n | 'SELECT'\n | 'INSERT'\n | 'UPDATE'\n | 'DELETE';\n\nexport interface PostgresPermissionBinding {\n action: Lowercase<PostgresPermissionAction> | PostgresPermissionAction;\n permission?: string;\n schemaName?: string;\n tableName: string;\n tenantField?: string;\n}\n\nexport interface PermissionDefinition {\n category?: string;\n className?: string;\n collection?: string;\n description?: string;\n name?: string;\n postgres?: {\n bindings?: PostgresPermissionBinding[];\n };\n qualifiedName?: string;\n slug: string;\n source?: PermissionCatalogSource;\n}\n\nexport interface PermissionCatalog {\n customPermissions: PermissionDefinition[];\n manifestPermissions: PermissionDefinition[];\n permissions: PermissionDefinition[];\n runtimePermissions: PermissionDefinition[];\n}\n\nexport interface PermissionCatalogSyncResult {\n catalog: PermissionCatalog;\n created: string[];\n unchanged: string[];\n updated: string[];\n}\n\nexport interface UsersConfig extends Record<string, unknown> {\n permissions?: {\n custom?: PermissionDefinition[];\n postgres?: {\n bindings?: PostgresPermissionBinding[];\n enabled?: boolean;\n };\n };\n}\n\ndeclare global {\n // eslint-disable-next-line no-var\n var __smrtUsersPermissionRegistrations:\n | Map<number, PermissionDefinition[]>\n | undefined;\n // eslint-disable-next-line no-var\n var __smrtUsersPermissionRegistrationCounter: number | undefined;\n}\n\nfunction getRuntimePermissionRegistrations(): Map<\n number,\n PermissionDefinition[]\n> {\n globalThis.__smrtUsersPermissionRegistrations ??= new Map<\n number,\n PermissionDefinition[]\n >();\n return globalThis.__smrtUsersPermissionRegistrations;\n}\n\nfunction toSnakeCase(value: string): string {\n return value\n .replace(/([a-z0-9])([A-Z])/g, '$1_$2')\n .replace(/[-\\s]+/g, '_')\n .toLowerCase();\n}\n\nfunction pluralize(word: string): string {\n if (word.endsWith('y') && !/[aeiou]y$/i.test(word)) {\n return `${word.slice(0, -1)}ies`;\n }\n\n if (\n word.endsWith('s') ||\n word.endsWith('x') ||\n word.endsWith('z') ||\n word.endsWith('ch') ||\n word.endsWith('sh')\n ) {\n return `${word}es`;\n }\n\n return `${word}s`;\n}\n\nfunction deriveCollectionName(className: string): string {\n return pluralize(toSnakeCase(className));\n}\n\nfunction humanizeResource(resource: string): string {\n return resource\n .replace(/[_-]+/g, ' ')\n .replace(/\\b\\w/g, (char) => char.toUpperCase());\n}\n\nfunction capitalize(value: string): string {\n return `${value[0]?.toUpperCase() ?? ''}${value.slice(1)}`;\n}\n\nfunction defaultPermissionName(slug: string): string {\n const parsed = parsePermissionSlug(slug);\n if (!parsed.isValid) {\n return humanizeResource(slug);\n }\n\n return `${capitalize(parsed.action)} ${humanizeResource(parsed.resource)}`;\n}\n\nfunction defaultPermissionDescription(slug: string): string {\n const parsed = parsePermissionSlug(slug);\n if (!parsed.isValid) {\n return `Allows ${slug}`;\n }\n\n return `Allows ${parsed.action} access for ${humanizeResource(parsed.resource).toLowerCase()}`;\n}\n\nfunction isCollectionManifestEntry(objectDef?: SmartObjectDefinition): boolean {\n return (\n objectDef?.extends === 'SmrtCollection' ||\n objectDef?.extendsTypeArg !== undefined\n );\n}\n\ninterface ManifestMethodCandidate {\n isPublic?: boolean;\n name?: string;\n}\n\nfunction getPublicCustomMethodNames(\n methodEntries: ManifestMethodCandidate[],\n standardActions: readonly string[],\n): string[] {\n return Array.from(\n new Set(\n methodEntries\n .filter(\n (method) =>\n Boolean(method?.name) &&\n method?.isPublic === true &&\n !standardActions.includes(method.name!),\n )\n .map((method) => method.name!),\n ),\n );\n}\n\nfunction getCustomMethodExposureNames(\n config: unknown,\n availableCustomMethods: string[],\n): Set<string> {\n if (!config || config === false) {\n return new Set();\n }\n\n if (config === true || typeof config !== 'object') {\n return new Set(availableCustomMethods);\n }\n\n const rawInclude = (config as { include?: string[] }).include;\n const include = Array.isArray(rawInclude) ? [...rawInclude] : undefined;\n const rawExclude = (config as { exclude?: string[] }).exclude;\n const exclude: string[] = Array.isArray(rawExclude) ? [...rawExclude] : [];\n\n if (!include) {\n return new Set(\n availableCustomMethods.filter(\n (methodName) => !exclude.includes(methodName),\n ),\n );\n }\n\n const baseMethods = include.filter((methodName) =>\n availableCustomMethods.includes(methodName),\n );\n\n return new Set(\n baseMethods.filter((methodName) => !exclude.includes(methodName)),\n );\n}\n\nfunction isOperationEnabled(config: unknown, action: string): boolean {\n if (config === false) {\n return false;\n }\n\n if (config && typeof config === 'object') {\n const include = Array.isArray((config as { include?: string[] }).include)\n ? (config as { include?: string[] }).include\n : undefined;\n const rawExclude = (config as { exclude?: string[] }).exclude;\n const exclude: string[] = Array.isArray(rawExclude) ? [...rawExclude] : [];\n\n if (include && !include.includes(action)) {\n return false;\n }\n if (exclude.includes(action)) {\n return false;\n }\n }\n\n return true;\n}\n\nfunction normalizePostgresAction(\n action: PostgresPermissionBinding['action'],\n): PostgresPermissionAction {\n const normalized = action.toUpperCase();\n if (\n normalized === 'SELECT' ||\n normalized === 'INSERT' ||\n normalized === 'UPDATE' ||\n normalized === 'DELETE'\n ) {\n return normalized;\n }\n\n throw new Error(\n `Unsupported Postgres permission action '${action}'. Expected SELECT, INSERT, UPDATE, or DELETE.`,\n );\n}\n\nfunction normalizeBinding(\n binding: PostgresPermissionBinding,\n fallbackPermission: string,\n): PostgresPermissionBinding {\n return {\n action: normalizePostgresAction(binding.action),\n permission: binding.permission || fallbackPermission,\n schemaName: binding.schemaName,\n tableName: binding.tableName,\n tenantField: binding.tenantField,\n };\n}\n\nfunction mergeStringField(\n fieldName:\n | 'category'\n | 'className'\n | 'collection'\n | 'description'\n | 'name'\n | 'qualifiedName',\n existing: PermissionDefinition,\n incoming: PermissionDefinition,\n slug: string,\n): void {\n const existingValue = existing[fieldName];\n const incomingValue = incoming[fieldName];\n\n if (!incomingValue) {\n return;\n }\n\n if (!existingValue) {\n existing[fieldName] = incomingValue;\n return;\n }\n\n if (existingValue !== incomingValue) {\n throw new Error(\n `Conflicting permission metadata for '${slug}' field '${fieldName}': '${existingValue}' !== '${incomingValue}'`,\n );\n }\n}\n\nfunction mergeBindings(\n existing: PermissionDefinition,\n incoming: PermissionDefinition,\n): void {\n const existingBindings = existing.postgres?.bindings ?? [];\n const incomingBindings = incoming.postgres?.bindings ?? [];\n if (incomingBindings.length === 0) {\n return;\n }\n\n const seen = new Set(\n existingBindings.map((binding) =>\n [\n binding.permission,\n binding.action,\n binding.schemaName ?? '',\n binding.tableName,\n binding.tenantField ?? '',\n ].join('|'),\n ),\n );\n\n const mergedBindings = [...existingBindings];\n for (const binding of incomingBindings) {\n const normalized = normalizeBinding(binding, incoming.slug);\n const key = [\n normalized.permission,\n normalized.action,\n normalized.schemaName ?? '',\n normalized.tableName,\n normalized.tenantField ?? '',\n ].join('|');\n if (!seen.has(key)) {\n seen.add(key);\n mergedBindings.push(normalized);\n }\n }\n\n existing.postgres = {\n bindings: mergedBindings,\n };\n}\n\nfunction normalizeDefinition(\n definition: PermissionDefinition,\n source: PermissionCatalogSource,\n): PermissionDefinition {\n if (!definition.slug || !isValidPermissionSlug(definition.slug.trim())) {\n throw new Error(\n `Invalid permission slug '${definition.slug}'. Expected 'resource.action'.`,\n );\n }\n\n const slug = definition.slug.trim();\n return {\n category: definition.category ?? parsePermissionSlug(slug).resource,\n className: definition.className,\n collection: definition.collection,\n description: definition.description ?? defaultPermissionDescription(slug),\n name: definition.name ?? defaultPermissionName(slug),\n postgres: definition.postgres?.bindings\n ? {\n bindings: definition.postgres.bindings.map((binding) =>\n normalizeBinding(binding, slug),\n ),\n }\n : undefined,\n qualifiedName: definition.qualifiedName,\n slug,\n source,\n };\n}\n\nfunction mergeDefinitionSet(\n current: Map<string, PermissionDefinition>,\n incomingDefinitions: PermissionDefinition[],\n source: PermissionCatalogSource,\n): void {\n for (const rawDefinition of incomingDefinitions) {\n const definition = normalizeDefinition(rawDefinition, source);\n const existing = current.get(definition.slug);\n\n if (!existing) {\n current.set(definition.slug, definition);\n continue;\n }\n\n mergeStringField('category', existing, definition, definition.slug);\n mergeStringField('className', existing, definition, definition.slug);\n mergeStringField('collection', existing, definition, definition.slug);\n mergeStringField('description', existing, definition, definition.slug);\n mergeStringField('name', existing, definition, definition.slug);\n mergeStringField('qualifiedName', existing, definition, definition.slug);\n mergeBindings(existing, definition);\n }\n}\n\nexport function registerPermissionDefinitions(\n definitions: PermissionDefinition[],\n): () => void {\n globalThis.__smrtUsersPermissionRegistrationCounter =\n (globalThis.__smrtUsersPermissionRegistrationCounter ?? 0) + 1;\n const registrationId = globalThis.__smrtUsersPermissionRegistrationCounter;\n getRuntimePermissionRegistrations().set(registrationId, definitions);\n\n return () => {\n getRuntimePermissionRegistrations().delete(registrationId);\n };\n}\n\nexport class PermissionCatalogService {\n constructor(private readonly options: SmrtClassOptions = {}) {}\n\n getUsersConfig(): UsersConfig {\n return getPackageConfig<UsersConfig>('users', {});\n }\n\n getRuntimePermissionDefinitions(): PermissionDefinition[] {\n return Array.from(getRuntimePermissionRegistrations().values()).flat();\n }\n\n getCustomPermissionDefinitions(): PermissionDefinition[] {\n return this.getUsersConfig().permissions?.custom ?? [];\n }\n\n getCatalog(): PermissionCatalog {\n const manifestPermissions = this.getManifestPermissionDefinitions();\n const customPermissions = this.getCustomPermissionDefinitions();\n const runtimePermissions = this.getRuntimePermissionDefinitions();\n\n const merged = new Map<string, PermissionDefinition>();\n mergeDefinitionSet(merged, manifestPermissions, 'manifest');\n mergeDefinitionSet(merged, customPermissions, 'config');\n mergeDefinitionSet(merged, runtimePermissions, 'runtime');\n\n return {\n customPermissions: customPermissions.map((definition) =>\n normalizeDefinition(definition, 'config'),\n ),\n manifestPermissions: manifestPermissions.map((definition) =>\n normalizeDefinition(definition, 'manifest'),\n ),\n permissions: Array.from(merged.values()).sort((left, right) =>\n left.slug.localeCompare(right.slug),\n ),\n runtimePermissions: runtimePermissions.map((definition) =>\n normalizeDefinition(definition, 'runtime'),\n ),\n };\n }\n\n async syncPermissionCatalog(): Promise<PermissionCatalogSyncResult> {\n const catalog = this.getCatalog();\n const permissions = await PermissionCollection.create(this.options);\n\n const created: string[] = [];\n const unchanged: string[] = [];\n const updated: string[] = [];\n\n for (const definition of catalog.permissions) {\n const existing = await permissions.findBySlug(definition.slug);\n if (!existing) {\n const permission = await permissions.create({\n category:\n definition.category ??\n parsePermissionSlug(definition.slug).resource,\n description: definition.description ?? '',\n name: definition.name ?? definition.slug,\n slug: definition.slug,\n });\n await permission.save();\n created.push(definition.slug);\n continue;\n }\n\n const nextName = definition.name ?? existing.name;\n const nextDescription = definition.description ?? existing.description;\n const nextCategory = definition.category ?? existing.category;\n\n if (\n existing.name === nextName &&\n existing.description === nextDescription &&\n existing.category === nextCategory\n ) {\n unchanged.push(definition.slug);\n continue;\n }\n\n existing.name = nextName;\n existing.description = nextDescription;\n existing.category = nextCategory;\n await existing.save();\n updated.push(definition.slug);\n }\n\n return {\n catalog,\n created,\n unchanged,\n updated,\n };\n }\n\n private getManifestPermissionDefinitions(): PermissionDefinition[] {\n const standardActions = ['list', 'get', 'create', 'update', 'delete'];\n const definitions = new Map<string, PermissionDefinition>();\n\n for (const metadata of ObjectRegistry.getAllObjectMetadata()) {\n const registered =\n ObjectRegistry.getClassByConstructor(metadata.constructor as any) ??\n ObjectRegistry.getClass(metadata.name);\n const manifestEntry = registered?.qualifiedName\n ? findManifestEntryByQualifiedName(registered.qualifiedName)\n : undefined;\n\n if (isCollectionManifestEntry(manifestEntry)) {\n continue;\n }\n\n const className = metadata.name;\n const qualifiedName = registered?.qualifiedName;\n const objectConfig = manifestEntry?.decoratorConfig ?? metadata.config;\n const rawCollection = (\n objectConfig as { collection?: unknown } | undefined\n )?.collection;\n const configuredCollection =\n typeof rawCollection === 'string' && rawCollection.length > 0\n ? rawCollection\n : undefined;\n const collection =\n configuredCollection ??\n manifestEntry?.collection ??\n deriveCollectionName(metadata.name);\n\n const readExposed =\n isOperationEnabled(objectConfig.api, 'list') ||\n isOperationEnabled(objectConfig.api, 'get') ||\n isOperationEnabled(objectConfig.cli, 'list') ||\n isOperationEnabled(objectConfig.cli, 'get') ||\n isOperationEnabled(objectConfig.mcp, 'list') ||\n isOperationEnabled(objectConfig.mcp, 'get');\n if (readExposed) {\n definitions.set(`${collection}.read`, {\n className,\n collection,\n qualifiedName,\n slug: `${collection}.read`,\n });\n }\n\n for (const action of ['create', 'update', 'delete'] as const) {\n const exposed =\n isOperationEnabled(objectConfig.api, action) ||\n isOperationEnabled(objectConfig.cli, action) ||\n isOperationEnabled(objectConfig.mcp, action);\n if (!exposed) {\n continue;\n }\n\n definitions.set(`${collection}.${action}`, {\n className,\n collection,\n qualifiedName,\n slug: `${collection}.${action}`,\n });\n }\n\n const methodEntries = manifestEntry?.methods\n ? Object.values(manifestEntry.methods)\n : Array.from(metadata.methods.values());\n const publicCustomMethodNames = getPublicCustomMethodNames(\n methodEntries,\n standardActions,\n );\n const customApiMethods = new Set<string>();\n const customCliMethods = getCustomMethodExposureNames(\n objectConfig.cli,\n publicCustomMethodNames,\n );\n const customMcpMethods = getCustomMethodExposureNames(\n objectConfig.mcp,\n publicCustomMethodNames,\n );\n\n for (const methodName of publicCustomMethodNames) {\n if (isOperationEnabled(objectConfig.api, methodName)) {\n customApiMethods.add(methodName);\n }\n }\n\n const customMethods = new Set<string>([\n ...customApiMethods,\n ...customCliMethods,\n ...customMcpMethods,\n ]);\n\n for (const methodName of customMethods) {\n definitions.set(`${collection}.${methodName}`, {\n className,\n collection,\n description: `Allows ${methodName} on ${humanizeResource(collection).toLowerCase()}`,\n name: `${capitalize(methodName)} ${humanizeResource(collection)}`,\n qualifiedName,\n slug: `${collection}.${methodName}`,\n });\n }\n }\n\n return Array.from(definitions.values()).sort((left, right) =>\n left.slug.localeCompare(right.slug),\n );\n }\n\n static create(options: SmrtClassOptions = {}): PermissionCatalogService {\n return new PermissionCatalogService(options);\n }\n}\n\nexport async function syncPermissionCatalog(\n options: SmrtClassOptions = {},\n): Promise<PermissionCatalogSyncResult> {\n return PermissionCatalogService.create(options).syncPermissionCatalog();\n}\n","/**\n * Postgres policy generation and application for SMRT permissions\n * @packageDocumentation\n */\n\nimport { createHash } from 'node:crypto';\nimport {\n findManifestEntryByQualifiedName,\n ObjectRegistry,\n type SmrtClassOptions,\n} from '@happyvertical/smrt-core';\nimport { PermissionCollection } from '../collections/PermissionCollection.js';\nimport {\n PermissionCatalogService,\n type PostgresPermissionAction,\n type PostgresPermissionBinding,\n} from './PermissionCatalogService.js';\n\ninterface QueryableDatabase {\n query: (sql: string, ...params: unknown[]) => Promise<unknown>;\n url?: string;\n}\n\ntype DatabaseConfig = SmrtClassOptions['db'] | SmrtClassOptions['persistence'];\n\ninterface TablePolicyTarget {\n actions: Map<PostgresPermissionAction, Set<string>>;\n className?: string;\n collection?: string;\n qualifiedName?: string;\n schemaName: string;\n tableName: string;\n tenantField: string;\n}\n\nexport interface PostgresPermissionPolicyReportItem {\n className?: string;\n collection?: string;\n qualifiedName?: string;\n reason: string;\n schemaName?: string;\n tableName?: string;\n}\n\nexport interface PostgresPermissionPolicyTarget {\n actions: Partial<Record<PostgresPermissionAction, string[]>>;\n className?: string;\n collection?: string;\n qualifiedName?: string;\n schemaName: string;\n tableName: string;\n tenantField: string;\n}\n\nexport interface GeneratePostgresPermissionSqlResult {\n bindings: PostgresPermissionBinding[];\n skipped: PostgresPermissionPolicyReportItem[];\n sql: string;\n statements: string[];\n targets: PostgresPermissionPolicyTarget[];\n}\n\nfunction toSnakeCase(value: string): string {\n return value\n .replace(/([a-z0-9])([A-Z])/g, '$1_$2')\n .replace(/[-\\s]+/g, '_')\n .toLowerCase();\n}\n\nfunction quoteIdent(identifier: string): string {\n return `\"${identifier.replaceAll('\"', '\"\"')}\"`;\n}\n\nfunction quoteLiteral(value: string): string {\n return `'${value.replaceAll(\"'\", \"''\")}'`;\n}\n\nfunction isProbablyPostgres(\n configDb: DatabaseConfig,\n database: QueryableDatabase,\n): boolean {\n if (\n configDb &&\n typeof configDb === 'object' &&\n !('query' in configDb) &&\n 'type' in configDb &&\n configDb.type === 'postgres'\n ) {\n return true;\n }\n\n if (typeof database.url === 'string' && database.url.startsWith('postgres')) {\n return true;\n }\n\n return (database.constructor?.name || '').toLowerCase().includes('postgres');\n}\n\nfunction normalizePostgresPermissionAction(\n action: PostgresPermissionBinding['action'],\n): PostgresPermissionAction {\n const normalized = action.toUpperCase();\n if (\n normalized === 'SELECT' ||\n normalized === 'INSERT' ||\n normalized === 'UPDATE' ||\n normalized === 'DELETE'\n ) {\n return normalized;\n }\n\n throw new Error(\n `Invalid Postgres permission binding action \"${action}\". Expected one of SELECT, INSERT, UPDATE, DELETE.`,\n );\n}\n\nfunction normalizePostgresPermissionBinding(\n binding: PostgresPermissionBinding,\n fallbackPermission?: string,\n): PostgresPermissionBinding {\n const tableName = binding.tableName?.trim();\n if (!tableName) {\n throw new Error(\n 'Postgres permission binding is missing a tableName value.',\n );\n }\n\n const permission = binding.permission ?? fallbackPermission;\n if (!permission) {\n throw new Error(\n 'Postgres permission binding is missing a permission value.',\n );\n }\n\n return {\n action: normalizePostgresPermissionAction(binding.action),\n permission,\n schemaName: binding.schemaName,\n tableName,\n tenantField: binding.tenantField,\n };\n}\n\nfunction parseTableReference(\n binding: Pick<PostgresPermissionBinding, 'schemaName' | 'tableName'>,\n): { schemaName: string; tableName: string } {\n if (binding.tableName.includes('.')) {\n const [schemaName, tableName] = binding.tableName.split('.', 2);\n return {\n schemaName,\n tableName,\n };\n }\n\n return {\n schemaName: binding.schemaName ?? 'public',\n tableName: binding.tableName,\n };\n}\n\nfunction buildPolicyName(\n tableName: string,\n action: PostgresPermissionAction,\n): string {\n const actionSegment = action.toLowerCase();\n const hash = createHash('sha1')\n .update(`${tableName}:${action}`)\n .digest('hex')\n .slice(0, 8);\n const sanitizedTable = tableName\n .replace(/[^a-zA-Z0-9_]+/g, '_')\n .replace(/^_+|_+$/g, '');\n const prefix = 'smrt_';\n const separatorLength = 2;\n const maxTableSegmentLength =\n 63 - prefix.length - actionSegment.length - hash.length - separatorLength;\n const tableSegment = (sanitizedTable || 'table').slice(\n 0,\n Math.max(maxTableSegmentLength, 1),\n );\n\n return `${prefix}${tableSegment}_${actionSegment}_${hash}`;\n}\n\nfunction buildPermissionExpression(permissionSlugs: string[]): string {\n if (permissionSlugs.length === 0) {\n return 'FALSE';\n }\n\n return permissionSlugs\n .map((permission) => `smrt_has_permission(${quoteLiteral(permission)})`)\n .join(' OR ');\n}\n\nfunction buildTenantMatchExpression(tenantField: string): string {\n return `${quoteIdent(tenantField)}::text = smrt_current_tenant_id()`;\n}\n\nfunction buildSelectPolicySql(\n target: TablePolicyTarget,\n permissions: string[],\n): string[] {\n const qualifiedTable = `${quoteIdent(target.schemaName)}.${quoteIdent(target.tableName)}`;\n const policyName = buildPolicyName(target.tableName, 'SELECT');\n const condition = `smrt_rls_bypass() OR ((${buildTenantMatchExpression(target.tenantField)}) AND (${buildPermissionExpression(permissions)}))`;\n\n return [\n `DROP POLICY IF EXISTS ${quoteIdent(policyName)} ON ${qualifiedTable}`,\n `CREATE POLICY ${quoteIdent(policyName)} ON ${qualifiedTable} FOR SELECT USING (${condition})`,\n ];\n}\n\nfunction buildInsertPolicySql(\n target: TablePolicyTarget,\n permissions: string[],\n): string[] {\n const qualifiedTable = `${quoteIdent(target.schemaName)}.${quoteIdent(target.tableName)}`;\n const policyName = buildPolicyName(target.tableName, 'INSERT');\n const condition = `smrt_rls_bypass() OR ((${buildTenantMatchExpression(target.tenantField)}) AND (${buildPermissionExpression(permissions)}))`;\n\n return [\n `DROP POLICY IF EXISTS ${quoteIdent(policyName)} ON ${qualifiedTable}`,\n `CREATE POLICY ${quoteIdent(policyName)} ON ${qualifiedTable} FOR INSERT WITH CHECK (${condition})`,\n ];\n}\n\nfunction buildUpdatePolicySql(\n target: TablePolicyTarget,\n permissions: string[],\n): string[] {\n const qualifiedTable = `${quoteIdent(target.schemaName)}.${quoteIdent(target.tableName)}`;\n const policyName = buildPolicyName(target.tableName, 'UPDATE');\n const condition = `smrt_rls_bypass() OR ((${buildTenantMatchExpression(target.tenantField)}) AND (${buildPermissionExpression(permissions)}))`;\n\n return [\n `DROP POLICY IF EXISTS ${quoteIdent(policyName)} ON ${qualifiedTable}`,\n `CREATE POLICY ${quoteIdent(policyName)} ON ${qualifiedTable} FOR UPDATE USING (${condition}) WITH CHECK (${condition})`,\n ];\n}\n\nfunction buildDeletePolicySql(\n target: TablePolicyTarget,\n permissions: string[],\n): string[] {\n const qualifiedTable = `${quoteIdent(target.schemaName)}.${quoteIdent(target.tableName)}`;\n const policyName = buildPolicyName(target.tableName, 'DELETE');\n const condition = `smrt_rls_bypass() OR ((${buildTenantMatchExpression(target.tenantField)}) AND (${buildPermissionExpression(permissions)}))`;\n\n return [\n `DROP POLICY IF EXISTS ${quoteIdent(policyName)} ON ${qualifiedTable}`,\n `CREATE POLICY ${quoteIdent(policyName)} ON ${qualifiedTable} FOR DELETE USING (${condition})`,\n ];\n}\n\nfunction buildHelperStatements(): string[] {\n return [\n [\n 'CREATE OR REPLACE FUNCTION smrt_rls_bypass()',\n 'RETURNS boolean',\n 'LANGUAGE sql',\n 'STABLE',\n 'AS $$',\n \" SELECT COALESCE(NULLIF(current_setting('smrt.system_context', true), ''), 'false')::boolean\",\n \" OR COALESCE(NULLIF(current_setting('smrt.super_admin_bypass', true), ''), 'false')::boolean\",\n '$$',\n ].join('\\n'),\n [\n 'CREATE OR REPLACE FUNCTION smrt_current_tenant_id()',\n 'RETURNS text',\n 'LANGUAGE sql',\n 'STABLE',\n 'AS $$',\n \" SELECT NULLIF(current_setting('smrt.tenant_id', true), '')\",\n '$$',\n ].join('\\n'),\n [\n 'CREATE OR REPLACE FUNCTION smrt_has_permission(required_permission text)',\n 'RETURNS boolean',\n 'LANGUAGE sql',\n 'STABLE',\n 'AS $$',\n ' SELECT smrt_rls_bypass()',\n \" OR jsonb_exists(COALESCE(NULLIF(current_setting('smrt.permissions', true), ''), '[]')::jsonb, required_permission)\",\n '$$',\n ].join('\\n'),\n ];\n}\n\nfunction addBindingToTarget(\n targets: Map<string, TablePolicyTarget>,\n binding: PostgresPermissionBinding,\n source: Pick<\n TablePolicyTarget,\n 'className' | 'collection' | 'qualifiedName'\n > = {},\n): void {\n const { schemaName, tableName } = parseTableReference(binding);\n const targetKey = `${schemaName}.${tableName}`;\n const existing = targets.get(targetKey);\n const tenantField = binding.tenantField ?? 'tenant_id';\n\n if (existing && existing.tenantField !== tenantField) {\n throw new Error(\n `Conflicting tenant fields for table '${targetKey}': '${existing.tenantField}' !== '${tenantField}'`,\n );\n }\n\n const target = existing ?? {\n actions: new Map<PostgresPermissionAction, Set<string>>(),\n ...source,\n schemaName,\n tableName,\n tenantField,\n };\n const action = normalizePostgresPermissionAction(binding.action);\n const permissions = target.actions.get(action) ?? new Set<string>();\n if (binding.permission) {\n permissions.add(binding.permission);\n }\n target.actions.set(action, permissions);\n targets.set(targetKey, target);\n}\n\nexport function generatePostgresPermissionSql(\n options: SmrtClassOptions = {},\n): GeneratePostgresPermissionSqlResult {\n const catalogService = PermissionCatalogService.create(options);\n const catalog = catalogService.getCatalog();\n const config = catalogService.getUsersConfig();\n const candidateTargets = new Map<string, TablePolicyTarget>();\n const skipped: PostgresPermissionPolicyReportItem[] = [];\n\n const autoCandidates = new Map<\n string,\n Array<{\n className?: string;\n collection: string;\n qualifiedName?: string;\n schemaName: string;\n tableName: string;\n tenantField: string;\n }>\n >();\n\n for (const metadata of ObjectRegistry.getAllObjectMetadata()) {\n const registered =\n ObjectRegistry.getClassByConstructor(metadata.constructor as any) ??\n ObjectRegistry.getClass(metadata.name);\n const tenantScoped = registered?.tenantScopedConfig;\n const manifestEntry = registered?.qualifiedName\n ? findManifestEntryByQualifiedName(registered.qualifiedName)\n : undefined;\n\n if (!tenantScoped) {\n skipped.push({\n className: metadata.name,\n qualifiedName: registered?.qualifiedName,\n reason: 'not tenant-scoped',\n });\n continue;\n }\n\n if (tenantScoped.mode !== 'required') {\n skipped.push({\n className: metadata.name,\n qualifiedName: registered?.qualifiedName,\n reason: `tenant mode '${tenantScoped.mode}' is not supported for automatic Postgres RLS generation`,\n });\n continue;\n }\n\n const rawTableName =\n registered?.schema?.tableName ?? manifestEntry?.schema?.tableName;\n if (!rawTableName) {\n skipped.push({\n className: metadata.name,\n qualifiedName: registered?.qualifiedName,\n reason: 'no schema table name available',\n });\n continue;\n }\n\n const parsedTable = parseTableReference({\n tableName: rawTableName,\n });\n const tableKey = `${parsedTable.schemaName}.${parsedTable.tableName}`;\n const objectConfig = manifestEntry?.decoratorConfig ?? metadata.config;\n const rawCollection = (objectConfig as { collection?: unknown } | undefined)\n ?.collection;\n const configuredCollection =\n typeof rawCollection === 'string' && rawCollection.length > 0\n ? rawCollection\n : undefined;\n const collection =\n configuredCollection ??\n manifestEntry?.collection ??\n `${toSnakeCase(metadata.name)}s`;\n\n const entries = autoCandidates.get(tableKey) ?? [];\n entries.push({\n className: metadata.name,\n collection,\n qualifiedName: registered?.qualifiedName,\n schemaName: parsedTable.schemaName,\n tableName: parsedTable.tableName,\n tenantField: toSnakeCase(tenantScoped.field),\n });\n autoCandidates.set(tableKey, entries);\n }\n\n for (const [tableKey, entries] of autoCandidates) {\n if (entries.length > 1) {\n for (const entry of entries) {\n skipped.push({\n className: entry.className,\n collection: entry.collection,\n qualifiedName: entry.qualifiedName,\n reason: `table '${tableKey}' is shared by multiple objects, so automatic policy generation was skipped`,\n schemaName: entry.schemaName,\n tableName: entry.tableName,\n });\n }\n continue;\n }\n\n const entry = entries[0];\n addBindingToTarget(\n candidateTargets,\n {\n action: 'SELECT',\n permission: `${entry.collection}.read`,\n schemaName: entry.schemaName,\n tableName: entry.tableName,\n tenantField: entry.tenantField,\n },\n entry,\n );\n addBindingToTarget(\n candidateTargets,\n {\n action: 'INSERT',\n permission: `${entry.collection}.create`,\n schemaName: entry.schemaName,\n tableName: entry.tableName,\n tenantField: entry.tenantField,\n },\n entry,\n );\n addBindingToTarget(\n candidateTargets,\n {\n action: 'UPDATE',\n permission: `${entry.collection}.update`,\n schemaName: entry.schemaName,\n tableName: entry.tableName,\n tenantField: entry.tenantField,\n },\n entry,\n );\n addBindingToTarget(\n candidateTargets,\n {\n action: 'DELETE',\n permission: `${entry.collection}.delete`,\n schemaName: entry.schemaName,\n tableName: entry.tableName,\n tenantField: entry.tenantField,\n },\n entry,\n );\n }\n\n const explicitBindings: PostgresPermissionBinding[] = [];\n for (const definition of catalog.permissions) {\n for (const binding of definition.postgres?.bindings ?? []) {\n explicitBindings.push(\n normalizePostgresPermissionBinding(binding, definition.slug),\n );\n }\n }\n for (const binding of config.permissions?.postgres?.bindings ?? []) {\n explicitBindings.push(normalizePostgresPermissionBinding(binding));\n }\n\n for (const binding of explicitBindings) {\n addBindingToTarget(candidateTargets, binding);\n }\n\n const statements = [...buildHelperStatements()];\n const targets = Array.from(candidateTargets.values())\n .sort((left, right) =>\n `${left.schemaName}.${left.tableName}`.localeCompare(\n `${right.schemaName}.${right.tableName}`,\n ),\n )\n .map((target) => ({\n actions: Object.fromEntries(\n Array.from(target.actions.entries()).map(([action, permissions]) => [\n action,\n Array.from(permissions).sort(),\n ]),\n ) as Partial<Record<PostgresPermissionAction, string[]>>,\n className: target.className,\n collection: target.collection,\n qualifiedName: target.qualifiedName,\n schemaName: target.schemaName,\n tableName: target.tableName,\n tenantField: target.tenantField,\n }));\n\n for (const target of Array.from(candidateTargets.values())) {\n const qualifiedTable = `${quoteIdent(target.schemaName)}.${quoteIdent(target.tableName)}`;\n statements.push(`ALTER TABLE ${qualifiedTable} ENABLE ROW LEVEL SECURITY`);\n statements.push(`ALTER TABLE ${qualifiedTable} FORCE ROW LEVEL SECURITY`);\n\n const selectPermissions = Array.from(\n target.actions.get('SELECT') ?? [],\n ).sort();\n const insertPermissions = Array.from(\n target.actions.get('INSERT') ?? [],\n ).sort();\n const updatePermissions = Array.from(\n target.actions.get('UPDATE') ?? [],\n ).sort();\n const deletePermissions = Array.from(\n target.actions.get('DELETE') ?? [],\n ).sort();\n\n if (selectPermissions.length > 0) {\n statements.push(...buildSelectPolicySql(target, selectPermissions));\n }\n if (insertPermissions.length > 0) {\n statements.push(...buildInsertPolicySql(target, insertPermissions));\n }\n if (updatePermissions.length > 0) {\n statements.push(...buildUpdatePolicySql(target, updatePermissions));\n }\n if (deletePermissions.length > 0) {\n statements.push(...buildDeletePolicySql(target, deletePermissions));\n }\n }\n\n return {\n bindings: explicitBindings,\n skipped,\n sql: `${statements.join(';\\n')};\\n`,\n statements,\n targets,\n };\n}\n\nexport async function applyPostgresPermissionPolicies(\n options: SmrtClassOptions = {},\n): Promise<GeneratePostgresPermissionSqlResult> {\n const permissions = await PermissionCollection.create(options);\n const databaseOptions = options.db ?? options.persistence;\n if (\n !isProbablyPostgres(databaseOptions, permissions.db as QueryableDatabase)\n ) {\n throw new Error(\n 'applyPostgresPermissionPolicies() requires a Postgres database connection.',\n );\n }\n\n const result = generatePostgresPermissionSql(options);\n for (const statement of result.statements) {\n try {\n await permissions.db.query(statement);\n } catch (error) {\n throw new Error(\n `Failed to apply Postgres permission policy statement:\\n${statement}`,\n {\n cause: error,\n },\n );\n }\n }\n\n return result;\n}\n","/**\n * TenantService - Manages tenant creation with configurable policies\n * @packageDocumentation\n */\n\nimport type { SmrtClassOptions } from '@happyvertical/smrt-core';\nimport { MembershipCollection } from '../collections/MembershipCollection.js';\nimport { RoleCollection } from '../collections/RoleCollection.js';\nimport { TenantCollection } from '../collections/TenantCollection.js';\nimport type { Membership } from '../models/Membership.js';\nimport type { Tenant } from '../models/Tenant.js';\nimport {\n DEFAULT_ROLE_SLUGS,\n DEFAULT_TENANT_POLICY,\n MembershipStatus,\n type TenantPolicy,\n} from '../types/index.js';\n\n/**\n * Result of tenant creation with ownership\n */\nexport interface TenantWithOwnershipResult {\n tenant: Tenant;\n membership: Membership;\n}\n\n/**\n * Result of ensureTenantForUser\n */\nexport interface EnsureTenantResult {\n /** The tenant (null if flexible mode and no existing tenant) */\n tenant: Tenant | null;\n /** The membership (null if no tenant) */\n membership: Membership | null;\n /** Whether a new tenant was created */\n created: boolean;\n}\n\n/**\n * TenantService manages tenant creation with configurable policies.\n *\n * Policies:\n * - `flexible`: No tenant created on signup, user can have zero tenants\n * - `personal`: Auto-create personal tenant on first login, can delete all\n * - `required`: Auto-create personal tenant, must keep at least one\n *\n * @example\n * ```typescript\n * const tenantService = new TenantService(options, {\n * mode: 'personal',\n * maxTenants: 5,\n * defaultName: 'My Workspace',\n * });\n * await tenantService.initialize();\n *\n * // During OIDC login\n * const { tenant, membership, created } = await tenantService.ensureTenantForUser(\n * user.id,\n * { email: user.email, name: user.name }\n * );\n *\n * // Creating additional tenants\n * if (await tenantService.canCreateTenant(user.id)) {\n * const { tenant } = await tenantService.createTenantWithOwnership(\n * user.id,\n * 'New Organization'\n * );\n * }\n * ```\n */\nexport class TenantService {\n private options: SmrtClassOptions;\n private policy: TenantPolicy;\n private tenantCollection!: TenantCollection;\n private membershipCollection!: MembershipCollection;\n private roleCollection!: RoleCollection;\n\n constructor(options: SmrtClassOptions, policy?: TenantPolicy) {\n this.options = options;\n this.policy = policy ?? DEFAULT_TENANT_POLICY;\n }\n\n /**\n * Initialize collections\n */\n async initialize(): Promise<void> {\n this.tenantCollection = await (TenantCollection as any).create(\n this.options,\n );\n this.membershipCollection = await (MembershipCollection as any).create(\n this.options,\n );\n this.roleCollection = await (RoleCollection as any).create(this.options);\n\n // Seed system roles if needed\n await this.roleCollection.seedSystemRoles();\n }\n\n /**\n * Get the current policy\n */\n getPolicy(): TenantPolicy {\n return { ...this.policy };\n }\n\n /**\n * Create a tenant and make the user the owner\n *\n * @param userId - The user to make owner\n * @param name - Tenant name\n * @param options - Optional slug override\n * @returns The created tenant and membership\n */\n async createTenantWithOwnership(\n userId: string,\n name: string,\n options?: { slug?: string },\n ): Promise<TenantWithOwnershipResult> {\n // Check if user can create\n if (!(await this.canCreateTenant(userId))) {\n throw new Error(\n `User has reached maximum tenant limit (${this.policy.maxTenants})`,\n );\n }\n\n // Create tenant\n const tenant = await this.tenantCollection.create({\n name,\n slug: options?.slug,\n });\n await tenant.save();\n\n // Get owner role\n const ownerRole = await this.roleCollection.findBySlug(\n DEFAULT_ROLE_SLUGS.OWNER,\n );\n if (!ownerRole) {\n throw new Error('Owner role not found - run seedSystemRoles first');\n }\n\n // Create ownership membership\n const membership = await this.membershipCollection.create({\n userId,\n tenantId: tenant.id as string,\n roleId: ownerRole.id as string,\n status: MembershipStatus.ACTIVE,\n });\n await membership.save();\n\n return { tenant, membership };\n }\n\n /**\n * Check if a user can create a new tenant\n *\n * Returns false if maxTenants limit is reached (0 = unlimited)\n */\n async canCreateTenant(userId: string): Promise<boolean> {\n if (this.policy.maxTenants === 0) {\n return true; // Unlimited\n }\n\n const ownerRole = await this.roleCollection.findBySlug(\n DEFAULT_ROLE_SLUGS.OWNER,\n );\n if (!ownerRole) {\n // Fail closed if owner role is not configured\n return false;\n }\n\n const memberships =\n await this.membershipCollection.findActiveByUser(userId);\n const ownedCount = memberships.filter(\n (m) => m.roleId === ownerRole.id,\n ).length;\n\n return ownedCount < this.policy.maxTenants;\n }\n\n /**\n * Get a specific error message explaining why a tenant cannot be deleted.\n *\n * @returns Error message if deletion is not allowed, or null if allowed.\n */\n private async getDeleteTenantError(\n userId: string,\n tenantId: string,\n ): Promise<string | null> {\n // Check user has an active membership for this tenant\n const membership = await this.membershipCollection.findByUserAndTenant(\n userId,\n tenantId,\n );\n if (!membership || membership.status !== MembershipStatus.ACTIVE) {\n return 'You are not a member of this tenant or it does not exist.';\n }\n\n const ownerRole = await this.roleCollection.findBySlug(\n DEFAULT_ROLE_SLUGS.OWNER,\n );\n if (!ownerRole) {\n return 'Owner role is not configured. Cannot determine deletion permissions.';\n }\n\n if (membership.roleId !== ownerRole.id) {\n return 'Only the tenant owner can delete this tenant.';\n }\n\n // For 'required' policy, check if this is the last tenant\n if (this.policy.mode === 'required') {\n const allMemberships =\n await this.membershipCollection.findActiveByUser(userId);\n const ownerMemberships = allMemberships.filter(\n (m) => m.roleId === ownerRole.id,\n );\n\n if (ownerMemberships.length <= 1) {\n return 'Cannot delete your last tenant. Policy requires at least one tenant.';\n }\n }\n\n return null;\n }\n\n /**\n * Check if a user can delete a specific tenant\n *\n * Returns false if:\n * - User is not an active member\n * - User is not the owner\n * - Policy is 'required' and this is the last tenant\n */\n async canDeleteTenant(userId: string, tenantId: string): Promise<boolean> {\n const error = await this.getDeleteTenantError(userId, tenantId);\n return error === null;\n }\n\n /**\n * Delete a tenant\n *\n * Note: This does not cascade delete related records (memberships, etc.).\n * The caller should handle cleanup of related data if needed.\n *\n * @throws Error if user cannot delete the tenant (with specific reason)\n */\n async deleteTenant(userId: string, tenantId: string): Promise<void> {\n const error = await this.getDeleteTenantError(userId, tenantId);\n if (error) {\n throw new Error(error);\n }\n\n const tenant = await this.tenantCollection.get(tenantId);\n if (tenant) {\n await tenant.delete();\n }\n }\n\n /**\n * Ensure a tenant exists for the user based on policy\n *\n * Called during OIDC login to apply tenant policy:\n * - `flexible`: Returns first existing tenant or null (no auto-create)\n * - `personal`/`required`: Creates default tenant if none exists\n *\n * @param userId - The user ID\n * @param userInfo - User info for naming the auto-created tenant\n * @returns Tenant and membership (may be null in flexible mode)\n */\n async ensureTenantForUser(\n userId: string,\n userInfo: { email?: string; name?: string },\n ): Promise<EnsureTenantResult> {\n // Get existing memberships\n const memberships =\n await this.membershipCollection.findActiveByUser(userId);\n\n // If user has tenants, return the first one\n if (memberships.length > 0) {\n const firstMembership = memberships[0];\n const tenant = await this.tenantCollection.get(\n firstMembership.tenantId as string,\n );\n\n return {\n tenant: tenant ?? null,\n membership: firstMembership,\n created: false,\n };\n }\n\n // No existing tenants - apply policy\n if (this.policy.mode === 'flexible') {\n // Flexible mode: don't auto-create\n return {\n tenant: null,\n membership: null,\n created: false,\n };\n }\n\n // Personal or required mode: create default tenant\n // Use user's name for personalized tenant name, or fall back to policy default\n const tenantName = userInfo.name\n ? `${userInfo.name}'s Workspace`\n : this.policy.defaultName;\n\n const { tenant, membership } = await this.createTenantWithOwnership(\n userId,\n tenantName,\n );\n\n return {\n tenant,\n membership,\n created: true,\n };\n }\n\n /**\n * Get all tenants for a user (where they are owner)\n */\n async getOwnedTenants(userId: string): Promise<Tenant[]> {\n const ownerRole = await this.roleCollection.findBySlug(\n DEFAULT_ROLE_SLUGS.OWNER,\n );\n if (!ownerRole) {\n return [];\n }\n\n const memberships =\n await this.membershipCollection.findActiveByUser(userId);\n const ownerMemberships = memberships.filter(\n (m) => m.roleId === ownerRole.id,\n );\n\n // Fetch all tenants in parallel to avoid N+1 queries\n const tenantPromises = ownerMemberships.map((m) =>\n this.tenantCollection.get(m.tenantId as string),\n );\n const tenantsOrNull = await Promise.all(tenantPromises);\n\n return tenantsOrNull.filter((tenant): tenant is Tenant => tenant !== null);\n }\n\n /**\n * Static factory method\n */\n static async create(\n options: SmrtClassOptions,\n policy?: TenantPolicy,\n ): Promise<TenantService> {\n const service = new TenantService(options, policy);\n await service.initialize();\n return service;\n }\n}\n"],"names":["__decorateClass","toSnakeCase","entry","m","tenant"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/models/MagicLinkToken.ts","../src/collections/MagicLinkTokenCollection.ts","../src/models/Role.ts","../src/collections/RoleCollection.ts","../src/services/MagicLinkService.ts","../src/services/PermissionCatalogService.ts","../src/services/PostgresPermissionPolicies.ts","../src/services/TenantService.ts"],"sourcesContent":["/**\n * MagicLinkToken model - replay protection for magic link authentication\n *\n * Stores nonces embedded in magic link JWTs to prevent replay attacks.\n * Each nonce can only be used once within its expiry window.\n *\n * @packageDocumentation\n */\n\nimport { field, SmrtObject, smrt } from '@happyvertical/smrt-core';\n\n/**\n * Default magic link token expiry: 10 minutes in seconds\n */\nexport const DEFAULT_TOKEN_EXPIRY_SECONDS = 10 * 60;\n\n@smrt({\n tableName: 'users_magic_link_tokens',\n // Magic link tokens are security-sensitive — no public API\n api: { include: [] },\n mcp: { include: [] },\n cli: true,\n})\nexport class UsersMagicLinkToken extends SmrtObject {\n /** Unique nonce embedded in the signed JWT */\n @field({ required: true, unique: true })\n nonce: string = '';\n\n /** Email address this token was generated for */\n email: string = '';\n\n /** Whether this token has been used */\n used: boolean = false;\n\n /** When this token expires */\n expiresAt: Date = new Date(Date.now() + DEFAULT_TOKEN_EXPIRY_SECONDS * 1000);\n\n constructor(options: any = {}) {\n super(options);\n if (options.nonce !== undefined) this.nonce = options.nonce;\n if (options.email !== undefined) this.email = options.email;\n if (options.used !== undefined) this.used = options.used;\n if (options.expiresAt !== undefined) {\n this.expiresAt =\n options.expiresAt instanceof Date\n ? options.expiresAt\n : new Date(options.expiresAt);\n }\n }\n\n /** Check if the token has expired */\n isExpired(): boolean {\n return new Date() > this.expiresAt;\n }\n\n /** Check if the token is still valid (unused and not expired) */\n isValid(): boolean {\n return !this.used && !this.isExpired();\n }\n}\n\nexport { UsersMagicLinkToken as MagicLinkToken };\n","/**\n * MagicLinkTokenCollection - Collection manager for MagicLinkToken objects\n *\n * Provides nonce lookup and single-use enforcement for magic link authentication.\n *\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { UsersMagicLinkToken } from '../models/MagicLinkToken.js';\n\n/**\n * Collection for managing smrt-users magic link token records\n */\nexport class UsersMagicLinkTokenCollection extends SmrtCollection<UsersMagicLinkToken> {\n static readonly _itemClass = UsersMagicLinkToken;\n\n /**\n * Find a token by its nonce\n */\n async findByNonce(nonce: string): Promise<UsersMagicLinkToken | null> {\n return this.findOne({\n where: { nonce },\n }) as Promise<UsersMagicLinkToken | null>;\n }\n\n /**\n * Atomically mark a token as used (single-use enforcement).\n *\n * Returns true if the nonce was successfully claimed (transitioned from\n * unused to used). Returns false if the nonce was already used, expired,\n * or doesn't exist — preventing race conditions in concurrent verify() calls.\n */\n async markUsed(nonce: string): Promise<boolean> {\n const now = new Date().toISOString();\n const { rowCount } = await this.db.query(\n `UPDATE ${this.tableName}\n SET used = ?, updated_at = ?\n WHERE nonce = ? AND used = ? AND expires_at > ?`,\n true,\n now,\n nonce,\n false,\n now,\n );\n\n return rowCount > 0;\n }\n\n /**\n * Delete expired tokens (cleanup job)\n */\n async deleteExpired(): Promise<number> {\n const now = new Date();\n const tokens = await this.list({\n where: {\n 'expiresAt <': now.toISOString(),\n },\n });\n\n let count = 0;\n for (const token of tokens) {\n await token.delete();\n count++;\n }\n\n return count;\n }\n}\n\nexport { UsersMagicLinkTokenCollection as MagicLinkTokenCollection };\n","/**\n * Role model - permission template\n * @packageDocumentation\n */\n\nimport { foreignKey, SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport type { Role as RoleContract } from '@happyvertical/smrt-types';\n\n/**\n * Role represents a permission template that can be assigned to users.\n *\n * Roles can be:\n * - System roles (tenantId = null): Available to all tenants, cannot be modified\n * - Custom roles (tenantId set): Specific to a tenant, can be customized\n *\n * @example\n * ```typescript\n * // System role (tenantId = null)\n * const adminRole = await roles.create({\n * slug: 'admin',\n * name: 'Administrator',\n * isSystem: true\n * });\n *\n * // Custom tenant role\n * const editorRole = await roles.create({\n * tenantId: tenant.id,\n * slug: 'editor',\n * name: 'Editor',\n * description: 'Can edit content'\n * });\n * ```\n */\n@smrt({\n // #1400: read-only generated surface — RBAC/identity writes go through\n // permission-gated services, not auth-only generated CRUD.\n api: { include: ['list', 'get'] },\n mcp: { include: ['list', 'get'] },\n cli: true,\n})\nexport class Role extends SmrtObject implements RoleContract {\n /**\n * Foreign key to Tenant\n * null = system role available to all tenants\n */\n @foreignKey('Tenant', { nullable: true })\n tenantId?: string | null;\n\n /**\n * Display name for the role\n */\n name: string = '';\n\n /**\n * Description of the role\n */\n description: string = '';\n\n /**\n * Whether this is a system role (cannot be deleted)\n */\n isSystem: boolean = false;\n\n constructor(options: any = {}) {\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.isSystem !== undefined) this.isSystem = options.isSystem;\n }\n\n /**\n * Check if this is a system-wide role\n */\n isSystemRole(): boolean {\n return this.tenantId === null || this.tenantId === undefined;\n }\n\n /**\n * Check if this is a tenant-specific role\n */\n isTenantRole(): boolean {\n return this.tenantId !== null && this.tenantId !== undefined;\n }\n\n /**\n * Check if this role can be deleted.\n * System roles (isSystem = true) cannot be deleted.\n * @returns true if the role can be deleted\n */\n canDelete(): boolean {\n return !this.isSystem;\n }\n\n /**\n * Delete guard - prevents deletion of system roles.\n * Override the delete method to check isSystem flag first.\n */\n async delete(): Promise<void> {\n if (this.isSystem) {\n throw new Error(\n `Cannot delete system role '${this.slug}'. System roles are protected.`,\n );\n }\n return super.delete();\n }\n}\n","/**\n * RoleCollection - Collection manager for Role objects\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { Role } from '../models/Role.js';\nimport { DEFAULT_ROLES } from '../types/index.js';\n\n/**\n * Collection for managing Role objects\n */\nexport class RoleCollection extends SmrtCollection<Role> {\n static readonly _itemClass = Role;\n\n /**\n * Find all system roles (tenantId is null)\n */\n async findSystemRoles(): Promise<Role[]> {\n // Query for roles where tenantId is null\n return await this.query(\n `SELECT * FROM ${this.tableName} WHERE tenant_id IS NULL ORDER BY name ASC`,\n );\n }\n\n /**\n * Find roles available for a tenant (system + tenant-specific)\n */\n async findByTenant(tenantId: string): Promise<Role[]> {\n return await this.query(\n `SELECT * FROM ${this.tableName}\n WHERE tenant_id IS NULL OR tenant_id = ?\n ORDER BY is_system DESC, name ASC`,\n [tenantId],\n );\n }\n\n /**\n * Find tenant-specific roles only\n */\n async findTenantRoles(tenantId: string): Promise<Role[]> {\n return await this.list({\n where: { tenantId },\n orderBy: 'name ASC',\n });\n }\n\n /**\n * Find role by slug within a tenant context\n */\n async findBySlug(slug: string, tenantId?: string): Promise<Role | null> {\n // First check tenant-specific role\n if (tenantId) {\n const tenantRoles = await this.list({\n where: { slug, tenantId },\n limit: 1,\n });\n if (tenantRoles.length > 0) {\n return tenantRoles[0];\n }\n }\n\n // Fall back to system role\n const systemRoles = await this.query(\n `SELECT * FROM ${this.tableName} WHERE slug = ? AND tenant_id IS NULL LIMIT 1`,\n [slug],\n );\n return systemRoles.length > 0 ? systemRoles[0] : null;\n }\n\n /**\n * Seed default system roles\n */\n async seedSystemRoles(): Promise<Role[]> {\n const roles: Role[] = [];\n\n for (const roleDef of DEFAULT_ROLES) {\n // Check if role already exists\n const existing = await this.findBySlug(roleDef.slug);\n if (existing) {\n roles.push(existing);\n continue;\n }\n\n // Create new system role\n const role = await this.create({\n slug: roleDef.slug,\n name: roleDef.name,\n description: roleDef.description,\n tenantId: null,\n isSystem: true,\n });\n await role.save();\n roles.push(role);\n }\n\n return roles;\n }\n}\n","/**\n * MagicLinkService - Passwordless email authentication via signed magic link tokens\n *\n * This service handles token generation and verification for magic link\n * authentication. It is framework-agnostic: it returns tokens and verification\n * results, leaving email delivery and cookie management to the caller.\n *\n * Flow:\n * 1. User enters email → generate(email) → token + expiresAt\n * 2. Caller sends email with link containing the token\n * 3. User clicks link → verify(token) → { email, nonce }\n * 4. Caller creates session (via SessionService or createSessionCookie)\n *\n * @packageDocumentation\n *\n * @example\n * ```typescript\n * const magicLink = await MagicLinkService.create({\n * db: { type: 'postgres', url: process.env.DATABASE_URL },\n * secret: process.env.AUTH_SECRET,\n * });\n *\n * // Generate a magic link token\n * const { token, expiresAt } = await magicLink.generate('user@example.com');\n *\n * // Verify a magic link token (single-use)\n * const { email, nonce } = await magicLink.verify(token);\n * ```\n */\n\nimport type { SmrtClassOptions } from '@happyvertical/smrt-core';\nimport { UsersMagicLinkTokenCollection } from '../collections/MagicLinkTokenCollection.js';\nimport { DEFAULT_TOKEN_EXPIRY_SECONDS } from '../models/MagicLinkToken.js';\nimport { isValidEmail, normalizeEmail } from '../models/User.js';\n\n/**\n * Options for MagicLinkService\n */\nexport interface MagicLinkServiceOptions extends SmrtClassOptions {\n /** Secret used to derive the HMAC signing key (required) */\n secret: string;\n /** Token expiry in seconds (default: 10 minutes) */\n tokenExpiry?: number;\n /** JWT issuer claim (default: 'smrt:magiclink') */\n issuer?: string;\n}\n\n/**\n * Result of generating a magic link token\n */\nexport interface MagicLinkResult {\n /** The signed JWT token */\n token: string;\n /** When the token expires */\n expiresAt: Date;\n}\n\n/**\n * Result of verifying a magic link token\n */\nexport interface MagicLinkVerifyResult {\n /** The email address from the token */\n email: string;\n /** The nonce (for correlation/logging) */\n nonce: string;\n}\n\n/**\n * Error class for magic link authentication failures\n */\nexport class MagicLinkError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'MagicLinkError';\n }\n}\n\n/**\n * MagicLinkService provides passwordless authentication via email magic links.\n *\n * Tokens are HMAC-signed JWTs with embedded nonces for replay protection.\n * The service is framework-agnostic — it does not send emails or set cookies.\n */\nexport class MagicLinkService {\n private tokenCollection!: UsersMagicLinkTokenCollection;\n private signingKey: Uint8Array | null = null;\n private readonly secret: string;\n private readonly tokenExpiry: number;\n private readonly issuer: string;\n private readonly options: MagicLinkServiceOptions;\n\n constructor(options: MagicLinkServiceOptions) {\n if (!options.secret) {\n throw new Error('MagicLinkService requires a secret for token signing');\n }\n this.secret = options.secret;\n this.tokenExpiry = options.tokenExpiry ?? DEFAULT_TOKEN_EXPIRY_SECONDS;\n this.issuer = options.issuer ?? 'smrt:magiclink';\n this.options = options;\n }\n\n /**\n * Initialize collections\n */\n async initialize(): Promise<void> {\n this.tokenCollection = (await (UsersMagicLinkTokenCollection as any).create(\n this.options,\n )) as UsersMagicLinkTokenCollection;\n }\n\n /**\n * Derive the HMAC signing key from the secret\n */\n private async getSigningKey(): Promise<Uint8Array> {\n if (this.signingKey) return this.signingKey;\n\n const encoder = new TextEncoder();\n const data = encoder.encode(`magiclink:${this.secret}`);\n const hashBuffer = await crypto.subtle.digest('SHA-256', data);\n this.signingKey = new Uint8Array(hashBuffer);\n\n return this.signingKey;\n }\n\n /**\n * Generate a magic link token for the given email.\n *\n * Stores a nonce in the database for replay protection.\n * The caller is responsible for emailing the token to the user.\n */\n async generate(email: string): Promise<MagicLinkResult> {\n // Dynamic import to avoid eagerly loading jose at module init\n const { SignJWT } = await import('jose');\n\n const key = await this.getSigningKey();\n const nonce = crypto.randomUUID();\n const normalizedEmail = normalizeEmail(email);\n\n if (!isValidEmail(normalizedEmail)) {\n throw new MagicLinkError('Invalid email address');\n }\n\n const expiresAt = new Date(Date.now() + this.tokenExpiry * 1000);\n\n // Store nonce for replay protection\n await this.tokenCollection.create({\n nonce,\n email: normalizedEmail,\n used: false,\n expiresAt,\n });\n\n // Sign the token\n const token = await new SignJWT({\n email: normalizedEmail,\n nonce,\n })\n .setProtectedHeader({ alg: 'HS256' })\n .setIssuedAt()\n .setExpirationTime(`${this.tokenExpiry}s`)\n .setIssuer(this.issuer)\n .sign(key);\n\n return { token, expiresAt };\n }\n\n /**\n * Verify a magic link token.\n *\n * Checks JWT signature, expiry, and that the nonce hasn't been used.\n * Marks the nonce as used on success (single-use enforcement).\n *\n * @throws {MagicLinkError} If the token is invalid, expired, or already used\n */\n async verify(token: string): Promise<MagicLinkVerifyResult> {\n const { jwtVerify, errors } = await import('jose');\n\n const key = await this.getSigningKey();\n\n // Verify JWT signature and expiry\n let payload: Record<string, unknown>;\n try {\n const result = await jwtVerify(token, key, {\n issuer: this.issuer,\n });\n payload = result.payload as Record<string, unknown>;\n } catch (err) {\n if (err instanceof errors.JWTExpired) {\n throw new MagicLinkError('Token has expired');\n }\n throw new MagicLinkError('Invalid token');\n }\n\n const email = payload.email;\n const nonce = payload.nonce;\n\n // Explicit type validation — JWT payload values are unknown\n if (typeof email !== 'string' || typeof nonce !== 'string') {\n throw new MagicLinkError('Invalid token payload');\n }\n\n // Atomically claim the nonce (single-use enforcement) via a single\n // conditional UPDATE. Only one concurrent verify() call can flip the\n // row from used=false to used=true before the DB expires the nonce.\n const claimed = await this.tokenCollection.markUsed(nonce);\n\n if (!claimed) {\n throw new MagicLinkError('Token has already been used or has expired');\n }\n\n return { email: normalizeEmail(email), nonce };\n }\n\n /**\n * Clean up expired tokens (run periodically)\n */\n async cleanupExpiredTokens(): Promise<number> {\n return this.tokenCollection.deleteExpired();\n }\n\n /**\n * Static factory method\n */\n static async create(\n options: MagicLinkServiceOptions,\n ): Promise<MagicLinkService> {\n const service = new MagicLinkService(options);\n await service.initialize();\n return service;\n }\n}\n","/**\n * Manifest-derived permission cataloging and syncing\n * @packageDocumentation\n */\n\nimport { getPackageConfig } from '@happyvertical/smrt-config';\nimport {\n findManifestEntryByQualifiedName,\n ObjectRegistry,\n type SmartObjectDefinition,\n type SmrtClassOptions,\n} from '@happyvertical/smrt-core';\nimport { PermissionCollection } from '../collections/PermissionCollection.js';\nimport {\n isValidPermissionSlug,\n parsePermissionSlug,\n} from '../models/Permission.js';\n\nexport type PermissionCatalogSource = 'manifest' | 'config' | 'runtime';\n\nexport type PostgresPermissionAction =\n | 'SELECT'\n | 'INSERT'\n | 'UPDATE'\n | 'DELETE';\n\nexport interface PostgresPermissionBinding {\n action: Lowercase<PostgresPermissionAction> | PostgresPermissionAction;\n permission?: string;\n schemaName?: string;\n tableName: string;\n tenantField?: string;\n}\n\nexport interface PermissionDefinition {\n category?: string;\n className?: string;\n collection?: string;\n description?: string;\n name?: string;\n postgres?: {\n bindings?: PostgresPermissionBinding[];\n };\n qualifiedName?: string;\n slug: string;\n source?: PermissionCatalogSource;\n}\n\nexport interface PermissionCatalog {\n customPermissions: PermissionDefinition[];\n manifestPermissions: PermissionDefinition[];\n permissions: PermissionDefinition[];\n runtimePermissions: PermissionDefinition[];\n}\n\nexport interface PermissionCatalogSyncResult {\n catalog: PermissionCatalog;\n created: string[];\n unchanged: string[];\n updated: string[];\n}\n\nexport interface UsersConfig extends Record<string, unknown> {\n permissions?: {\n custom?: PermissionDefinition[];\n postgres?: {\n bindings?: PostgresPermissionBinding[];\n enabled?: boolean;\n };\n };\n}\n\ndeclare global {\n // eslint-disable-next-line no-var\n var __smrtUsersPermissionRegistrations:\n | Map<number, PermissionDefinition[]>\n | undefined;\n // eslint-disable-next-line no-var\n var __smrtUsersPermissionRegistrationCounter: number | undefined;\n}\n\nfunction getRuntimePermissionRegistrations(): Map<\n number,\n PermissionDefinition[]\n> {\n globalThis.__smrtUsersPermissionRegistrations ??= new Map<\n number,\n PermissionDefinition[]\n >();\n return globalThis.__smrtUsersPermissionRegistrations;\n}\n\nfunction toSnakeCase(value: string): string {\n return value\n .replace(/([a-z0-9])([A-Z])/g, '$1_$2')\n .replace(/[-\\s]+/g, '_')\n .toLowerCase();\n}\n\nfunction pluralize(word: string): string {\n if (word.endsWith('y') && !/[aeiou]y$/i.test(word)) {\n return `${word.slice(0, -1)}ies`;\n }\n\n if (\n word.endsWith('s') ||\n word.endsWith('x') ||\n word.endsWith('z') ||\n word.endsWith('ch') ||\n word.endsWith('sh')\n ) {\n return `${word}es`;\n }\n\n return `${word}s`;\n}\n\nfunction deriveCollectionName(className: string): string {\n return pluralize(toSnakeCase(className));\n}\n\nfunction humanizeResource(resource: string): string {\n return resource\n .replace(/[_-]+/g, ' ')\n .replace(/\\b\\w/g, (char) => char.toUpperCase());\n}\n\nfunction capitalize(value: string): string {\n return `${value[0]?.toUpperCase() ?? ''}${value.slice(1)}`;\n}\n\nfunction defaultPermissionName(slug: string): string {\n const parsed = parsePermissionSlug(slug);\n if (!parsed.isValid) {\n return humanizeResource(slug);\n }\n\n return `${capitalize(parsed.action)} ${humanizeResource(parsed.resource)}`;\n}\n\nfunction defaultPermissionDescription(slug: string): string {\n const parsed = parsePermissionSlug(slug);\n if (!parsed.isValid) {\n return `Allows ${slug}`;\n }\n\n return `Allows ${parsed.action} access for ${humanizeResource(parsed.resource).toLowerCase()}`;\n}\n\nfunction isCollectionManifestEntry(objectDef?: SmartObjectDefinition): boolean {\n return (\n objectDef?.extends === 'SmrtCollection' ||\n objectDef?.extendsTypeArg !== undefined\n );\n}\n\ninterface ManifestMethodCandidate {\n isPublic?: boolean;\n name?: string;\n}\n\nfunction getPublicCustomMethodNames(\n methodEntries: ManifestMethodCandidate[],\n standardActions: readonly string[],\n): string[] {\n return Array.from(\n new Set(\n methodEntries\n .filter(\n (method) =>\n Boolean(method?.name) &&\n method?.isPublic === true &&\n !standardActions.includes(method.name!),\n )\n .map((method) => method.name!),\n ),\n );\n}\n\nfunction getCustomMethodExposureNames(\n config: unknown,\n availableCustomMethods: string[],\n): Set<string> {\n if (!config || config === false) {\n return new Set();\n }\n\n if (config === true || typeof config !== 'object') {\n return new Set(availableCustomMethods);\n }\n\n const rawInclude = (config as { include?: string[] }).include;\n const include = Array.isArray(rawInclude) ? [...rawInclude] : undefined;\n const rawExclude = (config as { exclude?: string[] }).exclude;\n const exclude: string[] = Array.isArray(rawExclude) ? [...rawExclude] : [];\n\n if (!include) {\n return new Set(\n availableCustomMethods.filter(\n (methodName) => !exclude.includes(methodName),\n ),\n );\n }\n\n const baseMethods = include.filter((methodName) =>\n availableCustomMethods.includes(methodName),\n );\n\n return new Set(\n baseMethods.filter((methodName) => !exclude.includes(methodName)),\n );\n}\n\nfunction isOperationEnabled(config: unknown, action: string): boolean {\n if (config === false) {\n return false;\n }\n\n if (config && typeof config === 'object') {\n const include = Array.isArray((config as { include?: string[] }).include)\n ? (config as { include?: string[] }).include\n : undefined;\n const rawExclude = (config as { exclude?: string[] }).exclude;\n const exclude: string[] = Array.isArray(rawExclude) ? [...rawExclude] : [];\n\n if (include && !include.includes(action)) {\n return false;\n }\n if (exclude.includes(action)) {\n return false;\n }\n }\n\n return true;\n}\n\nfunction normalizePostgresAction(\n action: PostgresPermissionBinding['action'],\n): PostgresPermissionAction {\n const normalized = action.toUpperCase();\n if (\n normalized === 'SELECT' ||\n normalized === 'INSERT' ||\n normalized === 'UPDATE' ||\n normalized === 'DELETE'\n ) {\n return normalized;\n }\n\n throw new Error(\n `Unsupported Postgres permission action '${action}'. Expected SELECT, INSERT, UPDATE, or DELETE.`,\n );\n}\n\nfunction normalizeBinding(\n binding: PostgresPermissionBinding,\n fallbackPermission: string,\n): PostgresPermissionBinding {\n return {\n action: normalizePostgresAction(binding.action),\n permission: binding.permission || fallbackPermission,\n schemaName: binding.schemaName,\n tableName: binding.tableName,\n tenantField: binding.tenantField,\n };\n}\n\nfunction mergeStringField(\n fieldName:\n | 'category'\n | 'className'\n | 'collection'\n | 'description'\n | 'name'\n | 'qualifiedName',\n existing: PermissionDefinition,\n incoming: PermissionDefinition,\n slug: string,\n): void {\n const existingValue = existing[fieldName];\n const incomingValue = incoming[fieldName];\n\n if (!incomingValue) {\n return;\n }\n\n if (!existingValue) {\n existing[fieldName] = incomingValue;\n return;\n }\n\n if (existingValue !== incomingValue) {\n throw new Error(\n `Conflicting permission metadata for '${slug}' field '${fieldName}': '${existingValue}' !== '${incomingValue}'`,\n );\n }\n}\n\nfunction mergeBindings(\n existing: PermissionDefinition,\n incoming: PermissionDefinition,\n): void {\n const existingBindings = existing.postgres?.bindings ?? [];\n const incomingBindings = incoming.postgres?.bindings ?? [];\n if (incomingBindings.length === 0) {\n return;\n }\n\n const seen = new Set(\n existingBindings.map((binding) =>\n [\n binding.permission,\n binding.action,\n binding.schemaName ?? '',\n binding.tableName,\n binding.tenantField ?? '',\n ].join('|'),\n ),\n );\n\n const mergedBindings = [...existingBindings];\n for (const binding of incomingBindings) {\n const normalized = normalizeBinding(binding, incoming.slug);\n const key = [\n normalized.permission,\n normalized.action,\n normalized.schemaName ?? '',\n normalized.tableName,\n normalized.tenantField ?? '',\n ].join('|');\n if (!seen.has(key)) {\n seen.add(key);\n mergedBindings.push(normalized);\n }\n }\n\n existing.postgres = {\n bindings: mergedBindings,\n };\n}\n\nfunction normalizeDefinition(\n definition: PermissionDefinition,\n source: PermissionCatalogSource,\n): PermissionDefinition {\n if (!definition.slug || !isValidPermissionSlug(definition.slug.trim())) {\n throw new Error(\n `Invalid permission slug '${definition.slug}'. Expected 'resource.action'.`,\n );\n }\n\n const slug = definition.slug.trim();\n return {\n category: definition.category ?? parsePermissionSlug(slug).resource,\n className: definition.className,\n collection: definition.collection,\n description: definition.description ?? defaultPermissionDescription(slug),\n name: definition.name ?? defaultPermissionName(slug),\n postgres: definition.postgres?.bindings\n ? {\n bindings: definition.postgres.bindings.map((binding) =>\n normalizeBinding(binding, slug),\n ),\n }\n : undefined,\n qualifiedName: definition.qualifiedName,\n slug,\n source,\n };\n}\n\nfunction mergeDefinitionSet(\n current: Map<string, PermissionDefinition>,\n incomingDefinitions: PermissionDefinition[],\n source: PermissionCatalogSource,\n): void {\n for (const rawDefinition of incomingDefinitions) {\n const definition = normalizeDefinition(rawDefinition, source);\n const existing = current.get(definition.slug);\n\n if (!existing) {\n current.set(definition.slug, definition);\n continue;\n }\n\n mergeStringField('category', existing, definition, definition.slug);\n mergeStringField('className', existing, definition, definition.slug);\n mergeStringField('collection', existing, definition, definition.slug);\n mergeStringField('description', existing, definition, definition.slug);\n mergeStringField('name', existing, definition, definition.slug);\n mergeStringField('qualifiedName', existing, definition, definition.slug);\n mergeBindings(existing, definition);\n }\n}\n\nexport function registerPermissionDefinitions(\n definitions: PermissionDefinition[],\n): () => void {\n globalThis.__smrtUsersPermissionRegistrationCounter =\n (globalThis.__smrtUsersPermissionRegistrationCounter ?? 0) + 1;\n const registrationId = globalThis.__smrtUsersPermissionRegistrationCounter;\n getRuntimePermissionRegistrations().set(registrationId, definitions);\n\n return () => {\n getRuntimePermissionRegistrations().delete(registrationId);\n };\n}\n\nexport class PermissionCatalogService {\n constructor(private readonly options: SmrtClassOptions = {}) {}\n\n getUsersConfig(): UsersConfig {\n return getPackageConfig<UsersConfig>('users', {});\n }\n\n getRuntimePermissionDefinitions(): PermissionDefinition[] {\n return Array.from(getRuntimePermissionRegistrations().values()).flat();\n }\n\n getCustomPermissionDefinitions(): PermissionDefinition[] {\n return this.getUsersConfig().permissions?.custom ?? [];\n }\n\n getCatalog(): PermissionCatalog {\n const manifestPermissions = this.getManifestPermissionDefinitions();\n const customPermissions = this.getCustomPermissionDefinitions();\n const runtimePermissions = this.getRuntimePermissionDefinitions();\n\n const merged = new Map<string, PermissionDefinition>();\n mergeDefinitionSet(merged, manifestPermissions, 'manifest');\n mergeDefinitionSet(merged, customPermissions, 'config');\n mergeDefinitionSet(merged, runtimePermissions, 'runtime');\n\n return {\n customPermissions: customPermissions.map((definition) =>\n normalizeDefinition(definition, 'config'),\n ),\n manifestPermissions: manifestPermissions.map((definition) =>\n normalizeDefinition(definition, 'manifest'),\n ),\n permissions: Array.from(merged.values()).sort((left, right) =>\n left.slug.localeCompare(right.slug),\n ),\n runtimePermissions: runtimePermissions.map((definition) =>\n normalizeDefinition(definition, 'runtime'),\n ),\n };\n }\n\n async syncPermissionCatalog(): Promise<PermissionCatalogSyncResult> {\n const catalog = this.getCatalog();\n const permissions = await PermissionCollection.create(this.options);\n\n const created: string[] = [];\n const unchanged: string[] = [];\n const updated: string[] = [];\n\n for (const definition of catalog.permissions) {\n const existing = await permissions.findBySlug(definition.slug);\n if (!existing) {\n const permission = await permissions.create({\n category:\n definition.category ??\n parsePermissionSlug(definition.slug).resource,\n description: definition.description ?? '',\n name: definition.name ?? definition.slug,\n slug: definition.slug,\n });\n await permission.save();\n created.push(definition.slug);\n continue;\n }\n\n const nextName = definition.name ?? existing.name;\n const nextDescription = definition.description ?? existing.description;\n const nextCategory = definition.category ?? existing.category;\n\n if (\n existing.name === nextName &&\n existing.description === nextDescription &&\n existing.category === nextCategory\n ) {\n unchanged.push(definition.slug);\n continue;\n }\n\n existing.name = nextName;\n existing.description = nextDescription;\n existing.category = nextCategory;\n await existing.save();\n updated.push(definition.slug);\n }\n\n return {\n catalog,\n created,\n unchanged,\n updated,\n };\n }\n\n private getManifestPermissionDefinitions(): PermissionDefinition[] {\n const standardActions = ['list', 'get', 'create', 'update', 'delete'];\n const definitions = new Map<string, PermissionDefinition>();\n\n for (const metadata of ObjectRegistry.getAllObjectMetadata()) {\n const registered =\n ObjectRegistry.getClassByConstructor(metadata.constructor as any) ??\n ObjectRegistry.getClass(metadata.name);\n const manifestEntry = registered?.qualifiedName\n ? findManifestEntryByQualifiedName(registered.qualifiedName)\n : undefined;\n\n if (isCollectionManifestEntry(manifestEntry)) {\n continue;\n }\n\n const className = metadata.name;\n const qualifiedName = registered?.qualifiedName;\n const objectConfig = manifestEntry?.decoratorConfig ?? metadata.config;\n const rawCollection = (\n objectConfig as { collection?: unknown } | undefined\n )?.collection;\n const configuredCollection =\n typeof rawCollection === 'string' && rawCollection.length > 0\n ? rawCollection\n : undefined;\n const collection =\n configuredCollection ??\n manifestEntry?.collection ??\n deriveCollectionName(metadata.name);\n\n const readExposed =\n isOperationEnabled(objectConfig.api, 'list') ||\n isOperationEnabled(objectConfig.api, 'get') ||\n isOperationEnabled(objectConfig.cli, 'list') ||\n isOperationEnabled(objectConfig.cli, 'get') ||\n isOperationEnabled(objectConfig.mcp, 'list') ||\n isOperationEnabled(objectConfig.mcp, 'get');\n if (readExposed) {\n definitions.set(`${collection}.read`, {\n className,\n collection,\n qualifiedName,\n slug: `${collection}.read`,\n });\n }\n\n for (const action of ['create', 'update', 'delete'] as const) {\n const exposed =\n isOperationEnabled(objectConfig.api, action) ||\n isOperationEnabled(objectConfig.cli, action) ||\n isOperationEnabled(objectConfig.mcp, action);\n if (!exposed) {\n continue;\n }\n\n definitions.set(`${collection}.${action}`, {\n className,\n collection,\n qualifiedName,\n slug: `${collection}.${action}`,\n });\n }\n\n const methodEntries = manifestEntry?.methods\n ? Object.values(manifestEntry.methods)\n : Array.from(metadata.methods.values());\n const publicCustomMethodNames = getPublicCustomMethodNames(\n methodEntries,\n standardActions,\n );\n const customApiMethods = new Set<string>();\n const customCliMethods = getCustomMethodExposureNames(\n objectConfig.cli,\n publicCustomMethodNames,\n );\n const customMcpMethods = getCustomMethodExposureNames(\n objectConfig.mcp,\n publicCustomMethodNames,\n );\n\n for (const methodName of publicCustomMethodNames) {\n if (isOperationEnabled(objectConfig.api, methodName)) {\n customApiMethods.add(methodName);\n }\n }\n\n const customMethods = new Set<string>([\n ...customApiMethods,\n ...customCliMethods,\n ...customMcpMethods,\n ]);\n\n for (const methodName of customMethods) {\n definitions.set(`${collection}.${methodName}`, {\n className,\n collection,\n description: `Allows ${methodName} on ${humanizeResource(collection).toLowerCase()}`,\n name: `${capitalize(methodName)} ${humanizeResource(collection)}`,\n qualifiedName,\n slug: `${collection}.${methodName}`,\n });\n }\n }\n\n return Array.from(definitions.values()).sort((left, right) =>\n left.slug.localeCompare(right.slug),\n );\n }\n\n static create(options: SmrtClassOptions = {}): PermissionCatalogService {\n return new PermissionCatalogService(options);\n }\n}\n\nexport async function syncPermissionCatalog(\n options: SmrtClassOptions = {},\n): Promise<PermissionCatalogSyncResult> {\n return PermissionCatalogService.create(options).syncPermissionCatalog();\n}\n","/**\n * Postgres policy generation and application for SMRT permissions\n * @packageDocumentation\n */\n\nimport { createHash } from 'node:crypto';\nimport {\n findManifestEntryByQualifiedName,\n ObjectRegistry,\n type SmrtClassOptions,\n} from '@happyvertical/smrt-core';\nimport { PermissionCollection } from '../collections/PermissionCollection.js';\nimport {\n PermissionCatalogService,\n type PostgresPermissionAction,\n type PostgresPermissionBinding,\n} from './PermissionCatalogService.js';\n\ninterface QueryableDatabase {\n query: (sql: string, ...params: unknown[]) => Promise<unknown>;\n url?: string;\n}\n\ntype DatabaseConfig = SmrtClassOptions['db'] | SmrtClassOptions['persistence'];\n\ninterface TablePolicyTarget {\n actions: Map<PostgresPermissionAction, Set<string>>;\n className?: string;\n collection?: string;\n qualifiedName?: string;\n schemaName: string;\n tableName: string;\n tenantField: string;\n}\n\nexport interface PostgresPermissionPolicyReportItem {\n className?: string;\n collection?: string;\n qualifiedName?: string;\n reason: string;\n schemaName?: string;\n tableName?: string;\n}\n\nexport interface PostgresPermissionPolicyTarget {\n actions: Partial<Record<PostgresPermissionAction, string[]>>;\n className?: string;\n collection?: string;\n qualifiedName?: string;\n schemaName: string;\n tableName: string;\n tenantField: string;\n}\n\nexport interface GeneratePostgresPermissionSqlResult {\n bindings: PostgresPermissionBinding[];\n skipped: PostgresPermissionPolicyReportItem[];\n sql: string;\n statements: string[];\n targets: PostgresPermissionPolicyTarget[];\n}\n\nfunction toSnakeCase(value: string): string {\n return value\n .replace(/([a-z0-9])([A-Z])/g, '$1_$2')\n .replace(/[-\\s]+/g, '_')\n .toLowerCase();\n}\n\nfunction quoteIdent(identifier: string): string {\n return `\"${identifier.replaceAll('\"', '\"\"')}\"`;\n}\n\nfunction quoteLiteral(value: string): string {\n return `'${value.replaceAll(\"'\", \"''\")}'`;\n}\n\nfunction isProbablyPostgres(\n configDb: DatabaseConfig,\n database: QueryableDatabase,\n): boolean {\n if (\n configDb &&\n typeof configDb === 'object' &&\n !('query' in configDb) &&\n 'type' in configDb &&\n configDb.type === 'postgres'\n ) {\n return true;\n }\n\n if (typeof database.url === 'string' && database.url.startsWith('postgres')) {\n return true;\n }\n\n return (database.constructor?.name || '').toLowerCase().includes('postgres');\n}\n\nfunction normalizePostgresPermissionAction(\n action: PostgresPermissionBinding['action'],\n): PostgresPermissionAction {\n const normalized = action.toUpperCase();\n if (\n normalized === 'SELECT' ||\n normalized === 'INSERT' ||\n normalized === 'UPDATE' ||\n normalized === 'DELETE'\n ) {\n return normalized;\n }\n\n throw new Error(\n `Invalid Postgres permission binding action \"${action}\". Expected one of SELECT, INSERT, UPDATE, DELETE.`,\n );\n}\n\nfunction normalizePostgresPermissionBinding(\n binding: PostgresPermissionBinding,\n fallbackPermission?: string,\n): PostgresPermissionBinding {\n const tableName = binding.tableName?.trim();\n if (!tableName) {\n throw new Error(\n 'Postgres permission binding is missing a tableName value.',\n );\n }\n\n const permission = binding.permission ?? fallbackPermission;\n if (!permission) {\n throw new Error(\n 'Postgres permission binding is missing a permission value.',\n );\n }\n\n return {\n action: normalizePostgresPermissionAction(binding.action),\n permission,\n schemaName: binding.schemaName,\n tableName,\n tenantField: binding.tenantField,\n };\n}\n\nfunction parseTableReference(\n binding: Pick<PostgresPermissionBinding, 'schemaName' | 'tableName'>,\n): { schemaName: string; tableName: string } {\n if (binding.tableName.includes('.')) {\n const [schemaName, tableName] = binding.tableName.split('.', 2);\n return {\n schemaName,\n tableName,\n };\n }\n\n return {\n schemaName: binding.schemaName ?? 'public',\n tableName: binding.tableName,\n };\n}\n\nfunction buildPolicyName(\n tableName: string,\n action: PostgresPermissionAction,\n): string {\n const actionSegment = action.toLowerCase();\n const hash = createHash('sha1')\n .update(`${tableName}:${action}`)\n .digest('hex')\n .slice(0, 8);\n const sanitizedTable = tableName\n .replace(/[^a-zA-Z0-9_]+/g, '_')\n .replace(/^_+|_+$/g, '');\n const prefix = 'smrt_';\n const separatorLength = 2;\n const maxTableSegmentLength =\n 63 - prefix.length - actionSegment.length - hash.length - separatorLength;\n const tableSegment = (sanitizedTable || 'table').slice(\n 0,\n Math.max(maxTableSegmentLength, 1),\n );\n\n return `${prefix}${tableSegment}_${actionSegment}_${hash}`;\n}\n\nfunction buildPermissionExpression(permissionSlugs: string[]): string {\n if (permissionSlugs.length === 0) {\n return 'FALSE';\n }\n\n return permissionSlugs\n .map((permission) => `smrt_has_permission(${quoteLiteral(permission)})`)\n .join(' OR ');\n}\n\nfunction buildTenantMatchExpression(tenantField: string): string {\n return `${quoteIdent(tenantField)}::text = smrt_current_tenant_id()`;\n}\n\nfunction buildSelectPolicySql(\n target: TablePolicyTarget,\n permissions: string[],\n): string[] {\n const qualifiedTable = `${quoteIdent(target.schemaName)}.${quoteIdent(target.tableName)}`;\n const policyName = buildPolicyName(target.tableName, 'SELECT');\n const condition = `smrt_rls_bypass() OR ((${buildTenantMatchExpression(target.tenantField)}) AND (${buildPermissionExpression(permissions)}))`;\n\n return [\n `DROP POLICY IF EXISTS ${quoteIdent(policyName)} ON ${qualifiedTable}`,\n `CREATE POLICY ${quoteIdent(policyName)} ON ${qualifiedTable} FOR SELECT USING (${condition})`,\n ];\n}\n\nfunction buildInsertPolicySql(\n target: TablePolicyTarget,\n permissions: string[],\n): string[] {\n const qualifiedTable = `${quoteIdent(target.schemaName)}.${quoteIdent(target.tableName)}`;\n const policyName = buildPolicyName(target.tableName, 'INSERT');\n const condition = `smrt_rls_bypass() OR ((${buildTenantMatchExpression(target.tenantField)}) AND (${buildPermissionExpression(permissions)}))`;\n\n return [\n `DROP POLICY IF EXISTS ${quoteIdent(policyName)} ON ${qualifiedTable}`,\n `CREATE POLICY ${quoteIdent(policyName)} ON ${qualifiedTable} FOR INSERT WITH CHECK (${condition})`,\n ];\n}\n\nfunction buildUpdatePolicySql(\n target: TablePolicyTarget,\n permissions: string[],\n): string[] {\n const qualifiedTable = `${quoteIdent(target.schemaName)}.${quoteIdent(target.tableName)}`;\n const policyName = buildPolicyName(target.tableName, 'UPDATE');\n const condition = `smrt_rls_bypass() OR ((${buildTenantMatchExpression(target.tenantField)}) AND (${buildPermissionExpression(permissions)}))`;\n\n return [\n `DROP POLICY IF EXISTS ${quoteIdent(policyName)} ON ${qualifiedTable}`,\n `CREATE POLICY ${quoteIdent(policyName)} ON ${qualifiedTable} FOR UPDATE USING (${condition}) WITH CHECK (${condition})`,\n ];\n}\n\nfunction buildDeletePolicySql(\n target: TablePolicyTarget,\n permissions: string[],\n): string[] {\n const qualifiedTable = `${quoteIdent(target.schemaName)}.${quoteIdent(target.tableName)}`;\n const policyName = buildPolicyName(target.tableName, 'DELETE');\n const condition = `smrt_rls_bypass() OR ((${buildTenantMatchExpression(target.tenantField)}) AND (${buildPermissionExpression(permissions)}))`;\n\n return [\n `DROP POLICY IF EXISTS ${quoteIdent(policyName)} ON ${qualifiedTable}`,\n `CREATE POLICY ${quoteIdent(policyName)} ON ${qualifiedTable} FOR DELETE USING (${condition})`,\n ];\n}\n\nfunction buildHelperStatements(): string[] {\n return [\n [\n 'CREATE OR REPLACE FUNCTION smrt_rls_bypass()',\n 'RETURNS boolean',\n 'LANGUAGE sql',\n 'STABLE',\n 'AS $$',\n \" SELECT COALESCE(NULLIF(current_setting('smrt.system_context', true), ''), 'false')::boolean\",\n \" OR COALESCE(NULLIF(current_setting('smrt.super_admin_bypass', true), ''), 'false')::boolean\",\n '$$',\n ].join('\\n'),\n [\n 'CREATE OR REPLACE FUNCTION smrt_current_tenant_id()',\n 'RETURNS text',\n 'LANGUAGE sql',\n 'STABLE',\n 'AS $$',\n \" SELECT NULLIF(current_setting('smrt.tenant_id', true), '')\",\n '$$',\n ].join('\\n'),\n [\n 'CREATE OR REPLACE FUNCTION smrt_has_permission(required_permission text)',\n 'RETURNS boolean',\n 'LANGUAGE sql',\n 'STABLE',\n 'AS $$',\n ' SELECT smrt_rls_bypass()',\n \" OR jsonb_exists(COALESCE(NULLIF(current_setting('smrt.permissions', true), ''), '[]')::jsonb, required_permission)\",\n '$$',\n ].join('\\n'),\n ];\n}\n\nfunction addBindingToTarget(\n targets: Map<string, TablePolicyTarget>,\n binding: PostgresPermissionBinding,\n source: Pick<\n TablePolicyTarget,\n 'className' | 'collection' | 'qualifiedName'\n > = {},\n): void {\n const { schemaName, tableName } = parseTableReference(binding);\n const targetKey = `${schemaName}.${tableName}`;\n const existing = targets.get(targetKey);\n const tenantField = binding.tenantField ?? 'tenant_id';\n\n if (existing && existing.tenantField !== tenantField) {\n throw new Error(\n `Conflicting tenant fields for table '${targetKey}': '${existing.tenantField}' !== '${tenantField}'`,\n );\n }\n\n const target = existing ?? {\n actions: new Map<PostgresPermissionAction, Set<string>>(),\n ...source,\n schemaName,\n tableName,\n tenantField,\n };\n const action = normalizePostgresPermissionAction(binding.action);\n const permissions = target.actions.get(action) ?? new Set<string>();\n if (binding.permission) {\n permissions.add(binding.permission);\n }\n target.actions.set(action, permissions);\n targets.set(targetKey, target);\n}\n\nexport function generatePostgresPermissionSql(\n options: SmrtClassOptions = {},\n): GeneratePostgresPermissionSqlResult {\n const catalogService = PermissionCatalogService.create(options);\n const catalog = catalogService.getCatalog();\n const config = catalogService.getUsersConfig();\n const candidateTargets = new Map<string, TablePolicyTarget>();\n const skipped: PostgresPermissionPolicyReportItem[] = [];\n\n const autoCandidates = new Map<\n string,\n Array<{\n className?: string;\n collection: string;\n qualifiedName?: string;\n schemaName: string;\n tableName: string;\n tenantField: string;\n }>\n >();\n\n for (const metadata of ObjectRegistry.getAllObjectMetadata()) {\n const registered =\n ObjectRegistry.getClassByConstructor(metadata.constructor as any) ??\n ObjectRegistry.getClass(metadata.name);\n const tenantScoped = registered?.tenantScopedConfig;\n const manifestEntry = registered?.qualifiedName\n ? findManifestEntryByQualifiedName(registered.qualifiedName)\n : undefined;\n\n if (!tenantScoped) {\n skipped.push({\n className: metadata.name,\n qualifiedName: registered?.qualifiedName,\n reason: 'not tenant-scoped',\n });\n continue;\n }\n\n if (tenantScoped.mode !== 'required') {\n skipped.push({\n className: metadata.name,\n qualifiedName: registered?.qualifiedName,\n reason: `tenant mode '${tenantScoped.mode}' is not supported for automatic Postgres RLS generation`,\n });\n continue;\n }\n\n const rawTableName =\n registered?.schema?.tableName ?? manifestEntry?.schema?.tableName;\n if (!rawTableName) {\n skipped.push({\n className: metadata.name,\n qualifiedName: registered?.qualifiedName,\n reason: 'no schema table name available',\n });\n continue;\n }\n\n const parsedTable = parseTableReference({\n tableName: rawTableName,\n });\n const tableKey = `${parsedTable.schemaName}.${parsedTable.tableName}`;\n const objectConfig = manifestEntry?.decoratorConfig ?? metadata.config;\n const rawCollection = (objectConfig as { collection?: unknown } | undefined)\n ?.collection;\n const configuredCollection =\n typeof rawCollection === 'string' && rawCollection.length > 0\n ? rawCollection\n : undefined;\n const collection =\n configuredCollection ??\n manifestEntry?.collection ??\n `${toSnakeCase(metadata.name)}s`;\n\n const entries = autoCandidates.get(tableKey) ?? [];\n entries.push({\n className: metadata.name,\n collection,\n qualifiedName: registered?.qualifiedName,\n schemaName: parsedTable.schemaName,\n tableName: parsedTable.tableName,\n tenantField: toSnakeCase(tenantScoped.field),\n });\n autoCandidates.set(tableKey, entries);\n }\n\n for (const [tableKey, entries] of autoCandidates) {\n if (entries.length > 1) {\n for (const entry of entries) {\n skipped.push({\n className: entry.className,\n collection: entry.collection,\n qualifiedName: entry.qualifiedName,\n reason: `table '${tableKey}' is shared by multiple objects, so automatic policy generation was skipped`,\n schemaName: entry.schemaName,\n tableName: entry.tableName,\n });\n }\n continue;\n }\n\n const entry = entries[0];\n addBindingToTarget(\n candidateTargets,\n {\n action: 'SELECT',\n permission: `${entry.collection}.read`,\n schemaName: entry.schemaName,\n tableName: entry.tableName,\n tenantField: entry.tenantField,\n },\n entry,\n );\n addBindingToTarget(\n candidateTargets,\n {\n action: 'INSERT',\n permission: `${entry.collection}.create`,\n schemaName: entry.schemaName,\n tableName: entry.tableName,\n tenantField: entry.tenantField,\n },\n entry,\n );\n addBindingToTarget(\n candidateTargets,\n {\n action: 'UPDATE',\n permission: `${entry.collection}.update`,\n schemaName: entry.schemaName,\n tableName: entry.tableName,\n tenantField: entry.tenantField,\n },\n entry,\n );\n addBindingToTarget(\n candidateTargets,\n {\n action: 'DELETE',\n permission: `${entry.collection}.delete`,\n schemaName: entry.schemaName,\n tableName: entry.tableName,\n tenantField: entry.tenantField,\n },\n entry,\n );\n }\n\n const explicitBindings: PostgresPermissionBinding[] = [];\n for (const definition of catalog.permissions) {\n for (const binding of definition.postgres?.bindings ?? []) {\n explicitBindings.push(\n normalizePostgresPermissionBinding(binding, definition.slug),\n );\n }\n }\n for (const binding of config.permissions?.postgres?.bindings ?? []) {\n explicitBindings.push(normalizePostgresPermissionBinding(binding));\n }\n\n for (const binding of explicitBindings) {\n addBindingToTarget(candidateTargets, binding);\n }\n\n const statements = [...buildHelperStatements()];\n const targets = Array.from(candidateTargets.values())\n .sort((left, right) =>\n `${left.schemaName}.${left.tableName}`.localeCompare(\n `${right.schemaName}.${right.tableName}`,\n ),\n )\n .map((target) => ({\n actions: Object.fromEntries(\n Array.from(target.actions.entries()).map(([action, permissions]) => [\n action,\n Array.from(permissions).sort(),\n ]),\n ) as Partial<Record<PostgresPermissionAction, string[]>>,\n className: target.className,\n collection: target.collection,\n qualifiedName: target.qualifiedName,\n schemaName: target.schemaName,\n tableName: target.tableName,\n tenantField: target.tenantField,\n }));\n\n for (const target of Array.from(candidateTargets.values())) {\n const qualifiedTable = `${quoteIdent(target.schemaName)}.${quoteIdent(target.tableName)}`;\n statements.push(`ALTER TABLE ${qualifiedTable} ENABLE ROW LEVEL SECURITY`);\n statements.push(`ALTER TABLE ${qualifiedTable} FORCE ROW LEVEL SECURITY`);\n\n const selectPermissions = Array.from(\n target.actions.get('SELECT') ?? [],\n ).sort();\n const insertPermissions = Array.from(\n target.actions.get('INSERT') ?? [],\n ).sort();\n const updatePermissions = Array.from(\n target.actions.get('UPDATE') ?? [],\n ).sort();\n const deletePermissions = Array.from(\n target.actions.get('DELETE') ?? [],\n ).sort();\n\n if (selectPermissions.length > 0) {\n statements.push(...buildSelectPolicySql(target, selectPermissions));\n }\n if (insertPermissions.length > 0) {\n statements.push(...buildInsertPolicySql(target, insertPermissions));\n }\n if (updatePermissions.length > 0) {\n statements.push(...buildUpdatePolicySql(target, updatePermissions));\n }\n if (deletePermissions.length > 0) {\n statements.push(...buildDeletePolicySql(target, deletePermissions));\n }\n }\n\n return {\n bindings: explicitBindings,\n skipped,\n sql: `${statements.join(';\\n')};\\n`,\n statements,\n targets,\n };\n}\n\nexport async function applyPostgresPermissionPolicies(\n options: SmrtClassOptions = {},\n): Promise<GeneratePostgresPermissionSqlResult> {\n const permissions = await PermissionCollection.create(options);\n const databaseOptions = options.db ?? options.persistence;\n if (\n !isProbablyPostgres(databaseOptions, permissions.db as QueryableDatabase)\n ) {\n throw new Error(\n 'applyPostgresPermissionPolicies() requires a Postgres database connection.',\n );\n }\n\n const result = generatePostgresPermissionSql(options);\n for (const statement of result.statements) {\n try {\n await permissions.db.query(statement);\n } catch (error) {\n throw new Error(\n `Failed to apply Postgres permission policy statement:\\n${statement}`,\n {\n cause: error,\n },\n );\n }\n }\n\n return result;\n}\n","/**\n * TenantService - Manages tenant creation with configurable policies\n * @packageDocumentation\n */\n\nimport type { SmrtClassOptions } from '@happyvertical/smrt-core';\nimport { MembershipCollection } from '../collections/MembershipCollection.js';\nimport { RoleCollection } from '../collections/RoleCollection.js';\nimport { TenantCollection } from '../collections/TenantCollection.js';\nimport type { Membership } from '../models/Membership.js';\nimport type { Tenant } from '../models/Tenant.js';\nimport {\n DEFAULT_ROLE_SLUGS,\n DEFAULT_TENANT_POLICY,\n MembershipStatus,\n type TenantPolicy,\n} from '../types/index.js';\n\n/**\n * Result of tenant creation with ownership\n */\nexport interface TenantWithOwnershipResult {\n tenant: Tenant;\n membership: Membership;\n}\n\n/**\n * Result of ensureTenantForUser\n */\nexport interface EnsureTenantResult {\n /** The tenant (null if flexible mode and no existing tenant) */\n tenant: Tenant | null;\n /** The membership (null if no tenant) */\n membership: Membership | null;\n /** Whether a new tenant was created */\n created: boolean;\n}\n\n/**\n * TenantService manages tenant creation with configurable policies.\n *\n * Policies:\n * - `flexible`: No tenant created on signup, user can have zero tenants\n * - `personal`: Auto-create personal tenant on first login, can delete all\n * - `required`: Auto-create personal tenant, must keep at least one\n *\n * @example\n * ```typescript\n * const tenantService = new TenantService(options, {\n * mode: 'personal',\n * maxTenants: 5,\n * defaultName: 'My Workspace',\n * });\n * await tenantService.initialize();\n *\n * // During OIDC login\n * const { tenant, membership, created } = await tenantService.ensureTenantForUser(\n * user.id,\n * { email: user.email, name: user.name }\n * );\n *\n * // Creating additional tenants\n * if (await tenantService.canCreateTenant(user.id)) {\n * const { tenant } = await tenantService.createTenantWithOwnership(\n * user.id,\n * 'New Organization'\n * );\n * }\n * ```\n */\nexport class TenantService {\n private options: SmrtClassOptions;\n private policy: TenantPolicy;\n private tenantCollection!: TenantCollection;\n private membershipCollection!: MembershipCollection;\n private roleCollection!: RoleCollection;\n\n constructor(options: SmrtClassOptions, policy?: TenantPolicy) {\n this.options = options;\n this.policy = policy ?? DEFAULT_TENANT_POLICY;\n }\n\n /**\n * Initialize collections\n */\n async initialize(): Promise<void> {\n this.tenantCollection = await (TenantCollection as any).create(\n this.options,\n );\n this.membershipCollection = await (MembershipCollection as any).create(\n this.options,\n );\n this.roleCollection = await (RoleCollection as any).create(this.options);\n\n // Seed system roles if needed\n await this.roleCollection.seedSystemRoles();\n }\n\n /**\n * Get the current policy\n */\n getPolicy(): TenantPolicy {\n return { ...this.policy };\n }\n\n /**\n * Create a tenant and make the user the owner\n *\n * @param userId - The user to make owner\n * @param name - Tenant name\n * @param options - Optional slug override\n * @returns The created tenant and membership\n */\n async createTenantWithOwnership(\n userId: string,\n name: string,\n options?: { slug?: string },\n ): Promise<TenantWithOwnershipResult> {\n // Check if user can create\n if (!(await this.canCreateTenant(userId))) {\n throw new Error(\n `User has reached maximum tenant limit (${this.policy.maxTenants})`,\n );\n }\n\n // Create tenant\n const tenant = await this.tenantCollection.create({\n name,\n slug: options?.slug,\n });\n await tenant.save();\n\n // Get owner role\n const ownerRole = await this.roleCollection.findBySlug(\n DEFAULT_ROLE_SLUGS.OWNER,\n );\n if (!ownerRole) {\n throw new Error('Owner role not found - run seedSystemRoles first');\n }\n\n // Create ownership membership\n const membership = await this.membershipCollection.create({\n userId,\n tenantId: tenant.id as string,\n roleId: ownerRole.id as string,\n status: MembershipStatus.ACTIVE,\n });\n await membership.save();\n\n return { tenant, membership };\n }\n\n /**\n * Check if a user can create a new tenant\n *\n * Returns false if maxTenants limit is reached (0 = unlimited)\n */\n async canCreateTenant(userId: string): Promise<boolean> {\n if (this.policy.maxTenants === 0) {\n return true; // Unlimited\n }\n\n const ownerRole = await this.roleCollection.findBySlug(\n DEFAULT_ROLE_SLUGS.OWNER,\n );\n if (!ownerRole) {\n // Fail closed if owner role is not configured\n return false;\n }\n\n const memberships =\n await this.membershipCollection.findActiveByUser(userId);\n const ownedCount = memberships.filter(\n (m) => m.roleId === ownerRole.id,\n ).length;\n\n return ownedCount < this.policy.maxTenants;\n }\n\n /**\n * Get a specific error message explaining why a tenant cannot be deleted.\n *\n * @returns Error message if deletion is not allowed, or null if allowed.\n */\n private async getDeleteTenantError(\n userId: string,\n tenantId: string,\n ): Promise<string | null> {\n // Check user has an active membership for this tenant\n const membership = await this.membershipCollection.findByUserAndTenant(\n userId,\n tenantId,\n );\n if (!membership || membership.status !== MembershipStatus.ACTIVE) {\n return 'You are not a member of this tenant or it does not exist.';\n }\n\n const ownerRole = await this.roleCollection.findBySlug(\n DEFAULT_ROLE_SLUGS.OWNER,\n );\n if (!ownerRole) {\n return 'Owner role is not configured. Cannot determine deletion permissions.';\n }\n\n if (membership.roleId !== ownerRole.id) {\n return 'Only the tenant owner can delete this tenant.';\n }\n\n // For 'required' policy, check if this is the last tenant\n if (this.policy.mode === 'required') {\n const allMemberships =\n await this.membershipCollection.findActiveByUser(userId);\n const ownerMemberships = allMemberships.filter(\n (m) => m.roleId === ownerRole.id,\n );\n\n if (ownerMemberships.length <= 1) {\n return 'Cannot delete your last tenant. Policy requires at least one tenant.';\n }\n }\n\n return null;\n }\n\n /**\n * Check if a user can delete a specific tenant\n *\n * Returns false if:\n * - User is not an active member\n * - User is not the owner\n * - Policy is 'required' and this is the last tenant\n */\n async canDeleteTenant(userId: string, tenantId: string): Promise<boolean> {\n const error = await this.getDeleteTenantError(userId, tenantId);\n return error === null;\n }\n\n /**\n * Delete a tenant\n *\n * Note: This does not cascade delete related records (memberships, etc.).\n * The caller should handle cleanup of related data if needed.\n *\n * @throws Error if user cannot delete the tenant (with specific reason)\n */\n async deleteTenant(userId: string, tenantId: string): Promise<void> {\n const error = await this.getDeleteTenantError(userId, tenantId);\n if (error) {\n throw new Error(error);\n }\n\n const tenant = await this.tenantCollection.get(tenantId);\n if (tenant) {\n await tenant.delete();\n }\n }\n\n /**\n * Ensure a tenant exists for the user based on policy\n *\n * Called during OIDC login to apply tenant policy:\n * - `flexible`: Returns first existing tenant or null (no auto-create)\n * - `personal`/`required`: Creates default tenant if none exists\n *\n * @param userId - The user ID\n * @param userInfo - User info for naming the auto-created tenant\n * @returns Tenant and membership (may be null in flexible mode)\n */\n async ensureTenantForUser(\n userId: string,\n userInfo: { email?: string; name?: string },\n ): Promise<EnsureTenantResult> {\n // Get existing memberships\n const memberships =\n await this.membershipCollection.findActiveByUser(userId);\n\n // If user has tenants, return the first one\n if (memberships.length > 0) {\n const firstMembership = memberships[0];\n const tenant = await this.tenantCollection.get(\n firstMembership.tenantId as string,\n );\n\n return {\n tenant: tenant ?? null,\n membership: firstMembership,\n created: false,\n };\n }\n\n // No existing tenants - apply policy\n if (this.policy.mode === 'flexible') {\n // Flexible mode: don't auto-create\n return {\n tenant: null,\n membership: null,\n created: false,\n };\n }\n\n // Personal or required mode: create default tenant\n // Use user's name for personalized tenant name, or fall back to policy default\n const tenantName = userInfo.name\n ? `${userInfo.name}'s Workspace`\n : this.policy.defaultName;\n\n const { tenant, membership } = await this.createTenantWithOwnership(\n userId,\n tenantName,\n );\n\n return {\n tenant,\n membership,\n created: true,\n };\n }\n\n /**\n * Get all tenants for a user (where they are owner)\n */\n async getOwnedTenants(userId: string): Promise<Tenant[]> {\n const ownerRole = await this.roleCollection.findBySlug(\n DEFAULT_ROLE_SLUGS.OWNER,\n );\n if (!ownerRole) {\n return [];\n }\n\n const memberships =\n await this.membershipCollection.findActiveByUser(userId);\n const ownerMemberships = memberships.filter(\n (m) => m.roleId === ownerRole.id,\n );\n\n // Fetch all tenants in parallel to avoid N+1 queries\n const tenantPromises = ownerMemberships.map((m) =>\n this.tenantCollection.get(m.tenantId as string),\n );\n const tenantsOrNull = await Promise.all(tenantPromises);\n\n return tenantsOrNull.filter((tenant): tenant is Tenant => tenant !== null);\n }\n\n /**\n * Static factory method\n */\n static async create(\n options: SmrtClassOptions,\n policy?: TenantPolicy,\n ): Promise<TenantService> {\n const service = new TenantService(options, policy);\n await service.initialize();\n return service;\n }\n}\n"],"names":["__decorateClass","toSnakeCase","entry","m","tenant"],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "version": "1.0.0",
3
- "timestamp": 1782183804308,
3
+ "timestamp": 1782198558582,
4
4
  "packageName": "@happyvertical/smrt-users",
5
- "packageVersion": "0.31.0",
5
+ "packageVersion": "0.32.0",
6
6
  "objects": {
7
7
  "@happyvertical/smrt-users:UsersCliAuthRequestCollection": {
8
8
  "name": "userscliauthrequestcollection",
@@ -34,6 +34,17 @@ export interface TenantPermissionInheritanceResult {
34
34
  contributingTenantIds: string[];
35
35
  /** Whether inheritance was active (at least one tenant in chain had inheritPermissions: true) */
36
36
  inheritanceActive: boolean;
37
+ /**
38
+ * Permission slugs explicitly DENY'd by a `TenantPermissionOverride` anywhere
39
+ * in the tenant hierarchy. These are a HARD, tenant-wide block:
40
+ * `resolvePermissions` subtracts them AFTER role + group grants are applied,
41
+ * so a tenant-DENY overrides a permission a role/group otherwise grants. A
42
+ * more-specific membership-override GRANT can still re-add a slug listed here;
43
+ * a membership-override DENY stays absolute. (This set is independent of the
44
+ * net `permissions` above, which only reflects DENY's effect on the inherited
45
+ * cascade.)
46
+ */
47
+ deniedPermissions: Set<string>;
37
48
  }
38
49
  /**
39
50
  * PermissionResolver resolves the effective permissions for a user in a tenant.
@@ -121,13 +132,23 @@ export declare class PermissionResolver {
121
132
  cascades: boolean;
122
133
  }>>;
123
134
  /**
124
- * Resolve all effective permissions for a user in a tenant
135
+ * Resolve all effective permissions for a user in a tenant.
136
+ *
137
+ * Precedence (broad -> specific, most-specific wins):
138
+ * tenant-inherited (cascade)
139
+ * -> role
140
+ * -> group roles
141
+ * -> tenant-DENY (removes; overrides role/group grants, tenant-wide)
142
+ * -> membership GRANT (re-adds; most specific, can win over a tenant-DENY)
143
+ * -> membership DENY (absolute; always wins)
125
144
  *
126
145
  * Algorithm:
127
146
  * 1. Get membership and collect all permission IDs from all sources
128
147
  * 2. Batch fetch all permissions in a single query
129
- * 3. Apply permissions from role, groups, and overrides
130
- * 4. DENY overrides take precedence over GRANT
148
+ * 3. Apply permissions from role, then groups
149
+ * 4. Subtract tenant-level DENY'd slugs (hard tenant-wide block)
150
+ * 5. Apply membership GRANT overrides (can re-add a tenant-DENY'd slug)
151
+ * 6. Subtract membership DENY overrides (absolute precedence)
131
152
  */
132
153
  resolvePermissions(userId: string, tenantId: string, options?: PermissionResolutionOptions): Promise<PermissionResolutionResult>;
133
154
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"PermissionResolver.d.ts","sourceRoot":"","sources":["../../src/services/PermissionResolver.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AASjE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAElD;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACzC,sCAAsC;IACtC,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACzB,wCAAwC;IACxC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,8BAA8B;IAC9B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,6CAA6C;IAC7C,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,uCAAuC;IACvC,mBAAmB,EAAE,MAAM,EAAE,CAAC;CAC/B;AAED,MAAM,WAAW,2BAA2B;IAC1C;;;;OAIG;IACH,UAAU,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;CAChC;AAED;;GAEG;AACH,MAAM,WAAW,iCAAiC;IAChD,wEAAwE;IACxE,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACzB,2DAA2D;IAC3D,qBAAqB,EAAE,MAAM,EAAE,CAAC;IAChC,iGAAiG;IACjG,iBAAiB,EAAE,OAAO,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,OAAO,CAAmB;IAClC,OAAO,CAAC,oBAAoB,CAAwB;IACpD,OAAO,CAAC,wBAAwB,CAA4B;IAC5D,OAAO,CAAC,4BAA4B,CAAgC;IACpE,OAAO,CAAC,qBAAqB,CAAyB;IACtD,OAAO,CAAC,mBAAmB,CAAuB;IAClD,OAAO,CAAC,oBAAoB,CAAwB;IACpD,OAAO,CAAC,gBAAgB,CAAoB;IAC5C,OAAO,CAAC,kCAAkC,CAAsC;gBAEpE,OAAO,EAAE,gBAAgB;IAIrC;;;;;;;;;OASG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IA+BjC;;;;;;;;;;;;OAYG;IACG,wBAAwB,CAC5B,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,iCAAiC,CAAC;IA6G7C;;OAEG;IACG,yBAAyB,CAC7B,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAmC3E;;;;;;;;OAQG;IACG,kBAAkB,CACtB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,2BAAgC,GACxC,OAAO,CAAC,0BAA0B,CAAC;IA6ItC;;OAEG;IACG,aAAa,CACjB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,cAAc,EAAE,MAAM,EACtB,OAAO,GAAE,2BAAgC,GACxC,OAAO,CAAC,OAAO,CAAC;IAKnB;;OAEG;IACG,iBAAiB,CACrB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,eAAe,EAAE,MAAM,EAAE,EACzB,OAAO,GAAE,2BAAgC,GACxC,OAAO,CAAC,OAAO,CAAC;IAKnB;;OAEG;IACG,gBAAgB,CACpB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,eAAe,EAAE,MAAM,EAAE,EACzB,OAAO,GAAE,2BAAgC,GACxC,OAAO,CAAC,OAAO,CAAC;IAKnB;;OAEG;WACU,MAAM,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,kBAAkB,CAAC;CAK5E"}
1
+ {"version":3,"file":"PermissionResolver.d.ts","sourceRoot":"","sources":["../../src/services/PermissionResolver.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import { SmrtClassOptions } from '@happyvertical/smrt-core';
2
2
  import { CreateSessionOptions } from '../collections/SessionCollection.js';
3
3
  import { Membership } from '../models/Membership.js';
4
+ import { Session } from '../models/Session.js';
4
5
  import { User } from '../models/User.js';
5
6
  /**
6
7
  * Session context with user and permissions
@@ -17,6 +18,28 @@ export interface SessionContext {
17
18
  /** Session ID */
18
19
  sessionId: string;
19
20
  }
21
+ /**
22
+ * Result of {@link SessionService.switchTenant}.
23
+ *
24
+ * A successful switch into a non-null tenant ROTATES the session id (#1354
25
+ * follow-up): a brand-new {@link Session} is minted and the old one is revoked,
26
+ * so a captured pre-switch id stops validating. Callers MUST persist `sessionId`
27
+ * (e.g. re-set the session cookie) after a rotation.
28
+ */
29
+ export interface SwitchTenantResult {
30
+ /** Whether the switch succeeded (true also for a `null` clear). */
31
+ switched: boolean;
32
+ /**
33
+ * The session id to use going forward: the NEW id after a rotation, the
34
+ * unchanged id after a `null` clear, or `null` when the switch failed
35
+ * (unknown session or non-member — fail-closed).
36
+ */
37
+ sessionId: string | null;
38
+ /** The resulting session (new on rotation; existing on clear; null on failure). */
39
+ session: Session | null;
40
+ /** True only when a fresh session id was minted (non-null tenant switch). */
41
+ rotated: boolean;
42
+ }
20
43
  /**
21
44
  * Options for SessionService
22
45
  */
@@ -106,15 +129,29 @@ export declare class SessionService {
106
129
  * query, so it must never be set to a tenant the session's user is not an
107
130
  * active member of — otherwise a caller could read/write another tenant's data
108
131
  * by feeding an arbitrary id here (e.g. straight from untrusted form data).
109
- * Fail-closed (#1400): returns `false` without switching when the session is
110
- * unknown or the user has no active membership in the target tenant. Passing
111
- * `null` clears the tenant context and is always allowed.
132
+ *
133
+ * Fail-closed (#1400): the user's ACTIVE membership in the target tenant is
134
+ * verified BEFORE any write. A non-member switch returns
135
+ * `{ switched: false, ... }` and mutates nothing.
136
+ *
137
+ * Session-id ROTATION (#1354 follow-up): a successful switch into a non-null
138
+ * tenant mints a BRAND-NEW session (fresh secure id, fresh TTL) for the same
139
+ * user with the new tenant, then REVOKES the old session — so any captured
140
+ * pre-switch session id immediately stops validating, shrinking the blast
141
+ * radius of a leaked id across a privilege/tenant boundary. The device context
142
+ * (user agent, IP, custom data) carries over to the new session. Callers MUST
143
+ * persist the returned `sessionId` (e.g. re-set the cookie).
144
+ *
145
+ * Passing `null` clears the tenant context, is always allowed, and stays
146
+ * in-place (no rotation — there is no privilege boundary being crossed).
147
+ *
148
+ * @returns A {@link SwitchTenantResult}; check `switched` for success.
112
149
  */
113
- switchTenant(sessionId: string, tenantId: string | null): Promise<boolean>;
150
+ switchTenant(sessionId: string, tenantId: string | null): Promise<SwitchTenantResult>;
114
151
  /**
115
152
  * Get all active sessions for a user (for "manage sessions" UI)
116
153
  */
117
- getUserSessions(userId: string): Promise<import('../index.js').Session[]>;
154
+ getUserSessions(userId: string): Promise<Session[]>;
118
155
  /**
119
156
  * Clean up expired sessions (run periodically)
120
157
  */
@@ -1 +1 @@
1
- {"version":3,"file":"SessionService.d.ts","sourceRoot":"","sources":["../../src/services/SessionService.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAEjE,OAAO,EACL,KAAK,oBAAoB,EAE1B,MAAM,qCAAqC,CAAC;AAE7C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAE1D,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAG9C;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,sBAAsB;IACtB,IAAI,EAAE,IAAI,CAAC;IACX,oEAAoE;IACpE,UAAU,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IAC/B,gCAAgC;IAChC,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,sCAAsC;IACtC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,iBAAiB;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAsB,SAAQ,gBAAgB;IAC7D,uDAAuD;IACvD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mCAAmC;IACnC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iEAAiE;IACjE,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,iBAAiB,CAAqB;IAC9C,OAAO,CAAC,cAAc,CAAkB;IACxC,OAAO,CAAC,oBAAoB,CAAwB;IACpD,OAAO,CAAC,kBAAkB,CAAsB;IAChD,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,UAAU,CAAU;gBAEhB,OAAO,EAAE,qBAAqB;IAM1C;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAWjC;;;;;;;OAOG;IACG,aAAa,CACjB,MAAM,EAAE,MAAM,EACd,QAAQ,CAAC,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,GACtC,OAAO,CAAC,MAAM,CAAC;IAalB;;;;OAIG;IACG,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IA6C3E;;OAEG;IACH,WAAW;IAIX;;OAEG;IACG,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAIzD;;OAEG;IACG,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAIzD;;OAEG;IACG,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAI7D;;;;;;;;;;OAUG;IACG,YAAY,CAChB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,GAAG,IAAI,GACtB,OAAO,CAAC,OAAO,CAAC;IAiBnB;;OAEG;IACG,eAAe,CAAC,MAAM,EAAE,MAAM;IAIpC;;OAEG;IACG,sBAAsB,IAAI,OAAO,CAAC,MAAM,CAAC;IAI/C;;OAEG;IACG,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAM5E;;OAEG;IACG,cAAc,CAAC,CAAC,EACpB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IAIzB;;OAEG;IACG,cAAc,CAClB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,OAAO,GACb,OAAO,CAAC,OAAO,CAAC;IAInB;;OAEG;WACU,MAAM,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,cAAc,CAAC;CAK7E"}
1
+ {"version":3,"file":"SessionService.d.ts","sourceRoot":"","sources":["../../src/services/SessionService.ts"],"names":[],"mappings":"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"}