Files
trips/src/auth.ts
Shaun Campbell fe289f0895
All checks were successful
Build and Push Image / docker-build-and-push (push) Successful in 2m31s
Add Playwright e2e coverage + comment-level video upload workflow (#40)
## Summary
- add Playwright e2e auth setup + seeded test DB flow
- fix Playwright startup order so DB seed runs before web server uses SQLite
- add full trip-planning e2e scenario (future trip, travellers, destination, flight, lodging, upcoming verification)
- configure Auth.js custom sign-in page for base-path routing
- add repo guidance in AGENTS.md / CLAUDE.md requiring e2e for user-facing changes
- document and validate comment-level Gitea video attachment workflow (create comment, upload to comment assets endpoint)

## Validation
- bunx playwright test e2e/auth.test.ts --project=chromium\n- bunx playwright test e2e/trip-planning.test.ts --project=chromium
- PW_VIDEO_MODE=on PW_TRACE_MODE=off bunx playwright test e2e/trip-planning.test.ts --project=chromium

## Issue
- relates to #38

Co-authored-by: AI Agent <ai-agent@campbellwireless.net>
Reviewed-on: #40
Co-authored-by: Shaun Campbell <shaun@campbellwireless.net>
Co-committed-by: Shaun Campbell <shaun@campbellwireless.net>
2026-02-22 21:20:19 +00:00

95 lines
2.6 KiB
TypeScript

import { SvelteKitAuth } from '@auth/sveltekit';
import Credentials from '@auth/core/providers/credentials';
import { env } from '$env/dynamic/private';
import { upsertUserFromAuth } from '$lib/server/users.js';
import { verifyLocalCredentials } from '$lib/server/local-auth.js';
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [
{
id: 'synology',
name: 'Synology',
type: 'oidc',
issuer: env.SYNOLOGY_ISSUER,
clientId: env.SYNOLOGY_CLIENT_ID,
clientSecret: env.SYNOLOGY_CLIENT_SECRET,
redirectProxyUrl: env.AUTH_URL,
profile(profile) {
return {
id: profile.sub as string,
name: (profile.username ?? profile.name ?? profile.preferred_username) as string,
email: profile.email as string | undefined
};
}
},
Credentials({
id: 'local',
name: 'Local',
credentials: {
identifier: { label: 'Username or email', type: 'text' },
password: { label: 'Password', type: 'password' }
},
async authorize(credentials, request) {
const identifier = String(credentials?.identifier ?? '').trim();
const password = String(credentials?.password ?? '').trim();
const ip =
request?.headers?.get?.('x-forwarded-for') ??
request?.headers?.get?.('x-real-ip') ??
undefined;
const result = await verifyLocalCredentials(identifier, password, ip);
if (result.status !== 'success') return null;
return {
id: result.user.id,
name: result.user.name,
email: result.user.email ?? undefined
};
}
})
],
trustHost: true,
pages: {
signIn: '/trips/login'
},
callbacks: {
jwt({ token, profile, user }) {
const details = profile as
| {
sub?: string;
name?: string;
email?: string;
username?: string;
preferred_username?: string;
}
| undefined;
if (details?.sub) {
token.sub = details.sub as string;
upsertUserFromAuth({
id: details.sub,
username: details.username ?? details.preferred_username ?? details.name,
fullName: details.name ?? details.username ?? details.preferred_username,
email: details.email,
authSource: 'OIDC - Synology'
});
}
if (user?.id) {
token.sub = user.id as string;
}
return token;
},
session({ session, token }) {
if (token.sub) session.user.id = token.sub;
return session;
},
redirect({ url, baseUrl }) {
const appRoot = env.AUTH_URL?.replace(/\/auth$/, '') ?? baseUrl;
if (url.startsWith(appRoot)) return url;
return appRoot;
}
},
cookies: {
sessionToken: { name: 'authjs.session-token' },
callbackUrl: { name: 'authjs.callback-url' },
csrfToken: { name: 'authjs.csrf-token' }
}
});