fix(auth): registration bypasses email gate — add setAuth() to properly init React state

Previously Register.tsx wrote auth_token directly to localStorage without
calling setToken/setUser in AuthContext. This made isAuthenticated=true
(computed from localStorage) while user=null (React state), so ProtectedRoute's
user.email_verified check was never reached.

- AuthContext: add setAuth(token, userData) that syncs both localStorage and React state
- Register: use setAuth() instead of direct localStorage write
- Register: add !registered guard to auto-redirect effect to prevent bypassing check-inbox screen

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Vadym Samoilenko 2026-05-24 19:32:29 +01:00
parent 2ae08f6d0a
commit 84dcfc46b9
2 changed files with 14 additions and 6 deletions

View file

@ -17,6 +17,7 @@ interface AuthContextType {
login: (username: string, password: string) => Promise<void>;
logout: () => void;
refreshUser: () => Promise<void>;
setAuth: (token: string, userData: User) => void;
isAuthenticated: boolean;
}
@ -141,6 +142,13 @@ export function AuthProvider({ children }: { children: ReactNode }) {
}
};
const setAuth = (accessToken: string, userData: User) => {
localStorage.setItem('auth_token', accessToken);
localStorage.setItem('user', JSON.stringify(userData));
setToken(accessToken);
setUser(userData);
};
const refreshUser = async () => {
if (!token) return;
const validationKey = `token_validated_${token.substring(0, 10)}`;
@ -175,7 +183,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
})();
return (
<AuthContext.Provider value={{ user, token, isLoading, login, logout, refreshUser, isAuthenticated }}>
<AuthContext.Provider value={{ user, token, isLoading, login, logout, refreshUser, setAuth, isAuthenticated }}>
{children}
</AuthContext.Provider>
);

View file

@ -123,7 +123,7 @@ function MockPanel() {
export default function Register() {
const navigate = useNavigate();
const [searchParams] = useSearchParams();
const { isAuthenticated } = useAuth();
const { isAuthenticated, setAuth } = useAuth();
const { t } = useTranslation();
const [isLoading, setIsLoading] = useState(false);
const [showPassword, setShowPassword] = useState(false);
@ -135,8 +135,8 @@ export default function Register() {
const planMeta = planKey ? PLAN_META[planKey] : null;
useEffect(() => {
if (isAuthenticated) navigate('/dashboard', { replace: true });
}, [isAuthenticated, navigate]);
if (isAuthenticated && !registered) navigate('/dashboard', { replace: true });
}, [isAuthenticated, navigate, registered]);
const form = useForm<RegisterFormValues>({
resolver: zodResolver(registerSchema),
@ -155,8 +155,8 @@ export default function Register() {
});
setRegisteredEmail(values.email);
setRegistered(true);
if (res.data.access_token) {
localStorage.setItem('auth_token', res.data.access_token);
if (res.data.access_token && res.data.user) {
setAuth(res.data.access_token, res.data.user);
toastService.success(t('auth.toast_account_created'), { description: t('auth.toast_account_created_desc') });
}
} catch (err: any) {