Skip to content

Commit d2b0ad3

Browse files
update super user to check from request token instead of database
1 parent d243ebc commit d2b0ad3

File tree

4 files changed

+49
-47
lines changed

4 files changed

+49
-47
lines changed

src/api/middleware/participantsMiddleware.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,12 @@ export const verifyAndEnrichParticipant = async (
1616
) => {
1717
const { participantId } = participantIdSchema.parse(req.params);
1818
const traceId = getTraceId(req);
19-
const userEmail = req.auth?.payload?.email as string;
2019

2120
const participant = await Participant.query()
2221
.findById(participantId)
2322
.withGraphFetched('[types, primaryContact]');
2423

25-
if (!participant || !(await canUserAccessParticipant(userEmail, participantId, traceId))) {
24+
if (!participant || !(await canUserAccessParticipant(req, participantId, traceId))) {
2625
return res.status(403).json({
2726
message: 'You do not have access to this participant.',
2827
errorHash: traceId,

src/api/middleware/userRoleMiddleware.ts

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
1-
import { Handler } from 'express';
1+
import { Handler, Request } from 'express';
22

33
import { UserRoleId } from '../entities/UserRole';
44
import { UserToParticipantRole } from '../entities/UserToParticipantRole';
55
import { ParticipantRequest } from '../services/participantsService';
66
import { findUserByEmail } from '../services/usersService';
77

8+
// Helper to check if email is a UID2 internal email
9+
export const isUid2InternalEmail = (email: string) => email.toLowerCase().includes('@unifiedid.com');
10+
811
export const isUid2Support = async (userEmail: string) => {
12+
// @unifiedid.com users automatically have UID2 Support access
13+
if (isUid2InternalEmail(userEmail)) {
14+
return true;
15+
}
916
const user = await findUserByEmail(userEmail);
1017
const userWithUid2SupportRole = await UserToParticipantRole.query()
1118
.where('userId', user!.id)
@@ -24,17 +31,13 @@ export const isUid2SupportCheck: Handler = async (req: ParticipantRequest, res,
2431
next();
2532
};
2633

27-
export const isSuperUser = async (userEmail: string) => {
28-
const user = await findUserByEmail(userEmail);
29-
const userWithSuperUserRole = await UserToParticipantRole.query()
30-
.where('userId', user!.id)
31-
.andWhere('userRoleId', UserRoleId.SuperUser)
32-
.first();
33-
return !!userWithSuperUserRole;
34+
export const isSuperUser = (req: Request) => {
35+
const userEmail = req.auth?.payload?.email as string;
36+
return isUid2InternalEmail(userEmail);
3437
};
3538

3639
export const isSuperUserCheck: Handler = async (req: ParticipantRequest, res, next) => {
37-
if (!(await isSuperUser(req.auth?.payload?.email as string))) {
40+
if (!isSuperUser(req)) {
3841
return res.status(403).json({
3942
message: 'Unauthorized. You do not have the necessary permissions.',
4043
errorHash: req.headers.traceId,
@@ -46,6 +49,10 @@ export const isSuperUserCheck: Handler = async (req: ParticipantRequest, res, ne
4649
export const isAdminOrUid2SupportCheck: Handler = async (req: ParticipantRequest, res, next) => {
4750
const { participant } = req;
4851
const userEmail = req.auth?.payload.email as string;
52+
// SuperUsers have admin-level access
53+
if (isSuperUser(req)) {
54+
return next();
55+
}
4956
const user = await findUserByEmail(userEmail);
5057
const userParticipant = user?.participants?.find((item) => item.id === participant?.id);
5158
const userIsAdminOrUid2Support =

src/api/middleware/usersMiddleware.ts

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
import { NextFunction, Response } from 'express';
1+
import { NextFunction, Request, Response } from 'express';
22
import { z } from 'zod';
33

44
import { User, UserJobFunction } from '../entities/User';
55
import { getLoggers, getTraceId, TraceId } from '../helpers/loggingHelpers';
6-
import { UserParticipantRequest } from '../services/participantsService';
6+
import { getAllParticipants, UserParticipantRequest } from '../services/participantsService';
77
import { findUserByEmail, UserRequest } from '../services/usersService';
8-
import { isSuperUser, isUid2Support } from './userRoleMiddleware';
8+
import { isSuperUser, isUid2InternalEmail, isUid2Support } from './userRoleMiddleware';
99

10-
// Helper to check if email is a UID2 internal email
11-
const isUid2InternalEmail = (email: string) => email.toLowerCase().endsWith('@unifiedid.com');
10+
// Extended user type with support role flags
11+
type UserWithSupportRoles = User & { isUid2Support: boolean; isSuperUser: boolean };
1212

1313
// Create a new @unifiedid.com user in the portal database from Keycloak token data
1414
const createUid2InternalUser = async (
@@ -49,14 +49,16 @@ export const isUserBelongsToParticipant = async (
4949
};
5050

5151
export const canUserAccessParticipant = async (
52-
requestingUserEmail: string,
52+
req: Request,
5353
participantId: number,
5454
traceId: TraceId
5555
) => {
56-
return (
57-
(await isUid2Support(requestingUserEmail)) ||
58-
(await isUserBelongsToParticipant(requestingUserEmail, participantId, traceId))
59-
);
56+
const requestingUserEmail = req.auth?.payload?.email as string;
57+
// SuperUsers and UID2Support have access to all participants
58+
if (isSuperUser(req) || (await isUid2Support(requestingUserEmail))) {
59+
return true;
60+
}
61+
return isUserBelongsToParticipant(requestingUserEmail, participantId, traceId);
6062
};
6163

6264
export const enrichCurrentUser = async (req: UserRequest, res: Response, next: NextFunction) => {
@@ -77,18 +79,19 @@ export const enrichCurrentUser = async (req: UserRequest, res: Response, next: N
7779
if (user.locked) {
7880
return res.status(403).send([{ message: 'Unauthorized.' }]);
7981
}
80-
req.user = user;
81-
return next();
82-
};
8382

84-
export const enrichUserWithSupportRoles = async (user: User) => {
85-
const userIsUid2Support = await isUid2Support(user.email);
86-
const userIsSuperUser = await isSuperUser(user.email);
87-
return {
88-
...user,
89-
isUid2Support: userIsUid2Support,
90-
isSuperUser: userIsSuperUser,
91-
};
83+
// Enrich user with support roles and participants
84+
const enrichedUser = user as UserWithSupportRoles;
85+
enrichedUser.isUid2Support = await isUid2Support(userEmail);
86+
enrichedUser.isSuperUser = isSuperUser(req);
87+
88+
// SuperUsers and UID2Support get all participants
89+
if (enrichedUser.isSuperUser || enrichedUser.isUid2Support) {
90+
enrichedUser.participants = await getAllParticipants();
91+
}
92+
93+
req.user = enrichedUser;
94+
return next();
9295
};
9396

9497
const userIdSchema = z.object({
@@ -122,9 +125,8 @@ export const verifyAndEnrichUser = async (
122125
return res.status(404).send([{ message: 'The user cannot be found.' }]);
123126
}
124127

125-
const requestingUserEmail = req.auth?.payload?.email as string;
126128
const canRequestingUserAccessParticipant = await canUserAccessParticipant(
127-
requestingUserEmail,
129+
req,
128130
participant!.id,
129131
traceId
130132
);

src/api/services/userService.ts

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,14 @@ import { UserToParticipantRole } from '../entities/UserToParticipantRole';
88
import { getTraceId } from '../helpers/loggingHelpers';
99
import { mapClientTypeToParticipantType } from '../helpers/siteConvertingHelpers';
1010
import { getKcAdminClient } from '../keycloakAdminClient';
11-
import { enrichUserWithSupportRoles } from '../middleware/usersMiddleware';
1211
import { getSite } from './adminServiceClient';
1312
import { getApiRoles } from './apiKeyService';
1413
import {
1514
constructAuditTrailObject,
1615
performAsyncOperationWithAuditTrail,
1716
} from './auditTrailService';
1817
import { removeApiParticipantMemberRole, updateUserProfile } from './kcUsersService';
19-
import { getAllParticipants, UserParticipantRequest } from './participantsService';
18+
import { UserParticipantRequest } from './participantsService';
2019
import { findUserByEmail, UserRequest } from './usersService';
2120

2221
const updateUserSchema = z.object({
@@ -32,17 +31,12 @@ export const UpdateUserRoleIdSchema = z.object({
3231
export const KeycloakRequestSchema = z.object({ email: z.string() });
3332

3433
export const getCurrentUser = async (req: UserRequest) => {
35-
const userEmail = req.auth?.payload?.email as string;
36-
const user = await findUserByEmail(userEmail);
37-
const userWithSupportRoles = await enrichUserWithSupportRoles(user!);
38-
if (userWithSupportRoles.isUid2Support) {
39-
const allParticipants = await getAllParticipants();
40-
userWithSupportRoles.participants = allParticipants;
41-
}
42-
userWithSupportRoles.participants = userWithSupportRoles?.participants?.sort((a, b) =>
43-
a.name.localeCompare(b.name)
44-
);
45-
return userWithSupportRoles;
34+
// req.user is already enriched by enrichCurrentUser middleware with:
35+
// - isUid2Support, isSuperUser flags
36+
// - all participants (for @unifiedid.com and UID2Support users)
37+
const user = req.user!;
38+
user.participants = user.participants?.sort((a, b) => a.name.localeCompare(b.name));
39+
return user;
4640
};
4741

4842
export const getDefaultParticipant = async (req: UserRequest) => {

0 commit comments

Comments
 (0)