Merge pull request #496 from mauriceboe/main

Align dev
This commit is contained in:
Julien G.
2026-04-07 16:01:02 +02:00
committed by GitHub
23 changed files with 39 additions and 17 deletions
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "trek-client", "name": "trek-client",
"version": "2.9.10", "version": "2.9.11",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "trek-client", "name": "trek-client",
"version": "2.9.10", "version": "2.9.11",
"dependencies": { "dependencies": {
"@react-pdf/renderer": "^4.3.2", "@react-pdf/renderer": "^4.3.2",
"axios": "^1.6.7", "axios": "^1.6.7",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "trek-client", "name": "trek-client",
"version": "2.9.10", "version": "2.9.11",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
+1 -1
View File
@@ -82,7 +82,7 @@ export default function App() {
const { loadSettings } = useSettingsStore() const { loadSettings } = useSettingsStore()
useEffect(() => { useEffect(() => {
if (!location.pathname.startsWith('/shared/')) { if (!location.pathname.startsWith('/shared/') && !location.pathname.startsWith('/login')) {
loadUser() loadUser()
} }
authApi.getAppConfig().then(async (config: { demo_mode?: boolean; dev_mode?: boolean; has_maps_key?: boolean; version?: string; timezone?: string; require_mfa?: boolean; trip_reminders_enabled?: boolean; permissions?: Record<string, PermissionLevel> }) => { authApi.getAppConfig().then(async (config: { demo_mode?: boolean; dev_mode?: boolean; has_maps_key?: boolean; version?: string; timezone?: string; require_mfa?: boolean; trip_reminders_enabled?: boolean; permissions?: Record<string, PermissionLevel> }) => {
+1 -1
View File
@@ -53,7 +53,7 @@ export default function Navbar({ tripTitle, tripId, onBack, showBack, onShare }:
const handleLogout = () => { const handleLogout = () => {
logout() logout()
navigate('/login') navigate('/login', { state: { noRedirect: true } })
} }
const toggleDarkMode = () => { const toggleDarkMode = () => {
@@ -575,7 +575,7 @@ export default function AccountTab(): React.ReactElement {
try { try {
await authApi.deleteOwnAccount() await authApi.deleteOwnAccount()
logout() logout()
navigate('/login') navigate('/login', { state: { noRedirect: true } })
} catch (err: unknown) { } catch (err: unknown) {
toast.error(getApiErrorMessage(err, t('common.error'))) toast.error(getApiErrorMessage(err, t('common.error')))
setShowDeleteConfirm(false) setShowDeleteConfirm(false)
+1
View File
@@ -367,6 +367,7 @@ const ar: Record<string, string | { name: string; category: string }[]> = {
'login.demoFailed': 'فشل الدخول إلى العرض التجريبي', 'login.demoFailed': 'فشل الدخول إلى العرض التجريبي',
'login.oidcSignIn': 'تسجيل الدخول عبر {name}', 'login.oidcSignIn': 'تسجيل الدخول عبر {name}',
'login.oidcOnly': 'تم تعطيل المصادقة بكلمة المرور. يرجى تسجيل الدخول عبر مزود SSO.', 'login.oidcOnly': 'تم تعطيل المصادقة بكلمة المرور. يرجى تسجيل الدخول عبر مزود SSO.',
'login.oidcLoggedOut': 'تم تسجيل خروجك. سجّل الدخول مجدداً عبر مزود SSO.',
'login.demoHint': 'جرّب العرض التجريبي دون الحاجة للتسجيل', 'login.demoHint': 'جرّب العرض التجريبي دون الحاجة للتسجيل',
'login.mfaTitle': 'المصادقة الثنائية', 'login.mfaTitle': 'المصادقة الثنائية',
'login.mfaSubtitle': 'أدخل الرمز المكون من 6 أرقام من تطبيق المصادقة.', 'login.mfaSubtitle': 'أدخل الرمز المكون من 6 أرقام من تطبيق المصادقة.',
+1
View File
@@ -362,6 +362,7 @@ const br: Record<string, string | { name: string; category: string }[]> = {
'login.demoFailed': 'Falha no login de demonstração', 'login.demoFailed': 'Falha no login de demonstração',
'login.oidcSignIn': 'Entrar com {name}', 'login.oidcSignIn': 'Entrar com {name}',
'login.oidcOnly': 'Login por senha desativado. Use o provedor SSO.', 'login.oidcOnly': 'Login por senha desativado. Use o provedor SSO.',
'login.oidcLoggedOut': 'Você foi desconectado. Entre novamente usando o provedor SSO.',
'login.demoHint': 'Experimente a demonstração — sem cadastro', 'login.demoHint': 'Experimente a demonstração — sem cadastro',
'login.mfaTitle': 'Autenticação em duas etapas', 'login.mfaTitle': 'Autenticação em duas etapas',
'login.mfaSubtitle': 'Digite o código de 6 dígitos do seu app autenticador.', 'login.mfaSubtitle': 'Digite o código de 6 dígitos do seu app autenticador.',
+1
View File
@@ -362,6 +362,7 @@ const cs: Record<string, string | { name: string; category: string }[]> = {
'login.demoFailed': 'Přihlášení do dema se nezdařilo', 'login.demoFailed': 'Přihlášení do dema se nezdařilo',
'login.oidcSignIn': 'Přihlásit se přes {name}', 'login.oidcSignIn': 'Přihlásit se přes {name}',
'login.oidcOnly': 'Ověřování heslem je zakázáno. Přihlaste se prosím přes SSO poskytovatele.', 'login.oidcOnly': 'Ověřování heslem je zakázáno. Přihlaste se prosím přes SSO poskytovatele.',
'login.oidcLoggedOut': 'Byl jste odhlášen. Přihlaste se znovu přes SSO poskytovatele.',
'login.demoHint': 'Vyzkoušejte demo registrace není nutná', 'login.demoHint': 'Vyzkoušejte demo registrace není nutná',
'login.mfaTitle': 'Dvoufaktorové ověření', 'login.mfaTitle': 'Dvoufaktorové ověření',
'login.mfaSubtitle': 'Zadejte 6místný kód z vaší autentizační aplikace.', 'login.mfaSubtitle': 'Zadejte 6místný kód z vaší autentizační aplikace.',
+1
View File
@@ -362,6 +362,7 @@ const de: Record<string, string | { name: string; category: string }[]> = {
'login.demoFailed': 'Demo-Login fehlgeschlagen', 'login.demoFailed': 'Demo-Login fehlgeschlagen',
'login.oidcSignIn': 'Anmelden mit {name}', 'login.oidcSignIn': 'Anmelden mit {name}',
'login.oidcOnly': 'Passwort-Authentifizierung ist deaktiviert. Bitte melde dich über deinen SSO-Anbieter an.', 'login.oidcOnly': 'Passwort-Authentifizierung ist deaktiviert. Bitte melde dich über deinen SSO-Anbieter an.',
'login.oidcLoggedOut': 'Du wurdest abgemeldet. Melde dich erneut über deinen SSO-Anbieter an.',
'login.demoHint': 'Demo ausprobieren — ohne Registrierung', 'login.demoHint': 'Demo ausprobieren — ohne Registrierung',
'login.mfaTitle': 'Zwei-Faktor-Authentifizierung', 'login.mfaTitle': 'Zwei-Faktor-Authentifizierung',
'login.mfaSubtitle': 'Gib den 6-stelligen Code aus deiner Authenticator-App ein.', 'login.mfaSubtitle': 'Gib den 6-stelligen Code aus deiner Authenticator-App ein.',
+1
View File
@@ -383,6 +383,7 @@ const en: Record<string, string | { name: string; category: string }[]> = {
'login.demoFailed': 'Demo login failed', 'login.demoFailed': 'Demo login failed',
'login.oidcSignIn': 'Sign in with {name}', 'login.oidcSignIn': 'Sign in with {name}',
'login.oidcOnly': 'Password authentication is disabled. Please sign in using your SSO provider.', 'login.oidcOnly': 'Password authentication is disabled. Please sign in using your SSO provider.',
'login.oidcLoggedOut': 'You have been logged out. Sign in again using your SSO provider.',
'login.demoHint': 'Try the demo — no registration needed', 'login.demoHint': 'Try the demo — no registration needed',
'login.mfaTitle': 'Two-factor authentication', 'login.mfaTitle': 'Two-factor authentication',
'login.mfaSubtitle': 'Enter the 6-digit code from your authenticator app.', 'login.mfaSubtitle': 'Enter the 6-digit code from your authenticator app.',
+1
View File
@@ -1490,6 +1490,7 @@ const es: Record<string, string> = {
'admin.oidcOnlyMode': 'Desactivar autenticación por contraseña', 'admin.oidcOnlyMode': 'Desactivar autenticación por contraseña',
'admin.oidcOnlyModeHint': 'Si está activado, solo se permite el inicio de sesión con SSO. El inicio de sesión y registro con contraseña se bloquean.', 'admin.oidcOnlyModeHint': 'Si está activado, solo se permite el inicio de sesión con SSO. El inicio de sesión y registro con contraseña se bloquean.',
'login.oidcOnly': 'La autenticación por contraseña está desactivada. Por favor, inicia sesión con tu proveedor SSO.', 'login.oidcOnly': 'La autenticación por contraseña está desactivada. Por favor, inicia sesión con tu proveedor SSO.',
'login.oidcLoggedOut': 'Has cerrado sesión. Vuelve a iniciar sesión con tu proveedor SSO.',
// Settings (2.6.2) // Settings (2.6.2)
'settings.currentPasswordRequired': 'La contraseña actual es obligatoria', 'settings.currentPasswordRequired': 'La contraseña actual es obligatoria',
+1
View File
@@ -369,6 +369,7 @@ const fr: Record<string, string> = {
'login.demoFailed': 'Échec de la connexion démo', 'login.demoFailed': 'Échec de la connexion démo',
'login.oidcSignIn': 'Se connecter avec {name}', 'login.oidcSignIn': 'Se connecter avec {name}',
'login.oidcOnly': 'L\'authentification par mot de passe est désactivée. Veuillez vous connecter via votre fournisseur SSO.', 'login.oidcOnly': 'L\'authentification par mot de passe est désactivée. Veuillez vous connecter via votre fournisseur SSO.',
'login.oidcLoggedOut': 'Vous avez été déconnecté. Reconnectez-vous via votre fournisseur SSO.',
'login.demoHint': 'Essayez la démo — aucune inscription nécessaire', 'login.demoHint': 'Essayez la démo — aucune inscription nécessaire',
// Register // Register
+1
View File
@@ -362,6 +362,7 @@ const hu: Record<string, string | { name: string; category: string }[]> = {
'login.demoFailed': 'Demo bejelentkezés sikertelen', 'login.demoFailed': 'Demo bejelentkezés sikertelen',
'login.oidcSignIn': 'Bejelentkezés ezzel: {name}', 'login.oidcSignIn': 'Bejelentkezés ezzel: {name}',
'login.oidcOnly': 'A jelszavas hitelesítés le van tiltva. Kérjük, jelentkezz be az SSO szolgáltatódon keresztül.', 'login.oidcOnly': 'A jelszavas hitelesítés le van tiltva. Kérjük, jelentkezz be az SSO szolgáltatódon keresztül.',
'login.oidcLoggedOut': 'Kijelentkeztél. Jelentkezz be újra az SSO szolgáltatódon keresztül.',
'login.demoHint': 'Próbáld ki a demót — regisztráció nélkül', 'login.demoHint': 'Próbáld ki a demót — regisztráció nélkül',
'login.mfaTitle': 'Kétfaktoros hitelesítés', 'login.mfaTitle': 'Kétfaktoros hitelesítés',
'login.mfaSubtitle': 'Add meg a 6 jegyű kódot a hitelesítő alkalmazásból.', 'login.mfaSubtitle': 'Add meg a 6 jegyű kódot a hitelesítő alkalmazásból.',
+1
View File
@@ -362,6 +362,7 @@ const it: Record<string, string | { name: string; category: string }[]> = {
'login.demoFailed': 'Accesso demo fallito', 'login.demoFailed': 'Accesso demo fallito',
'login.oidcSignIn': 'Accedi con {name}', 'login.oidcSignIn': 'Accedi con {name}',
'login.oidcOnly': 'L\'autenticazione tramite password è disabilitata. Accedi utilizzando il tuo provider SSO.', 'login.oidcOnly': 'L\'autenticazione tramite password è disabilitata. Accedi utilizzando il tuo provider SSO.',
'login.oidcLoggedOut': 'Sei stato disconnesso. Accedi nuovamente tramite il tuo provider SSO.',
'login.demoHint': 'Prova la demo — nessuna registrazione necessaria', 'login.demoHint': 'Prova la demo — nessuna registrazione necessaria',
'login.mfaTitle': 'Autenticazione a due fattori', 'login.mfaTitle': 'Autenticazione a due fattori',
'login.mfaSubtitle': 'Inserisci il codice a 6 cifre dalla tua app authenticator.', 'login.mfaSubtitle': 'Inserisci il codice a 6 cifre dalla tua app authenticator.',
+1
View File
@@ -369,6 +369,7 @@ const nl: Record<string, string> = {
'login.demoFailed': 'Demo-login mislukt', 'login.demoFailed': 'Demo-login mislukt',
'login.oidcSignIn': 'Inloggen met {name}', 'login.oidcSignIn': 'Inloggen met {name}',
'login.oidcOnly': 'Wachtwoordauthenticatie is uitgeschakeld. Log in via je SSO-provider.', 'login.oidcOnly': 'Wachtwoordauthenticatie is uitgeschakeld. Log in via je SSO-provider.',
'login.oidcLoggedOut': 'Je bent uitgelogd. Log opnieuw in via je SSO-provider.',
'login.demoHint': 'Probeer de demo — geen registratie nodig', 'login.demoHint': 'Probeer de demo — geen registratie nodig',
// Register // Register
+1
View File
@@ -329,6 +329,7 @@ const pl: Record<string, string | { name: string; category: string }[]> = {
'login.demoFailed': 'Nie udało się zalogować do wersji demonstracyjnej', 'login.demoFailed': 'Nie udało się zalogować do wersji demonstracyjnej',
'login.oidcSignIn': 'Zaloguj się z {name}', 'login.oidcSignIn': 'Zaloguj się z {name}',
'login.oidcOnly': 'Uwierzytelnianie hasłem jest wyłączone. Zaloguj się za pomocą swojego dostawcy SSO.', 'login.oidcOnly': 'Uwierzytelnianie hasłem jest wyłączone. Zaloguj się za pomocą swojego dostawcy SSO.',
'login.oidcLoggedOut': 'Zostałeś wylogowany. Zaloguj się ponownie za pomocą swojego dostawcy SSO.',
'login.demoHint': 'Wypróbuj demo — nie wymaga rejestracji', 'login.demoHint': 'Wypróbuj demo — nie wymaga rejestracji',
'login.mfaTitle': 'Uwierzytelnianie dwuskładnikowe', 'login.mfaTitle': 'Uwierzytelnianie dwuskładnikowe',
'login.mfaSubtitle': 'Wprowadź 6-cyfrowy kod z aplikacji uwierzytelniającej.', 'login.mfaSubtitle': 'Wprowadź 6-cyfrowy kod z aplikacji uwierzytelniającej.',
+1
View File
@@ -369,6 +369,7 @@ const ru: Record<string, string> = {
'login.demoFailed': 'Ошибка демо-входа', 'login.demoFailed': 'Ошибка демо-входа',
'login.oidcSignIn': 'Войти через {name}', 'login.oidcSignIn': 'Войти через {name}',
'login.oidcOnly': 'Вход по паролю отключён. Используйте вашего провайдера SSO для входа.', 'login.oidcOnly': 'Вход по паролю отключён. Используйте вашего провайдера SSO для входа.',
'login.oidcLoggedOut': 'Вы вышли из системы. Войдите снова через вашего провайдера SSO.',
'login.demoHint': 'Попробуйте демо — регистрация не требуется', 'login.demoHint': 'Попробуйте демо — регистрация не требуется',
// Register // Register
+1
View File
@@ -369,6 +369,7 @@ const zh: Record<string, string> = {
'login.demoFailed': '演示登录失败', 'login.demoFailed': '演示登录失败',
'login.oidcSignIn': '通过 {name} 登录', 'login.oidcSignIn': '通过 {name} 登录',
'login.oidcOnly': '密码登录已关闭。请通过 SSO 提供商登录。', 'login.oidcOnly': '密码登录已关闭。请通过 SSO 提供商登录。',
'login.oidcLoggedOut': '您已退出登录。请重新通过 SSO 提供商登录。',
'login.demoHint': '试用演示——无需注册', 'login.demoHint': '试用演示——无需注册',
// Register // Register
+1
View File
@@ -353,6 +353,7 @@ const zhTw: Record<string, string> = {
'login.demoFailed': '演示登入失敗', 'login.demoFailed': '演示登入失敗',
'login.oidcSignIn': '透過 {name} 登入', 'login.oidcSignIn': '透過 {name} 登入',
'login.oidcOnly': '密碼登入已關閉。請透過 SSO 提供商登入。', 'login.oidcOnly': '密碼登入已關閉。請透過 SSO 提供商登入。',
'login.oidcLoggedOut': '您已登出。請重新透過 SSO 提供商登入。',
'login.demoHint': '試用演示——無需註冊', 'login.demoHint': '試用演示——無需註冊',
// Register // Register
+1 -1
View File
@@ -1551,7 +1551,7 @@ docker run -d --name trek \\
await adminApi.rotateJwtSecret() await adminApi.rotateJwtSecret()
setShowRotateJwtModal(false) setShowRotateJwtModal(false)
logout() logout()
navigate('/login') navigate('/login', { state: { noRedirect: true } })
} catch { } catch {
toast.error(t('common.error')) toast.error(t('common.error'))
setRotatingJwt(false) setRotatingJwt(false)
+15 -7
View File
@@ -1,5 +1,5 @@
import React, { useState, useEffect, useMemo } from 'react' import React, { useState, useEffect, useMemo, useRef } from 'react'
import { useNavigate } from 'react-router-dom' import { useNavigate, useLocation } from 'react-router-dom'
import { useAuthStore } from '../store/authStore' import { useAuthStore } from '../store/authStore'
import { useSettingsStore } from '../store/settingsStore' import { useSettingsStore } from '../store/settingsStore'
import { SUPPORTED_LANGUAGES, useTranslation } from '../i18n' import { SUPPORTED_LANGUAGES, useTranslation } from '../i18n'
@@ -29,10 +29,13 @@ export default function LoginPage(): React.ReactElement {
const [appConfig, setAppConfig] = useState<AppConfig | null>(null) const [appConfig, setAppConfig] = useState<AppConfig | null>(null)
const [inviteToken, setInviteToken] = useState<string>('') const [inviteToken, setInviteToken] = useState<string>('')
const [inviteValid, setInviteValid] = useState<boolean>(false) const [inviteValid, setInviteValid] = useState<boolean>(false)
const exchangeInitiated = useRef(false)
const { login, register, demoLogin, completeMfaLogin, loadUser } = useAuthStore() const { login, register, demoLogin, completeMfaLogin, loadUser } = useAuthStore()
const { setLanguageLocal } = useSettingsStore() const { setLanguageLocal } = useSettingsStore()
const navigate = useNavigate() const navigate = useNavigate()
const location = useLocation()
const noRedirect = !!(location.state as { noRedirect?: boolean } | null)?.noRedirect
const redirectTarget = useMemo(() => { const redirectTarget = useMemo(() => {
const params = new URLSearchParams(window.location.search) const params = new URLSearchParams(window.location.search)
@@ -63,11 +66,13 @@ export default function LoginPage(): React.ReactElement {
} }
if (oidcCode) { if (oidcCode) {
if (exchangeInitiated.current) return
exchangeInitiated.current = true
setIsLoading(true) setIsLoading(true)
window.history.replaceState({}, '', '/login')
fetch('/api/auth/oidc/exchange?code=' + encodeURIComponent(oidcCode), { credentials: 'include' }) fetch('/api/auth/oidc/exchange?code=' + encodeURIComponent(oidcCode), { credentials: 'include' })
.then(r => r.json()) .then(r => r.json())
.then(async data => { .then(async data => {
window.history.replaceState({}, '', '/login')
if (data.token) { if (data.token) {
await loadUser() await loadUser()
navigate('/dashboard', { replace: true }) navigate('/dashboard', { replace: true })
@@ -75,7 +80,10 @@ export default function LoginPage(): React.ReactElement {
setError(data.error || 'OIDC login failed') setError(data.error || 'OIDC login failed')
} }
}) })
.catch(() => setError('OIDC login failed')) .catch(() => {
window.history.replaceState({}, '', '/login')
setError('OIDC login failed')
})
.finally(() => setIsLoading(false)) .finally(() => setIsLoading(false))
return return
} }
@@ -96,12 +104,12 @@ export default function LoginPage(): React.ReactElement {
if (config) { if (config) {
setAppConfig(config) setAppConfig(config)
if (!config.has_users) setMode('register') if (!config.has_users) setMode('register')
if (config.oidc_only_mode && config.oidc_configured && config.has_users && !invite) { if (config.oidc_only_mode && config.oidc_configured && config.has_users && !invite && !noRedirect) {
window.location.href = '/api/auth/oidc/login' window.location.href = '/api/auth/oidc/login'
} }
} }
}) })
}, [navigate, t]) }, [navigate, t, noRedirect])
const handleDemoLogin = async (): Promise<void> => { const handleDemoLogin = async (): Promise<void> => {
setError('') setError('')
@@ -527,7 +535,7 @@ export default function LoginPage(): React.ReactElement {
{oidcOnly ? ( {oidcOnly ? (
<> <>
<h2 style={{ margin: '0 0 4px', fontSize: 22, fontWeight: 800, color: '#111827' }}>{t('login.title')}</h2> <h2 style={{ margin: '0 0 4px', fontSize: 22, fontWeight: 800, color: '#111827' }}>{t('login.title')}</h2>
<p style={{ margin: '0 0 24px', fontSize: 13.5, color: '#9ca3af' }}>{t('login.oidcOnly')}</p> <p style={{ margin: '0 0 24px', fontSize: 13.5, color: '#9ca3af' }}>{noRedirect ? t('login.oidcLoggedOut') : t('login.oidcOnly')}</p>
{error && ( {error && (
<div style={{ padding: '10px 14px', background: '#fef2f2', border: '1px solid #fecaca', borderRadius: 10, fontSize: 13, color: '#dc2626', marginBottom: 16 }}> <div style={{ padding: '10px 14px', background: '#fef2f2', border: '1px solid #fecaca', borderRadius: 10, fontSize: 13, color: '#dc2626', marginBottom: 16 }}>
{error} {error}
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "trek-server", "name": "trek-server",
"version": "2.9.10", "version": "2.9.11",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "trek-server", "name": "trek-server",
"version": "2.9.10", "version": "2.9.11",
"dependencies": { "dependencies": {
"@modelcontextprotocol/sdk": "^1.28.0", "@modelcontextprotocol/sdk": "^1.28.0",
"archiver": "^6.0.1", "archiver": "^6.0.1",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "trek-server", "name": "trek-server",
"version": "2.9.10", "version": "2.9.11",
"main": "src/index.ts", "main": "src/index.ts",
"scripts": { "scripts": {
"start": "node --import tsx src/index.ts", "start": "node --import tsx src/index.ts",