Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 58 additions & 7 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,40 @@ DATABASE_URL=postgres://postgres:postgres@localhost:5432/postgres
# ───────────────────────────────────────────────────────────────────────────────
# AUTHENTICATION (OIDC)
# ───────────────────────────────────────────────────────────────────────────────
# Supports any OpenID Connect provider (tested with ZITADEL)
# Supports any OpenID Connect provider (recommended: Logto — https://logto.io/)
#
# ┌─── Logto Setup Guide ───────────────────────────────────────────────────────┐
# │ │
# │ 1. Create a "Traditional Web" application in Logto Console │
# │ 2. Set the redirect URI to: <your-domain>/auth/callback │
# │ 3. Copy the App ID → PUBLIC_OIDC_CLIENT_ID │
# │ 4. Copy the App Secret → OIDC_CLIENT_SECRET │
# │ │
# │ Custom JWT Claims (required): │
# │ Go to Logto Console → JWT Customizer → User access token and add: │
# │ │
# │ const getCustomJwtClaims = async ({ │
# │ token, context, environmentVariables, api │
# │ }) => { │
# │ return { │
# │ roles: context.user.roles, │
# │ mfa: context.user.mfaVerificationFactors, │
# │ password: context.user.hasPassword, │
# │ sso_identities: context.user.ssoIdentities, │
# │ social_identities: │
# │ Object.keys(context.user.identities) ?? [] │
# │ }; │
# │ } │
# │ │
# │ This exposes user roles, MFA status, and identity provider info │
# │ in the access token so the application can use them for │
# │ authorization and UI display. │
# │ │
# └─────────────────────────────────────────────────────────────────────────────┘

# [REQUIRED] OIDC Discovery URL (usually ends with /.well-known/openid-configuration)
# Local mock: http://localhost:8080/default/.well-known/openid-configuration
# Production: https://your-oidc-provider.com
# Logto: https://<your-tenant>.logto.app/oidc/.well-known/openid-configuration
PUBLIC_OIDC_AUTHORITY=http://localhost:8080/default/.well-known/openid-configuration

# [REQUIRED] OAuth2 Client ID from your OIDC provider
Expand All @@ -46,12 +75,34 @@ PUBLIC_OIDC_CLIENT_ID=default
# OIDC_CLIENT_SECRET=your-client-secret

# [REQUIRED] OAuth2 scopes to request
# Must include "openid" and "offline_access" at minimum
OIDC_SCOPES="openid profile offline_access address email family_name gender given_name locale name phone preferred_username urn:zitadel:iam:org:projects:roles urn:zitadel:iam:user:metadata urn:zitadel:iam:org:project:id:zitadel:aud"

# [OPTIONAL] JWT claim path for user roles (ZITADEL-specific format shown)
# These scopes control ID token and userinfo claims, NOT the access token.
# Access token claims are set via the JWT Customizer script above.
#
# openid — required by OIDC spec
# profile — user name, picture, etc.
# offline_access — enables refresh tokens
# email — user email address
# phone — user phone number
# identity — Logto scope: linked social & SSO identities
# role — Logto scope: user roles for authorization
# custom_data — Logto scope: custom user data
OIDC_SCOPES="openid profile offline_access email phone identity role custom_data"

# [OPTIONAL] JWT claim path for user roles
# Used to determine team member permissions
OIDC_ROLE_CLAIM=urn:zitadel:iam:org:project:275671427955294244:roles
# With the custom JWT claims above, roles are available at the "roles" claim
OIDC_ROLE_CLAIM=roles

# [OPTIONAL] Machine-to-machine app credentials for Logto Management API
# Required for user impersonation feature (token exchange via subject tokens)
# Create an M2M app in Logto Console with access to the Management API resource
# OIDC_M2M_CLIENT_ID=your-m2m-app-id
# OIDC_M2M_CLIENT_SECRET=your-m2m-app-secret
# [OPTIONAL] Logto Management API resource indicator
# For Logto Cloud: https://<tenant-id>.logto.app/api
# For self-hosted: check your Logto API Resources settings (e.g. https://default.logto.app/api)
# If not set, derived from PUBLIC_OIDC_AUTHORITY by replacing /oidc with /api
# OIDC_M2M_RESOURCE=https://default.logto.app/api

# ───────────────────────────────────────────────────────────────────────────────
# APPLICATION FEATURES
Expand Down
2 changes: 1 addition & 1 deletion .github/actions/setup-bun/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ runs:
steps:
- uses: oven-sh/setup-bun@v2

- uses: actions/cache@v4
- uses: actions/cache@v5
with:
path: |
~/.bun/install/cache
Expand Down
20 changes: 10 additions & 10 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,38 +18,38 @@ jobs:
format:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: ./.github/actions/setup-bun
- run: bun run format:check

lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: ./.github/actions/setup-bun
- run: bunx svelte-kit sync
- run: bun run lint

test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: ./.github/actions/setup-bun
- run: bunx svelte-kit sync
- run: bun run test

i18n:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: ./.github/actions/setup-bun
- run: bun run i18n:check
- run: bun run i18n:validate

security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: ./.github/actions/setup-bun
- uses: aquasecurity/trivy-action@v0.35.0
with:
Expand All @@ -67,7 +67,7 @@ jobs:
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: ./.github/actions/setup-bun
- run: bunx svelte-kit sync
- run: bunx prisma generate
Expand All @@ -81,7 +81,7 @@ jobs:
env:
NODE_OPTIONS: '--max-old-space-size=8192'
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: ./.github/actions/setup-bun
- run: bunx svelte-kit sync
- run: bunx prisma generate
Expand All @@ -101,7 +101,7 @@ jobs:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_URL: ${{ secrets.SENTRY_URL }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5

- uses: docker/setup-buildx-action@v3

Expand Down Expand Up @@ -198,7 +198,7 @@ jobs:
needs: [format, lint, test, i18n, security, typecheck]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
fetch-depth: 2

Expand Down Expand Up @@ -285,7 +285,7 @@ jobs:
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5

- name: Extract version
id: version
Expand Down
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ MUNify DELEGATOR is a SvelteKit-based application for managing Model United Nati
- **Backend**: Node.js with SvelteKit server routes
- **Database**: PostgreSQL via Prisma ORM
- **GraphQL**: Pothos schema builder with graphql-yoga server, Houdini client
- **Auth**: OpenID Connect (OIDC) - tested with ZITADEL
- **Auth**: OpenID Connect (OIDC) - recommended provider: Logto
- **i18n**: Paraglide-JS for internationalization (default locale: German)
- **Runtime**: Bun (package manager and development runtime)
- **Observability**: OpenTelemetry tracing support
Expand Down Expand Up @@ -281,7 +281,7 @@ Required variables (see `.env.example`):
- `PUBLIC_OIDC_AUTHORITY` - OIDC provider URL
- `PUBLIC_OIDC_CLIENT_ID` - OAuth client ID
- `OIDC_SCOPES` - OAuth scopes (must include `openid`)
- `OIDC_ROLE_CLAIM` - JWT claim for roles (ZITADEL format)
- `OIDC_ROLE_CLAIM` - JWT claim for roles
- `CERTIFICATE_SECRET` - Secret for signing participation certificates
- OpenTelemetry vars (optional): `OTEL_ENDPOINT_URL`, `OTEL_SERVICE_NAME`

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 [ZITADEL](https://zitadel.com/) 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/) 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.

## FAQ

Expand Down
4 changes: 2 additions & 2 deletions WARP.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ bun run machine-translate

#### Authentication & Authorization

- **OIDC Integration**: Uses OpenID Connect (designed for ZITADEL but supports any OIDC provider)
- **OIDC Integration**: Uses OpenID Connect (recommended: Logto, but supports any OIDC provider)
- **Context Building**: `src/api/context/context.ts` constructs request context with OIDC data
- **Permission System**: CASL ability-based authorization
- Definitions in `src/api/abilities/entities/`
Expand Down Expand Up @@ -221,5 +221,5 @@ Example: `feat(delegation): add nation preference selection`
Use provided Docker images: `deutschemodelunitednations/delegator`

- Example compose file in `example/` directory
- Requires external OIDC provider (ZITADEL recommended)
- Requires external OIDC provider (Logto recommended)
- Environment variables must be configured (see `.env.example`)
4 changes: 2 additions & 2 deletions dev.docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ services:
# "given_name": "Delegator Jr.",
# "preferred_username": "delegatoruser_123",
# "locale": "de",
# "urn:zitadel:iam:org:project:275671427955294244:roles": {"admin": {}}
# "roles": ["admin"]
# }
mockoidc:
image: ghcr.io/navikt/mock-oauth2-server:2.1.10
Expand Down Expand Up @@ -143,7 +143,7 @@ services:
- '1025:1025' # SMTP server
- '8025:8025' # Web UI
environment:
MP_SMTP_AUTH_ACCEPT_ANY: 1
MP_SMTP_AUTH: 'mailpit:mailpit'
MP_SMTP_AUTH_ALLOW_INSECURE: 1

# Bugsink - Self-hosted error tracking (Sentry-compatible)
Expand Down
14 changes: 14 additions & 0 deletions messages/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@
"accessFlowSaved": "Zugangsausweis zugewiesen und Anwesenheit erfasst.",
"accountExists": "Konto vorhanden",
"accountHolder": "Kontoinhaber*in",
"accountUpdateSuccessBackupCodes": "Deine Backup-Codes wurden erfolgreich aktualisiert.",
"accountUpdateSuccessEmail": "Deine E-Mail-Adresse wurde erfolgreich aktualisiert.",
"accountUpdateSuccessMfa": "Deine Authenticator-App-Einstellungen wurden erfolgreich aktualisiert.",
"accountUpdateSuccessPasskey": "Deine Passkey-Einstellungen wurden erfolgreich aktualisiert.",
"accountUpdateSuccessPassword": "Dein Passwort wurde erfolgreich geändert.",
"accountUpdateSuccessUsername": "Dein Benutzername wurde erfolgreich aktualisiert.",
"actions": "Aktionen",
"activeConferences": "Aktive Konferenzen",
"activeMembers": "Aktive Mitglieder",
Expand Down Expand Up @@ -193,6 +199,7 @@
"authErrorTechnicalTitle": "Anmeldung fehlgeschlagen",
"authErrorTokenExchangeDescription": "Der Anmeldevorgang konnte nicht abgeschlossen werden. Das kann passieren, wenn die Anmeldung zu lange gedauert hat oder ein Netzwerkproblem aufgetreten ist.",
"authErrorTryAgain": "Erneut versuchen",
"authenticatorApp": "Authenticator-App",
"author": "Autor*in",
"authorization": "Berechtigung",
"averageAgeOnlyApplied": "Konferenz-Altersdurchschnitt (nur angemeldete)",
Expand All @@ -201,6 +208,7 @@
"backToConferencePapers": "Zurück zu Konferenz-Papieren",
"backToDashboard": "Zurück zum Dashboard",
"backToHome": "Zurück zur Homepage",
"backupCodes": "Backup-Codes",
"badgeDataDescription": "CSV-Exporte für den Druck von Namensschildern. Enthält Teilnehmernamen, Länder und Einwilligungsstatus.",
"badgeDataTitle": "Namensschild-Daten",
"bankName": "Name der Bank",
Expand Down Expand Up @@ -319,6 +327,9 @@
"certificateTemplate": "Basis-PDF für das Zertifikat",
"changeAnswer": "Antwort ändern",
"changeDelegationPreferences": "Wünsche anpassen",
"changeEmail": "Ändern",
"changePassword": "Ändern",
"changeUsername": "Ändern",
"changesSuccessful": "Die Änderungen wurden erfolgreich gespeichert.",
"chaseSeedData": "CHASE Seed-Daten",
"checkEmails": "E-Mails prüfen",
Expand Down Expand Up @@ -824,6 +835,7 @@
"male": "Männlich",
"malformedPlaceholders": "Textbaustein enthält fehlerhafte Platzhalter. Stelle sicher, dass alle \\{\\{ ein passendes \\}\\} haben.",
"manageConference": "Konferenzeinstellungen und Teilnehmende verwalten",
"managePasskeys": "Verwalten",
"managePreferences": "Einstellungen verwalten",
"manageSnippets": "Textbausteine verwalten",
"markAsProblem": "Als Problem markieren",
Expand Down Expand Up @@ -1036,6 +1048,7 @@
"participation": "Teilnahme",
"participationCount": "Teilnahmen",
"participationType": "Teilnahmeart",
"passkeys": "Passkeys",
"password": "Passwort",
"pastConferences": "Vergangene Konferenzen",
"pathDoesNotExist": "Der Pfad {path} konnte nicht gefunden werden ({error})",
Expand Down Expand Up @@ -1382,6 +1395,7 @@
"snippets": "Textbausteine",
"specialRole": "Spezielle Rolle",
"specialWishes": "Spezielle Wünsche",
"ssoIdentities": "Verknüpfte Konten",
"start": "Start",
"startAssignment": "Assistent starten",
"startCamera": "Kamera starten",
Expand Down
14 changes: 14 additions & 0 deletions messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@
"accessFlowSaved": "Access card assigned and attendance recorded.",
"accountExists": "Account exists",
"accountHolder": "Account Holder",
"accountUpdateSuccessBackupCodes": "Your backup codes have been updated successfully.",
"accountUpdateSuccessEmail": "Your email address has been updated successfully.",
"accountUpdateSuccessMfa": "Your authenticator app settings have been updated successfully.",
"accountUpdateSuccessPasskey": "Your passkey settings have been updated successfully.",
"accountUpdateSuccessPassword": "Your password has been changed successfully.",
"accountUpdateSuccessUsername": "Your username has been updated successfully.",
"actions": "Actions",
"activeConferences": "Active Conferences",
"activeMembers": "Active members",
Expand Down Expand Up @@ -193,6 +199,7 @@
"authErrorTechnicalTitle": "Login Failed",
"authErrorTokenExchangeDescription": "Failed to complete the login process. This can happen if the login took too long or if there was a network issue.",
"authErrorTryAgain": "Try again",
"authenticatorApp": "Authenticator App",
"author": "Author",
"authorization": "Authorization",
"averageAgeOnlyApplied": "Average age at conference (registered only)",
Expand All @@ -201,6 +208,7 @@
"backToConferencePapers": "Back to Conference Papers",
"backToDashboard": "Back to Dashboard",
"backToHome": "Back To Home",
"backupCodes": "Backup Codes",
"badgeDataDescription": "CSV exports for badge and nametag printing. Includes participant names, countries, and consent status.",
"badgeDataTitle": "Badge Data",
"bankName": "Bank Name",
Expand Down Expand Up @@ -319,6 +327,9 @@
"certificateTemplate": "Base-PDF for the certificate",
"changeAnswer": "Change answer",
"changeDelegationPreferences": "Change Preferences",
"changeEmail": "Change",
"changePassword": "Change",
"changeUsername": "Change",
"changesSuccessful": "The changes were saved successfully.",
"chaseSeedData": "CHASE Seed Data",
"checkEmails": "Check Emails",
Expand Down Expand Up @@ -824,6 +835,7 @@
"male": "male",
"malformedPlaceholders": "Snippet contains malformed placeholders. Make sure all \\{\\{ have matching \\}\\}.",
"manageConference": "Manage conference settings and participants",
"managePasskeys": "Manage",
"managePreferences": "Manage settings",
"manageSnippets": "Manage Snippets",
"markAsProblem": "Mark as Problem",
Expand Down Expand Up @@ -1036,6 +1048,7 @@
"participation": "Participation",
"participationCount": "Participations",
"participationType": "Type of Participation",
"passkeys": "Passkeys",
"password": "Password",
"pastConferences": "Past Conferences",
"pathDoesNotExist": "The path {path} could not be found ( {error} )",
Expand Down Expand Up @@ -1382,6 +1395,7 @@
"snippets": "Snippets",
"specialRole": "Special role",
"specialWishes": "Special Wishes",
"ssoIdentities": "Connected Accounts",
"start": "Start",
"startAssignment": "Start Assignment",
"startCamera": "Start Camera",
Expand Down
4 changes: 1 addition & 3 deletions mock-oidc-landingpage.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,7 @@
given_name: faker.person.firstName(),
preferred_username: faker.internet.userName(),
locale: 'de',
'urn:zitadel:iam:org:project:275671427955294244:roles': {
admin: {}
}
roles: ['admin']
}
},
{
Expand Down
Loading
Loading