# Changelog

## v1.6.2 — Secure Link Reliability (May 11, 2026)

### Highlights

- **Fixes the "download failed" symptom on emailed PDF links.** Encrypted PDF and
  file-asset downloads through the new Portal wrapper completed on the server but
  the browser rejected the response as truncated. Root cause was an incorrect
  `Content-Length` header on encrypted assets; the wrapper now downloads
  immediately and correctly.
- **Emailed links survive automated link scanners.** Microsoft Safe Links,
  Mimecast, Proofpoint URL Defense, Barracuda, Slack/Teams unfurl bots and similar
  pre-fetchers no longer burn the recipient's download credit before they click.
- **Raised default link caps** so a real human always has clicks left even if a
  scanner manages to slip through the filter.

### Fixes

- `FormAdminPdfServiceImpl.buildAssetResponse(...)` no longer sends a
  `Content-Length` header for encrypted assets. The stored `sizeBytes` is the
  on-disk ciphertext size (`nonce + ciphertext + GCM tag`), but the response body
  is the decrypted plaintext, which is 28 bytes shorter. Sending the ciphertext
  size caused browsers and `fetch`/`XHR` clients to wait for bytes that never
  arrived and ultimately treat the response as truncated, surfacing as a generic
  "download failed" in the Portal wrapper while the backend audit recorded
  `result=SUCCESS`. Encrypted assets are now streamed chunked; unencrypted assets
  still send an accurate `Content-Length`.
- `IdObfuscationService.decode(...)` now logs the "this isn't an obfuscated form
  ID" fallback at `DEBUG` instead of `WARN`. The previous
  `WARN "Failed to decode token — invalid or tampered: Tag mismatch!"` line was a
  false alarm fired on every emailed-link request (which carries a JTI, not an
  obfuscated ID) and was easily mistaken for a security event.

### Hardening

- `FormAdminPdfServiceImpl.consumeEmailTokenDownload(...)` now skips the
  download counter when the inbound request is an HTTP `HEAD` probe or carries a
  User-Agent matching a known email-security URL scanner (Safe Links, Mimecast,
  Proofpoint `urldefense`, Barracuda, Forcepoint, Symantec, TrendMicro, Cisco,
  Sophos, Slackbot link expansion, Teams/Skype URL preview, BingPreview,
  GoogleImageProxy, `facebookexternalhit`). Real human downloads from a browser
  with the bearer token continue to increment normally and audit as `SUCCESS`.
- Skipped scanner probes log
  `Skipping download counter for jti=... - request looks like an automated link-scanner probe`
  so they are still observable in operations logs without consuming a credit.

### Configuration

- `cbm.email.link.standard.max-downloads`: default raised **2 → 5**.
- `cbm.email.link.sensitive.max-downloads`: default raised **1 → 3**.
- TTLs are unchanged (`standard: 24h`, `sensitive: 12h`).
- Both `application.yml` and `application-stage.yml` ship the new defaults; no
  environment variables are required.

### CORS

- `WebConfiguration` now exposes `Content-Disposition`, `Content-Length`,
  `Content-Type`, and `X-Correlation-Id` to browser JavaScript. This lets the
  Portal "Secure Download" wrapper read the suggested filename and the
  correlation id from the response, which previously required a non-standard
  proxy or a guessed filename.

### Operator notes

- After upgrading, **the first time a recipient clicks an emailed link** the
  per-token cap is governed by whichever max-downloads value was in effect when
  the token was issued. Tokens issued before this release still carry the old
  caps (2 / 1) until they expire naturally. New tokens issued after upgrade get
  the new caps (5 / 3).
- No database migration is required.
- Rolling back to v1.6.1 simply restores the old `max-downloads` defaults and
  re-enables the `Content-Length` over-report; the download bug returns. Prefer
  forward-fix.

## v1.6.1 — Secure Link Portal Wrapper (May 11, 2026)

### Highlights

- **Email links now route through the CBM Portal.** Form notification links open a
  short Portal "Secure Download" page that handles authentication and then triggers
  the backend download on the user's behalf. Users no longer see a raw `401` if they
  click a link before signing in — they are prompted, then automatically returned to
  the same secure download URL.
- **No new backend endpoints.** The Portal wrapper continues to call the existing
  `GET /api/v1/admin/forms/{formType}/{formToken}/pdf` (and `uploads.zip` /
  `files/{fileCategory}`) endpoints with the user's bearer token.

### Configuration

- New, optional `cbm.email.link.frontend-base-url` property. When set, emailed links
  target the Portal wrapper at
  `{frontend-base-url}{frontend-path-prefix}/{formType}/{formToken}[/...]`.
- New, optional `cbm.email.link.frontend-path-prefix` property. Defaults to
  `/secure/forms`.
- When `frontend-base-url` is unset (e.g. for rollback), links continue to point at
  the backend endpoint exactly as in v1.6.0 — no breaking change.
- Dev profile now defaults to `http://localhost:3000/secure/forms` so local testing
  exercises the wrapper flow end-to-end.

### Fixes

- Work Ticket submissions no longer require `currentLocation`; the field has been
  removed from `WorkTicketRequestDTO` and the controller/mapper/PDF template paths
  that referenced it.
- Hardened JSON parsing for the work-ticket request body. `Store.workTickets` and
  `District.store` are now read-only for JSON, which prevents Jackson from
  recursing into nested back-references when a payload includes the full store
  graph.
- Escaped reflected error messages in `FinanceSubmissionController.badRequest(...)`
  before they are placed into `ApiError`, neutralizing a reflected XSS sink on the
  finance submit endpoints.

### Notes for frontend / consumers

- See `SECURE_LINK_FRONTEND_WRAPPER.md` for the full wrapper outline (new
  `/secure/forms/...` routes, plain-JS download helper, login `returnTo` handling).
- See `OFFICE_USER_GUIDE_PDF_ACCESS.md` v1.6.1 for the updated end-user instructions.
- No change to the encrypted form tokens, the OTP flow, or the storage-encryption
  model introduced in v1.6.0.

---

## v1.6.0 — Secure Link Delivery (May 10, 2026)

### Highlights

- **Default email delivery is now `LINK`.** Form notification emails carry a secure portal link to the PDF instead of attaching it. Attachments remain available as a break-glass fallback only.
- **`NewHire` is now a link-only secure package.** Recipients receive a single link that opens a package page with the PDF, an uploads ZIP, and per-file downloads for sensitive items (`ID_BADGE`, `GOV_ID_FRONT`, `GOV_ID_REAR`, `SSN_CARD`).
- **OTP step-up for sensitive items.** Before downloading PII-bearing files, the user verifies a one-time code sent to their CBM inbox. The grant is short-lived and scoped to the package — it is not a login session.
- **Storage encryption at rest.** All stored PDFs and `NewHire` uploads are envelope-encrypted (AES-256-GCM per-file DEK, wrapped by an Azure Key Vault KEK). Files on disk are ciphertext only; decryption happens in-memory while streaming to an authenticated user.
- **Auditing and observability.** Every token validation, OTP request/verify, download, and unwrap failure is recorded. A scheduled cleanup job purges expired tokens and ages out audit rows per retention policy.

### Configuration

- `cbm.email.delivery.mode` now defaults to `LINK` in `application.yml` and `application-stage.yml`. `DUAL` and `ATTACHMENT` remain available for rollback or operational fallback.
- `cbm.security.encryption.provider` is `AZURE_KEY_VAULT` in stage; `LOCAL` only in `dev` / `test`.
- Reuses the existing Microsoft app registration credentials for Azure Key Vault access. The sanitized `credentialSource` signal is exposed on `/api/v1/health/detailed`.

### API additions (under `/api/v1/admin/forms/{formType}/{formToken}`)

- `GET /uploads.zip` — Stream the uploads-only ZIP for a `NewHire` package.
- `GET /files/{fileCategory}` — Stream a specific sensitive file.
- `POST /otp/request` — Issue a one-time code to the authenticated principal's CBM inbox.
- `POST /otp/verify` — Verify the code and open a short, package-scoped grant window.

Existing endpoints (`/pdf`, `/resend-email`) keep the same shape. `resend-email` is now considered an operational fallback.

### Operational

- One-time provisioning steps are in `deployment/KEY_VAULT_SETUP.md`.
- Day-2 procedures (rotation, kill-switch, restore, backfill) are in `deployment/KEY_VAULT_RUNBOOK.md`.
- Health endpoints `/api/v1/health` and `/api/v1/health/detailed` continue to back deployment and rollback scripts.

### Notes for frontend / consumers

- All invalid, expired, revoked, and exhausted token cases return `404`. Missing OTP step-up returns `403`. Rate limit returns `429`. The shared `ErrorResponse` contract is unchanged.
- `formToken` remains an opaque encrypted string in URL paths — do not parse or store decoded forms.
- The form submission response (`formId`, `pdfDownloadUrl`, `emailStatus`) is unchanged.
- See `FRONTEND_LINK_DELIVERY_PHASED_IMPLEMENTATION.md` for the phased frontend rollout.

---

## Unreleased

### Security / Infrastructure

- Added Phase 1 scaffolding for tokenized email-link delivery: new Flyway migrations, domain entities, repositories, and service interfaces for file assets, access tokens, audits, and OTP challenges.
- Added Phase 1.5 storage confidentiality scaffolding for envelope encryption at rest, including local KEK support, Azure Key Vault provider integration points, KEK re-wrap job skeleton, and sanitized encryption health visibility.
- Added operator documentation for Azure Key Vault setup and day-2 runbook procedures under `deployment/KEY_VAULT_SETUP.md` and `deployment/KEY_VAULT_RUNBOOK.md`.
- Switched the default delivery mode to `LINK` in app and stage configuration, with `ATTACHMENT` / `DUAL` retained only for rollback or break-glass scenarios.
- Reused the existing Microsoft app registration credentials for Azure Key Vault access and exposed a sanitized `credentialSource` signal on `/api/v1/health/detailed`.
- Added the scheduled `TokenCleanupJob` to purge expired email access tokens after grace and old email access audits per retention policy.
- Removed the remaining `NewHire` server-path leak from generated HTML/PDF output and aligned dev defaults with link-based delivery.

### Email Link Rollout

- Completed the `NewHire` secure package flow with link-only delivery, uploads ZIP access, per-file download endpoints, and principal-bound OTP verification.
- Added admin endpoints for `POST /otp/request`, `POST /otp/verify`, `GET /uploads.zip`, and `GET /files/{fileCategory}` under `/api/v1/admin/forms/{formType}/{formToken}`.
- Added audit coverage for OTP requests/verifications, token-backed downloads, and unwrap failures to support rollout observability and alerting.
- Marked legacy attachment-sending paths as deprecated in code and documented them as fallback-only behavior.

## New Features

### PDF Download for Form Submissions
Admin and Office users can now download a PDF copy of any submitted form directly from the admin panel. A **Download PDF** button appears in both the forms table and the form detail modal.

### Email Status Visibility
Every form submission now displays its email notification status — **Sent**, **Failed**, or **Pending** — shown as a color-coded badge in the forms table and form detail view. After submitting a form, the confirmation page shows whether the email notification was delivered successfully.

### Resend Failed Emails
When an email notification fails to send, Admin and Office users can retry delivery with a **Resend Email** button. The button appears automatically next to any form with a "Failed" status, and the badge updates in real time after a successful resend.

---

## Improvements

- The forms table in the admin panel now includes an **Email Status** column for at-a-glance monitoring.
- The form detail modal now has an **actions bar** at the top for quick access to PDF download and email resend.
- Form submission responses now capture additional metadata (form ID, PDF URL, email status) for better traceability.

---

## Security Updates

- Applied security patches across all backend dependencies to address multiple known vulnerabilities, including fixes for authentication bypass, path traversal, and denial-of-service risks.
- Added **XSS protection** to all API error responses and user-submitted data to prevent cross-site scripting attacks.
- Store creation and update inputs are now sanitized before processing.
- Upgraded the core framework and all supporting libraries to the latest stable versions.
- Removed outdated or pre-release dependency pins in favor of vendor-supported releases.

---

## Bug Fixes

- Fixed test suite compatibility issues following the framework upgrade.
- Resolved an issue with redundant user account data that could cause unexpected behavior.
- Fixed a file-locking issue during PDF generation on Windows environments.
