928a145f9a
- Replace magic-link-only login with email+password (primary) and magic link (toggle) - Add registration page with strong password validation - Add MFA enrollment (/mfa/enroll) with QR code and manual secret - Add MFA verification (/mfa/verify) with 6-digit TOTP input - Add password reset flow (/reset-password) - Add middleware MFA enforcement gated by NEXT_PUBLIC_REQUIRE_MFA env var - Self-hosted deployments (NEXT_PUBLIC_SELF_HOSTED=true) skip MFA entirely - Add Security tab in Settings for password change and MFA management - Add requireAuth() API route helper with MFA check - Update CLAUDE.md with Authentication section and env var docs - Update Dockerfile and docker-entrypoint.sh for new env var placeholders Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
41 lines
1.2 KiB
TypeScript
41 lines
1.2 KiB
TypeScript
import { createClient } from '@/lib/supabase/server'
|
|
import { NextResponse } from 'next/server'
|
|
import { isMfaRequired } from './mfa'
|
|
import type { User, SupabaseClient } from '@supabase/supabase-js'
|
|
|
|
type AuthResult =
|
|
| { user: User; supabase: SupabaseClient; error: null }
|
|
| { user: null; supabase: SupabaseClient; error: NextResponse }
|
|
|
|
/**
|
|
* Auth + MFA guard for API routes.
|
|
*
|
|
* Returns the authenticated user and Supabase client, or a JSON error response.
|
|
* When MFA is required (hosted deployment), verifies AAL2 assurance level.
|
|
*/
|
|
export async function requireAuth(): Promise<AuthResult> {
|
|
const supabase = await createClient()
|
|
const { data: { user } } = await supabase.auth.getUser()
|
|
|
|
if (!user) {
|
|
return {
|
|
user: null,
|
|
supabase,
|
|
error: NextResponse.json({ error: 'Unauthorized' }, { status: 401 }),
|
|
}
|
|
}
|
|
|
|
if (isMfaRequired()) {
|
|
const { data: aal } = await supabase.auth.mfa.getAuthenticatorAssuranceLevel()
|
|
if (aal?.nextLevel === 'aal2' && aal?.currentLevel !== 'aal2') {
|
|
return {
|
|
user: null,
|
|
supabase,
|
|
error: NextResponse.json({ error: 'MFA verification required' }, { status: 403 }),
|
|
}
|
|
}
|
|
}
|
|
|
|
return { user, supabase, error: null }
|
|
}
|