auth flow
This commit is contained in:
parent
d7922351c0
commit
a9294b0231
@ -112,24 +112,17 @@ export const authOptions: NextAuthOptions = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
profile(profile) {
|
profile(profile) {
|
||||||
console.log('Keycloak profile callback:', {
|
// Simplified profile logging to reduce console noise
|
||||||
rawProfile: profile,
|
console.log('Keycloak profile received');
|
||||||
rawRoles: profile.roles,
|
|
||||||
realmAccess: profile.realm_access,
|
|
||||||
groups: profile.groups
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get roles from realm_access
|
// Get roles from realm_access
|
||||||
const roles = profile.realm_access?.roles || [];
|
const roles = profile.realm_access?.roles || [];
|
||||||
console.log('Profile callback raw roles:', roles);
|
|
||||||
|
|
||||||
// Clean up roles by removing ROLE_ prefix and converting to lowercase
|
// Clean up roles by removing ROLE_ prefix and converting to lowercase
|
||||||
const cleanRoles = roles.map((role: string) =>
|
const cleanRoles = roles.map((role: string) =>
|
||||||
role.replace(/^ROLE_/, '').toLowerCase()
|
role.replace(/^ROLE_/, '').toLowerCase()
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log('Profile callback cleaned roles:', cleanRoles);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: profile.sub,
|
id: profile.sub,
|
||||||
name: profile.name ?? profile.preferred_username,
|
name: profile.name ?? profile.preferred_username,
|
||||||
@ -144,7 +137,7 @@ export const authOptions: NextAuthOptions = {
|
|||||||
],
|
],
|
||||||
session: {
|
session: {
|
||||||
strategy: "jwt",
|
strategy: "jwt",
|
||||||
maxAge: 30 * 24 * 60 * 60, // 30 days
|
maxAge: 12 * 60 * 60, // Reduce to 12 hours to help with token size
|
||||||
},
|
},
|
||||||
cookies: {
|
cookies: {
|
||||||
sessionToken: {
|
sessionToken: {
|
||||||
@ -155,12 +148,13 @@ export const authOptions: NextAuthOptions = {
|
|||||||
path: '/',
|
path: '/',
|
||||||
secure: true,
|
secure: true,
|
||||||
domain: process.env.NEXTAUTH_COOKIE_DOMAIN || undefined,
|
domain: process.env.NEXTAUTH_COOKIE_DOMAIN || undefined,
|
||||||
|
maxAge: 12 * 60 * 60, // Match session maxAge
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
jwt: {
|
jwt: {
|
||||||
// Add explicit max size to prevent chunking
|
// Maximum JWT size to prevent chunking
|
||||||
maxAge: 30 * 24 * 60 * 60, // 30 days
|
maxAge: 12 * 60 * 60, // Reduce to 12 hours
|
||||||
},
|
},
|
||||||
callbacks: {
|
callbacks: {
|
||||||
async jwt({ token, account, profile }) {
|
async jwt({ token, account, profile }) {
|
||||||
@ -168,26 +162,27 @@ export const authOptions: NextAuthOptions = {
|
|||||||
if (account && profile) {
|
if (account && profile) {
|
||||||
const keycloakProfile = profile as KeycloakProfile;
|
const keycloakProfile = profile as KeycloakProfile;
|
||||||
const roles = keycloakProfile.realm_access?.roles || [];
|
const roles = keycloakProfile.realm_access?.roles || [];
|
||||||
const cleanRoles = roles.map((role: string) =>
|
|
||||||
role.replace(/^ROLE_/, '').toLowerCase()
|
|
||||||
);
|
|
||||||
|
|
||||||
// Store minimal data in the token
|
// Only include admin, owner, user roles (most critical)
|
||||||
token.accessToken = account.access_token ?? '';
|
const criticalRoles = roles
|
||||||
token.refreshToken = account.refresh_token ?? '';
|
.filter(role =>
|
||||||
|
role.includes('admin') ||
|
||||||
|
role.includes('owner') ||
|
||||||
|
role.includes('user')
|
||||||
|
)
|
||||||
|
.map(role => role.replace(/^ROLE_/, '').toLowerCase());
|
||||||
|
|
||||||
|
// Store absolute minimal data in the token
|
||||||
|
token.accessToken = account.access_token;
|
||||||
|
token.refreshToken = account.refresh_token;
|
||||||
token.accessTokenExpires = account.expires_at ?? 0;
|
token.accessTokenExpires = account.expires_at ?? 0;
|
||||||
token.sub = keycloakProfile.sub;
|
token.sub = keycloakProfile.sub;
|
||||||
token.role = cleanRoles;
|
token.role = criticalRoles.length > 0 ? criticalRoles : ['user']; // Only critical roles
|
||||||
token.username = keycloakProfile.preferred_username ?? '';
|
token.username = keycloakProfile.preferred_username?.substring(0, 30) ?? '';
|
||||||
token.error = undefined; // Clear any errors
|
token.error = undefined;
|
||||||
|
|
||||||
// Only store these if they're short
|
// Don't store first/last name in the token to save space
|
||||||
if (keycloakProfile.given_name && keycloakProfile.given_name.length < 30) {
|
// Applications can get these from the userinfo endpoint if needed
|
||||||
token.first_name = keycloakProfile.given_name;
|
|
||||||
}
|
|
||||||
if (keycloakProfile.family_name && keycloakProfile.family_name.length < 30) {
|
|
||||||
token.last_name = keycloakProfile.family_name;
|
|
||||||
}
|
|
||||||
|
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
@ -235,15 +230,22 @@ export const authOptions: NextAuthOptions = {
|
|||||||
...session,
|
...session,
|
||||||
error: "RefreshTokenError",
|
error: "RefreshTokenError",
|
||||||
user: {
|
user: {
|
||||||
...session.user,
|
id: token.sub ?? '',
|
||||||
id: token.sub ?? ''
|
role: ['user'], // Default role
|
||||||
|
username: '', // Empty username
|
||||||
|
first_name: '',
|
||||||
|
last_name: '',
|
||||||
|
name: null,
|
||||||
|
email: null,
|
||||||
|
image: null,
|
||||||
|
nextcloudInitialized: false,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const userRoles = Array.isArray(token.role) ? token.role : [];
|
const userRoles = Array.isArray(token.role) ? token.role : [];
|
||||||
|
|
||||||
// Create a minimal user object
|
// Create an extremely minimal user object
|
||||||
session.user = {
|
session.user = {
|
||||||
id: token.sub ?? '',
|
id: token.sub ?? '',
|
||||||
email: token.email ?? null,
|
email: token.email ?? null,
|
||||||
@ -253,7 +255,7 @@ export const authOptions: NextAuthOptions = {
|
|||||||
first_name: token.first_name ?? '',
|
first_name: token.first_name ?? '',
|
||||||
last_name: token.last_name ?? '',
|
last_name: token.last_name ?? '',
|
||||||
role: userRoles,
|
role: userRoles,
|
||||||
nextcloudInitialized: false,
|
// Don't include nextcloudInitialized or other non-essential fields
|
||||||
};
|
};
|
||||||
|
|
||||||
// Only store access token, not the entire token
|
// Only store access token, not the entire token
|
||||||
@ -267,8 +269,15 @@ export const authOptions: NextAuthOptions = {
|
|||||||
...session,
|
...session,
|
||||||
error: "SessionError",
|
error: "SessionError",
|
||||||
user: {
|
user: {
|
||||||
...session.user,
|
id: token.sub ?? '',
|
||||||
id: token.sub ?? ''
|
role: ['user'],
|
||||||
|
username: '',
|
||||||
|
first_name: '',
|
||||||
|
last_name: '',
|
||||||
|
name: null,
|
||||||
|
email: null,
|
||||||
|
image: null,
|
||||||
|
nextcloudInitialized: false,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -278,7 +287,7 @@ export const authOptions: NextAuthOptions = {
|
|||||||
signIn: '/signin',
|
signIn: '/signin',
|
||||||
error: '/signin',
|
error: '/signin',
|
||||||
},
|
},
|
||||||
debug: process.env.NODE_ENV === 'development',
|
debug: false, // Disable debug to reduce cookie size from logging
|
||||||
};
|
};
|
||||||
|
|
||||||
const handler = NextAuth(authOptions);
|
const handler = NextAuth(authOptions);
|
||||||
|
|||||||
@ -18,18 +18,32 @@ export default async function RootLayout({
|
|||||||
}: {
|
}: {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
const session = await getServerSession(authOptions);
|
// Try to get the session, but handle potential errors gracefully
|
||||||
|
let session = null;
|
||||||
|
let sessionError = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
session = await getServerSession(authOptions);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error getting server session:", error);
|
||||||
|
sessionError = true;
|
||||||
|
}
|
||||||
|
|
||||||
const headersList = await headers();
|
const headersList = await headers();
|
||||||
const pathname = headersList.get("x-pathname") || "";
|
const pathname = headersList.get("x-pathname") || "";
|
||||||
const isSignInPage = pathname === "/signin";
|
const isSignInPage = pathname === "/signin";
|
||||||
|
|
||||||
|
// If we're on the signin page and there was a session error,
|
||||||
|
// don't pass the session to avoid refresh attempts
|
||||||
|
const safeSession = isSignInPage && sessionError ? null : session;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<html lang="fr">
|
<html lang="fr">
|
||||||
<body className={inter.className}>
|
<body className={inter.className}>
|
||||||
<Providers>
|
<Providers session={safeSession}>
|
||||||
<LayoutWrapper
|
<LayoutWrapper
|
||||||
isSignInPage={isSignInPage}
|
isSignInPage={isSignInPage}
|
||||||
isAuthenticated={!!session}
|
isAuthenticated={!!session && !sessionError}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</LayoutWrapper>
|
</LayoutWrapper>
|
||||||
|
|||||||
@ -2,30 +2,37 @@
|
|||||||
|
|
||||||
import { useSession, signOut } from "next-auth/react";
|
import { useSession, signOut } from "next-auth/react";
|
||||||
import { usePathname, useRouter } from "next/navigation";
|
import { usePathname, useRouter } from "next/navigation";
|
||||||
import { useEffect } from "react";
|
import { useEffect, useCallback } from "react";
|
||||||
|
|
||||||
export function AuthCheck({ children }: { children: React.ReactNode }) {
|
export function AuthCheck({ children }: { children: React.ReactNode }) {
|
||||||
const { data: session, status } = useSession();
|
const { data: session, status } = useSession();
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
// Create a memoized function to handle sign out to prevent excessive rerenders
|
||||||
|
const handleSessionError = useCallback((error: string) => {
|
||||||
|
console.log(`Session error detected: ${error}, signing out`);
|
||||||
|
|
||||||
|
// Force a clean sign out and redirect to login
|
||||||
|
signOut({
|
||||||
|
callbackUrl: `/signin?error=${encodeURIComponent(error)}`,
|
||||||
|
redirect: true
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Handle authentication status changes
|
// Handle expired sessions immediately
|
||||||
|
if (session?.error) {
|
||||||
|
handleSessionError(session.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle unauthenticated status (after checking for errors)
|
||||||
if (status === "unauthenticated" && !pathname.includes("/signin")) {
|
if (status === "unauthenticated" && !pathname.includes("/signin")) {
|
||||||
console.log("User is not authenticated, redirecting to signin page");
|
console.log("User is not authenticated, redirecting to signin page");
|
||||||
router.push("/signin");
|
router.push("/signin");
|
||||||
}
|
}
|
||||||
|
}, [status, session, router, pathname, handleSessionError]);
|
||||||
// Handle session errors (like refresh token failures)
|
|
||||||
if (session?.error) {
|
|
||||||
console.log(`Session error detected: ${session.error}, signing out`);
|
|
||||||
// Force a clean sign out
|
|
||||||
signOut({
|
|
||||||
callbackUrl: `/signin?error=${encodeURIComponent(session.error)}`,
|
|
||||||
redirect: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [status, session, router, pathname]);
|
|
||||||
|
|
||||||
// Show loading state
|
// Show loading state
|
||||||
if (status === "loading") {
|
if (status === "loading") {
|
||||||
@ -39,13 +46,13 @@ export function AuthCheck({ children }: { children: React.ReactNode }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If not authenticated and not on signin page, don't render children
|
// Do not render with session errors
|
||||||
if (status === "unauthenticated" && !pathname.includes("/signin")) {
|
if (session?.error) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Session has error, don't render children
|
// Do not render if not authenticated and not on signin page
|
||||||
if (session?.error) {
|
if (status === "unauthenticated" && !pathname.includes("/signin")) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -119,7 +119,10 @@ export function clearAuthCookies() {
|
|||||||
'KEYCLOAK_REMEMBER_ME',
|
'KEYCLOAK_REMEMBER_ME',
|
||||||
'KC_RESTART',
|
'KC_RESTART',
|
||||||
'KEYCLOAK_SESSION_LEGACY',
|
'KEYCLOAK_SESSION_LEGACY',
|
||||||
'KEYCLOAK_IDENTITY_LEGACY'
|
'KEYCLOAK_IDENTITY_LEGACY',
|
||||||
|
'AUTH_SESSION_ID',
|
||||||
|
'AUTH_SESSION_ID_LEGACY',
|
||||||
|
'JSESSIONID'
|
||||||
];
|
];
|
||||||
|
|
||||||
console.log(`Processing ${cookies.length} cookies`);
|
console.log(`Processing ${cookies.length} cookies`);
|
||||||
@ -127,18 +130,7 @@ export function clearAuthCookies() {
|
|||||||
// Get all cookie names to detect chunks (like next-auth.session-token.0)
|
// Get all cookie names to detect chunks (like next-auth.session-token.0)
|
||||||
const allCookieNames = cookies.map(cookie => cookie.split('=')[0].trim());
|
const allCookieNames = cookies.map(cookie => cookie.split('=')[0].trim());
|
||||||
|
|
||||||
// Find any chunked cookies
|
// First attempt: Forcefully delete all auth and session cookies by name
|
||||||
const chunkedCookies = allCookieNames.filter(name => {
|
|
||||||
return /\.\d+$/.test(name) && authCookiePrefixes.some(prefix => name.startsWith(prefix));
|
|
||||||
});
|
|
||||||
|
|
||||||
if (chunkedCookies.length > 0) {
|
|
||||||
console.log(`Found ${chunkedCookies.length} chunked cookies:`, chunkedCookies);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add detected chunked cookies to our specific cookies list
|
|
||||||
specificCookies.push(...chunkedCookies);
|
|
||||||
|
|
||||||
for (const cookie of cookies) {
|
for (const cookie of cookies) {
|
||||||
const [name] = cookie.split('=');
|
const [name] = cookie.split('=');
|
||||||
const trimmedName = name.trim();
|
const trimmedName = name.trim();
|
||||||
@ -156,16 +148,17 @@ export function clearAuthCookies() {
|
|||||||
trimmedName.toLowerCase().includes('login') ||
|
trimmedName.toLowerCase().includes('login') ||
|
||||||
trimmedName.toLowerCase().includes('id');
|
trimmedName.toLowerCase().includes('id');
|
||||||
|
|
||||||
if (isAuthCookie || containsAuthTerm) {
|
if (isAuthCookie || containsAuthTerm || /\.\d+$/.test(trimmedName)) {
|
||||||
console.log(`Clearing cookie: ${trimmedName}`);
|
console.log(`Clearing cookie: ${trimmedName}`);
|
||||||
|
|
||||||
// Try different combinations to ensure the cookie is cleared
|
// Try different combinations to ensure the cookie is cleared
|
||||||
const paths = ['/', '/auth', '/realms', '/admin', '/api'];
|
const paths = ['/', '/auth', '/realms', '/admin', '/api', '/signin', '/login', '/account'];
|
||||||
const domains = [
|
const domains = [
|
||||||
window.location.hostname, // Exact domain
|
window.location.hostname, // Exact domain
|
||||||
`.${window.location.hostname}`, // Domain with leading dot
|
`.${window.location.hostname}`, // Domain with leading dot
|
||||||
window.location.hostname.split('.').slice(-2).join('.'), // Root domain
|
window.location.hostname.split('.').slice(-2).join('.'), // Root domain
|
||||||
`.${window.location.hostname.split('.').slice(-2).join('.')}` // Root domain with leading dot
|
`.${window.location.hostname.split('.').slice(-2).join('.')}`, // Root domain with leading dot
|
||||||
|
"" // No domain
|
||||||
];
|
];
|
||||||
|
|
||||||
// Try each combination of path and domain
|
// Try each combination of path and domain
|
||||||
@ -175,19 +168,43 @@ export function clearAuthCookies() {
|
|||||||
|
|
||||||
// Try with SameSite and Secure attributes
|
// Try with SameSite and Secure attributes
|
||||||
document.cookie = `${trimmedName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=${path}; SameSite=None; Secure;`;
|
document.cookie = `${trimmedName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=${path}; SameSite=None; Secure;`;
|
||||||
|
document.cookie = `${trimmedName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=${path}; SameSite=Lax;`;
|
||||||
|
document.cookie = `${trimmedName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=${path}; SameSite=Strict;`;
|
||||||
|
|
||||||
// Try with different domains
|
// Try with different domains
|
||||||
for (const domain of domains) {
|
for (const domain of domains) {
|
||||||
|
if (domain) {
|
||||||
document.cookie = `${trimmedName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=${path}; domain=${domain};`;
|
document.cookie = `${trimmedName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=${path}; domain=${domain};`;
|
||||||
document.cookie = `${trimmedName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=${path}; domain=${domain}; SameSite=None; Secure;`;
|
document.cookie = `${trimmedName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=${path}; domain=${domain}; SameSite=None; Secure;`;
|
||||||
|
document.cookie = `${trimmedName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=${path}; domain=${domain}; SameSite=Lax;`;
|
||||||
|
document.cookie = `${trimmedName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=${path}; domain=${domain}; SameSite=Strict;`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second attempt: Clear by prefix pattern (helps with dynamically named cookies)
|
||||||
|
for (const prefix of authCookiePrefixes) {
|
||||||
|
// Set of paths and domains to try
|
||||||
|
const paths = ['/', '/auth', '/realms', '/admin', '/api', '/signin', '/login', '/account'];
|
||||||
|
const domains = [
|
||||||
|
window.location.hostname,
|
||||||
|
`.${window.location.hostname}`,
|
||||||
|
window.location.hostname.split('.').slice(-2).join('.'),
|
||||||
|
`.${window.location.hostname.split('.').slice(-2).join('.')}`
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const path of paths) {
|
||||||
|
for (const domain of domains) {
|
||||||
|
document.cookie = `${prefix}*=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=${path}; domain=${domain}; SameSite=None; Secure;`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Clear localStorage items that might be related to authentication
|
// Clear localStorage items that might be related to authentication
|
||||||
try {
|
try {
|
||||||
const authLocalStoragePrefixes = ['token', 'auth', 'session', 'keycloak', 'kc', 'user', 'oidc', 'login'];
|
const authLocalStoragePrefixes = ['token', 'auth', 'session', 'keycloak', 'kc', 'user', 'oidc', 'login', 'next-auth'];
|
||||||
|
|
||||||
console.log(`Checking localStorage (${localStorage.length} items)`);
|
console.log(`Checking localStorage (${localStorage.length} items)`);
|
||||||
for (let i = 0; i < localStorage.length; i++) {
|
for (let i = 0; i < localStorage.length; i++) {
|
||||||
@ -232,4 +249,20 @@ export function clearAuthCookies() {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Error clearing IndexedDB:', e);
|
console.error('Error clearing IndexedDB:', e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Third attempt: check again for any remaining auth cookies
|
||||||
|
const remainingCookies = document.cookie.split(';');
|
||||||
|
for (const cookie of remainingCookies) {
|
||||||
|
const [name] = cookie.split('=');
|
||||||
|
const trimmedName = name.trim();
|
||||||
|
|
||||||
|
if (trimmedName.includes('auth') ||
|
||||||
|
trimmedName.includes('session') ||
|
||||||
|
trimmedName.includes('token') ||
|
||||||
|
trimmedName.includes('.')) {
|
||||||
|
console.log(`Still trying to clear cookie: ${trimmedName}`);
|
||||||
|
document.cookie = `${trimmedName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
|
||||||
|
document.cookie = `${trimmedName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; domain=${window.location.hostname};`;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -17,24 +17,62 @@ export default async function middleware(req: NextRequest) {
|
|||||||
const url = req.nextUrl;
|
const url = req.nextUrl;
|
||||||
const response = NextResponse.next();
|
const response = NextResponse.next();
|
||||||
|
|
||||||
// Maximum size to prevent cookie chunking
|
// Maximum size to prevent cookie chunking - make it even more conservative
|
||||||
const MAX_COOKIE_SIZE = 3500; // conservative limit in bytes
|
const MAX_COOKIE_SIZE = 3000; // even more conservative limit in bytes
|
||||||
|
|
||||||
// Function to set all required nextAuth environment variables
|
// Function to set all required nextAuth environment variables
|
||||||
const setNextAuthEnvVars = () => {
|
const setNextAuthEnvVars = () => {
|
||||||
// Disable callbacks that could increase cookie size
|
// Set strict cookie size limits
|
||||||
process.env.NEXTAUTH_DISABLE_CALLBACK = 'true';
|
|
||||||
process.env.NEXTAUTH_DISABLE_JWT_CALLBACK = 'true';
|
|
||||||
process.env.NEXTAUTH_COOKIE_SIZE_LIMIT = String(MAX_COOKIE_SIZE);
|
process.env.NEXTAUTH_COOKIE_SIZE_LIMIT = String(MAX_COOKIE_SIZE);
|
||||||
process.env.NEXTAUTH_COOKIES_CHUNKING = 'true';
|
|
||||||
|
// Force cookie compression to reduce size
|
||||||
|
process.env.NEXTAUTH_COOKIES_CHUNKING = 'false'; // Disable chunking and force smaller cookies
|
||||||
process.env.NEXTAUTH_COOKIES_CHUNKING_SIZE = String(MAX_COOKIE_SIZE);
|
process.env.NEXTAUTH_COOKIES_CHUNKING_SIZE = String(MAX_COOKIE_SIZE);
|
||||||
|
|
||||||
|
// Set secure cookie settings
|
||||||
process.env.NEXTAUTH_COOKIES_SECURE = 'true';
|
process.env.NEXTAUTH_COOKIES_SECURE = 'true';
|
||||||
process.env.NEXTAUTH_COOKIES_SAMESITE = 'none';
|
process.env.NEXTAUTH_COOKIES_SAMESITE = 'none';
|
||||||
|
|
||||||
|
// Disable unnecessary callbacks that might increase cookie size
|
||||||
|
process.env.NEXTAUTH_DISABLE_CALLBACK = 'true';
|
||||||
|
process.env.NEXTAUTH_DISABLE_JWT_CALLBACK = 'true';
|
||||||
|
process.env.NEXTAUTH_JWT_STORE_RAW_TOKEN = 'false';
|
||||||
|
|
||||||
|
// Strongly enforce JWT max age
|
||||||
|
process.env.NEXTAUTH_JWT_MAX_AGE = String(12 * 60 * 60); // 12 hours in seconds
|
||||||
};
|
};
|
||||||
|
|
||||||
// Set environment variables for all routes
|
// Set environment variables for all routes
|
||||||
setNextAuthEnvVars();
|
setNextAuthEnvVars();
|
||||||
|
|
||||||
|
// Detect refresh token errors in cookies and clean them up
|
||||||
|
const checkForErrorsAndCleanup = () => {
|
||||||
|
const cookies = req.cookies;
|
||||||
|
const cookieNames = Object.keys(cookies.getAll());
|
||||||
|
|
||||||
|
// Check for error param in URL that would indicate token refresh errors
|
||||||
|
if (url.pathname.includes('/signin') && url.searchParams.has('error')) {
|
||||||
|
// Clean up all auth cookies to ensure a fresh start
|
||||||
|
const allAuthCookies = cookieNames.filter(name =>
|
||||||
|
name.includes('auth') ||
|
||||||
|
name.includes('keycloak') ||
|
||||||
|
name.includes('session') ||
|
||||||
|
name.includes('KEYCLOAK') ||
|
||||||
|
name.includes('KC_')
|
||||||
|
);
|
||||||
|
|
||||||
|
allAuthCookies.forEach(name => {
|
||||||
|
response.cookies.delete(name);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Special header to indicate a serious error that requires full cleanup
|
||||||
|
response.headers.set('X-Auth-Error-Recovery', 'true');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check for and clean up error cookies
|
||||||
|
checkForErrorsAndCleanup();
|
||||||
|
|
||||||
// Special handling for loggedout page to clean up cookies
|
// Special handling for loggedout page to clean up cookies
|
||||||
if (url.pathname === '/loggedout') {
|
if (url.pathname === '/loggedout') {
|
||||||
// Check if we're preserving SSO or doing a full logout
|
// Check if we're preserving SSO or doing a full logout
|
||||||
|
|||||||
17
types/next-auth.d.ts
vendored
17
types/next-auth.d.ts
vendored
@ -1,5 +1,13 @@
|
|||||||
import NextAuth, { DefaultSession, DefaultUser } from "next-auth";
|
import NextAuth, { DefaultSession, DefaultUser } from "next-auth";
|
||||||
|
|
||||||
|
// Define possible error types for better type checking
|
||||||
|
type AuthErrorType =
|
||||||
|
| "RefreshTokenError"
|
||||||
|
| "SessionError"
|
||||||
|
| "TokenError"
|
||||||
|
| "ConfigError"
|
||||||
|
| string;
|
||||||
|
|
||||||
declare module "next-auth" {
|
declare module "next-auth" {
|
||||||
interface Session {
|
interface Session {
|
||||||
user: {
|
user: {
|
||||||
@ -14,7 +22,8 @@ declare module "next-auth" {
|
|||||||
refreshToken?: string;
|
refreshToken?: string;
|
||||||
rocketChatToken?: string | null;
|
rocketChatToken?: string | null;
|
||||||
rocketChatUserId?: string | null;
|
rocketChatUserId?: string | null;
|
||||||
error?: string;
|
error?: AuthErrorType;
|
||||||
|
errorDescription?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface JWT {
|
interface JWT {
|
||||||
@ -27,7 +36,8 @@ declare module "next-auth" {
|
|||||||
role?: string[];
|
role?: string[];
|
||||||
rocketChatToken?: string | null;
|
rocketChatToken?: string | null;
|
||||||
rocketChatUserId?: string | null;
|
rocketChatUserId?: string | null;
|
||||||
error?: string;
|
error?: AuthErrorType;
|
||||||
|
errorDescription?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface User extends DefaultUser {
|
interface User extends DefaultUser {
|
||||||
@ -62,6 +72,7 @@ declare module "next-auth/jwt" {
|
|||||||
role?: string[];
|
role?: string[];
|
||||||
rocketChatToken?: string;
|
rocketChatToken?: string;
|
||||||
rocketChatUserId?: string;
|
rocketChatUserId?: string;
|
||||||
error?: string;
|
error?: AuthErrorType;
|
||||||
|
errorDescription?: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user