fix(auth): detect HTTPS via X-Forwarded-Proto for SameSite=None cookies, fix AuthGuard null overwrite
- Cookie SameSite now dynamic: None;Secure when behind HTTPS proxy, Lax otherwise - AuthGuard useEffect no longer overwrites Zustand store with null from background refetch - AuthInitializer: add staleTime 30s Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -28,14 +28,26 @@ function hashToken(token: string) {
|
||||
return createHash('sha256').update(`${env.sessionSecret}:${token}`).digest('hex');
|
||||
}
|
||||
|
||||
export function createSessionCookie(token: string) {
|
||||
function isSecureRequest(headers?: { get(name: string): string | null }) {
|
||||
if (env.secureCookies) return true;
|
||||
// Detect HTTPS behind proxy (X-Forwarded-Proto)
|
||||
const proto = headers?.get('x-forwarded-proto');
|
||||
if (proto === 'https') return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
function buildSameSite(headers?: { get(name: string): string | null }) {
|
||||
return isSecureRequest(headers) ? 'SameSite=None; Secure' : 'SameSite=Lax';
|
||||
}
|
||||
|
||||
export function createSessionCookie(token: string, headers?: { get(name: string): string | null }) {
|
||||
const maxAge = 60 * 60 * 24 * 30;
|
||||
const sameSite = env.secureCookies ? 'SameSite=None; Secure' : 'SameSite=Lax';
|
||||
const sameSite = buildSameSite(headers);
|
||||
return `${sessionCookieName}=${token}; HttpOnly; Path=/; ${sameSite}; Max-Age=${maxAge}`;
|
||||
}
|
||||
|
||||
export function clearSessionCookie() {
|
||||
const sameSite = env.secureCookies ? 'SameSite=None; Secure' : 'SameSite=Lax';
|
||||
export function clearSessionCookie(headers?: { get(name: string): string | null }) {
|
||||
const sameSite = buildSameSite(headers);
|
||||
return `${sessionCookieName}=; HttpOnly; Path=/; ${sameSite}; Max-Age=0`;
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ export const authRoutes = new Elysia({ prefix: '/api/v1/auth' })
|
||||
features: getAuthFeatures(),
|
||||
};
|
||||
})
|
||||
.post('/register', async ({ body, set }) => {
|
||||
.post('/register', async ({ body, set, request }) => {
|
||||
const req = body as Partial<RegisterRequest> | undefined;
|
||||
const email = normalizeEmail(req?.email);
|
||||
const name = normalizeName(req?.name);
|
||||
@@ -62,7 +62,7 @@ export const authRoutes = new Elysia({ prefix: '/api/v1/auth' })
|
||||
|
||||
const user = inserted[0];
|
||||
const token = await createSession(user.id);
|
||||
set.headers['Set-Cookie'] = createSessionCookie(token);
|
||||
set.headers['Set-Cookie'] = createSessionCookie(token, request.headers);
|
||||
|
||||
authCounter.labels('register', 'true').inc();
|
||||
|
||||
@@ -79,7 +79,7 @@ export const authRoutes = new Elysia({ prefix: '/api/v1/auth' })
|
||||
return serviceUnavailable('Database unavailable');
|
||||
}
|
||||
})
|
||||
.post('/login', async ({ body, set }) => {
|
||||
.post('/login', async ({ body, set, request }) => {
|
||||
const req = body as Partial<AuthRequest> | undefined;
|
||||
const email = normalizeEmail(req?.email);
|
||||
|
||||
@@ -98,7 +98,7 @@ export const authRoutes = new Elysia({ prefix: '/api/v1/auth' })
|
||||
}
|
||||
|
||||
const token = await createSession(user.id);
|
||||
set.headers['Set-Cookie'] = createSessionCookie(token);
|
||||
set.headers['Set-Cookie'] = createSessionCookie(token, request.headers);
|
||||
|
||||
authCounter.labels('login', 'true').inc();
|
||||
|
||||
@@ -116,8 +116,9 @@ export const authRoutes = new Elysia({ prefix: '/api/v1/auth' })
|
||||
}
|
||||
})
|
||||
.post('/logout', async ({ request, set }) => {
|
||||
await deleteSession(readSessionToken(request.headers.get('cookie')));
|
||||
set.headers['Set-Cookie'] = clearSessionCookie();
|
||||
const cookieHeader = request.headers.get('cookie');
|
||||
await deleteSession(readSessionToken(cookieHeader));
|
||||
set.headers['Set-Cookie'] = clearSessionCookie(request.headers);
|
||||
return { ok: true };
|
||||
})
|
||||
.get('/google', ({ set }) => {
|
||||
|
||||
Reference in New Issue
Block a user