export const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001'; // --------------------------------------------------------------------------- // CertDetails — mirrors the API response shape from CertManagerService // --------------------------------------------------------------------------- export interface CertDetails { subject: { cn: string; o: string; ou: string; c: string }; issuer: { cn: string; o: string; c: string }; validity: { notBefore: string; notAfter: string; isExpired: boolean; daysLeft: number }; san: string[]; keyType: string; keyBits: number; serialNumber: string; signatureAlgorithm: string; fingerprint: { sha1: string; sha256: string }; pemPreview: string; } // --------------------------------------------------------------------------- // inspectCertAction — calls POST /modules/cert-manager/parse // --------------------------------------------------------------------------- /** * Call POST /modules/cert-manager/parse. * - If pemText is present → JSON body { pemText, password } * - Otherwise → multipart FormData with file + optional password * * T-09-02: password is never placed in URL, logged, or echoed. * T-09-04: credentials:'include' ensures JWT cookie is sent. */ export async function inspectCertAction(input: { file?: File | null; pemText?: string; password?: string; }): Promise { const { file, pemText, password } = input; if (pemText) { // JSON path — content-type must be application/json (not multipart) const response = await fetch(`${API_URL}/modules/cert-manager/parse`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ pemText, password }), credentials: 'include', }); if (!response.ok) { const body = await response.text().catch(() => ''); throw new Error(`${response.status} ${body}`.trim()); } return response.json() as Promise; } else { // Multipart path — let browser set Content-Type with boundary const form = new FormData(); if (file) form.append('file', file); if (password) form.append('password', password); return postForm('parse', form) as Promise; } } /** * Download a base64-encoded file as a browser download. * T-09-02: password is never placed in URL, console.log, or filename. */ export function downloadBase64( filename: string, content: string, mimeType: string, ): void { const bytes = atob(content); const byteArray = new Uint8Array(bytes.length); for (let i = 0; i < bytes.length; i++) { byteArray[i] = bytes.charCodeAt(i); } const blob = new Blob([byteArray], { type: mimeType }); const url = URL.createObjectURL(blob); const anchor = document.createElement('a'); anchor.href = url; anchor.download = filename; anchor.click(); URL.revokeObjectURL(url); } /** * POST a FormData payload to a cert-manager endpoint. * T-09-04: credentials:'include' ensures JWT cookie is sent for ModuleGuard. * No manual Content-Type header — browser sets multipart boundary automatically. */ export async function postForm( endpoint: string, form: FormData, ): Promise { const response = await fetch( `${API_URL}/modules/cert-manager/${endpoint}`, { method: 'POST', body: form, credentials: 'include', }, ); if (!response.ok) { const body = await response.text().catch(() => ''); throw new Error(`${response.status} ${body}`.trim()); } return response.json(); }