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 { 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 { runSeeds } from '$lib/server/drizzle/seeds';
|
||||||
|
import { JWT } from '$lib/server/jwt';
|
||||||
import { migrate } from 'drizzle-orm/mysql2/migrator';
|
import { migrate } from 'drizzle-orm/mysql2/migrator';
|
||||||
import { handleSession } from 'svelte-kit-cookie-session';
|
import { handleSession } from 'svelte-kit-cookie-session';
|
||||||
|
|
||||||
|
await DB.init();
|
||||||
|
await JWT.init();
|
||||||
|
|
||||||
if (AUTO_MIGRATE === 'true') {
|
if (AUTO_MIGRATE === 'true') {
|
||||||
await migrate(db, { migrationsFolder: './migrations' });
|
await migrate(DB.drizzle, { migrationsFolder: './migrations' });
|
||||||
}
|
}
|
||||||
|
|
||||||
await runSeeds();
|
await runSeeds();
|
||||||
|
@ -3,12 +3,20 @@ import { drizzle } from 'drizzle-orm/mysql2';
|
|||||||
import mysql from 'mysql2/promise';
|
import mysql from 'mysql2/promise';
|
||||||
import * as schema from './schema';
|
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,
|
host: DATABASE_HOST,
|
||||||
user: DATABASE_PASS,
|
user: DATABASE_PASS,
|
||||||
password: DATABASE_PASS,
|
password: DATABASE_PASS,
|
||||||
database: DATABASE_DB
|
database: DATABASE_DB
|
||||||
});
|
});
|
||||||
|
|
||||||
|
DB.drizzle = drizzle(DB.mysqlConnection, { schema, mode: 'default' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const db = drizzle(connection, { schema, mode: 'default' });
|
|
||||||
export * from './schema';
|
export * from './schema';
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { eq } from 'drizzle-orm';
|
import { eq } from 'drizzle-orm';
|
||||||
import { db, privilege } from '..';
|
import { DB, privilege } from '..';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* System privileges which must always exist in the database.
|
* System privileges which must always exist in the database.
|
||||||
@ -18,11 +18,11 @@ const privileges = [
|
|||||||
|
|
||||||
export default async function privilegesSeed() {
|
export default async function privilegesSeed() {
|
||||||
for (const priv of privileges) {
|
for (const priv of privileges) {
|
||||||
const [exists] = await db
|
const [exists] = await DB.drizzle
|
||||||
.select({ id: privilege.id })
|
.select({ id: privilege.id })
|
||||||
.from(privilege)
|
.from(privilege)
|
||||||
.where(eq(privilege.name, priv));
|
.where(eq(privilege.name, priv));
|
||||||
if (exists) continue;
|
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 { JWT_ALGORITHM, JWT_EXPIRATION, JWT_ISSUER } from '$env/static/private';
|
||||||
import { readFile } from 'fs/promises';
|
import { readFile } from 'fs/promises';
|
||||||
import { SignJWT, importPKCS8, importSPKI, jwtVerify } from 'jose';
|
import {
|
||||||
|
SignJWT,
|
||||||
const privateKeyFile = await readFile('private/jwt.private.pem', { encoding: 'utf-8' });
|
exportJWK,
|
||||||
const publicKeyFile = await readFile('private/jwt.public.pem', { encoding: 'utf-8' });
|
importPKCS8,
|
||||||
const privateKey = await importPKCS8(privateKeyFile, JWT_ALGORITHM);
|
importSPKI,
|
||||||
const publicKey = await importSPKI(publicKeyFile, JWT_ALGORITHM);
|
jwtVerify,
|
||||||
|
type JWK,
|
||||||
|
type KeyLike
|
||||||
|
} from 'jose';
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate JWTs using the following commands:
|
* 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
|
* Public: openssl rsa -in jwt.private.pem -pubout -outform PEM -out jwt.public.pem
|
||||||
*/
|
*/
|
||||||
export class JWT {
|
export class JWT {
|
||||||
static privateKey = privateKey;
|
static privateKey: KeyLike;
|
||||||
static publicKey = publicKey;
|
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) {
|
static async issue(claims: Record<string, unknown>, subject: string, audience?: string) {
|
||||||
const sign = new SignJWT(claims)
|
const sign = new SignJWT(claims)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { PUBLIC_URL, PUBLIC_SITE_NAME } from '$env/static/public';
|
import { PUBLIC_URL, PUBLIC_SITE_NAME } from '$env/static/public';
|
||||||
import { CryptoUtils } from '$lib/server/crypto-utils';
|
import { CryptoUtils } from '$lib/server/crypto-utils';
|
||||||
import {
|
import {
|
||||||
db,
|
DB,
|
||||||
oauth2Client,
|
oauth2Client,
|
||||||
oauth2ClientAuthorization,
|
oauth2ClientAuthorization,
|
||||||
oauth2ClientManager,
|
oauth2ClientManager,
|
||||||
@ -82,7 +82,7 @@ export class OAuth2Clients {
|
|||||||
public static availableUrlTypes: OAuth2ClientURLType[] = Object.values(OAuth2ClientURLType);
|
public static availableUrlTypes: OAuth2ClientURLType[] = Object.values(OAuth2ClientURLType);
|
||||||
|
|
||||||
static async fetchById(id: string | number) {
|
static async fetchById(id: string | number) {
|
||||||
const [client] = await db
|
const [client] = await DB.drizzle
|
||||||
.select()
|
.select()
|
||||||
.from(oauth2Client)
|
.from(oauth2Client)
|
||||||
.where(typeof id === 'string' ? eq(oauth2Client.client_id, id) : eq(oauth2Client.id, id))
|
.where(typeof id === 'string' ? eq(oauth2Client.client_id, id) : eq(oauth2Client.id, id))
|
||||||
@ -94,7 +94,7 @@ export class OAuth2Clients {
|
|||||||
id: string,
|
id: string,
|
||||||
type: OAuth2ClientURLType = OAuth2ClientURLType.REDIRECT_URI
|
type: OAuth2ClientURLType = OAuth2ClientURLType.REDIRECT_URI
|
||||||
) {
|
) {
|
||||||
return await db
|
return await DB.drizzle
|
||||||
.select()
|
.select()
|
||||||
.from(oauth2ClientUrl)
|
.from(oauth2ClientUrl)
|
||||||
.innerJoin(oauth2Client, eq(oauth2ClientUrl.clientId, oauth2Client.id))
|
.innerJoin(oauth2Client, eq(oauth2ClientUrl.clientId, oauth2Client.id))
|
||||||
@ -102,7 +102,7 @@ export class OAuth2Clients {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static async getClientUrls(client: OAuth2Client) {
|
static async getClientUrls(client: OAuth2Client) {
|
||||||
return await db
|
return await DB.drizzle
|
||||||
.select()
|
.select()
|
||||||
.from(oauth2ClientUrl)
|
.from(oauth2ClientUrl)
|
||||||
.where(and(eq(oauth2ClientUrl.clientId, client.id)));
|
.where(and(eq(oauth2ClientUrl.clientId, client.id)));
|
||||||
@ -110,7 +110,7 @@ export class OAuth2Clients {
|
|||||||
|
|
||||||
static async checkRedirectUri(client: OAuth2Client, url: string) {
|
static async checkRedirectUri(client: OAuth2Client, url: string) {
|
||||||
return !!(
|
return !!(
|
||||||
await db
|
await DB.drizzle
|
||||||
.select()
|
.select()
|
||||||
.from(oauth2ClientUrl)
|
.from(oauth2ClientUrl)
|
||||||
.innerJoin(oauth2Client, eq(oauth2ClientUrl.clientId, oauth2Client.id))
|
.innerJoin(oauth2Client, eq(oauth2ClientUrl.clientId, oauth2Client.id))
|
||||||
@ -125,7 +125,7 @@ export class OAuth2Clients {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static async getAuthorizedUsers(client: OAuth2Client, userUuid?: string) {
|
static async getAuthorizedUsers(client: OAuth2Client, userUuid?: string) {
|
||||||
const junkList = await db
|
const junkList = await DB.drizzle
|
||||||
.select()
|
.select()
|
||||||
.from(oauth2ClientAuthorization)
|
.from(oauth2ClientAuthorization)
|
||||||
.innerJoin(user, eq(user.id, oauth2ClientAuthorization.userId))
|
.innerJoin(user, eq(user.id, oauth2ClientAuthorization.userId))
|
||||||
@ -215,7 +215,7 @@ export class OAuth2Clients {
|
|||||||
) {
|
) {
|
||||||
const filterText = `%${filters?.filter?.toLowerCase()}%`;
|
const filterText = `%${filters?.filter?.toLowerCase()}%`;
|
||||||
const limit = filters?.limit || 20;
|
const limit = filters?.limit || 20;
|
||||||
const allowedClients = db
|
const allowedClients = DB.drizzle
|
||||||
.select({ id: oauth2Client.id })
|
.select({ id: oauth2Client.id })
|
||||||
.from(oauth2Client)
|
.from(oauth2Client)
|
||||||
.leftJoin(oauth2ClientManager, eq(oauth2ClientManager.clientId, oauth2Client.id))
|
.leftJoin(oauth2ClientManager, eq(oauth2ClientManager.clientId, oauth2Client.id))
|
||||||
@ -238,14 +238,14 @@ export class OAuth2Clients {
|
|||||||
.offset(filters?.offset || 0)
|
.offset(filters?.offset || 0)
|
||||||
.as('allowedClients');
|
.as('allowedClients');
|
||||||
|
|
||||||
const [{ rowCount }] = await db
|
const [{ rowCount }] = await DB.drizzle
|
||||||
.select({
|
.select({
|
||||||
rowCount: count(oauth2Client.id).mapWith(Number)
|
rowCount: count(oauth2Client.id).mapWith(Number)
|
||||||
})
|
})
|
||||||
.from(allowedClients)
|
.from(allowedClients)
|
||||||
.innerJoin(oauth2Client, eq(allowedClients.id, oauth2Client.id));
|
.innerJoin(oauth2Client, eq(allowedClients.id, oauth2Client.id));
|
||||||
|
|
||||||
const junkList = await db
|
const junkList = await DB.drizzle
|
||||||
.select({
|
.select({
|
||||||
o_auth2_client: oauth2Client,
|
o_auth2_client: oauth2Client,
|
||||||
o_auth2_client_url: oauth2ClientUrl,
|
o_auth2_client_url: oauth2ClientUrl,
|
||||||
@ -308,7 +308,7 @@ export class OAuth2Clients {
|
|||||||
const uid = CryptoUtils.createUUID();
|
const uid = CryptoUtils.createUUID();
|
||||||
const secret = CryptoUtils.generateSecret();
|
const secret = CryptoUtils.generateSecret();
|
||||||
|
|
||||||
const [retval] = await db.insert(oauth2Client).values({
|
const [retval] = await DB.drizzle.insert(oauth2Client).values({
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
client_id: uid,
|
client_id: uid,
|
||||||
@ -321,7 +321,7 @@ export class OAuth2Clients {
|
|||||||
verified: 0
|
verified: 0
|
||||||
});
|
});
|
||||||
|
|
||||||
await db.insert(oauth2ClientUrl).values({
|
await DB.drizzle.insert(oauth2ClientUrl).values({
|
||||||
type: 'redirect_uri',
|
type: 'redirect_uri',
|
||||||
url: redirect,
|
url: redirect,
|
||||||
clientId: retval.insertId
|
clientId: retval.insertId
|
||||||
@ -334,18 +334,18 @@ export class OAuth2Clients {
|
|||||||
if (client.pictureId) {
|
if (client.pictureId) {
|
||||||
await Uploads.removeClientAvatar(client);
|
await Uploads.removeClientAvatar(client);
|
||||||
}
|
}
|
||||||
await db.delete(privilege).where(eq(privilege.clientId, client.id));
|
await DB.drizzle.delete(privilege).where(eq(privilege.clientId, client.id));
|
||||||
await db.delete(oauth2Client).where(eq(oauth2Client.id, client.id));
|
await DB.drizzle.delete(oauth2Client).where(eq(oauth2Client.id, client.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
static async deleteUrl(client: OAuth2Client, urlId: number) {
|
static async deleteUrl(client: OAuth2Client, urlId: number) {
|
||||||
await db
|
await DB.drizzle
|
||||||
.delete(oauth2ClientUrl)
|
.delete(oauth2ClientUrl)
|
||||||
.where(and(eq(oauth2ClientUrl.clientId, client.id), eq(oauth2ClientUrl.id, urlId)));
|
.where(and(eq(oauth2ClientUrl.clientId, client.id), eq(oauth2ClientUrl.id, urlId)));
|
||||||
}
|
}
|
||||||
|
|
||||||
static async addUrl(client: OAuth2Client, type: OAuth2ClientURLType, url: string) {
|
static async addUrl(client: OAuth2Client, type: OAuth2ClientURLType, url: string) {
|
||||||
await db.insert(oauth2ClientUrl).values({
|
await DB.drizzle.insert(oauth2ClientUrl).values({
|
||||||
type,
|
type,
|
||||||
url,
|
url,
|
||||||
clientId: client.id
|
clientId: client.id
|
||||||
@ -353,25 +353,25 @@ export class OAuth2Clients {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static async deletePrivilege(client: OAuth2Client, privilegeId: number) {
|
static async deletePrivilege(client: OAuth2Client, privilegeId: number) {
|
||||||
await db
|
await DB.drizzle
|
||||||
.delete(privilege)
|
.delete(privilege)
|
||||||
.where(and(eq(privilege.clientId, client.id), eq(privilege.id, privilegeId)));
|
.where(and(eq(privilege.clientId, client.id), eq(privilege.id, privilegeId)));
|
||||||
}
|
}
|
||||||
|
|
||||||
static async addPrivilege(client: OAuth2Client, name: string) {
|
static async addPrivilege(client: OAuth2Client, name: string) {
|
||||||
const realName = `${client.client_id.split('-')[0]}:${name}`;
|
const realName = `${client.client_id.split('-')[0]}:${name}`;
|
||||||
await db.insert(privilege).values({
|
await DB.drizzle.insert(privilege).values({
|
||||||
name: realName,
|
name: realName,
|
||||||
clientId: client.id
|
clientId: client.id
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static async update(client: OAuth2Client, body: Partial<OAuth2Client>) {
|
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) {
|
static async getManagers(client: OAuth2Client) {
|
||||||
return await db
|
return await DB.drizzle
|
||||||
.select({ id: oauth2ClientManager.id, email: user.email })
|
.select({ id: oauth2ClientManager.id, email: user.email })
|
||||||
.from(oauth2ClientManager)
|
.from(oauth2ClientManager)
|
||||||
.innerJoin(user, eq(user.id, oauth2ClientManager.userId))
|
.innerJoin(user, eq(user.id, oauth2ClientManager.userId))
|
||||||
@ -386,7 +386,7 @@ export class OAuth2Clients {
|
|||||||
await Users.grantPrivilege(subject, 'self:oauth2');
|
await Users.grantPrivilege(subject, 'self:oauth2');
|
||||||
}
|
}
|
||||||
|
|
||||||
await db.insert(oauth2ClientManager).values({
|
await DB.drizzle.insert(oauth2ClientManager).values({
|
||||||
clientId: client.id,
|
clientId: client.id,
|
||||||
userId: subject.id,
|
userId: subject.id,
|
||||||
issuerId: actor.id
|
issuerId: actor.id
|
||||||
@ -394,7 +394,7 @@ export class OAuth2Clients {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static async removeManager(client: OAuth2Client, managerId: number) {
|
static async removeManager(client: OAuth2Client, managerId: number) {
|
||||||
await db
|
await DB.drizzle
|
||||||
.delete(oauth2ClientManager)
|
.delete(oauth2ClientManager)
|
||||||
.where(
|
.where(
|
||||||
and(eq(oauth2ClientManager.clientId, client.id), eq(oauth2ClientManager.id, managerId))
|
and(eq(oauth2ClientManager.clientId, client.id), eq(oauth2ClientManager.id, managerId))
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
db,
|
DB,
|
||||||
oauth2Client,
|
oauth2Client,
|
||||||
oauth2Token,
|
oauth2Token,
|
||||||
type OAuth2Client,
|
type OAuth2Client,
|
||||||
@ -49,7 +49,7 @@ export class OAuth2Tokens {
|
|||||||
nonce?: string,
|
nonce?: string,
|
||||||
pcke?: string
|
pcke?: string
|
||||||
) {
|
) {
|
||||||
const [retval] = await db.insert(oauth2Token).values({
|
const [retval] = await DB.drizzle.insert(oauth2Token).values({
|
||||||
token,
|
token,
|
||||||
type,
|
type,
|
||||||
scope,
|
scope,
|
||||||
@ -60,7 +60,7 @@ export class OAuth2Tokens {
|
|||||||
pcke
|
pcke
|
||||||
});
|
});
|
||||||
|
|
||||||
const [newToken] = await db
|
const [newToken] = await DB.drizzle
|
||||||
.select()
|
.select()
|
||||||
.from(oauth2Token)
|
.from(oauth2Token)
|
||||||
.where(eq(oauth2Token.id, retval.insertId));
|
.where(eq(oauth2Token.id, retval.insertId));
|
||||||
@ -69,7 +69,7 @@ export class OAuth2Tokens {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static async fetchByToken(token: string, type: OAuth2TokenType) {
|
static async fetchByToken(token: string, type: OAuth2TokenType) {
|
||||||
const [retval] = await db
|
const [retval] = await DB.drizzle
|
||||||
.select()
|
.select()
|
||||||
.from(oauth2Token)
|
.from(oauth2Token)
|
||||||
.where(and(eq(oauth2Token.token, token), eq(oauth2Token.type, type)));
|
.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) {
|
static async fetchByUserIdClientId(userId: number, clientId: string, type: OAuth2TokenType) {
|
||||||
const [retval] = await db
|
const [retval] = await DB.drizzle
|
||||||
.select()
|
.select()
|
||||||
.from(oauth2Token)
|
.from(oauth2Token)
|
||||||
.innerJoin(oauth2Client, eq(oauth2Token.clientId, oauth2Client.id))
|
.innerJoin(oauth2Client, eq(oauth2Token.clientId, oauth2Client.id))
|
||||||
@ -92,7 +92,7 @@ export class OAuth2Tokens {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static async wipeClientTokens(client: OAuth2Client, user?: User) {
|
static async wipeClientTokens(client: OAuth2Client, user?: User) {
|
||||||
await db
|
await DB.drizzle
|
||||||
.delete(oauth2Token)
|
.delete(oauth2Token)
|
||||||
.where(
|
.where(
|
||||||
and(eq(oauth2Token.clientId, client.id), user ? eq(oauth2Token.userId, user.id) : undefined)
|
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) {
|
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() {
|
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) {
|
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 {
|
import {
|
||||||
db,
|
DB,
|
||||||
oauth2Client,
|
oauth2Client,
|
||||||
oauth2ClientAuthorization,
|
oauth2ClientAuthorization,
|
||||||
oauth2ClientUrl,
|
oauth2ClientUrl,
|
||||||
@ -21,7 +21,7 @@ export class OAuth2Users {
|
|||||||
static async consented(userId: number, clientId: string, scopes: string | string[]) {
|
static async consented(userId: number, clientId: string, scopes: string | string[]) {
|
||||||
const normalized = OAuth2Clients.splitScope(scopes);
|
const normalized = OAuth2Clients.splitScope(scopes);
|
||||||
return !!(
|
return !!(
|
||||||
await db
|
await DB.drizzle
|
||||||
.select({
|
.select({
|
||||||
id: oauth2ClientAuthorization.id,
|
id: oauth2ClientAuthorization.id,
|
||||||
scope: oauth2ClientAuthorization.scope
|
scope: oauth2ClientAuthorization.scope
|
||||||
@ -43,7 +43,7 @@ export class OAuth2Users {
|
|||||||
|
|
||||||
static async saveConsent(subject: User, client: OAuth2Client, scopes: string | string[]) {
|
static async saveConsent(subject: User, client: OAuth2Client, scopes: string | string[]) {
|
||||||
const normalized = OAuth2Clients.splitScope(scopes);
|
const normalized = OAuth2Clients.splitScope(scopes);
|
||||||
const [existing] = await db
|
const [existing] = await DB.drizzle
|
||||||
.select()
|
.select()
|
||||||
.from(oauth2ClientAuthorization)
|
.from(oauth2ClientAuthorization)
|
||||||
.where(
|
.where(
|
||||||
@ -62,14 +62,14 @@ export class OAuth2Users {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await db
|
await DB.drizzle
|
||||||
.update(oauth2ClientAuthorization)
|
.update(oauth2ClientAuthorization)
|
||||||
.set({ scope: OAuth2Clients.joinScope(splitScope), current: 1, expires_at: null })
|
.set({ scope: OAuth2Clients.joinScope(splitScope), current: 1, expires_at: null })
|
||||||
.where(eq(oauth2ClientAuthorization.id, existing.id));
|
.where(eq(oauth2ClientAuthorization.id, existing.id));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await db.insert(oauth2ClientAuthorization).values({
|
await DB.drizzle.insert(oauth2ClientAuthorization).values({
|
||||||
userId: subject.id,
|
userId: subject.id,
|
||||||
clientId: client.id,
|
clientId: client.id,
|
||||||
scope: OAuth2Clients.joinScope(normalized)
|
scope: OAuth2Clients.joinScope(normalized)
|
||||||
@ -81,7 +81,7 @@ export class OAuth2Users {
|
|||||||
if (!client) return false;
|
if (!client) return false;
|
||||||
|
|
||||||
await OAuth2Tokens.wipeClientTokens(client, subject);
|
await OAuth2Tokens.wipeClientTokens(client, subject);
|
||||||
await db
|
await DB.drizzle
|
||||||
.update(oauth2ClientAuthorization)
|
.update(oauth2ClientAuthorization)
|
||||||
.set({ current: 0, expires_at: new Date() })
|
.set({ current: 0, expires_at: new Date() })
|
||||||
.where(
|
.where(
|
||||||
@ -96,7 +96,7 @@ export class OAuth2Users {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static async listAuthorizations(subject: User) {
|
static async listAuthorizations(subject: User) {
|
||||||
return db
|
return DB.drizzle
|
||||||
.select()
|
.select()
|
||||||
.from(oauth2Client)
|
.from(oauth2Client)
|
||||||
.innerJoin(oauth2ClientAuthorization, eq(oauth2ClientAuthorization.clientId, oauth2Client.id))
|
.innerJoin(oauth2ClientAuthorization, eq(oauth2ClientAuthorization.clientId, oauth2Client.id))
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { eq } from 'drizzle-orm';
|
import { eq } from 'drizzle-orm';
|
||||||
import {
|
import {
|
||||||
db,
|
DB,
|
||||||
oauth2Client,
|
oauth2Client,
|
||||||
upload,
|
upload,
|
||||||
user,
|
user,
|
||||||
@ -9,19 +9,41 @@ import {
|
|||||||
type User
|
type User
|
||||||
} from './drizzle';
|
} from './drizzle';
|
||||||
import { Users } from './users';
|
import { Users } from './users';
|
||||||
import { readFile, unlink, writeFile } from 'fs/promises';
|
import { readFile, stat, unlink, writeFile } from 'fs/promises';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import * as mime from 'mime-types';
|
import * as mime from 'mime-types';
|
||||||
import { OAuth2Clients } from './oauth2';
|
import { OAuth2Clients } from './oauth2';
|
||||||
|
|
||||||
const userFallbackImage = await readFile(join('static', 'avatar.png'));
|
|
||||||
const clientFallbackImage = await readFile(join('static', 'application.png'));
|
|
||||||
|
|
||||||
export class Uploads {
|
export class Uploads {
|
||||||
static userFallbackImage = userFallbackImage;
|
static userFallbackImage: Buffer;
|
||||||
static clientFallbackImage = clientFallbackImage;
|
static clientFallbackImage: Buffer;
|
||||||
static uploads = join('uploads');
|
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) {
|
static async removeUpload(subject: Upload) {
|
||||||
try {
|
try {
|
||||||
unlink(join(Uploads.uploads, subject.file));
|
unlink(join(Uploads.uploads, subject.file));
|
||||||
@ -29,7 +51,7 @@ export class Uploads {
|
|||||||
// ignore unlink error
|
// 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(
|
static async getAvatarByUuid(
|
||||||
@ -40,7 +62,7 @@ export class Uploads {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [picture] = await db
|
const [picture] = await DB.drizzle
|
||||||
.select({ mimetype: upload.mimetype, file: upload.file })
|
.select({ mimetype: upload.mimetype, file: upload.file })
|
||||||
.from(upload)
|
.from(upload)
|
||||||
.where(eq(upload.id, user.pictureId));
|
.where(eq(upload.id, user.pictureId));
|
||||||
@ -55,7 +77,7 @@ export class Uploads {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [picture] = await db
|
const [picture] = await DB.drizzle
|
||||||
.select({ mimetype: upload.mimetype, file: upload.file })
|
.select({ mimetype: upload.mimetype, file: upload.file })
|
||||||
.from(upload)
|
.from(upload)
|
||||||
.where(eq(upload.id, client.pictureId));
|
.where(eq(upload.id, client.pictureId));
|
||||||
@ -65,23 +87,32 @@ export class Uploads {
|
|||||||
static async removeAvatar(subject: User) {
|
static async removeAvatar(subject: User) {
|
||||||
if (!subject.pictureId) return;
|
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) {
|
if (fileinfo) {
|
||||||
await Uploads.removeUpload(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) {
|
static async removeClientAvatar(client: OAuth2Client) {
|
||||||
if (!client.pictureId) return;
|
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) {
|
if (fileinfo) {
|
||||||
await Uploads.removeUpload(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) {
|
static async saveAvatar(subject: User, file: File) {
|
||||||
@ -93,13 +124,16 @@ export class Uploads {
|
|||||||
// Remove old
|
// Remove old
|
||||||
await Uploads.removeAvatar(subject);
|
await Uploads.removeAvatar(subject);
|
||||||
// Update DB
|
// Update DB
|
||||||
const [retval] = await db.insert(upload).values({
|
const [retval] = await DB.drizzle.insert(upload).values({
|
||||||
original_name: file.name,
|
original_name: file.name,
|
||||||
mimetype: file.type,
|
mimetype: file.type,
|
||||||
file: newName,
|
file: newName,
|
||||||
uploaderId: subject.id
|
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) {
|
static async saveClientAvatar(client: OAuth2Client, uploader: User, file: File) {
|
||||||
@ -111,13 +145,13 @@ export class Uploads {
|
|||||||
// Remove old
|
// Remove old
|
||||||
await Uploads.removeClientAvatar(client);
|
await Uploads.removeClientAvatar(client);
|
||||||
// Update DB
|
// Update DB
|
||||||
const [retval] = await db.insert(upload).values({
|
const [retval] = await DB.drizzle.insert(upload).values({
|
||||||
original_name: file.name,
|
original_name: file.name,
|
||||||
mimetype: file.type,
|
mimetype: file.type,
|
||||||
file: newName,
|
file: newName,
|
||||||
uploaderId: uploader.id
|
uploaderId: uploader.id
|
||||||
});
|
});
|
||||||
await db
|
await DB.drizzle
|
||||||
.update(oauth2Client)
|
.update(oauth2Client)
|
||||||
.set({ pictureId: retval.insertId })
|
.set({ pictureId: retval.insertId })
|
||||||
.where(eq(oauth2Client.id, client.id));
|
.where(eq(oauth2Client.id, client.id));
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { asc, count, eq, like, or, sql } from 'drizzle-orm';
|
import { asc, count, eq, like, or, sql } from 'drizzle-orm';
|
||||||
import {
|
import {
|
||||||
db,
|
DB,
|
||||||
privilege,
|
privilege,
|
||||||
user,
|
user,
|
||||||
userPrivilegesPrivilege,
|
userPrivilegesPrivilege,
|
||||||
@ -70,12 +70,12 @@ export class UsersAdmin {
|
|||||||
)
|
)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const [{ rowCount }] = await db
|
const [{ rowCount }] = await DB.drizzle
|
||||||
.select({ rowCount: count(user.id).mapWith(Number) })
|
.select({ rowCount: count(user.id).mapWith(Number) })
|
||||||
.from(user)
|
.from(user)
|
||||||
.where(searchExpression);
|
.where(searchExpression);
|
||||||
|
|
||||||
const baseQuery = db
|
const baseQuery = DB.drizzle
|
||||||
.select({ id: user.id })
|
.select({ id: user.id })
|
||||||
.from(user)
|
.from(user)
|
||||||
.where(searchExpression)
|
.where(searchExpression)
|
||||||
@ -84,7 +84,7 @@ export class UsersAdmin {
|
|||||||
.offset(offset)
|
.offset(offset)
|
||||||
.as('searchBase');
|
.as('searchBase');
|
||||||
|
|
||||||
const junkList = await db
|
const junkList = await DB.drizzle
|
||||||
.select({
|
.select({
|
||||||
user: user,
|
user: user,
|
||||||
user_privileges_privilege: userPrivilegesPrivilege,
|
user_privileges_privilege: userPrivilegesPrivilege,
|
||||||
@ -116,7 +116,7 @@ export class UsersAdmin {
|
|||||||
* @returns User infor
|
* @returns User infor
|
||||||
*/
|
*/
|
||||||
static async getUserDetails(uuid: string) {
|
static async getUserDetails(uuid: string) {
|
||||||
const junkList = await db
|
const junkList = await DB.drizzle
|
||||||
.select()
|
.select()
|
||||||
.from(user)
|
.from(user)
|
||||||
.leftJoin(userPrivilegesPrivilege, eq(userPrivilegesPrivilege.userId, user.id))
|
.leftJoin(userPrivilegesPrivilege, eq(userPrivilegesPrivilege.userId, user.id))
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import bcrypt from 'bcryptjs';
|
import bcrypt from 'bcryptjs';
|
||||||
import { and, eq, inArray, isNull, or, sql } from 'drizzle-orm';
|
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 type { UserSession } from './types';
|
||||||
import { error, redirect } from '@sveltejs/kit';
|
import { error, redirect } from '@sveltejs/kit';
|
||||||
import { CryptoUtils } from '../crypto-utils';
|
import { CryptoUtils } from '../crypto-utils';
|
||||||
@ -16,7 +16,7 @@ export class Users {
|
|||||||
* @returns User
|
* @returns User
|
||||||
*/
|
*/
|
||||||
static async getById(id: number): Promise<User | undefined> {
|
static async getById(id: number): Promise<User | undefined> {
|
||||||
const [result] = await db
|
const [result] = await DB.drizzle
|
||||||
.select()
|
.select()
|
||||||
.from(user)
|
.from(user)
|
||||||
.where(and(eq(user.id, id), eq(user.activated, 1)))
|
.where(and(eq(user.id, id), eq(user.activated, 1)))
|
||||||
@ -30,7 +30,7 @@ export class Users {
|
|||||||
* @returns User
|
* @returns User
|
||||||
*/
|
*/
|
||||||
static async getByUuid(uuid: string, activatedCheck = true): Promise<User | undefined> {
|
static async getByUuid(uuid: string, activatedCheck = true): Promise<User | undefined> {
|
||||||
const [result] = await db
|
const [result] = await DB.drizzle
|
||||||
.select()
|
.select()
|
||||||
.from(user)
|
.from(user)
|
||||||
.where(and(eq(user.uuid, uuid), activatedCheck ? eq(user.activated, 1) : undefined))
|
.where(and(eq(user.uuid, uuid), activatedCheck ? eq(user.activated, 1) : undefined))
|
||||||
@ -44,7 +44,7 @@ export class Users {
|
|||||||
* @returns User
|
* @returns User
|
||||||
*/
|
*/
|
||||||
static async getByLogin(login: string): Promise<User | undefined> {
|
static async getByLogin(login: string): Promise<User | undefined> {
|
||||||
const [result] = await db
|
const [result] = await DB.drizzle
|
||||||
.select()
|
.select()
|
||||||
.from(user)
|
.from(user)
|
||||||
.where(
|
.where(
|
||||||
@ -64,7 +64,7 @@ export class Users {
|
|||||||
*/
|
*/
|
||||||
static async getBySession(session?: UserSession): Promise<User | undefined> {
|
static async getBySession(session?: UserSession): Promise<User | undefined> {
|
||||||
if (!session) return undefined;
|
if (!session) return undefined;
|
||||||
const [result] = await db
|
const [result] = await DB.drizzle
|
||||||
.select()
|
.select()
|
||||||
.from(user)
|
.from(user)
|
||||||
.where(and(eq(user.id, session.uid), eq(user.activated, 1)))
|
.where(and(eq(user.id, session.uid), eq(user.activated, 1)))
|
||||||
@ -78,7 +78,7 @@ export class Users {
|
|||||||
* @param fields Fields to set
|
* @param fields Fields to set
|
||||||
*/
|
*/
|
||||||
static async update(subject: User, fields: Partial<User>) {
|
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) {
|
static async checkRegistration(username: string, email: string) {
|
||||||
return !(
|
return !(
|
||||||
await db
|
await DB.drizzle
|
||||||
.select({ id: user.id })
|
.select({ id: user.id })
|
||||||
.from(user)
|
.from(user)
|
||||||
.where(
|
.where(
|
||||||
@ -158,7 +158,7 @@ export class Users {
|
|||||||
const returnedToken = await UserTokens.getByToken(token, 'activation');
|
const returnedToken = await UserTokens.getByToken(token, 'activation');
|
||||||
if (!returnedToken?.userId) return undefined;
|
if (!returnedToken?.userId) return undefined;
|
||||||
|
|
||||||
const [userInfo] = await db
|
const [userInfo] = await DB.drizzle
|
||||||
.select()
|
.select()
|
||||||
.from(user)
|
.from(user)
|
||||||
.where(eq(user.id, returnedToken.userId as number));
|
.where(eq(user.id, returnedToken.userId as number));
|
||||||
@ -175,7 +175,7 @@ export class Users {
|
|||||||
* @param subject User
|
* @param subject User
|
||||||
*/
|
*/
|
||||||
static async activateUserBy(token: string, subject: User) {
|
static async activateUserBy(token: string, subject: User) {
|
||||||
await db
|
await DB.drizzle
|
||||||
.update(user)
|
.update(user)
|
||||||
.set({ activated: 1, activity_at: new Date() })
|
.set({ activated: 1, activity_at: new Date() })
|
||||||
.where(eq(user.id, subject.id));
|
.where(eq(user.id, subject.id));
|
||||||
@ -201,7 +201,7 @@ export class Users {
|
|||||||
activate?: boolean;
|
activate?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const passwordHash = await Users.hashPassword(password);
|
const passwordHash = await Users.hashPassword(password);
|
||||||
const [retval] = await db.insert(user).values({
|
const [retval] = await DB.drizzle.insert(user).values({
|
||||||
uuid: CryptoUtils.createUUID(),
|
uuid: CryptoUtils.createUUID(),
|
||||||
email,
|
email,
|
||||||
username,
|
username,
|
||||||
@ -211,7 +211,7 @@ export class Users {
|
|||||||
activity_at: new Date()
|
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) {
|
if (EMAIL_ENABLED !== 'false' && !activate) {
|
||||||
await Users.sendRegistrationEmail(newUser);
|
await Users.sendRegistrationEmail(newUser);
|
||||||
@ -306,7 +306,7 @@ export class Users {
|
|||||||
* @returns Available privileges
|
* @returns Available privileges
|
||||||
*/
|
*/
|
||||||
static async getAvailablePrivileges(clientId?: number) {
|
static async getAvailablePrivileges(clientId?: number) {
|
||||||
return await db
|
return await DB.drizzle
|
||||||
.select()
|
.select()
|
||||||
.from(privilege)
|
.from(privilege)
|
||||||
.where(clientId ? eq(privilege.clientId, clientId) : isNull(privilege.clientId));
|
.where(clientId ? eq(privilege.clientId, clientId) : isNull(privilege.clientId));
|
||||||
@ -319,7 +319,7 @@ export class Users {
|
|||||||
* @returns User privileges (string list)
|
* @returns User privileges (string list)
|
||||||
*/
|
*/
|
||||||
static async getUserPrivileges(subject: User, clientId?: number) {
|
static async getUserPrivileges(subject: User, clientId?: number) {
|
||||||
const list = await db
|
const list = await DB.drizzle
|
||||||
.select({
|
.select({
|
||||||
privilege: privilege.name
|
privilege: privilege.name
|
||||||
})
|
})
|
||||||
@ -348,13 +348,13 @@ export class Users {
|
|||||||
* @returns Boolean, whether the privilege was granted or not.
|
* @returns Boolean, whether the privilege was granted or not.
|
||||||
*/
|
*/
|
||||||
static async grantPrivilege(subject: User, name: string) {
|
static async grantPrivilege(subject: User, name: string) {
|
||||||
const [existingPrivilege] = await db
|
const [existingPrivilege] = await DB.drizzle
|
||||||
.select({ id: privilege.id })
|
.select({ id: privilege.id })
|
||||||
.from(privilege)
|
.from(privilege)
|
||||||
.where(and(eq(privilege.name, name), isNull(privilege.clientId)));
|
.where(and(eq(privilege.name, name), isNull(privilege.clientId)));
|
||||||
if (!existingPrivilege) return false;
|
if (!existingPrivilege) return false;
|
||||||
|
|
||||||
const [alreadyHas] = await db
|
const [alreadyHas] = await DB.drizzle
|
||||||
.select({ privilegeId: userPrivilegesPrivilege.privilegeId })
|
.select({ privilegeId: userPrivilegesPrivilege.privilegeId })
|
||||||
.from(userPrivilegesPrivilege)
|
.from(userPrivilegesPrivilege)
|
||||||
.where(
|
.where(
|
||||||
@ -365,7 +365,7 @@ export class Users {
|
|||||||
);
|
);
|
||||||
if (alreadyHas) return true;
|
if (alreadyHas) return true;
|
||||||
|
|
||||||
await db.insert(userPrivilegesPrivilege).values({
|
await DB.drizzle.insert(userPrivilegesPrivilege).values({
|
||||||
privilegeId: existingPrivilege.id,
|
privilegeId: existingPrivilege.id,
|
||||||
userId: subject.id
|
userId: subject.id
|
||||||
});
|
});
|
||||||
@ -385,7 +385,7 @@ export class Users {
|
|||||||
// The privileges in question must actually be related to the specified client.
|
// The privileges in question must actually be related to the specified client.
|
||||||
if (clientId) {
|
if (clientId) {
|
||||||
for (const id of privilegeIds) {
|
for (const id of privilegeIds) {
|
||||||
const [exists] = await db
|
const [exists] = await DB.drizzle
|
||||||
.select({ id: privilege.id })
|
.select({ id: privilege.id })
|
||||||
.from(privilege)
|
.from(privilege)
|
||||||
.where(and(eq(privilege.id, id), eq(privilege.clientId, clientId)));
|
.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({
|
.select({
|
||||||
privilegeId: userPrivilegesPrivilege.privilegeId
|
privilegeId: userPrivilegesPrivilege.privilegeId
|
||||||
})
|
})
|
||||||
@ -416,7 +416,7 @@ export class Users {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (toRemoveIds.length) {
|
if (toRemoveIds.length) {
|
||||||
await db
|
await DB.drizzle
|
||||||
.delete(userPrivilegesPrivilege)
|
.delete(userPrivilegesPrivilege)
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
@ -432,7 +432,7 @@ export class Users {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (toInsertIds.length) {
|
if (toInsertIds.length) {
|
||||||
await db
|
await DB.drizzle
|
||||||
.insert(userPrivilegesPrivilege)
|
.insert(userPrivilegesPrivilege)
|
||||||
.values(toInsertIds.map((privilegeId) => ({ userId: subject.id, privilegeId })));
|
.values(toInsertIds.map((privilegeId) => ({ userId: subject.id, privilegeId })));
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { and, eq, gt, isNull, or, sql } from 'drizzle-orm';
|
import { and, eq, gt, isNull, or, sql } from 'drizzle-orm';
|
||||||
import { CryptoUtils } from '../crypto-utils';
|
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 {
|
export class UserTokens {
|
||||||
static async create(
|
static async create(
|
||||||
@ -19,17 +19,17 @@ export class UserTokens {
|
|||||||
nonce,
|
nonce,
|
||||||
metadata
|
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;
|
return { id: retval.insertId, ...obj } as UserToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async remove(token: string | { token: string }) {
|
static async remove(token: string | { token: string }) {
|
||||||
const removeBy = typeof token === 'string' ? token : token.token;
|
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']) {
|
static async getByToken(token: string, type: (typeof userToken.$inferSelect)['type']) {
|
||||||
const [returned] = await db
|
const [returned] = await DB.drizzle
|
||||||
.select()
|
.select()
|
||||||
.from(userToken)
|
.from(userToken)
|
||||||
.where(
|
.where(
|
||||||
@ -44,10 +44,10 @@ export class UserTokens {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static async wipeUserTokens(user: User) {
|
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() {
|
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 { 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 { and, eq, gt, isNull, or } from 'drizzle-orm';
|
||||||
import { PUBLIC_SITE_NAME } from '$env/static/public';
|
import { PUBLIC_SITE_NAME } from '$env/static/public';
|
||||||
|
|
||||||
@ -21,7 +21,7 @@ export class TimeOTP {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static async isUserOtp(subject: PartialK<User, 'password'>) {
|
public static async isUserOtp(subject: PartialK<User, 'password'>) {
|
||||||
const tokens = await db
|
const tokens = await DB.drizzle
|
||||||
.select({ id: userToken.id })
|
.select({ id: userToken.id })
|
||||||
.from(userToken)
|
.from(userToken)
|
||||||
.where(
|
.where(
|
||||||
@ -35,7 +35,7 @@ export class TimeOTP {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static async getUserOtp(subject: User) {
|
public static async getUserOtp(subject: User) {
|
||||||
const [token] = await db
|
const [token] = await DB.drizzle
|
||||||
.select({ id: userToken.id, token: userToken.token })
|
.select({ id: userToken.id, token: userToken.token })
|
||||||
.from(userToken)
|
.from(userToken)
|
||||||
.where(
|
.where(
|
||||||
@ -50,7 +50,7 @@ export class TimeOTP {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static async saveUserOtp(subject: User, secret: string) {
|
public static async saveUserOtp(subject: User, secret: string) {
|
||||||
await db.insert(userToken).values({
|
await DB.drizzle.insert(userToken).values({
|
||||||
type: 'totp',
|
type: 'totp',
|
||||||
token: secret,
|
token: secret,
|
||||||
userId: subject.id
|
userId: subject.id
|
||||||
|
@ -1,13 +1,8 @@
|
|||||||
import { JWT_ALGORITHM } from '$env/static/private';
|
import { JWT_ALGORITHM } from '$env/static/private';
|
||||||
import { ApiUtils } from '$lib/server/api-utils';
|
import { ApiUtils } from '$lib/server/api-utils';
|
||||||
import { JWT } from '$lib/server/jwt';
|
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 () =>
|
export const GET = async () =>
|
||||||
ApiUtils.json({
|
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 } }) {
|
export async function GET({ params: { uuid } }) {
|
||||||
const uploadFile = await Uploads.getAvatarByUuid(uuid);
|
const uploadFile = await Uploads.getAvatarByUuid(uuid);
|
||||||
if (!uploadFile) {
|
if (!uploadFile) {
|
||||||
return new Response(Uploads.userFallbackImage, {
|
const fallback = await Uploads.getUserFallback();
|
||||||
|
return new Response(fallback, {
|
||||||
status: 200,
|
status: 200,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'image/png'
|
'Content-Type': 'image/png'
|
||||||
|
@ -5,7 +5,8 @@ import { join } from 'path';
|
|||||||
export async function GET({ params: { uuid } }) {
|
export async function GET({ params: { uuid } }) {
|
||||||
const uploadFile = await Uploads.getClientAvatarById(uuid);
|
const uploadFile = await Uploads.getClientAvatarById(uuid);
|
||||||
if (!uploadFile) {
|
if (!uploadFile) {
|
||||||
return new Response(Uploads.clientFallbackImage, {
|
const fallback = await Uploads.getClientFallback();
|
||||||
|
return new Response(fallback, {
|
||||||
status: 200,
|
status: 200,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'image/png'
|
'Content-Type': 'image/png'
|
||||||
|
Loading…
Reference in New Issue
Block a user