import { Injectable, NotFoundException } from '@nestjs/common'; import { PrismaService } from '../prisma/prisma.service'; import { EncryptionService } from './encryption.service'; import { CreateCredentialDto } from './dto/create-credential.dto'; import { UpdateCredentialDto } from './dto/update-credential.dto'; @Injectable() export class CredentialsService { constructor( private prisma: PrismaService, private encryption: EncryptionService, ) {} findAll() { return this.prisma.credential.findMany({ include: { sshKey: { select: { id: true, name: true, keyType: true, fingerprint: true } } }, orderBy: { name: 'asc' }, }); } async findOne(id: number) { const cred = await this.prisma.credential.findUnique({ where: { id }, include: { sshKey: true, hosts: { select: { id: true, name: true } } }, }); if (!cred) throw new NotFoundException(`Credential ${id} not found`); return cred; } create(dto: CreateCredentialDto) { const encryptedValue = dto.password ? this.encryption.encrypt(dto.password) : null; return this.prisma.credential.create({ data: { name: dto.name, username: dto.username, domain: dto.domain, type: dto.type, encryptedValue, sshKeyId: dto.sshKeyId, }, }); } async update(id: number, dto: UpdateCredentialDto) { await this.findOne(id); const data: any = { ...dto }; delete data.password; if (dto.password) { data.encryptedValue = this.encryption.encrypt(dto.password); } return this.prisma.credential.update({ where: { id }, data }); } async remove(id: number) { await this.findOne(id); return this.prisma.credential.delete({ where: { id } }); } /** Decrypt credential for use in SSH/RDP connections. Never expose over API. */ async decryptForConnection(id: number): Promise<{ username: string | null; domain: string | null; password: string | null; sshKey: { privateKey: string; passphrase: string | null } | null; }> { const cred = await this.prisma.credential.findUnique({ where: { id }, include: { sshKey: true }, }); if (!cred) throw new NotFoundException(`Credential ${id} not found`); let password: string | null = null; if (cred.encryptedValue) { password = this.encryption.decrypt(cred.encryptedValue); } let sshKey: { privateKey: string; passphrase: string | null } | null = null; if (cred.sshKey) { const privateKey = this.encryption.decrypt(cred.sshKey.encryptedPrivateKey); const passphrase = cred.sshKey.passphraseEncrypted ? this.encryption.decrypt(cred.sshKey.passphraseEncrypted) : null; sshKey = { privateKey, passphrase }; } else if (cred.sshKeyId) { // Orphaned reference — credential points to a deleted/missing SSH key throw new NotFoundException( `Credential "${cred.name}" references SSH key #${cred.sshKeyId} which no longer exists. Re-import the key or update the credential.`, ); } if (!password && !sshKey) { throw new NotFoundException( `Credential "${cred.name}" has no password or SSH key configured.`, ); } return { username: cred.username, domain: cred.domain, password, sshKey }; } }