Some checks failed
CI / Unit & Component Tests (push) Successful in 2m7s
CI / Backend Unit Tests (push) Successful in 2m3s
CI / E2E Tests (push) Failing after 14m54s
CI / Unit & Component Tests (pull_request) Successful in 2m4s
CI / E2E Tests (pull_request) Has been cancelled
CI / Backend Unit Tests (pull_request) Has been cancelled
- /forgot-password: email form → sends POST /api/auth/forgot-password → success banner - /reset-password: password form reads token from URL → sends POST /api/auth/reset-password - Login page: add "Passwort vergessen?" link - hooks.server.ts: add /forgot-password and /reset-password to PUBLIC_PATHS; skip auth injection for public auth API endpoints - errors.ts: add INVALID_RESET_TOKEN error code - i18n: add all new message keys in de/en/es - playwright.config.ts: use E2E_BASE_URL for webServer check URL (allows reusing docker dev server at port 5173 locally) - ci.yml: pass E2E_BACKEND_URL=http://localhost:8080 to E2E test step - e2e/password-reset.spec.ts: 5 tests (4 pass locally, full flow requires e2e profile in CI) - Regenerated OpenAPI types including new /api/auth/* endpoints Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
104 lines
3.0 KiB
TypeScript
104 lines
3.0 KiB
TypeScript
import { redirect, type Handle, type HandleFetch } from '@sveltejs/kit';
|
|
import { paraglideMiddleware } from '$lib/paraglide/server';
|
|
import { sequence } from '@sveltejs/kit/hooks';
|
|
import { env } from 'process';
|
|
import { cookieName, cookieMaxAge } from '$lib/paraglide/runtime';
|
|
import { detectLocale } from '$lib/server/locale';
|
|
|
|
const PUBLIC_PATHS = ['/login', '/logout', '/forgot-password', '/reset-password'];
|
|
|
|
const handleLocaleDetection: Handle = ({ event, resolve }) => {
|
|
if (!event.cookies.get(cookieName)) {
|
|
const locale = detectLocale(event.request.headers.get('accept-language') ?? '');
|
|
if (locale) {
|
|
event.cookies.set(cookieName, locale, {
|
|
path: '/',
|
|
sameSite: 'lax',
|
|
maxAge: cookieMaxAge,
|
|
httpOnly: false
|
|
});
|
|
}
|
|
}
|
|
return resolve(event);
|
|
};
|
|
|
|
const handleAuth: Handle = async ({ event, resolve }) => {
|
|
const isPublic = PUBLIC_PATHS.some((p) => event.url.pathname.startsWith(p));
|
|
if (!isPublic && !event.locals.user) {
|
|
throw redirect(302, '/login');
|
|
}
|
|
return resolve(event);
|
|
};
|
|
|
|
const handleParaglide: Handle = ({ event, resolve }) =>
|
|
paraglideMiddleware(event.request, ({ request, locale }) => {
|
|
event.request = request;
|
|
|
|
return resolve(event, {
|
|
transformPageChunk: ({ html }) => html.replace('%paraglide.lang%', locale)
|
|
});
|
|
});
|
|
|
|
const userGroup: Handle = async ({ event, resolve }) => {
|
|
const auth = event.cookies.get('auth_token');
|
|
|
|
if (auth) {
|
|
try {
|
|
const apiUrl = env.API_INTERNAL_URL || 'http://localhost:8080';
|
|
const response = await fetch(`${apiUrl}/api/users/me`, {
|
|
headers: { Authorization: auth }
|
|
});
|
|
if (response.ok) {
|
|
const user = await response.json();
|
|
event.locals.user = user;
|
|
}
|
|
} catch (error) {
|
|
console.error('Error fetching user in hook:', error);
|
|
}
|
|
}
|
|
|
|
return resolve(event);
|
|
};
|
|
|
|
export const handleFetch: HandleFetch = async ({ event, request, fetch }) => {
|
|
const apiUrl = env.API_INTERNAL_URL || 'http://localhost:8080';
|
|
const isApi = request.url.startsWith(apiUrl) || request.url.includes('/api/');
|
|
|
|
if (isApi) {
|
|
// If the request already carries an explicit Authorization header (e.g. the
|
|
// login action sends Basic auth), pass it through unchanged.
|
|
if (request.headers.has('Authorization')) {
|
|
return fetch(request);
|
|
}
|
|
|
|
// Password reset endpoints are public — no auth header needed.
|
|
const PUBLIC_API_PATHS = ['/api/auth/forgot-password', '/api/auth/reset-password'];
|
|
if (PUBLIC_API_PATHS.some((p) => request.url.includes(p))) {
|
|
return fetch(request);
|
|
}
|
|
|
|
const token = event.cookies.get('auth_token');
|
|
|
|
if (!token) {
|
|
return new Response('Unauthorized', { status: 401 });
|
|
}
|
|
|
|
// Clone the request first to preserve the body
|
|
const clonedRequest = request.clone();
|
|
|
|
// Create new request with Authorization header and preserved body
|
|
const modifiedRequest = new Request(clonedRequest, {
|
|
headers: {
|
|
...Object.fromEntries(clonedRequest.headers),
|
|
Authorization: token
|
|
}
|
|
});
|
|
|
|
return fetch(modifiedRequest);
|
|
}
|
|
|
|
return fetch(request);
|
|
};
|
|
|
|
export const handle = sequence(userGroup, handleAuth, handleLocaleDetection, handleParaglide);
|