From 754ae62bd38e3cb533961e2bc866f119f7d67c6a Mon Sep 17 00:00:00 2001 From: Riku Rouvila Date: Sun, 30 Jun 2024 20:28:11 +0300 Subject: [PATCH 001/269] =?UTF-8?q?Configurable=20user=20roles=20=E2=80=93?= =?UTF-8?q?=20refactor=20roles=20to=20be=20fetched=20from=20country=20conf?= =?UTF-8?q?ig,=20allow=20scopes=20to=20be=20configured=20per-role?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/auth/package.json | 3 +- .../auth/src/features/authenticate/handler.ts | 38 +- .../auth/src/features/authenticate/service.ts | 22 +- .../features/authenticateSuperUser/handler.ts | 11 +- packages/auth/src/features/scopes/service.ts | 47 + packages/auth/src/server.ts | 18 +- packages/auth/tsconfig.json | 26 +- packages/client/graphql.schema.json | 804 ++++-------------- .../src/components/Header/Hamburger.tsx | 20 +- .../client/src/components/ProfileMenu.tsx | 8 +- .../client/src/declarations/index.test.ts | 12 +- packages/client/src/forms/index.ts | 34 + .../forms/user/fieldDefinitions/createUser.ts | 11 - .../client/src/forms/user/query/queries.ts | 30 +- packages/client/src/i18n/reducer.ts | 51 +- .../client/src/profile/profileSelectors.ts | 2 +- packages/client/src/profile/queries.ts | 8 +- packages/client/src/tests/util.tsx | 250 +++--- packages/client/src/transformer/index.ts | 2 +- packages/client/src/user/queries.ts | 16 +- packages/client/src/user/selectors.ts | 15 - packages/client/src/user/userReducer.ts | 155 ++-- packages/client/src/utils/authUtils.ts | 24 +- .../utils/gateway-deprecated-do-not-use.d.ts | 25 +- packages/client/src/utils/gateway.ts | 295 +++---- packages/client/src/utils/userUtils.ts | 17 + .../AdvancedSearch/AdvancedSearchResult.tsx | 2 +- .../src/views/DataProvider/birth/queries.ts | 18 +- .../src/views/DataProvider/death/queries.ts | 18 +- .../views/DataProvider/marriage/queries.ts | 18 +- .../readyForReview/ReadyForReview.tsx | 2 +- .../requiresUpdate/RequiresUpdate.tsx | 2 +- .../sentForReview/SentForReview.tsx | 2 +- .../src/views/Performance/FieldAgentList.tsx | 3 +- .../client/src/views/RecordAudit/History.tsx | 4 +- .../src/views/RecordAudit/RecordAudit.tsx | 4 +- .../RegisterForm/RegisterForm.init.test.tsx | 12 +- .../src/views/RegisterForm/RegisterForm.tsx | 2 +- .../src/views/RegisterForm/ReviewForm.tsx | 2 +- .../RegisterForm/review/ReviewSection.tsx | 2 +- .../src/views/SearchResult/SearchResult.tsx | 2 +- .../client/src/views/Settings/items/Role.tsx | 6 +- packages/client/src/views/Settings/queries.ts | 4 +- .../Config/Certificates/Certificates.tsx | 5 +- .../Config/Certificates/previewDummyData.ts | 12 +- .../Config/UserRoles/UserRoles.test.tsx | 157 ---- .../SysAdmin/Config/UserRoles/UserRoles.tsx | 212 ----- .../views/SysAdmin/Config/UserRoles/utils.ts | 19 - .../SysAdmin/Performance/WorkflowStatus.tsx | 4 +- .../src/views/SysAdmin/Performance/queries.ts | 25 +- .../Team/user/UserAuditActionModal.test.tsx | 24 +- .../src/views/SysAdmin/Team/user/UserList.tsx | 10 +- .../Team/user/userCreation/CreateNewUser.tsx | 4 +- .../user/userCreation/SignatureForm.test.tsx | 7 +- .../Team/user/userCreation/UserForm.tsx | 31 +- .../Team/user/userCreation/UserReviewForm.tsx | 40 +- .../client/src/views/SysAdmin/Team/utils.ts | 4 - .../src/views/Unlock/ForgotPIN.test.tsx | 12 +- .../client/src/views/UserAudit/UserAudit.tsx | 71 +- .../UserRoles/UserRoleManagementModal.tsx | 259 ------ .../src/views/UserSetup/SetupReviewPage.tsx | 47 +- packages/client/src/views/ViewRecord/query.ts | 2 +- packages/commons/src/authentication.ts | 95 ++- packages/commons/src/http.ts | 24 + packages/commons/src/token-verifier.ts | 1 + packages/data-seeder/src/index.ts | 13 +- packages/data-seeder/src/roles.ts | 223 ----- packages/data-seeder/src/users.ts | 21 +- packages/data-seeder/src/utils.ts | 4 +- .../src/features/role/root-resolvers.ts | 83 +- .../gateway/src/features/role/schema.graphql | 65 +- .../src/features/role/type-resolvers.ts | 27 +- .../src/features/user/root-resolvers.ts | 40 +- .../gateway/src/features/user/schema.graphql | 5 +- .../src/features/user/type-resolvers.ts | 33 +- .../gateway/src/features/user/utils/index.ts | 1 + packages/gateway/src/graphql/config.ts | 54 +- packages/gateway/src/graphql/index.graphql | 1 - packages/gateway/src/graphql/schema.d.ts | 213 ++--- packages/gateway/src/graphql/schema.graphql | 71 +- packages/gateway/src/rate-limit.ts | 6 +- .../20240628124653-remove-userroles.ts | 80 ++ .../search/src/features/search/service.ts | 2 +- packages/user-mgnt/src/config/routes.ts | 92 +- .../src/features/createUser/handler.ts | 23 +- .../src/features/createUser/service.ts | 7 +- .../src/features/getRoles/handler.test.ts | 249 ------ .../src/features/getRoles/handler.ts | 70 -- .../user-mgnt/src/features/getUser/handler.ts | 6 +- .../user-mgnt/src/features/system/handler.ts | 6 +- .../src/features/updateRole/handler.ts | 6 +- .../src/features/updateUser/handler.ts | 37 +- .../src/features/verifyPassword/handler.ts | 14 +- .../features/verifyPasswordById/handler.ts | 2 - .../src/features/verifyUser/handler.ts | 3 - packages/user-mgnt/src/model/user.ts | 42 +- packages/user-mgnt/src/server.ts | 15 + 97 files changed, 1364 insertions(+), 3262 deletions(-) create mode 100644 packages/auth/src/features/scopes/service.ts delete mode 100644 packages/client/src/user/selectors.ts delete mode 100644 packages/client/src/views/SysAdmin/Config/UserRoles/UserRoles.test.tsx delete mode 100644 packages/client/src/views/SysAdmin/Config/UserRoles/UserRoles.tsx delete mode 100644 packages/client/src/views/SysAdmin/Config/UserRoles/utils.ts delete mode 100644 packages/client/src/views/UserRoles/UserRoleManagementModal.tsx delete mode 100644 packages/data-seeder/src/roles.ts create mode 100644 packages/migration/src/migrations/user-mgnt/20240628124653-remove-userroles.ts delete mode 100644 packages/user-mgnt/src/features/getRoles/handler.test.ts delete mode 100644 packages/user-mgnt/src/features/getRoles/handler.ts diff --git a/packages/auth/package.json b/packages/auth/package.json index 23a37d129a1..55dba9d0758 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -84,7 +84,8 @@ "" ], "moduleNameMapper": { - "@auth/(.*)": "/src/$1" + "@auth/(.*)": "/src/$1", + "@opencrvs/commons/(.*)": "@opencrvs/commons/build/dist/$1" }, "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$", "setupFiles": [ diff --git a/packages/auth/src/features/authenticate/handler.ts b/packages/auth/src/features/authenticate/handler.ts index c1118272ab4..75fc1309617 100644 --- a/packages/auth/src/features/authenticate/handler.ts +++ b/packages/auth/src/features/authenticate/handler.ts @@ -8,21 +8,23 @@ * * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. */ -import * as Hapi from '@hapi/hapi' -import * as Joi from 'joi' +import { JWT_ISSUER, WEB_USER_JWT_AUDIENCES } from '@auth/constants' import { + IAuthentication, authenticate, - storeUserInformation, createToken, generateAndSendVerificationCode, - IAuthentication + storeUserInformation } from '@auth/features/authenticate/service' import { NotificationEvent, generateNonce } from '@auth/features/verifyCode/service' -import { unauthorized, forbidden } from '@hapi/boom' -import { WEB_USER_JWT_AUDIENCES, JWT_ISSUER } from '@auth/constants' +import { forbidden, unauthorized } from '@hapi/boom' +import * as Hapi from '@hapi/hapi' +import { CoreUserRole } from '@opencrvs/commons/authentication' +import * as Joi from 'joi' +import { getUserRoleScopeMapping } from '@auth/features/scopes/service' interface IAuthPayload { username: string @@ -34,6 +36,7 @@ interface IAuthResponse { mobile?: string email?: string status: string + systemRole: CoreUserRole token?: string } @@ -43,6 +46,7 @@ export default async function authenticateHandler( ): Promise { const payload = request.payload as IAuthPayload let result: IAuthentication + const { username, password } = payload try { result = await authenticate(username.trim(), password) @@ -58,15 +62,28 @@ export default async function authenticateHandler( mobile: result.mobile, email: result.email, status: result.status, + systemRole: result.systemRole, nonce } const isPendingUser = response.status && response.status === 'pending' + const roleScopeMappings = await getUserRoleScopeMapping() + + let scopes = [] + + const role = result.role as keyof typeof roleScopeMappings + + if (roleScopeMappings[role]) { + scopes = roleScopeMappings[role] + } else { + scopes = roleScopeMappings[response.systemRole] + } + if (isPendingUser) { response.token = await createToken( result.userId, - result.scope, + scopes, WEB_USER_JWT_AUDIENCES, JWT_ISSUER ) @@ -75,7 +92,7 @@ export default async function authenticateHandler( nonce, result.name, result.userId, - result.scope, + scopes, result.mobile, result.email ) @@ -84,13 +101,14 @@ export default async function authenticateHandler( await generateAndSendVerificationCode( nonce, - result.scope, + scopes, notificationEvent, result.name, result.mobile, result.email ) } + return response } @@ -104,5 +122,7 @@ export const responseSchema = Joi.object({ mobile: Joi.string().optional(), email: Joi.string().optional(), status: Joi.string(), + role: Joi.string(), + systemRole: Joi.string(), token: Joi.string().optional() }) diff --git a/packages/auth/src/features/authenticate/service.ts b/packages/auth/src/features/authenticate/service.ts index c4afa38b732..505cda5f916 100644 --- a/packages/auth/src/features/authenticate/service.ts +++ b/packages/auth/src/features/authenticate/service.ts @@ -32,9 +32,12 @@ import { storeVerificationCode } from '@auth/features/verifyCode/service' import { logger } from '@opencrvs/commons' +import { CoreUserRole } from '@opencrvs/commons/authentication' import { unauthorized } from '@hapi/boom' -import { chainW, tryCatch } from 'fp-ts/Either' -import { pipe } from 'fp-ts/function' + +import * as F from 'fp-ts' +const { chainW, tryCatch } = F.either +const { pipe } = F.function const cert = readFileSync(CERT_PRIVATE_KEY_PATH) const publicCert = readFileSync(CERT_PUBLIC_KEY_PATH) @@ -55,8 +58,9 @@ export interface IAuthentication { mobile?: string userId: string status: string - scope: string[] email?: string + systemRole: CoreUserRole + role: string } export interface ISystemAuthentication { @@ -86,11 +90,14 @@ export async function authenticate( if (res.status !== 200) { throw Error(res.statusText) } + const body = await res.json() + return { name: body.name, userId: body.id, - scope: body.scope, + role: body.role, + systemRole: body.systemRole, status: body.status, mobile: body.mobile, email: body.email @@ -174,12 +181,7 @@ export async function generateAndSendVerificationCode( email?: string ) { const isDemoUser = scope.indexOf('demo') > -1 || QA_ENV - logger.info( - `isDemoUser, - ${JSON.stringify({ - isDemoUser: isDemoUser - })}` - ) + logger.info(`Is demo user: ${isDemoUser}. Scopes: ${scope.join(', ')}`) let verificationCode if (isDemoUser) { verificationCode = '000000' diff --git a/packages/auth/src/features/authenticateSuperUser/handler.ts b/packages/auth/src/features/authenticateSuperUser/handler.ts index df10febf82a..c27f4d0228f 100644 --- a/packages/auth/src/features/authenticateSuperUser/handler.ts +++ b/packages/auth/src/features/authenticateSuperUser/handler.ts @@ -17,6 +17,8 @@ import { } from '@auth/features/authenticate/service' import { unauthorized } from '@hapi/boom' import { WEB_USER_JWT_AUDIENCES, JWT_ISSUER } from '@auth/constants' +import { DEFAULT_ROLE_SCOPES } from '@opencrvs/commons/authentication' +import { logger } from '@opencrvs/commons' interface IAuthPayload { username: string @@ -36,9 +38,16 @@ export default async function authenticateSuperUserHandler( throw unauthorized() } + if (result.status === 'deactivated') { + logger.info('Login attempt with a deactivated super user account detected') + throw unauthorized() + } + + const scope = DEFAULT_ROLE_SCOPES.SUPER_ADMIN + const token = await createToken( result.userId, - result.scope, + scope, WEB_USER_JWT_AUDIENCES, JWT_ISSUER ) diff --git a/packages/auth/src/features/scopes/service.ts b/packages/auth/src/features/scopes/service.ts new file mode 100644 index 00000000000..e5e1f16092c --- /dev/null +++ b/packages/auth/src/features/scopes/service.ts @@ -0,0 +1,47 @@ +import { COUNTRY_CONFIG_URL } from '@auth/constants' +import { fetchJSON, joinURL, logger } from '@opencrvs/commons' +import { + DEFAULT_CORE_ROLE_SCOPES, + Scope, + CoreUserRole +} from '@opencrvs/commons/authentication' + +type NewRolesResponseFormat = Array<{ + id: string + systemRole: CoreUserRole + labels: Array<{ language: string; label: string }> + scopes: Scope[] +}> + +function isOpenCRVS1_7FormatWithScopes( + response: Record | NewRolesResponseFormat +): response is NewRolesResponseFormat { + return Array.isArray(response) +} + +export async function getUserRoleScopeMapping() { + const scopes = await fetchJSON< + NewRolesResponseFormat | typeof DEFAULT_CORE_ROLE_SCOPES + >(joinURL(COUNTRY_CONFIG_URL, '/roles')) + + if (!isOpenCRVS1_7FormatWithScopes(scopes)) { + logger.error('Country config implements the old /roles response format') + throw new Error('Old /roles response format. Check logs for more info') + } + logger.info( + 'Country config implements the new /roles response format. Custom scopes apply' + ) + + const userRoleMappings = scopes.reduce>( + (acc, { id, scopes }) => { + acc[id] = scopes + return acc + }, + {} + ) + + return { + ...DEFAULT_CORE_ROLE_SCOPES, + ...userRoleMappings + } +} diff --git a/packages/auth/src/server.ts b/packages/auth/src/server.ts index bb8def41742..d9fcbae9bee 100644 --- a/packages/auth/src/server.ts +++ b/packages/auth/src/server.ts @@ -72,6 +72,7 @@ import { getPublicKey } from '@auth/features/authenticate/service' import anonymousTokenHandler, { responseSchema } from './features/anonymousToken/handler' +import { Boom, badRequest } from '@hapi/boom' export async function createServer() { let whitelist: string[] = [HOSTNAME] @@ -84,7 +85,22 @@ export async function createServer() { port: AUTH_PORT, routes: { cors: { origin: whitelist }, - payload: { maxBytes: 52428800, timeout: DEFAULT_TIMEOUT } + payload: { maxBytes: 52428800, timeout: DEFAULT_TIMEOUT }, + response: { + failAction: async (req, _2, err: Boom) => { + if (process.env.NODE_ENV === 'production') { + // In prod, log a limited error message and throw the default Bad Request error. + logger.error(`Response validationError: ${err.message}`) + throw badRequest(`Invalid response payload returned from handler`) + } else { + // During development, log and respond with the full error. + logger.error( + `${req.path} response has a validation error: ${err.message}` + ) + throw err + } + } + } } }) diff --git a/packages/auth/tsconfig.json b/packages/auth/tsconfig.json index 00523a2349f..b541f39e20b 100644 --- a/packages/auth/tsconfig.json +++ b/packages/auth/tsconfig.json @@ -2,9 +2,7 @@ "compilerOptions": { "baseUrl": "./src", "paths": { - "@auth/*": [ - "./*" - ] + "@auth/*": ["./*"] }, "target": "es6", "module": "commonjs", @@ -12,30 +10,18 @@ "skipLibCheck": true, "outDir": "build/dist", "sourceMap": true, - "moduleResolution": "node", + "moduleResolution": "node16", "rootDir": ".", - "lib": [ - "esnext.asynciterable", - "es6", - "es2017" - ], + "lib": ["esnext.asynciterable", "es6", "es2017"], "forceConsistentCasingInFileNames": true, "noImplicitReturns": true, "noImplicitThis": true, "noImplicitAny": true, "strictNullChecks": true, - "suppressImplicitAnyIndexErrors": true, "noUnusedLocals": true, - "types": [ - "fhir", - "jest" - ] + "types": ["fhir", "jest"] }, - "include": [ - "resources/**/*.ts", - "src/**/*.ts", - "typings" - ], + "include": ["resources/**/*.ts", "src/**/*.ts", "typings"], "exclude": [ "node_modules", "build", @@ -43,4 +29,4 @@ "acceptance-tests", "src/setupTests.ts" ] -} \ No newline at end of file +} diff --git a/packages/client/graphql.schema.json b/packages/client/graphql.schema.json index fc275baeb7a..cae674b5483 100644 --- a/packages/client/graphql.schema.json +++ b/packages/client/graphql.schema.json @@ -4702,129 +4702,6 @@ "enumValues": null, "possibleTypes": null }, - { - "kind": "INPUT_OBJECT", - "name": "ComparisonInput", - "description": null, - "fields": null, - "inputFields": [ - { - "name": "eq", - "description": null, - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "gt", - "description": null, - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "gte", - "description": null, - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "in", - "description": null, - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - } - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "lt", - "description": null, - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "lte", - "description": null, - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "ne", - "description": null, - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "nin", - "description": null, - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - } - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - } - ], - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, { "kind": "OBJECT", "name": "ContactPoint", @@ -8302,6 +8179,65 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "OBJECT", + "name": "I18nMessage", + "description": null, + "fields": [ + { + "name": "defaultMessage", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "id", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, { "kind": "SCALAR", "name": "ID", @@ -8635,49 +8571,6 @@ ], "possibleTypes": null }, - { - "kind": "INPUT_OBJECT", - "name": "LabelInput", - "description": null, - "fields": null, - "inputFields": [ - { - "name": "label", - "description": null, - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "lang", - "description": null, - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - } - ], - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, { "kind": "OBJECT", "name": "LocalRegistrar", @@ -12736,35 +12629,6 @@ "isDeprecated": false, "deprecationReason": null }, - { - "name": "updateRole", - "description": null, - "args": [ - { - "name": "systemRole", - "description": null, - "type": { - "kind": "INPUT_OBJECT", - "name": "SystemRoleInput", - "ofType": null - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - } - ], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "Response", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, { "name": "usernameReminder", "description": null, @@ -15507,15 +15371,15 @@ "deprecationReason": null }, { - "name": "getSystemRoles", + "name": "getTotalCertifications", "description": null, "args": [ { - "name": "active", + "name": "locationId", "description": null, "type": { "kind": "SCALAR", - "name": "Boolean", + "name": "String", "ofType": null }, "defaultValue": null, @@ -15523,116 +15387,23 @@ "deprecationReason": null }, { - "name": "role", + "name": "timeEnd", "description": null, "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } }, "defaultValue": null, "isDeprecated": false, "deprecationReason": null }, { - "name": "sortBy", - "description": null, - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "sortOrder", - "description": null, - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "title", - "description": null, - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "value", - "description": null, - "type": { - "kind": "INPUT_OBJECT", - "name": "ComparisonInput", - "ofType": null - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - } - ], - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "SystemRole", - "ofType": null - } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "getTotalCertifications", - "description": null, - "args": [ - { - "name": "locationId", - "description": null, - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "timeEnd", - "description": null, - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "timeStart", + "name": "timeStart", "description": null, "type": { "kind": "NON_NULL", @@ -16055,6 +15826,30 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "getUserRoles", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "UserRole", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "getVSExports", "description": null, @@ -19262,174 +19057,6 @@ "enumValues": null, "possibleTypes": null }, - { - "kind": "OBJECT", - "name": "Response", - "description": null, - "fields": [ - { - "name": "roleIdMap", - "description": null, - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "Map", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "Role", - "description": null, - "fields": [ - { - "name": "_id", - "description": null, - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "ID", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "labels", - "description": null, - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "RoleLabel", - "ofType": null - } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "INPUT_OBJECT", - "name": "RoleInput", - "description": null, - "fields": null, - "inputFields": [ - { - "name": "_id", - "description": null, - "type": { - "kind": "SCALAR", - "name": "ID", - "ofType": null - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "labels", - "description": null, - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "INPUT_OBJECT", - "name": "LabelInput", - "ofType": null - } - } - } - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - } - ], - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "RoleLabel", - "description": null, - "fields": [ - { - "name": "label", - "description": null, - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "lang", - "description": null, - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, { "kind": "OBJECT", "name": "SMSNotification", @@ -19659,7 +19286,7 @@ "args": [], "type": { "kind": "OBJECT", - "name": "Role", + "name": "UserRole", "ofType": null }, "isDeprecated": false, @@ -20258,160 +19885,6 @@ "enumValues": null, "possibleTypes": null }, - { - "kind": "OBJECT", - "name": "SystemRole", - "description": null, - "fields": [ - { - "name": "active", - "description": null, - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "Boolean", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "id", - "description": null, - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "ID", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "roles", - "description": null, - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "Role", - "ofType": null - } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "value", - "description": null, - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "ENUM", - "name": "SystemRoleType", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "INPUT_OBJECT", - "name": "SystemRoleInput", - "description": null, - "fields": null, - "inputFields": [ - { - "name": "active", - "description": null, - "type": { - "kind": "SCALAR", - "name": "Boolean", - "ofType": null - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "id", - "description": null, - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "ID", - "ofType": null - } - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "roles", - "description": null, - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "INPUT_OBJECT", - "name": "RoleInput", - "ofType": null - } - } - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "value", - "description": null, - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - } - ], - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, { "kind": "ENUM", "name": "SystemRoleType", @@ -21222,7 +20695,7 @@ "name": null, "ofType": { "kind": "OBJECT", - "name": "Role", + "name": "UserRole", "ofType": null } }, @@ -21914,6 +21387,89 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "OBJECT", + "name": "UserRole", + "description": null, + "fields": [ + { + "name": "id", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "label", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "I18nMessage", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "scopes", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "systemRole", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "SystemRoleType", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, { "kind": "OBJECT", "name": "VSExport", diff --git a/packages/client/src/components/Header/Hamburger.tsx b/packages/client/src/components/Header/Hamburger.tsx index 9b724ed4442..2e59d88a1da 100644 --- a/packages/client/src/components/Header/Hamburger.tsx +++ b/packages/client/src/components/Header/Hamburger.tsx @@ -8,24 +8,23 @@ * * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. */ -import React, { useState } from 'react' -import { useIntl } from 'react-intl' -import { useSelector } from 'react-redux' -import { getUserDetails } from '@client/profile/profileSelectors' -import { getLanguage } from '@client/i18n/selectors' -import { getIndividualNameObj } from '@client/utils/userUtils' import { Avatar } from '@client/components/Avatar' -import { ExpandingMenu } from '@opencrvs/components/lib/ExpandingMenu' import { FixedNavigation } from '@client/components/interface/Navigation' +import { getLanguage } from '@client/i18n/selectors' +import { getUserDetails } from '@client/profile/profileSelectors' +import { getIndividualNameObj } from '@client/utils/userUtils' import { Button } from '@opencrvs/components/lib/Button' +import { ExpandingMenu } from '@opencrvs/components/lib/ExpandingMenu' import { Icon } from '@opencrvs/components/lib/Icon' -import { getUserRole } from '@client/views/SysAdmin/Config/UserRoles/utils' -import { Role } from '@client/utils/gateway' +import React, { useState } from 'react' +import { useIntl } from 'react-intl' +import { useSelector } from 'react-redux' export function Hamburger() { const [showMenu, setShowMenu] = useState(false) const userDetails = useSelector(getUserDetails) const language = useSelector(getLanguage) + const intl = useIntl() const toggleMenu = () => { setShowMenu((prevState) => !prevState) } @@ -37,9 +36,8 @@ export function Hamburger() { : '' } - // let's remove this type assertion after #4458 merges in const role = - (userDetails?.role && getUserRole(language, userDetails.role as Role)) ?? '' + (userDetails?.role && intl.formatMessage(userDetails.role.label)) ?? '' const avatar = diff --git a/packages/client/src/components/ProfileMenu.tsx b/packages/client/src/components/ProfileMenu.tsx index bcecc205efc..5fded9e7a2a 100644 --- a/packages/client/src/components/ProfileMenu.tsx +++ b/packages/client/src/components/ProfileMenu.tsx @@ -29,7 +29,6 @@ import { getUserDetails } from '@client/profile/profileSelectors' import { redirectToAuthentication } from '@client/profile/profileActions' import { goToSettings } from '@client/navigation' import { buttonMessages } from '@client/i18n/messages' -import { getUserRole } from '@client/views/SysAdmin/Config/UserRoles/utils' const UserName = styled.div` color: ${({ theme }) => theme.colors.copy}; @@ -96,14 +95,13 @@ const ProfileMenuComponent = ({ userDetails: UserDetails | null ): JSX.Element => { const userName = getUserName(language, userDetails) - // let's remove this type assertion after #4458 merges in - const userRole = - userDetails?.role && getUserRole(language, userDetails.role) return ( <> {userName} - {userRole} + + {userDetails && intl.formatMessage(userDetails.role.label)} + ) } diff --git a/packages/client/src/declarations/index.test.ts b/packages/client/src/declarations/index.test.ts index 85794d7855f..171c65bb80c 100644 --- a/packages/client/src/declarations/index.test.ts +++ b/packages/client/src/declarations/index.test.ts @@ -324,13 +324,11 @@ describe('archiveDeclaration tests', () => { creationDate: '2133213212', mobile: '09123433', role: { - _id: '778464c0-08f8-4fb7-8a37-b86d1efc462a', - labels: [ - { - lang: 'en', - label: 'Field Agent' - } - ] + label: { + id: 'userRoles.fieldAgent', + defaultMessage: 'Field Agent', + description: 'Field Agent' + } }, name: [], localRegistrar: { name: [], role: 'FIELD_AGENT' as SystemRoleType } diff --git a/packages/client/src/forms/index.ts b/packages/client/src/forms/index.ts index 1526d6ea32b..86c731a8ae9 100644 --- a/packages/client/src/forms/index.ts +++ b/packages/client/src/forms/index.ts @@ -1256,3 +1256,37 @@ export interface ICertificate { payments?: Payment[] data?: string } + +export function modifyFormField( + form: IForm, + sectionId: string, + groupId: string, + fieldName: string, + modifyFn: (field: IFormField) => IFormField +) { + return { + ...form, + sections: form.sections.map((section) => { + if (section.id === sectionId) { + return { + ...section, + groups: section.groups.map((group) => { + if (group.id === groupId) { + return { + ...group, + fields: group.fields.map((field) => { + if (field.name === fieldName) { + return modifyFn(field) + } + return field + }) + } + } + return group + }) + } + } + return section + }) + } +} diff --git a/packages/client/src/forms/user/fieldDefinitions/createUser.ts b/packages/client/src/forms/user/fieldDefinitions/createUser.ts index 37332d9fa9a..203e3c764e7 100644 --- a/packages/client/src/forms/user/fieldDefinitions/createUser.ts +++ b/packages/client/src/forms/user/fieldDefinitions/createUser.ts @@ -198,17 +198,6 @@ export function userSectionFormType(): ISerializedFormSection { options: [], conditionals: [] }, - { - name: 'systemRole', - type: TEXT, - label: userFormMessages.systemRole, - required: false, - hidden: true, - hideValueInPreview: true, - initialValue: '', - validator: [], - conditionals: [] - }, { name: 'device', type: TEXT, diff --git a/packages/client/src/forms/user/query/queries.ts b/packages/client/src/forms/user/query/queries.ts index 58d5e49cf40..57fd1e175b3 100644 --- a/packages/client/src/forms/user/query/queries.ts +++ b/packages/client/src/forms/user/query/queries.ts @@ -11,35 +11,25 @@ import { gql } from '@apollo/client' import { client } from '@client/utils/apolloClient' -export const getSystemRolesQuery = gql` - query getSystemRoles($value: ComparisonInput) { - getSystemRoles(active: true, value: $value) { +export const getUserRolesQuery = gql` + query getUserRoles { + getUserRoles { id - value - roles { - _id - labels { - lang - label - } + systemRole + label { + id + defaultMessage + description } } } ` -export const updateRoleQuery = gql` - mutation updateRole($systemRole: SystemRoleInput) { - updateRole(systemRole: $systemRole) { - roleIdMap - } - } -` -async function fetchRoles(criteria = {}) { +async function fetchRoles() { return ( client && client.query({ - query: getSystemRolesQuery, - variables: criteria, + query: getUserRolesQuery, fetchPolicy: 'no-cache' }) ) diff --git a/packages/client/src/i18n/reducer.ts b/packages/client/src/i18n/reducer.ts index 848404efb37..50a46e0c80f 100644 --- a/packages/client/src/i18n/reducer.ts +++ b/packages/client/src/i18n/reducer.ts @@ -8,23 +8,19 @@ * * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. */ -import { LoopReducer, Loop, loop, Cmd } from 'redux-loop' import * as actions from '@client/i18n/actions' import { - getDefaultLanguage, getAvailableLanguages, + getDefaultLanguage, storeLanguage } from '@client/i18n/utils' import * as offlineActions from '@client/offline/actions' import { ILocation } from '@client/offline/reducer' import { - IRoleMessagesLoadedAction, IRoleLoadedAction, - rolesMessageAddData, - ROLES_LOADED + IRoleMessagesLoadedAction } from '@client/user/userReducer' -import { SystemRole } from '@client/utils/gateway' -import { getUserRoleIntlKey } from '@client/views/SysAdmin/Team/utils' +import { Cmd, Loop, LoopReducer, loop } from 'redux-loop' export interface IntlMessages { [key: string]: string @@ -87,32 +83,6 @@ const formatLocationLanguageState = ( return languages } -const formatRoleLanguageState = ( - systemRoles: SystemRole[], - languages: ILanguageState -): ILanguageState => { - const transformedLanguages: ILanguageState = { - ...languages - } - systemRoles.forEach((systemRole) => { - systemRole.roles.forEach((role) => { - role.labels.forEach((label) => { - if (transformedLanguages[label.lang]) { - const generatedLabel = getUserRoleIntlKey(role._id) - transformedLanguages[label.lang] = { - ...transformedLanguages[label.lang], - messages: { - ...transformedLanguages[label.lang].messages, - [generatedLabel]: label.label - } - } - } - }) - }) - }) - return transformedLanguages -} - const getNextMessages = ( language: string, languages: ILanguageState @@ -187,21 +157,6 @@ export const intlReducer: LoopReducer = ( messages: updatedMessages, languages: languagesWithFacilities } - case ROLES_LOADED: - const systemRoles = action.payload.systemRoles - const languageWithRoles = formatRoleLanguageState( - systemRoles, - state.languages - ) - return loop( - { - ...state, - messages: getNextMessages(state.language, languageWithRoles), - languages: languageWithRoles - }, - - Cmd.action(rolesMessageAddData()) - ) default: return state diff --git a/packages/client/src/profile/profileSelectors.ts b/packages/client/src/profile/profileSelectors.ts index 2785d7aab59..9a95bc86c39 100644 --- a/packages/client/src/profile/profileSelectors.ts +++ b/packages/client/src/profile/profileSelectors.ts @@ -26,7 +26,7 @@ export const getTokenPayload = ( store: IStoreState ): ProfileState['tokenPayload'] => getKey(store, 'tokenPayload') -export const getScope = (store: IStoreState): Scope | null => { +export const getScope = (store: IStoreState): Scope[] | null => { const tokenPayload = getTokenPayload(store) return tokenPayload && tokenPayload.scope } diff --git a/packages/client/src/profile/queries.ts b/packages/client/src/profile/queries.ts index 24028720d5b..d7aadb74a2e 100644 --- a/packages/client/src/profile/queries.ts +++ b/packages/client/src/profile/queries.ts @@ -29,10 +29,10 @@ const FETCH_USER = gql` email systemRole role { - _id - labels { - lang - label + label { + id + defaultMessage + description } } status diff --git a/packages/client/src/tests/util.tsx b/packages/client/src/tests/util.tsx index e945ee99bda..2b9a7fb860b 100644 --- a/packages/client/src/tests/util.tsx +++ b/packages/client/src/tests/util.tsx @@ -54,7 +54,7 @@ import { mockOfflineData } from './mock-offline-data' import { Section, SubmissionAction } from '@client/forms' import { SUBMISSION_STATUS } from '@client/declarations' import { vi } from 'vitest' -import { getSystemRolesQuery } from '@client/forms/user/query/queries' +import { getUserRolesQuery } from '@client/forms/user/query/queries' import { createOrUpdateUserMutation } from '@client/forms/user/mutation/mutations' import { draftToGqlTransformer } from '@client/transformer' import { deserializeFormSection } from '@client/forms/deserializer/deserializer' @@ -304,13 +304,11 @@ export const userDetails: UserDetails = { ], systemRole: SystemRoleType.FieldAgent, role: { - _id: '778464c0-08f8-4fb7-8a37-b86d1efc462a', - labels: [ - { - lang: 'en', - label: 'ENTREPENEUR' - } - ] + label: { + id: 'userRoles.entrepreneur', + defaultMessage: 'Entrepreneur', + description: 'Entrepreneur' + } }, mobile: '01677701431', primaryOffice: { @@ -1002,13 +1000,11 @@ export function loginAsFieldAgent(store: AppStore) { mobile: '+8801711111111', systemRole: SystemRoleType.FieldAgent, role: { - _id: '778464c0-08f8-4fb7-8a37-b86d1efc462a', - labels: [ - { - lang: 'en', - label: 'CHA' - } - ] + label: { + id: 'userRoles.CHA', + defaultMessage: 'CHA', + description: 'CHA' + } }, status: Status.Active, name: [ @@ -1293,149 +1289,121 @@ export const mockRoles = { export const mockFetchRoleGraphqlOperation = { request: { - query: getSystemRolesQuery, + query: getUserRolesQuery, variables: {} }, result: { data: { - getSystemRoles: [ + getUserRoles: [ { - value: 'FIELD_AGENT', - roles: [ - { - labels: [ - { - lang: 'en', - label: 'Healthcare Worker' - }, - { - lang: 'fr', - label: 'Professionnel de Santé' - } - ] - }, - { - labels: [ - { - lang: 'en', - label: 'Police Officer' - }, - { - lang: 'fr', - label: 'Agent de Police' - } - ] - }, - { - labels: [ - { - lang: 'en', - label: 'Social Worker' - }, - { - lang: 'fr', - label: 'Travailleur Social' - } - ] - }, - { - labels: [ - { - lang: 'en', - label: 'Local Leader' - }, - { - lang: 'fr', - label: 'Leader Local' - } - ] - } - ], - active: true + id: 'FIELD_AGENT', + label: { + defaultMessage: 'Field Agent', + description: 'Name for user role Field Agent', + id: 'userRole.fieldAgent' + }, + systemRole: 'FIELD_AGENT', + scopes: ['declare'] }, { - value: 'REGISTRATION_AGENT', - roles: [ - { - lang: 'en', - label: 'Registration Agent' - }, - { - lang: 'fr', - label: "Agent d'enregistrement" - } - ], - active: true + id: 'POLICE_OFFICER', + label: { + defaultMessage: 'Police Officer', + description: 'Name for user role Police Officer', + id: 'userRole.policeOfficer' + }, + systemRole: 'FIELD_AGENT', + scopes: ['declare'] }, { - value: 'LOCAL_REGISTRAR', - roles: [ - { - lang: 'en', - label: 'Local Registrar' - }, - { - lang: 'fr', - label: 'Registraire local' - } - ], - active: true + id: 'SOCIAL_WORKER', + label: { + defaultMessage: 'Social Worker', + description: 'Name for user role Social Worker', + id: 'userRole.socialWorker' + }, + systemRole: 'FIELD_AGENT', + scopes: ['declare'] }, { - value: 'LOCAL_SYSTEM_ADMIN', - roles: [ - { - lang: 'en', - label: 'Local System_admin' - }, - { - lang: 'fr', - label: 'Administrateur système local' - } - ], - active: true + id: 'HEALTHCARE_WORKER', + label: { + defaultMessage: 'Healthcare Worker', + description: 'Name for user role Healthcare Worker', + id: 'userRole.healthcareWorker' + }, + systemRole: 'FIELD_AGENT', + scopes: ['declare'] }, { - value: 'NATIONAL_SYSTEM_ADMIN', - roles: [ - { - lang: 'en', - label: 'National System_admin' - }, - { - lang: 'fr', - label: 'Administrateur système national' - } - ], - active: true + id: 'LOCAL_LEADER', + label: { + defaultMessage: 'Local Leader', + description: 'Name for user role Local Leader', + id: 'userRole.localLeader' + }, + systemRole: 'FIELD_AGENT', + scopes: ['declare'] }, { - value: 'PERFORMANCE_MANAGEMENT', - roles: [ - { - lang: 'en', - label: 'Performance Management' - }, - { - lang: 'fr', - label: 'Gestion des performances' - } - ], - active: true + id: 'REGISTRATION_AGENT', + label: { + defaultMessage: 'Registration Agent', + description: 'Name for user role Registration Agent', + id: 'userRole.registrationAgent' + }, + systemRole: 'REGISTRATION_AGENT', + scopes: ['validate', 'performance', 'certify'] }, { - value: 'NATIONAL_REGISTRAR', - roles: [ - { - lang: 'en', - label: 'National Registrar' - }, - { - lang: 'fr', - label: 'Registraire national' - } - ], - active: true + id: 'LOCAL_REGISTRAR', + label: { + defaultMessage: 'Local Registrar', + description: 'Name for user role Local Registrar', + id: 'userRole.localRegistrar' + }, + systemRole: 'LOCAL_REGISTRAR', + scopes: ['register', 'performance', 'certify'] + }, + { + id: 'LOCAL_SYSTEM_ADMIN', + label: { + defaultMessage: 'Local System Admin', + description: 'Name for user role Local System Admin', + id: 'userRole.localSystemAdmin' + }, + systemRole: 'LOCAL_SYSTEM_ADMIN', + scopes: ['sysadmin'] + }, + { + id: 'NATIONAL_SYSTEM_ADMIN', + label: { + defaultMessage: 'National System Admin', + description: 'Name for user role National System Admin', + id: 'userRole.nationalSystemAdmin' + }, + systemRole: 'NATIONAL_SYSTEM_ADMIN', + scopes: ['sysadmin', 'natlsysadmin'] + }, + { + id: 'PERFORMANCE_MANAGER', + label: { + defaultMessage: 'Performance Manager', + description: 'Name for user role Performance Manager', + id: 'userRole.performanceManager' + }, + systemRole: 'PERFORMANCE_MANAGEMENT', + scopes: ['performance'] + }, + { + id: 'NATIONAL_REGISTRAR', + label: { + defaultMessage: 'National Registrar', + description: 'Name for user role National Registrar', + id: 'userRole.nationalRegistrar' + }, + systemRole: 'NATIONAL_REGISTRAR', + scopes: ['register', 'performance', 'certify', 'config', 'teams'] } ] } diff --git a/packages/client/src/transformer/index.ts b/packages/client/src/transformer/index.ts index ba92cd15e88..3664352ed29 100644 --- a/packages/client/src/transformer/index.ts +++ b/packages/client/src/transformer/index.ts @@ -434,7 +434,7 @@ export const gqlToDraftTransformer = ( } if (queryData.user?.role) { - transformedData.user.role = queryData.user.role._id + transformedData.user.role = queryData.user.role.id } return transformedData diff --git a/packages/client/src/user/queries.ts b/packages/client/src/user/queries.ts index e91e4c42438..a410350bb09 100644 --- a/packages/client/src/user/queries.ts +++ b/packages/client/src/user/queries.ts @@ -27,7 +27,12 @@ export const SEARCH_USERS = gql` mobile email role { - _id + id + label { + id + defaultMessage + description + } } status underInvestigation @@ -98,10 +103,11 @@ export const GET_USER = gql` } systemRole role { - _id - labels { - lang - label + id + label { + id + defaultMessage + description } } status diff --git a/packages/client/src/user/selectors.ts b/packages/client/src/user/selectors.ts deleted file mode 100644 index 9c190b6c234..00000000000 --- a/packages/client/src/user/selectors.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * OpenCRVS is also distributed under the terms of the Civil Registration - * & Healthcare Disclaimer located at http://opencrvs.org/license. - * - * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. - */ -import { IStoreState } from '@client/store' - -export function selectSystemRoleMap(store: IStoreState) { - return store.userForm.systemRoleMap -} diff --git a/packages/client/src/user/userReducer.ts b/packages/client/src/user/userReducer.ts index 98db63e8aa7..5021876ce09 100644 --- a/packages/client/src/user/userReducer.ts +++ b/packages/client/src/user/userReducer.ts @@ -8,15 +8,19 @@ * * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. */ +import { ApolloClient, ApolloError, ApolloQueryResult } from '@apollo/client' import { IForm, + IFormField, IFormSection, IFormSectionData, - ISelectFormFieldWithOptions, - ISelectOption, - UserSection + UserSection, + modifyFormField } from '@client/forms' -import { AnyFn, deserializeForm } from '@client/forms/deserializer/deserializer' +import { deserializeForm } from '@client/forms/deserializer/deserializer' +import { getCreateUserForm } from '@client/forms/user/fieldDefinitions/createUser' +import { roleQueries } from '@client/forms/user/query/queries' +import { validators } from '@client/forms/validators' import { goToTeamUserList } from '@client/navigation' import { ShowCreateUserDuplicateEmailErrorToast, @@ -29,19 +33,15 @@ import { import * as offlineActions from '@client/offline/actions' import * as profileActions from '@client/profile/profileActions' import { modifyUserDetails } from '@client/profile/profileActions' -import { SEARCH_USERS } from '@client/user/queries' -import { ApolloClient, ApolloError, ApolloQueryResult } from '@apollo/client' -import { Action } from 'redux' -import { ActionCmd, Cmd, Loop, loop, LoopReducer, RunCmd } from 'redux-loop' +import { gqlToDraftTransformer } from '@client/transformer' +import { GET_USER, SEARCH_USERS } from '@client/user/queries' import { IUserAuditForm, userAuditForm } from '@client/user/user-audit' -import { getCreateUserForm } from '@client/forms/user/fieldDefinitions/createUser' import { getToken, getTokenPayload } from '@client/utils/authUtils' -import { roleQueries } from '@client/forms/user/query/queries' -import { Role, SystemRole } from '@client/utils/gateway' +import { UserRole } from '@client/utils/gateway' + import type { GQLQuery } from '@client/utils/gateway-deprecated-do-not-use' -import { gqlToDraftTransformer } from '@client/transformer' -import { getUserRoleIntlKey } from '@client/views/SysAdmin/Team/utils' -import { validators, Validator } from '@client/forms/validators' +import { Action } from 'redux' +import { ActionCmd, Cmd, Loop, LoopReducer, RunCmd, loop } from 'redux-loop' export const ROLES_LOADED = 'USER_FORM/ROLES_LOADED' const MODIFY_USER_FORM_DATA = 'USER_FORM/MODIFY_USER_FORM_DATA' @@ -65,21 +65,15 @@ const initialState: IUserFormState = { userDetailsStored: false, submitting: false, loadingRoles: false, + userRoles: [], submissionError: false, - userAuditForm, - systemRoleMap: {} + userAuditForm } export interface IRoleMessagesLoadedAction { type: typeof ROLE_MESSAGES_LOADED } -export function rolesMessageAddData(): IRoleMessagesLoadedAction { - return { - type: ROLE_MESSAGES_LOADED - } -} - interface IUserFormDataModifyAction { type: typeof MODIFY_USER_FORM_DATA payload: { @@ -174,15 +168,15 @@ function submitFail(errorData: ApolloError): ISubmitFailedAction { export interface IRoleLoadedAction { type: typeof ROLES_LOADED payload: { - systemRoles: SystemRole[] + userRoles: UserRole[] } } -export function rolesLoaded(systemRoles: SystemRole[]): IRoleLoadedAction { +export function rolesLoaded(userRoles: UserRole[]): IRoleLoadedAction { return { type: ROLES_LOADED, payload: { - systemRoles + userRoles } } } @@ -191,21 +185,18 @@ interface IFetchAndStoreUserData { type: typeof FETCH_USER_DATA payload: { client: ApolloClient - query: any variables: { userId: string } } } export function fetchAndStoreUserData( client: ApolloClient, - query: any, variables: { userId: string } ): IFetchAndStoreUserData { return { type: FETCH_USER_DATA, payload: { client, - query, variables } } @@ -245,9 +236,10 @@ type UserFormAction = | ReturnType | ReturnType -export interface ISystemRolesMap { +export interface UserRolesMap { [key: string]: string } + export interface IUserFormState { userForm: IForm userFormData: IFormSectionData @@ -255,62 +247,13 @@ export interface IUserFormState { submitting: boolean loadingRoles: boolean submissionError: boolean + userRoles: UserRole[] userAuditForm: IUserAuditForm - systemRoleMap: ISystemRolesMap } const fetchRoles = async () => { const roles = await roleQueries.fetchRoles() - return roles.data.getSystemRoles -} - -const getRoleWiseSystemRoles = (systemRoles: SystemRole[]) => { - const roleMap: ISystemRolesMap = {} - systemRoles.forEach((systemRole: SystemRole) => { - systemRole.roles.forEach((role: Role) => { - roleMap[role._id] = systemRole.value - }) - }) - - return roleMap -} - -const generateIntlObject = ( - systemRole: SystemRole, - role: Role -): ISelectOption => { - return { - value: role._id, - label: { - id: getUserRoleIntlKey(role._id), - description: '', - defaultMessage: role.labels[0].label - } - } -} - -const optionsGenerator = (systemRoles: SystemRole[]) => { - const typeList: ISelectOption[] = [] - systemRoles.forEach((systemRole: SystemRole) => { - systemRole.roles.forEach((role: Role) => { - typeList.push(generateIntlObject(systemRole, role)) - }) - }) - - return typeList -} - -const generateUserFormWithRoles = ( - form: IForm, - mutateOptions: ISelectOption[] -) => { - const section = form.sections.find((section) => section.id === 'user')! - const group = section.groups.find((group) => group.id === 'user-view-group')! - const field = group.fields.find( - (field) => field.name === 'role' - ) as ISelectFormFieldWithOptions - - field.options = mutateOptions + return roles.data.getUserRoles } export const userFormReducer: LoopReducer = ( @@ -333,14 +276,14 @@ export const userFormReducer: LoopReducer = ( case MODIFY_USER_FORM_DATA: return { ...state, - userFormData: (action as IUserFormDataModifyAction).payload.data + userFormData: action.payload.data } case CLEAR_USER_FORM_DATA: return { ...initialState, userForm: state.userForm, - systemRoleMap: state.systemRoleMap + userRoles: state.userRoles } case SUBMIT_USER_FORM_DATA: @@ -434,23 +377,34 @@ export const userFormReducer: LoopReducer = ( ) case ROLES_LOADED: - const { systemRoles } = action.payload - const getSystemRoleMap = getRoleWiseSystemRoles(systemRoles) + const { userRoles } = action.payload + const form = deserializeForm(getCreateUserForm(), validators) - const mutateOptions = optionsGenerator(systemRoles) - generateUserFormWithRoles(form, mutateOptions) + const modifiedForm = modifyFormField( + form, + 'user', + 'user-view-group', + 'role', + (field: IFormField) => ({ + ...field, + options: userRoles.map((role) => ({ + value: role.id, + label: role.label + })) + }) + ) + console.log({ loaded: userRoles }) + return { ...state, - userForm: { - ...form - }, - systemRoleMap: getSystemRoleMap + userForm: modifiedForm, + userRoles, + loadingRoles: false } case FETCH_USER_DATA: const { client: userClient, - query: getUserQuery, variables: { userId } } = (action as IFetchAndStoreUserData).payload return loop( @@ -458,7 +412,7 @@ export const userFormReducer: LoopReducer = ( Cmd.run( () => userClient.query({ - query: getUserQuery, + query: GET_USER, variables: { userId }, fetchPolicy: 'no-cache' }), @@ -471,21 +425,22 @@ export const userFormReducer: LoopReducer = ( ) case STORE_USER_FORM_DATA: - const { queryData } = (action as IStoreUserFormDataAction).payload + const { queryData } = action.payload const formData = gqlToDraftTransformer( - { sections: (state.userForm as IForm).sections as IFormSection[] }, - { [UserSection.User]: queryData.data.getUser } + { sections: state.userForm.sections }, + { + [UserSection.User]: { + ...queryData.data.getUser + } + } ) + return { ...state, userFormData: formData.user, userDetailsStored: true } - case ROLE_MESSAGES_LOADED: - return { - ...state, - loadingRoles: false - } + default: return state } diff --git a/packages/client/src/utils/authUtils.ts b/packages/client/src/utils/authUtils.ts index f89b9194929..b5751b5b42f 100644 --- a/packages/client/src/utils/authUtils.ts +++ b/packages/client/src/utils/authUtils.ts @@ -16,13 +16,25 @@ import { authApi } from '@client/utils/authApi' import { SystemRoleType } from '@client/utils/gateway' import { UserDetails } from './userUtils' -export type Scope = string[] - +export const USER_SCOPES = [ + 'demo', + 'declare', + 'register', + 'certify', + 'performance', + 'sysadmin', + 'validate', + 'natlsysadmin', + 'bypassratelimit', + 'teams', + 'config' +] +export type Scope = (typeof USER_SCOPES)[number] export interface ITokenPayload { sub: string exp: string algorithm: string - scope: Scope + scope: Scope[] } export const isTokenStillValid = (decoded: ITokenPayload) => { @@ -111,21 +123,21 @@ export const enum AuthScope { NATLSYSADMIN = 'natlsysadmin' } -export const hasNatlSysAdminScope = (scope: Scope | null): boolean => { +export const hasNatlSysAdminScope = (scope: Scope[] | null): boolean => { if (scope?.includes(AuthScope.NATLSYSADMIN)) { return true } return false } -export const hasRegisterScope = (scope: Scope | null): boolean => { +export const hasRegisterScope = (scope: Scope[] | null): boolean => { if (scope?.includes(AuthScope.REGISTER)) { return true } return false } -export const hasRegistrationClerkScope = (scope: Scope | null): boolean => { +export const hasRegistrationClerkScope = (scope: Scope[] | null): boolean => { if (scope?.includes(AuthScope.VALIDATE)) { return true } diff --git a/packages/client/src/utils/gateway-deprecated-do-not-use.d.ts b/packages/client/src/utils/gateway-deprecated-do-not-use.d.ts index 57ca5c31d4b..da6761a7f8a 100644 --- a/packages/client/src/utils/gateway-deprecated-do-not-use.d.ts +++ b/packages/client/src/utils/gateway-deprecated-do-not-use.d.ts @@ -58,7 +58,7 @@ export interface GQLQuery { getUserAuditLog?: GQLUserAuditLogResultSet searchEvents?: GQLEventSearchResultSet getEventsWithProgress?: GQLEventProgressResultSet - getSystemRoles?: Array + getUserRoles?: Array getCertificateSVG?: GQLCertificateSVG getActiveCertificatesSVG?: Array fetchSystem?: GQLSystem @@ -694,13 +694,6 @@ export interface GQLResponse { roleIdMap: GQLMap } -export interface GQLSystemRoleInput { - id: string - value?: string - active?: boolean - roles?: Array -} - export interface GQLCertificateSVGInput { id?: string svgCode: string @@ -973,8 +966,12 @@ export const enum GQLSystemRoleType { } export interface GQLRole { - _id: string - labels: Array + id: string + label: { + id: string + defaultMessage: string + description: string + } } export const enum GQLStatus { @@ -2081,7 +2078,7 @@ export interface GQLQueryTypeResolver { getUserAuditLog?: QueryToGetUserAuditLogResolver searchEvents?: QueryToSearchEventsResolver getEventsWithProgress?: QueryToGetEventsWithProgressResolver - getSystemRoles?: QueryToGetSystemRolesResolver + getUserRoles?: QueryTogetUserRolesResolver getCertificateSVG?: QueryToGetCertificateSVGResolver getActiveCertificatesSVG?: QueryToGetActiveCertificatesSVGResolver fetchSystem?: QueryToFetchSystemResolver @@ -2680,7 +2677,7 @@ export interface QueryToGetEventsWithProgressResolver< ): TResult } -export interface QueryToGetSystemRolesArgs { +export interface QueryTogetUserRolesArgs { title?: string value?: GQLComparisonInput role?: string @@ -2688,10 +2685,10 @@ export interface QueryToGetSystemRolesArgs { sortBy?: string sortOrder?: string } -export interface QueryToGetSystemRolesResolver { +export interface QueryTogetUserRolesResolver { ( parent: TParent, - args: QueryToGetSystemRolesArgs, + args: QueryTogetUserRolesArgs, context: any, info: GraphQLResolveInfo ): TResult diff --git a/packages/client/src/utils/gateway.ts b/packages/client/src/utils/gateway.ts index c8d1dc3acfd..1d6c7f60956 100644 --- a/packages/client/src/utils/gateway.ts +++ b/packages/client/src/utils/gateway.ts @@ -506,17 +506,6 @@ export type CommentInput = { user?: InputMaybe } -export type ComparisonInput = { - eq?: InputMaybe - gt?: InputMaybe - gte?: InputMaybe - in?: InputMaybe> - lt?: InputMaybe - lte?: InputMaybe - ne?: InputMaybe - nin?: InputMaybe> -} - export type ContactPoint = { __typename?: 'ContactPoint' system?: Maybe @@ -892,6 +881,13 @@ export type HumanNameInput = { use?: InputMaybe } +export type I18nMessage = { + __typename?: 'I18nMessage' + defaultMessage: Scalars['String'] + description: Scalars['String'] + id: Scalars['String'] +} + export type Identifier = { __typename?: 'Identifier' system?: Maybe @@ -937,11 +933,6 @@ export enum IntegratingSystemType { Other = 'OTHER' } -export type LabelInput = { - label: Scalars['String'] - lang: Scalars['String'] -} - export type LocalRegistrar = { __typename?: 'LocalRegistrar' name: Array> @@ -1172,7 +1163,6 @@ export type Mutation = { updateBirthRegistration: Scalars['ID'] updateDeathRegistration: Scalars['ID'] updatePermissions?: Maybe - updateRole: Response usernameReminder?: Maybe voidNotification?: Maybe } @@ -1439,10 +1429,6 @@ export type MutationUpdatePermissionsArgs = { setting: UpdatePermissionsInput } -export type MutationUpdateRoleArgs = { - systemRole?: InputMaybe -} - export type MutationUsernameReminderArgs = { userId: Scalars['String'] } @@ -1651,7 +1637,6 @@ export type Query = { getLocationStatistics?: Maybe getOIDPUserInfo?: Maybe getRegistrationsListByFilter?: Maybe - getSystemRoles?: Maybe> getTotalCertifications?: Maybe> getTotalCorrections?: Maybe> getTotalMetrics?: Maybe @@ -1660,6 +1645,7 @@ export type Query = { getUserAuditLog?: Maybe getUserByEmail?: Maybe getUserByMobile?: Maybe + getUserRoles: Array getVSExports?: Maybe hasChildLocation?: Maybe informantSMSNotifications?: Maybe> @@ -1771,15 +1757,6 @@ export type QueryGetRegistrationsListByFilterArgs = { timeStart: Scalars['String'] } -export type QueryGetSystemRolesArgs = { - active?: InputMaybe - role?: InputMaybe - sortBy?: InputMaybe - sortOrder?: InputMaybe - title?: InputMaybe - value?: InputMaybe -} - export type QueryGetTotalCertificationsArgs = { locationId?: InputMaybe timeEnd: Scalars['String'] @@ -2150,28 +2127,6 @@ export type RemoveBookmarkedSeachInput = { userId: Scalars['String'] } -export type Response = { - __typename?: 'Response' - roleIdMap: Scalars['Map'] -} - -export type Role = { - __typename?: 'Role' - _id: Scalars['ID'] - labels: Array -} - -export type RoleInput = { - _id?: InputMaybe - labels: Array -} - -export type RoleLabel = { - __typename?: 'RoleLabel' - label: Scalars['String'] - lang: Scalars['String'] -} - export type SmsNotification = { __typename?: 'SMSNotification' createdAt: Scalars['String'] @@ -2195,7 +2150,7 @@ export type SearchFieldAgentResponse = { fullName?: Maybe practitionerId?: Maybe primaryOfficeId?: Maybe - role?: Maybe + role?: Maybe status?: Maybe totalNumberOfDeclarationStarted?: Maybe totalNumberOfInProgressAppStarted?: Maybe @@ -2272,21 +2227,6 @@ export type SystemInput = { type: SystemType } -export type SystemRole = { - __typename?: 'SystemRole' - active: Scalars['Boolean'] - id: Scalars['ID'] - roles: Array - value: SystemRoleType -} - -export type SystemRoleInput = { - active?: InputMaybe - id: Scalars['ID'] - roles?: InputMaybe> - value?: InputMaybe -} - export enum SystemRoleType { FieldAgent = 'FIELD_AGENT', LocalRegistrar = 'LOCAL_REGISTRAR', @@ -2394,7 +2334,7 @@ export type User = { name: Array practitionerId: Scalars['String'] primaryOffice?: Maybe - role: Role + role: UserRole searches?: Maybe> signature?: Maybe status: Status @@ -2463,6 +2403,14 @@ export type UserInput = { username?: InputMaybe } +export type UserRole = { + __typename?: 'UserRole' + id: Scalars['ID'] + label: I18nMessage + scopes: Array + systemRole: SystemRoleType +} + export type VsExport = { __typename?: 'VSExport' createdOn: Scalars['Date'] @@ -2655,31 +2603,21 @@ export type CreateOrUpdateUserMutation = { createOrUpdateUser: { __typename?: 'User'; username?: string | null } } -export type GetSystemRolesQueryVariables = Exact<{ - value?: InputMaybe -}> +export type GetUserRolesQueryVariables = Exact<{ [key: string]: never }> -export type GetSystemRolesQuery = { +export type GetUserRolesQuery = { __typename?: 'Query' - getSystemRoles?: Array<{ - __typename?: 'SystemRole' + getUserRoles: Array<{ + __typename?: 'UserRole' id: string - value: SystemRoleType - roles: Array<{ - __typename?: 'Role' - _id: string - labels: Array<{ __typename?: 'RoleLabel'; lang: string; label: string }> - }> - }> | null -} - -export type UpdateRoleMutationVariables = Exact<{ - systemRole?: InputMaybe -}> - -export type UpdateRoleMutation = { - __typename?: 'Mutation' - updateRole: { __typename?: 'Response'; roleIdMap: any } + systemRole: SystemRoleType + label: { + __typename?: 'I18nMessage' + id: string + defaultMessage: string + description: string + } + }> } export type AdvancedSeachParametersFragment = { @@ -2904,9 +2842,13 @@ export type FetchUserQuery = { systemRole: SystemRoleType status: Status role: { - __typename?: 'Role' - _id: string - labels: Array<{ __typename?: 'RoleLabel'; lang: string; label: string }> + __typename?: 'UserRole' + label: { + __typename?: 'I18nMessage' + id: string + defaultMessage: string + description: string + } } name: Array<{ __typename?: 'HumanName' @@ -3194,7 +3136,16 @@ export type SearchUsersQuery = { firstNames?: string | null familyName?: string | null }> - role: { __typename?: 'Role'; _id: string } + role: { + __typename?: 'UserRole' + id: string + label: { + __typename?: 'I18nMessage' + id: string + defaultMessage: string + description: string + } + } avatar?: { __typename?: 'Avatar'; type: string; data: string } | null } | null> | null } | null @@ -3269,9 +3220,13 @@ export type GetUserQuery = { value?: string | null } | null role: { - __typename?: 'Role' - _id: string - labels: Array<{ __typename?: 'RoleLabel'; lang: string; label: string }> + __typename?: 'UserRole' + label: { + __typename?: 'I18nMessage' + id: string + defaultMessage: string + description: string + } } primaryOffice?: { __typename?: 'Location' @@ -3696,13 +3651,14 @@ export type FetchBirthRegistrationForReviewQuery = { id: string systemRole: SystemRoleType role: { - __typename?: 'Role' - _id: string - labels: Array<{ - __typename?: 'RoleLabel' - lang: string - label: string - }> + __typename?: 'UserRole' + id: string + label: { + __typename?: 'I18nMessage' + id: string + defaultMessage: string + description: string + } } name: Array<{ __typename?: 'HumanName' @@ -4016,13 +3972,14 @@ export type FetchBirthRegistrationForCertificateQuery = { id: string systemRole: SystemRoleType role: { - __typename?: 'Role' - _id: string - labels: Array<{ - __typename?: 'RoleLabel' - lang: string - label: string - }> + __typename?: 'UserRole' + id: string + label: { + __typename?: 'I18nMessage' + id: string + defaultMessage: string + description: string + } } name: Array<{ __typename?: 'HumanName' @@ -4499,13 +4456,14 @@ export type FetchDeathRegistrationForReviewQuery = { id: string systemRole: SystemRoleType role: { - __typename?: 'Role' - _id: string - labels: Array<{ - __typename?: 'RoleLabel' - lang: string - label: string - }> + __typename?: 'UserRole' + id: string + label: { + __typename?: 'I18nMessage' + id: string + defaultMessage: string + description: string + } } name: Array<{ __typename?: 'HumanName' @@ -4808,13 +4766,14 @@ export type FetchDeathRegistrationForCertificationQuery = { id: string systemRole: SystemRoleType role: { - __typename?: 'Role' - _id: string - labels: Array<{ - __typename?: 'RoleLabel' - lang: string - label: string - }> + __typename?: 'UserRole' + id: string + label: { + __typename?: 'I18nMessage' + id: string + defaultMessage: string + description: string + } } name: Array<{ __typename?: 'HumanName' @@ -5172,7 +5131,6 @@ export type FetchMarriageRegistrationForReviewQuery = { __typename?: 'History' otherReason?: string | null requester?: string | null - requesterOther?: string | null hasShowedVerifiedDocument?: boolean | null noSupportingDocumentationRequired?: boolean | null date?: any | null @@ -5221,13 +5179,14 @@ export type FetchMarriageRegistrationForReviewQuery = { id: string systemRole: SystemRoleType role: { - __typename?: 'Role' - _id: string - labels: Array<{ - __typename?: 'RoleLabel' - lang: string - label: string - }> + __typename?: 'UserRole' + id: string + label: { + __typename?: 'I18nMessage' + id: string + defaultMessage: string + description: string + } } name: Array<{ __typename?: 'HumanName' @@ -5532,7 +5491,6 @@ export type FetchMarriageRegistrationForCertificateQuery = { __typename?: 'History' otherReason?: string | null requester?: string | null - requesterOther?: string | null hasShowedVerifiedDocument?: boolean | null date?: any | null action?: RegAction | null @@ -5564,13 +5522,14 @@ export type FetchMarriageRegistrationForCertificateQuery = { id: string systemRole: SystemRoleType role: { - __typename?: 'Role' - _id: string - labels: Array<{ - __typename?: 'RoleLabel' - lang: string - label: string - }> + __typename?: 'UserRole' + id: string + label: { + __typename?: 'I18nMessage' + id: string + defaultMessage: string + description: string + } } name: Array<{ __typename?: 'HumanName' @@ -7372,7 +7331,7 @@ export type GetUserByMobileQuery = { email?: string | null systemRole: SystemRoleType status: Status - role: { __typename?: 'Role'; _id: string } + role: { __typename?: 'UserRole'; id: string } } | null } @@ -7390,7 +7349,7 @@ export type GetUserByEmailQuery = { email?: string | null systemRole: SystemRoleType status: Status - role: { __typename?: 'Role'; _id: string } + role: { __typename?: 'UserRole'; id: string } } | null } @@ -7835,13 +7794,14 @@ export type GetEventsWithProgressQuery = { familyName?: string | null }> role: { - __typename?: 'Role' - _id: string - labels: Array<{ - __typename?: 'RoleLabel' - lang: string - label: string - }> + __typename?: 'UserRole' + id: string + label: { + __typename?: 'I18nMessage' + id: string + defaultMessage: string + description: string + } } } | null progressReport?: { @@ -7896,13 +7856,14 @@ export type GetRegistrationsListByFilterQuery = { id: string systemRole: SystemRoleType role: { - __typename?: 'Role' - _id: string - labels: Array<{ - __typename?: 'RoleLabel' - lang: string - label: string - }> + __typename?: 'UserRole' + id: string + label: { + __typename?: 'I18nMessage' + id: string + defaultMessage: string + description: string + } } primaryOffice?: { __typename?: 'Location' @@ -7970,8 +7931,13 @@ export type SearchFieldAgentsQuery = { totalNumberOfRejectedDeclarations?: number | null averageTimeForDeclaredDeclarations?: number | null role?: { - __typename?: 'Role' - labels: Array<{ __typename?: 'RoleLabel'; label: string; lang: string }> + __typename?: 'UserRole' + label: { + __typename?: 'I18nMessage' + id: string + defaultMessage: string + description: string + } } | null avatar?: { __typename?: 'Avatar'; type: string; data: string } | null } | null> | null @@ -8086,6 +8052,7 @@ export type FetchRecordDetailsForVerificationQuery = { __typename?: 'History' action?: RegAction | null regStatus?: RegStatus | null + date?: any | null user?: { __typename?: 'User' primaryOffice?: { @@ -8407,7 +8374,7 @@ export type FetchViewRecordByCompositionQuery = { __typename?: 'User' id: string systemRole: SystemRoleType - role: { __typename?: 'Role'; _id: string } + role: { __typename?: 'UserRole'; id: string } name: Array<{ __typename?: 'HumanName' firstNames?: string | null @@ -8793,7 +8760,7 @@ export type FetchViewRecordByCompositionQuery = { __typename?: 'User' id: string systemRole: SystemRoleType - role: { __typename?: 'Role'; _id: string } + role: { __typename?: 'UserRole'; id: string } name: Array<{ __typename?: 'HumanName' firstNames?: string | null @@ -9111,7 +9078,7 @@ export type FetchViewRecordByCompositionQuery = { __typename?: 'User' id: string systemRole: SystemRoleType - role: { __typename?: 'Role'; _id: string } + role: { __typename?: 'UserRole'; id: string } name: Array<{ __typename?: 'HumanName' firstNames?: string | null diff --git a/packages/client/src/utils/userUtils.ts b/packages/client/src/utils/userUtils.ts index 1054db87214..a318e0aa5ed 100644 --- a/packages/client/src/utils/userUtils.ts +++ b/packages/client/src/utils/userUtils.ts @@ -14,6 +14,7 @@ import { createNamesMap } from './data-formatting' import { LANG_EN } from './constants' import { useSelector } from 'react-redux' import { IStoreState } from '@client/store' +import { Scope } from './authUtils' export const USER_DETAILS = 'USER_DETAILS' @@ -60,3 +61,19 @@ export function useUserName() { return getUserName(userDetails) }) } + +export function useUserScopes() { + return useSelector((state) => { + const { tokenPayload } = state.profile + return tokenPayload ? tokenPayload.scope : [] + }) +} + +export function userHasScope(userScopes: Scope[], allowedScopes: Scope[]) { + return allowedScopes.every((scope) => userScopes.includes(scope)) +} + +export function useUserHasScope(allowedScopes: Scope[]) { + const userScopes = useUserScopes() + return userHasScope(userScopes, allowedScopes) +} diff --git a/packages/client/src/views/AdvancedSearch/AdvancedSearchResult.tsx b/packages/client/src/views/AdvancedSearch/AdvancedSearchResult.tsx index 94d5b09866d..5304eeedc18 100644 --- a/packages/client/src/views/AdvancedSearch/AdvancedSearchResult.tsx +++ b/packages/client/src/views/AdvancedSearch/AdvancedSearchResult.tsx @@ -106,7 +106,7 @@ type QueryData = SearchEventsQuery['searchEvents'] interface IBaseSearchResultProps { theme: ITheme language: string - scope: Scope | null + scope: Scope[] | null goToEvents: typeof goToEventsAction userDetails: UserDetails | null outboxDeclarations: IDeclaration[] diff --git a/packages/client/src/views/DataProvider/birth/queries.ts b/packages/client/src/views/DataProvider/birth/queries.ts index b316cbdcf39..a51390108e3 100644 --- a/packages/client/src/views/DataProvider/birth/queries.ts +++ b/packages/client/src/views/DataProvider/birth/queries.ts @@ -251,10 +251,11 @@ export const GET_BIRTH_REGISTRATION_FOR_REVIEW = gql` user { id role { - _id - labels { - lang - label + id + label { + id + defaultMessage + description } } systemRole @@ -528,10 +529,11 @@ export const GET_BIRTH_REGISTRATION_FOR_CERTIFICATE = gql` user { id role { - _id - labels { - lang - label + id + label { + id + defaultMessage + description } } systemRole diff --git a/packages/client/src/views/DataProvider/death/queries.ts b/packages/client/src/views/DataProvider/death/queries.ts index 0315d6b98bd..d8deae8102d 100644 --- a/packages/client/src/views/DataProvider/death/queries.ts +++ b/packages/client/src/views/DataProvider/death/queries.ts @@ -319,10 +319,11 @@ export const GET_DEATH_REGISTRATION_FOR_REVIEW = gql` user { id role { - _id - labels { - lang - label + id + label { + id + defaultMessage + description } } systemRole @@ -585,10 +586,11 @@ export const GET_DEATH_REGISTRATION_FOR_CERTIFICATION = gql` user { id role { - _id - labels { - lang - label + id + label { + id + defaultMessage + description } } systemRole diff --git a/packages/client/src/views/DataProvider/marriage/queries.ts b/packages/client/src/views/DataProvider/marriage/queries.ts index 92489c807d4..6f0d3fa59c0 100644 --- a/packages/client/src/views/DataProvider/marriage/queries.ts +++ b/packages/client/src/views/DataProvider/marriage/queries.ts @@ -257,10 +257,11 @@ const GET_MARRIAGE_REGISTRATION_FOR_REVIEW = gql` user { id role { - _id - labels { - lang - label + id + label { + id + defaultMessage + description } } systemRole @@ -553,10 +554,11 @@ const GET_MARRIAGE_REGISTRATION_FOR_CERTIFICATE = gql` user { id role { - _id - labels { - lang - label + id + label { + id + defaultMessage + description } } systemRole diff --git a/packages/client/src/views/OfficeHome/readyForReview/ReadyForReview.tsx b/packages/client/src/views/OfficeHome/readyForReview/ReadyForReview.tsx index 0ff669aeeff..49e2be80845 100644 --- a/packages/client/src/views/OfficeHome/readyForReview/ReadyForReview.tsx +++ b/packages/client/src/views/OfficeHome/readyForReview/ReadyForReview.tsx @@ -69,7 +69,7 @@ const ToolTipContainer = styled.span` ` interface IBaseReviewTabProps { theme: ITheme - scope: Scope | null + scope: Scope[] | null goToPage: typeof goToPage goToDeclarationRecordAudit: typeof goToDeclarationRecordAudit outboxDeclarations: IDeclaration[] diff --git a/packages/client/src/views/OfficeHome/requiresUpdate/RequiresUpdate.tsx b/packages/client/src/views/OfficeHome/requiresUpdate/RequiresUpdate.tsx index 75f31b2a5e7..a433ee85a26 100644 --- a/packages/client/src/views/OfficeHome/requiresUpdate/RequiresUpdate.tsx +++ b/packages/client/src/views/OfficeHome/requiresUpdate/RequiresUpdate.tsx @@ -57,7 +57,7 @@ import { RegStatus } from '@client/utils/gateway' interface IBaseRejectTabProps { theme: ITheme - scope: Scope | null + scope: Scope[] | null goToPage: typeof goToPage goToDeclarationRecordAudit: typeof goToDeclarationRecordAudit outboxDeclarations: IDeclaration[] diff --git a/packages/client/src/views/OfficeHome/sentForReview/SentForReview.tsx b/packages/client/src/views/OfficeHome/sentForReview/SentForReview.tsx index 8ce2c205d85..6ac136f56c4 100644 --- a/packages/client/src/views/OfficeHome/sentForReview/SentForReview.tsx +++ b/packages/client/src/views/OfficeHome/sentForReview/SentForReview.tsx @@ -63,7 +63,7 @@ interface IBaseApprovalTabProps { goToPage: typeof goToPage goToDeclarationRecordAudit: typeof goToDeclarationRecordAudit outboxDeclarations: IDeclaration[] - scope: Scope | null + scope: Scope[] | null queryData: { data: GQLEventSearchResultSet } diff --git a/packages/client/src/views/Performance/FieldAgentList.tsx b/packages/client/src/views/Performance/FieldAgentList.tsx index a8def4a5ba5..008a2b9189f 100644 --- a/packages/client/src/views/Performance/FieldAgentList.tsx +++ b/packages/client/src/views/Performance/FieldAgentList.tsx @@ -40,7 +40,6 @@ import format from '@client/utils/date-formatting' import { Content, ContentSize } from '@opencrvs/components/lib/Content' import { Avatar, Event } from '@client/utils/gateway' import { Pagination } from '@opencrvs/components/lib/Pagination' -import { getUserRole } from '@client/views/SysAdmin/Config/UserRoles/utils' import { getLanguage } from '@client/i18n/selectors' const ToolTipContainer = styled.span` @@ -343,7 +342,7 @@ function FieldAgentListComponent(props: IProps) { return { name: getNameWithAvatar(row.fullName || '', row.avatar), rawName: row.fullName || '', - role: (row.role && getUserRole(language, row.role)) || '', + role: (row.role && intl.formatMessage(row.role.label)) || '', officeName: (office && office.displayLabel) || '', startMonth: row.creationDate, totalDeclarations: String(row.totalNumberOfDeclarationStarted), diff --git a/packages/client/src/views/RecordAudit/History.tsx b/packages/client/src/views/RecordAudit/History.tsx index 27dc38a80c9..40c03891b4d 100644 --- a/packages/client/src/views/RecordAudit/History.tsx +++ b/packages/client/src/views/RecordAudit/History.tsx @@ -35,7 +35,7 @@ import { v4 as uuid } from 'uuid' import { History, Avatar, RegStatus, SystemType } from '@client/utils/gateway' import { Link } from '@opencrvs/components' import { integrationMessages } from '@client/i18n/messages/views/integrations' -import { getUserRole } from '@client/views/SysAdmin/Config/UserRoles/utils' + import { getLanguage } from '@client/i18n/selectors' import { useSelector } from 'react-redux' import { formatLongDate } from '@client/utils/date-formatting' @@ -292,7 +292,7 @@ export const GetHistory = ({ ) : isSystemInitiated(item) || !item.user?.systemRole ? ( intl.formatMessage(getSystemType(item.system?.type || '')) ) : ( - getUserRole(currentLanguage, item.user?.role) + item.user && intl.formatMessage(item.user.role.label) ), location: diff --git a/packages/client/src/views/RecordAudit/RecordAudit.tsx b/packages/client/src/views/RecordAudit/RecordAudit.tsx index 46dbbb89f20..0e134166497 100644 --- a/packages/client/src/views/RecordAudit/RecordAudit.tsx +++ b/packages/client/src/views/RecordAudit/RecordAudit.tsx @@ -170,7 +170,7 @@ interface IStateProps { userDetails: UserDetails | null language: string resources: IOfflineData - scope: Scope | null + scope: Scope[] | null declarationId: string draft: IDeclaration | null tab: IRecordAuditTabs @@ -307,7 +307,7 @@ function RecordAuditBody({ draft: IDeclaration | null duplicates?: string[] intl: IntlShape - scope: Scope | null + scope: Scope[] | null userDetails: UserDetails | null registerForm: IRegisterFormState offlineData: Partial diff --git a/packages/client/src/views/RegisterForm/RegisterForm.init.test.tsx b/packages/client/src/views/RegisterForm/RegisterForm.init.test.tsx index 4fb88d0ac09..80db8011c18 100644 --- a/packages/client/src/views/RegisterForm/RegisterForm.init.test.tsx +++ b/packages/client/src/views/RegisterForm/RegisterForm.init.test.tsx @@ -62,13 +62,11 @@ describe('when user logs in', () => { mobile: '+260921111111', systemRole: SystemRoleType.NationalSystemAdmin, role: { - _id: '778464c0-08f8-4fb7-8a37-b86d1efc462a', - labels: [ - { - lang: 'en', - label: 'National System Admin' - } - ] + label: { + id: 'userRoles.nationalSystemAdmin', + defaultMessage: 'National System Admin', + description: 'National System Admin' + } }, status: 'active' as Status, localRegistrar: { name: [], role: 'FIELD_AGENT' as SystemRoleType } diff --git a/packages/client/src/views/RegisterForm/RegisterForm.tsx b/packages/client/src/views/RegisterForm/RegisterForm.tsx index 7a620cf8966..9844e7fa9a3 100644 --- a/packages/client/src/views/RegisterForm/RegisterForm.tsx +++ b/packages/client/src/views/RegisterForm/RegisterForm.tsx @@ -177,7 +177,7 @@ type Props = { fieldsToShowValidationErrors?: IFormField[] isWritingDraft: boolean userDetails: UserDetails | null - scope: Scope | null + scope: Scope[] | null } export type FullProps = IFormProps & diff --git a/packages/client/src/views/RegisterForm/ReviewForm.tsx b/packages/client/src/views/RegisterForm/ReviewForm.tsx index 7b85e031c52..744cdf40fb0 100644 --- a/packages/client/src/views/RegisterForm/ReviewForm.tsx +++ b/packages/client/src/views/RegisterForm/ReviewForm.tsx @@ -36,7 +36,7 @@ import { WORKQUEUE_TABS } from '@client/components/interface/Navigation' interface IReviewProps { theme: ITheme - scope: Scope | null + scope: Scope[] | null event: Event } interface IDeclarationProp { diff --git a/packages/client/src/views/RegisterForm/review/ReviewSection.tsx b/packages/client/src/views/RegisterForm/review/ReviewSection.tsx index cb88f69ddf2..8c9098d5a62 100644 --- a/packages/client/src/views/RegisterForm/review/ReviewSection.tsx +++ b/packages/client/src/views/RegisterForm/review/ReviewSection.tsx @@ -282,7 +282,7 @@ interface IProps { submissionStatus: string, action: SubmissionAction ) => void - scope: Scope | null + scope: Scope[] | null offlineCountryConfiguration: IOfflineData language: string onChangeReviewForm?: onChangeReviewForm diff --git a/packages/client/src/views/SearchResult/SearchResult.tsx b/packages/client/src/views/SearchResult/SearchResult.tsx index 9cc6eedac09..ada9eb73742 100644 --- a/packages/client/src/views/SearchResult/SearchResult.tsx +++ b/packages/client/src/views/SearchResult/SearchResult.tsx @@ -124,7 +124,7 @@ export type ISearchInputProps = ISerachInputCustomProps & interface IBaseSearchResultProps { theme: ITheme language: string - scope: Scope | null + scope: Scope[] | null goToEvents: typeof goToEventsAction userDetails: UserDetails | null outboxDeclarations: IDeclaration[] diff --git a/packages/client/src/views/Settings/items/Role.tsx b/packages/client/src/views/Settings/items/Role.tsx index d4a2c63d465..6869a332269 100644 --- a/packages/client/src/views/Settings/items/Role.tsx +++ b/packages/client/src/views/Settings/items/Role.tsx @@ -19,16 +19,14 @@ import { } from '@client/views/Settings/items/components' import { useSelector } from 'react-redux' import { IStoreState } from '@client/store' -import { getLanguage } from '@client/i18n/selectors' -import { getUserRole } from '@client/views/SysAdmin/Config/UserRoles/utils' + import { getUserDetails } from '@client/profile/profileSelectors' export function Role() { const intl = useIntl() - const language = useSelector(getLanguage) const systemRole = useSelector((state) => { const userDetails = getUserDetails(state) - return (userDetails && getUserRole(language, userDetails.role)) ?? '' + return (userDetails && intl.formatMessage(userDetails.role.label)) || '' }) return ( theme.fonts.reg16}; @@ -104,8 +105,6 @@ const StyledHeader = styled(ListViewItemSimplified)` color: ${({ theme }) => theme.colors.grey400}; ` -export type Scope = string[] - export enum SVGFile { type = 'image/svg+xml' } @@ -113,7 +112,7 @@ export enum SVGFile { type Props = WrappedComponentProps & { intl: IntlShape userDetails: UserDetails | null - scope: Scope | null + scope: Scope[] | null offlineResources: IOfflineData registerForm: { birth: IForm diff --git a/packages/client/src/views/SysAdmin/Config/Certificates/previewDummyData.ts b/packages/client/src/views/SysAdmin/Config/Certificates/previewDummyData.ts index bc0e7d25729..fc8ad7d6531 100644 --- a/packages/client/src/views/SysAdmin/Config/Certificates/previewDummyData.ts +++ b/packages/client/src/views/SysAdmin/Config/Certificates/previewDummyData.ts @@ -1987,13 +1987,11 @@ const mockUserDetails: UserDetails = { mobile: '+260921111111', systemRole: SystemRoleType.NationalSystemAdmin, role: { - _id: '778464c0-08f8-4fb7-8a37-b86d1efc462a', - labels: [ - { - lang: 'en', - label: 'NATIONAL_SYSTEM_ADMIN' - } - ] + label: { + id: 'userRoles.nationalSystemAdmin', + defaultMessage: 'National System Admin', + description: 'National System Admin' + } }, status: Status.Active, primaryOffice: { diff --git a/packages/client/src/views/SysAdmin/Config/UserRoles/UserRoles.test.tsx b/packages/client/src/views/SysAdmin/Config/UserRoles/UserRoles.test.tsx deleted file mode 100644 index 4087d39cede..00000000000 --- a/packages/client/src/views/SysAdmin/Config/UserRoles/UserRoles.test.tsx +++ /dev/null @@ -1,157 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * OpenCRVS is also distributed under the terms of the Civil Registration - * & Healthcare Disclaimer located at http://opencrvs.org/license. - * - * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. - */ - -import { describe, Mock } from 'vitest' -import { ReactWrapper } from 'enzyme' -import { createStore } from '@client/store' -import { createTestComponent } from '@client/tests/util' -import { useParams } from 'react-router' -import React from 'react' -import UserRoles from '@client/views/SysAdmin/Config/UserRoles/UserRoles' -import { waitForElement } from '@client/tests/wait-for-element' -import { - getSystemRolesQuery, - updateRoleQuery -} from '@client/forms/user/query/queries' - -describe('render system role update modal', () => { - let component: ReactWrapper<{}, {}> - - beforeEach(async () => { - const { store, history } = createStore() - - component = await createTestComponent(, { - store, - history - }) - ;(useParams as Mock).mockImplementation(() => ({})) - }) - - it('Render system integrations properly ', async () => { - expect(component.exists('UserRoles')).toBeTruthy() - }) -}) - -describe('render update system role', () => { - let component: ReactWrapper<{}, {}> - - const mocks = [ - { - request: { - query: getSystemRolesQuery - }, - result: { - data: { - getSystemRoles: [ - { - id: '63b3f284452f2e40afa4409d', - value: 'FIELD_AGENT', - roles: [ - { - _id: '63da2e0e079116030b195d99', - labels: [ - { - lang: 'en', - label: 'Health Worker', - __typename: 'RoleLabel' - }, - { - lang: 'fr', - label: 'Professionnel de Santé', - __typename: 'RoleLabel' - } - ], - __typename: 'Role' - } - ], - __typename: 'SystemRole' - } - ] - } - } - }, - { - request: { - query: updateRoleQuery, - variables: { - systemRole: { - id: '63b3f284452f2e40afa4409d', - roles: [ - { - _id: '63da2e0e079116030b195d99', - labels: [ - { - lang: 'en', - label: 'UP Chairman' - }, - { - lang: 'fr', - label: 'Professionnel de Santé' - } - ] - } - ] - } - } - }, - result: { - data: { - updateRole: { - msg: 'System role updated' - } - } - } - } - ] - - beforeEach(async () => { - const { store, history } = createStore() - - component = await createTestComponent(, { - store, - history, - graphqlMocks: mocks as any - }) - - await new Promise((resolve) => { - setTimeout(resolve, 0) - }) - component.update() - }) - - it('should show the update system model after click the change button', async () => { - component.find('#changeButton').hostNodes().first().simulate('click') - expect(component.exists('ResponsiveModal')).toBeTruthy() - }) - - it.skip('should show the update system role success message after click the confirm button', async () => { - component.find('#changeButton').hostNodes().last().simulate('click') - component.update() - component.find('#editButton').first().simulate('click') - - component - .find('#roleNameInput') - .hostNodes() - .simulate('change', { - target: { - name: 'roleNameInput', - value: 'UP Chairman' - } - }) - component.update() - component.find('#confirm').hostNodes().first().simulate('click') - component.update() - await new Promise((resolve) => { - setTimeout(resolve, 0) - }) - await waitForElement(component, '#updateRoleSuccess') - }) -}) diff --git a/packages/client/src/views/SysAdmin/Config/UserRoles/UserRoles.tsx b/packages/client/src/views/SysAdmin/Config/UserRoles/UserRoles.tsx deleted file mode 100644 index 774a3bbf76e..00000000000 --- a/packages/client/src/views/SysAdmin/Config/UserRoles/UserRoles.tsx +++ /dev/null @@ -1,212 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * OpenCRVS is also distributed under the terms of the Civil Registration - * & Healthcare Disclaimer located at http://opencrvs.org/license. - * - * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. - */ -import React from 'react' -import { useIntl } from 'react-intl' -import { Content } from '@opencrvs/components/lib/Content' -import { messages } from '@client/i18n/messages/views/config' -import { - ListViewItemSimplified, - ListViewSimplified -} from '@opencrvs/components/lib/ListViewSimplified' -import { LoadingIndicator } from '@client/views/OfficeHome/LoadingIndicator' -import { GenericErrorToast } from '@client/components/GenericErrorToast' -import { - GetSystemRolesQuery, - Role, - SystemRoleInput, - UpdateRoleMutation, - UpdateRoleMutationVariables -} from '@client/utils/gateway' -import { Frame } from '@opencrvs/components/lib/Frame' -import { Navigation } from '@client/components/interface/Navigation' -import { buttonMessages, constantsMessages } from '@client/i18n/messages' -import { useMutation, useQuery } from '@apollo/client' -import { AppBar } from '@opencrvs/components/lib/AppBar' -import { HistoryNavigator } from '@client/components/Header/HistoryNavigator' -import { Link } from '@opencrvs/components/lib/Link' -import { Text } from '@opencrvs/components/lib/Text' -import { - getSystemRolesQuery, - updateRoleQuery -} from '@client/forms/user/query/queries' -import { - Label, - Value -} from '@client/views/SysAdmin/Config/Application/Components' -import { getUserSystemRole } from '@client/views/SysAdmin//Team/utils' -import { getLanguage } from '@client/i18n/selectors' -import { useDispatch, useSelector } from 'react-redux' -import { getUserRole } from './utils' -import { Stack, Toast } from '@opencrvs/components' -import { ProfileMenu } from '@client/components/ProfileMenu' -import { useModal } from '@client/hooks/useModal' -import { UserRoleManagementModal } from '@client/views/UserRoles/UserRoleManagementModal' -import { offlineDataReady } from '@client/offline/actions' -import { getOfflineData } from '@client/offline/selectors' - -export type RolesInput = (Omit & { _id?: string })[] - -export type ISystemRole = NonNullable< - GetSystemRolesQuery['getSystemRoles'] ->[number] - -const UserRoles = () => { - const intl = useIntl() - const [userRoleMgntModalNode, openUserRoleManage] = useModal() - const language = useSelector(getLanguage) - const dispatch = useDispatch() - const offlineData = useSelector(getOfflineData) - const { loading, error, data, refetch } = useQuery( - getSystemRolesQuery, - { - fetchPolicy: 'no-cache' - } - ) - - const [ - updateRoleMutate, - { data: roleUpdateSuccess, error: updateRoleError, reset } - ] = useMutation( - updateRoleQuery, - { - onCompleted: ({ updateRole }) => { - if (updateRole) { - refetch() - dispatch(offlineDataReady(offlineData)) - } - } - } - ) - - const roleChangeHandler = async (systemRole: ISystemRole) => { - const changedRoles = await openUserRoleManage( - (close) => ( - - ) - ) - if (changedRoles) { - const mutateAbleSystemRole: SystemRoleInput = { - id: systemRole.id, - roles: changedRoles - } - updateRoleMutate({ - variables: { - systemRole: mutateAbleSystemRole - } - }) - } - } - - return ( - <> - } - desktopRight={} - mobileLeft={} - mobileTitle={intl.formatMessage(messages.userRoles)} - /> - } - navigation={} - skipToContentText={intl.formatMessage( - constantsMessages.skipToMainContent - )} - > - { - window.open('https://www.opencrvs.org/', '_blank') - }} - font="reg16" - > - Link - - ) - })} - > - {!!updateRoleError || (error && )} - {loading && } - {!error && !loading && ( - - - {intl.formatMessage(messages.systemRoles)} - - } - value={ - - {intl.formatMessage(messages.role)} - - } - /> - {(data?.getSystemRoles ?? []).map((systemRole) => { - return ( - - {getUserSystemRole( - { systemRole: systemRole.value }, - intl - )} - - } - value={ - - - {systemRole.roles.map((role) => ( - - {getUserRole(language, role)} - - ))} - - - } - actions={ - { - roleChangeHandler(systemRole) - }} - > - {intl.formatMessage(buttonMessages.change)} - - } - /> - ) - })} - - )} - {userRoleMgntModalNode} - - - - {roleUpdateSuccess && ( - reset()}> - {intl.formatMessage(messages.systemRoleSuccessMsg)} - - )} - - ) -} - -export default UserRoles diff --git a/packages/client/src/views/SysAdmin/Config/UserRoles/utils.ts b/packages/client/src/views/SysAdmin/Config/UserRoles/utils.ts deleted file mode 100644 index 3b4c101745c..00000000000 --- a/packages/client/src/views/SysAdmin/Config/UserRoles/utils.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * OpenCRVS is also distributed under the terms of the Civil Registration - * & Healthcare Disclaimer located at http://opencrvs.org/license. - * - * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. - */ - -import { LANG_EN } from '@client/utils/constants' -import { Role } from '@client/utils/gateway' - -export function getUserRole(lang: string, role: Role) { - const defaultLabel = role?.labels?.find((label) => label.lang === LANG_EN) - const label = role?.labels?.find((label) => label.lang === lang) - return label?.label || defaultLabel?.label -} diff --git a/packages/client/src/views/SysAdmin/Performance/WorkflowStatus.tsx b/packages/client/src/views/SysAdmin/Performance/WorkflowStatus.tsx index 95aa1ebd909..9d3a42617cc 100644 --- a/packages/client/src/views/SysAdmin/Performance/WorkflowStatus.tsx +++ b/packages/client/src/views/SysAdmin/Performance/WorkflowStatus.tsx @@ -64,7 +64,7 @@ import { Content, ContentSize } from '@opencrvs/components/lib/Content' import { Spinner } from '@opencrvs/components/lib/Spinner' import { Table } from '@opencrvs/components/lib/Table' import { Pagination } from '@opencrvs/components/lib/Pagination' -import { getUserRole } from '@client/views/SysAdmin/Config/UserRoles/utils' + import { getLanguage } from '@client/i18n/selectors' type IDispatchProps = { @@ -516,7 +516,7 @@ function WorkflowStatusComponent(props: WorkflowStatusProps) { if (eventProgress.startedBy != null) { const user = eventProgress.startedBy starterPractitionerRole = - (user.role && getUserRole(language, user.role)) || '' + (user.role && intl.formatMessage(user.role.label)) || '' } const event = diff --git a/packages/client/src/views/SysAdmin/Performance/queries.ts b/packages/client/src/views/SysAdmin/Performance/queries.ts index fc0f84b3705..c07cee9efd5 100644 --- a/packages/client/src/views/SysAdmin/Performance/queries.ts +++ b/packages/client/src/views/SysAdmin/Performance/queries.ts @@ -114,10 +114,11 @@ export const FETCH_EVENTS_WITH_PROGRESS = gql` } systemRole role { - _id - labels { - lang - label + id + label { + id + defaultMessage + description } } } @@ -166,10 +167,11 @@ export const FETCH_REGISTRATIONS = gql` id systemRole role { - _id - labels { - lang - label + id + label { + id + defaultMessage + description } } primaryOffice { @@ -247,9 +249,10 @@ export const FETCH_FIELD_AGENTS_WITH_PERFORMANCE_DATA = gql` practitionerId fullName role { - labels { - label - lang + label { + id + defaultMessage + description } } status diff --git a/packages/client/src/views/SysAdmin/Team/user/UserAuditActionModal.test.tsx b/packages/client/src/views/SysAdmin/Team/user/UserAuditActionModal.test.tsx index 5890c21ff6e..818f2c11dc2 100644 --- a/packages/client/src/views/SysAdmin/Team/user/UserAuditActionModal.test.tsx +++ b/packages/client/src/views/SysAdmin/Team/user/UserAuditActionModal.test.tsx @@ -45,13 +45,11 @@ const users: UserDetails[] = [ signature: undefined }, role: { - _id: '778464c0-08f8-4fb7-8a37-b86d1efc462a', - labels: [ - { - lang: 'en', - label: 'ENTREPENEUR' - } - ] + label: { + id: 'userRoles.entrepreneur', + defaultMessage: 'Entrepreneur', + description: 'Entrepreneur' + } }, status: Status.Active, creationDate: '2022-10-03T10:42:46.920Z', @@ -77,13 +75,11 @@ const users: UserDetails[] = [ username: 'np.huq', systemRole: SystemRoleType.LocalRegistrar, role: { - _id: '778464c0-08f8-4fb7-8a37-b86d1efc462a', - labels: [ - { - lang: 'en', - label: 'MAYOR' - } - ] + label: { + id: 'userRoles.mayor', + defaultMessage: 'Mayor', + description: 'Mayor' + } }, status: Status.Deactivated, localRegistrar: { diff --git a/packages/client/src/views/SysAdmin/Team/user/UserList.tsx b/packages/client/src/views/SysAdmin/Team/user/UserList.tsx index 34a7659c9ab..2a1d4cc2d3c 100644 --- a/packages/client/src/views/SysAdmin/Team/user/UserList.tsx +++ b/packages/client/src/views/SysAdmin/Team/user/UserList.tsx @@ -36,11 +36,7 @@ import { } from '@client/utils/constants' import { createNamesMap } from '@client/utils/data-formatting' import { SysAdminContentWrapper } from '@client/views/SysAdmin/SysAdminContentWrapper' -import { - getAddressName, - getUserRoleIntlKey, - UserStatus -} from '@client/views/SysAdmin/Team/utils' +import { getAddressName, UserStatus } from '@client/views/SysAdmin/Team/utils' import { LinkButton } from '@opencrvs/components/lib/buttons' import { Button } from '@opencrvs/components/lib/Button' import { Pill } from '@opencrvs/components/lib/Pill' @@ -558,9 +554,7 @@ function UserListComponent(props: IProps) { ((createNamesMap(user.name)[intl.locale] as string) || (createNamesMap(user.name)[LANG_EN] as string))) || '' - const role = intl.formatMessage({ - id: getUserRoleIntlKey(user.role._id) - }) + const role = intl.formatMessage(user.role.label) const avatar = user.avatar return { diff --git a/packages/client/src/views/SysAdmin/Team/user/userCreation/CreateNewUser.tsx b/packages/client/src/views/SysAdmin/Team/user/userCreation/CreateNewUser.tsx index 69ab5ac2b45..3a510f6d760 100644 --- a/packages/client/src/views/SysAdmin/Team/user/userCreation/CreateNewUser.tsx +++ b/packages/client/src/views/SysAdmin/Team/user/userCreation/CreateNewUser.tsx @@ -99,7 +99,7 @@ class CreateNewUserComponent extends React.Component> { this.props.clearUserFormData() } if (userId) { - this.props.fetchAndStoreUserData(client as ApolloClient, GET_USER, { + this.props.fetchAndStoreUserData(client as ApolloClient, { userId }) } @@ -142,6 +142,7 @@ class CreateNewUserComponent extends React.Component> { render() { const { section, submitting, userDetailsStored, loadingRoles, userId } = this.props + if (submitting || loadingRoles || (userId && !userDetailsStored)) { return this.renderLoadingPage() } @@ -210,6 +211,7 @@ const mapStateToProps = (state: IStoreState, props: Props) => { } let formData = { ...state.userForm.userFormData } + if (props.match.params.locationId) { formData = { ...gqlToDraftTransformer( diff --git a/packages/client/src/views/SysAdmin/Team/user/userCreation/SignatureForm.test.tsx b/packages/client/src/views/SysAdmin/Team/user/userCreation/SignatureForm.test.tsx index c8fb2a3f68f..839457cd2b1 100644 --- a/packages/client/src/views/SysAdmin/Team/user/userCreation/SignatureForm.test.tsx +++ b/packages/client/src/views/SysAdmin/Team/user/userCreation/SignatureForm.test.tsx @@ -23,10 +23,7 @@ import { mockUserGraphqlOperation } from '@client/tests/util' import { waitForElement } from '@client/tests/wait-for-element' -import { - modifyUserFormData, - rolesMessageAddData -} from '@client/user/userReducer' +import { modifyUserFormData } from '@client/user/userReducer' import { CreateNewUser } from '@client/views/SysAdmin/Team/user/userCreation/CreateNewUser' import { ReactWrapper } from 'enzyme' import * as React from 'react' @@ -133,7 +130,7 @@ describe('signature upload tests', () => { describe('when user in review page', () => { beforeEach(async () => { store.dispatch(modifyUserFormData(mockDataWithRegistarRoleSelected)) - store.dispatch(rolesMessageAddData()) + testComponent = await createTestComponent( // @ts-ignore { values['registrationOffice'] !== '0' && values['registrationOffice'] !== '' ) { - if (values.role) { - const getSystemRoles = this.props.systemRoleMap - values.systemRole = getSystemRoles[values.role] - } this.props.modifyUserFormData({ ...formData, ...values }) this.setState({ disableContinueOnLocation: false @@ -200,11 +190,8 @@ class UserFormComponent extends React.Component { } } -const mapStateToProps = ( - state: IStoreState -): { offlineCountryConfig: IOfflineData; systemRoleMap: ISystemRolesMap } => { +const mapStateToProps = (state: IStoreState) => { return { - systemRoleMap: selectSystemRoleMap(state), offlineCountryConfig: getOfflineData(state) } } diff --git a/packages/client/src/views/SysAdmin/Team/user/userCreation/UserReviewForm.tsx b/packages/client/src/views/SysAdmin/Team/user/userCreation/UserReviewForm.tsx index b7b43fdfc44..c28c368778b 100644 --- a/packages/client/src/views/SysAdmin/Team/user/userCreation/UserReviewForm.tsx +++ b/packages/client/src/views/SysAdmin/Team/user/userCreation/UserReviewForm.tsx @@ -8,7 +8,7 @@ * * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. */ -import { SimpleDocumentUploader } from '@client/components/form/DocumentUploadfield/SimpleDocumentUploader' + import { DIVIDER, FIELD_GROUP_TITLE, @@ -68,8 +68,8 @@ import { } from '@opencrvs/components/lib/ListViewSimplified' import styled from 'styled-components' import { Content } from '@opencrvs/components/lib/Content' -import { getUserRoleIntlKey } from '@client/views/SysAdmin/Team/utils' import { Link } from '@opencrvs/components' +import { UserRole } from '@client/utils/gateway' interface IUserReviewFormProps { userId?: string @@ -78,16 +78,19 @@ interface IUserReviewFormProps { client: ApolloClient } +interface IStateProps { + userFormSection: IFormSection + offlineCountryConfiguration: IOfflineData + userRoles: UserRole[] + userDetails: UserDetails | null +} interface IDispatchProps { goToCreateUserSection: typeof goToCreateUserSection goToUserReviewForm: typeof goToUserReviewForm submitForm: (userFormSection: IFormSection) => void - userFormSection: IFormSection - offlineCountryConfiguration: IOfflineData goBack: typeof goBack goToTeamUserList: typeof goToTeamUserList modify: (values: IFormSectionData) => void - userDetails: UserDetails | null } interface ISectionData { @@ -114,18 +117,12 @@ const Label = styled.span` width: 100%; ` -const DocumentUploaderContainer = styled.div` - @media (max-width: ${({ theme }) => theme.grid.breakpoints.md}px) { - padding-right: 8px; - } -` - const Value = styled.span` ${({ theme }) => theme.fonts.reg16} ` class UserReviewFormComponent extends React.Component< - IFullProps & IDispatchProps + IFullProps & IDispatchProps & IStateProps > { transformSectionData = () => { const { intl, userFormSection } = this.props @@ -214,7 +211,7 @@ class UserReviewFormComponent extends React.Component< } getValue = (field: IFormField) => { - const { intl, formData } = this.props + const { intl, formData, userRoles } = this.props if (field.type === LOCATION_SEARCH_INPUT) { const offlineLocations = field.searchableResource.reduce( @@ -234,14 +231,15 @@ class UserReviewFormComponent extends React.Component< return offlineLocations[locationId] && offlineLocations[locationId].name } + const role = userRoles.find(({ id }) => id === formData.role) + console.log({ userRoles }) + return formData[field.name] ? typeof formData[field.name] !== 'object' ? field.name === 'systemRole' ? intl.formatMessage(userMessages[formData.systemRole as string]) - : field.name === 'role' - ? intl.formatMessage({ - id: getUserRoleIntlKey(formData.role as string) - }) + : field.name === 'role' && role + ? intl.formatMessage(role.label) : String(formData[field.name]) : (formData[field.name] as IDynamicValues).label : '' @@ -314,6 +312,7 @@ class UserReviewFormComponent extends React.Component< ) } + return ( { return { userFormSection: store.userForm.userForm!.sections[0], offlineCountryConfiguration: getOfflineData(store), - userDetails: getUserDetails(store) + userDetails: getUserDetails(store), + userRoles: store.userForm.userRoles } }, mapDispatchToProps)( - injectIntl<'intl', IFullProps & IDispatchProps>(UserReviewFormComponent) + injectIntl<'intl', IFullProps & IDispatchProps & IStateProps>( + UserReviewFormComponent + ) ) diff --git a/packages/client/src/views/SysAdmin/Team/utils.ts b/packages/client/src/views/SysAdmin/Team/utils.ts index ecffc403feb..9e8166ef670 100644 --- a/packages/client/src/views/SysAdmin/Team/utils.ts +++ b/packages/client/src/views/SysAdmin/Team/utils.ts @@ -114,7 +114,3 @@ export function getUserSystemRole( return undefined } } - -export const getUserRoleIntlKey = (_roleId: string) => { - return `role.${_roleId}` -} diff --git a/packages/client/src/views/Unlock/ForgotPIN.test.tsx b/packages/client/src/views/Unlock/ForgotPIN.test.tsx index 36bd1e63e3f..9eb748550df 100644 --- a/packages/client/src/views/Unlock/ForgotPIN.test.tsx +++ b/packages/client/src/views/Unlock/ForgotPIN.test.tsx @@ -49,13 +49,11 @@ describe('ForgotPIN tests', () => { mobile: '+8801711111111', systemRole: SystemRoleType.FieldAgent, role: { - _id: '778464c0-08f8-4fb7-8a37-b86d1efc462a', - labels: [ - { - lang: 'en', - label: 'CHA' - } - ] + label: { + id: 'userRoles.cha', + defaultMessage: 'CHA', + description: 'CHA' + } }, status: Status.Active, name: [ diff --git a/packages/client/src/views/UserAudit/UserAudit.tsx b/packages/client/src/views/UserAudit/UserAudit.tsx index 41cc2722d4d..3f5cd63f50a 100644 --- a/packages/client/src/views/UserAudit/UserAudit.tsx +++ b/packages/client/src/views/UserAudit/UserAudit.tsx @@ -9,51 +9,49 @@ * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. */ -import React, { useState } from 'react' -import { messages as userFormMessages } from '@client/i18n/messages/views/userForm' -import { constantsMessages, buttonMessages } from '@client/i18n/messages' -import { messages as sysMessages } from '@client/i18n/messages/views/sysAdmin' -import { Navigation } from '@client/components/interface/Navigation' -import { Frame } from '@opencrvs/components/lib/Frame' -import { IntlShape, useIntl } from 'react-intl' -import { useParams } from 'react-router' -import { GET_USER } from '@client/user/queries' -import { createNamesMap } from '@client/utils/data-formatting' +import { useQuery } from '@apollo/client' import { AvatarSmall } from '@client/components/Avatar' -import styled from 'styled-components' -import { ToggleMenu } from '@opencrvs/components/lib/ToggleMenu' -import { Button } from '@opencrvs/components/lib/Button' -import { getUserRoleIntlKey } from '@client/views/SysAdmin//Team/utils' -import { EMPTY_STRING, LANG_EN } from '@client/utils/constants' -import { Loader } from '@opencrvs/components/lib/Loader' +import { GenericErrorToast } from '@client/components/GenericErrorToast' +import { HistoryNavigator } from '@client/components/Header/HistoryNavigator' +import { ProfileMenu } from '@client/components/ProfileMenu' +import { Navigation } from '@client/components/interface/Navigation' +import { buttonMessages, constantsMessages } from '@client/i18n/messages' +import { messages as sysMessages } from '@client/i18n/messages/views/sysAdmin' +import { messages as userFormMessages } from '@client/i18n/messages/views/userForm' import { messages as userSetupMessages } from '@client/i18n/messages/views/userSetup' -import { Content, ContentSize } from '@opencrvs/components/lib/Content' -import { useDispatch, useSelector } from 'react-redux' import { goToReviewUserDetails, goToTeamUserList } from '@client/navigation' -import { Status } from '@client/views/SysAdmin/Team/user/UserList' -import { Icon } from '@opencrvs/components/lib/Icon' -import { IStoreState } from '@client/store' import { getScope, getUserDetails } from '@client/profile/profileSelectors' +import { IStoreState } from '@client/store' import { userMutations } from '@client/user/mutations' -import { UserAuditHistory } from '@client/views/UserAudit/UserAuditHistory' -import { Summary } from '@opencrvs/components/lib/Summary' -import { Toast } from '@opencrvs/components/lib/Toast' -import { UserAuditActionModal } from '@client/views/SysAdmin/Team/user/UserAuditActionModal' +import { GET_USER } from '@client/user/queries' +import { Scope } from '@client/utils/authUtils' +import { EMPTY_STRING, LANG_EN } from '@client/utils/constants' +import { createNamesMap } from '@client/utils/data-formatting' import { GetUserQuery, GetUserQueryVariables, HumanName, - User, - SystemRoleType + SystemRoleType, + User } from '@client/utils/gateway' -import { GenericErrorToast } from '@client/components/GenericErrorToast' -import { ResponsiveModal } from '@opencrvs/components/lib/ResponsiveModal' -import { useQuery } from '@apollo/client' +import { UserAuditActionModal } from '@client/views/SysAdmin/Team/user/UserAuditActionModal' +import { Status } from '@client/views/SysAdmin/Team/user/UserList' +import { UserAuditHistory } from '@client/views/UserAudit/UserAuditHistory' import { AppBar, Link } from '@opencrvs/components/lib' -import { ProfileMenu } from '@client/components/ProfileMenu' -import { HistoryNavigator } from '@client/components/Header/HistoryNavigator' -import { UserDetails } from '@client/utils/userUtils' -import { Scope } from '@client/utils/authUtils' +import { Button } from '@opencrvs/components/lib/Button' +import { Content, ContentSize } from '@opencrvs/components/lib/Content' +import { Frame } from '@opencrvs/components/lib/Frame' +import { Icon } from '@opencrvs/components/lib/Icon' +import { Loader } from '@opencrvs/components/lib/Loader' +import { ResponsiveModal } from '@opencrvs/components/lib/ResponsiveModal' +import { Summary } from '@opencrvs/components/lib/Summary' +import { Toast } from '@opencrvs/components/lib/Toast' +import { ToggleMenu } from '@opencrvs/components/lib/ToggleMenu' +import React, { useState } from 'react' +import { IntlShape, useIntl } from 'react-intl' +import { useDispatch, useSelector } from 'react-redux' +import { useParams } from 'react-router' +import styled from 'styled-components' const UserAvatar = styled(AvatarSmall)` @media (max-width: ${({ theme }) => theme.grid.breakpoints.md}px) { @@ -111,7 +109,7 @@ function canEditUserDetails( loggedInUser: Pick & { primaryOffice?: { id: string } | null }, - scopes: Scope + scopes: Scope[] ) { if (!scopes.includes('sysadmin')) { return false @@ -157,8 +155,7 @@ export const UserAudit = () => { GetUserQueryVariables >(GET_USER, { variables: { userId }, fetchPolicy: 'cache-and-network' }) const user = data?.getUser && transformUserQueryResult(data.getUser, intl) - const userRole = - user && intl.formatMessage({ id: getUserRoleIntlKey(user.role._id) }) + const userRole = user && intl.formatMessage(user.role.label) const toggleUserActivationModal = () => { setModalVisible(!modalVisible) diff --git a/packages/client/src/views/UserRoles/UserRoleManagementModal.tsx b/packages/client/src/views/UserRoles/UserRoleManagementModal.tsx deleted file mode 100644 index a7e6a7dce81..00000000000 --- a/packages/client/src/views/UserRoles/UserRoleManagementModal.tsx +++ /dev/null @@ -1,259 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * OpenCRVS is also distributed under the terms of the Civil Registration - * & Healthcare Disclaimer located at http://opencrvs.org/license. - * - * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. - */ - -import React, { useState } from 'react' -import { Text } from '@opencrvs/components/lib/Text' -import { ResponsiveModal, Select, Stack, TextInput } from '@opencrvs/components' -import { Button } from '@opencrvs/components/lib/Button' -import { buttonMessages } from '@client/i18n/messages' -import { useIntl } from 'react-intl' -import { Icon } from '@opencrvs/components/lib/Icon' -import styled from 'styled-components' -import { getAvailableLanguages } from '@client/i18n/utils' -import { useSelector } from 'react-redux' -import { getLanguages } from '@client/i18n/selectors' -import { getUserSystemRole } from '@client/views/SysAdmin/Team/utils' -import { messages } from '@client/i18n/messages/views/config' -import _ from 'lodash' -import { - ISystemRole, - RolesInput -} from '@client/views/SysAdmin/Config/UserRoles/UserRoles' - -const StyledTextInput = styled(TextInput)` - ${({ theme }) => theme.fonts.reg14}; - height: 40px; - border: solid 1px ${({ theme }) => theme.colors.grey600}; - align-self: center; - - :disabled { - border-color: ${({ theme }) => theme.colors.grey300}; - color: ${({ theme }) => theme.colors.grey500}; - } -` - -interface ILanguageOptions { - [key: string]: string -} - -interface IProps { - systemRole: ISystemRole - closeCallback: (result: RolesInput | null) => void -} - -const LanguageSelect = styled(Select)` - width: 175px; - border-radius: 2px; - - .react-select__control { - max-height: 32px; - min-height: 32px; - } - - .react-select__value-container { - display: block; - } - - div { - ${({ theme }) => theme.fonts.reg14}; - color: ${({ theme }) => theme.colors.primaryDark}; - } -` - -function stripTypenameFromRoles(roles: ISystemRole['roles']) { - return roles.map(({ __typename, ...rest }) => ({ - ...rest, - labels: rest.labels.map(({ __typename, ...rest }) => rest) - })) -} - -export function UserRoleManagementModal(props: IProps) { - const [userRoles, setUserRoles] = useState( - stripTypenameFromRoles(props.systemRole.roles) - ) - const [currentLanguage, setCurrentLanguage] = useState('en') - const [currentClipBoard, setCurrentClipBoard] = useState('') - const intl = useIntl() - const [actives, setActives] = useState(new Array(userRoles.length).fill(true)) - - const availableLangs = getAvailableLanguages() - const languages = useSelector(getLanguages) - const langChoice = availableLangs.reduce( - (choices, lang) => - languages[lang] - ? [ - ...choices, - { - value: lang, - label: intl.formatMessage(messages.language, { - language: languages[lang].lang - }) - } - ] - : choices, - [] - ) - - const isRoleUpdateValid = () => { - if (_.isEqual(userRoles, stripTypenameFromRoles(props.systemRole.roles))) { - return false - } - const inCompleteRoleEntries = userRoles.filter((role) => { - for (const label of role.labels) { - if (label.label === '') { - return true - } - } - return false - }) - - if (inCompleteRoleEntries.length > 0) { - return false - } - return true - } - - const updateRole = () => { - const newLabels = availableLangs.map((lang) => { - if (lang === currentLanguage) { - return { - lang: currentLanguage, - label: currentClipBoard - } - } - return { lang: lang, label: '' } - }) - - setUserRoles([...userRoles, { labels: newLabels }]) - setCurrentClipBoard('') - setActives(new Array(userRoles.length).fill(false)) - } - - return ( - { - props.closeCallback(null) - }} - > - {intl.formatMessage(buttonMessages.cancel)} - , - - ]} - show={true} - handleClose={() => props.closeCallback(null)} - > - - {intl.formatMessage(messages.roleUpdateInstruction, { - systemRole: - getUserSystemRole({ systemRole: props.systemRole.value }, intl) || - '' - })} - - - - { - setCurrentLanguage(val) - }} - value={currentLanguage} - options={langChoice} - placeholder="" - /> - {userRoles.map((item, index) => { - return ( - - e.lang === currentLanguage)?.label || - '' - } - isDisabled={actives[index]} - focusInput={!actives[index]} - onChange={(e) => { - const newUserRoles = userRoles.map((userRole, idx) => { - if (index !== idx) return userRole - return { - ...userRole, - labels: userRole.labels.map((label) => { - if (label.lang === currentLanguage) { - return { ...label, label: e.target.value } - } - return label - }) - } - }) - setUserRoles(newUserRoles) - }} - onBlur={() => - setActives(new Array(userRoles.length).fill(true)) - } - /> - {actives[index] && !item._id && ( - - )} - - ) - })} - - - { - setCurrentClipBoard(e.target.value) - }} - /> - - - - - ) -} diff --git a/packages/client/src/views/UserSetup/SetupReviewPage.tsx b/packages/client/src/views/UserSetup/SetupReviewPage.tsx index bbb623146af..4cd10f5af2c 100644 --- a/packages/client/src/views/UserSetup/SetupReviewPage.tsx +++ b/packages/client/src/views/UserSetup/SetupReviewPage.tsx @@ -8,40 +8,38 @@ * * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. */ -import * as React from 'react' -import { useSelector } from 'react-redux' -import { useIntl } from 'react-intl' -import styled from 'styled-components' -import { ActionPageLight } from '@opencrvs/components/lib/ActionPageLight' -import { DataRow, IDataProps } from '@opencrvs/components/lib/ViewData' -import { Button } from '@opencrvs/components/lib/Button' -import { Icon } from '@opencrvs/components/lib/Icon' -import { Loader } from '@opencrvs/components/lib/Loader' +import { Mutation } from '@apollo/client/react/components' import { - ProtectedAccoutStep, IProtectedAccountSetupData, - ISecurityQuestionAnswer + ISecurityQuestionAnswer, + ProtectedAccoutStep } from '@client/components/ProtectedAccount' +import { + buttonMessages, + constantsMessages, + errorMessages, + userMessages +} from '@client/i18n/messages' +import { messages } from '@client/i18n/messages/views/userSetup' import { getUserDetails } from '@client/profile/profileSelectors' import { IStoreState } from '@client/store' -import { getUserName, UserDetails } from '@client/utils/userUtils' import { SubmitActivateUserMutation, SubmitActivateUserMutationVariables } from '@client/utils/gateway' -import { Mutation } from '@apollo/client/react/components' -import { - userMessages, - buttonMessages, - constantsMessages, - errorMessages -} from '@client/i18n/messages' +import { UserDetails, getUserName } from '@client/utils/userUtils' import { activateUserMutation } from '@client/views/UserSetup/queries' -import { messages } from '@client/i18n/messages/views/userSetup' -import { Content } from '@opencrvs/components/lib/Content' -import { getUserRole } from '@client/views/SysAdmin/Config/UserRoles/utils' -import { getLanguage } from '@client/i18n/selectors' import { ErrorText } from '@opencrvs/components/lib/' +import { ActionPageLight } from '@opencrvs/components/lib/ActionPageLight' +import { Button } from '@opencrvs/components/lib/Button' +import { Content } from '@opencrvs/components/lib/Content' +import { Icon } from '@opencrvs/components/lib/Icon' +import { Loader } from '@opencrvs/components/lib/Loader' +import { DataRow, IDataProps } from '@opencrvs/components/lib/ViewData' +import * as React from 'react' +import { useIntl } from 'react-intl' +import { useSelector } from 'react-redux' +import styled from 'styled-components' const GlobalError = styled.div` color: ${({ theme }) => theme.colors.negative}; @@ -64,8 +62,7 @@ export function UserSetupReview({ setupData, goToStep }: IProps) { const englishName = getUserName(userDetails) const mobile = (userDetails && (userDetails.mobile as string)) || '' const email = (userDetails && (userDetails.email as string)) || '' - const language = useSelector(getLanguage) - const role = userDetails && getUserRole(language, userDetails.role) + const role = userDetails && intl.formatMessage(userDetails.role.label) const primaryOffice = (userDetails && userDetails.primaryOffice && diff --git a/packages/client/src/views/ViewRecord/query.ts b/packages/client/src/views/ViewRecord/query.ts index ec6d1c4c748..cb536afa89f 100644 --- a/packages/client/src/views/ViewRecord/query.ts +++ b/packages/client/src/views/ViewRecord/query.ts @@ -78,7 +78,7 @@ export const FETCH_VIEW_RECORD_BY_COMPOSITION = gql` user { id role { - _id + id } systemRole name { diff --git a/packages/commons/src/authentication.ts b/packages/commons/src/authentication.ts index 8b0f09afa8f..5c88d969937 100644 --- a/packages/commons/src/authentication.ts +++ b/packages/commons/src/authentication.ts @@ -13,7 +13,7 @@ import { IAuthHeader } from './http' import * as decode from 'jwt-decode' /** All the scopes user can be assigned to */ -export const userScopes = { +export const SCOPES = { demo: 'demo', declare: 'declare', register: 'register', @@ -28,35 +28,8 @@ export const userScopes = { config: 'config' } as const -export const userRoleScopes = { - FIELD_AGENT: [userScopes.declare], - REGISTRATION_AGENT: [ - userScopes.validate, - userScopes.performance, - userScopes.certify - ], - LOCAL_REGISTRAR: [ - userScopes.register, - userScopes.performance, - userScopes.certify - ], - LOCAL_SYSTEM_ADMIN: [userScopes.systemAdmin], - NATIONAL_SYSTEM_ADMIN: [ - userScopes.systemAdmin, - userScopes.nationalSystemAdmin - ], - PERFORMANCE_MANAGEMENT: [userScopes.performance], - NATIONAL_REGISTRAR: [ - userScopes.register, - userScopes.performance, - userScopes.certify, - userScopes.config, - userScopes.teams - ] -} - /** All the scopes system/integration can be assigned to */ -export const systemScopes = { +export const SYSTEM_INTEGRATION_SCOPES = { recordsearch: 'recordsearch', declare: 'declare', notificationApi: 'notification-api', @@ -66,21 +39,63 @@ export const systemScopes = { nationalId: 'nationalId' } as const -export const systemRoleScopes = { - HEALTH: [systemScopes.declare, systemScopes.notificationApi], - NATIONAL_ID: [systemScopes.nationalId], - EXTERNAL_VALIDATION: [systemScopes.validatorApi], - AGE_CHECK: [systemScopes.declare, systemScopes.ageVerificationApi], - RECORD_SEARCH: [systemScopes.recordsearch], - WEBHOOK: [systemScopes.webhook] +export const DEFAULT_CORE_ROLE_SCOPES = { + FIELD_AGENT: [SCOPES.declare], + REGISTRATION_AGENT: [SCOPES.validate, SCOPES.performance, SCOPES.certify], + LOCAL_REGISTRAR: [SCOPES.register, SCOPES.performance, SCOPES.certify], + LOCAL_SYSTEM_ADMIN: [SCOPES.systemAdmin], + NATIONAL_SYSTEM_ADMIN: [SCOPES.systemAdmin, SCOPES.nationalSystemAdmin], + PERFORMANCE_MANAGEMENT: [SCOPES.performance], + NATIONAL_REGISTRAR: [ + SCOPES.register, + SCOPES.performance, + SCOPES.certify, + SCOPES.config, + SCOPES.teams + ], + SUPER_ADMIN: [ + SCOPES.nationalSystemAdmin, + SCOPES.bypassRateLimit, + SCOPES.systemAdmin + ] } -export type UserRole = keyof typeof userRoleScopes -export type UserScope = (typeof userScopes)[keyof typeof userScopes] -export type SystemRole = keyof typeof systemRoleScopes -export type SystemScope = (typeof systemScopes)[keyof typeof systemScopes] +export const DEFAULT_SYSTEM_INTEGRATION_ROLE_SCOPES = { + HEALTH: [ + SYSTEM_INTEGRATION_SCOPES.declare, + SYSTEM_INTEGRATION_SCOPES.notificationApi + ], + NATIONAL_ID: [SYSTEM_INTEGRATION_SCOPES.nationalId], + EXTERNAL_VALIDATION: [SYSTEM_INTEGRATION_SCOPES.validatorApi], + AGE_CHECK: [ + SYSTEM_INTEGRATION_SCOPES.declare, + SYSTEM_INTEGRATION_SCOPES.ageVerificationApi + ], + RECORD_SEARCH: [SYSTEM_INTEGRATION_SCOPES.recordsearch], + WEBHOOK: [SYSTEM_INTEGRATION_SCOPES.webhook] +} + +/* + * Describes a "legacy" user role such as FIELD_AGENT, REGISTRATION_AGENT, etc. + * These are roles we are slowly sunsettings in favor of the new, more configurable user roles. + */ +export type CoreUserRole = keyof typeof DEFAULT_CORE_ROLE_SCOPES + +export type SystemIntegrationRole = + keyof typeof DEFAULT_SYSTEM_INTEGRATION_ROLE_SCOPES + +export type UserScope = (typeof SCOPES)[keyof typeof SCOPES] +export type SystemScope = + (typeof SYSTEM_INTEGRATION_SCOPES)[keyof typeof SYSTEM_INTEGRATION_SCOPES] export type Scope = UserScope | SystemScope +export type Roles = Array<{ + id: string + systemRole: CoreUserRole + labels: Array<{ language: string; label: string }> + scopes: Scope[] +}> + export interface ITokenPayload { sub: string exp: string diff --git a/packages/commons/src/http.ts b/packages/commons/src/http.ts index 8343c1489a5..fed68065645 100644 --- a/packages/commons/src/http.ts +++ b/packages/commons/src/http.ts @@ -10,6 +10,7 @@ */ import type * as Hapi from '@hapi/hapi' import { uniqueId } from 'lodash' +import nodeFetch from 'node-fetch' export interface IAuthHeader { Authorization: string @@ -31,3 +32,26 @@ export function joinURL(base: string, path: string) { const baseWithSlash = base.endsWith('/') ? base : base + '/' return new URL(path, baseWithSlash) } + +export class NotFound extends Error { + constructor(message: string) { + super(message) + this.name = 'NotFound' + } +} + +export async function fetchJSON( + ...params: Parameters +) { + const res = await nodeFetch(...params) + + if (!res.ok) { + if (res.status === 404) { + throw new NotFound(res.statusText) + } + + throw new Error(res.statusText) + } + + return res.json() as ResponseType +} diff --git a/packages/commons/src/token-verifier.ts b/packages/commons/src/token-verifier.ts index 9717e95633a..606bb90cb23 100644 --- a/packages/commons/src/token-verifier.ts +++ b/packages/commons/src/token-verifier.ts @@ -37,6 +37,7 @@ export const validateFunc = async ( authUrl: string ) => { let valid + if (checkInvalidToken === 'true') { valid = await verifyToken( request.headers.authorization.replace('Bearer ', ''), diff --git a/packages/data-seeder/src/index.ts b/packages/data-seeder/src/index.ts index 996f9dfa92e..ad2ca173fc3 100644 --- a/packages/data-seeder/src/index.ts +++ b/packages/data-seeder/src/index.ts @@ -12,7 +12,7 @@ import { AUTH_HOST, GATEWAY_HOST, SUPER_USER_PASSWORD } from './constants' import fetch from 'node-fetch' import { seedCertificate } from './certificates' import { seedLocations } from './locations' -import { seedRoles } from './roles' + import { seedUsers } from './users' import { parseGQLResponse, raise } from './utils' import { print } from 'graphql' @@ -32,7 +32,11 @@ async function getToken(): Promise { } }) if (!res.ok) { - raise('Could not login as the super user') + raise( + 'Could not login as the super user. This might because you have seeded the database already and the account has now been deactivated', + res.status, + res.statusText + ) } const body = await res.json() return body.token @@ -91,12 +95,11 @@ async function deactivateSuperuser(token: string) { async function main() { const token = await getToken() - console.log('Seeding roles') - const roleIdMap = await seedRoles(token) + console.log('Seeding locations') await seedLocations(token) console.log('Seeding users') - await seedUsers(token, roleIdMap) + await seedUsers(token) console.log('Seeding certificates') await seedCertificate(token) await deactivateSuperuser(token) diff --git a/packages/data-seeder/src/roles.ts b/packages/data-seeder/src/roles.ts deleted file mode 100644 index e088a17562f..00000000000 --- a/packages/data-seeder/src/roles.ts +++ /dev/null @@ -1,223 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * OpenCRVS is also distributed under the terms of the Civil Registration - * & Healthcare Disclaimer located at http://opencrvs.org/license. - * - * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. - */ -import { COUNTRY_CONFIG_HOST, GATEWAY_HOST } from './constants' -import { raise, parseGQLResponse } from './utils' -import fetch from 'node-fetch' -import { z } from 'zod' -import { print } from 'graphql' -import gql from 'graphql-tag' -import { inspect } from 'util' - -const LabelSchema = z.array( - z.object({ - labels: z.array(z.object({ lang: z.string(), label: z.string() })) - }) -) - -/* - * at least LOCAL_REGISTRAR & NATIONAL_SYSTEM_ADMIN - * roles are required - */ -const CountryRoleSchema = z.object({ - FIELD_AGENT: LabelSchema.optional(), - LOCAL_REGISTRAR: LabelSchema, - LOCAL_SYSTEM_ADMIN: LabelSchema.optional(), - NATIONAL_REGISTRAR: LabelSchema.optional(), - NATIONAL_SYSTEM_ADMIN: LabelSchema, - PERFORMANCE_MANAGEMENT: LabelSchema.optional(), - REGISTRATION_AGENT: LabelSchema.optional() -}) - -const SYSTEM_ROLES = [ - 'FIELD_AGENT', - 'LOCAL_REGISTRAR', - 'LOCAL_SYSTEM_ADMIN', - 'NATIONAL_REGISTRAR', - 'NATIONAL_SYSTEM_ADMIN', - 'PERFORMANCE_MANAGEMENT', - 'REGISTRATION_AGENT' -] as const - -export interface Label { - lang: string - label: string -} - -export interface Role { - _id?: string - labels: Array