Files
buildfor_life_repair/src/lib/server/auth/index.ts
T
grabowski 6252041631
Deploy to LXC / deploy (push) Successful in 19s
Fix NetBird login: set Secure cookie flag from actual origin, not forwarded proto
Caddy sets X-Forwarded-Proto: https on all routes, making SvelteKit
think the request is HTTPS. The session cookie got the Secure flag,
but the browser on http://100.81.174.129 won't send Secure cookies
over plain HTTP. Now checks the actual Origin header to determine
if the connection is truly HTTPS.

Tor works because .onion is treated as a secure context by Tor Browser.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 14:38:20 +07:00

114 lines
3.1 KiB
TypeScript

import { sha256 } from '@oslojs/crypto/sha2';
import { encodeBase32LowerCaseNoPadding, encodeHexLowerCase } from '@oslojs/encoding';
import { db } from '$lib/server/db/index.js';
import { sessions, users } from '$lib/server/db/schema.js';
import { eq } from 'drizzle-orm';
import type { RequestEvent } from '@sveltejs/kit';
const SESSION_DURATION_MS = 30 * 24 * 60 * 60 * 1000; // 30 days
const SESSION_REFRESH_MS = 15 * 24 * 60 * 60 * 1000; // 15 days
export function generateSessionToken(): string {
const bytes = new Uint8Array(20);
crypto.getRandomValues(bytes);
return encodeBase32LowerCaseNoPadding(bytes);
}
export function generateUserId(): string {
const bytes = new Uint8Array(15);
crypto.getRandomValues(bytes);
return encodeBase32LowerCaseNoPadding(bytes);
}
function hashToken(token: string): string {
const encoded = new TextEncoder().encode(token);
return encodeHexLowerCase(sha256(encoded));
}
export async function createSession(token: string, userId: string) {
const id = hashToken(token);
const expiresAt = new Date(Date.now() + SESSION_DURATION_MS);
await db.insert(sessions).values({ id, userId, expiresAt });
return { id, userId, expiresAt, fresh: false };
}
export async function validateSession(token: string) {
const id = hashToken(token);
const [result] = await db
.select({
sessionId: sessions.id,
userId: sessions.userId,
expiresAt: sessions.expiresAt,
userEmail: users.email,
userDisplayName: users.displayName
})
.from(sessions)
.innerJoin(users, eq(sessions.userId, users.id))
.where(eq(sessions.id, id));
if (!result) return { session: null, user: null };
if (result.expiresAt < new Date()) {
await db.delete(sessions).where(eq(sessions.id, id));
return { session: null, user: null };
}
const session = {
id: result.sessionId,
userId: result.userId,
expiresAt: result.expiresAt,
fresh: false
};
// Refresh if older than 15 days
if (Date.now() - (result.expiresAt.getTime() - SESSION_DURATION_MS) > SESSION_REFRESH_MS) {
session.expiresAt = new Date(Date.now() + SESSION_DURATION_MS);
session.fresh = true;
await db
.update(sessions)
.set({ expiresAt: session.expiresAt })
.where(eq(sessions.id, id));
}
const user = {
id: result.userId,
email: result.userEmail,
displayName: result.userDisplayName
};
return { session, user };
}
export async function invalidateSession(token: string) {
const id = hashToken(token);
await db.delete(sessions).where(eq(sessions.id, id));
}
export function setSessionCookie(event: RequestEvent, token: string, expiresAt: Date) {
// Use the actual request origin, not the forwarded protocol
// This allows Secure cookies over HTTPS but plain cookies over HTTP (NetBird, LAN)
const actualOrigin = event.request.headers.get('origin') ?? event.url.origin;
const isSecure = actualOrigin.startsWith('https:');
event.cookies.set('session', token, {
httpOnly: true,
sameSite: 'lax',
secure: isSecure,
path: '/',
expires: expiresAt
});
}
export function deleteSessionCookie(event: RequestEvent) {
event.cookies.set('session', '', {
httpOnly: true,
sameSite: 'lax',
secure: event.url.protocol === 'https:',
path: '/',
maxAge: 0
});
}