All checks were successful
Build and Push Image / docker-build-and-push (push) Successful in 2m31s
## 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>
95 lines
2.6 KiB
TypeScript
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' }
|
|
}
|
|
});
|