import { Injectable, BadRequestException, ConflictException, UnauthorizedException, NotFoundException, } from '@nestjs/common' import { AdminsRepository } from './admins.repository' import { CreateAdminDto, CreateAdminRequestDto } from './dto/create-admin.dto' import { Admin } from './classes/admin.class' import * as firebaseAdmin from 'firebase-admin' import { AdminRole, canUserCreateSuperAdmin, getSuperAdminACLKey, canUserCreateNationalAdmin, canUserCreatePrefectureAdmin, getPrefectureAdminACLKey, getNationalAdminACLKey, canUserAccessResource, } from '../shared/acl' import { RequestAdminUser } from '../shared/interfaces' import { PrefecturesService } from '../prefectures/prefectures.service' import { FirebaseService } from '../shared/firebase/firebase.service' @Injectable() export class AdminsService { constructor( private adminsRepository: AdminsRepository, private prefecturesService: PrefecturesService, private firebaseService: FirebaseService ) {} async createOneAdminUser( requestAdminUser: RequestAdminUser, createAdminRequest: CreateAdminRequestDto ): Promise<void> { // Check if an admin already exists with this email. const adminExists = await this.adminsRepository.findOneByEmail(createAdminRequest.email) if (adminExists) { throw new ConflictException('An admin with this email already exists') } // Start preparing the create admin object. It will be passed to the repo function. const createAdminDto: CreateAdminDto = new CreateAdminDto() createAdminDto.email = createAdminRequest.email createAdminDto.addedByAdminUserId = requestAdminUser.uid createAdminDto.addedByAdminEmail = requestAdminUser.email createAdminDto.userAdminRole = createAdminRequest.adminRole createAdminDto.accessControlList = [getSuperAdminACLKey()] // Check if the user has access to create new user with desired adminRole in the payload. // Also, determine what accessKey will be added to the new created admin. switch (createAdminRequest.adminRole) { case AdminRole.superAdminRole: if (!canUserCreateSuperAdmin(requestAdminUser.userAccessKey)) { throw new UnauthorizedException('Insufficient access to create this adminRole') } createAdminDto.userAccessKey = getSuperAdminACLKey() // No need to add any ACL Key in accessControlList, since it already contains the // superAdmin key added above. break case AdminRole.nationalAdminRole: if (!canUserCreateNationalAdmin(requestAdminUser.userAccessKey)) { throw new UnauthorizedException('Insufficient access to create this adminRole') } throw new UnauthorizedException('WIP. nationalAdminRole not supported yet') case AdminRole.prefectureAdminRole: if ( !canUserCreatePrefectureAdmin( requestAdminUser.userAccessKey, createAdminRequest.prefectureId ) ) { throw new UnauthorizedException('Insufficient access to create this adminRole') } // Check if prefectureId is valid const isPrefectureCodeValid = await this.prefecturesService.isPrefectureCodeValid( createAdminRequest.prefectureId ) if (!isPrefectureCodeValid) { throw new BadRequestException('Invalid prefectureId value') } createAdminDto.userAccessKey = getPrefectureAdminACLKey(createAdminRequest.prefectureId) createAdminDto.prefectureId = createAdminRequest.prefectureId createAdminDto.accessControlList.push( getNationalAdminACLKey(), getPrefectureAdminACLKey(createAdminRequest.prefectureId) ) break default: throw new BadRequestException('Invalid adminRole value') } let firebaseUserRecord: firebaseAdmin.auth.UserRecord try { firebaseUserRecord = await firebaseAdmin.auth().createUser({ email: createAdminRequest.email, emailVerified: false, disabled: false, }) } catch (error) { throw new BadRequestException(error.message) } createAdminDto.adminUserId = firebaseUserRecord.uid return this.adminsRepository.createOne(createAdminDto) } async getOneAdminById(requestAdminUser: RequestAdminUser, adminId: string): Promise<Admin> { // Fetch resource and perform ACL check. const admin = await this.adminsRepository.findOneById(adminId) if (!admin) { throw new NotFoundException('Could not find admin with this id') } if (!canUserAccessResource(requestAdminUser.userAccessKey, admin)) { throw new UnauthorizedException('User does not have access on this resource') } return admin } /** * Fetches one admin by adminId. * Internal functions do not perform any ACL checks and should be used carefully. * @param adminId: string */ async findOneAdminByIdInternal(adminId: string): Promise<Admin | undefined> { return this.adminsRepository.findOneById(adminId) } async findAllAdminUsers( requestAdminUser: RequestAdminUser, limit: number, offset: number ): Promise<Admin[]> { // ACL check is automatically performed in the repository function. return this.adminsRepository.findAll(requestAdminUser.userAccessKey, limit, offset) } async deleteOneAdminById(requestAdminUser: RequestAdminUser, adminId: string): Promise<void> { // Fetch resource and perform ACL check. Check performed within the called function. await this.getOneAdminById(requestAdminUser, adminId) // Delete admin in Firestore admins collection. await this.adminsRepository.deleteOneById(adminId) // Delete admin in Firebase auth. await this.firebaseService.DeleteFirebaseUser(adminId) } }