Docker
This commit is contained in:
parent
9753804b28
commit
874b4804b9
4
.dockerignore
Normal file
4
.dockerignore
Normal file
@ -0,0 +1,4 @@
|
||||
uploads
|
||||
node_modules
|
||||
private
|
||||
devdocker
|
29
Dockerfile
Normal file
29
Dockerfile
Normal 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" ]
|
@ -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();
|
||||
|
@ -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
|
||||
});
|
||||
|
||||
export const db = drizzle(connection, { schema, mode: 'default' });
|
||||
DB.drizzle = drizzle(DB.mysqlConnection, { schema, mode: 'default' });
|
||||
}
|
||||
}
|
||||
|
||||
export * from './schema';
|
||||
|
@ -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 });
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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))
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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))
|
||||
|
@ -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));
|
||||
|
@ -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))
|
||||
|
@ -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 })));
|
||||
}
|
||||
|
@ -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()`);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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' }]
|
||||
});
|
||||
|
@ -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'
|
||||
|
@ -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'
|
||||
|
Loading…
Reference in New Issue
Block a user