Skip to content

Commit febde23

Browse files
committed
disallow access to locked users
1 parent 50e816d commit febde23

File tree

7 files changed

+45
-5
lines changed

7 files changed

+45
-5
lines changed

src/api/entities/User.ts

+2
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ export class User extends BaseModel {
6363
declare jobFunction: UserJobFunction;
6464
declare participants?: Participant[];
6565
declare acceptedTerms: boolean;
66+
declare locked?: boolean;
6667
declare userToParticipantRoles?: UserToParticipantRole[];
6768

6869
static readonly modifiers = {
@@ -85,6 +86,7 @@ export const UserSchema = z.object({
8586
phone: z.string().optional(),
8687
jobFunction: z.nativeEnum(UserJobFunction).optional(),
8788
acceptedTerms: z.boolean(),
89+
locked: z.boolean().optional(),
8890
});
8991

9092
export const UserCreationPartial = UserSchema.pick({

src/api/middleware/usersMiddleware.ts

+3
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ export const enrichCurrentUser = async (req: UserRequest, res: Response, next: N
4646
if (!user) {
4747
return res.status(404).send([{ message: 'The user cannot be found.' }]);
4848
}
49+
if (user.locked) {
50+
return res.status(403).send([{ message: 'Unauthorized.' }]);
51+
}
4952
req.user = user;
5053
return next();
5154
};

src/web/App.tsx

+4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { EnvironmentBanner } from './components/Core/Banner/EnvironmentBanner';
66
import { ErrorView } from './components/Core/ErrorView/ErrorView';
77
import { Loading } from './components/Core/Loading/Loading';
88
import { ToastContainerWrapper } from './components/Core/Popups/Toast';
9+
import { LockedUserView } from './components/Navigation/LockedUserView';
910
import { NoParticipantAccessView } from './components/Navigation/NoParticipantAccessView';
1011
import { PortalHeader } from './components/PortalHeader/PortalHeader';
1112
import { UpdatesTour } from './components/SiteTour/UpdatesTour';
@@ -25,6 +26,9 @@ function AppContent() {
2526
const { participant } = useContext(ParticipantContext);
2627
const isLocalDev = process.env.NODE_ENV === 'development';
2728

29+
if (LoggedInUser?.isLocked) {
30+
return <LockedUserView />;
31+
}
2832
if (LoggedInUser?.user?.participants!.length === 0) {
2933
return <ErrorView message='You do not have access to any participants.' />;
3034
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
.user-locked-container {
2+
display: flex;
3+
flex-direction: column;
4+
align-items: center;
5+
padding: 4.5rem 6.5rem;
6+
font-size: 2rem;
7+
background-color: var(--theme-background-content);
8+
height: 100vh;
9+
10+
.no-access-text {
11+
font-weight: bold;
12+
margin-bottom: 0.25rem;
13+
}
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import './LockedUserView.scss';
2+
3+
export function LockedUserView() {
4+
return (
5+
<div className='user-locked-container'>
6+
<p className='no-access-text'>Access Forbidden.</p>
7+
</div>
8+
);
9+
}

src/web/contexts/CurrentUserProvider.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,11 @@ function CurrentUserProvider({ children }: Readonly<{ children: ReactNode }>) {
2626
setIsLoading(true);
2727
try {
2828
const profile = await keycloak.loadUserProfile();
29-
const user = await GetLoggedInUserAccount();
29+
const { user, isLocked } = await GetLoggedInUserAccount();
3030
SetLoggedInUser({
3131
profile,
3232
user,
33+
isLocked,
3334
});
3435
} catch (e: unknown) {
3536
if (e instanceof Error) throwError(e);

src/web/services/userAccount.ts

+11-4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { backendError } from '../utils/apiError';
1010
export type UserAccount = {
1111
profile: KeycloakProfile;
1212
user: UserWithParticipantRoles | null;
13+
isLocked?: boolean;
1314
};
1415

1516
export type InviteTeamMemberForm = {
@@ -20,17 +21,23 @@ export type InviteTeamMemberForm = {
2021
userRoleId?: number;
2122
};
2223

24+
export type LoggedInUser = {
25+
user: UserWithParticipantRoles | null;
26+
isLocked?: boolean;
27+
};
28+
2329
export type UpdateTeamMemberForm = Omit<InviteTeamMemberForm, 'email'>;
2430

2531
export type UserPayload = z.infer<typeof UserCreationPartial>;
2632

27-
export async function GetLoggedInUserAccount(): Promise<UserWithParticipantRoles | null> {
33+
export async function GetLoggedInUserAccount(): Promise<LoggedInUser> {
2834
try {
2935
const result = await axios.get<UserWithParticipantRoles>(`/users/current`, {
30-
validateStatus: (status) => [200, 404].includes(status),
36+
validateStatus: (status) => [200, 403, 404].includes(status),
3137
});
32-
if (result.status === 200) return result.data;
33-
return null;
38+
if (result.status === 403) return { user: null, isLocked: true };
39+
if (result.status === 200) return { user: result.data };
40+
return { user: null };
3441
} catch (e: unknown) {
3542
throw backendError(e, 'Could not get user account');
3643
}

0 commit comments

Comments
 (0)