mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-07-09 15:05:59 +00:00
feat(auth): explain the plain-HTTP secure-cookie gotcha on login
When the server issues a Secure session cookie but the request arrived over plain HTTP (the common LAN install over http://ip:3000), the browser drops the cookie and the next request dead-ends on a bare "Access token required" — the top source of avoidable install issues. The login response now flags this exact case and the login page shows a localized box explaining the fix (use HTTPS, or set COOKIE_SECURE=false) with a link to the Troubleshooting guide. It only triggers in the real failure case, never for correct HTTPS setups.
This commit is contained in:
@@ -11,7 +11,7 @@ export default function LoginPage(): React.ReactElement {
|
||||
navigate,
|
||||
mode, setMode,
|
||||
username, setUsername, email, setEmail, password, setPassword, rememberMe, setRememberMe, showPassword, setShowPassword,
|
||||
isLoading, error, setError, appConfig, inviteToken,
|
||||
isLoading, error, setError, insecureCookie, appConfig, inviteToken,
|
||||
langDropdownOpen, setLangDropdownOpen, setLanguageLocal,
|
||||
showTakeoff, mfaStep, setMfaStep, mfaToken, setMfaToken, mfaCode, setMfaCode,
|
||||
passwordChangeStep, newPassword, setNewPassword, confirmPassword, setConfirmPassword,
|
||||
@@ -447,6 +447,17 @@ export default function LoginPage(): React.ReactElement {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{insecureCookie && (
|
||||
<div style={{ padding: '12px 14px', background: '#fffbeb', border: '1px solid #fde68a', borderRadius: 10, fontSize: 'calc(13px * var(--fs-scale-body, 1))', color: '#92400e' }}>
|
||||
<div style={{ fontWeight: 700, marginBottom: 4 }}>{t('login.insecureCookie.title')}</div>
|
||||
<div style={{ lineHeight: 1.55 }}>{t('login.insecureCookie.body')}</div>
|
||||
<a href="https://github.com/mauriceboe/TREK/wiki/Troubleshooting" target="_blank" rel="noopener noreferrer"
|
||||
style={{ display: 'inline-block', marginTop: 6, fontWeight: 600, color: '#b45309', textDecoration: 'underline' }}>
|
||||
{t('login.insecureCookie.link')} ↗
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{passwordChangeStep && (
|
||||
<>
|
||||
<div style={{ padding: '10px 14px', background: '#fefce8', border: '1px solid #fde68a', borderRadius: 10, fontSize: 'calc(13px * var(--fs-scale-body, 1))', color: '#92400e' }}>
|
||||
|
||||
@@ -41,6 +41,9 @@ export function useLogin() {
|
||||
const [showPassword, setShowPassword] = useState<boolean>(false)
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false)
|
||||
const [error, setError] = useState<string>('')
|
||||
// Set when the server signals it just issued a Secure cookie over plain HTTP —
|
||||
// the browser drops it, so we explain the fix instead of a bare 401 later.
|
||||
const [insecureCookie, setInsecureCookie] = useState(false)
|
||||
const [appConfig, setAppConfig] = useState<AppConfig | null>(null)
|
||||
const [inviteToken, setInviteToken] = useState<string>('')
|
||||
const [inviteValid, setInviteValid] = useState<boolean>(false)
|
||||
@@ -225,6 +228,7 @@ export function useLogin() {
|
||||
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>): Promise<void> => {
|
||||
e.preventDefault()
|
||||
setError('')
|
||||
setInsecureCookie(false)
|
||||
setIsLoading(true)
|
||||
try {
|
||||
if (passwordChangeStep) {
|
||||
@@ -260,6 +264,13 @@ export function useLogin() {
|
||||
await register(username, email, password, inviteToken || undefined)
|
||||
} else {
|
||||
const result = await login(email, password, rememberMe)
|
||||
if ((result as { insecureCookie?: boolean }).insecureCookie) {
|
||||
// Credentials were correct, but the secure cookie won't survive plain
|
||||
// HTTP — proceeding would just dead-end on "Access token required".
|
||||
setInsecureCookie(true)
|
||||
setIsLoading(false)
|
||||
return
|
||||
}
|
||||
if ('mfa_required' in result && result.mfa_required && 'mfa_token' in result) {
|
||||
setMfaToken(result.mfa_token)
|
||||
setMfaStep(true)
|
||||
@@ -291,7 +302,7 @@ export function useLogin() {
|
||||
navigate,
|
||||
mode, setMode,
|
||||
username, setUsername, email, setEmail, password, setPassword, rememberMe, setRememberMe, showPassword, setShowPassword,
|
||||
isLoading, error, setError, appConfig, inviteToken,
|
||||
isLoading, error, setError, insecureCookie, appConfig, inviteToken,
|
||||
langDropdownOpen, setLangDropdownOpen, setLanguageLocal,
|
||||
showTakeoff, mfaStep, setMfaStep, mfaToken, setMfaToken, mfaCode, setMfaCode,
|
||||
passwordChangeStep, newPassword, setNewPassword, confirmPassword, setConfirmPassword,
|
||||
|
||||
@@ -4,6 +4,7 @@ import { AuthService } from './auth.service';
|
||||
import { RateLimitService } from './rate-limit.service';
|
||||
import { OptionalJwtGuard } from './optional-jwt.guard';
|
||||
import { writeAudit, getClientIp } from '../../services/auditLog';
|
||||
import { willDropSecureCookie } from '../../services/cookie';
|
||||
import type { User } from '../../types';
|
||||
|
||||
const WINDOW = 15 * 60 * 1000;
|
||||
@@ -88,7 +89,13 @@ export class AuthPublicController {
|
||||
return { mfa_required: true, mfa_token: result.mfa_token };
|
||||
}
|
||||
this.auth.setAuthCookie(res, result.token!, req, result.remember);
|
||||
return { token: result.token, user: result.user };
|
||||
return {
|
||||
token: result.token,
|
||||
user: result.user,
|
||||
// Surfaced so the client can explain the plain-HTTP cookie gotcha instead
|
||||
// of the user hitting a bare "Access token required" on the next request.
|
||||
...(willDropSecureCookie(req) ? { insecureCookie: true } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
@Post('forgot-password')
|
||||
|
||||
@@ -54,6 +54,22 @@ function buildOptions(clear: boolean, secure: boolean, remember?: RememberOption
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* True when we are about to set a `Secure` session cookie but the request did
|
||||
* NOT arrive over HTTPS — the browser silently drops the cookie, so the next
|
||||
* request has no session and the server answers "Access token required". This is
|
||||
* the classic plain-HTTP install gotcha; callers surface it to the user with a
|
||||
* concrete fix (use HTTPS or set COOKIE_SECURE=false) instead of a bare 401.
|
||||
*/
|
||||
export function willDropSecureCookie(req?: Request): boolean {
|
||||
if (process.env.COOKIE_SECURE?.toLowerCase() === 'false') return false;
|
||||
if (req?.secure === true) return false;
|
||||
return (
|
||||
process.env.NODE_ENV?.toLowerCase() === 'production' ||
|
||||
process.env.FORCE_HTTPS?.toLowerCase() === 'true'
|
||||
);
|
||||
}
|
||||
|
||||
export function setAuthCookie(res: Response, token: string, req?: Request, remember?: RememberOption): void {
|
||||
res.cookie(COOKIE_NAME, token, cookieOptions(false, req, remember));
|
||||
}
|
||||
|
||||
@@ -85,5 +85,8 @@ const login: TranslationStrings = {
|
||||
'login.emailPlaceholder': 'your@email.com', // en-fallback
|
||||
'login.passkey.signIn': 'تسجيل الدخول باستخدام مفتاح المرور',
|
||||
'login.passkey.failed': 'فشل تسجيل الدخول بمفتاح المرور. يرجى المحاولة مرة أخرى.',
|
||||
'login.insecureCookie.title': "Login won't stick over HTTP",
|
||||
'login.insecureCookie.body': "You’re connecting over plain HTTP, so your browser drops TREK’s secure session cookie — the next request fails with \"Access token required\". Fix: use HTTPS, or for a home-lab set COOKIE_SECURE=false.",
|
||||
'login.insecureCookie.link': "Open the Troubleshooting guide",
|
||||
};
|
||||
export default login;
|
||||
|
||||
@@ -84,5 +84,8 @@ const login: TranslationStrings = {
|
||||
'login.resetPasswordFailed': 'Falha na redefinição. O link pode ter expirado.',
|
||||
'login.passkey.signIn': 'Entrar com uma passkey',
|
||||
'login.passkey.failed': 'Falha ao entrar com passkey. Tente novamente.',
|
||||
'login.insecureCookie.title': "Login won't stick over HTTP",
|
||||
'login.insecureCookie.body': "You’re connecting over plain HTTP, so your browser drops TREK’s secure session cookie — the next request fails with \"Access token required\". Fix: use HTTPS, or for a home-lab set COOKIE_SECURE=false.",
|
||||
'login.insecureCookie.link': "Open the Troubleshooting guide",
|
||||
};
|
||||
export default login;
|
||||
|
||||
@@ -84,5 +84,8 @@ const login: TranslationStrings = {
|
||||
'login.resetPasswordFailed': 'Obnovení se nezdařilo. Odkaz mohl vypršet.',
|
||||
'login.passkey.signIn': 'Přihlásit se pomocí přístupového klíče',
|
||||
'login.passkey.failed': 'Přihlášení přístupovým klíčem se nezdařilo. Zkuste to prosím znovu.',
|
||||
'login.insecureCookie.title': "Login won't stick over HTTP",
|
||||
'login.insecureCookie.body': "You’re connecting over plain HTTP, so your browser drops TREK’s secure session cookie — the next request fails with \"Access token required\". Fix: use HTTPS, or for a home-lab set COOKIE_SECURE=false.",
|
||||
'login.insecureCookie.link': "Open the Troubleshooting guide",
|
||||
};
|
||||
export default login;
|
||||
|
||||
@@ -86,5 +86,8 @@ const login: TranslationStrings = {
|
||||
'login.resetPasswordFailed': 'Zurücksetzen fehlgeschlagen. Der Link ist möglicherweise abgelaufen.',
|
||||
'login.passkey.signIn': 'Mit Passkey anmelden',
|
||||
'login.passkey.failed': 'Anmeldung mit Passkey fehlgeschlagen. Bitte erneut versuchen.',
|
||||
'login.insecureCookie.title': "Login hält über HTTP nicht",
|
||||
'login.insecureCookie.body': "Du verbindest dich über reines HTTP, daher verwirft dein Browser TREKs sicheren Session-Cookie — die nächste Anfrage scheitert mit „Access token required\". Lösung: HTTPS nutzen, oder für ein Heim-Setup COOKIE_SECURE=false setzen.",
|
||||
'login.insecureCookie.link': "Zur Troubleshooting-Anleitung",
|
||||
};
|
||||
export default login;
|
||||
|
||||
@@ -85,5 +85,8 @@ const login: TranslationStrings = {
|
||||
'login.resetPasswordFailed': 'Reset failed. The link may have expired.',
|
||||
'login.passkey.signIn': 'Sign in with a passkey',
|
||||
'login.passkey.failed': 'Passkey sign-in failed. Please try again.',
|
||||
'login.insecureCookie.title': "Login won't stick over HTTP",
|
||||
'login.insecureCookie.body': "You’re connecting over plain HTTP, so your browser drops TREK’s secure session cookie — the next request fails with \"Access token required\". Fix: use HTTPS, or for a home-lab set COOKIE_SECURE=false.",
|
||||
'login.insecureCookie.link': "Open the Troubleshooting guide",
|
||||
};
|
||||
export default login;
|
||||
|
||||
@@ -86,5 +86,8 @@ const login: TranslationStrings = {
|
||||
'login.oidcLoggedOut': 'Has cerrado sesión. Vuelve a iniciar sesión con tu proveedor SSO.',
|
||||
'login.passkey.signIn': 'Iniciar sesión con una passkey',
|
||||
'login.passkey.failed': 'Error al iniciar sesión con la passkey. Inténtalo de nuevo.',
|
||||
'login.insecureCookie.title': "Login won't stick over HTTP",
|
||||
'login.insecureCookie.body': "You’re connecting over plain HTTP, so your browser drops TREK’s secure session cookie — the next request fails with \"Access token required\". Fix: use HTTPS, or for a home-lab set COOKIE_SECURE=false.",
|
||||
'login.insecureCookie.link': "Open the Troubleshooting guide",
|
||||
};
|
||||
export default login;
|
||||
|
||||
@@ -88,5 +88,8 @@ const login: TranslationStrings = {
|
||||
'login.demoHint': 'Essayez la démo — aucune inscription nécessaire',
|
||||
'login.passkey.signIn': 'Se connecter avec une passkey',
|
||||
'login.passkey.failed': 'Échec de la connexion par passkey. Veuillez réessayer.',
|
||||
'login.insecureCookie.title': "Login won't stick over HTTP",
|
||||
'login.insecureCookie.body': "You’re connecting over plain HTTP, so your browser drops TREK’s secure session cookie — the next request fails with \"Access token required\". Fix: use HTTPS, or for a home-lab set COOKIE_SECURE=false.",
|
||||
'login.insecureCookie.link': "Open the Troubleshooting guide",
|
||||
};
|
||||
export default login;
|
||||
|
||||
@@ -88,5 +88,8 @@ const login: TranslationStrings = {
|
||||
'login.resetPasswordFailed': 'Η επαναφορά απέτυχε. Ο σύνδεσμος μπορεί να έχει λήξει.',
|
||||
'login.passkey.signIn': 'Σύνδεση με passkey',
|
||||
'login.passkey.failed': 'Η σύνδεση με passkey απέτυχε. Παρακαλώ δοκιμάστε ξανά.',
|
||||
'login.insecureCookie.title': "Login won't stick over HTTP",
|
||||
'login.insecureCookie.body': "You’re connecting over plain HTTP, so your browser drops TREK’s secure session cookie — the next request fails with \"Access token required\". Fix: use HTTPS, or for a home-lab set COOKIE_SECURE=false.",
|
||||
'login.insecureCookie.link': "Open the Troubleshooting guide",
|
||||
};
|
||||
export default login;
|
||||
|
||||
@@ -86,5 +86,8 @@ const login: TranslationStrings = {
|
||||
'login.resetPasswordFailed': 'A visszaállítás nem sikerült. A link lehet, hogy lejárt.',
|
||||
'login.passkey.signIn': 'Bejelentkezés passkey-jel',
|
||||
'login.passkey.failed': 'A passkey-bejelentkezés sikertelen. Kérjük, próbáld újra.',
|
||||
'login.insecureCookie.title': "Login won't stick over HTTP",
|
||||
'login.insecureCookie.body': "You’re connecting over plain HTTP, so your browser drops TREK’s secure session cookie — the next request fails with \"Access token required\". Fix: use HTTPS, or for a home-lab set COOKIE_SECURE=false.",
|
||||
'login.insecureCookie.link': "Open the Troubleshooting guide",
|
||||
};
|
||||
export default login;
|
||||
|
||||
@@ -83,5 +83,8 @@ const login: TranslationStrings = {
|
||||
'login.resetPasswordFailed': 'Reset gagal. Tautan mungkin sudah kedaluwarsa.',
|
||||
'login.passkey.signIn': 'Masuk dengan passkey',
|
||||
'login.passkey.failed': 'Masuk dengan passkey gagal. Silakan coba lagi.',
|
||||
'login.insecureCookie.title': "Login won't stick over HTTP",
|
||||
'login.insecureCookie.body': "You’re connecting over plain HTTP, so your browser drops TREK’s secure session cookie — the next request fails with \"Access token required\". Fix: use HTTPS, or for a home-lab set COOKIE_SECURE=false.",
|
||||
'login.insecureCookie.link': "Open the Troubleshooting guide",
|
||||
};
|
||||
export default login;
|
||||
|
||||
@@ -85,5 +85,8 @@ const login: TranslationStrings = {
|
||||
'login.resetPasswordFailed': 'Reset non riuscito. Il link potrebbe essere scaduto.',
|
||||
'login.passkey.signIn': 'Accedi con una passkey',
|
||||
'login.passkey.failed': 'Accesso con passkey non riuscito. Riprova.',
|
||||
'login.insecureCookie.title': "Login won't stick over HTTP",
|
||||
'login.insecureCookie.body': "You’re connecting over plain HTTP, so your browser drops TREK’s secure session cookie — the next request fails with \"Access token required\". Fix: use HTTPS, or for a home-lab set COOKIE_SECURE=false.",
|
||||
'login.insecureCookie.link': "Open the Troubleshooting guide",
|
||||
};
|
||||
export default login;
|
||||
|
||||
@@ -85,5 +85,8 @@ const login: TranslationStrings = {
|
||||
'login.resetPasswordFailed': 'リセットに失敗しました。リンクの有効期限が切れている可能性があります。',
|
||||
'login.passkey.signIn': 'パスキーでサインイン',
|
||||
'login.passkey.failed': 'パスキーでのサインインに失敗しました。もう一度お試しください。',
|
||||
'login.insecureCookie.title': "Login won't stick over HTTP",
|
||||
'login.insecureCookie.body': "You’re connecting over plain HTTP, so your browser drops TREK’s secure session cookie — the next request fails with \"Access token required\". Fix: use HTTPS, or for a home-lab set COOKIE_SECURE=false.",
|
||||
'login.insecureCookie.link': "Open the Troubleshooting guide",
|
||||
};
|
||||
export default login;
|
||||
|
||||
@@ -83,5 +83,8 @@ const login: TranslationStrings = {
|
||||
'login.resetPasswordFailed': '재설정 실패. 링크가 만료되었을 수 있습니다.',
|
||||
'login.passkey.signIn': '패스키로 로그인',
|
||||
'login.passkey.failed': '패스키 로그인에 실패했습니다. 다시 시도하세요.',
|
||||
'login.insecureCookie.title': "Login won't stick over HTTP",
|
||||
'login.insecureCookie.body': "You’re connecting over plain HTTP, so your browser drops TREK’s secure session cookie — the next request fails with \"Access token required\". Fix: use HTTPS, or for a home-lab set COOKIE_SECURE=false.",
|
||||
'login.insecureCookie.link': "Open the Troubleshooting guide",
|
||||
};
|
||||
export default login;
|
||||
|
||||
@@ -85,5 +85,8 @@ const login: TranslationStrings = {
|
||||
'login.demoHint': 'Probeer de demo — geen registratie nodig',
|
||||
'login.passkey.signIn': 'Inloggen met een passkey',
|
||||
'login.passkey.failed': 'Inloggen met passkey mislukt. Probeer het opnieuw.',
|
||||
'login.insecureCookie.title': "Login won't stick over HTTP",
|
||||
'login.insecureCookie.body': "You’re connecting over plain HTTP, so your browser drops TREK’s secure session cookie — the next request fails with \"Access token required\". Fix: use HTTPS, or for a home-lab set COOKIE_SECURE=false.",
|
||||
'login.insecureCookie.link': "Open the Troubleshooting guide",
|
||||
};
|
||||
export default login;
|
||||
|
||||
@@ -85,5 +85,8 @@ const login: TranslationStrings = {
|
||||
'login.setNewPasswordHint': 'Musisz zmienić hasło.',
|
||||
'login.passkey.signIn': 'Zaloguj się kluczem dostępu',
|
||||
'login.passkey.failed': 'Logowanie kluczem dostępu nie powiodło się. Spróbuj ponownie.',
|
||||
'login.insecureCookie.title': "Login won't stick over HTTP",
|
||||
'login.insecureCookie.body': "You’re connecting over plain HTTP, so your browser drops TREK’s secure session cookie — the next request fails with \"Access token required\". Fix: use HTTPS, or for a home-lab set COOKIE_SECURE=false.",
|
||||
'login.insecureCookie.link': "Open the Troubleshooting guide",
|
||||
};
|
||||
export default login;
|
||||
|
||||
@@ -86,5 +86,8 @@ const login: TranslationStrings = {
|
||||
'login.demoHint': 'Попробуйте демо — регистрация не требуется',
|
||||
'login.passkey.signIn': 'Войти с помощью passkey',
|
||||
'login.passkey.failed': 'Не удалось войти с помощью passkey. Попробуйте ещё раз.',
|
||||
'login.insecureCookie.title': "Login won't stick over HTTP",
|
||||
'login.insecureCookie.body': "You’re connecting over plain HTTP, so your browser drops TREK’s secure session cookie — the next request fails with \"Access token required\". Fix: use HTTPS, or for a home-lab set COOKIE_SECURE=false.",
|
||||
'login.insecureCookie.link': "Open the Troubleshooting guide",
|
||||
};
|
||||
export default login;
|
||||
|
||||
@@ -85,5 +85,8 @@ const login: TranslationStrings = {
|
||||
'login.resetPasswordFailed': 'Återställningen misslyckades. Länken kan ha gått ut.',
|
||||
'login.passkey.signIn': 'Logga in med en inloggningsnyckel',
|
||||
'login.passkey.failed': 'Inloggningsnyckel misslyckades. Försök igen.',
|
||||
'login.insecureCookie.title': "Login won't stick over HTTP",
|
||||
'login.insecureCookie.body': "You’re connecting over plain HTTP, so your browser drops TREK’s secure session cookie — the next request fails with \"Access token required\". Fix: use HTTPS, or for a home-lab set COOKIE_SECURE=false.",
|
||||
'login.insecureCookie.link': "Open the Troubleshooting guide",
|
||||
};
|
||||
export default login;
|
||||
|
||||
@@ -86,5 +86,8 @@ const login: TranslationStrings = {
|
||||
'login.resetPasswordFailed': 'Sıfırlama başarısız oldu. Bağlantının süresi dolmuş olabilir.',
|
||||
'login.passkey.signIn': 'Passkey ile oturum açın',
|
||||
'login.passkey.failed': 'Passkey ile oturum açma başarısız oldu. Lütfen tekrar deneyin.',
|
||||
'login.insecureCookie.title': "Login won't stick over HTTP",
|
||||
'login.insecureCookie.body': "You’re connecting over plain HTTP, so your browser drops TREK’s secure session cookie — the next request fails with \"Access token required\". Fix: use HTTPS, or for a home-lab set COOKIE_SECURE=false.",
|
||||
'login.insecureCookie.link': "Open the Troubleshooting guide",
|
||||
};
|
||||
export default login;
|
||||
|
||||
@@ -85,5 +85,8 @@ const login: TranslationStrings = {
|
||||
'login.demoHint': 'Спробуйте демо — реєстрація не потрібна',
|
||||
'login.passkey.signIn': 'Увійти за допомогою passkey',
|
||||
'login.passkey.failed': 'Не вдалося увійти за допомогою passkey. Спробуйте ще раз.',
|
||||
'login.insecureCookie.title': "Login won't stick over HTTP",
|
||||
'login.insecureCookie.body': "You’re connecting over plain HTTP, so your browser drops TREK’s secure session cookie — the next request fails with \"Access token required\". Fix: use HTTPS, or for a home-lab set COOKIE_SECURE=false.",
|
||||
'login.insecureCookie.link': "Open the Troubleshooting guide",
|
||||
};
|
||||
export default login;
|
||||
|
||||
@@ -82,5 +82,8 @@ const login: TranslationStrings = {
|
||||
'login.demoHint': '試用演示——無需註冊',
|
||||
'login.passkey.signIn': '使用 Passkey 登入',
|
||||
'login.passkey.failed': 'Passkey 登入失敗,請重試。',
|
||||
'login.insecureCookie.title': "Login won't stick over HTTP",
|
||||
'login.insecureCookie.body': "You’re connecting over plain HTTP, so your browser drops TREK’s secure session cookie — the next request fails with \"Access token required\". Fix: use HTTPS, or for a home-lab set COOKIE_SECURE=false.",
|
||||
'login.insecureCookie.link': "Open the Troubleshooting guide",
|
||||
};
|
||||
export default login;
|
||||
|
||||
@@ -82,5 +82,8 @@ const login: TranslationStrings = {
|
||||
'login.demoHint': '试用演示——无需注册',
|
||||
'login.passkey.signIn': '使用通行密钥登录',
|
||||
'login.passkey.failed': '通行密钥登录失败,请重试。',
|
||||
'login.insecureCookie.title': "Login won't stick over HTTP",
|
||||
'login.insecureCookie.body': "You’re connecting over plain HTTP, so your browser drops TREK’s secure session cookie — the next request fails with \"Access token required\". Fix: use HTTPS, or for a home-lab set COOKIE_SECURE=false.",
|
||||
'login.insecureCookie.link': "Open the Troubleshooting guide",
|
||||
};
|
||||
export default login;
|
||||
|
||||
Reference in New Issue
Block a user