chat.sad.ovh/src/lib/server/auth.ts
fucksophie 7af96ca084 implement channels fully, implement servers fully, make dms impossible
to send if no longer friends, update overview information on
invalidation (form response recieved, friends update)
2026-01-11 13:47:46 +02:00

206 lines
5.9 KiB
TypeScript

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<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: '/'
});
}
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)
);
}