to send if no longer friends, update overview information on invalidation (form response recieved, friends update)
206 lines
5.9 KiB
TypeScript
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)
|
|
);
|
|
}
|