oidc error correction

This commit is contained in:
null 2026-05-31 16:08:24 -05:00
parent ab93c53c82
commit 9f27775da9
2 changed files with 20 additions and 14 deletions

View File

@ -1,17 +1,5 @@
# Bill Tracker — Changelog
## v0.36.0
### 🔧 Changed
- **Bump**`0.35.0``0.36.0`
### 🔒 Security
- **CSRF token moved out of readable cookie** — The CSRF cookie previously defaulted to `httpOnly: false` so the SPA could read it from `document.cookie`. Any XSS vulnerability could steal the token from there and bypass CSRF protection entirely. The cookie is now `httpOnly: true` by default, removing it from the XSS-accessible cookie surface. The SPA instead fetches the token once from `GET /api/auth/csrf-token` on startup and stores it in a module-level memory cache; all mutations continue to send it in the `x-csrf-token` header unchanged. The server-side double-submit validation (`header == cookie`) is identical. `CSRF_HTTP_ONLY=false` remains available in `.env` for compatibility, but is no longer the default.
---
## v0.35.0
### 🔧 Changed
@ -24,6 +12,11 @@
- **HKDF key derivation with automatic migration**`encryptionService.js` previously derived the AES-256-GCM key from raw input via `SHA-256(ikm)`, which lacks domain separation and offers no protection if a low-entropy passphrase is supplied. Replaced with **HKDF-SHA-256** (RFC 5869) using info label `bill-tracker-token-encryption-v1`. New ciphertext carries a `v2:` prefix; `decryptSecret` uses it to choose the correct derivation path, so legacy and new ciphertext coexist transparently. DB migration `v0.78` re-encrypts all existing secrets (`data_sources.encrypted_secret` and `notify_smtp_password`) to the v2 format on first startup — no manual action required.
- **CSRF token moved out of readable cookie** — The CSRF cookie previously defaulted to `httpOnly: false` so the SPA could read it from `document.cookie`. Any XSS vulnerability could steal the token from there and bypass CSRF protection entirely. The cookie is now `httpOnly: true` by default, removing it from the XSS-accessible cookie surface. The SPA instead fetches the token once from `GET /api/auth/csrf-token` on startup and stores it in a module-level memory cache; all mutations continue to send it in the `x-csrf-token` header unchanged. The server-side double-submit validation (`header == cookie`) is identical. `CSRF_HTTP_ONLY=false` remains available in `.env` for compatibility, but is no longer the default.
### Release Image
![Doing my part](/img/doingmypart.jpg)
---
## v0.34.3
@ -70,6 +63,9 @@
- **Monetary aggregation rounding hardened** — Floating-point rounding was already applied per-payment in `statusService`, `billsService`, and `payments.js`, but the aggregation layer was unprotected: `reduce()` sums and subtraction results in `trackerService`, `routes/summary.js`, and `routes/monthly-starting-amounts.js` were returned without rounding, allowing IEEE 754 artifacts (e.g. `12.10 - 0.20 = 12.100000000000001`) to leak into API responses. All computed monetary aggregates (`total_paid`, `total_expected`, `paid_toward_due`, `remaining`, `total_remaining`, `overdue`, `combined_amount`, `*_remaining`, `expense_total`, `result`, etc.) are now passed through `Math.round(x * 100) / 100` before being returned. `roundMoney` is also now exported from `statusService` so other modules can share the same implementation instead of re-implementing it.
### Release Image
![Doing my part](/img/doingmypart.jpg)
---
## v0.34.2

View File

@ -40,7 +40,12 @@ router.get('/login', async (req, res) => {
const authUrl = await buildAuthorizationUrl(config, state);
res.redirect(authUrl);
} catch (err) {
console.error('[oidc] Login initiation error:', err.message);
const msg = err.message || String(err);
console.error('[oidc] Login initiation error:', msg || '(no message)');
if (err.errors) {
err.errors.forEach((e, i) => console.error(` [${i}]`, e.message || String(e)));
}
if (err.cause) console.error(' cause:', err.cause.message || String(err.cause));
res.status(502).json({ error: 'Failed to reach the identity provider. Please try again.' });
}
});
@ -98,7 +103,12 @@ router.get('/callback', async (req, res) => {
res.redirect(savedState.redirect_to || '/');
} catch (err) {
// Log message only — never log tokens, codes, or ID token contents
console.error('[oidc] Callback error:', err.message);
const msg = err.message || String(err);
console.error('[oidc] Callback error:', msg || '(no message)');
if (err.errors) {
err.errors.forEach((e, i) => console.error(` [${i}]`, e.message || String(e)));
}
if (err.cause) console.error(' cause:', err.cause.message || String(err.cause));
const errCode = err.status === 403 ? 'access_denied' : 'authentication_failed';
res.redirect(`/?oidc_error=${errCode}`);
}