@open-mercato/enterprise 0.6.8-develop.7100.1.fbf66fca35 → 0.7.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 (36) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/record_locks/backend/settings/record-locks/page.js +2 -2
  3. package/dist/modules/record_locks/backend/settings/record-locks/page.js.map +2 -2
  4. package/dist/modules/security/api/interceptors.js +2 -5
  5. package/dist/modules/security/api/interceptors.js.map +2 -2
  6. package/dist/modules/security/api/mfa/_shared.js +0 -1
  7. package/dist/modules/security/api/mfa/_shared.js.map +2 -2
  8. package/dist/modules/security/data/entities.js +0 -18
  9. package/dist/modules/security/data/entities.js.map +2 -2
  10. package/dist/modules/security/index.js +0 -3
  11. package/dist/modules/security/index.js.map +2 -2
  12. package/dist/modules/security/lib/enforcement-redirect.js +2 -28
  13. package/dist/modules/security/lib/enforcement-redirect.js.map +2 -2
  14. package/dist/modules/security/lib/security-config.js +0 -27
  15. package/dist/modules/security/lib/security-config.js.map +2 -2
  16. package/dist/modules/security/services/SudoChallengeService.js +41 -130
  17. package/dist/modules/security/services/SudoChallengeService.js.map +2 -2
  18. package/dist/modules/sso/backend/page.js +0 -1
  19. package/dist/modules/sso/backend/page.js.map +2 -2
  20. package/package.json +6 -7
  21. package/src/modules/record_locks/backend/settings/record-locks/page.tsx +4 -4
  22. package/src/modules/security/api/interceptors.ts +2 -6
  23. package/src/modules/security/api/mfa/_shared.ts +0 -1
  24. package/src/modules/security/data/entities.ts +0 -18
  25. package/src/modules/security/index.ts +0 -4
  26. package/src/modules/security/lib/enforcement-redirect.ts +2 -30
  27. package/src/modules/security/lib/security-config.ts +0 -29
  28. package/src/modules/security/migrations/.snapshot-open-mercato.json +276 -849
  29. package/src/modules/security/services/SudoChallengeService.ts +49 -170
  30. package/src/modules/sso/backend/page.tsx +0 -2
  31. package/dist/modules/security/lib/mfaCompletionRoutes.js +0 -17
  32. package/dist/modules/security/lib/mfaCompletionRoutes.js.map +0 -7
  33. package/dist/modules/security/migrations/Migration20260811143757_security.js +0 -13
  34. package/dist/modules/security/migrations/Migration20260811143757_security.js.map +0 -7
  35. package/src/modules/security/lib/mfaCompletionRoutes.ts +0 -25
  36. package/src/modules/security/migrations/Migration20260811143757_security.ts +0 -13
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/security/lib/security-config.ts"],
4
- "sourcesContent": ["import { parseBooleanWithDefault } from '@open-mercato/shared/lib/boolean'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst TOTP_PERIOD_SECONDS = 30\nconst TOTP_DIGITS = 6\nconst DEFAULT_SETUP_TTL_MS = 10 * 60 * 1000\nconst DEFAULT_WEBAUTHN_CHALLENGE_TTL_MS = 5 * 60 * 1000\nconst DEFAULT_SUDO_PENDING_CHALLENGE_TTL_MS = 10 * 60 * 1000\nconst MIN_SUDO_TTL_SECONDS = 30\n\nconst securityBypassLogger = createLogger('security').child({ component: 'mfa-emergency-bypass' })\nlet startupBypassWarningEmitted = false\n\nexport type SecurityModuleConfig = {\n totp: {\n issuer: string\n window: number\n digits: number\n periodSeconds: number\n setupTtlMs: number\n }\n otpEmail: {\n expirySeconds: number\n maxAttempts: number\n setupTtlMs: number\n challengeTtlMs: number\n subject: string\n }\n mfa: {\n challengeTtlMs: number\n maxAttempts: number\n emergencyBypass: boolean\n }\n sudo: {\n minTtlSeconds: number\n defaultTtlSeconds: number\n maxTtlSeconds: number\n pendingChallengeTtlMs: number\n }\n webauthn: {\n rpName: string\n rpId: string\n expectedOrigins: string[]\n setupTtlMs: number\n challengeTtlMs: number\n }\n recoveryCodes: {\n count: number\n bcryptCost: number\n }\n}\n\nexport const defaultSecurityModuleConfig: SecurityModuleConfig = {\n totp: {\n issuer: 'Open Mercato',\n window: 1,\n digits: TOTP_DIGITS,\n periodSeconds: TOTP_PERIOD_SECONDS,\n setupTtlMs: DEFAULT_SETUP_TTL_MS,\n },\n otpEmail: {\n expirySeconds: 600,\n maxAttempts: 5,\n setupTtlMs: DEFAULT_SETUP_TTL_MS,\n challengeTtlMs: 600 * 1000,\n subject: 'Your Open Mercato verification code',\n },\n mfa: {\n challengeTtlMs: 600 * 1000,\n maxAttempts: 5,\n emergencyBypass: false,\n },\n sudo: {\n minTtlSeconds: MIN_SUDO_TTL_SECONDS,\n defaultTtlSeconds: 300,\n maxTtlSeconds: 1800,\n pendingChallengeTtlMs: DEFAULT_SUDO_PENDING_CHALLENGE_TTL_MS,\n },\n webauthn: {\n rpName: 'Open Mercato',\n rpId: 'localhost',\n expectedOrigins: ['http://localhost:3000'],\n setupTtlMs: DEFAULT_SETUP_TTL_MS,\n challengeTtlMs: DEFAULT_WEBAUTHN_CHALLENGE_TTL_MS,\n },\n recoveryCodes: {\n count: 10,\n bcryptCost: 10,\n },\n}\n\nexport function readSecurityModuleConfig(\n env: NodeJS.ProcessEnv = process.env,\n): SecurityModuleConfig {\n const otpExpirySeconds = parsePositiveInteger(env.OM_SECURITY_OTP_EXPIRY_SECONDS)\n ?? defaultSecurityModuleConfig.otpEmail.expirySeconds\n const otpMaxAttempts = parsePositiveInteger(env.OM_SECURITY_OTP_MAX_ATTEMPTS)\n ?? defaultSecurityModuleConfig.otpEmail.maxAttempts\n const sudoMaxTtlSeconds = Math.max(\n parsePositiveInteger(env.OM_SECURITY_SUDO_MAX_TTL) ?? defaultSecurityModuleConfig.sudo.maxTtlSeconds,\n defaultSecurityModuleConfig.sudo.minTtlSeconds,\n )\n const sudoDefaultTtlSeconds = clamp(\n parsePositiveInteger(env.OM_SECURITY_SUDO_DEFAULT_TTL) ?? defaultSecurityModuleConfig.sudo.defaultTtlSeconds,\n defaultSecurityModuleConfig.sudo.minTtlSeconds,\n sudoMaxTtlSeconds,\n )\n const webauthnRpId = readText(env.OM_SECURITY_WEBAUTHN_RP_ID)\n ?? readHostname(env.APP_URL)\n ?? readHostname(env.NEXT_PUBLIC_APP_URL)\n ?? defaultSecurityModuleConfig.webauthn.rpId\n\n return {\n totp: {\n ...defaultSecurityModuleConfig.totp,\n issuer: readText(env.OM_SECURITY_TOTP_ISSUER) ?? defaultSecurityModuleConfig.totp.issuer,\n window: parseNonNegativeInteger(env.OM_SECURITY_TOTP_WINDOW) ?? defaultSecurityModuleConfig.totp.window,\n },\n otpEmail: {\n ...defaultSecurityModuleConfig.otpEmail,\n expirySeconds: otpExpirySeconds,\n maxAttempts: otpMaxAttempts,\n challengeTtlMs: otpExpirySeconds * 1000,\n },\n mfa: {\n challengeTtlMs: otpExpirySeconds * 1000,\n maxAttempts: otpMaxAttempts,\n emergencyBypass: parseBooleanWithDefault(\n env.OM_SECURITY_MFA_EMERGENCY_BYPASS,\n defaultSecurityModuleConfig.mfa.emergencyBypass,\n ),\n },\n sudo: {\n ...defaultSecurityModuleConfig.sudo,\n defaultTtlSeconds: sudoDefaultTtlSeconds,\n maxTtlSeconds: sudoMaxTtlSeconds,\n },\n webauthn: {\n ...defaultSecurityModuleConfig.webauthn,\n rpName: readText(env.OM_SECURITY_WEBAUTHN_RP_NAME) ?? defaultSecurityModuleConfig.webauthn.rpName,\n rpId: webauthnRpId,\n expectedOrigins: readWebAuthnOrigins(env, webauthnRpId),\n },\n recoveryCodes: {\n ...defaultSecurityModuleConfig.recoveryCodes,\n count: parsePositiveInteger(env.OM_SECURITY_RECOVERY_CODE_COUNT)\n ?? parsePositiveInteger(env.OM_SECURITY_RECOVERY_CODES_COUNT)\n ?? defaultSecurityModuleConfig.recoveryCodes.count,\n },\n }\n}\n\nexport function resolveSecurityModuleConfigForRequest(\n baseConfig: SecurityModuleConfig,\n request?: Request,\n env: NodeJS.ProcessEnv = process.env,\n): SecurityModuleConfig {\n const explicitRpId = readText(env.OM_SECURITY_WEBAUTHN_RP_ID)\n const requestRpId = readRequestHostname(request)\n const rpId = explicitRpId ?? requestRpId ?? baseConfig.webauthn.rpId\n const expectedOrigins = readWebAuthnOrigins(env, rpId, request, baseConfig.webauthn.expectedOrigins)\n\n if (rpId === baseConfig.webauthn.rpId && areOriginsEqual(expectedOrigins, baseConfig.webauthn.expectedOrigins)) {\n return baseConfig\n }\n\n return {\n ...baseConfig,\n webauthn: {\n ...baseConfig.webauthn,\n rpId,\n expectedOrigins,\n },\n }\n}\n\nexport function emitMfaEmergencyBypassActiveWarning(context: string, extra?: Record<string, unknown>): void {\n securityBypassLogger.warn('MFA emergency bypass is active', {\n emergencyBypass: true,\n context,\n ...extra,\n })\n}\n\nexport function emitMfaEmergencyBypassStartupWarningIfNeeded(env: NodeJS.ProcessEnv = process.env): void {\n if (startupBypassWarningEmitted) return\n const bypass = parseBooleanWithDefault(\n env.OM_SECURITY_MFA_EMERGENCY_BYPASS,\n defaultSecurityModuleConfig.mfa.emergencyBypass,\n )\n if (!bypass) return\n startupBypassWarningEmitted = true\n emitMfaEmergencyBypassActiveWarning('startup', { startup: true })\n}\n\nexport function resetMfaEmergencyBypassStartupWarningForTests(): void {\n startupBypassWarningEmitted = false\n}\n\nemitMfaEmergencyBypassStartupWarningIfNeeded()\n\nexport function readSecuritySetupTokenSecret(env: NodeJS.ProcessEnv = process.env): string {\n const secret = readText(env.OM_SECURITY_MFA_SETUP_SECRET)\n ?? readText(env.AUTH_JWT_SECRET)\n ?? readText(env.AUTH_SECRET)\n ?? readText(env.JWT_SECRET)\n\n if (secret) {\n return secret\n }\n\n throw new Error(\n 'Security MFA setup tokens require OM_SECURITY_MFA_SETUP_SECRET, AUTH_JWT_SECRET, AUTH_SECRET, or JWT_SECRET.',\n )\n}\n\nfunction readWebAuthnOrigins(\n env: NodeJS.ProcessEnv,\n rpId: string,\n request?: Request,\n fallbackOrigins?: string[],\n): string[] {\n const fromList = (env.OM_SECURITY_WEBAUTHN_ORIGINS ?? '')\n .split(',')\n .map((value) => value.trim())\n .filter((value) => value.length > 0)\n const explicitOrigin = readText(env.OM_SECURITY_WEBAUTHN_ORIGIN)\n\n if (fromList.length > 0 || explicitOrigin) {\n return [...new Set([...fromList, ...(explicitOrigin ? [explicitOrigin] : [])])]\n }\n\n const requestOrigin = readRequestOrigin(request)\n const singletons = [\n requestOrigin,\n env.APP_URL,\n env.NEXT_PUBLIC_APP_URL,\n ]\n .map((value) => readText(value))\n .filter((value): value is string => typeof value === 'string')\n\n const origins = [...new Set([...fromList, ...singletons])]\n if (origins.length > 0) {\n return origins\n }\n\n if (fallbackOrigins && fallbackOrigins.length > 0) {\n return [...fallbackOrigins]\n }\n\n if (rpId === 'localhost') {\n return [...defaultSecurityModuleConfig.webauthn.expectedOrigins]\n }\n\n return [`https://${rpId}`]\n}\n\nfunction readText(value: string | undefined): string | undefined {\n const trimmed = value?.trim()\n return trimmed ? trimmed : undefined\n}\n\nfunction readHostname(value: string | undefined): string | undefined {\n const trimmed = readText(value)\n if (!trimmed) return undefined\n\n try {\n const url = new URL(trimmed)\n return readText(url.hostname)\n } catch {\n return undefined\n }\n}\n\nfunction readRequestHostname(request: Request | undefined): string | undefined {\n const url = readRequestUrl(request)\n return url ? readText(url.hostname) : undefined\n}\n\nfunction readRequestOrigin(request: Request | undefined): string | undefined {\n const url = readRequestUrl(request)\n return url ? readText(url.origin) : undefined\n}\n\nfunction readRequestUrl(request: Request | undefined): URL | null {\n if (!request) return null\n\n const forwardedHost = readForwardedHeader(request.headers.get('x-forwarded-host'))\n const forwardedProto = readForwardedHeader(request.headers.get('x-forwarded-proto'))\n if (forwardedHost) {\n const protocol = forwardedProto ?? 'https'\n try {\n return new URL(`${protocol}://${forwardedHost}`)\n } catch {\n return null\n }\n }\n\n try {\n return new URL(request.url)\n } catch {\n return null\n }\n}\n\nfunction readForwardedHeader(value: string | null): string | undefined {\n const trimmed = readText(value ?? undefined)\n if (!trimmed) return undefined\n const [first] = trimmed.split(',')\n return readText(first)\n}\n\nfunction areOriginsEqual(left: string[], right: string[]): boolean {\n if (left.length !== right.length) return false\n return left.every((value, index) => value === right[index])\n}\n\nfunction parsePositiveInteger(value: string | undefined): number | undefined {\n if (!value) return undefined\n const parsed = Number(value)\n if (!Number.isFinite(parsed) || parsed <= 0) return undefined\n return Math.trunc(parsed)\n}\n\nfunction parseNonNegativeInteger(value: string | undefined): number | undefined {\n if (!value) return undefined\n const parsed = Number(value)\n if (!Number.isFinite(parsed) || parsed < 0) return undefined\n return Math.trunc(parsed)\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max)\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { parseBooleanWithDefault } from '@open-mercato/shared/lib/boolean'\n\nconst TOTP_PERIOD_SECONDS = 30\nconst TOTP_DIGITS = 6\nconst DEFAULT_SETUP_TTL_MS = 10 * 60 * 1000\nconst DEFAULT_WEBAUTHN_CHALLENGE_TTL_MS = 5 * 60 * 1000\nconst DEFAULT_SUDO_PENDING_CHALLENGE_TTL_MS = 10 * 60 * 1000\nconst MIN_SUDO_TTL_SECONDS = 30\n\nexport type SecurityModuleConfig = {\n totp: {\n issuer: string\n window: number\n digits: number\n periodSeconds: number\n setupTtlMs: number\n }\n otpEmail: {\n expirySeconds: number\n maxAttempts: number\n setupTtlMs: number\n challengeTtlMs: number\n subject: string\n }\n mfa: {\n challengeTtlMs: number\n maxAttempts: number\n emergencyBypass: boolean\n }\n sudo: {\n minTtlSeconds: number\n defaultTtlSeconds: number\n maxTtlSeconds: number\n pendingChallengeTtlMs: number\n }\n webauthn: {\n rpName: string\n rpId: string\n expectedOrigins: string[]\n setupTtlMs: number\n challengeTtlMs: number\n }\n recoveryCodes: {\n count: number\n bcryptCost: number\n }\n}\n\nexport const defaultSecurityModuleConfig: SecurityModuleConfig = {\n totp: {\n issuer: 'Open Mercato',\n window: 1,\n digits: TOTP_DIGITS,\n periodSeconds: TOTP_PERIOD_SECONDS,\n setupTtlMs: DEFAULT_SETUP_TTL_MS,\n },\n otpEmail: {\n expirySeconds: 600,\n maxAttempts: 5,\n setupTtlMs: DEFAULT_SETUP_TTL_MS,\n challengeTtlMs: 600 * 1000,\n subject: 'Your Open Mercato verification code',\n },\n mfa: {\n challengeTtlMs: 600 * 1000,\n maxAttempts: 5,\n emergencyBypass: false,\n },\n sudo: {\n minTtlSeconds: MIN_SUDO_TTL_SECONDS,\n defaultTtlSeconds: 300,\n maxTtlSeconds: 1800,\n pendingChallengeTtlMs: DEFAULT_SUDO_PENDING_CHALLENGE_TTL_MS,\n },\n webauthn: {\n rpName: 'Open Mercato',\n rpId: 'localhost',\n expectedOrigins: ['http://localhost:3000'],\n setupTtlMs: DEFAULT_SETUP_TTL_MS,\n challengeTtlMs: DEFAULT_WEBAUTHN_CHALLENGE_TTL_MS,\n },\n recoveryCodes: {\n count: 10,\n bcryptCost: 10,\n },\n}\n\nexport function readSecurityModuleConfig(\n env: NodeJS.ProcessEnv = process.env,\n): SecurityModuleConfig {\n const otpExpirySeconds = parsePositiveInteger(env.OM_SECURITY_OTP_EXPIRY_SECONDS)\n ?? defaultSecurityModuleConfig.otpEmail.expirySeconds\n const otpMaxAttempts = parsePositiveInteger(env.OM_SECURITY_OTP_MAX_ATTEMPTS)\n ?? defaultSecurityModuleConfig.otpEmail.maxAttempts\n const sudoMaxTtlSeconds = Math.max(\n parsePositiveInteger(env.OM_SECURITY_SUDO_MAX_TTL) ?? defaultSecurityModuleConfig.sudo.maxTtlSeconds,\n defaultSecurityModuleConfig.sudo.minTtlSeconds,\n )\n const sudoDefaultTtlSeconds = clamp(\n parsePositiveInteger(env.OM_SECURITY_SUDO_DEFAULT_TTL) ?? defaultSecurityModuleConfig.sudo.defaultTtlSeconds,\n defaultSecurityModuleConfig.sudo.minTtlSeconds,\n sudoMaxTtlSeconds,\n )\n const webauthnRpId = readText(env.OM_SECURITY_WEBAUTHN_RP_ID)\n ?? readHostname(env.APP_URL)\n ?? readHostname(env.NEXT_PUBLIC_APP_URL)\n ?? defaultSecurityModuleConfig.webauthn.rpId\n\n return {\n totp: {\n ...defaultSecurityModuleConfig.totp,\n issuer: readText(env.OM_SECURITY_TOTP_ISSUER) ?? defaultSecurityModuleConfig.totp.issuer,\n window: parseNonNegativeInteger(env.OM_SECURITY_TOTP_WINDOW) ?? defaultSecurityModuleConfig.totp.window,\n },\n otpEmail: {\n ...defaultSecurityModuleConfig.otpEmail,\n expirySeconds: otpExpirySeconds,\n maxAttempts: otpMaxAttempts,\n challengeTtlMs: otpExpirySeconds * 1000,\n },\n mfa: {\n challengeTtlMs: otpExpirySeconds * 1000,\n maxAttempts: otpMaxAttempts,\n emergencyBypass: parseBooleanWithDefault(\n env.OM_SECURITY_MFA_EMERGENCY_BYPASS,\n defaultSecurityModuleConfig.mfa.emergencyBypass,\n ),\n },\n sudo: {\n ...defaultSecurityModuleConfig.sudo,\n defaultTtlSeconds: sudoDefaultTtlSeconds,\n maxTtlSeconds: sudoMaxTtlSeconds,\n },\n webauthn: {\n ...defaultSecurityModuleConfig.webauthn,\n rpName: readText(env.OM_SECURITY_WEBAUTHN_RP_NAME) ?? defaultSecurityModuleConfig.webauthn.rpName,\n rpId: webauthnRpId,\n expectedOrigins: readWebAuthnOrigins(env, webauthnRpId),\n },\n recoveryCodes: {\n ...defaultSecurityModuleConfig.recoveryCodes,\n count: parsePositiveInteger(env.OM_SECURITY_RECOVERY_CODE_COUNT)\n ?? parsePositiveInteger(env.OM_SECURITY_RECOVERY_CODES_COUNT)\n ?? defaultSecurityModuleConfig.recoveryCodes.count,\n },\n }\n}\n\nexport function resolveSecurityModuleConfigForRequest(\n baseConfig: SecurityModuleConfig,\n request?: Request,\n env: NodeJS.ProcessEnv = process.env,\n): SecurityModuleConfig {\n const explicitRpId = readText(env.OM_SECURITY_WEBAUTHN_RP_ID)\n const requestRpId = readRequestHostname(request)\n const rpId = explicitRpId ?? requestRpId ?? baseConfig.webauthn.rpId\n const expectedOrigins = readWebAuthnOrigins(env, rpId, request, baseConfig.webauthn.expectedOrigins)\n\n if (rpId === baseConfig.webauthn.rpId && areOriginsEqual(expectedOrigins, baseConfig.webauthn.expectedOrigins)) {\n return baseConfig\n }\n\n return {\n ...baseConfig,\n webauthn: {\n ...baseConfig.webauthn,\n rpId,\n expectedOrigins,\n },\n }\n}\n\nexport function readSecuritySetupTokenSecret(env: NodeJS.ProcessEnv = process.env): string {\n const secret = readText(env.OM_SECURITY_MFA_SETUP_SECRET)\n ?? readText(env.AUTH_JWT_SECRET)\n ?? readText(env.AUTH_SECRET)\n ?? readText(env.JWT_SECRET)\n\n if (secret) {\n return secret\n }\n\n throw new Error(\n 'Security MFA setup tokens require OM_SECURITY_MFA_SETUP_SECRET, AUTH_JWT_SECRET, AUTH_SECRET, or JWT_SECRET.',\n )\n}\n\nfunction readWebAuthnOrigins(\n env: NodeJS.ProcessEnv,\n rpId: string,\n request?: Request,\n fallbackOrigins?: string[],\n): string[] {\n const fromList = (env.OM_SECURITY_WEBAUTHN_ORIGINS ?? '')\n .split(',')\n .map((value) => value.trim())\n .filter((value) => value.length > 0)\n const explicitOrigin = readText(env.OM_SECURITY_WEBAUTHN_ORIGIN)\n\n if (fromList.length > 0 || explicitOrigin) {\n return [...new Set([...fromList, ...(explicitOrigin ? [explicitOrigin] : [])])]\n }\n\n const requestOrigin = readRequestOrigin(request)\n const singletons = [\n requestOrigin,\n env.APP_URL,\n env.NEXT_PUBLIC_APP_URL,\n ]\n .map((value) => readText(value))\n .filter((value): value is string => typeof value === 'string')\n\n const origins = [...new Set([...fromList, ...singletons])]\n if (origins.length > 0) {\n return origins\n }\n\n if (fallbackOrigins && fallbackOrigins.length > 0) {\n return [...fallbackOrigins]\n }\n\n if (rpId === 'localhost') {\n return [...defaultSecurityModuleConfig.webauthn.expectedOrigins]\n }\n\n return [`https://${rpId}`]\n}\n\nfunction readText(value: string | undefined): string | undefined {\n const trimmed = value?.trim()\n return trimmed ? trimmed : undefined\n}\n\nfunction readHostname(value: string | undefined): string | undefined {\n const trimmed = readText(value)\n if (!trimmed) return undefined\n\n try {\n const url = new URL(trimmed)\n return readText(url.hostname)\n } catch {\n return undefined\n }\n}\n\nfunction readRequestHostname(request: Request | undefined): string | undefined {\n const url = readRequestUrl(request)\n return url ? readText(url.hostname) : undefined\n}\n\nfunction readRequestOrigin(request: Request | undefined): string | undefined {\n const url = readRequestUrl(request)\n return url ? readText(url.origin) : undefined\n}\n\nfunction readRequestUrl(request: Request | undefined): URL | null {\n if (!request) return null\n\n const forwardedHost = readForwardedHeader(request.headers.get('x-forwarded-host'))\n const forwardedProto = readForwardedHeader(request.headers.get('x-forwarded-proto'))\n if (forwardedHost) {\n const protocol = forwardedProto ?? 'https'\n try {\n return new URL(`${protocol}://${forwardedHost}`)\n } catch {\n return null\n }\n }\n\n try {\n return new URL(request.url)\n } catch {\n return null\n }\n}\n\nfunction readForwardedHeader(value: string | null): string | undefined {\n const trimmed = readText(value ?? undefined)\n if (!trimmed) return undefined\n const [first] = trimmed.split(',')\n return readText(first)\n}\n\nfunction areOriginsEqual(left: string[], right: string[]): boolean {\n if (left.length !== right.length) return false\n return left.every((value, index) => value === right[index])\n}\n\nfunction parsePositiveInteger(value: string | undefined): number | undefined {\n if (!value) return undefined\n const parsed = Number(value)\n if (!Number.isFinite(parsed) || parsed <= 0) return undefined\n return Math.trunc(parsed)\n}\n\nfunction parseNonNegativeInteger(value: string | undefined): number | undefined {\n if (!value) return undefined\n const parsed = Number(value)\n if (!Number.isFinite(parsed) || parsed < 0) return undefined\n return Math.trunc(parsed)\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max)\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -14,7 +14,7 @@ import {
14
14
  } from "../data/entities.js";
15
15
  import { emitSecurityEvent } from "../events.js";
16
16
  import { sudoTargets as defaultSudoTargets } from "../security.sudo.js";
17
- import { emitMfaEmergencyBypassActiveWarning, readSecurityModuleConfig } from "../lib/security-config.js";
17
+ import { readSecurityModuleConfig } from "../lib/security-config.js";
18
18
  const signedSudoTokenPayloadSchema = z.object({
19
19
  sid: z.string(),
20
20
  sub: z.string(),
@@ -74,25 +74,31 @@ class SudoChallengeService {
74
74
  }
75
75
  async isProtected(targetIdentifier, tenantId, organizationId) {
76
76
  await this.ensureDeveloperDefaultsRegistered();
77
- return this.resolveProtection(this.em, targetIdentifier, tenantId ?? null, organizationId ?? null);
77
+ const candidates = await this.em.find(SudoChallengeConfig, {
78
+ targetIdentifier,
79
+ deletedAt: null
80
+ });
81
+ const resolved = candidates.filter((config) => this.matchesScope(config, tenantId ?? null, organizationId ?? null)).sort((left, right) => this.compareConfigPriority(left, right, tenantId ?? null, organizationId ?? null))[0];
82
+ if (!resolved || !resolved.isEnabled) {
83
+ return { protected: false };
84
+ }
85
+ return { protected: true, config: resolved };
78
86
  }
79
87
  async initiate(userId, targetIdentifier, options) {
80
- const user = await this.findUserScope(userId);
81
- if (!user?.tenantId) {
82
- throw new SudoChallengeServiceError("User not found", 404);
83
- }
84
- const scopeTenantId = options?.tenantId !== void 0 ? options.tenantId : user.tenantId;
85
- const scopeOrganizationId = options?.organizationId !== void 0 ? options.organizationId : user.organizationId;
86
88
  const protection = await this.isProtected(
87
89
  targetIdentifier,
88
- scopeTenantId,
89
- scopeOrganizationId
90
+ options?.tenantId ?? null,
91
+ options?.organizationId ?? null
90
92
  );
91
93
  if (!protection.protected || !protection.config) {
92
94
  return { required: false };
93
95
  }
96
+ const user = await this.findUserScope(userId);
97
+ if (!user?.tenantId) {
98
+ throw new SudoChallengeServiceError("User not found", 404);
99
+ }
94
100
  const userMethods = await this.mfaService.getUserMethods(userId);
95
- const method = this.resolveChallengeMethod(protection.config.challengeMethod, userMethods.length, userId, targetIdentifier);
101
+ const method = this.resolveChallengeMethod(protection.config.challengeMethod, userMethods.length);
96
102
  let sessionToken = randomBytes(16).toString("hex");
97
103
  let availableMfaMethods;
98
104
  if (method === "mfa") {
@@ -104,15 +110,9 @@ class SudoChallengeService {
104
110
  const session = this.em.create(SudoSession, {
105
111
  userId,
106
112
  tenantId: user.tenantId,
107
- scopeTenantId,
108
- scopeOrganizationId,
109
- targetIdentifier,
110
- sudoConfigId: protection.config.id,
111
- sudoConfigUpdatedAt: protection.config.updatedAt,
112
113
  sessionToken,
113
114
  challengeMethod: method,
114
115
  expiresAt,
115
- verifiedAt: null,
116
116
  createdAt: /* @__PURE__ */ new Date()
117
117
  });
118
118
  this.em.persist(session);
@@ -156,27 +156,16 @@ class SudoChallengeService {
156
156
  if (!user?.tenantId) {
157
157
  throw new SudoChallengeServiceError("User not found", 404);
158
158
  }
159
- if (user.tenantId !== session.tenantId) {
160
- throw new SudoChallengeServiceError("Unable to verify sudo challenge", 403);
161
- }
162
- const requestedTenantId = options.tenantId !== void 0 ? options.tenantId : user.tenantId;
163
- const requestedOrganizationId = options.organizationId !== void 0 ? options.organizationId : user.organizationId;
164
- if (options.targetIdentifier !== session.targetIdentifier || requestedTenantId !== session.scopeTenantId || requestedOrganizationId !== session.scopeOrganizationId) {
165
- throw new SudoChallengeServiceError("Unable to verify sudo challenge", 403);
166
- }
167
- const scopeTenantId = session.scopeTenantId ?? null;
168
- const scopeOrganizationId = session.scopeOrganizationId ?? null;
159
+ const scopeTenantId = options.tenantId !== void 0 ? options.tenantId : user.tenantId;
160
+ const scopeOrganizationId = options.organizationId !== void 0 ? options.organizationId : user.organizationId;
169
161
  const protection = await this.isProtected(
170
- session.targetIdentifier,
162
+ options.targetIdentifier,
171
163
  scopeTenantId,
172
164
  scopeOrganizationId
173
165
  );
174
166
  if (!protection.protected || !protection.config) {
175
167
  throw new SudoChallengeServiceError("Sudo protection is not configured for this target", 404);
176
168
  }
177
- if (protection.config.id !== session.sudoConfigId || protection.config.updatedAt.getTime() !== session.sudoConfigUpdatedAt?.getTime()) {
178
- throw new SudoChallengeServiceError("Unable to verify sudo challenge", 403);
179
- }
180
169
  let verified = false;
181
170
  let methodUsed = methodType;
182
171
  if (session.challengeMethod === "password") {
@@ -197,65 +186,30 @@ class SudoChallengeService {
197
186
  userId: session.userId,
198
187
  tenantId: user.tenantId,
199
188
  organizationId: user.organizationId,
200
- targetIdentifier: session.targetIdentifier,
189
+ targetIdentifier: options.targetIdentifier,
201
190
  method: methodUsed
202
191
  });
203
192
  throw new SudoChallengeServiceError("Unable to verify sudo challenge", 401);
204
193
  }
205
- const pendingToken = session.sessionToken;
206
- const { sudoToken, expiresAt, verifiedAt } = await this.em.transactional(async (transactionalEm) => {
207
- await transactionalEm.execute('lock table "sudo_challenge_configs" in share mode');
208
- const currentProtection = await this.resolveProtection(
209
- transactionalEm,
210
- session.targetIdentifier,
211
- scopeTenantId,
212
- scopeOrganizationId
213
- );
214
- if (!currentProtection.protected || !currentProtection.config || currentProtection.config.id !== session.sudoConfigId || currentProtection.config.updatedAt.getTime() !== session.sudoConfigUpdatedAt.getTime()) {
215
- throw new SudoChallengeServiceError("Unable to verify sudo challenge", 403);
216
- }
217
- const ttlSeconds = this.normalizeTtl(currentProtection.config.ttlSeconds);
218
- const currentVerifiedAt = /* @__PURE__ */ new Date();
219
- const currentExpiresAt = new Date(Date.now() + ttlSeconds * 1e3);
220
- const currentSudoToken = this.signToken({
221
- sid: session.id,
222
- sub: session.userId,
223
- tid: scopeTenantId,
224
- oid: scopeOrganizationId,
225
- tgt: session.targetIdentifier,
226
- exp: currentExpiresAt.getTime()
227
- });
228
- const consumedRows = await transactionalEm.nativeUpdate(
229
- SudoSession,
230
- {
231
- id: session.id,
232
- sessionToken: pendingToken,
233
- verifiedAt: null,
234
- expiresAt: { $gt: currentVerifiedAt }
235
- },
236
- {
237
- sessionToken: currentSudoToken,
238
- expiresAt: currentExpiresAt,
239
- verifiedAt: currentVerifiedAt
240
- }
241
- );
242
- if (consumedRows !== 1) {
243
- throw new SudoChallengeServiceError("Unable to verify sudo challenge", 400);
244
- }
245
- return {
246
- sudoToken: currentSudoToken,
247
- expiresAt: currentExpiresAt,
248
- verifiedAt: currentVerifiedAt
249
- };
194
+ const ttlSeconds = this.normalizeTtl(protection.config.ttlSeconds);
195
+ const expiresAt = new Date(Date.now() + ttlSeconds * 1e3);
196
+ const sudoToken = this.signToken({
197
+ sid: session.id,
198
+ sub: session.userId,
199
+ tid: scopeTenantId,
200
+ oid: scopeOrganizationId,
201
+ tgt: options.targetIdentifier,
202
+ exp: expiresAt.getTime()
250
203
  });
251
204
  session.sessionToken = sudoToken;
205
+ session.challengeMethod = methodUsed;
252
206
  session.expiresAt = expiresAt;
253
- session.verifiedAt = verifiedAt;
207
+ await this.em.flush();
254
208
  await emitSecurityEvent("security.sudo.verified", {
255
209
  userId: session.userId,
256
210
  tenantId: scopeTenantId,
257
211
  organizationId: scopeOrganizationId,
258
- targetIdentifier: session.targetIdentifier,
212
+ targetIdentifier: options.targetIdentifier,
259
213
  method: methodUsed,
260
214
  expiresAt: expiresAt.toISOString()
261
215
  });
@@ -273,11 +227,7 @@ class SudoChallengeService {
273
227
  const session = await this.em.findOne(SudoSession, {
274
228
  id: payload.sid,
275
229
  userId: payload.sub,
276
- sessionToken: token,
277
- targetIdentifier,
278
- scopeTenantId: payload.tid,
279
- scopeOrganizationId: payload.oid,
280
- verifiedAt: { $ne: null }
230
+ sessionToken: token
281
231
  });
282
232
  return Boolean(session && session.expiresAt.getTime() > Date.now());
283
233
  }
@@ -372,17 +322,12 @@ class SudoChallengeService {
372
322
  isDeveloperDefault: true
373
323
  });
374
324
  if (existing) {
375
- const ttlSeconds = this.normalizeTtl(input.ttlSeconds);
376
- const challengeMethod = input.challengeMethod ?? ChallengeMethod.AUTO;
377
- const changed = !existing.isEnabled || Boolean(existing.deletedAt) || existing.ttlSeconds !== ttlSeconds || existing.challengeMethod !== challengeMethod;
378
- if (changed) {
379
- existing.isEnabled = true;
380
- existing.deletedAt = null;
381
- existing.ttlSeconds = ttlSeconds;
382
- existing.challengeMethod = challengeMethod;
383
- existing.updatedAt = /* @__PURE__ */ new Date();
384
- await this.em.flush();
385
- }
325
+ existing.isEnabled = true;
326
+ existing.deletedAt = null;
327
+ existing.ttlSeconds = this.normalizeTtl(input.ttlSeconds);
328
+ existing.challengeMethod = input.challengeMethod ?? ChallengeMethod.AUTO;
329
+ existing.updatedAt = /* @__PURE__ */ new Date();
330
+ await this.em.flush();
386
331
  return;
387
332
  }
388
333
  const config = this.em.create(SudoChallengeConfig, {
@@ -460,27 +405,10 @@ class SudoChallengeService {
460
405
  throw new SudoChallengeServiceError("Sudo configuration not found", 404);
461
406
  }
462
407
  }
463
- resolveChallengeMethod(configuredMethod, availableMfaMethodCount, userId, targetIdentifier) {
408
+ resolveChallengeMethod(configuredMethod, availableMfaMethodCount) {
464
409
  if (this.securityConfig.mfa.emergencyBypass) {
465
- let wouldHaveBeenMfa = false;
466
- try {
467
- wouldHaveBeenMfa = this.resolveChallengeMethodWithoutBypass(configuredMethod, availableMfaMethodCount) === "mfa";
468
- } catch {
469
- wouldHaveBeenMfa = configuredMethod === ChallengeMethod.MFA;
470
- }
471
- if (wouldHaveBeenMfa) {
472
- emitMfaEmergencyBypassActiveWarning("sudo challenge downgraded to password", {
473
- configuredMethod,
474
- availableMfaMethodCount,
475
- userId,
476
- targetIdentifier
477
- });
478
- }
479
410
  return "password";
480
411
  }
481
- return this.resolveChallengeMethodWithoutBypass(configuredMethod, availableMfaMethodCount);
482
- }
483
- resolveChallengeMethodWithoutBypass(configuredMethod, availableMfaMethodCount) {
484
412
  if (configuredMethod === ChallengeMethod.PASSWORD) return "password";
485
413
  if (configuredMethod === ChallengeMethod.MFA) {
486
414
  if (availableMfaMethodCount === 0) {
@@ -490,17 +418,6 @@ class SudoChallengeService {
490
418
  }
491
419
  return availableMfaMethodCount > 0 ? "mfa" : "password";
492
420
  }
493
- async resolveProtection(em, targetIdentifier, tenantId, organizationId) {
494
- const candidates = await em.find(SudoChallengeConfig, {
495
- targetIdentifier,
496
- deletedAt: null
497
- });
498
- const resolved = candidates.filter((config) => this.matchesScope(config, tenantId, organizationId)).sort((left, right) => this.compareConfigPriority(left, right, tenantId, organizationId))[0];
499
- if (!resolved || !resolved.isEnabled) {
500
- return { protected: false };
501
- }
502
- return { protected: true, config: resolved };
503
- }
504
421
  matchesScope(config, tenantId, organizationId) {
505
422
  if (config.organizationId) {
506
423
  return config.organizationId === organizationId && config.tenantId === tenantId;
@@ -531,12 +448,6 @@ class SudoChallengeService {
531
448
  if (!session) {
532
449
  throw new SudoChallengeServiceError("Sudo challenge session not found", 404);
533
450
  }
534
- if (!session.targetIdentifier || !session.sudoConfigId || !session.sudoConfigUpdatedAt) {
535
- throw new SudoChallengeServiceError("Sudo challenge session not found", 404);
536
- }
537
- if (session.verifiedAt) {
538
- throw new SudoChallengeServiceError("Unable to verify sudo challenge", 400);
539
- }
540
451
  if (session.expiresAt.getTime() <= Date.now()) {
541
452
  throw new SudoChallengeServiceError("Sudo challenge session expired", 400);
542
453
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/security/services/SudoChallengeService.ts"],
4
- "sourcesContent": ["import { createHmac, randomBytes, timingSafeEqual } from 'node:crypto'\nimport { z } from 'zod'\nimport type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'\nimport { User } from '@open-mercato/core/modules/auth/data/entities'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport {\n dedupeSudoTargets,\n getSecuritySudoTargetEntries,\n type SecuritySudoTarget,\n} from '../lib/module-security-registry'\nimport {\n ChallengeMethod,\n SudoChallengeConfig,\n SudoChallengeMethodUsed,\n SudoSession,\n} from '../data/entities'\nimport { emitSecurityEvent } from '../events'\nimport type {\n SudoConfigInput,\n SudoConfigUpdateInput,\n} from '../data/validators'\nimport type { PasswordService } from './PasswordService'\nimport type { MfaService } from './MfaService'\nimport type { MfaVerificationService } from './MfaVerificationService'\nimport { sudoTargets as defaultSudoTargets } from '../security.sudo'\nimport type { SecurityModuleConfig } from '../lib/security-config'\nimport { emitMfaEmergencyBypassActiveWarning, readSecurityModuleConfig } from '../lib/security-config'\n\ntype SudoMethod = 'password' | 'mfa'\n\nexport type SudoAvailableMethod = {\n type: string\n label: string\n icon: string\n}\n\nexport type SudoProtectionResolution = {\n protected: boolean\n config?: SudoChallengeConfig\n}\n\nconst signedSudoTokenPayloadSchema = z.object({\n sid: z.string(),\n sub: z.string(),\n tid: z.string().nullable(),\n oid: z.string().nullable(),\n tgt: z.string(),\n exp: z.number(),\n})\n\ntype SignedSudoTokenPayload = z.infer<typeof signedSudoTokenPayloadSchema>\n\ntype UserScope = {\n id: string\n tenantId: string | null\n organizationId: string | null\n}\n\ntype BoundPendingSudoSession = SudoSession & {\n targetIdentifier: string\n sudoConfigId: string\n sudoConfigUpdatedAt: Date\n}\n\nexport type SudoAuthScope = {\n tenantId: string | null\n organizationId?: string | null\n isSuperAdmin?: boolean\n}\n\ntype DeveloperDefaultPayload = {\n targetIdentifier: string\n label?: string | null\n ttlSeconds?: number\n challengeMethod?: ChallengeMethod\n}\n\nfunction readTrimmedEnv(value: string | undefined): string | undefined {\n const trimmed = value?.trim()\n return trimmed ? trimmed : undefined\n}\n\nexport class SudoChallengeServiceError extends Error {\n constructor(\n message: string,\n public readonly statusCode: number,\n ) {\n super(message)\n this.name = 'SudoChallengeServiceError'\n }\n}\n\nexport class SudoChallengeService {\n constructor(\n private readonly em: EntityManager,\n private readonly passwordService: PasswordService,\n private readonly mfaService: MfaService,\n private readonly mfaVerificationService: MfaVerificationService,\n private readonly securityConfig: SecurityModuleConfig = readSecurityModuleConfig(),\n ) {}\n\n async listConfigs(scope?: SudoAuthScope): Promise<SudoChallengeConfig[]> {\n await this.ensureDeveloperDefaultsRegistered()\n const filter: FilterQuery<SudoChallengeConfig> = { deletedAt: null }\n if (scope && !scope.isSuperAdmin) {\n if (!scope.tenantId) return []\n ;(filter as Record<string, unknown>).$or = [\n { tenantId: scope.tenantId },\n { tenantId: null },\n ]\n }\n return this.em.find(\n SudoChallengeConfig,\n filter,\n {\n orderBy: {\n targetIdentifier: 'asc',\n tenantId: 'asc',\n organizationId: 'asc',\n createdAt: 'asc',\n },\n },\n )\n }\n\n async getConfigById(id: string, scope?: SudoAuthScope): Promise<SudoChallengeConfig | null> {\n await this.ensureDeveloperDefaultsRegistered()\n const config = await this.em.findOne(SudoChallengeConfig, { id, deletedAt: null })\n if (!config) return null\n if (scope && !this.isConfigVisibleToScope(config, scope)) return null\n return config\n }\n\n async isProtected(\n targetIdentifier: string,\n tenantId?: string | null,\n organizationId?: string | null,\n ): Promise<SudoProtectionResolution> {\n await this.ensureDeveloperDefaultsRegistered()\n return this.resolveProtection(this.em, targetIdentifier, tenantId ?? null, organizationId ?? null)\n }\n\n async initiate(\n userId: string,\n targetIdentifier: string,\n options?: { tenantId?: string | null; organizationId?: string | null },\n ): Promise<{\n required: boolean\n sessionId?: string\n method?: SudoMethod\n availableMfaMethods?: SudoAvailableMethod[]\n expiresAt?: Date\n }> {\n const user = await this.findUserScope(userId)\n if (!user?.tenantId) {\n throw new SudoChallengeServiceError('User not found', 404)\n }\n const scopeTenantId = options?.tenantId !== undefined ? options.tenantId : user.tenantId\n const scopeOrganizationId = options?.organizationId !== undefined ? options.organizationId : user.organizationId\n const protection = await this.isProtected(\n targetIdentifier,\n scopeTenantId,\n scopeOrganizationId,\n )\n\n if (!protection.protected || !protection.config) {\n return { required: false }\n }\n\n const userMethods = await this.mfaService.getUserMethods(userId)\n const method = this.resolveChallengeMethod(protection.config.challengeMethod, userMethods.length, userId, targetIdentifier)\n\n let sessionToken = randomBytes(16).toString('hex')\n let availableMfaMethods: SudoAvailableMethod[] | undefined\n if (method === 'mfa') {\n const challenge = await this.mfaVerificationService.createChallenge(userId)\n sessionToken = challenge.challengeId\n availableMfaMethods = challenge.availableMethods\n }\n\n const expiresAt = new Date(Date.now() + this.securityConfig.sudo.pendingChallengeTtlMs)\n const session = this.em.create(SudoSession, {\n userId,\n tenantId: user.tenantId,\n scopeTenantId,\n scopeOrganizationId,\n targetIdentifier,\n sudoConfigId: protection.config.id,\n sudoConfigUpdatedAt: protection.config.updatedAt,\n sessionToken,\n challengeMethod: method,\n expiresAt,\n verifiedAt: null,\n createdAt: new Date(),\n })\n this.em.persist(session)\n await this.em.flush()\n\n await emitSecurityEvent('security.sudo.challenged', {\n userId,\n tenantId: user.tenantId,\n organizationId: user.organizationId,\n targetIdentifier,\n method,\n })\n\n return {\n required: true,\n sessionId: session.id,\n method,\n availableMfaMethods,\n expiresAt,\n }\n }\n\n async prepare(\n sessionId: string,\n methodType: string,\n options: { expectedUserId?: string } = {},\n request?: Request,\n ): Promise<{ clientData?: Record<string, unknown> }> {\n const session = await this.getPendingSession(sessionId)\n if (options.expectedUserId && session.userId !== options.expectedUserId) {\n throw new SudoChallengeServiceError('Sudo challenge user mismatch', 403)\n }\n if (session.challengeMethod !== 'mfa') {\n throw new SudoChallengeServiceError('This sudo session does not require MFA', 400)\n }\n return this.mfaVerificationService.prepareChallenge(\n session.sessionToken,\n methodType,\n { request },\n { userId: session.userId },\n )\n }\n\n async verify(\n sessionId: string,\n methodType: string,\n payload: unknown,\n options: {\n expectedUserId?: string\n tenantId?: string | null\n organizationId?: string | null\n targetIdentifier: string\n },\n request?: Request,\n ): Promise<{ sudoToken: string; expiresAt: Date }> {\n const session = await this.getPendingSession(sessionId)\n if (options.expectedUserId && session.userId !== options.expectedUserId) {\n throw new SudoChallengeServiceError('Sudo challenge user mismatch', 403)\n }\n const user = await this.findUserScope(session.userId)\n if (!user?.tenantId) {\n throw new SudoChallengeServiceError('User not found', 404)\n }\n if (user.tenantId !== session.tenantId) {\n throw new SudoChallengeServiceError('Unable to verify sudo challenge', 403)\n }\n\n const requestedTenantId = options.tenantId !== undefined ? options.tenantId : user.tenantId\n const requestedOrganizationId = options.organizationId !== undefined ? options.organizationId : user.organizationId\n if (\n options.targetIdentifier !== session.targetIdentifier\n || requestedTenantId !== session.scopeTenantId\n || requestedOrganizationId !== session.scopeOrganizationId\n ) {\n throw new SudoChallengeServiceError('Unable to verify sudo challenge', 403)\n }\n\n const scopeTenantId = session.scopeTenantId ?? null\n const scopeOrganizationId = session.scopeOrganizationId ?? null\n const protection = await this.isProtected(\n session.targetIdentifier,\n scopeTenantId,\n scopeOrganizationId,\n )\n if (!protection.protected || !protection.config) {\n throw new SudoChallengeServiceError('Sudo protection is not configured for this target', 404)\n }\n if (\n protection.config.id !== session.sudoConfigId\n || protection.config.updatedAt.getTime() !== session.sudoConfigUpdatedAt?.getTime()\n ) {\n throw new SudoChallengeServiceError('Unable to verify sudo challenge', 403)\n }\n\n let verified = false\n let methodUsed: string = methodType\n if (session.challengeMethod === 'password') {\n const password = this.readPassword(payload)\n verified = await this.passwordService.verifyPassword(session.userId, password)\n methodUsed = SudoChallengeMethodUsed.PASSWORD\n } else {\n verified = await this.mfaVerificationService.verifyChallenge(\n session.sessionToken,\n methodType,\n payload,\n { request },\n { userId: session.userId },\n )\n }\n\n if (!verified) {\n await emitSecurityEvent('security.sudo.failed', {\n userId: session.userId,\n tenantId: user.tenantId,\n organizationId: user.organizationId,\n targetIdentifier: session.targetIdentifier,\n method: methodUsed,\n })\n throw new SudoChallengeServiceError('Unable to verify sudo challenge', 401)\n }\n\n const pendingToken = session.sessionToken\n const { sudoToken, expiresAt, verifiedAt } = await this.em.transactional(async (transactionalEm) => {\n await transactionalEm.execute('lock table \"sudo_challenge_configs\" in share mode')\n const currentProtection = await this.resolveProtection(\n transactionalEm,\n session.targetIdentifier,\n scopeTenantId,\n scopeOrganizationId,\n )\n if (\n !currentProtection.protected\n || !currentProtection.config\n || currentProtection.config.id !== session.sudoConfigId\n || currentProtection.config.updatedAt.getTime() !== session.sudoConfigUpdatedAt.getTime()\n ) {\n throw new SudoChallengeServiceError('Unable to verify sudo challenge', 403)\n }\n\n const ttlSeconds = this.normalizeTtl(currentProtection.config.ttlSeconds)\n const currentVerifiedAt = new Date()\n const currentExpiresAt = new Date(Date.now() + ttlSeconds * 1000)\n const currentSudoToken = this.signToken({\n sid: session.id,\n sub: session.userId,\n tid: scopeTenantId,\n oid: scopeOrganizationId,\n tgt: session.targetIdentifier,\n exp: currentExpiresAt.getTime(),\n })\n const consumedRows = await transactionalEm.nativeUpdate(\n SudoSession,\n {\n id: session.id,\n sessionToken: pendingToken,\n verifiedAt: null,\n expiresAt: { $gt: currentVerifiedAt },\n },\n {\n sessionToken: currentSudoToken,\n expiresAt: currentExpiresAt,\n verifiedAt: currentVerifiedAt,\n },\n )\n if (consumedRows !== 1) {\n throw new SudoChallengeServiceError('Unable to verify sudo challenge', 400)\n }\n return {\n sudoToken: currentSudoToken,\n expiresAt: currentExpiresAt,\n verifiedAt: currentVerifiedAt,\n }\n })\n session.sessionToken = sudoToken\n session.expiresAt = expiresAt\n session.verifiedAt = verifiedAt\n\n await emitSecurityEvent('security.sudo.verified', {\n userId: session.userId,\n tenantId: scopeTenantId,\n organizationId: scopeOrganizationId,\n targetIdentifier: session.targetIdentifier,\n method: methodUsed,\n expiresAt: expiresAt.toISOString(),\n })\n\n return { sudoToken, expiresAt }\n }\n\n async validateToken(\n token: string,\n targetIdentifier: string,\n options?: {\n expectedUserId?: string\n tenantId?: string | null\n organizationId?: string | null\n },\n ): Promise<boolean> {\n if (!token) return false\n const payload = this.readSignedToken(token)\n if (!payload) return false\n if (payload.exp <= Date.now()) return false\n if (payload.tgt !== targetIdentifier) return false\n if (options?.expectedUserId && payload.sub !== options.expectedUserId) return false\n if (options?.tenantId !== undefined && payload.tid !== (options.tenantId ?? null)) return false\n if (options?.organizationId !== undefined && payload.oid !== (options.organizationId ?? null)) return false\n\n const session = await this.em.findOne(SudoSession, {\n id: payload.sid,\n userId: payload.sub,\n sessionToken: token,\n targetIdentifier,\n scopeTenantId: payload.tid,\n scopeOrganizationId: payload.oid,\n verifiedAt: { $ne: null },\n } as FilterQuery<SudoSession>)\n\n return Boolean(session && session.expiresAt.getTime() > Date.now())\n }\n\n async createConfig(\n input: SudoConfigInput,\n configuredBy: string,\n scope?: SudoAuthScope,\n ): Promise<SudoChallengeConfig> {\n await this.ensureDeveloperDefaultsRegistered()\n const inputTenantId = input.tenantId ?? null\n const inputOrganizationId = input.organizationId ?? null\n this.validateScope(inputTenantId, inputOrganizationId)\n if (scope) this.assertWriteScope(inputTenantId, inputOrganizationId, scope)\n await this.ensureUniqueConfig(input.targetIdentifier, inputTenantId, inputOrganizationId)\n\n const config = this.em.create(SudoChallengeConfig, {\n tenantId: inputTenantId,\n organizationId: inputOrganizationId,\n label: input.label ?? null,\n targetIdentifier: input.targetIdentifier,\n isEnabled: input.isEnabled,\n ttlSeconds: this.normalizeTtl(input.ttlSeconds),\n challengeMethod: input.challengeMethod,\n configuredBy,\n isDeveloperDefault: false,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n this.em.persist(config)\n await this.em.flush()\n\n await emitSecurityEvent('security.sudo.config.created', {\n id: config.id,\n targetIdentifier: config.targetIdentifier,\n configuredBy,\n })\n\n return config\n }\n\n async updateConfig(\n id: string,\n input: SudoConfigUpdateInput,\n configuredBy: string,\n scope?: SudoAuthScope,\n ): Promise<SudoChallengeConfig> {\n await this.ensureDeveloperDefaultsRegistered()\n const config = await this.em.findOne(SudoChallengeConfig, { id, deletedAt: null })\n if (!config) {\n throw new SudoChallengeServiceError('Sudo configuration not found', 404)\n }\n if (scope) {\n this.assertWriteScope(config.tenantId ?? null, config.organizationId ?? null, scope)\n if (config.isDeveloperDefault && !scope.isSuperAdmin) {\n throw new SudoChallengeServiceError('Sudo configuration not found', 404)\n }\n }\n\n const nextTenantId = input.tenantId !== undefined ? input.tenantId ?? null : config.tenantId ?? null\n const nextOrganizationId = input.organizationId !== undefined ? input.organizationId ?? null : config.organizationId ?? null\n const nextTargetIdentifier = input.targetIdentifier ?? config.targetIdentifier\n this.validateScope(nextTenantId, nextOrganizationId)\n if (scope) this.assertWriteScope(nextTenantId, nextOrganizationId, scope)\n await this.ensureUniqueConfig(nextTargetIdentifier, nextTenantId, nextOrganizationId, config.id)\n\n if (input.tenantId !== undefined) config.tenantId = input.tenantId ?? null\n if (input.organizationId !== undefined) config.organizationId = input.organizationId ?? null\n if (input.label !== undefined) config.label = input.label ?? null\n if (input.targetIdentifier !== undefined) config.targetIdentifier = input.targetIdentifier\n if (input.isEnabled !== undefined) config.isEnabled = input.isEnabled\n if (input.ttlSeconds !== undefined) config.ttlSeconds = this.normalizeTtl(input.ttlSeconds)\n if (input.challengeMethod !== undefined) config.challengeMethod = input.challengeMethod\n config.configuredBy = configuredBy\n config.updatedAt = new Date()\n await this.em.flush()\n\n await emitSecurityEvent('security.sudo.config.updated', {\n id: config.id,\n targetIdentifier: config.targetIdentifier,\n configuredBy,\n })\n\n return config\n }\n\n async deleteConfig(id: string, scope?: SudoAuthScope): Promise<void> {\n const config = await this.em.findOne(SudoChallengeConfig, { id, deletedAt: null })\n if (!config) {\n throw new SudoChallengeServiceError('Sudo configuration not found', 404)\n }\n if (scope) {\n this.assertWriteScope(config.tenantId ?? null, config.organizationId ?? null, scope)\n if (config.isDeveloperDefault && !scope.isSuperAdmin) {\n throw new SudoChallengeServiceError('Sudo configuration not found', 404)\n }\n }\n config.deletedAt = new Date()\n config.updatedAt = new Date()\n await this.em.flush()\n\n await emitSecurityEvent('security.sudo.config.deleted', {\n id: config.id,\n targetIdentifier: config.targetIdentifier,\n })\n }\n\n async registerDeveloperDefault(\n input: DeveloperDefaultPayload,\n ): Promise<void> {\n const existing = await this.em.findOne(SudoChallengeConfig, {\n targetIdentifier: input.targetIdentifier,\n tenantId: null,\n organizationId: null,\n isDeveloperDefault: true,\n })\n\n if (existing) {\n const ttlSeconds = this.normalizeTtl(input.ttlSeconds)\n const challengeMethod = input.challengeMethod ?? ChallengeMethod.AUTO\n const changed = !existing.isEnabled\n || Boolean(existing.deletedAt)\n || existing.ttlSeconds !== ttlSeconds\n || existing.challengeMethod !== challengeMethod\n if (changed) {\n existing.isEnabled = true\n existing.deletedAt = null\n existing.ttlSeconds = ttlSeconds\n existing.challengeMethod = challengeMethod\n existing.updatedAt = new Date()\n await this.em.flush()\n }\n return\n }\n\n const config = this.em.create(SudoChallengeConfig, {\n tenantId: null,\n organizationId: null,\n label: input.label ?? null,\n targetIdentifier: input.targetIdentifier,\n isEnabled: true,\n isDeveloperDefault: true,\n ttlSeconds: this.normalizeTtl(input.ttlSeconds),\n challengeMethod: input.challengeMethod ?? ChallengeMethod.AUTO,\n configuredBy: null,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n this.em.persist(config)\n await this.em.flush()\n }\n\n async cleanupExpired(): Promise<number> {\n return this.em.nativeDelete(SudoSession, {\n expiresAt: { $lte: new Date() },\n })\n }\n\n private async ensureDeveloperDefaultsRegistered(): Promise<void> {\n const registryEntries = getSecuritySudoTargetEntries()\n const registryTargets = registryEntries.flatMap((entry) => entry.targets ?? [])\n const fallbackTargets = registryEntries.length === 0 ? defaultSudoTargets : []\n\n for (const target of dedupeSudoTargets([\n ...registryTargets,\n ...fallbackTargets,\n ])) {\n await this.registerDeveloperDefault(this.readDeveloperDefault(target))\n }\n }\n\n private readDeveloperDefault(target: SecuritySudoTarget): DeveloperDefaultPayload {\n return {\n targetIdentifier: target.identifier,\n label: target.label ?? null,\n ttlSeconds: target.ttlSeconds,\n challengeMethod: this.toChallengeMethod(target.challengeMethod),\n }\n }\n\n private async ensureUniqueConfig(\n targetIdentifier: string,\n tenantId: string | null,\n organizationId: string | null,\n ignoreId?: string,\n ): Promise<void> {\n const existing = await this.em.findOne(SudoChallengeConfig, {\n targetIdentifier,\n tenantId,\n organizationId,\n deletedAt: null,\n })\n if (existing && existing.id !== ignoreId) {\n throw new SudoChallengeServiceError('A sudo configuration for this target and scope already exists', 409)\n }\n }\n\n private validateScope(tenantId: string | null, organizationId: string | null): void {\n if (organizationId && !tenantId) {\n throw new SudoChallengeServiceError('Organization-scoped sudo config requires a tenant', 400)\n }\n }\n\n private isConfigVisibleToScope(config: SudoChallengeConfig, scope: SudoAuthScope): boolean {\n if (scope.isSuperAdmin) return true\n if (!scope.tenantId) return false\n const configTenantId = config.tenantId ?? null\n if (configTenantId === null) return true\n return configTenantId === scope.tenantId\n }\n\n private assertWriteScope(\n targetTenantId: string | null,\n targetOrganizationId: string | null,\n scope: SudoAuthScope,\n ): void {\n if (scope.isSuperAdmin) return\n if (!scope.tenantId) {\n throw new SudoChallengeServiceError('Sudo configuration not found', 404)\n }\n if ((targetTenantId ?? null) !== scope.tenantId) {\n throw new SudoChallengeServiceError('Sudo configuration not found', 404)\n }\n if (\n scope.organizationId !== undefined\n && scope.organizationId !== null\n && targetOrganizationId !== null\n && targetOrganizationId !== scope.organizationId\n ) {\n throw new SudoChallengeServiceError('Sudo configuration not found', 404)\n }\n }\n\n private resolveChallengeMethod(\n configuredMethod: ChallengeMethod,\n availableMfaMethodCount: number,\n userId?: string,\n targetIdentifier?: string,\n ): SudoMethod {\n if (this.securityConfig.mfa.emergencyBypass) {\n let wouldHaveBeenMfa = false\n try {\n wouldHaveBeenMfa = this.resolveChallengeMethodWithoutBypass(configuredMethod, availableMfaMethodCount) === 'mfa'\n } catch {\n wouldHaveBeenMfa = configuredMethod === ChallengeMethod.MFA\n }\n if (wouldHaveBeenMfa) {\n emitMfaEmergencyBypassActiveWarning('sudo challenge downgraded to password', {\n configuredMethod,\n availableMfaMethodCount,\n userId,\n targetIdentifier,\n })\n }\n return 'password'\n }\n return this.resolveChallengeMethodWithoutBypass(configuredMethod, availableMfaMethodCount)\n }\n\n private resolveChallengeMethodWithoutBypass(\n configuredMethod: ChallengeMethod,\n availableMfaMethodCount: number,\n ): SudoMethod {\n if (configuredMethod === ChallengeMethod.PASSWORD) return 'password'\n if (configuredMethod === ChallengeMethod.MFA) {\n if (availableMfaMethodCount === 0) {\n throw new SudoChallengeServiceError('This sudo target requires MFA, but no MFA methods are configured', 400)\n }\n return 'mfa'\n }\n return availableMfaMethodCount > 0 ? 'mfa' : 'password'\n }\n\n private async resolveProtection(\n em: EntityManager,\n targetIdentifier: string,\n tenantId: string | null,\n organizationId: string | null,\n ): Promise<SudoProtectionResolution> {\n const candidates = await em.find(SudoChallengeConfig, {\n targetIdentifier,\n deletedAt: null,\n })\n\n const resolved = candidates\n .filter((config) => this.matchesScope(config, tenantId, organizationId))\n .sort((left, right) => this.compareConfigPriority(left, right, tenantId, organizationId))[0]\n\n if (!resolved || !resolved.isEnabled) {\n return { protected: false }\n }\n\n return { protected: true, config: resolved }\n }\n\n private matchesScope(config: SudoChallengeConfig, tenantId: string | null, organizationId: string | null): boolean {\n if (config.organizationId) {\n return config.organizationId === organizationId && config.tenantId === tenantId\n }\n if (config.tenantId) {\n return config.tenantId === tenantId\n }\n return true\n }\n\n private compareConfigPriority(\n left: SudoChallengeConfig,\n right: SudoChallengeConfig,\n tenantId: string | null,\n organizationId: string | null,\n ): number {\n const leftScore = this.getScopePriority(left, tenantId, organizationId)\n const rightScore = this.getScopePriority(right, tenantId, organizationId)\n if (leftScore !== rightScore) return leftScore - rightScore\n if (left.isDeveloperDefault !== right.isDeveloperDefault) {\n return left.isDeveloperDefault ? 1 : -1\n }\n return right.updatedAt.getTime() - left.updatedAt.getTime()\n }\n\n private getScopePriority(\n config: SudoChallengeConfig,\n tenantId: string | null,\n organizationId: string | null,\n ): number {\n if (config.organizationId === organizationId && config.tenantId === tenantId) return 0\n if (!config.organizationId && config.tenantId === tenantId) return 1\n if (!config.organizationId && !config.tenantId && !config.isDeveloperDefault) return 2\n if (!config.organizationId && !config.tenantId && config.isDeveloperDefault) return 3\n return 4\n }\n\n private async getPendingSession(sessionId: string): Promise<BoundPendingSudoSession> {\n const session = await this.em.findOne(SudoSession, { id: sessionId })\n if (!session) {\n throw new SudoChallengeServiceError('Sudo challenge session not found', 404)\n }\n if (!session.targetIdentifier || !session.sudoConfigId || !session.sudoConfigUpdatedAt) {\n throw new SudoChallengeServiceError('Sudo challenge session not found', 404)\n }\n if (session.verifiedAt) {\n throw new SudoChallengeServiceError('Unable to verify sudo challenge', 400)\n }\n if (session.expiresAt.getTime() <= Date.now()) {\n throw new SudoChallengeServiceError('Sudo challenge session expired', 400)\n }\n return session as BoundPendingSudoSession\n }\n\n private async findUserScope(userId: string): Promise<UserScope | null> {\n const user = await findOneWithDecryption(\n this.em,\n User,\n { id: userId, deletedAt: null },\n undefined,\n {},\n )\n\n if (!user) return null\n return {\n id: String(user.id),\n tenantId: user.tenantId ? String(user.tenantId) : null,\n organizationId: user.organizationId ? String(user.organizationId) : null,\n }\n }\n\n private normalizeTtl(value?: number | null): number {\n const rawValue = value ?? this.securityConfig.sudo.defaultTtlSeconds\n return Math.min(\n Math.max(rawValue, this.securityConfig.sudo.minTtlSeconds),\n this.securityConfig.sudo.maxTtlSeconds,\n )\n }\n\n private readPassword(payload: unknown): string {\n if (!payload || typeof payload !== 'object') {\n throw new SudoChallengeServiceError('Password is required', 400)\n }\n const maybePassword = (payload as Record<string, unknown>).password\n if (typeof maybePassword !== 'string' || maybePassword.trim().length === 0) {\n throw new SudoChallengeServiceError('Password is required', 400)\n }\n return maybePassword\n }\n\n private signToken(payload: SignedSudoTokenPayload): string {\n const encodedPayload = Buffer.from(JSON.stringify(payload)).toString('base64url')\n const signature = createHmac('sha256', this.getSudoSecret()).update(encodedPayload).digest('base64url')\n return `${encodedPayload}.${signature}`\n }\n\n private readSignedToken(token: string): SignedSudoTokenPayload | null {\n const [encodedPayload, signature] = token.split('.')\n if (!encodedPayload || !signature) return null\n\n const expected = createHmac('sha256', this.getSudoSecret()).update(encodedPayload).digest('base64url')\n const signatureBuffer = Buffer.from(signature)\n const expectedBuffer = Buffer.from(expected)\n if (signatureBuffer.length !== expectedBuffer.length) return null\n if (!timingSafeEqual(signatureBuffer, expectedBuffer)) return null\n\n try {\n const parsed = signedSudoTokenPayloadSchema.safeParse(\n JSON.parse(Buffer.from(encodedPayload, 'base64url').toString('utf8')),\n )\n if (!parsed.success) return null\n return parsed.data\n } catch {\n return null\n }\n }\n\n private getSudoSecret(): string {\n const secret =\n readTrimmedEnv(process.env.OM_SECURITY_SUDO_SECRET)\n ?? readTrimmedEnv(process.env.AUTH_JWT_SECRET)\n ?? readTrimmedEnv(process.env.AUTH_SECRET)\n ?? readTrimmedEnv(process.env.JWT_SECRET)\n if (secret) return secret\n throw new Error(\n 'Sudo step-up tokens require OM_SECURITY_SUDO_SECRET, AUTH_JWT_SECRET, AUTH_SECRET, or JWT_SECRET to be set.',\n )\n }\n\n private toChallengeMethod(\n method: SecuritySudoTarget['challengeMethod'],\n ): ChallengeMethod | undefined {\n switch (method) {\n case 'password':\n return ChallengeMethod.PASSWORD\n case 'mfa':\n return ChallengeMethod.MFA\n case 'auto':\n default:\n return ChallengeMethod.AUTO\n }\n }\n}\n\nexport default SudoChallengeService\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { createHmac, randomBytes, timingSafeEqual } from 'node:crypto'\nimport { z } from 'zod'\nimport type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'\nimport { User } from '@open-mercato/core/modules/auth/data/entities'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport {\n dedupeSudoTargets,\n getSecuritySudoTargetEntries,\n type SecuritySudoTarget,\n} from '../lib/module-security-registry'\nimport {\n ChallengeMethod,\n SudoChallengeConfig,\n SudoChallengeMethodUsed,\n SudoSession,\n} from '../data/entities'\nimport { emitSecurityEvent } from '../events'\nimport type {\n SudoConfigInput,\n SudoConfigUpdateInput,\n} from '../data/validators'\nimport type { PasswordService } from './PasswordService'\nimport type { MfaService } from './MfaService'\nimport type { MfaVerificationService } from './MfaVerificationService'\nimport { sudoTargets as defaultSudoTargets } from '../security.sudo'\nimport type { SecurityModuleConfig } from '../lib/security-config'\nimport { readSecurityModuleConfig } from '../lib/security-config'\n\ntype SudoMethod = 'password' | 'mfa'\n\nexport type SudoAvailableMethod = {\n type: string\n label: string\n icon: string\n}\n\nexport type SudoProtectionResolution = {\n protected: boolean\n config?: SudoChallengeConfig\n}\n\nconst signedSudoTokenPayloadSchema = z.object({\n sid: z.string(),\n sub: z.string(),\n tid: z.string().nullable(),\n oid: z.string().nullable(),\n tgt: z.string(),\n exp: z.number(),\n})\n\ntype SignedSudoTokenPayload = z.infer<typeof signedSudoTokenPayloadSchema>\n\ntype UserScope = {\n id: string\n tenantId: string | null\n organizationId: string | null\n}\n\nexport type SudoAuthScope = {\n tenantId: string | null\n organizationId?: string | null\n isSuperAdmin?: boolean\n}\n\ntype DeveloperDefaultPayload = {\n targetIdentifier: string\n label?: string | null\n ttlSeconds?: number\n challengeMethod?: ChallengeMethod\n}\n\nfunction readTrimmedEnv(value: string | undefined): string | undefined {\n const trimmed = value?.trim()\n return trimmed ? trimmed : undefined\n}\n\nexport class SudoChallengeServiceError extends Error {\n constructor(\n message: string,\n public readonly statusCode: number,\n ) {\n super(message)\n this.name = 'SudoChallengeServiceError'\n }\n}\n\nexport class SudoChallengeService {\n constructor(\n private readonly em: EntityManager,\n private readonly passwordService: PasswordService,\n private readonly mfaService: MfaService,\n private readonly mfaVerificationService: MfaVerificationService,\n private readonly securityConfig: SecurityModuleConfig = readSecurityModuleConfig(),\n ) {}\n\n async listConfigs(scope?: SudoAuthScope): Promise<SudoChallengeConfig[]> {\n await this.ensureDeveloperDefaultsRegistered()\n const filter: FilterQuery<SudoChallengeConfig> = { deletedAt: null }\n if (scope && !scope.isSuperAdmin) {\n if (!scope.tenantId) return []\n ;(filter as Record<string, unknown>).$or = [\n { tenantId: scope.tenantId },\n { tenantId: null },\n ]\n }\n return this.em.find(\n SudoChallengeConfig,\n filter,\n {\n orderBy: {\n targetIdentifier: 'asc',\n tenantId: 'asc',\n organizationId: 'asc',\n createdAt: 'asc',\n },\n },\n )\n }\n\n async getConfigById(id: string, scope?: SudoAuthScope): Promise<SudoChallengeConfig | null> {\n await this.ensureDeveloperDefaultsRegistered()\n const config = await this.em.findOne(SudoChallengeConfig, { id, deletedAt: null })\n if (!config) return null\n if (scope && !this.isConfigVisibleToScope(config, scope)) return null\n return config\n }\n\n async isProtected(\n targetIdentifier: string,\n tenantId?: string | null,\n organizationId?: string | null,\n ): Promise<SudoProtectionResolution> {\n await this.ensureDeveloperDefaultsRegistered()\n\n const candidates = await this.em.find(SudoChallengeConfig, {\n targetIdentifier,\n deletedAt: null,\n })\n\n const resolved = candidates\n .filter((config) => this.matchesScope(config, tenantId ?? null, organizationId ?? null))\n .sort((left, right) => this.compareConfigPriority(left, right, tenantId ?? null, organizationId ?? null))[0]\n\n if (!resolved || !resolved.isEnabled) {\n return { protected: false }\n }\n\n return { protected: true, config: resolved }\n }\n\n async initiate(\n userId: string,\n targetIdentifier: string,\n options?: { tenantId?: string | null; organizationId?: string | null },\n ): Promise<{\n required: boolean\n sessionId?: string\n method?: SudoMethod\n availableMfaMethods?: SudoAvailableMethod[]\n expiresAt?: Date\n }> {\n const protection = await this.isProtected(\n targetIdentifier,\n options?.tenantId ?? null,\n options?.organizationId ?? null,\n )\n\n if (!protection.protected || !protection.config) {\n return { required: false }\n }\n\n const user = await this.findUserScope(userId)\n if (!user?.tenantId) {\n throw new SudoChallengeServiceError('User not found', 404)\n }\n\n const userMethods = await this.mfaService.getUserMethods(userId)\n const method = this.resolveChallengeMethod(protection.config.challengeMethod, userMethods.length)\n\n let sessionToken = randomBytes(16).toString('hex')\n let availableMfaMethods: SudoAvailableMethod[] | undefined\n if (method === 'mfa') {\n const challenge = await this.mfaVerificationService.createChallenge(userId)\n sessionToken = challenge.challengeId\n availableMfaMethods = challenge.availableMethods\n }\n\n const expiresAt = new Date(Date.now() + this.securityConfig.sudo.pendingChallengeTtlMs)\n const session = this.em.create(SudoSession, {\n userId,\n tenantId: user.tenantId,\n sessionToken,\n challengeMethod: method,\n expiresAt,\n createdAt: new Date(),\n })\n this.em.persist(session)\n await this.em.flush()\n\n await emitSecurityEvent('security.sudo.challenged', {\n userId,\n tenantId: user.tenantId,\n organizationId: user.organizationId,\n targetIdentifier,\n method,\n })\n\n return {\n required: true,\n sessionId: session.id,\n method,\n availableMfaMethods,\n expiresAt,\n }\n }\n\n async prepare(\n sessionId: string,\n methodType: string,\n options: { expectedUserId?: string } = {},\n request?: Request,\n ): Promise<{ clientData?: Record<string, unknown> }> {\n const session = await this.getPendingSession(sessionId)\n if (options.expectedUserId && session.userId !== options.expectedUserId) {\n throw new SudoChallengeServiceError('Sudo challenge user mismatch', 403)\n }\n if (session.challengeMethod !== 'mfa') {\n throw new SudoChallengeServiceError('This sudo session does not require MFA', 400)\n }\n return this.mfaVerificationService.prepareChallenge(\n session.sessionToken,\n methodType,\n { request },\n { userId: session.userId },\n )\n }\n\n async verify(\n sessionId: string,\n methodType: string,\n payload: unknown,\n options: {\n expectedUserId?: string\n tenantId?: string | null\n organizationId?: string | null\n targetIdentifier: string\n },\n request?: Request,\n ): Promise<{ sudoToken: string; expiresAt: Date }> {\n const session = await this.getPendingSession(sessionId)\n if (options.expectedUserId && session.userId !== options.expectedUserId) {\n throw new SudoChallengeServiceError('Sudo challenge user mismatch', 403)\n }\n const user = await this.findUserScope(session.userId)\n if (!user?.tenantId) {\n throw new SudoChallengeServiceError('User not found', 404)\n }\n\n const scopeTenantId = options.tenantId !== undefined ? options.tenantId : user.tenantId\n const scopeOrganizationId = options.organizationId !== undefined ? options.organizationId : user.organizationId\n const protection = await this.isProtected(\n options.targetIdentifier,\n scopeTenantId,\n scopeOrganizationId,\n )\n if (!protection.protected || !protection.config) {\n throw new SudoChallengeServiceError('Sudo protection is not configured for this target', 404)\n }\n\n let verified = false\n let methodUsed: string = methodType\n if (session.challengeMethod === 'password') {\n const password = this.readPassword(payload)\n verified = await this.passwordService.verifyPassword(session.userId, password)\n methodUsed = SudoChallengeMethodUsed.PASSWORD\n } else {\n verified = await this.mfaVerificationService.verifyChallenge(\n session.sessionToken,\n methodType,\n payload,\n { request },\n { userId: session.userId },\n )\n }\n\n if (!verified) {\n await emitSecurityEvent('security.sudo.failed', {\n userId: session.userId,\n tenantId: user.tenantId,\n organizationId: user.organizationId,\n targetIdentifier: options.targetIdentifier,\n method: methodUsed,\n })\n throw new SudoChallengeServiceError('Unable to verify sudo challenge', 401)\n }\n\n const ttlSeconds = this.normalizeTtl(protection.config.ttlSeconds)\n const expiresAt = new Date(Date.now() + ttlSeconds * 1000)\n const sudoToken = this.signToken({\n sid: session.id,\n sub: session.userId,\n tid: scopeTenantId,\n oid: scopeOrganizationId,\n tgt: options.targetIdentifier,\n exp: expiresAt.getTime(),\n })\n\n session.sessionToken = sudoToken\n session.challengeMethod = methodUsed\n session.expiresAt = expiresAt\n await this.em.flush()\n\n await emitSecurityEvent('security.sudo.verified', {\n userId: session.userId,\n tenantId: scopeTenantId,\n organizationId: scopeOrganizationId,\n targetIdentifier: options.targetIdentifier,\n method: methodUsed,\n expiresAt: expiresAt.toISOString(),\n })\n\n return { sudoToken, expiresAt }\n }\n\n async validateToken(\n token: string,\n targetIdentifier: string,\n options?: {\n expectedUserId?: string\n tenantId?: string | null\n organizationId?: string | null\n },\n ): Promise<boolean> {\n if (!token) return false\n const payload = this.readSignedToken(token)\n if (!payload) return false\n if (payload.exp <= Date.now()) return false\n if (payload.tgt !== targetIdentifier) return false\n if (options?.expectedUserId && payload.sub !== options.expectedUserId) return false\n if (options?.tenantId !== undefined && payload.tid !== (options.tenantId ?? null)) return false\n if (options?.organizationId !== undefined && payload.oid !== (options.organizationId ?? null)) return false\n\n const session = await this.em.findOne(SudoSession, {\n id: payload.sid,\n userId: payload.sub,\n sessionToken: token,\n } as FilterQuery<SudoSession>)\n\n return Boolean(session && session.expiresAt.getTime() > Date.now())\n }\n\n async createConfig(\n input: SudoConfigInput,\n configuredBy: string,\n scope?: SudoAuthScope,\n ): Promise<SudoChallengeConfig> {\n await this.ensureDeveloperDefaultsRegistered()\n const inputTenantId = input.tenantId ?? null\n const inputOrganizationId = input.organizationId ?? null\n this.validateScope(inputTenantId, inputOrganizationId)\n if (scope) this.assertWriteScope(inputTenantId, inputOrganizationId, scope)\n await this.ensureUniqueConfig(input.targetIdentifier, inputTenantId, inputOrganizationId)\n\n const config = this.em.create(SudoChallengeConfig, {\n tenantId: inputTenantId,\n organizationId: inputOrganizationId,\n label: input.label ?? null,\n targetIdentifier: input.targetIdentifier,\n isEnabled: input.isEnabled,\n ttlSeconds: this.normalizeTtl(input.ttlSeconds),\n challengeMethod: input.challengeMethod,\n configuredBy,\n isDeveloperDefault: false,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n this.em.persist(config)\n await this.em.flush()\n\n await emitSecurityEvent('security.sudo.config.created', {\n id: config.id,\n targetIdentifier: config.targetIdentifier,\n configuredBy,\n })\n\n return config\n }\n\n async updateConfig(\n id: string,\n input: SudoConfigUpdateInput,\n configuredBy: string,\n scope?: SudoAuthScope,\n ): Promise<SudoChallengeConfig> {\n await this.ensureDeveloperDefaultsRegistered()\n const config = await this.em.findOne(SudoChallengeConfig, { id, deletedAt: null })\n if (!config) {\n throw new SudoChallengeServiceError('Sudo configuration not found', 404)\n }\n if (scope) {\n this.assertWriteScope(config.tenantId ?? null, config.organizationId ?? null, scope)\n if (config.isDeveloperDefault && !scope.isSuperAdmin) {\n throw new SudoChallengeServiceError('Sudo configuration not found', 404)\n }\n }\n\n const nextTenantId = input.tenantId !== undefined ? input.tenantId ?? null : config.tenantId ?? null\n const nextOrganizationId = input.organizationId !== undefined ? input.organizationId ?? null : config.organizationId ?? null\n const nextTargetIdentifier = input.targetIdentifier ?? config.targetIdentifier\n this.validateScope(nextTenantId, nextOrganizationId)\n if (scope) this.assertWriteScope(nextTenantId, nextOrganizationId, scope)\n await this.ensureUniqueConfig(nextTargetIdentifier, nextTenantId, nextOrganizationId, config.id)\n\n if (input.tenantId !== undefined) config.tenantId = input.tenantId ?? null\n if (input.organizationId !== undefined) config.organizationId = input.organizationId ?? null\n if (input.label !== undefined) config.label = input.label ?? null\n if (input.targetIdentifier !== undefined) config.targetIdentifier = input.targetIdentifier\n if (input.isEnabled !== undefined) config.isEnabled = input.isEnabled\n if (input.ttlSeconds !== undefined) config.ttlSeconds = this.normalizeTtl(input.ttlSeconds)\n if (input.challengeMethod !== undefined) config.challengeMethod = input.challengeMethod\n config.configuredBy = configuredBy\n config.updatedAt = new Date()\n await this.em.flush()\n\n await emitSecurityEvent('security.sudo.config.updated', {\n id: config.id,\n targetIdentifier: config.targetIdentifier,\n configuredBy,\n })\n\n return config\n }\n\n async deleteConfig(id: string, scope?: SudoAuthScope): Promise<void> {\n const config = await this.em.findOne(SudoChallengeConfig, { id, deletedAt: null })\n if (!config) {\n throw new SudoChallengeServiceError('Sudo configuration not found', 404)\n }\n if (scope) {\n this.assertWriteScope(config.tenantId ?? null, config.organizationId ?? null, scope)\n if (config.isDeveloperDefault && !scope.isSuperAdmin) {\n throw new SudoChallengeServiceError('Sudo configuration not found', 404)\n }\n }\n config.deletedAt = new Date()\n config.updatedAt = new Date()\n await this.em.flush()\n\n await emitSecurityEvent('security.sudo.config.deleted', {\n id: config.id,\n targetIdentifier: config.targetIdentifier,\n })\n }\n\n async registerDeveloperDefault(\n input: DeveloperDefaultPayload,\n ): Promise<void> {\n const existing = await this.em.findOne(SudoChallengeConfig, {\n targetIdentifier: input.targetIdentifier,\n tenantId: null,\n organizationId: null,\n isDeveloperDefault: true,\n })\n\n if (existing) {\n existing.isEnabled = true\n existing.deletedAt = null\n existing.ttlSeconds = this.normalizeTtl(input.ttlSeconds)\n existing.challengeMethod = input.challengeMethod ?? ChallengeMethod.AUTO\n existing.updatedAt = new Date()\n await this.em.flush()\n return\n }\n\n const config = this.em.create(SudoChallengeConfig, {\n tenantId: null,\n organizationId: null,\n label: input.label ?? null,\n targetIdentifier: input.targetIdentifier,\n isEnabled: true,\n isDeveloperDefault: true,\n ttlSeconds: this.normalizeTtl(input.ttlSeconds),\n challengeMethod: input.challengeMethod ?? ChallengeMethod.AUTO,\n configuredBy: null,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n this.em.persist(config)\n await this.em.flush()\n }\n\n async cleanupExpired(): Promise<number> {\n return this.em.nativeDelete(SudoSession, {\n expiresAt: { $lte: new Date() },\n })\n }\n\n private async ensureDeveloperDefaultsRegistered(): Promise<void> {\n const registryEntries = getSecuritySudoTargetEntries()\n const registryTargets = registryEntries.flatMap((entry) => entry.targets ?? [])\n const fallbackTargets = registryEntries.length === 0 ? defaultSudoTargets : []\n\n for (const target of dedupeSudoTargets([\n ...registryTargets,\n ...fallbackTargets,\n ])) {\n await this.registerDeveloperDefault(this.readDeveloperDefault(target))\n }\n }\n\n private readDeveloperDefault(target: SecuritySudoTarget): DeveloperDefaultPayload {\n return {\n targetIdentifier: target.identifier,\n label: target.label ?? null,\n ttlSeconds: target.ttlSeconds,\n challengeMethod: this.toChallengeMethod(target.challengeMethod),\n }\n }\n\n private async ensureUniqueConfig(\n targetIdentifier: string,\n tenantId: string | null,\n organizationId: string | null,\n ignoreId?: string,\n ): Promise<void> {\n const existing = await this.em.findOne(SudoChallengeConfig, {\n targetIdentifier,\n tenantId,\n organizationId,\n deletedAt: null,\n })\n if (existing && existing.id !== ignoreId) {\n throw new SudoChallengeServiceError('A sudo configuration for this target and scope already exists', 409)\n }\n }\n\n private validateScope(tenantId: string | null, organizationId: string | null): void {\n if (organizationId && !tenantId) {\n throw new SudoChallengeServiceError('Organization-scoped sudo config requires a tenant', 400)\n }\n }\n\n private isConfigVisibleToScope(config: SudoChallengeConfig, scope: SudoAuthScope): boolean {\n if (scope.isSuperAdmin) return true\n if (!scope.tenantId) return false\n const configTenantId = config.tenantId ?? null\n if (configTenantId === null) return true\n return configTenantId === scope.tenantId\n }\n\n private assertWriteScope(\n targetTenantId: string | null,\n targetOrganizationId: string | null,\n scope: SudoAuthScope,\n ): void {\n if (scope.isSuperAdmin) return\n if (!scope.tenantId) {\n throw new SudoChallengeServiceError('Sudo configuration not found', 404)\n }\n if ((targetTenantId ?? null) !== scope.tenantId) {\n throw new SudoChallengeServiceError('Sudo configuration not found', 404)\n }\n if (\n scope.organizationId !== undefined\n && scope.organizationId !== null\n && targetOrganizationId !== null\n && targetOrganizationId !== scope.organizationId\n ) {\n throw new SudoChallengeServiceError('Sudo configuration not found', 404)\n }\n }\n\n private resolveChallengeMethod(\n configuredMethod: ChallengeMethod,\n availableMfaMethodCount: number,\n ): SudoMethod {\n if (this.securityConfig.mfa.emergencyBypass) {\n return 'password'\n }\n if (configuredMethod === ChallengeMethod.PASSWORD) return 'password'\n if (configuredMethod === ChallengeMethod.MFA) {\n if (availableMfaMethodCount === 0) {\n throw new SudoChallengeServiceError('This sudo target requires MFA, but no MFA methods are configured', 400)\n }\n return 'mfa'\n }\n return availableMfaMethodCount > 0 ? 'mfa' : 'password'\n }\n\n private matchesScope(config: SudoChallengeConfig, tenantId: string | null, organizationId: string | null): boolean {\n if (config.organizationId) {\n return config.organizationId === organizationId && config.tenantId === tenantId\n }\n if (config.tenantId) {\n return config.tenantId === tenantId\n }\n return true\n }\n\n private compareConfigPriority(\n left: SudoChallengeConfig,\n right: SudoChallengeConfig,\n tenantId: string | null,\n organizationId: string | null,\n ): number {\n const leftScore = this.getScopePriority(left, tenantId, organizationId)\n const rightScore = this.getScopePriority(right, tenantId, organizationId)\n if (leftScore !== rightScore) return leftScore - rightScore\n if (left.isDeveloperDefault !== right.isDeveloperDefault) {\n return left.isDeveloperDefault ? 1 : -1\n }\n return right.updatedAt.getTime() - left.updatedAt.getTime()\n }\n\n private getScopePriority(\n config: SudoChallengeConfig,\n tenantId: string | null,\n organizationId: string | null,\n ): number {\n if (config.organizationId === organizationId && config.tenantId === tenantId) return 0\n if (!config.organizationId && config.tenantId === tenantId) return 1\n if (!config.organizationId && !config.tenantId && !config.isDeveloperDefault) return 2\n if (!config.organizationId && !config.tenantId && config.isDeveloperDefault) return 3\n return 4\n }\n\n private async getPendingSession(sessionId: string): Promise<SudoSession> {\n const session = await this.em.findOne(SudoSession, { id: sessionId })\n if (!session) {\n throw new SudoChallengeServiceError('Sudo challenge session not found', 404)\n }\n if (session.expiresAt.getTime() <= Date.now()) {\n throw new SudoChallengeServiceError('Sudo challenge session expired', 400)\n }\n return session\n }\n\n private async findUserScope(userId: string): Promise<UserScope | null> {\n const user = await findOneWithDecryption(\n this.em,\n User,\n { id: userId, deletedAt: null },\n undefined,\n {},\n )\n\n if (!user) return null\n return {\n id: String(user.id),\n tenantId: user.tenantId ? String(user.tenantId) : null,\n organizationId: user.organizationId ? String(user.organizationId) : null,\n }\n }\n\n private normalizeTtl(value?: number | null): number {\n const rawValue = value ?? this.securityConfig.sudo.defaultTtlSeconds\n return Math.min(\n Math.max(rawValue, this.securityConfig.sudo.minTtlSeconds),\n this.securityConfig.sudo.maxTtlSeconds,\n )\n }\n\n private readPassword(payload: unknown): string {\n if (!payload || typeof payload !== 'object') {\n throw new SudoChallengeServiceError('Password is required', 400)\n }\n const maybePassword = (payload as Record<string, unknown>).password\n if (typeof maybePassword !== 'string' || maybePassword.trim().length === 0) {\n throw new SudoChallengeServiceError('Password is required', 400)\n }\n return maybePassword\n }\n\n private signToken(payload: SignedSudoTokenPayload): string {\n const encodedPayload = Buffer.from(JSON.stringify(payload)).toString('base64url')\n const signature = createHmac('sha256', this.getSudoSecret()).update(encodedPayload).digest('base64url')\n return `${encodedPayload}.${signature}`\n }\n\n private readSignedToken(token: string): SignedSudoTokenPayload | null {\n const [encodedPayload, signature] = token.split('.')\n if (!encodedPayload || !signature) return null\n\n const expected = createHmac('sha256', this.getSudoSecret()).update(encodedPayload).digest('base64url')\n const signatureBuffer = Buffer.from(signature)\n const expectedBuffer = Buffer.from(expected)\n if (signatureBuffer.length !== expectedBuffer.length) return null\n if (!timingSafeEqual(signatureBuffer, expectedBuffer)) return null\n\n try {\n const parsed = signedSudoTokenPayloadSchema.safeParse(\n JSON.parse(Buffer.from(encodedPayload, 'base64url').toString('utf8')),\n )\n if (!parsed.success) return null\n return parsed.data\n } catch {\n return null\n }\n }\n\n private getSudoSecret(): string {\n const secret =\n readTrimmedEnv(process.env.OM_SECURITY_SUDO_SECRET)\n ?? readTrimmedEnv(process.env.AUTH_JWT_SECRET)\n ?? readTrimmedEnv(process.env.AUTH_SECRET)\n ?? readTrimmedEnv(process.env.JWT_SECRET)\n if (secret) return secret\n throw new Error(\n 'Sudo step-up tokens require OM_SECURITY_SUDO_SECRET, AUTH_JWT_SECRET, AUTH_SECRET, or JWT_SECRET to be set.',\n )\n }\n\n private toChallengeMethod(\n method: SecuritySudoTarget['challengeMethod'],\n ): ChallengeMethod | undefined {\n switch (method) {\n case 'password':\n return ChallengeMethod.PASSWORD\n case 'mfa':\n return ChallengeMethod.MFA\n case 'auto':\n default:\n return ChallengeMethod.AUTO\n }\n }\n}\n\nexport default SudoChallengeService\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -159,7 +159,6 @@ function SsoConfigListPage() {
159
159
  pageSize: 50,
160
160
  total: data.total,
161
161
  totalPages: data.totalPages,
162
- totalIsCapped: data?.totalIsCapped === true,
163
162
  onPageChange: setPage
164
163
  },
165
164
  isLoading