create-tigra 2.0.3 → 2.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import type { NextConfig } from "next";
|
|
2
2
|
|
|
3
|
+
const apiBaseUrl = process.env.NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000";
|
|
4
|
+
const apiOrigin = (() => {
|
|
5
|
+
try {
|
|
6
|
+
return new URL(apiBaseUrl).origin;
|
|
7
|
+
} catch {
|
|
8
|
+
return "http://localhost:8000";
|
|
9
|
+
}
|
|
10
|
+
})();
|
|
11
|
+
|
|
3
12
|
const nextConfig: NextConfig = {
|
|
4
13
|
async headers() {
|
|
5
14
|
return [
|
|
@@ -22,7 +31,7 @@ const nextConfig: NextConfig = {
|
|
|
22
31
|
"base-uri 'self'",
|
|
23
32
|
"form-action 'self'",
|
|
24
33
|
"frame-ancestors 'none'",
|
|
25
|
-
`connect-src 'self' ${
|
|
34
|
+
`connect-src 'self' ${apiOrigin}`,
|
|
26
35
|
].join("; "),
|
|
27
36
|
},
|
|
28
37
|
],
|
|
@@ -80,12 +80,22 @@ apiClient.interceptors.response.use(
|
|
|
80
80
|
} catch (refreshError) {
|
|
81
81
|
processQueue(refreshError);
|
|
82
82
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
83
|
+
// Only logout on definitive auth failures (server responded with 4xx).
|
|
84
|
+
// Network errors (server unreachable, timeout) should NOT destroy the session.
|
|
85
|
+
const isAuthFailure =
|
|
86
|
+
axios.isAxiosError(refreshError) &&
|
|
87
|
+
refreshError.response != null &&
|
|
88
|
+
refreshError.response.status >= 400 &&
|
|
89
|
+
refreshError.response.status < 500;
|
|
90
|
+
|
|
91
|
+
if (isAuthFailure) {
|
|
92
|
+
const { logout } = await import('@/features/auth/store/authSlice');
|
|
93
|
+
const { store } = await import('@/store');
|
|
94
|
+
store.dispatch(logout());
|
|
95
|
+
|
|
96
|
+
if (typeof window !== 'undefined') {
|
|
97
|
+
window.location.href = ROUTES.LOGIN;
|
|
98
|
+
}
|
|
89
99
|
}
|
|
90
100
|
|
|
91
101
|
return Promise.reject(refreshError);
|
|
@@ -23,12 +23,21 @@ export function middleware(request: NextRequest): NextResponse {
|
|
|
23
23
|
path === '/' ? pathname === '/' : pathname.startsWith(path)
|
|
24
24
|
);
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
// No token at all — user is not logged in, redirect to login
|
|
27
|
+
if (isProtectedPath && !token) {
|
|
27
28
|
const loginUrl = new URL('/login', request.url);
|
|
28
29
|
loginUrl.searchParams.set('from', pathname);
|
|
29
30
|
return NextResponse.redirect(loginUrl);
|
|
30
31
|
}
|
|
31
32
|
|
|
33
|
+
// Expired token on protected path — allow through so client-side can attempt refresh.
|
|
34
|
+
// Delete the stale access_token so AuthInitializer starts fresh: getMe() → 401 → refresh.
|
|
35
|
+
if (isProtectedPath && token && isTokenExpired(token)) {
|
|
36
|
+
const response = NextResponse.next();
|
|
37
|
+
response.cookies.delete('access_token');
|
|
38
|
+
return response;
|
|
39
|
+
}
|
|
40
|
+
|
|
32
41
|
const isAuthPath = authPaths.some((path) => pathname.startsWith(path));
|
|
33
42
|
|
|
34
43
|
if (isAuthPath && token) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
|
|
2
2
|
import { v4 as uuidv4 } from 'uuid';
|
|
3
3
|
import { env } from '@config/env.js';
|
|
4
|
+
import { prisma } from '@libs/prisma.js';
|
|
4
5
|
import { UnauthorizedError, ForbiddenError } from '@shared/errors/errors.js';
|
|
5
6
|
import type { JwtPayload, UserRole } from '@shared/types/index.js';
|
|
6
7
|
|
|
@@ -58,6 +59,16 @@ export async function authenticate(
|
|
|
58
59
|
} catch {
|
|
59
60
|
throw new UnauthorizedError('Invalid or expired token');
|
|
60
61
|
}
|
|
62
|
+
|
|
63
|
+
// Verify user still exists, is active, and not soft-deleted
|
|
64
|
+
const user = await prisma.user.findUnique({
|
|
65
|
+
where: { id: request.user.userId },
|
|
66
|
+
select: { isActive: true, deletedAt: true },
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
if (!user || !user.isActive || user.deletedAt) {
|
|
70
|
+
throw new UnauthorizedError('Account is deactivated or deleted');
|
|
71
|
+
}
|
|
61
72
|
}
|
|
62
73
|
|
|
63
74
|
export async function optionalAuth(
|
|
@@ -54,15 +54,17 @@ function sanitizeUser(user: {
|
|
|
54
54
|
isActive: boolean;
|
|
55
55
|
createdAt: Date;
|
|
56
56
|
updatedAt: Date;
|
|
57
|
-
password: string;
|
|
58
57
|
}): SanitizedUser {
|
|
59
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
60
|
-
const { password: _password, createdAt, updatedAt, avatarUrl, ...rest } = user;
|
|
61
58
|
return {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
59
|
+
id: user.id,
|
|
60
|
+
email: user.email,
|
|
61
|
+
firstName: user.firstName,
|
|
62
|
+
lastName: user.lastName,
|
|
63
|
+
avatarUrl: user.avatarUrl ?? null,
|
|
64
|
+
role: user.role,
|
|
65
|
+
isActive: user.isActive,
|
|
66
|
+
createdAt: user.createdAt.toISOString(),
|
|
67
|
+
updatedAt: user.updatedAt.toISOString(),
|
|
66
68
|
};
|
|
67
69
|
}
|
|
68
70
|
|