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.
127 lines
3.5 KiB
TypeScript
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);
|
|
}
|