From 12298df594058d7111854da4d3c438ac6bcd5a27 Mon Sep 17 00:00:00 2001 From: "coderabbitai[bot]" <136622811+coderabbitai[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2026 11:21:01 +0000 Subject: [PATCH 1/3] fix: apply CodeRabbit auto-fixes Fixed 10 file(s) based on 11 unresolved review comments. Co-authored-by: CodeRabbit --- .env.example | 4 +- README.md | 4 +- src/api/context/oidc.ts | 128 ++++++++++++------ src/api/resolvers/modules/auth.ts | 54 ++++++-- src/api/resolvers/modules/impersonation.ts | 5 +- src/api/resolvers/modules/user.ts | 12 +- src/api/services/OIDC.ts | 27 +++- src/config/private.ts | 6 +- src/config/public.ts | 10 +- .../(authenticated)/my-account/+page.svelte | 11 +- 10 files changed, 178 insertions(+), 83 deletions(-) diff --git a/.env.example b/.env.example index 5495e880..8fc9d20a 100644 --- a/.env.example +++ b/.env.example @@ -37,7 +37,7 @@ DATABASE_URL=postgres://postgres:postgres@localhost:5432/postgres # ┌─── Logto Setup Guide ───────────────────────────────────────────────────────┐ # │ │ # │ 1. Create a "Traditional Web" application in Logto Console │ -# │ 2. Set the redirect URI to: /auth/callback │ +# │ 2. Set the redirect URI to: /auth/login-callback │ # │ 3. Copy the App ID → PUBLIC_OIDC_CLIENT_ID │ # │ 4. Copy the App Secret → OIDC_CLIENT_SECRET │ # │ │ @@ -246,4 +246,4 @@ SMTP_FROM_NAME=MUNIFY Delegator # PUBLIC_VERSION=1.0.0 # [AUTO] Git commit SHA -# PUBLIC_SHA=abc123 +# PUBLIC_SHA=abc123 \ No newline at end of file diff --git a/README.md b/README.md index 0b3e6d9d..d7406f1e 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ bunx lefthook install ## Deployment -The easiest way to deploy delegator on your own hardware is to use our provided [docker images](https://hub.docker.com/r/deutschemodelunitednations/delegator). You can find an example docker compose file in the [example](./example/) directoy. Please note that delegator relies on an [OIDC](https://auth0.com/intro-to-iam/what-is-openid-connect-oidc) issuer to be connected and properly configured. We recommend [Logto](https://logto.io/) but any issuer of your choice will work. There are some additional instructions on this topic to be found in the example compose file. +The easiest way to deploy delegator on your own hardware is to use our provided [docker images](https://hub.docker.com/r/deutschemodelunitednations/delegator). You can find an example Docker Compose file in the [example](./example/) directory. Please note that delegator relies on an [OIDC](https://auth0.com/intro-to-iam/what-is-openid-connect-oidc) issuer to be connected and properly configured. We recommend [Logto](https://logto.io/) but any issuer of your choice will work. There are some additional instructions on this topic to be found in the example compose file. ## FAQ @@ -87,4 +87,4 @@ This aspect is work in progress since the project is currently in its developmen You can support our work by donating to our non-profit organization [Deutsche Model United Nations (DMUN) e.V.](https://dmun.de). Please contact our board for details on how to donate by sending an email to [vorstand@dmun.de](mailto:vorstand@dmun.de). -All donations are tax deductible in Germany and we are happy to provide you with a donation receipt. The money will be used to support our development of MUNify if you don't specify a different purpose. +All donations are tax deductible in Germany and we are happy to provide you with a donation receipt. The money will be used to support our development of MUNify if you don't specify a different purpose. \ No newline at end of file diff --git a/src/api/context/oidc.ts b/src/api/context/oidc.ts index 3ff91a30..f19199d4 100644 --- a/src/api/context/oidc.ts +++ b/src/api/context/oidc.ts @@ -1,9 +1,10 @@ import { z } from 'zod'; -import { oidcRoles, refresh, validateTokens, type OIDCUser } from '$api/services/OIDC'; +import { oidcRoles, refresh, validateTokens, getJwks, type OIDCUser } from '$api/services/OIDC'; import { configPrivate } from '$config/private'; +import { configPublic } from '$config/public'; import type { RequestEvent } from '@sveltejs/kit'; import { GraphQLError } from 'graphql'; -import { decodeJwt } from 'jose'; +import { jwtVerify } from 'jose'; import { db } from '$db/db'; const TokenCookieSchema = z @@ -31,6 +32,43 @@ export type ImpersonationContext = { export const tokensCookieName = 'token_set'; export const impersonationTokenCookieName = 'impersonation_token_set'; +/** + * Parse and validate OIDC roles from raw claim data. + * Handles multiple provider formats: plain string arrays, objects with `.name` property, or key-value objects. + * + * @param rolesRaw - The raw roles claim value from the OIDC token + * @param allowedRoles - Set or array of allowed role values + * @returns Array of validated role names + */ +function parseOidcRoles( + rolesRaw: unknown, + allowedRoles: readonly string[] +): (typeof oidcRoles)[number][] { + const result: string[] = []; + + if (Array.isArray(rolesRaw)) { + for (const role of rolesRaw) { + if (typeof role === 'string') { + // Simple string array (e.g. ["admin"]) + result.push(role); + } else if (role && typeof role === 'object' && 'name' in role) { + // Logto returns role objects (e.g. [{name: "admin", ...}]) + const roleName = role.name; + if (typeof roleName === 'string') { + result.push(roleName); + } + } + } + } else if (rolesRaw && typeof rolesRaw === 'object') { + // Zitadel returned roles as an object with role names as keys + const roleNames = Object.keys(rolesRaw); + result.push(...roleNames); + } + + // Filter to only allowed roles + return result.filter((role) => allowedRoles.includes(role)) as (typeof oidcRoles)[number][]; +} + /** * Builds an OIDC context from request cookies: validates or refreshes tokens, extracts roles, and handles optional impersonation. * @@ -116,25 +154,11 @@ export async function oidc(cookies: RequestEvent['cookies']) { } } - const OIDCRoleNames: (typeof oidcRoles)[number][] = []; + let OIDCRoleNames: (typeof oidcRoles)[number][] = []; if (user && configPrivate.OIDC_ROLE_CLAIM) { - const rolesRaw = user[configPrivate.OIDC_ROLE_CLAIM]!; - if (Array.isArray(rolesRaw)) { - for (const role of rolesRaw) { - if (typeof role === 'string') { - // Simple string array (e.g. ["admin"]) - OIDCRoleNames.push(role as any); - } else if (role && typeof role === 'object' && 'name' in role) { - // Logto returns role objects (e.g. [{name: "admin", ...}]) - OIDCRoleNames.push(role.name as any); - } - } - } else if (rolesRaw && typeof rolesRaw === 'object') { - // Zitadel returned roles as an object with role names as keys - const roleNames = Object.keys(rolesRaw); - OIDCRoleNames.push(...(roleNames as any)); - } + const rolesRaw = user[configPrivate.OIDC_ROLE_CLAIM]; + OIDCRoleNames = parseOidcRoles(rolesRaw, oidcRoles); } const hasRole = (role: (typeof OIDCRoleNames)[number]) => { @@ -151,22 +175,47 @@ export async function oidc(cookies: RequestEvent['cookies']) { const impersonationTokenSet = TokenCookieSchema.safeParse(JSON.parse(impersonationCookie)); if (impersonationTokenSet.success && impersonationTokenSet.data.access_token) { - // Decode the impersonation JWT directly — it's a resource-scoped token - // that cannot be used with the userinfo endpoint. - // The token only contains `sub` and custom JWT claims (roles, mfa, etc.) - // but no profile claims, so we look up the user from the database. - const decoded = decodeJwt(impersonationTokenSet.data.access_token); - if (!decoded.sub) { + // Verify the impersonation JWT cryptographically against the OIDC issuer JWKS + const jwks = getJwks(); + if (!jwks) { + throw new Error('JWKS not available for impersonation token verification'); + } + + let verifiedPayload; + try { + const verification = await jwtVerify( + impersonationTokenSet.data.access_token, + jwks, + { + issuer: configPublic.PUBLIC_OIDC_AUTHORITY.replace('/.well-known/openid-configuration', ''), + audience: configPrivate.OIDC_RESOURCE ?? undefined + } + ); + verifiedPayload = verification.payload; + } catch (verificationError) { + console.warn('Impersonation token verification failed:', verificationError); + cookies.delete(impersonationTokenCookieName, { path: '/' }); + return { + nextTokenRefreshDue: tokenSet.expires_in + ? new Date(Date.now() + tokenSet.expires_in * 1000) + : undefined, + tokenSet, + user: user ? { ...user, hasRole, OIDCRoleNames } : undefined, + impersonation: impersonationContext + }; + } + + if (!verifiedPayload.sub) { throw new Error('Impersonation token missing sub claim'); } - const dbUser = await db.user.findUnique({ where: { id: decoded.sub } }); + const dbUser = await db.user.findUnique({ where: { id: verifiedPayload.sub } }); if (!dbUser) { - throw new Error(`Impersonated user ${decoded.sub} not found in database`); + throw new Error(`Impersonated user ${verifiedPayload.sub} not found in database`); } const impersonatedUser: OIDCUser = { - sub: decoded.sub, + sub: verifiedPayload.sub, email: dbUser.email ?? '', preferred_username: dbUser.preferred_username ?? undefined, family_name: dbUser.family_name ?? undefined, @@ -174,11 +223,11 @@ export async function oidc(cookies: RequestEvent['cookies']) { locale: dbUser.locale ?? undefined, phone: dbUser.phone ?? undefined, // Spread custom JWT claims (roles, mfa, password, etc.) - ...decoded + ...verifiedPayload }; // Extract actor information from the JWT token (if present) - const actorInfo = decoded.act as Record | undefined; + const actorInfo = verifiedPayload.act as Record | undefined; // If actor claim is present, verify it matches the current user if (actorInfo && typeof actorInfo === 'object') { @@ -212,21 +261,10 @@ export async function oidc(cookies: RequestEvent['cookies']) { user = impersonatedUser; // Update role information for impersonated user - const impersonatedOIDCRoleNames: (typeof oidcRoles)[number][] = []; + let impersonatedOIDCRoleNames: (typeof oidcRoles)[number][] = []; if (impersonatedUser && configPrivate.OIDC_ROLE_CLAIM) { - const impersonatedRolesRaw = impersonatedUser[configPrivate.OIDC_ROLE_CLAIM]!; - if (Array.isArray(impersonatedRolesRaw)) { - for (const role of impersonatedRolesRaw) { - if (typeof role === 'string') { - impersonatedOIDCRoleNames.push(role as any); - } else if (role && typeof role === 'object' && 'name' in role) { - impersonatedOIDCRoleNames.push(role.name as any); - } - } - } else if (impersonatedRolesRaw && typeof impersonatedRolesRaw === 'object') { - const impersonatedRoleNames = Object.keys(impersonatedRolesRaw); - impersonatedOIDCRoleNames.push(...(impersonatedRoleNames as any)); - } + const impersonatedRolesRaw = impersonatedUser[configPrivate.OIDC_ROLE_CLAIM]; + impersonatedOIDCRoleNames = parseOidcRoles(impersonatedRolesRaw, oidcRoles); } // Override role functions for impersonated user @@ -269,4 +307,4 @@ export async function oidc(cookies: RequestEvent['cookies']) { }; } -export type OIDC = Awaited>; +export type OIDC = Awaited>; \ No newline at end of file diff --git a/src/api/resolvers/modules/auth.ts b/src/api/resolvers/modules/auth.ts index f454c7b9..653cc2bd 100644 --- a/src/api/resolvers/modules/auth.ts +++ b/src/api/resolvers/modules/auth.ts @@ -68,21 +68,51 @@ builder.queryFields((t) => { }), resolve: (root, args, ctx) => { const user = ctx.oidc.user; - // TYPE-SAFETY-EXCEPTION: `password` and `mfa` are custom JWT claims - // injected by Logto's Custom JWT feature. They exist on the OIDCUser - // index signature but TypeScript loses it after the spread in oidc context. - const claims = user as Record | undefined; + + // Type guards for custom JWT claims + const isBooleanClaim = (value: unknown): value is boolean => { + return typeof value === 'boolean'; + }; + + const isStringArrayClaim = (value: unknown): value is string[] => { + return Array.isArray(value) && value.every((item) => typeof item === 'string'); + }; + + const isSsoIdentitiesArrayClaim = ( + value: unknown + ): value is { issuer: string; identityId: string }[] => { + return ( + Array.isArray(value) && + value.every( + (item) => + item && + typeof item === 'object' && + 'issuer' in item && + 'identityId' in item && + typeof item.issuer === 'string' && + typeof item.identityId === 'string' + ) + ); + }; + + // Extract custom JWT claims with runtime validation + const passwordClaim = user?.['password']; + const mfaClaim = user?.['mfa']; + const ssoIdentitiesClaim = user?.['sso_identities']; + const socialIdentitiesClaim = user?.['social_identities']; + return { user: user ? { ...user, - hasPassword: (claims?.['password'] as boolean) ?? null, - mfaVerificationFactors: (claims?.['mfa'] as string[]) ?? null, - ssoIdentities: - (claims?.['sso_identities'] as - | { issuer: string; identityId: string }[] - | undefined) ?? null, - socialIdentities: (claims?.['social_identities'] as string[] | undefined) ?? null + hasPassword: isBooleanClaim(passwordClaim) ? passwordClaim : null, + mfaVerificationFactors: isStringArrayClaim(mfaClaim) ? mfaClaim : null, + ssoIdentities: isSsoIdentitiesArrayClaim(ssoIdentitiesClaim) + ? ssoIdentitiesClaim + : null, + socialIdentities: isStringArrayClaim(socialIdentitiesClaim) + ? socialIdentitiesClaim + : null } : null, nextTokenRefreshDue: ctx.oidc.nextTokenRefreshDue @@ -90,4 +120,4 @@ builder.queryFields((t) => { } }) }; -}); +}); \ No newline at end of file diff --git a/src/api/resolvers/modules/impersonation.ts b/src/api/resolvers/modules/impersonation.ts index b36e72ae..994adf4d 100644 --- a/src/api/resolvers/modules/impersonation.ts +++ b/src/api/resolvers/modules/impersonation.ts @@ -262,7 +262,8 @@ builder.mutationFields((t) => ({ // Perform token exchange (no scope needed — Logto uses the resource indicator) const impersonationTokens = await performTokenExchange( ctx.oidc.tokenSet.access_token, - args.targetUserId + args.targetUserId, + args.scope ?? undefined ); // Store impersonation tokens in cookie @@ -331,4 +332,4 @@ builder.mutationFields((t) => ({ return true; } }) -})); +})); \ No newline at end of file diff --git a/src/api/resolvers/modules/user.ts b/src/api/resolvers/modules/user.ts index 29fc29d1..993ce65e 100644 --- a/src/api/resolvers/modules/user.ts +++ b/src/api/resolvers/modules/user.ts @@ -429,11 +429,11 @@ builder.mutationFields((t) => { }, update: { email: issuerUserData.email, - ...(issuerUserData.family_name && { family_name: issuerUserData.family_name }), - ...(issuerUserData.given_name && { given_name: issuerUserData.given_name }), - ...(issuerUserData.preferred_username && { - preferred_username: issuerUserData.preferred_username - }), + ...(issuerUserData.family_name != null ? { family_name: issuerUserData.family_name } : {}), + ...(issuerUserData.given_name != null ? { given_name: issuerUserData.given_name } : {}), + ...(issuerUserData.preferred_username != null + ? { preferred_username: issuerUserData.preferred_username } + : {}), locale: issuerUserData.locale ?? configPublic.PUBLIC_DEFAULT_LOCALE, phone: issuerUserData.phone ?? user.phone } @@ -608,4 +608,4 @@ builder.mutationFields((t) => { } }) }; -}); +}); \ No newline at end of file diff --git a/src/api/services/OIDC.ts b/src/api/services/OIDC.ts index 33ea0c09..64dc5fa5 100644 --- a/src/api/services/OIDC.ts +++ b/src/api/services/OIDC.ts @@ -106,6 +106,22 @@ const { config, cryptr, jwks } = await (async () => { return { config, cryptr, jwks }; })(); +/** + * Get the JWKS for token verification. + * @returns The JWKS remote set or undefined if not available. + */ +export function getJwks() { + return jwks; +} + +/** + * Get the OIDC configuration. + * @returns The OIDC configuration object. + */ +export function getConfig() { + return config; +} + export async function startSignin(visitedUrl: URL) { //TODO https://github.com/gornostay25/svelte-adapter-bun/issues/62 if (configPrivate.NODE_ENV === 'production') { @@ -360,7 +376,9 @@ export async function performTokenExchange( const tokenExchangeParams: Record = { grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange', subject_token: subjectToken, - subject_token_type: 'urn:ietf:params:oauth:token-type:access_token' + subject_token_type: 'urn:ietf:params:oauth:token-type:access_token', + actor_token: actorToken, + actor_token_type: 'urn:ietf:params:oauth:token-type:access_token' }; // Resource is required for Logto token exchange @@ -368,6 +386,11 @@ export async function performTokenExchange( tokenExchangeParams.resource = configPrivate.OIDC_RESOURCE; } + // Optional scope parameter + if (scope) { + tokenExchangeParams.scope = scope; + } + const response = await fetch(config.serverMetadata().token_endpoint!, { method: 'POST', headers: { @@ -436,4 +459,4 @@ export async function performTokenExchange( `Token exchange failed: ${error instanceof Error ? error.message : 'Unknown error'}` ); } -} +} \ No newline at end of file diff --git a/src/config/private.ts b/src/config/private.ts index e2449a59..a6184e02 100644 --- a/src/config/private.ts +++ b/src/config/private.ts @@ -19,7 +19,7 @@ const schema = z.object({ OIDC_M2M_CLIENT_ID: z.string().optional(), OIDC_M2M_CLIENT_SECRET: z.string().optional(), // Logto Management API resource indicator (e.g. https://default.logto.app/api) - OIDC_M2M_RESOURCE: z.string().url().optional(), + OIDC_M2M_RESOURCE: z.preprocess((v) => (v === '' ? undefined : v), z.string().url().optional()), SECRET: z.string(), NODE_ENV: z.union([z.literal('development'), z.literal('production'), z.literal('test')]), OTEL_SERVICE_NAME: z.string().default('MUNIFY-DELEGATOR'), @@ -39,8 +39,8 @@ const schema = z.object({ SMTP_FROM_ADDRESS: z.string().email().default('noreply@munify.cloud'), SMTP_FROM_NAME: z.string().default('MUNIFY Delegator'), // Sentry/Bugsink error tracking - SENTRY_DSN: z.string().url().optional(), + SENTRY_DSN: z.preprocess((v) => (v === '' ? undefined : v), z.string().url().optional()), SENTRY_SEND_DEFAULT_PII: z.stringbool().optional() }); -export const configPrivate = building ? ({} as z.infer) : schema.parse(env); +export const configPrivate = building ? ({} as z.infer) : schema.parse(env); \ No newline at end of file diff --git a/src/config/public.ts b/src/config/public.ts index d67bab45..da4887e0 100644 --- a/src/config/public.ts +++ b/src/config/public.ts @@ -8,7 +8,7 @@ const schema = z.object({ PUBLIC_OIDC_AUTHORITY: z.string(), PUBLIC_OIDC_CLIENT_ID: z.string(), PUBLIC_DEFAULT_LOCALE: z.string().default('de'), - PUBLIC_OIDC_ACCOUNT_URL: z.string().url().optional(), + PUBLIC_OIDC_ACCOUNT_URL: z.preprocess((v) => (v === '' ? undefined : v), z.string().url().optional()), PUBLIC_FEEDBACK_URL: z.optional(z.string()), PUBLIC_GLOBAL_USER_NOTES_ACTIVE: z.coerce.boolean().default(false), @@ -18,14 +18,14 @@ const schema = z.object({ PUBLIC_MAINTENANCE_WINDOW_START: z.iso.datetime({ offset: true }).optional(), PUBLIC_MAINTENANCE_WINDOW_END: z.iso.datetime({ offset: true }).optional(), // Sentry/Bugsink error tracking - PUBLIC_SENTRY_DSN: z.string().url().optional(), + PUBLIC_SENTRY_DSN: z.preprocess((v) => (v === '' ? undefined : v), z.string().url().optional()), PUBLIC_SENTRY_SEND_DEFAULT_PII: z.stringbool().optional(), // Badge generator URL (optional) - PUBLIC_BADGE_GENERATOR_URL: z.string().url().optional(), + PUBLIC_BADGE_GENERATOR_URL: z.preprocess((v) => (v === '' ? undefined : v), z.string().url().optional()), // Documentation URL (optional) - global link to DELEGATOR app documentation - PUBLIC_DOCS_URL: z.string().url().optional(), + PUBLIC_DOCS_URL: z.preprocess((v) => (v === '' ? undefined : v), z.string().url().optional()), // Support email for error pages and help requests PUBLIC_SUPPORT_EMAIL: z.string().email().default('support@dmun.de'), @@ -34,4 +34,4 @@ const schema = z.object({ PUBLIC_TEAM_ORGANIZATION_DOMAIN: z.string().optional() }); -export const configPublic = building ? ({} as z.infer) : schema.parse(env); +export const configPublic = building ? ({} as z.infer) : schema.parse(env); \ No newline at end of file diff --git a/src/routes/(authenticated)/my-account/+page.svelte b/src/routes/(authenticated)/my-account/+page.svelte index d4f59e1e..f32b234a 100644 --- a/src/routes/(authenticated)/my-account/+page.svelte +++ b/src/routes/(authenticated)/my-account/+page.svelte @@ -190,7 +190,7 @@
{m.loginName()}
{data.user.preferred_username ?? '–'}
- + @@ -200,7 +200,7 @@
{m.email()}
{data.user.email}
- + @@ -212,7 +212,7 @@
•••••
{/if} - + @@ -227,6 +227,7 @@ @@ -242,6 +243,7 @@ @@ -258,6 +260,7 @@ @@ -362,4 +365,4 @@ --angle: 360deg; } } - + \ No newline at end of file From f4df902b0ed2104670098817e48f28ad7b83c579 Mon Sep 17 00:00:00 2001 From: Tade Strehk Date: Mon, 6 Apr 2026 14:07:11 +0200 Subject: [PATCH 2/3] fix: Remove actor_token from token exchange and use proper issuer for JWT verification actor_token causes Logto to reject the token exchange with invalid_grant. Use getConfig().serverMetadata().issuer instead of fragile string replace on PUBLIC_OIDC_AUTHORITY for JWT issuer verification. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/api/context/oidc.ts | 24 +++++++++++++----------- src/api/services/OIDC.ts | 6 ++---- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/api/context/oidc.ts b/src/api/context/oidc.ts index f19199d4..516fb6ca 100644 --- a/src/api/context/oidc.ts +++ b/src/api/context/oidc.ts @@ -1,7 +1,13 @@ import { z } from 'zod'; -import { oidcRoles, refresh, validateTokens, getJwks, type OIDCUser } from '$api/services/OIDC'; +import { + oidcRoles, + refresh, + validateTokens, + getJwks, + getConfig, + type OIDCUser +} from '$api/services/OIDC'; import { configPrivate } from '$config/private'; -import { configPublic } from '$config/public'; import type { RequestEvent } from '@sveltejs/kit'; import { GraphQLError } from 'graphql'; import { jwtVerify } from 'jose'; @@ -183,14 +189,10 @@ export async function oidc(cookies: RequestEvent['cookies']) { let verifiedPayload; try { - const verification = await jwtVerify( - impersonationTokenSet.data.access_token, - jwks, - { - issuer: configPublic.PUBLIC_OIDC_AUTHORITY.replace('/.well-known/openid-configuration', ''), - audience: configPrivate.OIDC_RESOURCE ?? undefined - } - ); + const verification = await jwtVerify(impersonationTokenSet.data.access_token, jwks, { + issuer: getConfig().serverMetadata().issuer, + audience: configPrivate.OIDC_RESOURCE ?? undefined + }); verifiedPayload = verification.payload; } catch (verificationError) { console.warn('Impersonation token verification failed:', verificationError); @@ -307,4 +309,4 @@ export async function oidc(cookies: RequestEvent['cookies']) { }; } -export type OIDC = Awaited>; \ No newline at end of file +export type OIDC = Awaited>; diff --git a/src/api/services/OIDC.ts b/src/api/services/OIDC.ts index 64dc5fa5..34170478 100644 --- a/src/api/services/OIDC.ts +++ b/src/api/services/OIDC.ts @@ -376,9 +376,7 @@ export async function performTokenExchange( const tokenExchangeParams: Record = { grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange', subject_token: subjectToken, - subject_token_type: 'urn:ietf:params:oauth:token-type:access_token', - actor_token: actorToken, - actor_token_type: 'urn:ietf:params:oauth:token-type:access_token' + subject_token_type: 'urn:ietf:params:oauth:token-type:access_token' }; // Resource is required for Logto token exchange @@ -459,4 +457,4 @@ export async function performTokenExchange( `Token exchange failed: ${error instanceof Error ? error.message : 'Unknown error'}` ); } -} \ No newline at end of file +} From 181cae7d305a8213bfaef51318fcb799912a5e05 Mon Sep 17 00:00:00 2001 From: Tade Strehk Date: Mon, 6 Apr 2026 14:08:24 +0200 Subject: [PATCH 3/3] format --- README.md | 2 +- src/api/resolvers/modules/auth.ts | 2 +- src/api/resolvers/modules/impersonation.ts | 2 +- src/api/resolvers/modules/user.ts | 10 +++++++--- src/config/private.ts | 2 +- src/config/public.ts | 12 +++++++++--- src/routes/(authenticated)/my-account/+page.svelte | 14 +++++++++++--- 7 files changed, 31 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index d7406f1e..9dd652ff 100644 --- a/README.md +++ b/README.md @@ -87,4 +87,4 @@ This aspect is work in progress since the project is currently in its developmen You can support our work by donating to our non-profit organization [Deutsche Model United Nations (DMUN) e.V.](https://dmun.de). Please contact our board for details on how to donate by sending an email to [vorstand@dmun.de](mailto:vorstand@dmun.de). -All donations are tax deductible in Germany and we are happy to provide you with a donation receipt. The money will be used to support our development of MUNify if you don't specify a different purpose. \ No newline at end of file +All donations are tax deductible in Germany and we are happy to provide you with a donation receipt. The money will be used to support our development of MUNify if you don't specify a different purpose. diff --git a/src/api/resolvers/modules/auth.ts b/src/api/resolvers/modules/auth.ts index 653cc2bd..f7a9bfc3 100644 --- a/src/api/resolvers/modules/auth.ts +++ b/src/api/resolvers/modules/auth.ts @@ -120,4 +120,4 @@ builder.queryFields((t) => { } }) }; -}); \ No newline at end of file +}); diff --git a/src/api/resolvers/modules/impersonation.ts b/src/api/resolvers/modules/impersonation.ts index 994adf4d..5a95f042 100644 --- a/src/api/resolvers/modules/impersonation.ts +++ b/src/api/resolvers/modules/impersonation.ts @@ -332,4 +332,4 @@ builder.mutationFields((t) => ({ return true; } }) -})); \ No newline at end of file +})); diff --git a/src/api/resolvers/modules/user.ts b/src/api/resolvers/modules/user.ts index 993ce65e..8c84c2c4 100644 --- a/src/api/resolvers/modules/user.ts +++ b/src/api/resolvers/modules/user.ts @@ -429,8 +429,12 @@ builder.mutationFields((t) => { }, update: { email: issuerUserData.email, - ...(issuerUserData.family_name != null ? { family_name: issuerUserData.family_name } : {}), - ...(issuerUserData.given_name != null ? { given_name: issuerUserData.given_name } : {}), + ...(issuerUserData.family_name != null + ? { family_name: issuerUserData.family_name } + : {}), + ...(issuerUserData.given_name != null + ? { given_name: issuerUserData.given_name } + : {}), ...(issuerUserData.preferred_username != null ? { preferred_username: issuerUserData.preferred_username } : {}), @@ -608,4 +612,4 @@ builder.mutationFields((t) => { } }) }; -}); \ No newline at end of file +}); diff --git a/src/config/private.ts b/src/config/private.ts index a6184e02..0e2df182 100644 --- a/src/config/private.ts +++ b/src/config/private.ts @@ -43,4 +43,4 @@ const schema = z.object({ SENTRY_SEND_DEFAULT_PII: z.stringbool().optional() }); -export const configPrivate = building ? ({} as z.infer) : schema.parse(env); \ No newline at end of file +export const configPrivate = building ? ({} as z.infer) : schema.parse(env); diff --git a/src/config/public.ts b/src/config/public.ts index da4887e0..2804cacd 100644 --- a/src/config/public.ts +++ b/src/config/public.ts @@ -8,7 +8,10 @@ const schema = z.object({ PUBLIC_OIDC_AUTHORITY: z.string(), PUBLIC_OIDC_CLIENT_ID: z.string(), PUBLIC_DEFAULT_LOCALE: z.string().default('de'), - PUBLIC_OIDC_ACCOUNT_URL: z.preprocess((v) => (v === '' ? undefined : v), z.string().url().optional()), + PUBLIC_OIDC_ACCOUNT_URL: z.preprocess( + (v) => (v === '' ? undefined : v), + z.string().url().optional() + ), PUBLIC_FEEDBACK_URL: z.optional(z.string()), PUBLIC_GLOBAL_USER_NOTES_ACTIVE: z.coerce.boolean().default(false), @@ -22,7 +25,10 @@ const schema = z.object({ PUBLIC_SENTRY_SEND_DEFAULT_PII: z.stringbool().optional(), // Badge generator URL (optional) - PUBLIC_BADGE_GENERATOR_URL: z.preprocess((v) => (v === '' ? undefined : v), z.string().url().optional()), + PUBLIC_BADGE_GENERATOR_URL: z.preprocess( + (v) => (v === '' ? undefined : v), + z.string().url().optional() + ), // Documentation URL (optional) - global link to DELEGATOR app documentation PUBLIC_DOCS_URL: z.preprocess((v) => (v === '' ? undefined : v), z.string().url().optional()), @@ -34,4 +40,4 @@ const schema = z.object({ PUBLIC_TEAM_ORGANIZATION_DOMAIN: z.string().optional() }); -export const configPublic = building ? ({} as z.infer) : schema.parse(env); \ No newline at end of file +export const configPublic = building ? ({} as z.infer) : schema.parse(env); diff --git a/src/routes/(authenticated)/my-account/+page.svelte b/src/routes/(authenticated)/my-account/+page.svelte index f32b234a..08a17312 100644 --- a/src/routes/(authenticated)/my-account/+page.svelte +++ b/src/routes/(authenticated)/my-account/+page.svelte @@ -190,7 +190,11 @@
{m.loginName()}
{data.user.preferred_username ?? '–'}
- + @@ -212,7 +216,11 @@
•••••
{/if} - + @@ -365,4 +373,4 @@ --angle: 360deg; } } - \ No newline at end of file +