import argon2 from "argon2"; import {first} from "lodash"; import {sql} from "bun"; export class Claims { userId: string | undefined; claims: string[] = []; public static test(userClaims: Claims, guardClaim: string): Boolean { return userClaims.claims.some(x => x === guardClaim); } } class ClaimsOrm { async getByUserId(userId: string): Promise { const dbResults: any[] = await sql`SELECT c.name from user_claims as uc JOIN claims as c on uc.claimid = c.id where uc.userid = ${userId};`; const claims = new Claims(); claims.userId = userId; claims.claims = dbResults.map(x => x.name); return claims; } async getDefaultClaims(): Promise { const dbResults: any[] = await sql`SELECT id FROM claims WHERE is_default = true;`; return dbResults.map(x => x.id); } } class User { id: string; name: string; isAdmin: boolean; isActive: boolean; constructor(id: string, name: string, isAdmin: boolean = false, isActive: boolean = true) { this.id = id; this.name = name; this.isAdmin = isAdmin; this.isActive = isActive; } } class UsersOrm { #claims: ClaimsOrm; constructor(claims: ClaimsOrm) { this.#claims = claims; } async get(id: string, claims: Claims): Promise { if (!( Claims.test(claims, 'ADMIN') || Claims.test(claims, 'USERS_OTHER_READ') || (Claims.test(claims, 'USERS_SELF_READ') && id === claims.userId) )) { throw new Error('Unauthorized'); } const dbResult: any = first(await sql`select * from users where id = ${id} and is_active = true limit 1`); return new User(dbResult.id, dbResult.username, dbResult.is_admin); } async verify(username: string, password: string): Promise { try { const dbResult: any = first(await sql`select * from users where username = ${username} limit 1`); if (!await argon2.verify(dbResult.pass_hash, password)) { return null; } return this.#claims.getByUserId(dbResult.id); } catch (error) { console.log(error); throw error; } } async create(username: string, password: string, claims: Claims): Promise { const existingUser: any = first(await sql`SELECT id FROM users WHERE username = ${username} LIMIT 1`); if (existingUser) { throw new Error(`User with id ${existingUser.id} already exists`); } const defaultClaims: number[] = await this.#claims.getDefaultClaims(); const passwordHash = await argon2.hash(password); await sql`INSERT INTO users (username, pass_hash) VALUES (${username}, ${passwordHash})`; const newUserId: string = (first(await sql`SELECT lastval();`) as any)?.lastval as string; await sql.transaction(async (tx) => { for (let i in defaultClaims) { await tx`INSERT INTO user_claims (userid, claimid) VALUES (${newUserId}, ${defaultClaims[i]})`; } }) return await this.get(newUserId, claims); } } class Orm { claims: ClaimsOrm; users: UsersOrm; constructor() { this.claims = new ClaimsOrm(); this.users = new UsersOrm(this.claims); } } export const orm = new Orm();