From 7e58cfc0909993c8885d63de7ea48709470e98b5 Mon Sep 17 00:00:00 2001 From: aloftus23 <79927030+aloftus23@users.noreply.github.com> Date: Thu, 10 Oct 2024 16:31:58 -0400 Subject: [PATCH] Update PUT user to allow regional admins to update users within their region (#686) * Allow regionalAdmins to update users * Update so globaladmins without regions aren't blocked * Add to approve endpoint as well * MAke matches user region not async * Fix can access user * hotfix to resolve issue where regional admin could not approve user from within their region --------- Co-authored-by: Janson Bunce --- backend/src/api/auth.ts | 20 +++++++++++++++++++- backend/src/api/organizations.ts | 5 ++++- backend/src/api/users.ts | 9 ++++++++- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/backend/src/api/auth.ts b/backend/src/api/auth.ts index a40356e8..9a248d5c 100644 --- a/backend/src/api/auth.ts +++ b/backend/src/api/auth.ts @@ -21,6 +21,7 @@ export interface UserToken { org: string; role: 'user' | 'admin'; }[]; + regionId: string | null; dateAcceptedTerms: Date | undefined; acceptedTermsVersion: string | undefined; lastLoggedIn: Date | undefined; @@ -95,6 +96,7 @@ export const userTokenBody = (user): UserToken => ({ email: user.email, userType: user.userType, dateAcceptedTerms: user.dateAcceptedTerms, + regionId: user.regionId, acceptedTermsVersion: user.acceptedTermsVersion, lastLoggedIn: user.lastLoggedIn, loginBlockedByMaintenance: user.loginBlockedByMaintenance, @@ -310,9 +312,25 @@ export const matchesRegion = async ( return regionId === (await getRegion(organizationId)); }; +/** Checks if the authorizer's region is the same as the user's being modified */ +export const matchesUserRegion = ( + event: APIGatewayProxyEvent, + userRegionId?: string +) => { + // Global admins can match with any region + if (isGlobalWriteAdmin(event)) return true; + if (!event.requestContext.authorizer || !userRegionId) return false; + return userRegionId === event.requestContext.authorizer.regionId; +}; + /** Checks if the current user is allowed to access (modify) a user with id userId */ export const canAccessUser = (event: APIGatewayProxyEvent, userId?: string) => { - return userId && (userId === getUserId(event) || isGlobalWriteAdmin(event)); + return ( + userId && + (userId === getUserId(event) || + isGlobalWriteAdmin(event) || + isRegionalAdmin(event)) + ); }; /** Checks if a user is an admin of the given organization */ diff --git a/backend/src/api/organizations.ts b/backend/src/api/organizations.ts index c223ba66..6a75afe7 100644 --- a/backend/src/api/organizations.ts +++ b/backend/src/api/organizations.ts @@ -32,7 +32,8 @@ import { isRegionalAdmin, isRegionalAdminForOrganization, getOrgMemberships, - isGlobalViewAdmin + isGlobalViewAdmin, + matchesUserRegion } from './auth'; import { In } from 'typeorm'; import { plainToClass } from 'class-transformer'; @@ -981,6 +982,8 @@ export const addUserV2 = wrapHandler(async (event) => { // Get User from the database const user = await User.findOneOrFail(userId); + if (!matchesUserRegion(event, user.regionId)) return Unauthorized; + const newRoleData = { user: user, organization: org, diff --git a/backend/src/api/users.ts b/backend/src/api/users.ts index da35e748..08cc4a2d 100644 --- a/backend/src/api/users.ts +++ b/backend/src/api/users.ts @@ -33,7 +33,8 @@ import { isGlobalViewAdmin, isRegionalAdmin, isOrgAdmin, - isGlobalWriteAdmin + isGlobalWriteAdmin, + matchesUserRegion } from './auth'; import { fetchAssessmentsByUser } from '../tasks/rscSync'; @@ -634,6 +635,9 @@ export const registrationApproval = wrapHandler(async (event) => { return NotFound; } + // Check if authorizer's region matches the user's + if (!matchesUserRegion(event, user.regionId)) return Unauthorized; + // Send email notification await sendRegistrationApprovedEmail( user.email, @@ -919,6 +923,9 @@ export const updateV2 = wrapHandler(async (event) => { return NotFound; } + // Check if authorizer's region matches the user's + if (!matchesUserRegion(event, user.regionId)) return Unauthorized; + if (body.state) { body.regionId = REGION_STATE_MAP[body.state]; }