diff --git a/src/app.css b/src/app.css index 6ea048d..c1051bb 100644 --- a/src/app.css +++ b/src/app.css @@ -73,3 +73,8 @@ a[target='_blank']::after { position: absolute; } } + +ul { + padding-left: 1.45rem; + margin: 0; +} diff --git a/src/lib/components/avatar/AvatarCard.svelte b/src/lib/components/avatar/AvatarCard.svelte index 04f3c00..b2e6da7 100644 --- a/src/lib/components/avatar/AvatarCard.svelte +++ b/src/lib/components/avatar/AvatarCard.svelte @@ -37,7 +37,6 @@ height: 120px; width: 120px; flex: 0 0 120px; - background-color: var(--in-normalized-background); &.small { height: 60px; diff --git a/src/lib/components/form/FormActions.svelte b/src/lib/components/form/FormActions.svelte new file mode 100644 index 0000000..22b0359 --- /dev/null +++ b/src/lib/components/form/FormActions.svelte @@ -0,0 +1,26 @@ + + +
+ {@render children?.()} +
+ + diff --git a/src/lib/server/oauth2/model/client.ts b/src/lib/server/oauth2/model/client.ts index 1ec657d..0997469 100644 --- a/src/lib/server/oauth2/model/client.ts +++ b/src/lib/server/oauth2/model/client.ts @@ -20,6 +20,7 @@ import { UserTokens, Users } from '$lib/server/users'; import type { OAuth2ClientInfo, PaginationMeta } from '$lib/types'; import { and, count, eq, like, or, sql } from 'drizzle-orm'; import { createLocalJWKSet, exportJWK, importJWK, jwtVerify, type JWK } from 'jose'; +import { ensureArray } from '$lib/utils'; export enum OAuth2ClientURLType { REDIRECT_URI = 'redirect_uri', @@ -203,7 +204,15 @@ export class OAuth2Clients { return scope; } - return scope.includes(',') ? scope.split(',').map((item) => item.trim()) : scope.split(' '); + const delimeterSplit = scope.includes(',') ? scope.split(',') : scope.split(' '); + return delimeterSplit.reduce((list, scope) => { + const trimmed = scope.trim(); + if (!!trimmed) { + list.push(trimmed); + } + + return list; + }, []); } static joinScope(scope: string[]): string { @@ -549,7 +558,7 @@ export class OAuth2Clients { }); // Check audience, token must be intended for our service - const checkAudience = Array.isArray(payload.aud) ? payload.aud : [payload.aud]; + const checkAudience = ensureArray(payload.aud); if ( !checkAudience.some( (entry) => entry?.startsWith(env.PUBLIC_URL) || entry?.startsWith(privateEnv.JWT_ISSUER) diff --git a/src/lib/server/oauth2/model/tokens.ts b/src/lib/server/oauth2/model/tokens.ts index 9aa60ba..50be229 100644 --- a/src/lib/server/oauth2/model/tokens.ts +++ b/src/lib/server/oauth2/model/tokens.ts @@ -168,7 +168,7 @@ export class OAuth2Codes { const user = await Users.getById(userId); const accessToken = CryptoUtils.generateString(64); - const scopes = (!Array.isArray(scope) ? OAuth2Clients.splitScope(scope) : scope).join(' '); + const scopes = OAuth2Clients.joinScope(OAuth2Clients.splitScope(scope)); const expiresAt = new Date(Date.now() + ttl * 1000); const pcke = @@ -238,7 +238,7 @@ export class OAuth2AccessTokens { const user = userId != null ? await Users.getById(userId) : undefined; const accessToken = CryptoUtils.generateString(128); - const scopes = (!Array.isArray(scope) ? OAuth2Clients.splitScope(scope) : scope).join(' '); + const scopes = OAuth2Clients.joinScope(OAuth2Clients.splitScope(scope)); const expiresAt = new Date(Date.now() + ttl * 1000); @@ -298,7 +298,7 @@ export class OAuth2RefreshTokens { const user = await Users.getById(userId); const accessToken = CryptoUtils.generateString(64); - const scopes = (!Array.isArray(scope) ? OAuth2Clients.splitScope(scope) : scope).join(' '); + const scopes = OAuth2Clients.joinScope(OAuth2Clients.splitScope(scope)); const expiresAt = new Date(Date.now() + OAuth2Tokens.refreshTtl * 1000); @@ -350,7 +350,7 @@ export class OAuth2DeviceCodes { const userCode = `${CryptoUtils.generateString(3)}-${CryptoUtils.generateString(3)}`.toUpperCase(); - const scopes = (!Array.isArray(scope) ? OAuth2Clients.splitScope(scope) : scope).join(' '); + const scopes = OAuth2Clients.joinScope(OAuth2Clients.splitScope(scope)); const expiresAt = new Date(Date.now() + OAuth2Tokens.deviceTtl * 1000); await OAuth2Tokens.insert( @@ -442,7 +442,7 @@ export class OAuth2ParCodes { const client = await OAuth2Clients.fetchById(clientId); const parCode = CryptoUtils.generateString(32); - const scopes = (!Array.isArray(scope) ? OAuth2Clients.splitScope(scope) : scope).join(' '); + const scopes = OAuth2Clients.joinScope(OAuth2Clients.splitScope(scope)); const expiresAt = new Date(Date.now() + OAuth2Tokens.parTtl * 1000); const pcke = diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 87f212e..676680b 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -14,6 +14,12 @@ export const hasPrivileges = (list: string[], privileges: RequiredPrivileges) => return list.includes(item); }); +/** + * Wait until the `check` function returns a truthy value. + * @param check Condition to run + * @param checkInterval Interval to run condition (ms) + * @param checkTimeout Timeout of the check (ms) + */ export const waitIsTruthy = (check: () => boolean, checkInterval = 500, checkTimeout = 5000) => { let time = 0; return new Promise((resolve, reject) => { @@ -46,3 +52,10 @@ export function self) => void } }; } + +/** + * Ensures that the given input is an array. + * @param input Array or single value + * @returns Array + */ +export const ensureArray = (input: T | T[]): T[] => (Array.isArray(input) ? input : [input]); diff --git a/src/routes/ssoadmin/audit/+page.svelte b/src/routes/ssoadmin/audit/+page.svelte index c69d323..3d3b45a 100644 --- a/src/routes/ssoadmin/audit/+page.svelte +++ b/src/routes/ssoadmin/audit/+page.svelte @@ -9,8 +9,8 @@ import SplitView from '$lib/components/container/SplitView.svelte'; import Button from '$lib/components/Button.svelte'; import AdminAuditCard from '$lib/components/admin/AdminAuditCard.svelte'; - import { hasPrivileges } from '$lib/utils'; import FormActions from '$lib/components/form/FormActions.svelte'; + import { hasPrivileges } from '$lib/utils'; interface Props { data: PageData; diff --git a/svelte.config.js b/svelte.config.js index 08420be..cf6c387 100644 --- a/svelte.config.js +++ b/svelte.config.js @@ -16,6 +16,10 @@ const config = { // This is reimplemented in hooks.server.ts to allow certain endpoints csrf: { checkOrigin: false + }, + + router: { + resolution: 'server' } } };