/** * @fileoverview Authentication Domain - Modern Supabase Auth with JWKS * v0.18.0 Enhancement: Publishable/Secret key pattern with JWKS validation * Purpose: Modern Supabase auth, JWT validation, role-based access control */ import { z } from 'zod'; import { RoleNameSchema, PermissionNameSchema, SessionStatusSchema } from '../types/enums.js'; // Re-export shared enums for backward compatibility and convenience export const RoleName = RoleNameSchema; export const PermissionName = PermissionNameSchema; export const SessionStatus = SessionStatusSchema; // ML-specific authentication scopes (ML Team requirement) export const MLAuthScope = z.enum([ 'ml:signals:read', // Read ML signals 'ml:signals:write', // Generate signals 'ml:models:read', // View model metadata 'ml:models:deploy', // Deploy models to production 'ml:consensus:read', // Read consensus decisions 'ml:consensus:write', // Generate consensus 'ml:features:read', // Read feature data 'ml:features:write', // Store feature data 'ml:performance:read', // View performance metrics 'ml:admin', // Emergency stop, model management 'ml:marketplace' // Publish models to marketplace ]); // Standard role definitions with Zod schema validation (using shared enum) export const StandardRoleSchema = RoleNameSchema.describe('Standard application roles'); export const STANDARD_ROLES = { OWNER: 'owner' as const, ML_DEVELOPER: 'ml_developer' as const, CLIENT: 'client' as const, ADMIN: 'admin' as const } as const satisfies Record>; // Role permissions mapping with Zod schema validation export const RolePermissionsSchema = z.record( StandardRoleSchema, z.array(PermissionName) ).describe('Role to permissions mapping'); export const ROLE_PERMISSIONS = { owner: ['read:all', 'write:all', 'admin:all', 'ml:admin'] as const, ml_developer: ['ml:signals:read', 'ml:signals:write', 'ml:models:read', 'ml:models:deploy', 'ml:consensus:read', 'ml:consensus:write', 'ml:features:read', 'ml:features:write', 'ml:performance:read'] as const, client: ['read:portfolio', 'read:signals', 'ml:signals:read'] as const, admin: ['admin:all', 'read:all'] as const } as const satisfies z.infer; // Modern Supabase JWT Claims (JWKS validated) export const SupabaseJWTClaims = z.object({ sub: z.string().uuid(), // User ID (Supabase auth.users.id) email: z.string().email(), aud: z.literal('authenticated'), // Supabase audience iss: z.string().url(), // Supabase issuer URL iat: z.number().int().positive(), // Issued at (Unix timestamp) exp: z.number().int().positive(), // Expires at (Unix timestamp) // Supabase-specific claims role: z.literal('authenticated'), // Supabase role (always 'authenticated') session_id: z.string().uuid(), // Custom app metadata (stored in auth.users.app_metadata) app_metadata: z.object({ provider: z.string().default('email'), providers: z.array(z.string()).default(['email']) }).strict().optional(), // Custom user metadata (stored in auth.users.user_metadata) user_metadata: z.object({ app_role: RoleName.optional(), // Our custom role system permissions: z.array(PermissionName).optional(), organization_id: z.string().uuid().optional() }).strict().optional() }).strict(); // Role Definition (SSOT for permissions) export const RoleDefinition = z.object({ name: RoleName, permissions: z.array(PermissionName), description: z.string(), inherits_from: RoleName.optional(), // Role hierarchy created_at: z.string().datetime(), updated_at: z.string().datetime() }).strict(); // User Profile (Enhanced) export const UserProfile = z.object({ id: z.string().uuid(), email: z.string().email(), roles: z.array(RoleName), permissions: z.array(PermissionName), // Computed from roles organization_id: z.string().uuid().optional(), preferences: z.record(z.any()).optional(), created_at: z.string().datetime(), updated_at: z.string().datetime(), last_login: z.string().datetime().optional(), is_active: z.boolean().default(true) }).strict(); // Session Management export const UserSession = z.object({ id: z.string().uuid(), user_id: z.string().uuid(), token_hash: z.string(), // SHA256 of JWT expires_at: z.number().int().positive(), created_at: z.string().datetime(), last_accessed: z.string().datetime(), ip_address: z.string().optional(), user_agent: z.string().optional(), status: SessionStatus }).strict(); // Modern Supabase Auth Responses (using publishable/secret key pattern) export const SupabaseAuthResponse = z.object({ success: z.literal(true), data: z.object({ user: z.object({ id: z.string().uuid(), email: z.string().email(), phone: z.string().optional(), created_at: z.string().datetime(), updated_at: z.string().datetime(), last_sign_in_at: z.string().datetime().optional(), // Supabase metadata app_metadata: z.object({ provider: z.string(), providers: z.array(z.string()) }).strict(), user_metadata: z.object({ app_role: RoleName.optional(), permissions: z.array(PermissionName).optional(), organization_id: z.string().uuid().optional() }).strict() }).strict(), session: z.object({ access_token: z.string(), // JWT validated via JWKS refresh_token: z.string(), expires_in: z.number().int().positive(), expires_at: z.number().int().positive(), token_type: z.literal('bearer'), user: z.object({ id: z.string().uuid(), email: z.string().email() }).strict() }).strict() }).strict(), requestId: z.string(), timestamp: z.string().datetime() }).strict(); export const RefreshTokenRequest = z.object({ refresh_token: z.string() }).strict(); // JWKS Validation (Modern Supabase pattern) export const JWKSKey = z.object({ kty: z.literal('RSA'), // Key type use: z.literal('sig'), // Usage kid: z.string(), // Key ID n: z.string(), // RSA modulus e: z.string(), // RSA exponent alg: z.literal('RS256') // Algorithm }).strict(); export const JWKSResponse = z.object({ success: z.literal(true), data: z.object({ keys: z.array(JWKSKey) }).strict(), requestId: z.string(), timestamp: z.string().datetime() }).strict(); // Token Validation (JWKS-based) export const ValidateTokenRequest = z.object({ access_token: z.string(), // JWT to validate require_permissions: z.array(PermissionName).optional(), resource_id: z.string().optional() // Resource-specific validation }).strict(); export const TokenValidationResponse = z.object({ success: z.literal(true), data: z.object({ valid: z.boolean(), claims: SupabaseJWTClaims.optional(), user_id: z.string().uuid().optional(), app_role: RoleName.optional(), permissions: z.array(PermissionName).optional(), expires_in: z.number().int().optional(), // Seconds until expiry organization_id: z.string().uuid().optional() }).strict(), requestId: z.string(), timestamp: z.string().datetime() }).strict(); // Permission Check Request export const PermissionCheckRequest = z.object({ user_id: z.string().uuid(), permission: PermissionName, resource_id: z.string().optional() // Optional resource-specific check }).strict(); export const PermissionCheckResponse = z.object({ success: z.literal(true), data: z.object({ granted: z.boolean(), reason: z.string().optional() }).strict(), requestId: z.string(), timestamp: z.string().datetime() }).strict(); // Enhanced User Auth Response (Frontend requirement) export const UserAuthResponse = z.object({ success: z.literal(true), data: z.object({ user: z.object({ id: z.string().uuid(), email: z.string().email(), roles: z.array(RoleName), permissions: z.array(PermissionName) }).strict(), session: z.object({ expires_at: z.number().int().positive(), token_type: z.literal('Bearer'), access_token: z.string(), refresh_token: z.string().optional() }).strict() }).strict(), requestId: z.string(), timestamp: z.string().datetime() }).strict(); // ✅ RESTORED: Session Envelope (used by SSR getUser()) export const SessionEnvelope = z.object({ status: z.literal('ok'), user: z.object({ id: z.string().uuid(), email: z.string().email().nullable(), roles: z.array(RoleName).optional(), permissions: z.array(PermissionName).optional() }).strict() }).strict(); // ✅ RESTORED: User Envelope (general user data responses) export const UserEnvelope = z.object({ status: z.literal('ok'), data: z.any(), meta: z.record(z.any()).optional() }).strict(); // ✅ RESTORED: Auth Response (general auth responses) export const AuthResponse = z.object({ status: z.literal('ok'), data: z.any(), message: z.string().optional() }).strict(); // Type exports (using shared types for consistency) export type RoleNameType = z.infer; export type PermissionNameType = z.infer; export type StandardRoleType = z.infer; export type RolePermissionsType = z.infer; export type MLAuthScopeType = z.infer; export type SupabaseJWTClaimsType = z.infer; export type JWKSKeyType = z.infer; export type JWKSResponseType = z.infer; export type RoleDefinitionType = z.infer; export type UserProfileType = z.infer; export type UserSessionType = z.infer; export type SupabaseAuthResponseType = z.infer; export type UserAuthResponseType = z.infer; export type RefreshTokenRequestType = z.infer; export type ValidateTokenRequestType = z.infer; export type TokenValidationResponseType = z.infer; export type PermissionCheckRequestType = z.infer; export type PermissionCheckResponseType = z.infer; // ✅ RESTORED: Envelope type exports export type SessionEnvelopeType = z.infer; export type UserEnvelopeType = z.infer; export type AuthResponseType = z.infer;