This repository has been archived on 2026-02-06. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
serengo/src/lib/server/auth.ts
Zias van Nes 96a173b73b feat:use local proxy for media
use local proxy for media so that media doesnt need to be requested from
r2 everytime but can be cached locally. this also fixes some csp issues
ive been having.
2025-11-17 10:48:40 +01:00

127 lines
3.5 KiB
TypeScript

import type { RequestEvent } from '@sveltejs/kit';
import { eq } from 'drizzle-orm';
import { sha256 } from '@oslojs/crypto/sha2';
import { encodeBase64url, encodeHexLowerCase } from '@oslojs/encoding';
import { db } from '$lib/server/db';
import * as table from '$lib/server/db/schema';
import { getLocalR2Url } from '$lib/server/r2';
const DAY_IN_MS = 1000 * 60 * 60 * 24;
export const sessionCookieName = 'auth-session';
export function generateSessionToken() {
const bytes = crypto.getRandomValues(new Uint8Array(18));
const token = encodeBase64url(bytes);
return token;
}
export async function createSession(token: string, userId: string) {
const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token)));
const session: table.Session = {
id: sessionId,
userId,
expiresAt: new Date(Date.now() + DAY_IN_MS * 30)
};
await db.insert(table.session).values(session);
return session;
}
export async function validateSessionToken(token: string) {
const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token)));
const [result] = await db
.select({
// Adjust user table here to tweak returned data
user: {
id: table.user.id,
username: table.user.username,
profilePictureUrl: table.user.profilePictureUrl
},
session: table.session
})
.from(table.session)
.innerJoin(table.user, eq(table.session.userId, table.user.id))
.where(eq(table.session.id, sessionId));
if (!result) {
return { session: null, user: null };
}
const { session, user } = result;
const sessionExpired = Date.now() >= session.expiresAt.getTime();
if (sessionExpired) {
await db.delete(table.session).where(eq(table.session.id, session.id));
return { session: null, user: null };
}
const renewSession = Date.now() >= session.expiresAt.getTime() - DAY_IN_MS * 15;
if (renewSession) {
session.expiresAt = new Date(Date.now() + DAY_IN_MS * 30);
await db
.update(table.session)
.set({ expiresAt: session.expiresAt })
.where(eq(table.session.id, session.id));
}
// Generate local proxy URL for profile picture if it exists
let profilePictureUrl = user.profilePictureUrl;
if (profilePictureUrl && !profilePictureUrl.startsWith('http')) {
// It's a path, generate local proxy URL
profilePictureUrl = getLocalR2Url(profilePictureUrl);
}
return {
session,
user: {
...user,
profilePictureUrl
}
};
}
export type SessionValidationResult = Awaited<ReturnType<typeof validateSessionToken>>;
export async function invalidateSession(sessionId: string) {
await db.delete(table.session).where(eq(table.session.id, sessionId));
}
export function setSessionTokenCookie(event: RequestEvent, token: string, expiresAt: Date) {
event.cookies.set(sessionCookieName, token, {
expires: expiresAt,
path: '/',
httpOnly: true,
secure: false, // Allow HTTP in development
sameSite: 'lax'
});
}
export function deleteSessionTokenCookie(event: RequestEvent) {
event.cookies.delete(sessionCookieName, {
path: '/'
});
}
export async function getUserFromGoogleId(googleId: string) {
const [user] = await db.select().from(table.user).where(eq(table.user.googleId, googleId));
return user || null;
}
export async function createUser(googleId: string, name: string) {
const userId = generateUserId();
const user: table.User = {
id: userId,
username: name,
googleId,
passwordHash: null,
age: null,
profilePictureUrl: null
};
await db.insert(table.user).values(user);
return user;
}
function generateUserId(): string {
const bytes = crypto.getRandomValues(new Uint8Array(15));
return encodeBase64url(bytes);
}