import type { CertificateDto } from "@mmapp/contracts/certificates" export type RosCertificateRow = Record function parseRosDate(raw: string | undefined): Date | null { if (!raw?.trim()) return null const d = new Date(raw) return Number.isNaN(d.getTime()) ? null : d } function fmtDate(d: Date): string { return d.toISOString().slice(0, 10) } function parseSans(raw: string | undefined): string[] { if (!raw?.trim()) return [] return raw .split(",") .map((part) => part.trim()) .map((part) => { const m = part.match(/^(?:DNS|IP|email):(.+)$/i) return (m?.[1] ?? part).trim() }) .filter(Boolean) } function parseUsage(raw: string | undefined): CertificateDto["usage"] { if (!raw?.trim()) return [] const out = new Set() for (const part of raw.split(",")) { const token = part.trim().toLowerCase() if (token === "tls-server") out.add("server") else if (token === "tls-client") out.add("client") else if (token === "key-cert-sign") out.add("ca") else if (token === "crl-sign") out.add("crl-sign") } return [...out] } function parseFlags(raw: string | undefined): { revoked: boolean; expired: boolean } { const flags = (raw ?? "").toUpperCase() return { revoked: flags.includes("R"), expired: flags.includes("E"), } } export function mapRosCertificateRow( serverId: number, serverName: string, row: RosCertificateRow, ): CertificateDto | null { const name = (row.name ?? "").trim() if (!name) return null const rosId = (row[".id"] ?? "").trim() const fingerprint = (row.fingerprint ?? "").trim() const serialNumber = (row["serial-number"] ?? "").trim() const stableId = rosId || fingerprint || serialNumber || name const { revoked, expired: expiredFlag } = parseFlags(row.flags) const validFrom = parseRosDate(row["invalid-before"]) const validUntil = parseRosDate(row["invalid-after"]) const now = new Date() const daysLeft = validUntil ? Math.ceil((validUntil.getTime() - now.getTime()) / 86_400_000) : 0 let status: CertificateDto["status"] = "valid" if (revoked) status = "revoked" else if (expiredFlag || (validUntil != null && validUntil.getTime() < now.getTime())) status = "expired" const keySize = Number.parseInt(String(row["key-size"] ?? "0"), 10) return { id: `${serverId}:${stableId}`, name, serverId: String(serverId), serverName, commonName: (row["common-name"] ?? name).trim(), sans: parseSans(row["subject-alt-name"]), issuedBy: (row.issuer ?? "—").trim() || "—", validFrom: validFrom ? fmtDate(validFrom) : "—", validUntil: validUntil ? fmtDate(validUntil) : "—", daysLeft, keySize: Number.isFinite(keySize) ? keySize : 0, usage: parseUsage(row["key-usage"]), trusted: row.trusted === "true", status, acmeStatus: row["acme-status"]?.trim() || undefined, } } export function mapRosCertificates( serverId: number, serverName: string, rows: RosCertificateRow[], ): CertificateDto[] { const seen = new Set() return rows .map((row) => mapRosCertificateRow(serverId, serverName, row)) .filter((row): row is CertificateDto => row != null) .map((cert, index) => { if (!seen.has(cert.id)) { seen.add(cert.id) return cert } const uniqueId = `${cert.id}#${index}` seen.add(uniqueId) return { ...cert, id: uniqueId } }) }