Initial
This commit is contained in:
121
utilities/orm.ts
Normal file
121
utilities/orm.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
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<Claims> {
|
||||
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<number[]> {
|
||||
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<User> {
|
||||
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<Claims | null> {
|
||||
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<User> {
|
||||
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();
|
||||
Reference in New Issue
Block a user