/** * TASK-0131 — SSO ENFORCE mode (org policy `ssoEnforced`). * * The check is a module-AGNOSTIC free function (takes a Prisma client, no * Nest DI) so it can be called from `AuthService.login`/`register` WITHOUT * creating an Auth→Sso→Auth module cycle (the SSO provisioning service * already depends on `AuthService` for session minting). `AuthService` holds * a `PrismaService`, so it passes its own client in. * * Resolution (per the task spec): email → user → org memberships → the orgs * that have an ACTIVE, ENFORCED `SsoProvider`. A user with NO org (or an * unknown email) resolves to [] → normal password login (the fast path). * The mirror invariant (spec: "mirrored into SsoProvider.enforce as the * source of truth"): the admin policy editor writes BOTH `Organization.policy * .ssoEnforced` and `SsoProvider.enforce`; this reads `SsoProvider.enforce` * (the source of truth), so the two cannot drift at login time. */ import type { PrismaClient } from "@prisma/client"; export interface EnforcedOrg { orgId: string; slug: string; type: "SAML" | "OIDC"; } export async function resolveEnforcedSsoOrgs( prisma: PrismaClient, email: string ): Promise { const user = await prisma.user.findUnique({ where: { email: email.toLowerCase() }, select: { orgMemberships: { select: { organizationId: true } } } }); if (!user || user.orgMemberships.length === 0) return []; const providers = await prisma.ssoProvider.findMany({ where: { organizationId: { in: user.orgMemberships.map((m) => m.organizationId) }, enforce: true }, select: { organizationId: true, type: true, organization: { select: { slug: true } } } }); return providers.map((p) => ({ orgId: p.organizationId, slug: p.organization.slug, type: p.type })); }