-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathmiddleware.ts
150 lines (125 loc) · 4.71 KB
/
middleware.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
import { corsConfig } from '@/config/cors'
import { supabase } from '@/lib/supabase'
function isAllowedOrigin(origin: string | null) {
if (!origin) return false
if (corsConfig.allowedOrigins.includes('*')) return true
return corsConfig.allowedOrigins.includes(origin)
}
export async function middleware(request: NextRequest) {
console.log('Middleware running for path:', request.nextUrl.pathname)
const origin = request.headers.get('origin')
// Handle CORS preflight requests
if (request.method === 'OPTIONS') {
const response = new NextResponse(null, { status: 200 })
if (corsConfig.allowedOrigins.includes('*')) {
response.headers.set('Access-Control-Allow-Origin', '*')
} else if (origin && isAllowedOrigin(origin)) {
response.headers.set('Access-Control-Allow-Origin', origin)
}
response.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization')
return response
}
// Create response early so we can add CORS headers to all responses
const response = NextResponse.next()
if (corsConfig.allowedOrigins.includes('*')) {
response.headers.set('Access-Control-Allow-Origin', '*')
} else if (origin && isAllowedOrigin(origin)) {
response.headers.set('Access-Control-Allow-Origin', origin)
}
response.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization')
// Add your public paths that should bypass auth
const publicPaths = [
'/',
'/login',
'/signup',
'/verify-email',
'/verify-otp',
'/api/auth',
'/api/auth/authorize',
'/api/auth/token',
'/api/auth/userinfo',
'/api/auth/otp',
'/api/auth/signup',
'/forgot-password',
'/reset-password',
'/auth/2fa-verify',
'/_next',
'/favicon.ico',
'/sso-integration/test.html',
'/sso-integration/callback.html',
'/developer-docs',
'/developer-docs/',
'/about',
'/about/'
]
// Get auth tokens
const accessToken = request.cookies.get('access_token')?.value
console.log('Auth tokens:', { accessToken })
// Check if the requested path is exactly one of the public paths
const isPublicPath = publicPaths.some(path =>
request.nextUrl.pathname === path ||
request.nextUrl.pathname.startsWith('/_next/') ||
request.nextUrl.pathname.startsWith('/images/') ||
request.nextUrl.pathname.startsWith('/sso-integration/') ||
request.nextUrl.pathname.startsWith('/api/auth/') ||
// Allow login page with any query parameters
(request.nextUrl.pathname === '/login' && request.nextUrl.search)
)
console.log('Is public path?', isPublicPath, 'Path:', request.nextUrl.pathname, 'Search:', request.nextUrl.search)
// If it's a public path, allow access
if (isPublicPath) {
return response
}
// For protected routes (including dashboard)
if (!accessToken) {
console.log('No valid session, redirecting to login')
return NextResponse.redirect(new URL('/login', request.url))
}
// Get user info from Keycloak
try {
const userInfoResponse = await fetch(
`${process.env.NEXT_PUBLIC_KEYCLOAK_URL}/realms/${process.env.NEXT_PUBLIC_KEYCLOAK_REALM}/protocol/openid-connect/userinfo`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
},
}
)
if (!userInfoResponse.ok) {
console.log('Invalid token, redirecting to login')
return NextResponse.redirect(new URL('/login', request.url))
}
const userInfo = await userInfoResponse.json()
console.log('User info:', userInfo)
// Check if 2FA is required
const { data: settings, error } = await supabase
.from('user_2fa_settings')
.select('totp_enabled')
.eq('user_id', userInfo.sub)
.single()
console.log('2FA settings:', settings, 'Error:', error)
// If 2FA is enabled but not verified
if (settings?.totp_enabled && !request.cookies.get('2fa_verified')) {
console.log('2FA required but not verified')
// If already on 2FA verification page, allow access
if (request.nextUrl.pathname === '/auth/2fa-verify') {
return response
}
console.log('Redirecting to 2FA verification')
return NextResponse.redirect(new URL('/auth/2fa-verify', request.url))
}
} catch (error) {
console.error('Middleware error:', error)
return NextResponse.redirect(new URL('/login', request.url))
}
return response
}
export const config = {
matcher: [
'/((?!api/auth|_next/static|_next/image|favicon.ico|images/).*)',
],
}