Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: informant verification #43

Merged
merged 6 commits into from
Jan 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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": "[email protected]",
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 });
};
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