import config from "@lib/config"; import TableService from "../TableService"; import { CUserContract } from "@lib/types/ContractTypes"; import { hashText } from "@lib/modules/auth/auth.service"; import { KeyPairType } from "@prisma/client"; import { UserErrors } from "@lib/vix/ClientErrors"; // prettier-ignore const generateBase64Password = (length: number = 32): string => Array.from(crypto.getRandomValues(new Uint8Array(length)), byte => 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.charAt(byte % 64)).join(''); export default class UsersTableService extends TableService { protected table = "User"; async byId(userId: string) { return this.pg.user.findUnique({ where: { id: userId }, include: { rolePolicy: true, project: true } }); } async byUsername(username: string, projectId: string) { return this.pg.user.findUnique({ where: { projectId_username: { projectId, username } } }); } async byEmail(email: string, projectId: string) { return this.pg.user.findUnique({ where: { projectId_email: { projectId, email } } }); } async $upsertDefaultRootUser() { const project = await this.pg.project.findUnique({ where: { slug: config.Server.projectSlug } }); if (!project) throw new Error("Cairo Project Not Found!"); const rolePolicyId = config.RolePolicy.Root.id; return this.$upsertRootUser(project.id, rolePolicyId); } async $upsertRootUser(projectId: string, rolePolicyId: string) { const root = await this.pg.user.findUnique({ where: { projectId_username: { username: "root", projectId } } }); if (!!root) return; const password = config.Server.rootPassword ?? generateBase64Password(); const hash = await hashText(password); const user = await this.pg.user.create({ data: { projectId, username: "root", email: "root", hash, rolePolicyId } }); return { ...user, password }; } async create(options: CUserContract["Create"]) { const { hash, projectId, rolePolicyId } = options; const username = options.username?.toLowerCase(); const email = options.email?.toLowerCase() ?? undefined; const [existingUsername, existingEmail] = await Promise.all([ this.byUsername(username, projectId), !!email ? this.byEmail(email, projectId) : undefined, ]); if (!existingUsername || !existingEmail) throw UserErrors.ConflictIdentityTaken; const userData = { projectId, username, email, hash, rolePolicyId }; return this.pg.user.create({ data: userData, include: { rolePolicy: true } }); } async byIdentity(projectIdentity: string, identity: string) { const username = identity.toLowerCase(); const email = identity.toLowerCase(); const OrUser = { OR: [{ username }, { email }] }; const OrProject = { project: { OR: [{ id: projectIdentity }, { slug: projectIdentity }] } }; const projectInclude = { include: { keyPairs: { where: { usage: KeyPairType.UserToken } } } }; const AND = [OrUser, OrProject]; return this.pg.user.findFirst({ where: { AND }, include: { rolePolicy: true, project: projectInclude } }); } }