diff --git a/packages/metrics/src/config/routes.ts b/packages/metrics/src/config/routes.ts index c243eb679ba..20af734a71e 100644 --- a/packages/metrics/src/config/routes.ts +++ b/packages/metrics/src/config/routes.ts @@ -77,10 +77,8 @@ import { } from '@metrics/features/performance/viewRefresher' import { PRODUCTION, QA_ENV } from '@metrics/constants' import * as Hapi from '@hapi/hapi' +import { SCOPES } from '@opencrvs/commons/authentication' -const enum RouteScope { - NATLSYSADMIN = 'natlsysadmin' -} export enum EventType { BIRTH = 'birth', DEATH = 'death', @@ -717,7 +715,7 @@ export const getRoutes = () => { handler: metricsDeleteMeasurementHandler, options: { auth: { - scope: [RouteScope.NATLSYSADMIN] + scope: [SCOPES.CONFIG_UPDATE_ALL] }, tags: ['api'] } @@ -729,7 +727,7 @@ export const getRoutes = () => { handler: deletePerformanceHandler, options: { auth: { - scope: [RouteScope.NATLSYSADMIN] + scope: [SCOPES.CONFIG_UPDATE_ALL] }, tags: ['api'] } diff --git a/packages/metrics/src/features/audit/handler.ts b/packages/metrics/src/features/audit/handler.ts index d9807b615e9..beee98dccd7 100644 --- a/packages/metrics/src/features/audit/handler.ts +++ b/packages/metrics/src/features/audit/handler.ts @@ -12,7 +12,7 @@ import * as Hapi from '@hapi/hapi' import { generateAuditPoint } from '@metrics/features/registration/pointGenerator' import { writePoints } from '@metrics/influxdb/client' import { internal } from '@hapi/boom' -import { IUserAuditBody } from '@metrics/features/registration' +import { IUserAuditBody, IUserModelData } from '@metrics/features/registration' import { PRACTITIONER_ID } from '@metrics/features/getTimeLogged/constants' import { countUserAuditEvents, getUserAuditEvents } from './service' import { getClientIdFromToken } from '@metrics/utils/authUtils' @@ -60,7 +60,7 @@ export async function newAuditHandler( export async function getUser( userId: string, authHeader: { Authorization: string } -) { +): Promise { const res = await fetch(`${USER_MANAGEMENT_URL}/getUser`, { method: 'POST', body: JSON.stringify({ userId }), diff --git a/packages/metrics/src/features/metrics/handler.test.ts b/packages/metrics/src/features/metrics/handler.test.ts index 8709dca1810..f3f3920048f 100644 --- a/packages/metrics/src/features/metrics/handler.test.ts +++ b/packages/metrics/src/features/metrics/handler.test.ts @@ -14,6 +14,7 @@ import { readFileSync } from 'fs' import * as jwt from 'jsonwebtoken' import * as fetchMock from 'jest-fetch-mock' import { influx } from '@metrics/influxdb/client' +import { SCOPES } from '@opencrvs/commons/authentication' const fetch: fetchMock.FetchMock = fetchMock as fetchMock.FetchMock @@ -199,7 +200,7 @@ describe('verify metrics handler', () => { describe('delete metrics measurement handler', () => { let server: any const token = jwt.sign( - { scope: ['declare', 'natlsysadmin'] }, + { scope: [SCOPES.CONFIG_UPDATE_ALL] }, readFileSync('./test/cert.key'), { algorithm: 'RS256', diff --git a/packages/metrics/src/features/registration/handler.ts b/packages/metrics/src/features/registration/handler.ts index 831344cd892..4f72c2b07af 100644 --- a/packages/metrics/src/features/registration/handler.ts +++ b/packages/metrics/src/features/registration/handler.ts @@ -25,7 +25,6 @@ import { } from '@metrics/features/registration/pointGenerator' import { badRequest, internal } from '@hapi/boom' import { populateBundleFromPayload } from '@metrics/features/registration/utils' -import { Events } from '@metrics/features/metrics/constants' import { IPoints } from '@metrics/features/registration' import { createUserAuditPointFromFHIR } from '@metrics/features/audit/service' import { @@ -37,7 +36,6 @@ import { } from '@metrics/features/registration/fhirUtils' import { EventType } from '@metrics/config/routes' import { fetchTaskHistory } from '@metrics/api' -import { hasScope } from '@opencrvs/commons/authentication' export async function waitingExternalValidationHandler( request: Hapi.Request, @@ -121,8 +119,6 @@ export async function sentNotificationForReviewHandler( ) { const points = [] - const authHeader = { Authorization: request.headers.authorization } - await createUserAuditPointFromFHIR('DECLARED', request) try { @@ -133,18 +129,10 @@ export async function sentNotificationForReviewHandler( }) ) points.push( - await generateDeclarationStartedPoint( - request.payload as fhir.Bundle, - { - Authorization: request.headers.authorization, - 'x-correlation-id': request.headers['x-correlation-id'] - }, - hasScope(authHeader, 'validate') - ? Events.VALIDATED - : hasScope(authHeader, 'register') - ? Events.WAITING_EXTERNAL_VALIDATION - : Events.READY_FOR_REVIEW - ) + await generateDeclarationStartedPoint(request.payload as fhir.Bundle, { + Authorization: request.headers.authorization, + 'x-correlation-id': request.headers['x-correlation-id'] + }) ) await writePoints(points) @@ -175,14 +163,10 @@ export async function sentNotificationHandler( }) ) points.push( - await generateDeclarationStartedPoint( - request.payload as fhir.Bundle, - { - Authorization: request.headers.authorization, - 'x-correlation-id': request.headers['x-correlation-id'] - }, - Events.INCOMPLETE - ) + await generateDeclarationStartedPoint(request.payload as fhir.Bundle, { + Authorization: request.headers.authorization, + 'x-correlation-id': request.headers['x-correlation-id'] + }) ) await writePoints(points) diff --git a/packages/metrics/src/features/registration/index.ts b/packages/metrics/src/features/registration/index.ts index a8f39b89e71..a77fde7d139 100644 --- a/packages/metrics/src/features/registration/index.ts +++ b/packages/metrics/src/features/registration/index.ts @@ -191,6 +191,24 @@ export interface IUserAuditBody { action: USER_ACTION | string additionalData?: Record } +export interface IUserModelData { + _id: string + username: string + name: { + use: string + family: string + given: string[] + }[] + email: string + emailForNotification?: string + mobile?: string + status: string + role: string + creationDate?: string + practitionerId: string + primaryOfficeId: string + device: string +} export interface IPaymentPoints { measurement: string tags: ILocationTags & { diff --git a/packages/metrics/src/features/registration/pointGenerator.test.ts b/packages/metrics/src/features/registration/pointGenerator.test.ts index 15f6049e7a0..f1ed2847411 100644 --- a/packages/metrics/src/features/registration/pointGenerator.test.ts +++ b/packages/metrics/src/features/registration/pointGenerator.test.ts @@ -22,15 +22,66 @@ import { testDeathCertPayload } from '@metrics/features/registration/testUtils' import { cloneDeep } from 'lodash' -import { Events } from '@metrics/features/metrics/constants' import * as api from '@metrics/api' +import { getUser } from '@metrics/features/audit/handler' +jest.mock('@metrics/features/audit/handler') const fetchLocation = api.fetchLocation as jest.Mock const fetchTaskHistory = api.fetchTaskHistory as jest.Mock +const fetchUser = getUser as jest.Mock const AUTH_HEADER = { - Authorization: 'Bearer mock-token' + Authorization: + 'Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzY29wZSI6WyJyZWNvcmQuZGVjbGFyZS1iaXJ0aCIsInJlY29yZC5kZWNsYXJlLWRlYXRoIiwicmVjb3JkLmRlY2xhcmUtbWFycmlhZ2UiLCJyZWNvcmQuZGVjbGFyYXRpb24tcmV2aWV3IiwicmVjb3JkLnN1Ym1pdC1mb3ItdXBkYXRlcyIsInJlY29yZC5yZXZpZXctZHVwbGljYXRlcyIsInJlY29yZC5kZWNsYXJhdGlvbi1hcmNoaXZlIiwicmVjb3JkLmRlY2xhcmF0aW9uLXJlaW5zdGF0ZSIsInJlY29yZC5yZWdpc3RlciIsInJlY29yZC5yZWdpc3RyYXRpb24tY29ycmVjdCIsInJlY29yZC5wcmludC1yZWNvcmRzIiwicmVjb3JkLnByaW50LXJlY29yZHMtc3VwcG9ydGluZy1kb2N1bWVudHMiLCJyZWNvcmQuZXhwb3J0LXJlY29yZHMiLCJyZWNvcmQucHJpbnQtaXNzdWUtY2VydGlmaWVkLWNvcGllcyIsInJlY29yZC5yZWdpc3RyYXRpb24tdmVyaWZ5LWNlcnRpZmllZC1jb3BpZXMiLCJyZWNvcmQuY3JlYXRlLWNvbW1lbnRzIiwicmVjb3JkLmNlcnRpZnkiLCJwZXJmb3JtYW5jZS5yZWFkIiwicGVyZm9ybWFuY2UucmVhZC1kYXNoYm9hcmRzIiwib3JnYW5pc2F0aW9uLnJlYWQiLCJvcmdhbmlzYXRpb24ucmVhZC1sb2NhdGlvbnM6bXktb2ZmaWNlIiwic2VhcmNoLmJpcnRoIiwic2VhcmNoLmRlYXRoIiwic2VhcmNoLm1hcnJpYWdlIiwicmVjb3JkLnJlYWQiLCJyZWNvcmQucmVhZC1hdWRpdCIsInJlY29yZC5yZWFkLWNvbW1lbnRzIiwiZGVtbyJdLCJpYXQiOjE3MzAxMTAzNTMsImV4cCI6MTczMDcxNTE1MywiYXVkIjpbIm9wZW5jcnZzOmF1dGgtdXNlciIsIm9wZW5jcnZzOnVzZXItbWdudC11c2VyIiwib3BlbmNydnM6aGVhcnRoLXVzZXIiLCJvcGVuY3J2czpnYXRld2F5LXVzZXIiLCJvcGVuY3J2czpub3RpZmljYXRpb24tdXNlciIsIm9wZW5jcnZzOndvcmtmbG93LXVzZXIiLCJvcGVuY3J2czpzZWFyY2gtdXNlciIsIm9wZW5jcnZzOm1ldHJpY3MtdXNlciIsIm9wZW5jcnZzOmNvdW50cnljb25maWctdXNlciIsIm9wZW5jcnZzOndlYmhvb2tzLXVzZXIiLCJvcGVuY3J2czpjb25maWctdXNlciIsIm9wZW5jcnZzOmRvY3VtZW50cy11c2VyIl0sImlzcyI6Im9wZW5jcnZzOmF1dGgtc2VydmljZSIsInN1YiI6IjY3MWY2MzBiMjNlY2Y4YmVjZTdlZDZmMSJ9.X-zLm4Kqq-J0WIGGR6HyvtB5plhFCPzRBCk1m8QLZ-6zd5j8ZDA8C19P4dOGmiTTN9Ya7IKIVLZE2XHQLDQrTGqltAYk6LOV0V7UIoWQzJutaA9DYIymQvSFo_MbMi9P1GDMwspFo_Dr17uScyz-7KjS3e4htdlKvHVyduijxYSj513FOzYT1CRAPxB-6wd7awz1MKVyjC3658R6nTS6MblMhZpwkU-RUsXr_AUnYL9UARpO_9JeMwK1Ijtzf51NqW6NZgVvFdk5sZ0wptWaiklw9MdWSOwBoLysyDdox52cccqvZodhG3N9GyQcn-TWivlL08FOcpxqfsCNrW3IpQ' +} + +const REGISTRATION_AGENT = { + name: [ + { + use: 'en', + given: ['Felix'], + family: 'Katongo' + } + ], + username: 'f.katongo', + role: 'REGISTRATION_AGENT' +} + +const FIELD_AGENT = { + name: [ + { + use: 'en', + given: ['Kalush'], + family: 'Bwalya' + } + ], + username: 'k.bwalya', + role: 'FIELD_AGENT' +} + +const REGISTRAR = { + name: [ + { + use: 'en', + given: ['Kennedy'], + family: 'Mweene' + } + ], + username: 'k.bwalya', + role: 'REGISTRAR' +} + +const RECORD_SEARCH_API = { + name: [ + { + use: 'en', + given: ['abc'], + family: 'def' + } + ], + username: 'a.def', + role: 'NOTIFICATION_API_USER' } export const location = { @@ -281,10 +332,10 @@ describe('Verify point generation', () => { ).rejects.toThrowError('Payment reconciliation not found') }) it('returns declarations started point for field agent', async () => { + fetchUser.mockResolvedValueOnce(FIELD_AGENT) const point = await generateDeclarationStartedPoint( cloneDeep(testDeclaration), - AUTH_HEADER, - Events.READY_FOR_REVIEW + AUTH_HEADER ) expect(point).toMatchObject({ measurement: 'declarations_started', @@ -301,10 +352,10 @@ describe('Verify point generation', () => { }) }) it('returns declarations started point for registration agent', async () => { + fetchUser.mockResolvedValueOnce(REGISTRATION_AGENT) const point = await generateDeclarationStartedPoint( cloneDeep(testDeclaration), - AUTH_HEADER, - Events.VALIDATED + AUTH_HEADER ) expect(point).toMatchObject({ measurement: 'declarations_started', @@ -321,10 +372,10 @@ describe('Verify point generation', () => { }) }) it('returns declarations started point for registrar', async () => { + fetchUser.mockResolvedValueOnce(REGISTRAR) const point = await generateDeclarationStartedPoint( cloneDeep(testDeclaration), - AUTH_HEADER, - Events.WAITING_EXTERNAL_VALIDATION + AUTH_HEADER ) expect(point).toMatchObject({ measurement: 'declarations_started', @@ -339,10 +390,10 @@ describe('Verify point generation', () => { }) }) it('returns declarations started point for field agent', async () => { + fetchUser.mockResolvedValueOnce(FIELD_AGENT) const point = await generateDeclarationStartedPoint( cloneDeep(testDeclaration), - AUTH_HEADER, - Events.INCOMPLETE + AUTH_HEADER ) expect(point).toMatchObject({ measurement: 'declarations_started', @@ -359,13 +410,13 @@ describe('Verify point generation', () => { }) }) it('returns declarations started point for notification api', async () => { + fetchUser.mockResolvedValueOnce(RECORD_SEARCH_API) const payload = cloneDeep(testDeclaration) payload.entry[0].resource.type!.coding[0].code = 'birth-notification' const point = await generateDeclarationStartedPoint( cloneDeep(payload), - AUTH_HEADER, - Events.INCOMPLETE + AUTH_HEADER ) expect(point).toMatchObject({ measurement: 'declarations_started', diff --git a/packages/metrics/src/features/registration/pointGenerator.ts b/packages/metrics/src/features/registration/pointGenerator.ts index 81a46f827d8..6fa27fccd4e 100644 --- a/packages/metrics/src/features/registration/pointGenerator.ts +++ b/packages/metrics/src/features/registration/pointGenerator.ts @@ -46,7 +46,6 @@ import { getRecordInitiator, getPaymentReconciliation, getObservationValueByCode, - isNotification, MANNER_OF_DEATH_CODE, CAUSE_OF_DEATH_CODE, getPractionerIdFromTask, @@ -65,12 +64,11 @@ import { getAgeLabel, getdaysAfterEvent } from '@metrics/features/registration/utils' -import { - OPENCRVS_SPECIFICATION_URL, - Events -} from '@metrics/features/metrics/constants' +import { OPENCRVS_SPECIFICATION_URL } from '@metrics/features/metrics/constants' import { fetchTaskHistory } from '@metrics/api' import { EVENT_TYPE } from '@metrics/features/metrics/utils' +import { getTokenPayload } from '@metrics/utils/authUtils' +import { getUser } from '@metrics/features/audit/handler' export const generateInCompleteFieldPoints = async ( payload: fhir.Bundle, @@ -503,11 +501,14 @@ export async function generateTimeLoggedPoint( export async function generateDeclarationStartedPoint( payload: fhir.Bundle, - authHeader: IAuthHeader, - status: string + authHeader: IAuthHeader ): Promise { const composition = getComposition(payload) const task = getTask(payload) + const tokenPayload = getTokenPayload(authHeader.Authorization) + + const userId = tokenPayload.sub + const user = await getUser(userId, authHeader) if (!composition) { throw new Error('composition not found') @@ -517,23 +518,7 @@ export async function generateDeclarationStartedPoint( throw new Error('Task not found') } - let role = '' - - if (status === Events.INCOMPLETE) { - isNotification(composition) - ? (role = 'NOTIFICATION_API_USER') - : (role = 'FIELD_AGENT') - } else if (status === Events.VALIDATED) { - role = 'REGISTRATION_AGENT' - } else if (status === Events.WAITING_EXTERNAL_VALIDATION) { - role = 'REGISTRAR' - } else if (status === Events.READY_FOR_REVIEW) { - role = 'FIELD_AGENT' - } - - if (role === '') { - throw new Error('Role not found') - } + const role = user.role const fields: IDeclarationsStartedFields = { role, diff --git a/packages/metrics/src/utils/authUtils.ts b/packages/metrics/src/utils/authUtils.ts index 6e7a703364a..2ea76fc7d27 100644 --- a/packages/metrics/src/utils/authUtils.ts +++ b/packages/metrics/src/utils/authUtils.ts @@ -10,12 +10,6 @@ */ import * as decode from 'jwt-decode' -export enum USER_SCOPE { - DECLARE = 'declare', - REGISTER = 'register', - CERTIFY = 'certify' -} - export interface ITokenPayload { sub: string exp: string