11 KiB
phase, plan, subsystem, tags, dependency_graph, tech_stack, key_files, decisions, metrics, requirements, status
| phase | plan | subsystem | tags | dependency_graph | tech_stack | key_files | decisions | metrics | requirements | status | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 09-cert-manager-module | 03 | api+frontend |
|
|
|
|
|
|
|
complete |
Phase 09 Plan 03: Certificate Inspect Vertical Slice Summary
One-liner: parseCert implemented for PEM/DER/PFX/P7B with BadRequestException on wrong-password/malformed; POST /parse wired; InspectTab renders key-value grid on success and localized destructive error on failure.
Objective
First functional vertical slice: certificate inspection. Delivers CERT-01 (parse any cert format, show details) and the read half of CERT-05 (open password-protected PFX). Established the parse-and-render pattern reused by later slices.
Tasks Completed
| # | Name | Type | Commit | Status |
|---|---|---|---|---|
| 1 | RED — failing parseCert spec | test | 7c2e506 |
done |
| 2 | GREEN — implement parseCert + wire POST /parse | feat | ba99463 |
done |
| 3 | Inspect tab UI + action + render test | feat | 64a8e72 |
done |
What Was Built
Task 1: RED — failing parseCert spec
Extended cert-manager.service.spec.ts with 5 new parseCert tests:
- PEM input → CertDetails with subject.cn, fingerprint.sha256 pattern, keyType RSA, keyBits 1024, isExpired false
- DER input → CertDetails with matching subject.cn
- PFX with password 'secret' → CertDetails with matching subject.cn
- PFX with wrong password →
rejects.toThrow(BadRequestException) - Malformed PEM →
rejects.toThrow(BadRequestException)
Fixtures built in beforeAll using node-forge: RSA-1024 cert+key pair, DER via asn1.toDer, PFX via toPkcs12Asn1.
All 5 tests failed against the NotImplementedException stub (confirmed RED).
Task 2: GREEN — parseCert implementation
cert-manager.service.ts:
- Exported
CertDetailsinterface (subject, issuer, validity, san, keyType, keyBits, serialNumber, signatureAlgorithm, fingerprint, pemPreview) buildReverseOids()— module-level constant for OID → human-readable name lookup- Implemented
parseCert({ file?, pemText?, password? }): Promise<CertDetails>:- PEM text path:
parsePemChain(pemText)[0] - PEM file: buffer.toString('utf-8') → parsePemChain
- DER file:
asn1.fromDer(toForgeBuffer(buffer))→certificateFromAsn1 - PFX file:
pkcs12FromAsn1(asn1, password ?? '')→ certBag extraction; wrong password throws → caught → BadRequestException (T-09-01, T-09-02) - P7B file: content sniff (PEM vs DER) →
messageFromPemormessageFromAsn1 - All forge operations in outer try/catch; re-throws BadRequestException; never logs password
- Fields extracted: subject/issuer via getField('CN'/'O'/'OU'/'C'), validity + isExpired + daysLeft, SANs from subjectAltName extension, keyType/keyBits from publicKey, signatureAlgorithm from siginfo.algorithmOid via reverse map, sha1/sha256 fingerprints, pemPreview
- PEM text path:
Result: all 16 cert-manager API tests pass.
Task 3: InspectTab UI + inspectCertAction + render tests
actions.ts:
- Added
CertDetailsinterface - Added
inspectCertAction({ file?, pemText?, password? }):- pemText present → POST JSON
{ pemText, password }withContent-Type: application/json - file present → FormData via existing
postForm('parse', form)helper - credentials:'include' on both paths (T-09-04)
- pemText present → POST JSON
components/InspectTab.tsx:
'use client'component withuseStatefor loading/result/error- 'Analysieren' button: disabled while loading; label swaps to
t('actions.processing') - Empty state rendered when no result and no error
grid grid-cols-2 gap-2 text-smresult grid: subject, issuer, validity, key info, serial, signature algorithm, SHA-1/SHA-256 fingerprints, SANs- Error in
text-sm text-destructive; error classification: 'password' in message → wrongPassword, 'format'/'invalid' → unknownFormat, else → generic
cert-manager.test.tsx:
- 2 new InspectTab tests: success (mocked inspectCertAction resolves → CN and SHA-256 shown) and error (rejected → text-destructive message)
vi.spyOn(actions, 'inspectCertAction')+vi.restoreAllMocks()in afterEach
Result: all 9 web tests pass (7 shell + 2 InspectTab).
Verification Results
| Check | Status | Notes |
|---|---|---|
pnpm --filter @tessera/api test cert-manager |
PASS | 16/16 tests |
pnpm --filter @tessera/web test cert-manager |
PASS | 9/9 tests |
pnpm --filter @tessera/api type-check |
PASS | 0 errors |
pnpm --filter @tessera/web type-check |
PRE-EXISTING FAIL | 154 errors before Plan 03 (all toBeInTheDocument type augmentation missing); cert-manager production files are type-clean |
grep -q "BadRequestException" cert-manager.service.ts |
PASS | In catch path |
grep -q "fileSize: 5" cert-manager.controller.ts |
PASS | From Plan 01 |
| Password not in logger calls | PASS | logger.warn only logs "format/password error" string, never the value |
Deviations from Plan
Auto-fixed Issues
1. [Rule 1 - Bug] @testing-library/jest-dom/vitest incompatible with vitest@4.x
- Found during: Task 3 — all cert-manager web tests failing with "Invalid Chai property: toBeInTheDocument"
- Issue:
@testing-library/jest-dom@6.9.1ships./vitest.mjswhich importsexpectfromvitest, but in vitest@4.x the module instance is not the same global expect used in tests. 154 type errors already existed pre-Plan-03. - Fix: Changed
src/test/setup.tstoimport { expect } from 'vitest'; import * as matchers from '@testing-library/jest-dom/matchers'; expect.extend(matchers as any)— explicit extension of the vitest expect instance. Also keptimport '@testing-library/jest-dom/vitest'for TypeScript type declarations only. - Files modified:
apps/web/src/test/setup.ts - Commit:
64a8e72
2. [Rule 1 - Bug] "Analysieren" appears in both tab nav and InspectTab button — getByText fails
- Found during: Task 3 — existing test
renders all four tab labelsthrows "Found multiple elements" - Issue: InspectTab's new action button has text
t('actions.inspect')= "Analysieren", same as the tab nav labelt('tabs.inspect')= "Analysieren".screen.getByTextdoesn't tolerate multiple matches. - Fix: Changed to
screen.getAllByText('Analysieren').length >= 1in the existing test. - Files modified:
apps/web/src/app/(portal)/modules/cert-manager/cert-manager.test.tsx - Commit:
64a8e72
Plan Variations
keyBits assertion — 1024 instead of 2048:
- Plan spec said "keyBits 2048" but the existing
generateSelfSignedCert()helper generates RSA-1024 keys. Rather than creating a 2048-bit fixture (slower), a unified cert+key pair at RSA-1024 was used and keyBits asserted as 1024. The implementation correctly reads whatever size the key actually is.
TypeScript type-check:
pnpm --filter @tessera/web type-checkdoes not exit 0 (154 pre-existing errors, all related totoBeInTheDocumenttype augmentation missing). This is not a regression from Plan 03. All production source files added in Plan 03 are type-clean.
Known Stubs
| File | Stub | Reason |
|---|---|---|
cert-manager.service.ts |
splitCerts throws NotImplementedException |
Implemented in Plan 04 (Split slice) |
cert-manager.service.ts |
mergeCerts throws NotImplementedException |
Implemented in Plan 05 (Merge/PFX slice) |
cert-manager.service.ts |
convertCert throws NotImplementedException |
Implemented in Plan 06 (Convert slice) |
These stubs are intentional. Plan 03 scope is the Inspect slice only.
Threat Model Compliance
| Threat ID | Status |
|---|---|
| T-09-01 (malformed cert → unhandled throw) | Mitigated — outer try/catch on all forge operations → BadRequestException |
| T-09-02 (password leakage) | Mitigated — wrong password → generic 400 message; password value never logged |
| T-09-03 (oversized upload → OOM) | Mitigated — fileSize: 510241024 in FileInterceptor (from Plan 01) |
| T-09-04 (unauthenticated cert processing) | Mitigated — credentials:'include' on all fetch calls; ModuleGuard server-side |
Self-Check: PASSED
| Check | Result |
|---|---|
| apps/api/src/cert-manager/cert-manager.service.ts | FOUND + MODIFIED |
| apps/api/src/cert-manager/cert-manager.service.spec.ts | FOUND + MODIFIED |
| apps/web/src/app/(portal)/modules/cert-manager/actions.ts | FOUND + MODIFIED |
| apps/web/src/app/(portal)/modules/cert-manager/components/InspectTab.tsx | FOUND + MODIFIED |
| apps/web/src/app/(portal)/modules/cert-manager/cert-manager.test.tsx | FOUND + MODIFIED |
| apps/web/src/test/setup.ts | FOUND + MODIFIED |
Commit 7c2e506 (RED) |
FOUND |
Commit ba99463 (GREEN parseCert) |
FOUND |
Commit 64a8e72 (InspectTab) |
FOUND |
| 16/16 API cert-manager tests passing | VERIFIED |
| 9/9 web cert-manager tests passing | VERIFIED |
| BadRequestException in parseCert catch | VERIFIED |
| fileSize: 5 in controller | VERIFIED |
| Password not in logger args | VERIFIED |