diff --git a/README.md b/README.md index 59dde4a..7578a80 100644 --- a/README.md +++ b/README.md @@ -2,14 +2,20 @@ This is a SvelteKit-powered authentication service. +## Requirements + +1. A MySQL or MariaDB database. +2. Node.js 20+ or Docker. + ## Set up -1. Install dependenices - `npm install`. -2. Configure the environment - `cp .env.example .env`. -3. Generate secrets and stuff: +1. Clone the repository. +2. Install dependenices - `npm install`. +3. Configure the environment - `cp .env.example .env`. +4. Generate secrets and stuff: 1. Session secret - `node -e 'console.log(require("crypto").randomBytes(16).toString("hex"))'`. 2. Challenge secret - `node -e 'console.log(require("crypto").randomBytes(32).toString("hex"))'`. 3. Generate JWT keys in the `private` directory - `openssl genpkey -out jwt.private.pem -algorithm RSA -pkeyopt rsa_keygen_bits:2048`. 4. Also make the public key - `openssl rsa -in jwt.private.pem -pubout -outform PEM -out jwt.public.pem`. -4. Build the application - `npm run build`. -5. Run the application - `node -r dotenv/config build`. +5. Build the application - `npm run build`. +6. Run the application - `node -r dotenv/config build`. diff --git a/src/hooks.server.ts b/src/hooks.server.ts index a45179d..4febc45 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -11,19 +11,26 @@ import { handleSession } from 'svelte-kit-cookie-session'; const { AUTO_MIGRATE, SESSION_SECRET, SESSION_SECURE } = env; +// Initialize the database await DB.init(); + +// Initialize the JWT keys await JWT.init(); +// Migrate the database when automatic migration is enabled if (AUTO_MIGRATE === 'true') { await migrate(DB.drizzle, { migrationsFolder: './migrations' }); } +// Run database data seeders await runSeeds(); +/** + * Set the document theme mode from the cookies + */ const handleThemeHook = (async ({ resolve, event }) => { // Take the theme from the cookies const cookieTheme = event.cookies.get('themeMode') as ThemeModeType; - if (cookieTheme) { return await resolve(event, { transformPageChunk: ({ html }) => html.replace('theme-base=""', `theme-base="${cookieTheme}"`) diff --git a/src/lib/server/jwt.ts b/src/lib/server/jwt.ts index 9da7123..c5e70d7 100644 --- a/src/lib/server/jwt.ts +++ b/src/lib/server/jwt.ts @@ -26,14 +26,21 @@ export class JWT { static jwksKid: string; static async init() { - const privateKeyFile = await readFile(join('private', 'jwt.private.pem'), { - encoding: 'utf-8' - }); - const publicKeyFile = await readFile(join('private', 'jwt.public.pem'), { encoding: 'utf-8' }); - JWT.privateKey = await importPKCS8(privateKeyFile, JWT_ALGORITHM); - JWT.publicKey = await importSPKI(publicKeyFile, JWT_ALGORITHM); - JWT.jwks = await exportJWK(JWT.publicKey); - JWT.jwksKid = uuidv4({ random: Buffer.from(JWT.jwks.n as string).subarray(0, 16) }); + try { + const privateKeyFile = await readFile(join('private', 'jwt.private.pem'), { + encoding: 'utf-8' + }); + const publicKeyFile = await readFile(join('private', 'jwt.public.pem'), { + encoding: 'utf-8' + }); + JWT.privateKey = await importPKCS8(privateKeyFile, JWT_ALGORITHM); + JWT.publicKey = await importSPKI(publicKeyFile, JWT_ALGORITHM); + JWT.jwks = await exportJWK(JWT.publicKey); + JWT.jwksKid = uuidv4({ random: Buffer.from(JWT.jwks.n as string).subarray(0, 16) }); + } catch (error) { + console.error('Failed to initialize the JWT backend:', error); + console.error('OpenID Connect flows will not work!'); + } } static async issue(claims: Record, subject: string, audience?: string) { diff --git a/src/lib/theme-mode.ts b/src/lib/theme-mode.ts index a1e41ad..038a6d1 100644 --- a/src/lib/theme-mode.ts +++ b/src/lib/theme-mode.ts @@ -18,25 +18,31 @@ export const themeMode = writable('light', (set) => { export const setThemeCookie = (theme: ThemeModeType) => (document.cookie = `themeMode=${theme}; path=/; SameSite=Lax; Max-Age=${THEME_COOKIE_MAX_AGE}`); +/** + * Forward the initial theme to the theme store, and subscribe to changes. + */ export const forwardInitialTheme = () => onMount(() => { // If a cookie-set theme is not present, set the theme to current user agent theme. const hasServerTheme = !!document.documentElement.getAttribute('theme-base'); const uaTheme = window?.matchMedia?.('(prefers-color-scheme: dark)'); const uaMode: ThemeModeType = uaTheme?.matches ? 'dark' : 'light'; - const uaSetter = () => { - themeMode.set(uaTheme?.matches ? 'dark' : 'light'); - }; if (!hasServerTheme) { themeMode.set(uaMode); } + // Set the theme to the current theme store value, and set the cookie. const unsubscribeForward = themeMode.subscribe(async (theme) => { document.documentElement.setAttribute('theme-base', theme); setThemeCookie(theme); }); + // Set the theme to the user agent theme when the user agent theme changes. + const uaSetter = () => { + themeMode.set(uaTheme?.matches ? 'dark' : 'light'); + }; + uaTheme?.addEventListener?.('change', uaSetter); return () => { unsubscribeForward();