diff --git a/frontend/src/lib/api/client.ts b/frontend/src/lib/api/client.ts index 82fe7e9b..445e1ccf 100644 --- a/frontend/src/lib/api/client.ts +++ b/frontend/src/lib/api/client.ts @@ -35,7 +35,10 @@ const SERVER_STATUS_HEADER = 'x-server-status'; const REFRESH_ENDPOINT = '/api/auth/refresh'; -/** Auth primitives — a 401 here is genuine, never an expired access token. */ +/** Auth primitives — a 401 here is genuine, never an expired access token. + * Also used by the session-teardown gate to exempt endpoints that must + * still fire during / immediately after a logout (the logout POST itself, + * and every login path a user might retry on the /login landing). */ const AUTH_PRIMITIVES = [ '/api/auth/login', '/api/auth/logout', @@ -43,7 +46,11 @@ const AUTH_PRIMITIVES = [ '/api/auth/register', '/api/auth/setup', '/api/auth/oidc/', - '/api/auth/device/' + '/api/auth/device/', + '/api/auth/opaque/', + '/api/auth/magic-link/', + '/api/auth/status', + '/api/auth/dpop/' ]; export type FetchFn = typeof fetch; diff --git a/frontend/src/lib/components/AppShell.svelte b/frontend/src/lib/components/AppShell.svelte index 6200c1ec..12047e12 100644 --- a/frontend/src/lib/components/AppShell.svelte +++ b/frontend/src/lib/components/AppShell.svelte @@ -524,7 +524,11 @@ // landing from `?source=session_expired` (auto-divert on 401 → // refresh 401). The login page reads the flag, shows the success // notice, and skips its existing-session probe. - await goto(resolve('/login?source=logged_out')); + // `resolve()` from `$app/paths` normalises a route-id (adds base + // path prefix); it does NOT accept a query string. Concatenate + // the `?source=logged_out` marker AFTER resolve so the login + // page's onMount branch actually sees it. + await goto(resolve('/login') + '?source=logged_out'); } diff --git a/frontend/src/lib/stores/session.svelte.ts b/frontend/src/lib/stores/session.svelte.ts index 2e724f70..82046670 100644 --- a/frontend/src/lib/stores/session.svelte.ts +++ b/frontend/src/lib/stores/session.svelte.ts @@ -7,6 +7,7 @@ * folder and land on the shared-with-me view. */ import { bindDpopIfPossible, fetchMe, tryRefresh } from '$lib/api/endpoints/auth'; +import { setLogoutInProgress } from '$lib/api/client'; import { drives } from '$lib/stores/drives.svelte'; import type { User } from '$lib/api/types'; import { ensureActiveUser } from '$lib/utils/localStoragePrefs'; @@ -77,6 +78,12 @@ class SessionStore { setUser(user: User): void { this.user = user; ensureActiveUser(user.id); + // Any successful login clears the session-teardown gate. Without + // this, a logout → login within the same SPA session leaves the + // gate stuck at `true` — the login POST is exempted via + // `AUTH_PRIMITIVES`, but the /me + /drives + … fetches the app + // fires post-login would all abort with "Session terminated". + setLogoutInProgress(false); } /** diff --git a/frontend/static/locales/ar.json b/frontend/static/locales/ar.json index f4017a46..1a6c1bf4 100644 --- a/frontend/static/locales/ar.json +++ b/frontend/static/locales/ar.json @@ -843,6 +843,28 @@ "quotas": "الحصص", "reset_pw_for": "كلمة مرور جديدة لـ", "role": "الدور", + "sessions": { + "filter_user": "المستخدم (UUID)", + "include_revoked": "تضمين الملغاة / المنتهية", + "refresh": "تحديث", + "col_user": "المستخدم", + "col_created": "تم الإنشاء", + "col_expires": "تنتهي في", + "col_ip": "IP", + "col_user_agent": "وكيل المستخدم", + "col_bound": "مرتبطة", + "col_status": "الحالة", + "current_tooltip": "هذه هي الجلسة التي تستخدمها حاليًا — إلغاؤها سيؤدي إلى تسجيل خروجك.", + "bound_tooltip": "مرتبطة بـ DPoP (jkt {{prefix}}…)", + "unbound": "غير مرتبطة", + "revoked": "ملغاة", + "expired": "منتهية", + "active": "نشطة", + "revoke": "إلغاء", + "empty": "لا توجد جلسات مطابقة للتصفية الحالية.", + "revoke_self_confirm": "⚠️ هذه جلستك الحالية. إلغاؤها سيؤدي إلى تسجيل خروجك فورًا وعليك تسجيل الدخول مرة أخرى. هل تريد المتابعة؟", + "revoke_confirm": "إلغاء هذه الجلسة؟ سيتلقى المتصفح رمز 401 عند الطلب التالي." + }, "smtp_fail": "فشل الإرسال.", "smtp_send": "إرسال", "smtp_test": "إرسال بريد اختباري", diff --git a/frontend/static/locales/de.json b/frontend/static/locales/de.json index 1a310897..53eb5701 100644 --- a/frontend/static/locales/de.json +++ b/frontend/static/locales/de.json @@ -843,6 +843,28 @@ "quotas": "Kontingente", "reset_pw_for": "Neues Passwort für", "role": "Rolle", + "sessions": { + "filter_user": "Benutzer (UUID)", + "include_revoked": "Widerrufene / abgelaufene einschließen", + "refresh": "Aktualisieren", + "col_user": "Benutzer", + "col_created": "Erstellt", + "col_expires": "Läuft ab", + "col_ip": "IP", + "col_user_agent": "User Agent", + "col_bound": "Gebunden", + "col_status": "Status", + "current_tooltip": "Dies ist die Sitzung, die Sie gerade nutzen — der Widerruf meldet Sie ab.", + "bound_tooltip": "DPoP-gebunden (jkt {{prefix}}…)", + "unbound": "ungebunden", + "revoked": "widerrufen", + "expired": "abgelaufen", + "active": "aktiv", + "revoke": "Widerrufen", + "empty": "Keine Sitzungen entsprechen dem aktuellen Filter.", + "revoke_self_confirm": "⚠️ Dies ist IHRE aktuelle Sitzung. Wenn Sie sie widerrufen, werden Sie sofort abgemeldet und müssen sich neu anmelden. Fortfahren?", + "revoke_confirm": "Diese Sitzung widerrufen? Die nächste Anfrage von diesem Browser erhält 401." + }, "smtp_fail": "Senden fehlgeschlagen.", "smtp_send": "Senden", "smtp_test": "Test-E-Mail senden", diff --git a/frontend/static/locales/en.json b/frontend/static/locales/en.json index 0f63edc7..4403ad63 100644 --- a/frontend/static/locales/en.json +++ b/frontend/static/locales/en.json @@ -1115,6 +1115,28 @@ "registration_disabled_warning": "Public registration is disabled. Only admins can create new accounts.", "reset_pw_for": "New password for", "role": "Role", + "sessions": { + "filter_user": "User (UUID)", + "include_revoked": "Include revoked / expired", + "refresh": "Refresh", + "col_user": "User", + "col_created": "Created", + "col_expires": "Expires", + "col_ip": "IP", + "col_user_agent": "User agent", + "col_bound": "Bound", + "col_status": "Status", + "current_tooltip": "This is the session you're using right now — revoking it will log you out.", + "bound_tooltip": "DPoP-bound (jkt {{prefix}}…)", + "unbound": "unbound", + "revoked": "revoked", + "expired": "expired", + "active": "active", + "revoke": "Revoke", + "empty": "No sessions match the current filter.", + "revoke_self_confirm": "⚠️ This is YOUR current session. Revoking it will log YOU out immediately and you'll have to sign back in. Continue?", + "revoke_confirm": "Revoke this session? The next request from that browser will 401." + }, "settings_saved_ok": "Settings saved.", "smtp": "Email (SMTP)", "smtp_fail": "Send failed.", diff --git a/frontend/static/locales/es.json b/frontend/static/locales/es.json index a7657360..a2bf1b0b 100644 --- a/frontend/static/locales/es.json +++ b/frontend/static/locales/es.json @@ -848,6 +848,28 @@ "quotas": "Cuotas", "reset_pw_for": "Nueva contraseña para", "role": "Rol", + "sessions": { + "filter_user": "Usuario (UUID)", + "include_revoked": "Incluir revocadas / caducadas", + "refresh": "Actualizar", + "col_user": "Usuario", + "col_created": "Creada", + "col_expires": "Caduca", + "col_ip": "IP", + "col_user_agent": "Agente de usuario", + "col_bound": "Vinculada", + "col_status": "Estado", + "current_tooltip": "Esta es la sesión que está usando ahora mismo — al revocarla se cerrará su sesión.", + "bound_tooltip": "Vinculada con DPoP (jkt {{prefix}}…)", + "unbound": "no vinculada", + "revoked": "revocada", + "expired": "caducada", + "active": "activa", + "revoke": "Revocar", + "empty": "Ninguna sesión coincide con el filtro actual.", + "revoke_self_confirm": "⚠️ Esta es SU sesión actual. Al revocarla se cerrará su sesión inmediatamente y tendrá que iniciar sesión de nuevo. ¿Continuar?", + "revoke_confirm": "¿Revocar esta sesión? La próxima petición desde ese navegador devolverá 401." + }, "smtp_fail": "Fallo al enviar.", "smtp_send": "Enviar", "smtp_test": "Enviar correo de prueba", diff --git a/frontend/static/locales/fa.json b/frontend/static/locales/fa.json index 66fca898..fb1fbab5 100644 --- a/frontend/static/locales/fa.json +++ b/frontend/static/locales/fa.json @@ -826,6 +826,28 @@ "quotas": "سهمیه‌ها", "reset_pw_for": "رمز جدید برای", "role": "نقش", + "sessions": { + "filter_user": "کاربر (UUID)", + "include_revoked": "شامل لغو‌شده‌ها / منقضی‌شده‌ها", + "refresh": "به‌روزرسانی", + "col_user": "کاربر", + "col_created": "ایجاد شده", + "col_expires": "انقضا", + "col_ip": "IP", + "col_user_agent": "عامل کاربر", + "col_bound": "متصل", + "col_status": "وضعیت", + "current_tooltip": "این همان نشستی است که هم‌اکنون از آن استفاده می‌کنید — لغو آن شما را خارج می‌کند.", + "bound_tooltip": "متصل به DPoP (jkt {{prefix}}…)", + "unbound": "متصل نیست", + "revoked": "لغو شده", + "expired": "منقضی", + "active": "فعال", + "revoke": "لغو", + "empty": "هیچ نشستی با فیلتر فعلی مطابقت ندارد.", + "revoke_self_confirm": "⚠️ این نشست فعلی شماست. با لغو آن، بلافاصله خارج می‌شوید و باید دوباره وارد شوید. ادامه؟", + "revoke_confirm": "این نشست لغو شود؟ درخواست بعدی از آن مرورگر با 401 پاسخ داده می‌شود." + }, "smtp_fail": "ارسال ناموفق.", "smtp_send": "ارسال", "smtp_test": "ارسال ایمیل آزمایشی", diff --git a/frontend/static/locales/fr.json b/frontend/static/locales/fr.json index d0ae7c0b..6b980ebf 100644 --- a/frontend/static/locales/fr.json +++ b/frontend/static/locales/fr.json @@ -854,6 +854,28 @@ "quotas": "Quotas", "reset_pw_for": "Nouveau mot de passe pour", "role": "Rôle", + "sessions": { + "filter_user": "Utilisateur (UUID)", + "include_revoked": "Inclure les révoquées / expirées", + "refresh": "Actualiser", + "col_user": "Utilisateur", + "col_created": "Créée", + "col_expires": "Expire", + "col_ip": "IP", + "col_user_agent": "Agent utilisateur", + "col_bound": "Liée", + "col_status": "Statut", + "current_tooltip": "Il s'agit de la session que vous utilisez actuellement — la révoquer vous déconnectera.", + "bound_tooltip": "Liée par DPoP (jkt {{prefix}}…)", + "unbound": "non liée", + "revoked": "révoquée", + "expired": "expirée", + "active": "active", + "revoke": "Révoquer", + "empty": "Aucune session ne correspond au filtre actuel.", + "revoke_self_confirm": "⚠️ Il s'agit de VOTRE session actuelle. La révoquer vous déconnectera immédiatement et vous devrez vous reconnecter. Continuer ?", + "revoke_confirm": "Révoquer cette session ? La prochaine requête depuis ce navigateur renverra 401." + }, "smtp_fail": "Échec de l'envoi.", "smtp_send": "Envoyer", "smtp_test": "Envoyer l'e-mail de test", diff --git a/frontend/static/locales/hi.json b/frontend/static/locales/hi.json index 08595261..fbd3c38d 100644 --- a/frontend/static/locales/hi.json +++ b/frontend/static/locales/hi.json @@ -843,6 +843,28 @@ "quotas": "कोटा", "reset_pw_for": "नया पासवर्ड", "role": "भूमिका", + "sessions": { + "filter_user": "उपयोगकर्ता (UUID)", + "include_revoked": "रद्द / समाप्त शामिल करें", + "refresh": "ताज़ा करें", + "col_user": "उपयोगकर्ता", + "col_created": "बनाई गई", + "col_expires": "समाप्त होगी", + "col_ip": "IP", + "col_user_agent": "उपयोगकर्ता एजेंट", + "col_bound": "बद्ध", + "col_status": "स्थिति", + "current_tooltip": "यह वह सत्र है जिसे आप अभी उपयोग कर रहे हैं — इसे रद्द करने पर आप साइन आउट हो जाएंगे।", + "bound_tooltip": "DPoP से बद्ध (jkt {{prefix}}…)", + "unbound": "अबद्ध", + "revoked": "रद्द की गई", + "expired": "समाप्त", + "active": "सक्रिय", + "revoke": "रद्द करें", + "empty": "वर्तमान फ़िल्टर से कोई सत्र मेल नहीं खाता।", + "revoke_self_confirm": "⚠️ यह आपका वर्तमान सत्र है। इसे रद्द करने पर आप तुरंत साइन आउट हो जाएंगे और आपको दोबारा साइन इन करना होगा। जारी रखें?", + "revoke_confirm": "इस सत्र को रद्द करें? उस ब्राउज़र से अगला अनुरोध 401 होगा।" + }, "smtp_fail": "भेजना विफल।", "smtp_send": "भेजें", "smtp_test": "परीक्षण ईमेल भेजें", diff --git a/frontend/static/locales/it.json b/frontend/static/locales/it.json index 2970096f..5cefa8f9 100644 --- a/frontend/static/locales/it.json +++ b/frontend/static/locales/it.json @@ -843,6 +843,28 @@ "quotas": "Quote", "reset_pw_for": "Nuova password per", "role": "Ruolo", + "sessions": { + "filter_user": "Utente (UUID)", + "include_revoked": "Includi revocate / scadute", + "refresh": "Aggiorna", + "col_user": "Utente", + "col_created": "Creata", + "col_expires": "Scade", + "col_ip": "IP", + "col_user_agent": "User agent", + "col_bound": "Collegata", + "col_status": "Stato", + "current_tooltip": "Questa è la sessione che stai usando adesso — revocarla ti disconnetterà.", + "bound_tooltip": "Collegata a DPoP (jkt {{prefix}}…)", + "unbound": "non collegata", + "revoked": "revocata", + "expired": "scaduta", + "active": "attiva", + "revoke": "Revoca", + "empty": "Nessuna sessione corrisponde al filtro attuale.", + "revoke_self_confirm": "⚠️ Questa è la TUA sessione corrente. Revocarla ti disconnetterà immediatamente e dovrai accedere di nuovo. Continuare?", + "revoke_confirm": "Revocare questa sessione? La prossima richiesta da quel browser risponderà 401." + }, "smtp_fail": "Invio non riuscito.", "smtp_send": "Invia", "smtp_test": "Invia email di prova", diff --git a/frontend/static/locales/ja.json b/frontend/static/locales/ja.json index 61eaae0a..8ee1b2bb 100644 --- a/frontend/static/locales/ja.json +++ b/frontend/static/locales/ja.json @@ -843,6 +843,28 @@ "quotas": "クォータ", "reset_pw_for": "新しいパスワード:", "role": "役割", + "sessions": { + "filter_user": "ユーザー (UUID)", + "include_revoked": "取り消し済み / 期限切れを含む", + "refresh": "更新", + "col_user": "ユーザー", + "col_created": "作成日時", + "col_expires": "有効期限", + "col_ip": "IP", + "col_user_agent": "ユーザーエージェント", + "col_bound": "バインド", + "col_status": "状態", + "current_tooltip": "これは現在使用中のセッションです — 取り消すとサインアウトされます。", + "bound_tooltip": "DPoP にバインド済み (jkt {{prefix}}…)", + "unbound": "バインドなし", + "revoked": "取り消し済み", + "expired": "期限切れ", + "active": "有効", + "revoke": "取り消し", + "empty": "現在のフィルターに一致するセッションはありません。", + "revoke_self_confirm": "⚠️ これはあなたの現在のセッションです。取り消すと直ちにサインアウトされ、再度サインインが必要になります。続行しますか?", + "revoke_confirm": "このセッションを取り消しますか? そのブラウザからの次のリクエストは 401 になります。" + }, "smtp_fail": "送信に失敗しました。", "smtp_send": "送信", "smtp_test": "テストメールを送信", diff --git a/frontend/static/locales/ko.json b/frontend/static/locales/ko.json index 5496d5e8..1f0f9ede 100644 --- a/frontend/static/locales/ko.json +++ b/frontend/static/locales/ko.json @@ -970,6 +970,28 @@ "quotas": "할당량", "reset_pw_for": "새 비밀번호:", "role": "역할", + "sessions": { + "filter_user": "사용자 (UUID)", + "include_revoked": "취소됨 / 만료됨 포함", + "refresh": "새로 고침", + "col_user": "사용자", + "col_created": "생성일", + "col_expires": "만료일", + "col_ip": "IP", + "col_user_agent": "사용자 에이전트", + "col_bound": "바인딩", + "col_status": "상태", + "current_tooltip": "지금 사용 중인 세션입니다 — 이 세션을 취소하면 로그아웃됩니다.", + "bound_tooltip": "DPoP 바인딩됨 (jkt {{prefix}}…)", + "unbound": "미바인딩", + "revoked": "취소됨", + "expired": "만료됨", + "active": "활성", + "revoke": "취소", + "empty": "현재 필터와 일치하는 세션이 없습니다.", + "revoke_self_confirm": "⚠️ 현재 사용 중인 세션입니다. 취소하면 즉시 로그아웃되고 다시 로그인해야 합니다. 계속하시겠습니까?", + "revoke_confirm": "이 세션을 취소하시겠습니까? 해당 브라우저의 다음 요청은 401을 받게 됩니다." + }, "smtp_fail": "전송 실패.", "smtp_send": "보내기", "smtp_test": "테스트 이메일 보내기", diff --git a/frontend/static/locales/nl.json b/frontend/static/locales/nl.json index d8a891a0..bf60deba 100644 --- a/frontend/static/locales/nl.json +++ b/frontend/static/locales/nl.json @@ -843,6 +843,28 @@ "quotas": "Quota", "reset_pw_for": "Nieuw wachtwoord voor", "role": "Rol", + "sessions": { + "filter_user": "Gebruiker (UUID)", + "include_revoked": "Ingetrokken / verlopen tonen", + "refresh": "Vernieuwen", + "col_user": "Gebruiker", + "col_created": "Aangemaakt", + "col_expires": "Verloopt", + "col_ip": "IP", + "col_user_agent": "User agent", + "col_bound": "Gebonden", + "col_status": "Status", + "current_tooltip": "Dit is de sessie die u nu gebruikt — intrekken meldt u af.", + "bound_tooltip": "Gebonden met DPoP (jkt {{prefix}}…)", + "unbound": "niet gebonden", + "revoked": "ingetrokken", + "expired": "verlopen", + "active": "actief", + "revoke": "Intrekken", + "empty": "Geen sessies komen overeen met het huidige filter.", + "revoke_self_confirm": "⚠️ Dit is UW huidige sessie. Intrekken meldt u direct af en u moet opnieuw inloggen. Doorgaan?", + "revoke_confirm": "Deze sessie intrekken? Het volgende verzoek van die browser krijgt 401." + }, "smtp_fail": "Verzenden mislukt.", "smtp_send": "Verzenden", "smtp_test": "Test-e-mail verzenden", diff --git a/frontend/static/locales/pl.json b/frontend/static/locales/pl.json index 31a49a92..f30660d0 100644 --- a/frontend/static/locales/pl.json +++ b/frontend/static/locales/pl.json @@ -843,6 +843,28 @@ "quotas": "Przydziały", "reset_pw_for": "Nowe hasło dla", "role": "Rola", + "sessions": { + "filter_user": "Użytkownik (UUID)", + "include_revoked": "Uwzględnij unieważnione / wygasłe", + "refresh": "Odśwież", + "col_user": "Użytkownik", + "col_created": "Utworzono", + "col_expires": "Wygasa", + "col_ip": "IP", + "col_user_agent": "User agent", + "col_bound": "Powiązana", + "col_status": "Stan", + "current_tooltip": "To sesja, której obecnie używasz — unieważnienie jej wyloguje Cię.", + "bound_tooltip": "Powiązana z DPoP (jkt {{prefix}}…)", + "unbound": "niepowiązana", + "revoked": "unieważniona", + "expired": "wygasła", + "active": "aktywna", + "revoke": "Unieważnij", + "empty": "Żadne sesje nie pasują do bieżącego filtra.", + "revoke_self_confirm": "⚠️ To TWOJA bieżąca sesja. Unieważnienie jej wyloguje Cię natychmiast i musisz zalogować się ponownie. Kontynuować?", + "revoke_confirm": "Unieważnić tę sesję? Następne żądanie z tej przeglądarki otrzyma 401." + }, "smtp_fail": "Wysłanie nie powiodło się.", "smtp_send": "Wyślij", "smtp_test": "Wyślij e-mail testowy", diff --git a/frontend/static/locales/pt.json b/frontend/static/locales/pt.json index 8a326636..5c395bac 100644 --- a/frontend/static/locales/pt.json +++ b/frontend/static/locales/pt.json @@ -843,6 +843,28 @@ "quotas": "Cotas", "reset_pw_for": "Nova senha para", "role": "Função", + "sessions": { + "filter_user": "Utilizador (UUID)", + "include_revoked": "Incluir revogadas / expiradas", + "refresh": "Atualizar", + "col_user": "Utilizador", + "col_created": "Criada", + "col_expires": "Expira", + "col_ip": "IP", + "col_user_agent": "Agente de utilizador", + "col_bound": "Vinculada", + "col_status": "Estado", + "current_tooltip": "Esta é a sessão que está a utilizar agora — revogá-la irá terminar a sua sessão.", + "bound_tooltip": "Vinculada por DPoP (jkt {{prefix}}…)", + "unbound": "não vinculada", + "revoked": "revogada", + "expired": "expirada", + "active": "ativa", + "revoke": "Revogar", + "empty": "Nenhuma sessão corresponde ao filtro atual.", + "revoke_self_confirm": "⚠️ Esta é a SUA sessão atual. Revogá-la irá terminar a sessão imediatamente e terá de iniciar sessão novamente. Continuar?", + "revoke_confirm": "Revogar esta sessão? O próximo pedido desse browser receberá 401." + }, "smtp_fail": "Falha no envio.", "smtp_send": "Enviar", "smtp_test": "Enviar e-mail de teste", diff --git a/frontend/static/locales/ru.json b/frontend/static/locales/ru.json index 27e8e456..945b32f8 100644 --- a/frontend/static/locales/ru.json +++ b/frontend/static/locales/ru.json @@ -843,6 +843,28 @@ "quotas": "Квоты", "reset_pw_for": "Новый пароль для", "role": "Роль", + "sessions": { + "filter_user": "Пользователь (UUID)", + "include_revoked": "Включая отозванные / истёкшие", + "refresh": "Обновить", + "col_user": "Пользователь", + "col_created": "Создан", + "col_expires": "Истекает", + "col_ip": "IP", + "col_user_agent": "Агент пользователя", + "col_bound": "Привязан", + "col_status": "Статус", + "current_tooltip": "Это сеанс, который вы сейчас используете — при отзыве вы будете разлогинены.", + "bound_tooltip": "Привязан к DPoP (jkt {{prefix}}…)", + "unbound": "не привязан", + "revoked": "отозван", + "expired": "истёк", + "active": "активен", + "revoke": "Отозвать", + "empty": "Нет сеансов, соответствующих текущему фильтру.", + "revoke_self_confirm": "⚠️ Это ВАШ текущий сеанс. При отзыве вы будете немедленно разлогинены и придётся войти снова. Продолжить?", + "revoke_confirm": "Отозвать этот сеанс? Следующий запрос из этого браузера получит 401." + }, "smtp_fail": "Сбой отправки.", "smtp_send": "Отправить", "smtp_test": "Отправить тестовое письмо", diff --git a/frontend/static/locales/zh-TW.json b/frontend/static/locales/zh-TW.json index 9d87dded..9fdd2699 100644 --- a/frontend/static/locales/zh-TW.json +++ b/frontend/static/locales/zh-TW.json @@ -826,6 +826,28 @@ "quotas": "配額", "reset_pw_for": "新密碼用於", "role": "角色", + "sessions": { + "filter_user": "使用者 (UUID)", + "include_revoked": "包含已撤銷 / 已過期", + "refresh": "重新整理", + "col_user": "使用者", + "col_created": "建立於", + "col_expires": "過期於", + "col_ip": "IP", + "col_user_agent": "使用者代理", + "col_bound": "已繫結", + "col_status": "狀態", + "current_tooltip": "這是您正在使用的工作階段 — 撤銷會將您登出。", + "bound_tooltip": "已繫結 DPoP (jkt {{prefix}}…)", + "unbound": "未繫結", + "revoked": "已撤銷", + "expired": "已過期", + "active": "使用中", + "revoke": "撤銷", + "empty": "沒有工作階段符合目前的篩選條件。", + "revoke_self_confirm": "⚠️ 這是您目前的工作階段。撤銷後將立即登出,您必須重新登入。要繼續嗎?", + "revoke_confirm": "撤銷此工作階段?該瀏覽器的下一次請求將回傳 401。" + }, "smtp_fail": "傳送失敗。", "smtp_send": "傳送", "smtp_test": "傳送測試郵件", diff --git a/frontend/static/locales/zh.json b/frontend/static/locales/zh.json index e72201ab..c2834b2e 100644 --- a/frontend/static/locales/zh.json +++ b/frontend/static/locales/zh.json @@ -826,6 +826,28 @@ "quotas": "配额", "reset_pw_for": "新密码用于", "role": "角色", + "sessions": { + "filter_user": "用户 (UUID)", + "include_revoked": "包含已撤销 / 已过期", + "refresh": "刷新", + "col_user": "用户", + "col_created": "创建于", + "col_expires": "过期于", + "col_ip": "IP", + "col_user_agent": "用户代理", + "col_bound": "已绑定", + "col_status": "状态", + "current_tooltip": "这是您正在使用的会话 — 撤销将使您退出登录。", + "bound_tooltip": "已绑定 DPoP (jkt {{prefix}}…)", + "unbound": "未绑定", + "revoked": "已撤销", + "expired": "已过期", + "active": "活动", + "revoke": "撤销", + "empty": "没有会话与当前筛选条件匹配。", + "revoke_self_confirm": "⚠️ 这是您当前的会话。撤销后您将立即被注销并需要重新登录。是否继续?", + "revoke_confirm": "撤销此会话吗?该浏览器的下一次请求将返回 401。" + }, "smtp_fail": "发送失败。", "smtp_send": "发送", "smtp_test": "发送测试邮件",