import { Injectable } from '@nestjs/common'; import { IdentityAuthDevice } from '@prisma/client'; import { PrismaService } from './prisma.service'; import { Identity } from '../domain/identity.types'; import { safeJsonParse } from '../utils'; import * as Joi from 'joi'; const eligibleForTwoFactor: Identity.AuthDevice.Type[] = [ ] @Injectable() export class IdentityAuthDeviceDao { constructor( private readonly prismaService: PrismaService, ) {} async findByRealmAndUsername(realm: string, username: string): Promise { const devices = await this.prismaService.identityAuthDevice.findMany({ where: { user: { username: username.toLowerCase(), realm: { name: realm.toLowerCase(), }, }, }, }); return devices.map(d => IdentityAuthDeviceDao.modelToEntity(d)); } async findOneByUrn(urn: string): Promise { const device = await this.prismaService.identityAuthDevice.findFirst({ where: { id: urn.replace('urn:identity:auth-device:', ''), }, }); if (!device) { return null; } return IdentityAuthDeviceDao.modelToEntity(device); } private static modelToEntity(model: IdentityAuthDevice, skipAttributes = false): Identity.AuthDevice { const entity: Identity.AuthDevice = { urn: `urn:identity:auth-device:${model.id}`, userId: model.userId, deviceType: model.deviceType as Identity.AuthDevice.Type, preferred: model.preferred, twoFactorEligible: eligibleForTwoFactor.includes(model.deviceType as Identity.AuthDevice.Type), createdAt: new Date(model.createdAt), } if (skipAttributes || !model.attributes) { return entity; } if (model.deviceType === Identity.AuthDevice.Type.Password) { const [_, attributes] = safeJsonParse(model.attributes); const { error, value } = Joi.object({ expiry: Joi.date().required(), passwordHashString: Joi.string().required(), locked: Joi.boolean().required(), }).validate(attributes); if (error) { return entity; } return { ...entity, ...value, } } return entity; } }