diff --git a/.env.example b/.env.example index 9b86496a..bef7f9c7 100644 --- a/.env.example +++ b/.env.example @@ -29,3 +29,11 @@ BODY_SIZE_LIMIT="10mb" # Security Configuration APP_SECRET="" # Secret key for encrypting sensitive data (generate with: openssl rand -hex 32) HTTP_MODE="http" # Set to "https" when served over TLS, so auth cookies get the secure flag + +# OIDC Authentication (optional - for SSO with Authentik or any OIDC provider) +OIDC_ENABLED=false +OIDC_ISSUER="" # OIDC issuer URL (e.g., https://authentik.example.com/application/o/tracktor/) +OIDC_CLIENT_ID="" +OIDC_CLIENT_SECRET="" +OIDC_REDIRECT_URI="" # Callback URL (e.g., http://localhost:5173/api/auth/oidc/callback) +OIDC_SCOPES="openid profile email" # Space-separated OIDC scopes diff --git a/docs/openwiki/domain/data-models.md b/docs/openwiki/domain/data-models.md index 52fb3cdf..11a0fc5f 100644 --- a/docs/openwiki/domain/data-models.md +++ b/docs/openwiki/domain/data-models.md @@ -128,7 +128,7 @@ Important keys: Tables: -- `users` — `id`, `username`, `passwordHash`, timestamps. +- `users` — `id`, `username`, `passwordHash`, timestamps, `oidc_id`, `oidc_provider`. - `sessions` — `id`, `userId`, `expiresAt`, timestamps. - `auth` — legacy table kept for migration compatibility. diff --git a/docs/openwiki/operations/runbook.md b/docs/openwiki/operations/runbook.md index de387b09..b9ffe472 100644 --- a/docs/openwiki/operations/runbook.md +++ b/docs/openwiki/operations/runbook.md @@ -30,6 +30,12 @@ Defined and typed in `src/lib/config/env.server.ts`. Public variables must be pr | `HTTP_MODE` | `http` | Set to `https` for secure cookies. | | `APP_VERSION` | branch name (dev) / package version (prod) | Override displayed version. | | `TRACKTOR_API_BASE_URL` | — | Optional external API base URL. | +| `OIDC_ENABLED` | `false` | Enables OIDC login | +| `OIDC_ISSUER` | `""` | Sets OIDC issuer | +| `OIDC_CLIENT_ID` | `""` | OIDC client ID | +| `OIDC_CLIENT_SECRET` | `""` | OIDC client secret | +| `OIDC_REDIRECT_URI` | `""` | OIDC redirect uri | +| `OIDC_SCOPES` | `openid profile email` | Scopes to request to OIDC | > Do not commit secrets. The repo includes `.env.example` with placeholder values. diff --git a/docs/openwiki/workflows/authentication.md b/docs/openwiki/workflows/authentication.md index 5f122950..242c7565 100644 --- a/docs/openwiki/workflows/authentication.md +++ b/docs/openwiki/workflows/authentication.md @@ -62,10 +62,15 @@ This is intended for local development or single-user trusted deployments, not p - changing the username, - changing the password after verifying the current password. +## OIDC authentication + +Set `OIDC_ENABLED=true` and all the `OIDC_` related variables to the corresponding values. + ## Source references - Auth schema: `src/server/db/schema/auth.ts` - Auth service: `src/server/services/authService.ts` +- Oidc service: `src/server/services/oidcService.ts` - Session utilities: `src/server/utils/session.ts` - Auth middleware: `src/server/middlewares/auth.ts` - Auth API route: `src/routes/api/auth/+server.ts` diff --git a/i18n/messages/ar.json b/i18n/messages/ar.json index b6788748..baf8cd32 100644 --- a/i18n/messages/ar.json +++ b/i18n/messages/ar.json @@ -18,6 +18,9 @@ "auth_login_loading": "جاري تسجيل الدخول...", "auth_signup_loading": "جاري إنشاء الحساب...", "auth_password_mismatch": "كلمات المرور غير متطابقة!!!", + "auth_login_or": "أو", + "auth_sso_login_button": "تسجيل الدخول عبر SSO", + "auth_sso_login_loading": "جارٍ إعادة التوجيه...", "settings_tab_personalization": "التخصيص", "settings_tab_interface": "الواجهة", "settings_tab_features": "الميزات", diff --git a/i18n/messages/de.json b/i18n/messages/de.json index 2862677f..57c3f7bc 100644 --- a/i18n/messages/de.json +++ b/i18n/messages/de.json @@ -18,6 +18,9 @@ "auth_login_loading": "Anmeldung läuft...", "auth_signup_loading": "Konto wird erstellt...", "auth_password_mismatch": "Passwörter stimmen nicht überein!!!", + "auth_login_or": "oder", + "auth_sso_login_button": "Mit SSO anmelden", + "auth_sso_login_loading": "Weiterleitung...", "settings_tab_personalization": "Personalisierung", "settings_tab_interface": "Oberfläche", "settings_tab_features": "Funktionen", diff --git a/i18n/messages/en.json b/i18n/messages/en.json index fb0349ca..68c6cb56 100644 --- a/i18n/messages/en.json +++ b/i18n/messages/en.json @@ -23,6 +23,9 @@ "auth_login_loading": "Signing in...", "auth_signup_loading": "Creating account...", "auth_password_mismatch": "Passwords do not match!!!", + "auth_login_or": "or", + "auth_sso_login_button": "Login with SSO", + "auth_sso_login_loading": "Redirecting...", "auth_login_title": "Welcome back", "auth_login_subtitle": "Sign in to keep your vehicles, fuel, and paperwork on track.", "settings_tab_personalization": "Personalization", diff --git a/i18n/messages/es.json b/i18n/messages/es.json index 29b5f60e..86846d40 100644 --- a/i18n/messages/es.json +++ b/i18n/messages/es.json @@ -18,6 +18,9 @@ "auth_login_loading": "Iniciando sesión...", "auth_signup_loading": "Creando cuenta...", "auth_password_mismatch": "¡Las contraseñas no coinciden!", + "auth_login_or": "o", + "auth_sso_login_button": "Iniciar sesión con SSO", + "auth_sso_login_loading": "Redirigiendo...", "settings_tab_personalization": "Personalización", "settings_tab_interface": "Interfaz", "settings_tab_features": "Funciones", diff --git a/i18n/messages/fi.json b/i18n/messages/fi.json index 1b140bd8..6f9bb62c 100644 --- a/i18n/messages/fi.json +++ b/i18n/messages/fi.json @@ -19,6 +19,9 @@ "auth_login_loading": "Kirjaudutaan sisään...", "auth_signup_loading": "Luodaan tiliä...", "auth_password_mismatch": "Salasanat eivät täsmää!", + "auth_login_or": "tai", + "auth_sso_login_button": "Kirjaudu sisään SSO:lla", + "auth_sso_login_loading": "Uudelleenohjataan...", "settings_tab_personalization": "Mukauta", "settings_tab_interface": "Käyttöliittymä", "settings_tab_features": "Ominaisuudet", diff --git a/i18n/messages/fr.json b/i18n/messages/fr.json index 65537fa6..d17193e7 100644 --- a/i18n/messages/fr.json +++ b/i18n/messages/fr.json @@ -18,6 +18,9 @@ "auth_login_loading": "Connexion en cours...", "auth_signup_loading": "Création du compte...", "auth_password_mismatch": "Les mots de passe ne correspondent pas !!!", + "auth_login_or": "ou", + "auth_sso_login_button": "Se connecter avec SSO", + "auth_sso_login_loading": "Redirection...", "settings_tab_personalization": "Personnalisation", "settings_tab_interface": "Interface", "settings_tab_features": "Fonctionnalités", diff --git a/i18n/messages/hi.json b/i18n/messages/hi.json index 0795d5e1..1f44ea01 100644 --- a/i18n/messages/hi.json +++ b/i18n/messages/hi.json @@ -18,6 +18,9 @@ "auth_login_loading": "साइन इन हो रहा है...", "auth_signup_loading": "खाता बनाया जा रहा है...", "auth_password_mismatch": "पासवर्ड मेल नहीं खाते!!!", + "auth_login_or": "या", + "auth_sso_login_button": "SSO से लॉगिन करें", + "auth_sso_login_loading": "पुनर्निर्देशित किया जा रहा है...", "settings_tab_personalization": "व्यक्तिकरण", "settings_tab_interface": "इंटरफ़ेस", "settings_tab_features": "फ़ीचर्स", diff --git a/i18n/messages/hu.json b/i18n/messages/hu.json index 82c60cd8..e331edf8 100644 --- a/i18n/messages/hu.json +++ b/i18n/messages/hu.json @@ -19,6 +19,9 @@ "auth_login_loading": "Bejelentkezés...", "auth_signup_loading": "Fiók létrehozása...", "auth_password_mismatch": "A jelszavak nem egyeznek!", + "auth_login_or": "vagy", + "auth_sso_login_button": "Bejelentkezés SSO-val", + "auth_sso_login_loading": "Átirányítás...", "settings_tab_personalization": "Testreszabás", "settings_tab_interface": "Kezelőfelület", "settings_tab_features": "Funkciók", diff --git a/i18n/messages/it.json b/i18n/messages/it.json index 1c2384b4..9809571c 100644 --- a/i18n/messages/it.json +++ b/i18n/messages/it.json @@ -18,6 +18,9 @@ "auth_login_loading": "Accesso in corso...", "auth_signup_loading": "Creazione account...", "auth_password_mismatch": "Le password non corrispondono!!!", + "auth_login_or": "o", + "auth_sso_login_button": "Accedi con SSO", + "auth_sso_login_loading": "Reindirizzamento...", "settings_tab_personalization": "Personalizzazione", "settings_tab_interface": "Interfaccia", "settings_tab_features": "Funzionalità", diff --git a/i18n/messages/ro.json b/i18n/messages/ro.json index e6cd73ec..f77ec0b5 100644 --- a/i18n/messages/ro.json +++ b/i18n/messages/ro.json @@ -19,6 +19,9 @@ "auth_login_loading": "Se autentifică...", "auth_signup_loading": "Se creează contul...", "auth_password_mismatch": "Parolele nu se potrivesc!!!", + "auth_login_or": "sau", + "auth_sso_login_button": "Autentificare cu SSO", + "auth_sso_login_loading": "Redirecționare...", "settings_tab_personalization": "Personalizare", "settings_tab_interface": "Interfață", "settings_tab_features": "Funcționalități", diff --git a/package.json b/package.json index 7d7b43c7..18a6ed12 100644 --- a/package.json +++ b/package.json @@ -87,6 +87,7 @@ "mode-watcher": "^1.1.0", "node-cron": "^4.6.0", "nodemailer": "^9.0.4", + "openid-client": "^6.8.4", "pdfkit": "^0.19.1", "svelte-dnd-action": "^0.9.78", "zod": "^4.4.3" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e54befe4..37c8e62d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -44,6 +44,9 @@ importers: nodemailer: specifier: ^9.0.4 version: 9.0.4 + openid-client: + specifier: ^6.8.4 + version: 6.8.4 pdfkit: specifier: ^0.19.1 version: 0.19.1 @@ -2356,6 +2359,9 @@ packages: joi@17.13.4: resolution: {integrity: sha512-1RuuER6kmt8K8I3nIWvPZKi5RQCb568ZPyY4Pwjlua+yo+63ZTmIwxLZH0heBmiKN4uxjvCiarDrjaeH84xicQ==} + jose@6.2.4: + resolution: {integrity: sha512-N8acGzVsQy6M/fjFcxtysNc4Q379TcM5dM/qKkNtsHFji88yANnXTr7BLeP75iPnFwBfQzM/jg2BZ9+HZrHCZA==} + js-base64@3.7.8: resolution: {integrity: sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==} @@ -2689,9 +2695,15 @@ packages: nub@0.0.0: resolution: {integrity: sha512-dK0Ss9C34R/vV0FfYJXuqDAqHlaW9fvWVufq9MmGF2umCuDbd5GRfRD9fpi/LiM0l4ZXf8IBB+RYmZExqCrf0w==} + oauth4webapi@3.8.6: + resolution: {integrity: sha512-iwemM91xz8nryHti2yTmg5fhyEMVOkOXwHNqbvcATjyajb5oQxCQzrNOA6uElRHuMhQQTKUyFKV9y/CNyg25BQ==} + obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + openid-client@6.8.4: + resolution: {integrity: sha512-QSw0BA08piujetEwfZsHoTrDpMEha7GDZDicQqVwX4u0ChCjefvjDB++TZ8BTg76UpwhzIQgdvvfgfl3HpCSAw==} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -5223,6 +5235,8 @@ snapshots: '@sideway/pinpoint': 2.0.0 optional: true + jose@6.2.4: {} + js-base64@3.7.8: {} js-md5@0.8.3: {} @@ -5525,8 +5539,15 @@ snapshots: nub@0.0.0: {} + oauth4webapi@3.8.6: {} + obug@2.1.1: {} + openid-client@6.8.4: + dependencies: + jose: 6.2.4 + oauth4webapi: 3.8.6 + optionator@0.9.4: dependencies: deep-is: 0.1.4 diff --git a/src/app.d.ts b/src/app.d.ts index 2f1d244f..d5ec2c5b 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -36,6 +36,12 @@ declare namespace NodeJS { APP_VERSION?: string; TRACKTOR_DISABLE_AUTH: string; TRACKTOR_API_BASE_URL?: string; + OIDC_ENABLED: string; + OIDC_ISSUER: string; + OIDC_CLIENT_ID: string; + OIDC_CLIENT_SECRET: string; + OIDC_REDIRECT_URI: string; + OIDC_SCOPES: string; } } diff --git a/src/lib/components/feature/auth/login-form.svelte b/src/lib/components/feature/auth/login-form.svelte index f18f9375..834259ce 100644 --- a/src/lib/components/feature/auth/login-form.svelte +++ b/src/lib/components/feature/auth/login-form.svelte @@ -5,6 +5,7 @@ import { goto } from '$app/navigation'; import UserIcon from '@lucide/svelte/icons/circle-user-round'; import RectangleEllipsis from '@lucide/svelte/icons/rectangle-ellipsis'; + import LogIn from '@lucide/svelte/icons/log-in'; import SubmitButton from '$appui/SubmitButton.svelte'; import * as m from '$lib/paraglide/messages'; @@ -12,6 +13,7 @@ let username = $state(''); let password = $state(''); let processing = $state(false); + let oidcProcessing = $state(false); $effect(() => { // Check if users exist when component mounts @@ -82,3 +84,25 @@ + +{#if authStore.oidcEnabled && !authStore.isAuthDisabled} +