feat(phase-39): role-based access control overhaul + forced password change
Aegis CI / lint-and-test (push) Has been cancelled
Aegis CI / lint-and-test (push) Has been cancelled
- Add must_change_password field to User model with migration b023 - Add POST /auth/change-password endpoint with password policy validation - Add require_password_changed dependency to block requests until password is changed - Add ChangePasswordModal with live password policy checklist (forced on first login) - Show password policy in CreateUserModal and EditUserModal - Fix backend permissions: tests, campaigns, templates, reports, evidence, worklogs - red_tech/blue_tech: execute only, cannot create tests/campaigns/templates - red_lead/blue_lead: create/edit tests/campaigns/templates, generate reports, no system access - viewer: read-only everywhere, can generate reports - Fix frontend role checks across TestDetailPage, TestDetailHeader, TeamTabs, TestsPage, CampaignsPage, CampaignDetailPage, Sidebar
This commit is contained in:
@@ -12,6 +12,7 @@ import {
|
||||
getMe,
|
||||
} from "../api/auth";
|
||||
import type { User } from "../types/models";
|
||||
import ChangePasswordModal from "../components/ChangePasswordModal";
|
||||
|
||||
/* ── Context shape ────────────────────────────────────────────────── */
|
||||
|
||||
@@ -31,18 +32,20 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
const [user, setUser] = useState<User | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
// On mount — try to hydrate the user from the existing HttpOnly cookie.
|
||||
// If no valid cookie exists the /auth/me call will 401 and we stay
|
||||
// unauthenticated — no localStorage involved.
|
||||
useEffect(() => {
|
||||
getMe()
|
||||
.then(setUser)
|
||||
.catch(() => setUser(null))
|
||||
.finally(() => setIsLoading(false));
|
||||
const refreshUser = useCallback(async () => {
|
||||
try {
|
||||
const me = await getMe();
|
||||
setUser(me);
|
||||
} catch {
|
||||
setUser(null);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
refreshUser().finally(() => setIsLoading(false));
|
||||
}, [refreshUser]);
|
||||
|
||||
const login = useCallback(async (username: string, password: string) => {
|
||||
// The backend sets the HttpOnly cookie automatically
|
||||
await apiLogin(username, password);
|
||||
const me = await getMe();
|
||||
setUser(me);
|
||||
@@ -53,6 +56,8 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
setUser(null);
|
||||
}, []);
|
||||
|
||||
const mustChangePassword = user?.must_change_password === true;
|
||||
|
||||
return (
|
||||
<AuthContext.Provider
|
||||
value={{
|
||||
@@ -64,6 +69,9 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
{mustChangePassword && (
|
||||
<ChangePasswordModal isForced onSuccess={refreshUser} />
|
||||
)}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user