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:
Maurice
2026-06-29 18:24:49 +02:00
committed by Maurice
parent e91f592f22
commit 3701ab6cad
25 changed files with 111 additions and 3 deletions
+12 -1
View File
@@ -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' }}>
+12 -1
View File
@@ -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,