Self-hosted dashboard for the Spanish digital certificates (FNMT / Cl@ve style) that agencies and firms hold for their clients. It watches certificate folders — local or on SMB/CIFS network shares — reads expiry dates, groups certificates by holder identity, and sends renewal reminders by email + calendar invite (.ics).
LAN-only by design: no outbound calls except to the SMTP server you configure and to the SMB/CIFS file servers an admin explicitly adds in the UI.
- Folder watcher — drop a certificate into the watched folder and it appears in the UI within a second (chokidar, with hot-restart when the folder changes). An unreadable directory is treated as unknown state, never as empty: the catalog is never pruned because a share blinked.
- Modern PKCS#12 parsing —
.p12/.pfxfiles as exported by OpenSSL 3.x / current FNMT (PBES2 + AES-256) are parsed by delegating to the OpenSSL CLI with a-legacyfallback for old RC2/3DES bundles. Passwords are passed via environment, never argv. Also reads PEM, DER and.cer/.crt(detecting DER disguised as PEM). - Identity grouping — certificates are grouped by holder (DNI/NIE extracted from the subject, e.g. FNMT's
IDCES-…serial numbers). Superseded versions of the same holder are collapsed under the newest one, and automatic alerts only fire for the current version — no false alarms for certificates you already renewed. - PKCS#12 password from filename — configurable regex; by default the password is read between parentheses:
NAME_NIF (PASSWORD).p12. A per-certificate manual override is also available (stored AES-256-GCM encrypted, with rollback if the new password breaks a previously parsing certificate, and throttled to keep the endpoint from acting as a password oracle). - SMB/CIFS network shares — discover, mount and watch shares from the UI. Credentials are stored AES-256-GCM encrypted (scrypt-derived key); at mount time they are written as
0600files on a RAM-only tmpfs and deleted in afinally. Mount options are whitelisted, child processes get a minimal environment, and every log line is scrubbed of secrets. An optionalSMB_ALLOWED_CIDRSegress allowlist is available. - Expiry scanning + automatic digest — a daily cron flags certificates expiring within configurable warning/critical windows, and an automatic email digest (grouped by severity, deduplicated so the same alarm is not repeated every day) keeps the team informed.
- Renewal scheduling — pick a date and the server emails an
.icsinvite; reminders retry with exponential backoff, distinguishing permanent SMTP failures from transient ones. Bulk.icsexport for up to 200 certificates at once. - Realtime UI — an authenticated WebSocket pushes certificate changes to the dashboard as they happen (session-cookie auth reused in the upgrade handshake, ping/pong heartbeat, client reconnection with backoff).
- Local users — bcrypt + session cookie. Login is protected by a three-bucket brute-force throttle designed so an attacker exhausts their own budget before they can lock out the victim, plus timing padding on failures and session regeneration after login. The server refuses to boot with a weak or default
SESSION_SECRET, and the first-admin bootstrap is closed behind a token by default. - Safe deletion — deleting a certificate archives the file into a
.trash/folder; the server never unlinks certificate material. - Single Docker image — Express 5 serves both the API and the built Angular SPA on one port. Optional nginx reverse-proxy profiles (HTTP or TLS) and a systemd autostart installer are included, plus scripts to bundle the image + data for turnkey delivery to an offline client server.
cp .env.example .env
# Edit .env — at minimum set SESSION_SECRET (32+ chars; the server refuses weak secrets).
docker compose up -dOpen http://your-server:4000, create the first (admin) account, then configure SMTP under Settings.
Drop certificates into ./data/certificates/ on the host — they show up in the dashboard within a second.
The default regex is \(([^()]+)\)(?:_\d+)?\.(?:p12|pfx)$ — the password is taken from the parentheses in the filename. Example: maria_lopez_12345678Z (MyP4ss).p12 uses password MyP4ss. Change the pattern in Settings → Certificates or via the P12_PASSWORD_REGEX env var, or set a per-certificate override from the certificate's detail view.
pnpm install
pnpm dev # api on :4000, Angular dev server on :4200 (proxied to api)pnpm demo:start builds and starts the app with a realistic synthetic FNMT-style dataset (18 generated certificates covering multi-version holders, representation certificates and a wrong-password case) — useful for walkthroughs. All demo data is generated locally by scripts/gen-test-certs.ts; nothing real is included.
server/ Express 5 + better-sqlite3 + chokidar + node-cron + nodemailer (ESM, TypeScript)
web/ Angular 21 standalone components + signals + Tailwind v4
scripts/ Demo dataset generator, client-delivery bundling, autostart installer
deploy/ Optional nginx reverse-proxy configs (HTTP / TLS)
data/ Bind-mounted at runtime — SQLite DB and the certificates/ folder
- Sessions live in SQLite; the cookie is
httpOnly+sameSite=lax. Put a TLS-terminating reverse proxy in front if you serve beyond a trusted LAN (a ready-made nginx TLS profile is included). - PKCS#12 filename passwords are used to parse metadata. A manual per-certificate password override, when set by an admin, is persisted encrypted (AES-256-GCM under a key derived from
CREDENTIAL_ENCRYPTION_KEY); the certificate-install endpoint that returns it is admin-only and audited. - SMB credentials are stored encrypted and never reach argv, logs or child-process environments.
- No telemetry. Outbound traffic is limited to your configured SMTP server and explicitly added SMB/CIFS servers.