-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathproxy.ts
More file actions
190 lines (157 loc) · 5.94 KB
/
proxy.ts
File metadata and controls
190 lines (157 loc) · 5.94 KB
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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
import { NextRequest, NextResponse } from 'next/server';
import { JwtPayload } from 'jsonwebtoken'
import createMiddleware from 'next-intl/middleware';
import { routing } from './i18n/routing';
import { verifyJwtToken } from './lib/server/auth';
import logECS from '@/utils/clientLogger';
import { refreshAuthTokens } from "@/utils/authHelper";
import { getAuthTokenServer } from '@/utils/getAuthTokenServer';
import { locales, defaultLocale } from './config/i18nConfig';
interface JWTAccessToken extends JwtPayload {
id: number,
email: string,
givenName: string,
surName: string,
role: string,
languageId: string,
jti: string,
expiresIn: number,
}
// TODO: These routes will need to be updated.
const excludedPaths = ['/email', '/favicon.ico', '/_next', '/api', '/login', '/signup', '/styleguide'];
// Check if the request is for a server action
function isServerAction(request: NextRequest): boolean {
// Server actions are POST requests with specific content types
return (
request.method === 'POST' &&
(
request.headers.get('content-type')?.includes('text/plain') ||
request.headers.get('next-action') !== null ||
request.headers.get('next-router-state-tree') !== null
)
);
}
const handleI18nRouting = createMiddleware(routing);
async function getLocaleFromJWT(): Promise<string | null> {
try {
const token = await getAuthTokenServer();
if (!token) {
return null;
}
const user = await verifyJwtToken(token);
if (!user) {
return null;
}
const { languageId } = user as JWTAccessToken;
if (languageId && locales.includes(languageId)) {
return languageId;
}
return null;
} catch (error) {
console.error('Error parsing JWT:', error);
return null;
}
}
async function getLocale(request: NextRequest) {
try {
// First try and get locale from JWT
const userLocale = await getLocaleFromJWT();
if (userLocale) return userLocale;
//Fall back to Accept-Language header
const acceptLanguage = request.headers.get('Accept-Language');
if (acceptLanguage) {
const browserLocale = acceptLanguage
.split(',')[0]
.split('-')[0]
.toLowerCase();
if (locales.includes(browserLocale)) {
return browserLocale
}
}
//Otherwise, use Default locale
return defaultLocale;
} catch (error) {
console.log('Error detecting locale:', error);
return defaultLocale;
}
}
export async function proxy(request: NextRequest) {
const response = NextResponse.next();
const { pathname } = request.nextUrl;
/* TODO: might want to add a 'redirect' query param to url to redirect user after
login to the original page they were trying to get to.*/
// Exclude paths from authentication checks
const isExcludedPath = excludedPaths.some((path) => pathname.includes(path));
// Also exclude server actions from authentication middleware
const isServerActionRequest = isServerAction(request);
// Build cookie header string from NextRequest cookies
const cookieHeader = request.cookies
.getAll()
.map(({ name, value }) => `${name}=${value}`)
.join("; ");
const accessToken = request.cookies.get('dmspt');
const refreshToken = request.cookies.get('dmspr');
const locale = await getLocale(request);
// Redirect to login if no tokens are found
if (!isExcludedPath && !isServerActionRequest) {
if (!accessToken && !refreshToken) {
return NextResponse.redirect(new URL(`/${locale}/login`, request.url));
}
//Refresh tokens if necessary
if (!accessToken && refreshToken) {
try {
const refreshResult = await refreshAuthTokens(cookieHeader);
if (refreshResult?.response) {
const backendResponse = refreshResult.response;
// We need to redirect to the same URL to ensure cookies are set properly in browser
const newResponse = NextResponse.redirect(request.url);
// Copy Set-Cookie headers from backend response to NextResponse
backendResponse.headers.forEach((value, key) => {
if (key.toLowerCase() === 'set-cookie') {
newResponse.headers.append('set-cookie', value);
}
});
return newResponse;
}
// If refresh helper tells us to redirect, do it now
if (refreshResult?.shouldRedirect) {
const redirectResponse = NextResponse.redirect(new URL(`/${locale}/login`, request.url));
// Clear the expired refresh token so subsequent requests don't keep trying to refresh
redirectResponse.cookies.set('dmspr', '', { maxAge: 0 });
return redirectResponse;
}
} catch (error) {
logECS('error', 'refreshing', {
error,
url: { path: 'middleware' }
});
const redirectResponse = NextResponse.redirect(new URL(`/${locale}/login`, request.url));
// Clear the expired refresh token so subsequent requests don't keep trying to refresh
redirectResponse.cookies.set('dmspr', '', { maxAge: 0 });
return redirectResponse;
}
}
}
const pathnameIsMissingLocale = locales.every(
(locale) => !pathname.startsWith(`/${locale}`) && pathname !== `/${locale}`
);
if (pathnameIsMissingLocale) {
const locale = await getLocale(request);
const newUrl = new URL(`/${locale}${pathname}`, request.url);
if (request.nextUrl.search) {
newUrl.search = request.nextUrl.search; // Only assign if it's valid
}
return NextResponse.redirect(newUrl);
}
const i18nResponse = handleI18nRouting(request);
if (i18nResponse) return i18nResponse;
// Add url info to custom header. Need this for just the /dmps landing page
if (request.nextUrl.pathname.startsWith('/en-US/dmps')) {
response.headers.set('x-url', request.nextUrl.href);
}
return response;
}
export const config = {
// Don't run middleware for api endpoints, static files, anything in our pubic folder or _next files
matcher: ['/((?!api|_next|static|.*\\..*).*)',]
};