From 5bfa463528503f6987f983f1d40ae55f8d7da2a5 Mon Sep 17 00:00:00 2001 From: Alexa <53471801+Alexa-RR@users.noreply.github.com> Date: Sun, 25 Oct 2026 15:54:51 +0200 Subject: [PATCH] fix(auth): show only known sign-in error codes Sign-in errors arrive in the query string, so unknown codes get a fixed message instead of being echoed into the banner. --- DorkNet.Server/admin-ui/src/pages/Login.tsx | 5 +++-- DorkNet.Server/site/src/components/Header.tsx | 4 ++-- DorkNet.Server/site/src/lib/account.ts | 6 ++++++ DorkNet.Server/site/src/pages/Link.tsx | 4 ++-- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/DorkNet.Server/admin-ui/src/pages/Login.tsx b/DorkNet.Server/admin-ui/src/pages/Login.tsx index cb5452a..1e0c33c 100644 --- a/DorkNet.Server/admin-ui/src/pages/Login.tsx +++ b/DorkNet.Server/admin-ui/src/pages/Login.tsx @@ -54,7 +54,7 @@ export function Login() { const errorText = (e: unknown, codes: Record): React.ReactNode => { if (e instanceof ApiError && e.body && typeof e.body === 'object' && 'error' in e.body) { const code = (e.body as { error: string }).error; - return codes[code] ?? e.message; + return Object.hasOwn(codes, code) ? codes[code] : e.message; } return e instanceof Error ? e.message : String(e); }; @@ -75,7 +75,8 @@ export function Login() { ssoHandled.current = true; setErr(signinError === 'not_linked' ? <>This sign-in isn't linked to a DorkNet account yet. Link it on the website first, then sign in here again. - : SSO_ERRORS[signinError] ?? `Single sign-on failed (${signinError}).`); + // Only known codes are shown: the query string is attacker-controllable. + : Object.hasOwn(SSO_ERRORS, signinError) ? SSO_ERRORS[signinError] : 'Single sign-on failed. Try again.'); setParams({}, { replace: true }); return; } diff --git a/DorkNet.Server/site/src/components/Header.tsx b/DorkNet.Server/site/src/components/Header.tsx index a58e13b..6a4a867 100644 --- a/DorkNet.Server/site/src/components/Header.tsx +++ b/DorkNet.Server/site/src/components/Header.tsx @@ -2,7 +2,7 @@ import { NavLink, Link, useLocation, useNavigate, useSearchParams } from 'react- import { useEffect, useRef, useState } from 'react'; import { BrandMark } from './BrandMark'; import { getDownloads, SignInRequiredError } from '../lib/downloads'; -import { getMe, signInUrl, signOut, SIGNIN_ERRORS, type AccountMe } from '../lib/account'; +import { errorCopy, getMe, signInUrl, signOut, SIGNIN_ERRORS, type AccountMe } from '../lib/account'; const NAV = [ { to: '/', label: 'Home', exact: true }, @@ -154,7 +154,7 @@ export function Header() { {signinError && (
- Sign-in didn't work: {SIGNIN_ERRORS[signinError] ?? signinError} + Sign-in didn't work: {errorCopy(SIGNIN_ERRORS, signinError, 'something went wrong. Try again.')}
diff --git a/DorkNet.Server/site/src/lib/account.ts b/DorkNet.Server/site/src/lib/account.ts index c17d848..62789b8 100644 --- a/DorkNet.Server/site/src/lib/account.ts +++ b/DorkNet.Server/site/src/lib/account.ts @@ -46,6 +46,12 @@ export const LINK_ERRORS: Record = { no_pending_identity: 'Your sign-in expired. Start again with "Sign in".', }; +/** Friendly copy for a known error code, else `fallback`. For codes read from + * the query string, pass a fixed fallback: anyone can put text in a link. */ +export function errorCopy(map: Record, code: string | null | undefined, fallback: string): string { + return code && Object.hasOwn(map, code) ? map[code] : fallback; +} + export const SIGNIN_ERRORS: Record = { account_unavailable: 'The account linked to this sign-in is unavailable (deleted or banned).', no_identity: "The sign-in provider didn't return an identity. Try again.", diff --git a/DorkNet.Server/site/src/pages/Link.tsx b/DorkNet.Server/site/src/pages/Link.tsx index 7338683..da6072f 100644 --- a/DorkNet.Server/site/src/pages/Link.tsx +++ b/DorkNet.Server/site/src/pages/Link.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react'; import { Link as RouterLink, useNavigate, useSearchParams } from 'react-router'; -import { getMe, link, LINK_ERRORS, localPath, signInUrl, type AccountMe } from '../lib/account'; +import { errorCopy, getMe, link, LINK_ERRORS, localPath, signInUrl, type AccountMe } from '../lib/account'; // After a first sign-in with the provider: prove which DorkNet account this // identity belongs to, once, with that account's password. @@ -35,7 +35,7 @@ export function Link() { window.location.assign(returnUrl); // the header re-reads who is signed in } catch (e) { const key = (e as Error).message; - setErr(LINK_ERRORS[key] ?? key); + setErr(errorCopy(LINK_ERRORS, key, key)); } finally { setBusy(false); }