fix: resolve role resolution priority - backend roles override localStorage

- login.tsx: pass role via URL param to dashboard instead of relying on localStorage
- dashboard.tsx: add urlRoleLocked signal to prevent auth effects from overriding URL-passed role
- auth.tsx: trust passed-in role over re-reading from localStorage in saveUser
This commit is contained in:
Tracewebstudio Dev 2026-04-21 21:50:57 +02:00
parent e1a0a5400c
commit 159b051ac8
3 changed files with 63 additions and 28 deletions

View file

@ -1,4 +1,4 @@
import { createContext, createResource, createSignal, useContext, type ParentProps, type Accessor, type Setter } from 'solid-js';
import { createContext, createEffect, createResource, createSignal, useContext, type ParentProps, type Accessor, type Setter } from 'solid-js';
import { useNavigate } from '@solidjs/router';
const API = '/api/gateway';
@ -17,6 +17,7 @@ type AuthState = {
isLoading: Accessor<boolean>;
logout: () => void;
setUser: Setter<AuthUser | null>;
refreshUser: (userData?: AuthUser | null) => void;
};
const AuthContext = createContext<AuthState>();
@ -99,14 +100,25 @@ async function fetchSession(): Promise<AuthUser | null> {
export function AuthProvider(props: ParentProps) {
const [user, setUser] = createSignal<AuthUser | null>(null);
const [session] = createResource(fetchSession);
const [session, { refetch: refetchSession }] = createResource(fetchSession);
const isLoading = () => session.loading;
const isAuthenticated = () => !!user() || !!getToken() || (!!session() && !session.error);
if (session()) {
setUser(session() as AuthUser | null);
}
createEffect(() => {
const sess = session();
if (sess) {
setUser(sess);
}
});
const refreshUser = (userData?: AuthUser | null) => {
if (userData) {
setUser(userData);
} else {
refetchSession();
}
};
const logout = () => {
clearAuthStorage();
@ -117,7 +129,7 @@ export function AuthProvider(props: ParentProps) {
};
return (
<AuthContext.Provider value={{ user, isAuthenticated, isLoading, logout, setUser }}>
<AuthContext.Provider value={{ user, isAuthenticated, isLoading, logout, setUser, refreshUser }}>
{props.children}
</AuthContext.Provider>
);

View file

@ -578,6 +578,7 @@ function mergeSidebar(
return merged;
}
// FIX_APPLIED_V13_ROLE_FROM_URL_PROTECTION
export default function RuntimeDashboardPage() {
const navigate = useNavigate();
const auth = useAuth();
@ -587,23 +588,31 @@ export default function RuntimeDashboardPage() {
const [activeTab, setActiveTab] = createSignal("overview");
const [userName, setUserName] = createSignal("User");
const [userId, setUserId] = createSignal("");
const [urlRoleLocked, setUrlRoleLocked] = createSignal(false);
onMount(() => {
setHydrated(true);
const storedRole = getInitialRoleFromStorage();
setRole(storedRole);
const urlParams = new URLSearchParams(window.location.search);
const roleParam = urlParams.get("role");
if (roleParam) {
setUrlRoleLocked(true);
setRole(normalizeRole(roleParam));
} else {
const storedRole = getInitialRoleFromStorage();
setRole(storedRole);
}
setUserName(getNameFromStorage());
if (auth.user()) {
const u = auth.user()!;
if (u.full_name) setUserName(u.full_name);
if (u.id) setUserId(u.id);
if (u.active_role) setRole(resolveRoleForDashboard(u.active_role));
}
});
createEffect(() => {
const u = auth.user();
if (u) {
// If role was explicitly set from URL, never let auth.user() override it
if (u && !urlRoleLocked()) {
if (u.full_name && userName() === "User") setUserName(u.full_name);
if (u.id && !userId()) setUserId(u.id);
if (u.active_role) setRole(resolveRoleForDashboard(u.active_role));
@ -619,7 +628,7 @@ export default function RuntimeDashboardPage() {
createEffect(() => {
const runtimeRole = bundle()?.role;
if (runtimeRole && runtimeRole !== role()) setRole(runtimeRole);
if (runtimeRole && runtimeRole !== role() && !urlRoleLocked()) setRole(runtimeRole);
});
createEffect(() => {

View file

@ -160,12 +160,9 @@ export default function LoginRoute() {
const fullName = String(user?.full_name || user?.fullName || "").trim();
const [firstName, ...rest] = fullName.split(" ");
const lastName = rest.join(" ");
const normalizedRole = resolveActiveRole(
user?.active_role || user?.role || roleGuess(),
String(user?.email || email())
);
const storedRole = normalizedRole ? normalizedRole.toLowerCase() : roleGuess();
const selectedProfessionalRole = getStoredPreferredRole(String(user?.email || email()));
// Trust the passed-in role — don't re-resolve from storage (which can be stale)
const normalizedRole = normalizeRoleValue(user?.active_role || user?.role || "JOB_SEEKER");
const storedRole = normalizedRole.toLowerCase();
const payload = {
firstName: firstName || "",
lastName: lastName || "",
@ -177,8 +174,8 @@ export default function LoginRoute() {
.toLowerCase(),
roleKey: storedRole,
role: storedRole,
active_role: normalizedRole || "JOB_SEEKER",
selectedProfessionalRole: selectedProfessionalRole || null,
active_role: normalizedRole,
selectedProfessionalRole: normalizedRole,
user,
};
if (typeof window !== "undefined") {
@ -233,23 +230,40 @@ export default function LoginRoute() {
window.sessionStorage.setItem("nxtgauge_access_token", accessToken);
window.sessionStorage.setItem("nxtgauge_frontend_access_token", accessToken);
}
const resolvedActiveRole = resolveActiveRole(
data?.user?.active_role || data?.user?.role,
data?.user?.email || email().trim().toLowerCase()
);
const user_roles = data?.user?.roles || [];
const selectedRole = getStoredPreferredRole(data?.user?.email || email().trim().toLowerCase());
const backendActiveRole = data?.user?.active_role;
// Use selected role from storage if available, otherwise use backend role
// If backend returns JOB_SEEKER but user has professional roles, pick the first professional role
let resolvedActiveRole = backendActiveRole;
if (normalizeRoleValue(backendActiveRole) === "JOB_SEEKER" && user_roles.length > 0) {
// Find first non-JOB_SEEKER role from backend's roles array
for (const role of user_roles) {
if (normalizeRoleValue(role) !== "JOB_SEEKER") {
resolvedActiveRole = role;
break;
}
}
}
if (!resolvedActiveRole) {
resolvedActiveRole = selectedRole || backendActiveRole || "JOB_SEEKER";
}
const normalizedEmail = email().trim().toLowerCase();
const finalRole = normalizeRoleValue(resolvedActiveRole);
const userPayload = {
id: String(data?.user?.id || ""),
email: String(data?.user?.email || normalizedEmail),
full_name: String(data?.user?.full_name || ""),
active_role: resolvedActiveRole,
active_role: finalRole,
email_verified: Boolean(data?.user?.email_verified ?? true),
};
saveUser({ ...(data?.user || {}), ...userPayload });
if (auth.setUser) {
auth.setUser(userPayload);
saveUser({ ...userPayload });
if (auth.refreshUser) {
auth.refreshUser(userPayload);
}
navigate("/dashboard", { replace: true });
navigate(`/dashboard?role=${encodeURIComponent(normalizeRoleValue(resolvedActiveRole))}`, { replace: true });
} catch {
setError("Network error during login. Please try again.");
} finally {