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

Re-implementing E-Signet flow to mosip repository #23

Merged
merged 22 commits into from
Jan 7, 2025
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
693c91f
feat: initial commit of re-implementing E-Signet flow to mosip reposi…
Dec 11, 2024
2aa2ea2
feat: set up the get-oidp-user-info endpoint and replicated GraphQL q…
PathumN99 Dec 12, 2024
4b67f2f
feat: E-Signet mock server initial commit
PathumN99 Dec 12, 2024
695007b
refactor: moved e-signet related methods to esignet-api.ts
PathumN99 Dec 13, 2024
aed6020
refactor: refactored get-oidp-user-info endpoint
PathumN99 Dec 13, 2024
4f4ddb7
feat: Form configuration changes
PathumN99 Dec 17, 2024
f9a5720
Some minor amends
euanmillar Dec 17, 2024
fe72e50
aligned fetch token to e-signet requirements and changed port
euanmillar Dec 17, 2024
8e9c98b
Add search params to authorise and comments for todos
euanmillar Dec 17, 2024
ef95c8f
Add note around search params
euanmillar Dec 17, 2024
fd610fd
feat: oidc/userinfo endpoint in esignet-mock server
PathumN99 Dec 18, 2024
ad5cb20
/authorize endpoint in esignet-mock server
PathumN99 Dec 19, 2024
7e0e4b1
Added @fastify/formbody plugin to accept x-www-form-urlencoded conten…
PathumN99 Dec 19, 2024
823cdc5
/esignet/get-oidp-user-info endpoint minor changes
PathumN99 Dec 20, 2024
993c8b2
Minor changes in fetch location from FHIR URL
PathumN99 Dec 30, 2024
720bbb1
generateSignedJwt issue fixed in get-oidp-user-info API
PathumN99 Dec 30, 2024
1175621
Refactor JWT and set up monorepo
euanmillar Jan 3, 2025
1825d50
Remove completed todos
euanmillar Jan 6, 2025
54ae37e
Fix conflicts
euanmillar Jan 6, 2025
9f0f475
remove .DS_Store and update gitignore
euanmillar Jan 7, 2025
9f68810
rename webhooks in a future PR
euanmillar Jan 7, 2025
8781067
Bump version number
euanmillar Jan 7, 2025
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
Binary file added .DS_Store
Binary file not shown.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
node_modules
node_modules
.secrets
.turbo
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 nice, please introduce Turbo to me!

7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,12 @@ This package ensures a secure and a robust integration between OpenCRVS and MOSI
## Development

```sh
# start the web server
cd packages/server
# start the server and all the mocked servers
yarn install
yarn dev

# optionally run MOSIP mock server
cd packages/mosip-mock
# optionally run an individual package
cd packages/*
yarn install
yarn dev
```
Expand Down
17 changes: 17 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "@opencrvs/mosip",
"version": "1.7.0-alpha.2",
"license": "MPL-2.0",
"private": true,
"packageManager": "yarn@1.0.0",
"workspaces": [
"packages/*"
],
"devDependencies": {
"turbo": "^2.3.3",
"typescript": "^5.6.3"
},
"scripts": {
"dev": "turbo run dev"
}
}
Binary file added packages/.DS_Store
euanmillar marked this conversation as resolved.
Show resolved Hide resolved
Binary file not shown.
205 changes: 124 additions & 81 deletions packages/country-config/src/forms.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,84 @@
// @TODO: Yet to be implemented! Placeholders for NPM publish.

/**
* E-Signet popup button form definition
* E-Signet popup button and hidden field form definitions
* @example
* ```
* [
* // ...other fields
* ...esignet({ url: "https://opencrvs-mosip-gateway.farajaland.opencrvs.org" })
* ]
* ```
*/

/**
*
* @description E-Signet REDIRECT button form definition. Calls E-Signet /authorize (this field may not be supported in the latest release of OpenCRVS yet)
*
*/
export const esignet = (esignetAuthUrl: string, openIdProviderClientId: string, openIdProviderClaims: string, fieldName: string, callbackFieldName: string) => {

const url = new URL(esignetAuthUrl)

url.searchParams.append(
'client_id',
openIdProviderClientId || 'mock-client_id'
)
url.searchParams.append('response_type', 'code')
url.searchParams.append('scope', 'openid profile')
url.searchParams.append('acr_values', 'mosip:idp:acr:static-code')
url.searchParams.append('claims', openIdProviderClaims || 'mock-claims')
url.searchParams.append('state', 'trigger-onmount')

/*

TODO: Understand from Tahmid about how to handle this:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tahmidrahman-dsi can you advise how we can send this state and callback URL to E-Signet using the REDIRECT button please?

Copy link
Collaborator

@tahmidrahman-dsi tahmidrahman-dsi Dec 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@euanmillar Current implementation saves no state. But can be saved if the requirement needs. Before developing, it would be great if I have a bit more understanding from my side.

client_id=$openIdProviderClientId
response_type='code'
scope='openid profile'
acr_values='mosip:idp:acr:static-code'
claims=$openIdProviderClaims

As per my understanding, the above parameters are appended as search query params to the redirect url (esignetAuthUrl). And when the redirection is completed, eSignet forwards user to the opencrvs form url with param authorized=true which causes calling the callback url esignetUserinfoUrl. But you need those params for calling the esignetUserinfoUrl. Isn't it?

If my understanding is correct, does putting those params inside redirectCallbackFetch field's options field solve the issue? Please let me know your thoughts.

cc: @naftis

Copy link
Contributor

@naftis naftis Dec 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are correct to this step:

As per my understanding, the above parameters are appended as search query params to the redirect url (esignetAuthUrl).

After that though, the above URL redirects back to OpenCRVS with

https://register.mosip.opencrvs.dev/drafts/1234-1234-1234-1234/informant?state=same-that-you-supplied&nonce=same-that-you-supplied&code=authorization_code&error_description=in_case_of_error_this_is_sent&error=error_code

The onmount-fetch should be able to be configured so that for example a ?code=.. exists. To the ?state=.. we can set anything that E-Signet sends us back as-is, so we could put something like "?state=trigger-onmount" there to be explicit. So yes, we can use these parameters instead of authorized=true. This code is then used to fetch the details from @opencrvs/mosip/server, which then again fetches the userinfo from E-Signet.

https://docs.esignet.io/integration/relying-party
The full flow described above is documented here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tahmidrahman-dsi @naftis my question was, how does the REDIRECT button know the draftId in order to format the state URL query param so that this works with mock-authorizer HTML which appears to need it like this: const params = new URLSearchParams(window.location.search); const opencrvsDraft = params.get("opencrvsDraft"); const opencrvsFormPath = "{{CLIENT_URL}}" + "/drafts/" + opencrvsDraft + "/events/birth/child/group/child-view-group"; const destinationURL = new URL(opencrvsFormPath); destinationURL.searchParams.set("authorized", true); window.location.replace(destinationURL);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


url.searchParams.append(
'redirect_uri',
`${window.location.origin}${OIDP_VERIFICATION_CALLBACK}`
)
const stateToBeSent: INidCallbackState = {
pathname: currentPathname,
declarationId: declarationId,
section: currentSection
}
url.searchParams.append('state', JSON.stringify(stateToBeSent))
*/
window.location.href = url.toString()

return {
name: fieldName,
validator: [],
icon: {
desktop: 'Globe',
mobile: 'Fingerprint'
},
type: "REDIRECT",
custom: true,
label: {
id: 'views.idReader.label.eSignet',
defaultMessage: 'E-signet'
},
hideInPreview: true,
conditionals: [
{
action: "disable",
expression: "!!$form.redirectCallbackFetch",
},
],
options: {
url: esignetAuthUrl,
callback: {
params: {
code: "esignet-mock-code",
},
trigger: callbackFieldName
},
},
};
};

export const popupButton = ({
/** URL to OpenCRVS-MOSIP gateway (e.g. https://opencrvs-mosip-gateway.farajaland.opencrvs.org) */
url
Expand All @@ -16,29 +92,63 @@ export const popupButton = ({
};
};

export const hidden = () => {
/**
*
* @description esignet callback button form definition. Calls server/esignet-api /esignet/get-oidp-user-info (this field may not be supported in the latest release of OpenCRVS yet)
*
*/

export const esignetCallback = ({
fieldName,
getOIDPUserInfoUrl,
openIdProviderClientId
}: {
fieldName: string;
getOIDPUserInfoUrl: string;
openIdProviderClientId: string;
}) => ({
name: fieldName,
type: 'HTTP',
custom: true,
label: {
id: 'form.field.label.empty',
defaultMessage: ''
},
validator: [],
options: {
getOIDPUserInfoUrl,
headers: {
'Content-type': 'application/json'
},
body: {
code: "esignet-mock-code",
clientId: openIdProviderClientId,
redirectUri: ""
},
method: 'POST'
}
});

export const returnExpression = (fieldName: string) => {
return {
dependsOn: ["redirectCallbackFetch"],
expression: `$form.redirectCallbackFetch?.data?.${fieldName}`,
};
};

export const esignetHidden = () => {
return {
name: 'INFORMANT_PSUT_TOKEN',
type: 'HIDDEN'
};
};

/**
* E-Signet popup button and hidden field form definitions
* @example
* ```
* [
* // ...other fields
* ...esignet({ url: "https://opencrvs-mosip-gateway.farajaland.opencrvs.org" })
* ]
* ```
*/

/**
*
* @description ID reader field definition (this field may not be supported in the latest release of OpenCRVS yet)
* @description QR reader type definition (this field may not be supported in the latest release of OpenCRVS yet)
*
*/

export const idReader = (
event: string,
sectionId: string,
Expand Down Expand Up @@ -73,76 +183,9 @@ export const idReader = (
};
};

/**
*
* @description QR reader type definition (this field may not be supported in the latest release of OpenCRVS yet)
*
*/
export const qr = () => ({
type: 'QR'
});

/**
*
* @description esignet reader type definition (this field may not be supported in the latest release of OpenCRVS yet)
*
*/
export const esignet = ({
url,
callbackFieldName
}: {
url: string;
callbackFieldName: string;
}) => ({
name: 'redirect',
validator: [],
icon: {
desktop: 'Globe',
mobile: 'Fingerprint'
},
type: 'REDIRECT',
label: {
id: 'views.idReader.label.eSignet',
defaultMessage: 'E-signet'
},
options: {
url,
callback: {
params: {
authorized: 'true'
},
trigger: callbackFieldName
}
}
});

export const esignetCallback = ({
fieldName,
url
}: {
fieldName: string;
url: string;
}) => ({
name: fieldName,
type: 'HTTP',
custom: true,
label: {
id: 'form.field.label.empty',
defaultMessage: ''
},
validator: [],
options: {
url,
headers: {
'Content-type': 'application/json'
},
method: 'GET'
}
});

// export const esignet = ({
// url
// }: {
// /** URL to OpenCRVS-MOSIP gateway (e.g. https://opencrvs-mosip-gateway.farajaland.opencrvs.org) */
// url: string;
// }) => [popupButton({ url }), hidden()];
9 changes: 9 additions & 0 deletions packages/esignet-mock/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM node:23.1.0
WORKDIR /usr/src/app

COPY package.json package.json
COPY yarn.lock yarn.lock
RUN yarn install --production --frozen-lockfile
COPY src/ src/

CMD ["yarn", "start"]
11 changes: 11 additions & 0 deletions packages/esignet-mock/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# esignet-mock

This simple mock is used to simulate the ESIGNET application & API for local development. It simulates the eSignet flow by redirecting you instantly back to the redirect_uri and allows fecthing of user info once authenticated.

## Usage

1. Run the mock server:

```bash
yarn start
```
21 changes: 21 additions & 0 deletions packages/esignet-mock/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "@opencrvs/esignet-mock",
"license": "MPL-2.0",
"version": "1.7.0-alpha.2",
"main": "index.js",
"scripts": {
"dev": "NODE_ENV=development tsx watch src/index.ts",
"start": "NODE_ENV=production tsx src/index.ts"
},
"dependencies": {
"@fastify/formbody": "^8.0.1",
"@fastify/static": "^8.0.3",
"@types/node": "^22.4.1",
"envalid": "^8.0.0",
"fastify": "^5.0.0",
"jose": "^5.9.6",
"jsonwebtoken": "^9.0.2",
"tsx": "^4.19.2",
"typescript": "^5.6.3"
}
}
7 changes: 7 additions & 0 deletions packages/esignet-mock/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { cleanEnv, port, str, url } from "envalid";

export const env = cleanEnv(process.env, {
PORT: port({ default: 20260 }),
HOST: str({ default: "0.0.0.0", devDefault: "localhost" }),
CLIENT_URL: url({ devDefault: "http://localhost:3000" }),
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
});
});

hmmm have I might have forgot to install Prettier to this repo. I can action on that

1 change: 1 addition & 0 deletions packages/esignet-mock/src/dev-secrets/jwk.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
eyJwIjoiX2lyZGtxaWJUQXk0ZjR0R2pnYUJsVzNtbXJIakZFb1hYTnEzdlN4UUZSLWFkU2dHQjN3eWQzckJxZkZKOFhZY1B5YVpnZy1kLTNxXzhscEVHel9rZ2VDNkFsRFFfd0gwSDFrSVV2Y0NueHAxR0hTMXF2YWFSWThQQUFTbkVyVzM5Tm9GR3JFblhOVFo5cHpwRHZ3WEwtcERkU3V4T2dSRXhkekdna0dDVURzIiwia3R5IjoiUlNBIiwicSI6InYySUp4NEV3VjdudXYzSFdpMjBqdkcyWm5SdnQtOENfRWgyOXp6NWJTM1NUeDlsUGw4dlNyZWJmVzVoRjJuZ1o3eW94NEF4R2h3UjZtdFo0SEJpcVR3RjQ3U28yMGdBeENpa1FadG9CSG1TdFpyTVo3Z011cGVDdXh3WHNhc0prRGdrVks5RmJ5UzFzdk1aTzRaWTdxVHhsZ3pRWl9uRi1ERGpRenN4OGpOTSIsImQiOiJYbHBKY28zTjZfd0F6UE4wZEliRjkxR0xLT2J5NVN5WVFjTWQ0eUV6ZkVYa2xKWU9nc1N1TTBiTTloU0loVU50U1JTV1B6U045aXBqb3RQc29yZExMa25qTkY3aWw2NGtkQVZaSTA4SnVjWXVHSDNVZ3ppTDB3WkNnNnBDRnd4NzlPVDkwU0xxTl9GV3NyNllYWXJIbDhiRHNENTBQSTlCaUgzTmdRTEx3SWowcFdLXzZ0OFhoZ3BJUElON2ZwdF9oQkVCTkNYenpUbEtoMHVBYTZ0VHd6eHdEOHlHWjB5LU42VVlYbkJoLUwzREZ4UWdzQ09zU050ZHhlQVBFRHBzeU1zTzRhU3FyLWxxZVpCTWI5MHR0T3FoczJ0UTlzVXRKcklaMy15WVJCN2N3VUg5TkVzQzV4UjIzZm5rSHFZbk5BYXd3a2Q1WDlSTXlrYU5saWpXN1EiLCJlIjoiQVFBQiIsInVzZSI6InNpZyIsInFpIjoic2JQWVpPNDdRbG04NXRWR19INDJfdjIxcDVQNUVJeEtBZmNLd3RfZDR1UW84MXNpa3VmVTg3MDkzaTA1R2RXeTUzUXo3bkgyNkItdGliN3p0bHVSUXhEdV9uaEpTUm9XRnk2M3pRLUVrd3picmFGN2xSZXRYZjYxcGZjNy1XTFJTcWpPa1J3LVc0VGROdURmdWxicGNyUFBURVNQS0w4ZVpNMzdoU0JnMTFrIiwiZHAiOiJaWllZYlUtNmtsRGJSUThUcWh4cm1xQU1kWFA0QU5vRU1IYXl6WWR5a3A3SkMxNXQybndIWjczR3ZmZlV5QS1mQnBhVThHanhpZHZyOEItbjdRUkNmcTZsYWR1SFRRVW8zNGFrVHhTdkZZeHJsTlRBNzQ0VTV3eGd3Rzhibjl6Rm83V29LSno1MEo2Nloxa2J4WU40ZVF3MjNoUVNoOF9BOVJ4aXN4a2Z6cFUiLCJhbGciOiJSUzI1NiIsImRxIjoiS3phcDJxQnpGX3ZseXRpYmh4UDNzNzVUcDBQOU1wRk5FM3dmdGNId3YxTHRZM3pRR2dodDQ5SnpzS3pyYU84aGVfMWRFYWZ6N055NENtcE82Smt4SzNHN3FtR1R5MHM3eDMyS05JOFpIWkhDRGFSM1FHVDdqWHowT2dBLVo4VEk3dHBpSzJpMGZ2S0EwUWMtSEhYZHR0THFZUHZNdXNWSm50emRXVFNYRXYwIiwibiI6InZnTlJidE5RWnVpLXNSdlZ2YTNnb1FvWXRGRXVTNV9wOWZrZk5WbjFYRDdvU1VZVFg3U2RiS0wyNzlTUTJqZDZubzUxRVY0aHpOck1OV1Q2Ylh4UVVGZGNqVFJrQlBJcDhnOFkyd0ZTWkhSa1ZiYUUxNTBnU2VZUmZwb1dYZTVDMURyMUR3WGZCOExCb0liREd6d3FCdnpfcDJpT3dQWXM2bkxKeEV1bWdWcUYtZVc3N09LVzZFdl9KSG02U28wUmVYVHFlakpqQlF4eGFlMFA3akFPcTFOU3lobTBYTG5XQ08yNlpvM0RMUklrb1hPWlZZQl9peGkyLUtsUF9uVnFRajF4VEtGWmpWSURIcE5WVnJmX2YxZUpvRkYyQklJTXNfWXpYUWxoRTZHbGkwTmJwanhmX21LWVVJS1I4eE4zY1VQbVItMjhjRnl0eHV1M0l0eGtvUSJ9Cg==
Loading
Loading