import type { RequestEvent } from '@sveltejs/kit'; import { eq, inArray, or } 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 { _findDmId } from '../../routes/api/messages/[[grp_srv_dm]]/[[channelId]]/[[channelId]]/+server'; 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 [row] = await db .select({ user: { id: table.user.id, username: table.user.username, friends: table.user.friends, servers: table.user.servers, groups: table.user.groups, statusOverwrite: table.user.statusOverwrite }, session: table.session }) .from(table.session) .innerJoin(table.user, eq(table.session.userId, table.user.id)) .where(eq(table.session.id, sessionId)); if (!row) { return { session: null, user: null }; } const { session, user } = row; 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)); } const friends = await Promise.all( ((user.friends as string[]).length ? await db .select({ id: table.user.id, username: table.user.username }) .from(table.user) .where(inArray(table.user.id, user.friends as string[])) : [] ).map(async (z) => { const dmid = await _findDmId(z.id, user.id); return { ...z, dmId: dmid }; }) ); let servers = (user.servers as string[]).length ? await db .select({ id: table.server.id, name: table.server.name, ownerId: table.server.owner, channels: table.server.channels }) .from(table.server) .where(inArray(table.server.id, user.servers as string[])) : []; servers = await Promise.all( servers.map(async (z) => { return { ...z, channels: ( await Promise.all( (z.channels as string[]).map(async (m) => { const channel = await db.select().from(table.channel).where(eq(table.channel.id, m)); if (!channel || channel.length == 0) return; return { name: channel[0].name, id: channel[0].id }; }) ) ).filter(Boolean) }; }) ); const groups = (user.groups as string[]).length ? await db .select({ id: table.group.id, name: table.group.name, ownerId: table.group.owner, members: table.group.members, changeTitle: table.group.changeTitle, addMembers: table.group.addMembers, removeMembers: table.group.removeMembers }) .from(table.group) .where(inArray(table.group.id, user.groups as string[])) : []; const friendRequests = await db .select({ id: table.friendRequest.id, fromUser: table.friendRequest.fromUser, fromUsername: table.friendRequest.fromUsername, toUsername: table.friendRequest.toUsername, toUser: table.friendRequest.toUser }) .from(table.friendRequest) .where(or(eq(table.friendRequest.fromUser, user.id), eq(table.friendRequest.toUser, user.id))); return { session, user: { ...user, servers, friends, groups: groups.map((z) => { return { ...z, members: (z.members as string[]).length, permissions: { changeTitle: !!z.changeTitle, addMembers: !!z.addMembers, removeMembers: !!z.removeMembers } }; }), friendRequests } }; } export type SessionValidationResult = Awaited>; 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: '/' }); } export function deleteSessionTokenCookie(event: RequestEvent) { event.cookies.delete(sessionCookieName, { path: '/' }); } export function validateUsername(username: unknown): username is string { return ( typeof username === 'string' && username.length >= 3 && username.length <= 31 && /^[a-z0-9_-]+$/.test(username) ); } export function validatePassword(password: unknown): password is string { return typeof password === 'string' && password.length >= 6 && password.length <= 255; } export function validateEmail(email: unknown): email is string { return ( typeof email === 'string' && email.length >= 5 && email.length <= 254 && /^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$/i.test(email) ); }