diff options
Diffstat (limited to 'src/lib')
| -rw-r--r-- | src/lib/auth.server.ts | 36 | ||||
| -rw-r--r-- | src/lib/auth.ts | 37 | ||||
| -rw-r--r-- | src/lib/oncePromise.ts | 16 | ||||
| -rw-r--r-- | src/lib/util-types.ts | 4 |
4 files changed, 87 insertions, 6 deletions
diff --git a/src/lib/auth.server.ts b/src/lib/auth.server.ts index 77e0dd7..f762cef 100644 --- a/src/lib/auth.server.ts +++ b/src/lib/auth.server.ts @@ -2,6 +2,7 @@ import { env as env_priv } from '$env/dynamic/private'; import { env } from '$env/dynamic/public'; import * as client from 'openid-client'; import oncePromise from './oncePromise'; +import type { Cookies } from '@sveltejs/kit'; const server = new URL(env.PUBLIC_AUTH_KEYCLOAK_ISSUER); const clientId = env_priv.PRIVATE_AUTH_KEYCLOAK_ID; @@ -10,7 +11,10 @@ const redirectPath = '/login/callback'; // Only trigger discovery on first client.discovery (resetting the function after a failed discovery) export const getConfig = oncePromise(() => - client.discovery(server, clientId, clientSecret) + client.discovery(server, clientId, clientSecret).then((config) => { + client.useJwtResponseMode(config); + return config; + }) ); const codeVerifier = client.randomPKCECodeVerifier(); @@ -71,3 +75,33 @@ export const authorizeNewSession = async ( return tokens; }; + +export const unsetCookies = (cookies: Cookies) => { + for (const v of [ + 'oid__access_token', + 'oid__refresh_token', + 'oid__token_type', + 'oid__expires_at', + 'oid__scopes', + ]) + if (cookies.get(v)) cookies.delete(v, { path: '/' }); +}; +export const setCookies = ( + cookies: Cookies, + tokens: client.TokenEndpointResponse & client.TokenEndpointResponseHelpers +) => { + for (const [k, v] of Object.entries({ + oid__access_token: tokens.access_token, + oid__refresh_token: tokens.refresh_token, + oid__token_type: tokens.token_type, + oid__expires_at: '' + (Date.now() + (tokens.expiresIn() ?? 0) * 1000), + oid__scopes: tokens.scope, + })) + if (v) + cookies.set(k, v, { + path: '/', + secure: true, + httpOnly: true, + sameSite: true, + }); +}; diff --git a/src/lib/auth.ts b/src/lib/auth.ts new file mode 100644 index 0000000..dd6b043 --- /dev/null +++ b/src/lib/auth.ts @@ -0,0 +1,37 @@ +import { browser } from '$app/environment'; +import { base } from '$app/paths'; +import { redirect } from '@sveltejs/kit'; +import type { ClientSession } from '../hooks.server'; +import { goto } from '$app/navigation'; + +/** + * Returns `true` if scopes are all included in session, otherwise either attempts to re-login with the new scope added (unless `getScopeOnFail` is false) and returns false + * + * Check the return value of this, even if getScopeOnFail is true; navigating client-side may not stop thread immediately! + */ +export const checkScope = ( + session: ClientSession, + /** The scopes we want */ + neededScopes: string[], + /** Redirect to login page if the scopes aren't found */ + getScopeOnFail = false, + /** The target URL if redirecting */ + next?: string +) => { + const scopes = session.tokens.scope?.split(' ') ?? []; + if (!neededScopes.find((v) => !scopes.includes(v))) return true; + else if (getScopeOnFail) { + const targetUrl = `${base}/login?${ + next || browser + ? `next=${next ?? encodeURIComponent(location.href)}&` + : '' + }scope=${encodeURIComponent( + [...scopes, ...neededScopes] + .filter((v, i, a) => a.indexOf(v) === i) + .join(' ') + )}`; + if (browser) goto(targetUrl); + else throw redirect(307, targetUrl); + } + return false; +}; diff --git a/src/lib/oncePromise.ts b/src/lib/oncePromise.ts index f6ce775..6ce6287 100644 --- a/src/lib/oncePromise.ts +++ b/src/lib/oncePromise.ts @@ -10,13 +10,19 @@ const ensurePromise = <T>(maybePromise: T | PromiseLike<T>): Promise<T> => ? (maybePromise as Promise<T>) : Promise.resolve(maybePromise); /** Returns a function that caches successful promises until time runs out, and throws away unsuccessful ones */ -export const oncePromise = <T>(create: () => Promise<T>, timeout = -1) => { +export const oncePromise = <T>( + create: () => Promise<T>, + retries = true, + timeout = -1 +) => { let getPromise = (): Promise<T> => { const oldGetPromise = getPromise, - promise = ensurePromise(create()).catch((e) => { - getPromise = oldGetPromise; - throw e; - }), + promise = retries + ? ensurePromise(create()).catch((e) => { + getPromise = oldGetPromise; + throw e; + }) + : ensurePromise(create()), expires = timeout > 0 ? performance.now() + timeout : 0; return (getPromise = expires ? ((() => diff --git a/src/lib/util-types.ts b/src/lib/util-types.ts new file mode 100644 index 0000000..861edcf --- /dev/null +++ b/src/lib/util-types.ts @@ -0,0 +1,4 @@ +export type Unpromise<T extends Promise<any>> = T extends Promise<infer U> + ? U + : never; +export type Defined<T> = T extends undefined | null ? never : T; |