When a credential's sshKeyId points to a deleted/missing SSH key row, the connection attempt silently had zero auth methods. Now throws a clear error explaining the SSH key is missing. Also catches the case where a credential has neither password nor SSH key configured. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
100 lines
3.2 KiB
TypeScript
100 lines
3.2 KiB
TypeScript
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 };
|
|
}
|
|
}
|