Files
familienarchiv/frontend/src/routes/login/+page.server.ts
Marcel 48c8bb8a5f
Some checks failed
CI / Unit & Component Tests (push) Failing after 2m48s
CI / OCR Service Tests (push) Successful in 17s
CI / Backend Unit Tests (push) Successful in 4m10s
CI / fail2ban Regex (push) Successful in 38s
CI / Compose Bucket Idempotency (push) Successful in 56s
fixup: address Nora's review on #520 (security blockers)
- frontend/login: derive cookie `secure` flag from request URL protocol.
  Pre-PR the cookie was only read by SSR so the flag didn't matter; now
  the cookie IS the API credential and must be Secure on HTTPS or it
  leaks a 24h Basic token on plaintext networks. Dev runs over HTTP and
  would silently lose the cookie if we hardcoded `secure: true`, so the
  flag follows `event.url.protocol === 'https:'`.

- SecurityConfig: rewrite the CSRF-disabled comment. The old
  "browsers block cross-origin custom headers" justification no longer
  holds once /api/* is authenticated via the cookie. Make the
  load-bearing dependencies explicit: SameSite=strict on the auth_token
  cookie + Spring's default CORS rejection.

- AuthTokenCookieFilter:
  - Scope to /api/* only. /actuator/health and similar must not be
    cookie-authenticated.
  - Refuse malformed percent-encoding (URLDecoder throws); forward the
    request without a promoted Authorization rather than crash.
  - Use isBlank() instead of isEmpty() per Nora.
  - Javadoc warning: getHeaderNames/getHeaders exposes the Basic
    credential; any future header-iterating logger must scrub
    Authorization before logging.

- Tests: add `passes_through_unchanged_when_request_is_outside_api_scope`
  (/actuator/health with cookie should NOT be wrapped) and
  `passes_through_unchanged_when_cookie_value_is_malformed_percent_encoding`.
  Tighten the explicit-header test to verify same-instance forwarding
  rather than just header equality.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 18:20:10 +02:00

61 lines
2.0 KiB
TypeScript

import { fail, redirect, type Actions } from '@sveltejs/kit';
import { env } from '$env/dynamic/private';
import { getErrorMessage } from '$lib/shared/errors';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = ({ url }) => {
return { registered: url.searchParams.get('registered') === '1' };
};
export const actions = {
login: async ({ request, cookies, fetch, url }) => {
const data = await request.formData();
const email = data.get('email') as string;
const password = data.get('password') as string;
if (!email || !password) {
return fail(400, { error: getErrorMessage('MISSING_CREDENTIALS') });
}
const credentials = btoa(`${email}:${password}`);
const authHeader = `Basic ${credentials}`;
// Raw fetch is intentional here: we need to pass an explicit Authorization
// header built from the form data, not the cookie-based auth used elsewhere.
try {
const baseUrl = env.API_INTERNAL_URL || 'http://localhost:8080';
const response = await fetch(`${baseUrl}/api/users/me`, {
method: 'GET',
headers: { Authorization: authHeader }
});
if (response.status === 401 || response.status === 403) {
return fail(401, { error: getErrorMessage('UNAUTHORIZED') });
}
if (!response.ok) {
return fail(500, { error: getErrorMessage('INTERNAL_ERROR') });
}
// The cookie IS the API credential — promoted to `Authorization: Basic …`
// on every browser → backend request by AuthTokenCookieFilter on the
// Spring side (see #520). It must be Secure on HTTPS or it leaks
// a 24h Basic token on plaintext networks. Dev runs over HTTP and
// would silently lose the cookie if we hardcoded secure=true.
const isHttps = url.protocol === 'https:';
cookies.set('auth_token', authHeader, {
path: '/',
httpOnly: true,
sameSite: 'strict',
secure: isHttps,
maxAge: 60 * 60 * 24
});
} catch (e) {
console.error(e);
return fail(500, { error: getErrorMessage('INTERNAL_ERROR') });
}
return redirect(303, '/');
}
} satisfies Actions;