Skip to content

Commit

Permalink
feat: informant verification (#43)
Browse files Browse the repository at this point in the history
* feat: initial informant verification

* fix: change field value to be "verified" on success

* feat: add handler for approval action

* chore: bump up version

---------

Co-authored-by: Tameem Bin Haider <haidertameem@gmail.com>
  • Loading branch information
naftis and Zangetsu101 authored Jan 29, 2025
1 parent b6ba27c commit ba137ef
Show file tree
Hide file tree
Showing 14 changed files with 194 additions and 24 deletions.
18 changes: 9 additions & 9 deletions docs/playground.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
},
{
"cell_type": "code",
"execution_count": 1,
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
Expand All @@ -21,7 +21,7 @@
},
{
"cell_type": "code",
"execution_count": 5,
"execution_count": 8,
"metadata": {},
"outputs": [
{
Expand All @@ -35,8 +35,8 @@
"source": [
"import requests\n",
"\n",
"url = \"http://localhost:2024/webhooks/opencrvs\"\n",
"token = \"your_token_here\"\n",
"url = \"http://localhost:2024/events/registration\"\n",
"token = \"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzY29wZSI6WyJyZWdpc3RlciIsInBlcmZvcm1hbmNlIiwiY2VydGlmeSIsImRlbW8iXSwiaWF0IjoxNzM3NDcwNjI1LCJleHAiOjE3MzgwNzU0MjUsImF1ZCI6WyJvcGVuY3J2czphdXRoLXVzZXIiLCJvcGVuY3J2czp1c2VyLW1nbnQtdXNlciIsIm9wZW5jcnZzOmhlYXJ0aC11c2VyIiwib3BlbmNydnM6Z2F0ZXdheS11c2VyIiwib3BlbmNydnM6bm90aWZpY2F0aW9uLXVzZXIiLCJvcGVuY3J2czp3b3JrZmxvdy11c2VyIiwib3BlbmNydnM6c2VhcmNoLXVzZXIiLCJvcGVuY3J2czptZXRyaWNzLXVzZXIiLCJvcGVuY3J2czpjb3VudHJ5Y29uZmlnLXVzZXIiLCJvcGVuY3J2czp3ZWJob29rcy11c2VyIiwib3BlbmNydnM6Y29uZmlnLXVzZXIiLCJvcGVuY3J2czpkb2N1bWVudHMtdXNlciJdLCJpc3MiOiJvcGVuY3J2czphdXRoLXNlcnZpY2UiLCJzdWIiOiI2NzRkZTAzMGY4YzBhMWMxMmVmODBjODcifQ.BViXNILaE8aEKEXdb46gWGuuIarwxAMCY1hKM7lO6X3p7vcM7VfarPu36usM3Ca0AygOVIYwxZ5wEsJwAng1F10FSYBnu1G8vlk1nB99vqZa5_9Q0p-2lyfHkjFEOsusFjU1z7uTZ53VYJ_EsLwv6ClSF9slr4SxUL5486xC8mG9MuJpvKyGCPt9yPvfUyEX41PImrReMHJLgnE4S74bQW-B8CH2gi_CnZBGmYewljXF1Wf8AQgHqXfpTMO8M7mP947x3CMgdZVaRkd9mycsoPQCKVyH_P8kCjobwZxgPmmMAr9yfXfWGCVJvxQSJVNlpzcPpR9uygdl14IGn_eiQA\"\n",
"headers = {\"Authorization\": f\"Bearer {token}\"}\n",
"response = requests.post(url, json=event, headers=headers)\n",
"print(response.status_code)\n"
Expand All @@ -51,7 +51,7 @@
},
{
"cell_type": "code",
"execution_count": 6,
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
Expand All @@ -63,7 +63,7 @@
},
{
"cell_type": "code",
"execution_count": 8,
"execution_count": 10,
"metadata": {},
"outputs": [
{
Expand All @@ -77,8 +77,8 @@
"source": [
"import requests\n",
"\n",
"url = \"http://localhost:2024/webhooks/opencrvs\"\n",
"token = \"your_token_here\"\n",
"url = \"http://localhost:2024/events/registration\"\n",
"token = \"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzY29wZSI6WyJyZWdpc3RlciIsInBlcmZvcm1hbmNlIiwiY2VydGlmeSIsImRlbW8iXSwiaWF0IjoxNzM3NDcwNjI1LCJleHAiOjE3MzgwNzU0MjUsImF1ZCI6WyJvcGVuY3J2czphdXRoLXVzZXIiLCJvcGVuY3J2czp1c2VyLW1nbnQtdXNlciIsIm9wZW5jcnZzOmhlYXJ0aC11c2VyIiwib3BlbmNydnM6Z2F0ZXdheS11c2VyIiwib3BlbmNydnM6bm90aWZpY2F0aW9uLXVzZXIiLCJvcGVuY3J2czp3b3JrZmxvdy11c2VyIiwib3BlbmNydnM6c2VhcmNoLXVzZXIiLCJvcGVuY3J2czptZXRyaWNzLXVzZXIiLCJvcGVuY3J2czpjb3VudHJ5Y29uZmlnLXVzZXIiLCJvcGVuY3J2czp3ZWJob29rcy11c2VyIiwib3BlbmNydnM6Y29uZmlnLXVzZXIiLCJvcGVuY3J2czpkb2N1bWVudHMtdXNlciJdLCJpc3MiOiJvcGVuY3J2czphdXRoLXNlcnZpY2UiLCJzdWIiOiI2NzRkZTAzMGY4YzBhMWMxMmVmODBjODcifQ.BViXNILaE8aEKEXdb46gWGuuIarwxAMCY1hKM7lO6X3p7vcM7VfarPu36usM3Ca0AygOVIYwxZ5wEsJwAng1F10FSYBnu1G8vlk1nB99vqZa5_9Q0p-2lyfHkjFEOsusFjU1z7uTZ53VYJ_EsLwv6ClSF9slr4SxUL5486xC8mG9MuJpvKyGCPt9yPvfUyEX41PImrReMHJLgnE4S74bQW-B8CH2gi_CnZBGmYewljXF1Wf8AQgHqXfpTMO8M7mP947x3CMgdZVaRkd9mycsoPQCKVyH_P8kCjobwZxgPmmMAr9yfXfWGCVJvxQSJVNlpzcPpR9uygdl14IGn_eiQA\"\n",
"headers = {\"Authorization\": f\"Bearer {token}\"}\n",
"response = requests.post(url, json=event, headers=headers)\n",
"print(response.status_code)"
Expand All @@ -101,7 +101,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.10"
"version": "3.11.11"
}
},
"nbformat": 4,
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@opencrvs/mosip",
"version": "1.7.0-alpha.15",
"version": "1.7.0-alpha.16",
"license": "MPL-2.0",
"private": true,
"packageManager": "yarn@1.22.13",
Expand Down
2 changes: 1 addition & 1 deletion packages/country-config/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@opencrvs/mosip",
"version": "1.7.0-alpha.15",
"version": "1.7.0-alpha.16",
"license": "MPL-2.0",
"main": "./build/index.js",
"exports": {
Expand Down
54 changes: 50 additions & 4 deletions packages/country-config/src/events.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,70 @@
import type * as Hapi from "@hapi/hapi";
import fetch from "node-fetch";

/**
* Replaces event registration handler in country config
*/
export const mosipRegistrationHandler = ({ url }: { url: string }) =>
(async (request: Hapi.Request, h: Hapi.ResponseToolkit) => {
const OPENCRVS_MOSIP_GATEWAY_URL = new URL("./webhooks/opencrvs", url);
const MOSIP_API_REGISTRATION_EVENT_URL = new URL(
"./events/registration",
url,
);

const response = await fetch(OPENCRVS_MOSIP_GATEWAY_URL, {
const response = await fetch(MOSIP_API_REGISTRATION_EVENT_URL, {
method: "POST",
body: JSON.stringify(request.payload),
headers: request.headers,
headers: {
...request.headers,
"content-type": "application/json",
},
});

if (!response.ok) {
return h
.response({
error: "OpenCRVS-MOSIP gateway did not return a 200",
error: "OpenCRVS-MOSIP event registration route did not return a 200",
response: await response.text(),
})
.code(500);
}

return h.response({ success: true }).code(200);
}) satisfies Hapi.ServerRoute["handler"];

/**
* Replaces `/events/{event}/actions/sent-notification-for-review` handler in country config
*/
export const mosipRegistrationForReviewHandler = ({ url }: { url: string }) =>
(async (request: Hapi.Request, h: Hapi.ResponseToolkit) => {
// Corresponds to `packages/mosip-api` /events/review -route
const MOSIP_API_REVIEW_EVENT_URL = new URL("./events/review", url);

console.info(request.headers);

const response = await fetch(MOSIP_API_REVIEW_EVENT_URL, {
method: "POST",
body: JSON.stringify(request.payload),
headers: {
...request.headers,
"content-type": "application/json",
},
});

if (!response.ok) {
return h
.response({
error: "OpenCRVS-MOSIP event review route did not return a 200",
response: await response.text(),
})
.code(500);
}

return h.response({ success: true }).code(200);
}) satisfies Hapi.ServerRoute["handler"];

/**
* Replaces `/events/{event}/actions/sent-for-approval` handler in country config
* Currently the same as `/events/{event}/actions/sent-notification-for-review`
*/
export const mosipRegistrationForApprovalHandler = mosipRegistrationForReviewHandler
2 changes: 1 addition & 1 deletion packages/esignet-mock/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@opencrvs/esignet-mock",
"license": "MPL-2.0",
"version": "1.7.0-alpha.15",
"version": "1.7.0-alpha.16",
"main": "index.js",
"scripts": {
"dev": "NODE_ENV=development tsx watch src/index.ts",
Expand Down
2 changes: 1 addition & 1 deletion packages/mosip-api/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@opencrvs/mosip-api",
"version": "1.7.0-alpha.15",
"version": "1.7.0-alpha.16",
"license": "MPL-2.0",
"scripts": {
"dev": "NODE_ENV=development tsx watch src/index.ts",
Expand Down
20 changes: 16 additions & 4 deletions packages/mosip-api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@ import {
validatorCompiler,
ZodTypeProvider,
} from "fastify-type-provider-zod";
import { mosipHandler, mosipNidSchema } from "./webhooks/mosip";
import { opencrvsHandler, opencrvsRecordSchema } from "./webhooks/opencrvs";
import { mosipHandler, mosipNidSchema } from "./routes/mosip";
import {
registrationEventHandler,
opencrvsRecordSchema,
} from "./routes/event-registration";
import { env } from "./constants";
import * as openapi from "./openapi-documentation";
import { getOIDPUserInfo, OIDPUserInfoSchema } from "./esignet-api";
import formbody from "@fastify/formbody";
import { reviewEventHandler } from "./routes/event-review";

const envToLogger = {
development: {
Expand Down Expand Up @@ -38,9 +42,17 @@ app.setErrorHandler((error, request, reply) => {

app.after(() => {
app.withTypeProvider<ZodTypeProvider>().route({
url: "/webhooks/opencrvs",
url: "/events/registration",
method: "POST",
handler: registrationEventHandler,
schema: {
body: opencrvsRecordSchema,
},
});
app.withTypeProvider<ZodTypeProvider>().route({
url: "/events/review",
method: "POST",
handler: opencrvsHandler,
handler: reviewEventHandler,
schema: {
body: opencrvsRecordSchema,
},
Expand Down
5 changes: 5 additions & 0 deletions packages/mosip-api/src/mosip-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,8 @@ export const deactivateNid = async ({ nid }: { nid: string }) => {

return response;
};

export const verifyNid = async ({ nid: _nid }: { nid: string }) => {
// @TODO: Implement verification via mock MOSIP API & actual MOSIP API
return true;
};
16 changes: 16 additions & 0 deletions packages/mosip-api/src/opencrvs-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,19 @@ export const upsertRegistrationIdentifier = (
},
headers,
});

export const updateField = (
id: string,
fieldId: string,
valueString: string,
{ headers }: { headers: Record<string, any> },
) =>
post({
query: /* GraphQL */ `
mutation updateField($id: ID!, $details: UpdateFieldInput!) {
updateField(id: $id, details: $details)
}
`,
variables: { id, details: { fieldId, valueString } },
headers,
});
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ type OpenCRVSRequest = FastifyRequest<{
}>;

/** Handles the calls coming from OpenCRVS countryconfig */
export const opencrvsHandler = async (
export const registrationEventHandler = async (
request: OpenCRVSRequest,
reply: FastifyReply,
) => {
Expand Down
38 changes: 38 additions & 0 deletions packages/mosip-api/src/routes/event-review.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { FastifyRequest, FastifyReply } from "fastify";
import { getComposition, getInformantType } from "../types/fhir";
import { updateField } from "../opencrvs-api";

type OpenCRVSRequest = FastifyRequest<{
Body: fhir3.Bundle;
}>;

export const reviewEventHandler = async (
request: OpenCRVSRequest,
reply: FastifyReply,
) => {
const informantType = getInformantType(request.body);
const { id: eventId } = getComposition(request.body);

if (!request.headers.authorization) {
return reply.code(401).send({ error: "Authorization header is missing" });
}

const token = request.headers.authorization.split(" ")[1];
if (!token) {
return reply
.code(401)
.send({ error: "Token is missing in Authorization header" });
}

// Initial test of the verification, we will verify only other informants than mother and father
if (informantType !== "mother" && informantType !== "father") {
await updateField(
eventId,
`birth.informant.informant-view-group.verified`,
'verified',
{ headers: { Authorization: `Bearer ${token}` } },
);
}

return reply.code(200).send({ success: true });
};
File renamed without changes.
55 changes: 54 additions & 1 deletion packages/mosip-api/src/types/fhir.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copypasted types from @opencrvs/commons
// In 1.8.0 we'll move importing these from @opencrvs/toolkit
// For this reason, here are shortcuts and `!` assertions, as we haven't copypasted ALL types from @opencrvs/commons

declare const __nominal__type: unique symbol;
export type Nominal<Type, Identifier extends string> = Type & {
Expand Down Expand Up @@ -507,3 +507,56 @@ export const getDeceasedNid = (bundle: fhir3.Bundle) => {
const deceased = findDeceasedEntry(composition, bundle);
return getPatientNationalId(deceased as fhir3.Patient);
};

export function findCompositionSection<T extends fhir3.Composition>(
code: string,
composition: T,
) {
return composition.section!.find((section) =>
section.code!.coding!.some((coding) => coding.code === code),
);
}

export function resourceIdentifierToUUID(
resourceIdentifier: ResourceIdentifier,
) {
const urlParts = resourceIdentifier.split("/");
return urlParts[urlParts.length - 1] as UUID;
}

export type URNReference = `urn:uuid:${UUID}`;

export function isURNReference(id: string): id is URNReference {
return id.startsWith("urn:uuid:");
}

export function isSaved<T extends Resource>(resource: T) {
return resource.id !== undefined;
}

export function findEntryFromBundle(
bundle: fhir3.Bundle,
reference: fhir3.Reference["reference"],
) {
return isURNReference(reference!)
? bundle.entry!.find((entry) => entry.fullUrl === reference)
: bundle.entry!.find(
(entry) =>
isSaved(entry.resource!) &&
entry.resource!.id ===
resourceIdentifierToUUID(reference as ResourceIdentifier),
);
}

export function getInformantType(record: fhir3.Bundle) {
const compositionSection = findCompositionSection(
"informant-details",
getComposition(record),
);
if (!compositionSection) return undefined;
const personSectionEntry = compositionSection.entry![0];
const personEntry = findEntryFromBundle(record, personSectionEntry.reference);

return (personEntry?.resource as fhir3.RelatedPerson).relationship
?.coding?.[0].code;
}
2 changes: 1 addition & 1 deletion packages/mosip-mock/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@opencrvs/mosip-mock",
"version": "1.7.0-alpha.15",
"version": "1.7.0-alpha.16",
"license": "MPL-2.0",
"scripts": {
"dev": "NODE_ENV=development tsx watch src/index.ts",
Expand Down

0 comments on commit ba137ef

Please sign in to comment.