refactor(frontend): centralize configurable auth/session values in config.ts
This commit is contained in:
@@ -6,13 +6,12 @@ import {
|
||||
sessionCookieOptions,
|
||||
SESSION_COOKIE
|
||||
} from '$lib/server/auth';
|
||||
|
||||
const REFRESH_THRESHOLD_SECONDS = 30;
|
||||
import { authConfig } from '$lib/server/config';
|
||||
|
||||
export const handle: Handle = async ({ event, resolve }) => {
|
||||
let session = await decodeSession(event.cookies.get(SESSION_COOKIE));
|
||||
|
||||
if (session && session.expiresAt - REFRESH_THRESHOLD_SECONDS <= Math.floor(Date.now() / 1000)) {
|
||||
if (session && session.expiresAt - authConfig.refreshThresholdSeconds <= Math.floor(Date.now() / 1000)) {
|
||||
session = await refreshSession(session);
|
||||
if (session) {
|
||||
event.cookies.set(SESSION_COOKIE, await encodeSession(session), sessionCookieOptions(event.url));
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as client from 'openid-client';
|
||||
import { EncryptJWT, jwtDecrypt } from 'jose';
|
||||
import { env } from '$env/dynamic/private';
|
||||
import { authConfig } from '$lib/server/config';
|
||||
import type { SessionUser } from '$lib/types';
|
||||
|
||||
export const SESSION_COOKIE = 'mf_session';
|
||||
@@ -16,40 +16,27 @@ export interface Session extends SessionUser {
|
||||
expiresAt: number;
|
||||
}
|
||||
|
||||
export const OIDC_SCOPE = 'openid profile email offline_access';
|
||||
|
||||
let configPromise: Promise<client.Configuration> | null = null;
|
||||
|
||||
export function getOidcConfig(): Promise<client.Configuration> {
|
||||
configPromise ??= (async () => {
|
||||
const issuer = new URL(env.ZITADEL_ISSUER);
|
||||
const issuer = new URL(authConfig.issuer);
|
||||
const options =
|
||||
issuer.protocol === 'http:' ? { execute: [client.allowInsecureRequests] } : undefined;
|
||||
|
||||
const secret = env.ZITADEL_CLIENT_SECRET;
|
||||
const hasSecret = !!secret && !secret.startsWith('REPLACE_');
|
||||
|
||||
return hasSecret
|
||||
? client.discovery(issuer, env.ZITADEL_CLIENT_ID, secret, undefined, options)
|
||||
: client.discovery(issuer, env.ZITADEL_CLIENT_ID, undefined, client.None(), options);
|
||||
const secret = authConfig.clientSecret;
|
||||
return secret
|
||||
? client.discovery(issuer, authConfig.clientId, secret, undefined, options)
|
||||
: client.discovery(issuer, authConfig.clientId, undefined, client.None(), options);
|
||||
})();
|
||||
return configPromise;
|
||||
}
|
||||
|
||||
export const authConfig = {
|
||||
get redirectUri() {
|
||||
return env.AUTH_REDIRECT_URI;
|
||||
},
|
||||
get postLogoutUri() {
|
||||
return env.AUTH_POST_LOGOUT_URI;
|
||||
}
|
||||
};
|
||||
|
||||
let keyPromise: Promise<Uint8Array> | null = null;
|
||||
|
||||
function sessionKey(): Promise<Uint8Array> {
|
||||
keyPromise ??= crypto.subtle
|
||||
.digest('SHA-256', new TextEncoder().encode(env.SESSION_SECRET))
|
||||
.digest('SHA-256', new TextEncoder().encode(authConfig.sessionSecret))
|
||||
.then((digest) => new Uint8Array(digest));
|
||||
return keyPromise;
|
||||
}
|
||||
@@ -60,15 +47,16 @@ export function sessionCookieOptions(url: URL) {
|
||||
sameSite: 'lax' as const,
|
||||
secure: url.protocol === 'https:',
|
||||
path: '/',
|
||||
maxAge: 60 * 60 * 24 * 7
|
||||
maxAge: authConfig.sessionTtlSeconds
|
||||
};
|
||||
}
|
||||
|
||||
export async function encodeSession(session: Session): Promise<string> {
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
return new EncryptJWT({ ...session })
|
||||
.setProtectedHeader({ alg: 'dir', enc: 'A256GCM' })
|
||||
.setIssuedAt()
|
||||
.setExpirationTime('7d')
|
||||
.setIssuedAt(now)
|
||||
.setExpirationTime(now + authConfig.sessionTtlSeconds)
|
||||
.encrypt(await sessionKey());
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { env } from '$env/dynamic/private';
|
||||
|
||||
const DAY_SECONDS = 60 * 60 * 24;
|
||||
const DEFAULT_OIDC_SCOPE = 'openid profile email offline_access';
|
||||
|
||||
function positiveInt(value: string | undefined, fallback: number): number {
|
||||
if (value === undefined || value === '') return fallback;
|
||||
const parsed = Number(value);
|
||||
return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
|
||||
}
|
||||
|
||||
export const authConfig = {
|
||||
get issuer() {
|
||||
return env.ZITADEL_ISSUER;
|
||||
},
|
||||
get clientId() {
|
||||
return env.ZITADEL_CLIENT_ID;
|
||||
},
|
||||
get clientSecret(): string | undefined {
|
||||
const secret = env.ZITADEL_CLIENT_SECRET;
|
||||
return secret && !secret.startsWith('REPLACE_') ? secret : undefined;
|
||||
},
|
||||
get scope() {
|
||||
return env.OIDC_SCOPE || DEFAULT_OIDC_SCOPE;
|
||||
},
|
||||
get redirectUri() {
|
||||
return env.AUTH_REDIRECT_URI;
|
||||
},
|
||||
get postLogoutUri() {
|
||||
return env.AUTH_POST_LOGOUT_URI;
|
||||
},
|
||||
get sessionSecret() {
|
||||
return env.SESSION_SECRET;
|
||||
},
|
||||
get sessionTtlSeconds() {
|
||||
return positiveInt(env.SESSION_TTL_SECONDS, 7 * DAY_SECONDS);
|
||||
},
|
||||
get refreshThresholdSeconds() {
|
||||
return positiveInt(env.SESSION_REFRESH_THRESHOLD_SECONDS, 30);
|
||||
},
|
||||
get flowTtlSeconds() {
|
||||
return positiveInt(env.AUTH_FLOW_TTL_SECONDS, 600);
|
||||
}
|
||||
};
|
||||
@@ -3,13 +3,12 @@ import * as client from 'openid-client';
|
||||
import type { RequestHandler } from './$types';
|
||||
import {
|
||||
getOidcConfig,
|
||||
authConfig,
|
||||
OIDC_SCOPE,
|
||||
STATE_COOKIE,
|
||||
VERIFIER_COOKIE,
|
||||
NONCE_COOKIE,
|
||||
RETURN_COOKIE
|
||||
} from '$lib/server/auth';
|
||||
import { authConfig } from '$lib/server/config';
|
||||
|
||||
export const GET: RequestHandler = async ({ url, cookies }) => {
|
||||
const config = await getOidcConfig();
|
||||
@@ -24,7 +23,7 @@ export const GET: RequestHandler = async ({ url, cookies }) => {
|
||||
|
||||
const parameters: Record<string, string> = {
|
||||
redirect_uri: authConfig.redirectUri,
|
||||
scope: OIDC_SCOPE,
|
||||
scope: authConfig.scope,
|
||||
code_challenge: codeChallenge,
|
||||
code_challenge_method: 'S256',
|
||||
state,
|
||||
@@ -39,7 +38,7 @@ export const GET: RequestHandler = async ({ url, cookies }) => {
|
||||
sameSite: 'lax' as const,
|
||||
secure: url.protocol === 'https:',
|
||||
path: '/',
|
||||
maxAge: 600
|
||||
maxAge: authConfig.flowTtlSeconds
|
||||
};
|
||||
cookies.set(VERIFIER_COOKIE, codeVerifier, cookieOptions);
|
||||
cookies.set(STATE_COOKIE, state, cookieOptions);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import * as client from 'openid-client';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { getOidcConfig, authConfig, decodeSession, SESSION_COOKIE } from '$lib/server/auth';
|
||||
import { getOidcConfig, decodeSession, SESSION_COOKIE } from '$lib/server/auth';
|
||||
import { authConfig } from '$lib/server/config';
|
||||
|
||||
const handleLogout: RequestHandler = async ({ cookies }) => {
|
||||
const session = await decodeSession(cookies.get(SESSION_COOKIE));
|
||||
|
||||
Reference in New Issue
Block a user