refactor(auth): unify authorization patterns with MANAGE_OWN_PROFILE permission
Issue #2: The profile route used a custom role-based check instead of the permission-based pattern used everywhere else. Changes: - Add MANAGE_OWN_PROFILE permission to backend Permission enum - Add permission to ROLE_REGULAR role definition - Update profile routes to use require_permission(MANAGE_OWN_PROFILE) - Remove custom require_regular_user dependency - Update frontend Permission constant and profile page - Update invites page to use permission instead of role check - Update profile tests with proper permission mocking This ensures consistent authorization patterns across all routes.
This commit is contained in:
parent
81cd34b0e7
commit
21698203fe
7 changed files with 40 additions and 23 deletions
|
|
@ -13,6 +13,7 @@ export const Permission = {
|
|||
INCREMENT_COUNTER: "increment_counter",
|
||||
USE_SUM: "use_sum",
|
||||
VIEW_AUDIT: "view_audit",
|
||||
MANAGE_OWN_PROFILE: "manage_own_profile",
|
||||
MANAGE_INVITES: "manage_invites",
|
||||
VIEW_OWN_INVITES: "view_own_invites",
|
||||
// Booking permissions (regular users)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { Header } from "../components/Header";
|
|||
import { useRequireAuth } from "../hooks/useRequireAuth";
|
||||
import { components } from "../generated/api";
|
||||
import constants from "../../../shared/constants.json";
|
||||
import { Permission } from "../auth-context";
|
||||
import {
|
||||
layoutStyles,
|
||||
cardStyles,
|
||||
|
|
@ -19,7 +20,7 @@ type Invite = components["schemas"]["UserInviteResponse"];
|
|||
|
||||
export default function InvitesPage() {
|
||||
const { user, isLoading, isAuthorized } = useRequireAuth({
|
||||
requiredRole: constants.roles.REGULAR,
|
||||
requiredPermission: Permission.VIEW_OWN_INVITES,
|
||||
fallbackRedirect: "/audit",
|
||||
});
|
||||
const [invites, setInvites] = useState<Invite[]>([]);
|
||||
|
|
|
|||
|
|
@ -15,11 +15,14 @@ let mockUser: { id: number; email: string; roles: string[]; permissions: string[
|
|||
id: 1,
|
||||
email: "test@example.com",
|
||||
roles: ["regular"],
|
||||
permissions: ["view_counter", "increment_counter", "use_sum"],
|
||||
permissions: ["view_counter", "increment_counter", "use_sum", "manage_own_profile"],
|
||||
};
|
||||
let mockIsLoading = false;
|
||||
const mockLogout = vi.fn();
|
||||
const mockHasRole = vi.fn((role: string) => mockUser?.roles.includes(role) ?? false);
|
||||
const mockHasPermission = vi.fn(
|
||||
(permission: string) => mockUser?.permissions.includes(permission) ?? false
|
||||
);
|
||||
|
||||
vi.mock("../auth-context", () => ({
|
||||
useAuth: () => ({
|
||||
|
|
@ -27,7 +30,23 @@ vi.mock("../auth-context", () => ({
|
|||
isLoading: mockIsLoading,
|
||||
logout: mockLogout,
|
||||
hasRole: mockHasRole,
|
||||
hasPermission: mockHasPermission,
|
||||
}),
|
||||
Permission: {
|
||||
VIEW_COUNTER: "view_counter",
|
||||
INCREMENT_COUNTER: "increment_counter",
|
||||
USE_SUM: "use_sum",
|
||||
VIEW_AUDIT: "view_audit",
|
||||
MANAGE_OWN_PROFILE: "manage_own_profile",
|
||||
MANAGE_INVITES: "manage_invites",
|
||||
VIEW_OWN_INVITES: "view_own_invites",
|
||||
BOOK_APPOINTMENT: "book_appointment",
|
||||
VIEW_OWN_APPOINTMENTS: "view_own_appointments",
|
||||
CANCEL_OWN_APPOINTMENT: "cancel_own_appointment",
|
||||
MANAGE_AVAILABILITY: "manage_availability",
|
||||
VIEW_ALL_APPOINTMENTS: "view_all_appointments",
|
||||
CANCEL_ANY_APPOINTMENT: "cancel_any_appointment",
|
||||
},
|
||||
}));
|
||||
|
||||
// Mock profile data
|
||||
|
|
@ -45,10 +64,13 @@ beforeEach(() => {
|
|||
id: 1,
|
||||
email: "test@example.com",
|
||||
roles: ["regular"],
|
||||
permissions: ["view_counter", "increment_counter", "use_sum"],
|
||||
permissions: ["view_counter", "increment_counter", "use_sum", "manage_own_profile"],
|
||||
};
|
||||
mockIsLoading = false;
|
||||
mockHasRole.mockImplementation((role: string) => mockUser?.roles.includes(role) ?? false);
|
||||
mockHasPermission.mockImplementation(
|
||||
(permission: string) => mockUser?.permissions.includes(permission) ?? false
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { Header } from "../components/Header";
|
|||
import { useRequireAuth } from "../hooks/useRequireAuth";
|
||||
import { components } from "../generated/api";
|
||||
import constants from "../../../shared/constants.json";
|
||||
import { Permission } from "../auth-context";
|
||||
import {
|
||||
layoutStyles,
|
||||
cardStyles,
|
||||
|
|
@ -121,7 +122,7 @@ function toFormData(data: ProfileData): FormData {
|
|||
|
||||
export default function ProfilePage() {
|
||||
const { user, isLoading, isAuthorized } = useRequireAuth({
|
||||
requiredRole: constants.roles.REGULAR,
|
||||
requiredPermission: Permission.MANAGE_OWN_PROFILE,
|
||||
fallbackRedirect: "/audit",
|
||||
});
|
||||
const [originalData, setOriginalData] = useState<FormData | null>(null);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue