append timestamp to cookie name to prevent redirect loops

This commit is contained in:
miloschwartz 2025-03-21 21:38:06 -04:00
parent f37be774a6
commit f2a14e6a36
No known key found for this signature in database
2 changed files with 58 additions and 15 deletions

View file

@ -170,16 +170,17 @@ export function serializeResourceSessionCookie(
isHttp: boolean = false, isHttp: boolean = false,
expiresAt?: Date expiresAt?: Date
): string { ): string {
const now = new Date().getTime();
if (!isHttp) { if (!isHttp) {
if (expiresAt === undefined) { if (expiresAt === undefined) {
return `${cookieName}_s=${token}; HttpOnly; SameSite=Lax; Path=/; Secure; Domain=${"." + domain}`; return `${cookieName}_s.${now}=${token}; HttpOnly; SameSite=Lax; Path=/; Secure; Domain=${"." + domain}`;
} }
return `${cookieName}_s=${token}; HttpOnly; SameSite=Lax; Expires=${expiresAt.toUTCString()}; Path=/; Secure; Domain=${"." + domain}`; return `${cookieName}_s.${now}=${token}; HttpOnly; SameSite=Lax; Expires=${expiresAt.toUTCString()}; Path=/; Secure; Domain=${"." + domain}`;
} else { } else {
if (expiresAt === undefined) { if (expiresAt === undefined) {
return `${cookieName}=${token}; HttpOnly; SameSite=Lax; Path=/; Domain=${"." + domain}`; return `${cookieName}.${now}=${token}; HttpOnly; SameSite=Lax; Path=/; Domain=${"." + domain}`;
} }
return `${cookieName}=${token}; HttpOnly; SameSite=Lax; Expires=${expiresAt.toUTCString()}; Path=/; Domain=${"." + domain}`; return `${cookieName}.${now}=${token}; HttpOnly; SameSite=Lax; Expires=${expiresAt.toUTCString()}; Path=/; Domain=${"." + domain}`;
} }
} }

View file

@ -229,12 +229,10 @@ export async function verifyResourceSession(
return notAllowed(res); return notAllowed(res);
} }
const resourceSessionToken = const resourceSessionToken = extractResourceSessionToken(
sessions[ sessions,
`${config.getRawConfig().server.session_cookie_name}${ resource.ssl
resource.ssl ? "_s" : "" );
}`
];
if (resourceSessionToken) { if (resourceSessionToken) {
const sessionCacheKey = `session:${resourceSessionToken}`; const sessionCacheKey = `session:${resourceSessionToken}`;
@ -354,6 +352,50 @@ export async function verifyResourceSession(
} }
} }
function extractResourceSessionToken(
sessions: Record<string, string>,
ssl: boolean
) {
const prefix = `${config.getRawConfig().server.session_cookie_name}${
ssl ? "_s" : ""
}`;
const all: { cookieName: string; token: string; priority: number }[] =
[];
for (const [key, value] of Object.entries(sessions)) {
const parts = key.split(".");
const timestamp = parts[parts.length - 1];
// check if string is only numbers
if (!/^\d+$/.test(timestamp)) {
continue;
}
// cookie name is the key without the timestamp
const cookieName = key.slice(0, -timestamp.length - 1);
if (cookieName === prefix) {
all.push({
cookieName,
token: value,
priority: parseInt(timestamp)
});
}
}
// sort by priority in desc order
all.sort((a, b) => b.priority - a.priority);
const latest = all[0];
if (!latest) {
return;
}
return latest.token;
}
function notAllowed(res: Response, redirectUrl?: string) { function notAllowed(res: Response, redirectUrl?: string) {
const data = { const data = {
data: { valid: false, redirectUrl }, data: { valid: false, redirectUrl },
@ -612,21 +654,21 @@ export function isPathAllowed(pattern: string, path: string): boolean {
logger.debug( logger.debug(
`${indent}Found in-segment wildcard in "${currentPatternPart}"` `${indent}Found in-segment wildcard in "${currentPatternPart}"`
); );
// Convert the pattern segment to a regex pattern // Convert the pattern segment to a regex pattern
const regexPattern = currentPatternPart const regexPattern = currentPatternPart
.replace(/\*/g, ".*") // Replace * with .* for regex wildcard .replace(/\*/g, ".*") // Replace * with .* for regex wildcard
.replace(/\?/g, "."); // Replace ? with . for single character wildcard if needed .replace(/\?/g, "."); // Replace ? with . for single character wildcard if needed
const regex = new RegExp(`^${regexPattern}$`); const regex = new RegExp(`^${regexPattern}$`);
if (regex.test(currentPathPart)) { if (regex.test(currentPathPart)) {
logger.debug( logger.debug(
`${indent}Segment with wildcard matches: "${currentPatternPart}" matches "${currentPathPart}"` `${indent}Segment with wildcard matches: "${currentPatternPart}" matches "${currentPathPart}"`
); );
return matchSegments(patternIndex + 1, pathIndex + 1); return matchSegments(patternIndex + 1, pathIndex + 1);
} }
logger.debug( logger.debug(
`${indent}Segment with wildcard mismatch: "${currentPatternPart}" doesn't match "${currentPathPart}"` `${indent}Segment with wildcard mismatch: "${currentPatternPart}" doesn't match "${currentPathPart}"`
); );
@ -651,4 +693,4 @@ export function isPathAllowed(pattern: string, path: string): boolean {
const result = matchSegments(0, 0); const result = matchSegments(0, 0);
logger.debug(`Final result: ${result}`); logger.debug(`Final result: ${result}`);
return result; return result;
} }