This commit is contained in:
Evert Prants 2024-06-04 21:28:38 +03:00
parent 9753804b28
commit 874b4804b9
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
17 changed files with 211 additions and 118 deletions

4
.dockerignore Normal file
View File

@ -0,0 +1,4 @@
uploads
node_modules
private
devdocker

29
Dockerfile Normal file
View File

@ -0,0 +1,29 @@
# Build the application with a specific environment
FROM node:20 AS builder
WORKDIR /usr/src/app
ARG envFile=.env
COPY . .
COPY ./${envFile} ./.env
RUN npm ci
RUN npm run build
# Create the executor image
FROM node:20
WORKDIR /app
COPY --from=builder --chown=node:node /usr/src/app/build ./build
COPY --from=builder --chown=node:node /usr/src/app/migrations ./migrations
COPY --from=builder --chown=node:node /usr/src/app/package* .
RUN npm ci --omit=dev
USER node
VOLUME [ "/app/private" ]
VOLUME [ "/app/uploads" ]
CMD [ "node", "/app/build" ]

View File

@ -1,11 +1,15 @@
import { AUTO_MIGRATE, SESSION_SECRET } from '$env/static/private';
import { db } from '$lib/server/drizzle';
import { DB } from '$lib/server/drizzle';
import { runSeeds } from '$lib/server/drizzle/seeds';
import { JWT } from '$lib/server/jwt';
import { migrate } from 'drizzle-orm/mysql2/migrator';
import { handleSession } from 'svelte-kit-cookie-session';
await DB.init();
await JWT.init();
if (AUTO_MIGRATE === 'true') {
await migrate(db, { migrationsFolder: './migrations' });
await migrate(DB.drizzle, { migrationsFolder: './migrations' });
}
await runSeeds();

View File

@ -3,12 +3,20 @@ import { drizzle } from 'drizzle-orm/mysql2';
import mysql from 'mysql2/promise';
import * as schema from './schema';
const connection = await mysql.createConnection({
export class DB {
static mysqlConnection: mysql.Connection;
static drizzle: ReturnType<typeof drizzle<typeof schema>>;
static async init() {
DB.mysqlConnection = await mysql.createConnection({
host: DATABASE_HOST,
user: DATABASE_PASS,
password: DATABASE_PASS,
database: DATABASE_DB
});
});
DB.drizzle = drizzle(DB.mysqlConnection, { schema, mode: 'default' });
}
}
export const db = drizzle(connection, { schema, mode: 'default' });
export * from './schema';

View File

@ -1,5 +1,5 @@
import { eq } from 'drizzle-orm';
import { db, privilege } from '..';
import { DB, privilege } from '..';
/**
* System privileges which must always exist in the database.
@ -18,11 +18,11 @@ const privileges = [
export default async function privilegesSeed() {
for (const priv of privileges) {
const [exists] = await db
const [exists] = await DB.drizzle
.select({ id: privilege.id })
.from(privilege)
.where(eq(privilege.name, priv));
if (exists) continue;
await db.insert(privilege).values({ name: priv });
await DB.drizzle.insert(privilege).values({ name: priv });
}
}

View File

@ -1,11 +1,15 @@
import { JWT_ALGORITHM, JWT_EXPIRATION, JWT_ISSUER } from '$env/static/private';
import { readFile } from 'fs/promises';
import { SignJWT, importPKCS8, importSPKI, jwtVerify } from 'jose';
const privateKeyFile = await readFile('private/jwt.private.pem', { encoding: 'utf-8' });
const publicKeyFile = await readFile('private/jwt.public.pem', { encoding: 'utf-8' });
const privateKey = await importPKCS8(privateKeyFile, JWT_ALGORITHM);
const publicKey = await importSPKI(publicKeyFile, JWT_ALGORITHM);
import {
SignJWT,
exportJWK,
importPKCS8,
importSPKI,
jwtVerify,
type JWK,
type KeyLike
} from 'jose';
import { v4 as uuidv4 } from 'uuid';
/**
* Generate JWTs using the following commands:
@ -13,8 +17,19 @@ const publicKey = await importSPKI(publicKeyFile, JWT_ALGORITHM);
* Public: openssl rsa -in jwt.private.pem -pubout -outform PEM -out jwt.public.pem
*/
export class JWT {
static privateKey = privateKey;
static publicKey = publicKey;
static privateKey: KeyLike;
static publicKey: KeyLike;
static jwks: JWK;
static jwksKid: string;
static async init() {
const privateKeyFile = await readFile('private/jwt.private.pem', { encoding: 'utf-8' });
const publicKeyFile = await readFile('private/jwt.public.pem', { encoding: 'utf-8' });
JWT.privateKey = await importPKCS8(privateKeyFile, JWT_ALGORITHM);
JWT.publicKey = await importSPKI(publicKeyFile, JWT_ALGORITHM);
JWT.jwks = await exportJWK(JWT.publicKey);
JWT.jwksKid = uuidv4({ random: Buffer.from(JWT.jwks.n as string).subarray(0, 16) });
}
static async issue(claims: Record<string, unknown>, subject: string, audience?: string) {
const sign = new SignJWT(claims)

View File

@ -1,7 +1,7 @@
import { PUBLIC_URL, PUBLIC_SITE_NAME } from '$env/static/public';
import { CryptoUtils } from '$lib/server/crypto-utils';
import {
db,
DB,
oauth2Client,
oauth2ClientAuthorization,
oauth2ClientManager,
@ -82,7 +82,7 @@ export class OAuth2Clients {
public static availableUrlTypes: OAuth2ClientURLType[] = Object.values(OAuth2ClientURLType);
static async fetchById(id: string | number) {
const [client] = await db
const [client] = await DB.drizzle
.select()
.from(oauth2Client)
.where(typeof id === 'string' ? eq(oauth2Client.client_id, id) : eq(oauth2Client.id, id))
@ -94,7 +94,7 @@ export class OAuth2Clients {
id: string,
type: OAuth2ClientURLType = OAuth2ClientURLType.REDIRECT_URI
) {
return await db
return await DB.drizzle
.select()
.from(oauth2ClientUrl)
.innerJoin(oauth2Client, eq(oauth2ClientUrl.clientId, oauth2Client.id))
@ -102,7 +102,7 @@ export class OAuth2Clients {
}
static async getClientUrls(client: OAuth2Client) {
return await db
return await DB.drizzle
.select()
.from(oauth2ClientUrl)
.where(and(eq(oauth2ClientUrl.clientId, client.id)));
@ -110,7 +110,7 @@ export class OAuth2Clients {
static async checkRedirectUri(client: OAuth2Client, url: string) {
return !!(
await db
await DB.drizzle
.select()
.from(oauth2ClientUrl)
.innerJoin(oauth2Client, eq(oauth2ClientUrl.clientId, oauth2Client.id))
@ -125,7 +125,7 @@ export class OAuth2Clients {
}
static async getAuthorizedUsers(client: OAuth2Client, userUuid?: string) {
const junkList = await db
const junkList = await DB.drizzle
.select()
.from(oauth2ClientAuthorization)
.innerJoin(user, eq(user.id, oauth2ClientAuthorization.userId))
@ -215,7 +215,7 @@ export class OAuth2Clients {
) {
const filterText = `%${filters?.filter?.toLowerCase()}%`;
const limit = filters?.limit || 20;
const allowedClients = db
const allowedClients = DB.drizzle
.select({ id: oauth2Client.id })
.from(oauth2Client)
.leftJoin(oauth2ClientManager, eq(oauth2ClientManager.clientId, oauth2Client.id))
@ -238,14 +238,14 @@ export class OAuth2Clients {
.offset(filters?.offset || 0)
.as('allowedClients');
const [{ rowCount }] = await db
const [{ rowCount }] = await DB.drizzle
.select({
rowCount: count(oauth2Client.id).mapWith(Number)
})
.from(allowedClients)
.innerJoin(oauth2Client, eq(allowedClients.id, oauth2Client.id));
const junkList = await db
const junkList = await DB.drizzle
.select({
o_auth2_client: oauth2Client,
o_auth2_client_url: oauth2ClientUrl,
@ -308,7 +308,7 @@ export class OAuth2Clients {
const uid = CryptoUtils.createUUID();
const secret = CryptoUtils.generateSecret();
const [retval] = await db.insert(oauth2Client).values({
const [retval] = await DB.drizzle.insert(oauth2Client).values({
title,
description,
client_id: uid,
@ -321,7 +321,7 @@ export class OAuth2Clients {
verified: 0
});
await db.insert(oauth2ClientUrl).values({
await DB.drizzle.insert(oauth2ClientUrl).values({
type: 'redirect_uri',
url: redirect,
clientId: retval.insertId
@ -334,18 +334,18 @@ export class OAuth2Clients {
if (client.pictureId) {
await Uploads.removeClientAvatar(client);
}
await db.delete(privilege).where(eq(privilege.clientId, client.id));
await db.delete(oauth2Client).where(eq(oauth2Client.id, client.id));
await DB.drizzle.delete(privilege).where(eq(privilege.clientId, client.id));
await DB.drizzle.delete(oauth2Client).where(eq(oauth2Client.id, client.id));
}
static async deleteUrl(client: OAuth2Client, urlId: number) {
await db
await DB.drizzle
.delete(oauth2ClientUrl)
.where(and(eq(oauth2ClientUrl.clientId, client.id), eq(oauth2ClientUrl.id, urlId)));
}
static async addUrl(client: OAuth2Client, type: OAuth2ClientURLType, url: string) {
await db.insert(oauth2ClientUrl).values({
await DB.drizzle.insert(oauth2ClientUrl).values({
type,
url,
clientId: client.id
@ -353,25 +353,25 @@ export class OAuth2Clients {
}
static async deletePrivilege(client: OAuth2Client, privilegeId: number) {
await db
await DB.drizzle
.delete(privilege)
.where(and(eq(privilege.clientId, client.id), eq(privilege.id, privilegeId)));
}
static async addPrivilege(client: OAuth2Client, name: string) {
const realName = `${client.client_id.split('-')[0]}:${name}`;
await db.insert(privilege).values({
await DB.drizzle.insert(privilege).values({
name: realName,
clientId: client.id
});
}
static async update(client: OAuth2Client, body: Partial<OAuth2Client>) {
await db.update(oauth2Client).set(body).where(eq(oauth2Client.id, client.id));
await DB.drizzle.update(oauth2Client).set(body).where(eq(oauth2Client.id, client.id));
}
static async getManagers(client: OAuth2Client) {
return await db
return await DB.drizzle
.select({ id: oauth2ClientManager.id, email: user.email })
.from(oauth2ClientManager)
.innerJoin(user, eq(user.id, oauth2ClientManager.userId))
@ -386,7 +386,7 @@ export class OAuth2Clients {
await Users.grantPrivilege(subject, 'self:oauth2');
}
await db.insert(oauth2ClientManager).values({
await DB.drizzle.insert(oauth2ClientManager).values({
clientId: client.id,
userId: subject.id,
issuerId: actor.id
@ -394,7 +394,7 @@ export class OAuth2Clients {
}
static async removeManager(client: OAuth2Client, managerId: number) {
await db
await DB.drizzle
.delete(oauth2ClientManager)
.where(
and(eq(oauth2ClientManager.clientId, client.id), eq(oauth2ClientManager.id, managerId))

View File

@ -1,5 +1,5 @@
import {
db,
DB,
oauth2Client,
oauth2Token,
type OAuth2Client,
@ -49,7 +49,7 @@ export class OAuth2Tokens {
nonce?: string,
pcke?: string
) {
const [retval] = await db.insert(oauth2Token).values({
const [retval] = await DB.drizzle.insert(oauth2Token).values({
token,
type,
scope,
@ -60,7 +60,7 @@ export class OAuth2Tokens {
pcke
});
const [newToken] = await db
const [newToken] = await DB.drizzle
.select()
.from(oauth2Token)
.where(eq(oauth2Token.id, retval.insertId));
@ -69,7 +69,7 @@ export class OAuth2Tokens {
}
static async fetchByToken(token: string, type: OAuth2TokenType) {
const [retval] = await db
const [retval] = await DB.drizzle
.select()
.from(oauth2Token)
.where(and(eq(oauth2Token.token, token), eq(oauth2Token.type, type)));
@ -77,7 +77,7 @@ export class OAuth2Tokens {
}
static async fetchByUserIdClientId(userId: number, clientId: string, type: OAuth2TokenType) {
const [retval] = await db
const [retval] = await DB.drizzle
.select()
.from(oauth2Token)
.innerJoin(oauth2Client, eq(oauth2Token.clientId, oauth2Client.id))
@ -92,7 +92,7 @@ export class OAuth2Tokens {
}
static async wipeClientTokens(client: OAuth2Client, user?: User) {
await db
await DB.drizzle
.delete(oauth2Token)
.where(
and(eq(oauth2Token.clientId, client.id), user ? eq(oauth2Token.userId, user.id) : undefined)
@ -100,15 +100,17 @@ export class OAuth2Tokens {
}
static async wipeUserTokens(user: User) {
await db.delete(oauth2Token).where(eq(oauth2Token.userId, user.id));
await DB.drizzle.delete(oauth2Token).where(eq(oauth2Token.userId, user.id));
}
static async wipeExpiredTokens() {
await db.execute(sql`DELETE FROM ${oauth2Token} WHERE ${oauth2Token.expires_at} < NOW()`);
await DB.drizzle.execute(
sql`DELETE FROM ${oauth2Token} WHERE ${oauth2Token.expires_at} < NOW()`
);
}
static async remove(token: OAuth2Token) {
await db.delete(oauth2Token).where(eq(oauth2Token.id, token.id));
await DB.drizzle.delete(oauth2Token).where(eq(oauth2Token.id, token.id));
}
}

View File

@ -1,5 +1,5 @@
import {
db,
DB,
oauth2Client,
oauth2ClientAuthorization,
oauth2ClientUrl,
@ -21,7 +21,7 @@ export class OAuth2Users {
static async consented(userId: number, clientId: string, scopes: string | string[]) {
const normalized = OAuth2Clients.splitScope(scopes);
return !!(
await db
await DB.drizzle
.select({
id: oauth2ClientAuthorization.id,
scope: oauth2ClientAuthorization.scope
@ -43,7 +43,7 @@ export class OAuth2Users {
static async saveConsent(subject: User, client: OAuth2Client, scopes: string | string[]) {
const normalized = OAuth2Clients.splitScope(scopes);
const [existing] = await db
const [existing] = await DB.drizzle
.select()
.from(oauth2ClientAuthorization)
.where(
@ -62,14 +62,14 @@ export class OAuth2Users {
}
});
await db
await DB.drizzle
.update(oauth2ClientAuthorization)
.set({ scope: OAuth2Clients.joinScope(splitScope), current: 1, expires_at: null })
.where(eq(oauth2ClientAuthorization.id, existing.id));
return;
}
await db.insert(oauth2ClientAuthorization).values({
await DB.drizzle.insert(oauth2ClientAuthorization).values({
userId: subject.id,
clientId: client.id,
scope: OAuth2Clients.joinScope(normalized)
@ -81,7 +81,7 @@ export class OAuth2Users {
if (!client) return false;
await OAuth2Tokens.wipeClientTokens(client, subject);
await db
await DB.drizzle
.update(oauth2ClientAuthorization)
.set({ current: 0, expires_at: new Date() })
.where(
@ -96,7 +96,7 @@ export class OAuth2Users {
}
static async listAuthorizations(subject: User) {
return db
return DB.drizzle
.select()
.from(oauth2Client)
.innerJoin(oauth2ClientAuthorization, eq(oauth2ClientAuthorization.clientId, oauth2Client.id))

View File

@ -1,6 +1,6 @@
import { eq } from 'drizzle-orm';
import {
db,
DB,
oauth2Client,
upload,
user,
@ -9,19 +9,41 @@ import {
type User
} from './drizzle';
import { Users } from './users';
import { readFile, unlink, writeFile } from 'fs/promises';
import { readFile, stat, unlink, writeFile } from 'fs/promises';
import { join } from 'path';
import * as mime from 'mime-types';
import { OAuth2Clients } from './oauth2';
const userFallbackImage = await readFile(join('static', 'avatar.png'));
const clientFallbackImage = await readFile(join('static', 'application.png'));
export class Uploads {
static userFallbackImage = userFallbackImage;
static clientFallbackImage = clientFallbackImage;
static userFallbackImage: Buffer;
static clientFallbackImage: Buffer;
static uploads = join('uploads');
static async determineStaticPath() {
try {
await stat('static');
return 'static';
} catch {
return join('build', 'client');
}
}
static async getUserFallback() {
if (!Uploads.userFallbackImage) {
const staticPath = await Uploads.determineStaticPath();
Uploads.userFallbackImage = await readFile(join(staticPath, 'avatar.png'));
}
return Uploads.userFallbackImage;
}
static async getClientFallback() {
if (!Uploads.clientFallbackImage) {
const staticPath = await Uploads.determineStaticPath();
Uploads.clientFallbackImage = await readFile(join(staticPath, 'application.png'));
}
return Uploads.clientFallbackImage;
}
static async removeUpload(subject: Upload) {
try {
unlink(join(Uploads.uploads, subject.file));
@ -29,7 +51,7 @@ export class Uploads {
// ignore unlink error
}
await db.delete(upload).where(eq(upload.id, subject.id));
await DB.drizzle.delete(upload).where(eq(upload.id, subject.id));
}
static async getAvatarByUuid(
@ -40,7 +62,7 @@ export class Uploads {
return undefined;
}
const [picture] = await db
const [picture] = await DB.drizzle
.select({ mimetype: upload.mimetype, file: upload.file })
.from(upload)
.where(eq(upload.id, user.pictureId));
@ -55,7 +77,7 @@ export class Uploads {
return undefined;
}
const [picture] = await db
const [picture] = await DB.drizzle
.select({ mimetype: upload.mimetype, file: upload.file })
.from(upload)
.where(eq(upload.id, client.pictureId));
@ -65,23 +87,32 @@ export class Uploads {
static async removeAvatar(subject: User) {
if (!subject.pictureId) return;
const [fileinfo] = await db.select().from(upload).where(eq(upload.id, subject.pictureId));
const [fileinfo] = await DB.drizzle
.select()
.from(upload)
.where(eq(upload.id, subject.pictureId));
if (fileinfo) {
await Uploads.removeUpload(fileinfo);
}
await db.update(user).set({ pictureId: null }).where(eq(user.id, subject.id));
await DB.drizzle.update(user).set({ pictureId: null }).where(eq(user.id, subject.id));
}
static async removeClientAvatar(client: OAuth2Client) {
if (!client.pictureId) return;
const [fileinfo] = await db.select().from(upload).where(eq(upload.id, client.pictureId));
const [fileinfo] = await DB.drizzle
.select()
.from(upload)
.where(eq(upload.id, client.pictureId));
if (fileinfo) {
await Uploads.removeUpload(fileinfo);
}
await db.update(oauth2Client).set({ pictureId: null }).where(eq(oauth2Client.id, client.id));
await DB.drizzle
.update(oauth2Client)
.set({ pictureId: null })
.where(eq(oauth2Client.id, client.id));
}
static async saveAvatar(subject: User, file: File) {
@ -93,13 +124,16 @@ export class Uploads {
// Remove old
await Uploads.removeAvatar(subject);
// Update DB
const [retval] = await db.insert(upload).values({
const [retval] = await DB.drizzle.insert(upload).values({
original_name: file.name,
mimetype: file.type,
file: newName,
uploaderId: subject.id
});
await db.update(user).set({ pictureId: retval.insertId }).where(eq(user.id, subject.id));
await DB.drizzle
.update(user)
.set({ pictureId: retval.insertId })
.where(eq(user.id, subject.id));
}
static async saveClientAvatar(client: OAuth2Client, uploader: User, file: File) {
@ -111,13 +145,13 @@ export class Uploads {
// Remove old
await Uploads.removeClientAvatar(client);
// Update DB
const [retval] = await db.insert(upload).values({
const [retval] = await DB.drizzle.insert(upload).values({
original_name: file.name,
mimetype: file.type,
file: newName,
uploaderId: uploader.id
});
await db
await DB.drizzle
.update(oauth2Client)
.set({ pictureId: retval.insertId })
.where(eq(oauth2Client.id, client.id));

View File

@ -1,6 +1,6 @@
import { asc, count, eq, like, or, sql } from 'drizzle-orm';
import {
db,
DB,
privilege,
user,
userPrivilegesPrivilege,
@ -70,12 +70,12 @@ export class UsersAdmin {
)
: undefined;
const [{ rowCount }] = await db
const [{ rowCount }] = await DB.drizzle
.select({ rowCount: count(user.id).mapWith(Number) })
.from(user)
.where(searchExpression);
const baseQuery = db
const baseQuery = DB.drizzle
.select({ id: user.id })
.from(user)
.where(searchExpression)
@ -84,7 +84,7 @@ export class UsersAdmin {
.offset(offset)
.as('searchBase');
const junkList = await db
const junkList = await DB.drizzle
.select({
user: user,
user_privileges_privilege: userPrivilegesPrivilege,
@ -116,7 +116,7 @@ export class UsersAdmin {
* @returns User infor
*/
static async getUserDetails(uuid: string) {
const junkList = await db
const junkList = await DB.drizzle
.select()
.from(user)
.leftJoin(userPrivilegesPrivilege, eq(userPrivilegesPrivilege.userId, user.id))

View File

@ -1,6 +1,6 @@
import bcrypt from 'bcryptjs';
import { and, eq, inArray, isNull, or, sql } from 'drizzle-orm';
import { db, privilege, user, userPrivilegesPrivilege, type User } from '../drizzle';
import { DB, privilege, user, userPrivilegesPrivilege, type User } from '../drizzle';
import type { UserSession } from './types';
import { error, redirect } from '@sveltejs/kit';
import { CryptoUtils } from '../crypto-utils';
@ -16,7 +16,7 @@ export class Users {
* @returns User
*/
static async getById(id: number): Promise<User | undefined> {
const [result] = await db
const [result] = await DB.drizzle
.select()
.from(user)
.where(and(eq(user.id, id), eq(user.activated, 1)))
@ -30,7 +30,7 @@ export class Users {
* @returns User
*/
static async getByUuid(uuid: string, activatedCheck = true): Promise<User | undefined> {
const [result] = await db
const [result] = await DB.drizzle
.select()
.from(user)
.where(and(eq(user.uuid, uuid), activatedCheck ? eq(user.activated, 1) : undefined))
@ -44,7 +44,7 @@ export class Users {
* @returns User
*/
static async getByLogin(login: string): Promise<User | undefined> {
const [result] = await db
const [result] = await DB.drizzle
.select()
.from(user)
.where(
@ -64,7 +64,7 @@ export class Users {
*/
static async getBySession(session?: UserSession): Promise<User | undefined> {
if (!session) return undefined;
const [result] = await db
const [result] = await DB.drizzle
.select()
.from(user)
.where(and(eq(user.id, session.uid), eq(user.activated, 1)))
@ -78,7 +78,7 @@ export class Users {
* @param fields Fields to set
*/
static async update(subject: User, fields: Partial<User>) {
return db.update(user).set(fields).where(eq(user.id, subject.id));
return DB.drizzle.update(user).set(fields).where(eq(user.id, subject.id));
}
/**
@ -137,7 +137,7 @@ export class Users {
*/
static async checkRegistration(username: string, email: string) {
return !(
await db
await DB.drizzle
.select({ id: user.id })
.from(user)
.where(
@ -158,7 +158,7 @@ export class Users {
const returnedToken = await UserTokens.getByToken(token, 'activation');
if (!returnedToken?.userId) return undefined;
const [userInfo] = await db
const [userInfo] = await DB.drizzle
.select()
.from(user)
.where(eq(user.id, returnedToken.userId as number));
@ -175,7 +175,7 @@ export class Users {
* @param subject User
*/
static async activateUserBy(token: string, subject: User) {
await db
await DB.drizzle
.update(user)
.set({ activated: 1, activity_at: new Date() })
.where(eq(user.id, subject.id));
@ -201,7 +201,7 @@ export class Users {
activate?: boolean;
}) {
const passwordHash = await Users.hashPassword(password);
const [retval] = await db.insert(user).values({
const [retval] = await DB.drizzle.insert(user).values({
uuid: CryptoUtils.createUUID(),
email,
username,
@ -211,7 +211,7 @@ export class Users {
activity_at: new Date()
});
const [newUser] = await db.select().from(user).where(eq(user.id, retval.insertId));
const [newUser] = await DB.drizzle.select().from(user).where(eq(user.id, retval.insertId));
if (EMAIL_ENABLED !== 'false' && !activate) {
await Users.sendRegistrationEmail(newUser);
@ -306,7 +306,7 @@ export class Users {
* @returns Available privileges
*/
static async getAvailablePrivileges(clientId?: number) {
return await db
return await DB.drizzle
.select()
.from(privilege)
.where(clientId ? eq(privilege.clientId, clientId) : isNull(privilege.clientId));
@ -319,7 +319,7 @@ export class Users {
* @returns User privileges (string list)
*/
static async getUserPrivileges(subject: User, clientId?: number) {
const list = await db
const list = await DB.drizzle
.select({
privilege: privilege.name
})
@ -348,13 +348,13 @@ export class Users {
* @returns Boolean, whether the privilege was granted or not.
*/
static async grantPrivilege(subject: User, name: string) {
const [existingPrivilege] = await db
const [existingPrivilege] = await DB.drizzle
.select({ id: privilege.id })
.from(privilege)
.where(and(eq(privilege.name, name), isNull(privilege.clientId)));
if (!existingPrivilege) return false;
const [alreadyHas] = await db
const [alreadyHas] = await DB.drizzle
.select({ privilegeId: userPrivilegesPrivilege.privilegeId })
.from(userPrivilegesPrivilege)
.where(
@ -365,7 +365,7 @@ export class Users {
);
if (alreadyHas) return true;
await db.insert(userPrivilegesPrivilege).values({
await DB.drizzle.insert(userPrivilegesPrivilege).values({
privilegeId: existingPrivilege.id,
userId: subject.id
});
@ -385,7 +385,7 @@ export class Users {
// The privileges in question must actually be related to the specified client.
if (clientId) {
for (const id of privilegeIds) {
const [exists] = await db
const [exists] = await DB.drizzle
.select({ id: privilege.id })
.from(privilege)
.where(and(eq(privilege.id, id), eq(privilege.clientId, clientId)));
@ -396,7 +396,7 @@ export class Users {
}
}
const current = await db
const current = await DB.drizzle
.select({
privilegeId: userPrivilegesPrivilege.privilegeId
})
@ -416,7 +416,7 @@ export class Users {
);
if (toRemoveIds.length) {
await db
await DB.drizzle
.delete(userPrivilegesPrivilege)
.where(
and(
@ -432,7 +432,7 @@ export class Users {
);
if (toInsertIds.length) {
await db
await DB.drizzle
.insert(userPrivilegesPrivilege)
.values(toInsertIds.map((privilegeId) => ({ userId: subject.id, privilegeId })));
}

View File

@ -1,6 +1,6 @@
import { and, eq, gt, isNull, or, sql } from 'drizzle-orm';
import { CryptoUtils } from '../crypto-utils';
import { db, userToken, type User, type UserToken } from '../drizzle';
import { DB, userToken, type User, type UserToken } from '../drizzle';
export class UserTokens {
static async create(
@ -19,17 +19,17 @@ export class UserTokens {
nonce,
metadata
};
const [retval] = await db.insert(userToken).values(obj);
const [retval] = await DB.drizzle.insert(userToken).values(obj);
return { id: retval.insertId, ...obj } as UserToken;
}
static async remove(token: string | { token: string }) {
const removeBy = typeof token === 'string' ? token : token.token;
await db.delete(userToken).where(eq(userToken.token, removeBy));
await DB.drizzle.delete(userToken).where(eq(userToken.token, removeBy));
}
static async getByToken(token: string, type: (typeof userToken.$inferSelect)['type']) {
const [returned] = await db
const [returned] = await DB.drizzle
.select()
.from(userToken)
.where(
@ -44,10 +44,10 @@ export class UserTokens {
}
static async wipeUserTokens(user: User) {
await db.delete(userToken).where(eq(userToken.userId, user.id));
await DB.drizzle.delete(userToken).where(eq(userToken.userId, user.id));
}
static async wipeExpiredTokens() {
await db.execute(sql`DELETE FROM ${userToken} WHERE ${userToken.expires_at} < NOW()`);
await DB.drizzle.execute(sql`DELETE FROM ${userToken} WHERE ${userToken.expires_at} < NOW()`);
}
}

View File

@ -1,5 +1,5 @@
import { authenticator as totp } from 'otplib';
import { db, userToken, type User } from '../drizzle';
import { DB, userToken, type User } from '../drizzle';
import { and, eq, gt, isNull, or } from 'drizzle-orm';
import { PUBLIC_SITE_NAME } from '$env/static/public';
@ -21,7 +21,7 @@ export class TimeOTP {
}
public static async isUserOtp(subject: PartialK<User, 'password'>) {
const tokens = await db
const tokens = await DB.drizzle
.select({ id: userToken.id })
.from(userToken)
.where(
@ -35,7 +35,7 @@ export class TimeOTP {
}
public static async getUserOtp(subject: User) {
const [token] = await db
const [token] = await DB.drizzle
.select({ id: userToken.id, token: userToken.token })
.from(userToken)
.where(
@ -50,7 +50,7 @@ export class TimeOTP {
}
public static async saveUserOtp(subject: User, secret: string) {
await db.insert(userToken).values({
await DB.drizzle.insert(userToken).values({
type: 'totp',
token: secret,
userId: subject.id

View File

@ -1,13 +1,8 @@
import { JWT_ALGORITHM } from '$env/static/private';
import { ApiUtils } from '$lib/server/api-utils';
import { JWT } from '$lib/server/jwt';
import { exportJWK } from 'jose';
import { v4 as uuidv4 } from 'uuid';
const jwks = await exportJWK(JWT.publicKey);
const kid = uuidv4({ random: Buffer.from(jwks.n as string).subarray(0, 16) });
export const GET = async () =>
ApiUtils.json({
keys: [{ alg: JWT_ALGORITHM, kid, ...jwks, use: 'sig' }]
keys: [{ alg: JWT_ALGORITHM, kid: JWT.jwksKid, ...JWT.jwks, use: 'sig' }]
});

View File

@ -5,7 +5,8 @@ import { join } from 'path';
export async function GET({ params: { uuid } }) {
const uploadFile = await Uploads.getAvatarByUuid(uuid);
if (!uploadFile) {
return new Response(Uploads.userFallbackImage, {
const fallback = await Uploads.getUserFallback();
return new Response(fallback, {
status: 200,
headers: {
'Content-Type': 'image/png'

View File

@ -5,7 +5,8 @@ import { join } from 'path';
export async function GET({ params: { uuid } }) {
const uploadFile = await Uploads.getClientAvatarById(uuid);
if (!uploadFile) {
return new Response(Uploads.clientFallbackImage, {
const fallback = await Uploads.getClientFallback();
return new Response(fallback, {
status: 200,
headers: {
'Content-Type': 'image/png'