oidc error correction
This commit is contained in:
parent
ab93c53c82
commit
9f27775da9
20
HISTORY.md
20
HISTORY.md
|
|
@ -1,17 +1,5 @@
|
||||||
# Bill Tracker — Changelog
|
# 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
|
## v0.35.0
|
||||||
|
|
||||||
### 🔧 Changed
|
### 🔧 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.
|
- **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
|
||||||
|
|
||||||
|

|
||||||
---
|
---
|
||||||
|
|
||||||
## v0.34.3
|
## 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.
|
- **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
|
||||||
|
|
||||||
|

|
||||||
---
|
---
|
||||||
|
|
||||||
## v0.34.2
|
## v0.34.2
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,12 @@ router.get('/login', async (req, res) => {
|
||||||
const authUrl = await buildAuthorizationUrl(config, state);
|
const authUrl = await buildAuthorizationUrl(config, state);
|
||||||
res.redirect(authUrl);
|
res.redirect(authUrl);
|
||||||
} catch (err) {
|
} 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.' });
|
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 || '/');
|
res.redirect(savedState.redirect_to || '/');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Log message only — never log tokens, codes, or ID token contents
|
// 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';
|
const errCode = err.status === 403 ? 'access_denied' : 'authentication_failed';
|
||||||
res.redirect(`/?oidc_error=${errCode}`);
|
res.redirect(`/?oidc_error=${errCode}`);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue