diff --git a/infrastructure/docker-compose.deploy.yml b/infrastructure/docker-compose.deploy.yml index 5db14d744..93543df3e 100644 --- a/infrastructure/docker-compose.deploy.yml +++ b/infrastructure/docker-compose.deploy.yml @@ -581,6 +581,11 @@ services: - INFOBIP_API_KEY=${INFOBIP_API_KEY:-} - INFOBIP_SENDER_ID=${INFOBIP_SENDER_ID:-} - DOMAIN={{hostname}} + - ESIGNET_TOKEN_URL=${ESIGNET_TOKEN_URL:-} + - OPENID_PROVIDER_CLIENT_ID=${OPENID_PROVIDER_CLIENT_ID:-} + - OPENID_PROVIDER_CLAIMS=${OPENID_PROVIDER_CLAIMS:-} + - ESIGNET_USERINFO_URL=${ESIGNET_USERINFO_URL:-} + - LOCALE=en networks: - overlay_net logging: diff --git a/infrastructure/docker-compose.development-deploy.yml b/infrastructure/docker-compose.development-deploy.yml index 10450eb28..574fe0023 100644 --- a/infrastructure/docker-compose.development-deploy.yml +++ b/infrastructure/docker-compose.development-deploy.yml @@ -28,6 +28,10 @@ services: - SMTP_USERNAME=${SMTP_USERNAME} - SMTP_PASSWORD=${SMTP_PASSWORD} - SMTP_SECURE=${SMTP_SECURE} + - ESIGNET_TOKEN_URL=${ESIGNET_TOKEN_URL:-} + - OPENID_PROVIDER_CLIENT_ID=${OPENID_PROVIDER_CLIENT_ID:-} + - OPENID_PROVIDER_CLAIMS=${OPENID_PROVIDER_CLAIMS:-} + - ESIGNET_USERINFO_URL=${ESIGNET_USERINFO_URL:-} deploy: replicas: 1 networks: diff --git a/infrastructure/docker-compose.qa-deploy.yml b/infrastructure/docker-compose.qa-deploy.yml index 68a315434..561960c01 100644 --- a/infrastructure/docker-compose.qa-deploy.yml +++ b/infrastructure/docker-compose.qa-deploy.yml @@ -57,6 +57,10 @@ services: - SMTP_USERNAME=${SMTP_USERNAME} - SMTP_PASSWORD=${SMTP_PASSWORD} - SMTP_SECURE=${SMTP_SECURE} + - ESIGNET_TOKEN_URL=${ESIGNET_TOKEN_URL:-} + - OPENID_PROVIDER_CLIENT_ID=${OPENID_PROVIDER_CLIENT_ID:-} + - OPENID_PROVIDER_CLAIMS=${OPENID_PROVIDER_CLAIMS:-} + - ESIGNET_USERINFO_URL=${ESIGNET_USERINFO_URL:-} deploy: replicas: 1 networks: diff --git a/infrastructure/docker-compose.staging-deploy.yml b/infrastructure/docker-compose.staging-deploy.yml index dab5ee159..99cb9b3a4 100644 --- a/infrastructure/docker-compose.staging-deploy.yml +++ b/infrastructure/docker-compose.staging-deploy.yml @@ -107,6 +107,10 @@ services: - SMTP_USERNAME=${SMTP_USERNAME} - SMTP_PASSWORD=${SMTP_PASSWORD} - SMTP_SECURE=${SMTP_SECURE} + - ESIGNET_TOKEN_URL=${ESIGNET_TOKEN_URL:-} + - OPENID_PROVIDER_CLIENT_ID=${OPENID_PROVIDER_CLIENT_ID:-} + - OPENID_PROVIDER_CLAIMS=${OPENID_PROVIDER_CLAIMS:-} + - ESIGNET_USERINFO_URL=${ESIGNET_USERINFO_URL:-} deploy: replicas: 1 diff --git a/package.json b/package.json index 368b7aba2..41e7faada 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "@hapi/boom": "^9.1.1", "@hapi/hapi": "^20.0.1", "@hapi/inert": "^6.0.3", - "@opencrvs/mosip": "^1.7.0-alpha.9", + "@opencrvs/mosip": "^1.7.0-alpha.10", "@opencrvs/toolkit": "0.0.6-events", "@types/chalk": "^2.2.0", "@types/csv2json": "^1.4.0", @@ -122,4 +122,4 @@ "minimist": "^1.2.2", "acorn": "^6.4.1" } -} +} \ No newline at end of file diff --git a/src/api/certificates/source/birth-certificate.svg b/src/api/certificates/source/birth-certificate.svg index 506461702..5de954737 100644 --- a/src/api/certificates/source/birth-certificate.svg +++ b/src/api/certificates/source/birth-certificate.svg @@ -62,6 +62,8 @@ + NID + {{ childIdentifier }} Registrar / L'Officier de l'État Civil diff --git a/src/constants.ts b/src/constants.ts index fecca17fc..ad48df188 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -30,3 +30,9 @@ export const CHECK_INVALID_TOKEN = env.CHECK_INVALID_TOKEN export const PRODUCTION = env.isProd export const QA_ENV = env.QA_ENV + +// e-signet +export const ESIGNET_TOKEN_URL = env.ESIGNET_TOKEN_URL +export const OPENID_PROVIDER_CLIENT_ID = env.OPENID_PROVIDER_CLIENT_ID +export const OPENID_PROVIDER_CLAIMS = env.OPENID_PROVIDER_CLAIMS +export const ESIGNET_USERINFO_URL = env.ESIGNET_USERINFO_URL diff --git a/src/environment.ts b/src/environment.ts index 1a084ab61..701aff0f5 100644 --- a/src/environment.ts +++ b/src/environment.ts @@ -30,5 +30,9 @@ export const env = cleanEnv(process.env, { CONFIRM_REGISTRATION_URL: url({ devDefault: 'http://localhost:5050/confirm/registration' }), - QA_ENV: bool({ default: false }) + QA_ENV: bool({ default: false }), + ESIGNET_TOKEN_URL: url({ devDefault: '' }), + OPENID_PROVIDER_CLIENT_ID: str({ devDefault: '' }), + OPENID_PROVIDER_CLAIMS: str({ devDefault: '' }), + ESIGNET_USERINFO_URL: url({ devDefault: '' }) }) diff --git a/src/form/birth/custom-fields.ts b/src/form/birth/custom-fields.ts new file mode 100644 index 000000000..c296c696f --- /dev/null +++ b/src/form/birth/custom-fields.ts @@ -0,0 +1,36 @@ +import { getCustomFieldMapping } from '@countryconfig/utils/mapping/field-mapping-utils' +import { formMessageDescriptors } from '../common/messages' +import { Conditional, SerializedFormField } from '../types/types' +import { genderOptions } from '../common/select-options' + +/** + * + * @param event + * @param sectionId + * @returns hidden field to store QR scanned data + */ + +/** To bypass config validation */ +export function getGenderCustom( + event: string, + sectionId: string, + conditionals: Conditional[], + initialValue: { dependsOn: string[]; expression: string } | string = '' +) { + const fieldName: string = 'gender' + const fieldId: string = `${event}.${sectionId}.${sectionId}-view-group.${fieldName}` + return { + name: fieldName, + type: 'SELECT_WITH_OPTIONS', + customQuestionMappingId: fieldId, + custom: true, + label: formMessageDescriptors.sex, + required: false, + initialValue, + conditionals, + validator: [], + placeholder: formMessageDescriptors.formSelectPlaceholder, + mapping: getCustomFieldMapping(fieldId), + options: genderOptions + } satisfies SerializedFormField +} diff --git a/src/form/birth/index.ts b/src/form/birth/index.ts index 3a3906a0f..a8f67835a 100644 --- a/src/form/birth/index.ts +++ b/src/form/birth/index.ts @@ -9,7 +9,7 @@ * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. */ -import { Event, ISerializedForm } from '../types/types' +import { Event, ISerializedForm, SerializedFormField } from '../types/types' import { formMessageDescriptors } from '../common/messages' import { informantType } from './required-fields' import { @@ -79,6 +79,20 @@ import { getSectionMapping } from '@countryconfig/utils/mapping/section/birth/ma import { getCommonSectionMapping } from '@countryconfig/utils/mapping/field-mapping-utils' import { getReasonForLateRegistration } from '../custom-fields' import { getIDNumberFields, getIDType } from '../custom-fields' +import { + esignet, + esignetCallback, + idReader, + idVerificationFields, + qr +} from '@opencrvs/mosip' +import { getGenderCustom } from './custom-fields' +import { + ESIGNET_TOKEN_URL, + ESIGNET_USERINFO_URL, + OPENID_PROVIDER_CLAIMS, + OPENID_PROVIDER_CLIENT_ID +} from '@countryconfig/constants' // import { createCustomFieldExample } from '../custom-fields' // ======================= FORM CONFIGURATION ======================= @@ -219,20 +233,68 @@ export const birthForm: ISerializedForm = { fields: [ informantType, // Required field. otherInformantType(Event.Birth), // Required field. + idReader( + 'birth', + 'informant', + informantFirstNameConditionals + .concat(hideIfInformantMotherOrFather) + .concat({ + action: 'hide', + expression: '!!$form?.verified' + }), + [ + qr(), + esignet( + 'birth', + 'informant', + ESIGNET_TOKEN_URL, + OPENID_PROVIDER_CLIENT_ID, + OPENID_PROVIDER_CLAIMS, + 'esignet', + 'esignetCallback' + ) + ] + ) as SerializedFormField, + esignetCallback({ + fieldName: 'esignetCallback', + event: 'birth', + sectionId: 'informant', + getOIDPUserInfoUrl: ESIGNET_USERINFO_URL, + openIdProviderClientId: OPENID_PROVIDER_CLIENT_ID + }) as SerializedFormField, + ...(idVerificationFields( + 'birth', + 'informant' + ) as SerializedFormField[]), getFirstNameField( 'informantNameInEnglish', informantFirstNameConditionals.concat( hideIfInformantMotherOrFather ), - certificateHandlebars.informantFirstName - ), // Required field. + certificateHandlebars.informantFirstName, + { + dependsOn: ['idReader', 'esignetCallback'], + expression: + '$form?.idReader?.firstName || $form?.esignetCallback?.data?.firstName || ""' + } + ), // Required field. In Farajaland, we have built the option to integrate with MOSIP. So we have different conditionals for each name to check MOSIP responses. You could always refactor firstNamesEng for a basic setup getFamilyNameField( 'informantNameInEnglish', informantFamilyNameConditionals.concat( hideIfInformantMotherOrFather ), - certificateHandlebars.informantFamilyName + certificateHandlebars.informantFamilyName, + { + dependsOn: ['idReader', 'esignetCallback'], + expression: + '$form?.idReader?.familyName || $form?.esignetCallback?.data?.familyName || ""' + } ), // Required field. + getGenderCustom('birth', 'informant', [], { + dependsOn: ['idReader', 'esignetCallback'], + expression: + '$form?.idReader?.gender || $form?.esignetCallback?.data?.gender || ""' + }), getBirthDate( 'informantBirthDate', informantBirthDateConditionals.concat( @@ -248,7 +310,12 @@ export const birthForm: ISerializedForm = { parameters: [] } ], - certificateHandlebars.informantBirthDate + certificateHandlebars.informantBirthDate, + { + dependsOn: ['idReader', 'esignetCallback'], + expression: + '$form?.idReader?.birthDate || $form?.esignetCallback?.data?.birthDate || ""' + } ), // Required field. exactDateOfBirthUnknown(hideIfInformantMotherOrFather), getAgeOfIndividualInYears( diff --git a/src/form/common/common-required-fields.ts b/src/form/common/common-required-fields.ts index 7213712fd..0de61988f 100644 --- a/src/form/common/common-required-fields.ts +++ b/src/form/common/common-required-fields.ts @@ -20,14 +20,15 @@ export const getBirthDate = ( fieldName: string, conditionals: Conditional[], validator: any[], - certificateHandlebar: string + certificateHandlebar: string, + initialValue: string | { dependsOn: string[]; expression: string } = '' ): SerializedFormField => ({ name: fieldName, // A field with this name MUST exist type: 'DATE', label: formMessageDescriptors.dateOfBirth, required: true, conditionals, - initialValue: '', + initialValue, validator, mapping: getFieldMapping('birthDate', certificateHandlebar) }) @@ -48,7 +49,8 @@ export const getGender = (certificateHandlebar: string) => export const getFamilyNameField = ( previewGroup: string, conditionals: Conditional[], - certificateHandlebar: string + certificateHandlebar: string, + initialValue: string | { dependsOn: string[]; expression: string } = '' ) => ({ name: 'familyNameEng', // A field with this name MUST exist @@ -58,7 +60,7 @@ export const getFamilyNameField = ( label: formMessageDescriptors.familyName, maxLength: 32, required: true, - initialValue: '', + initialValue, validator: [ { operation: 'englishOnlyNameFormat' @@ -70,7 +72,8 @@ export const getFamilyNameField = ( export const getFirstNameField = ( previewGroup: string, conditionals: Conditional[], - certificateHandlebar: string + certificateHandlebar: string, + initialValue: string | { dependsOn: string[]; expression: string } = '' ) => ({ name: 'firstNamesEng', // A field with this name MUST exist @@ -84,7 +87,7 @@ export const getFirstNameField = ( conditionals, maxLength: 32, required: true, - initialValue: '', + initialValue, validator: [ { operation: 'englishOnlyNameFormat' diff --git a/src/form/types/types.ts b/src/form/types/types.ts index 6af2c0b2e..3adceacb6 100644 --- a/src/form/types/types.ts +++ b/src/form/types/types.ts @@ -115,6 +115,7 @@ export const TEL = 'TEL' export const NUMBER = 'NUMBER' export const BIG_NUMBER = 'BIG_NUMBER' export const RADIO_GROUP = 'RADIO_GROUP' +export const HIDDEN = 'HIDDEN' export const INFORMATIVE_RADIO_GROUP = 'INFORMATIVE_RADIO_GROUP' export const CHECKBOX_GROUP = 'CHECKBOX_GROUP' export const CHECKBOX = 'CHECKBOX' @@ -142,6 +143,10 @@ export const NID_VERIFICATION_BUTTON = 'NID_VERIFICATION_BUTTON' export const DIVIDER = 'DIVIDER' export const HEADING3 = 'HEADING3' export const SIGNATURE = 'SIGNATURE' +export const LINK_BUTTON = 'LINK_BUTTON' +export const ID_READER = 'ID_READER' +export const HTTP = 'HTTP' +export const ID_VERIFICATION_BANNER = 'ID_VERIFICATION_BANNER' export enum RadioSize { LARGE = 'large', @@ -491,6 +496,50 @@ export interface ISignatureFormField extends IFormFieldBase { )[] } +export interface IHttpFormField extends IFormFieldBase { + type: typeof HTTP + options: { + url: string + method?: string + headers: Record + body?: Record + params?: Record + } +} + +export interface ILinkButtonFormField extends IFormFieldBase { + type: typeof LINK_BUTTON + icon?: { + desktop: string + mobile: string + } + options: { + url: string + callback: { + trigger: string + params: Record + } + } +} + +export interface QRReaderType { + type: 'QR' +} + +type ReaderType = QRReaderType | ILinkButtonFormField +export interface IIDReaderFormField extends IFormFieldBase { + type: typeof ID_READER + dividerLabel: MessageDescriptor + manualInputInstructionLabel: MessageDescriptor + readers: [ReaderType, ...ReaderType[]] +} + +export type BannerType = 'pending' | 'verified' | 'failed' +export interface IIDVerificationBannerFormField extends IFormFieldBase { + type: typeof ID_VERIFICATION_BANNER + bannerType: BannerType + idFieldName: string +} export type IFormField = | ITextFormField | ITelFormField @@ -524,6 +573,11 @@ export type IFormField = | IDividerField | IHeading3Field | ISignatureFormField + | IHiddenFormField + | IIDReaderFormField + | ILinkButtonFormField + | IHttpFormField + | IIDVerificationBannerFormField export interface SelectComponentOption { value: string @@ -739,6 +793,10 @@ export type SerializedFormField = UnionOmit< mapping?: IFormFieldMapping } +export interface IHiddenFormField extends IFormFieldBase { + type: typeof HIDDEN +} + export type IFormSectionQueryMapFunction = ( transFormedData: IFormData, queryData: any, diff --git a/src/translations/client.csv b/src/translations/client.csv index 55ea6a280..b58473c54 100644 --- a/src/translations/client.csv +++ b/src/translations/client.csv @@ -2354,6 +2354,14 @@ verifyCertificate.successTitle,title for success alert,Valid QR code,Code QR val verifyCertificate.successUrl,title for success alert for url validation,URL Verification,Vérification des URL verifyCertificate.timeOut,message when time out after few minute,You been timed out,Délais expirée verifyCertificate.toastMessage,Message for the toast when time spend 1 minute,"After verifying the certificate, please close the browser window","Après avoir vérifié le certificat, veuillez fermer la fenêtre du navigateur." +views.idReader.label.eSignet,E-signet button label,E-Signet,E-Signet +views.idReader.label.manualInput,Label that shows below the divider on the id reader component,Complete fields below,Complétez les champs ci-dessous +views.idReader.label.or,Label that shows on the divider,Or,Ou +views.qrReader.button,Button to scan QR code,Scan QR code,Scan QR code +views.qrReader.scannerDialogSupportingCopy,Supporting copy for the scanner dialog,Place the Notifier's ID card in front of the camera.,Place the Notifier's ID card in front of the camera. +views.qrReader.tutorial.cameraCleanliness,Camera cleanliness tutorial,Ensure your camera is clean and functional.,Ensure your camera is clean and functional. +views.qrReader.tutorial.distance,Distance tutorial,Hold the device steadily 6-12 inches away from the QR code.,Hold the device steadily 6-12 inches away from the QR code. +views.qrReader.tutorial.lightBalance,Light balance tutorial,Ensure the QR code is well-lit and not damaged or blurry.,Ensure the QR code is well-lit and not damaged or blurry. wq.noRecords.draft,No records messages for empty draft tab,No records in progress,Aucun enregistrement en cours wq.noRecords.externalValidation,No records messages for empty external validation tab,No records in external validation,Aucun enregistrement dans la validation externe wq.noRecords.fieldAgents,No records messages for empty field agents tab,No records from field agents,Aucun enregistrement des agents de terrain diff --git a/yarn.lock b/yarn.lock index 3b59f55aa..288bfedb2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -790,12 +790,10 @@ dependencies: "@octokit/openapi-types" "^18.0.0" -"@opencrvs/mosip@^1.7.0-alpha.2": - version "1.7.0-alpha.2" - resolved "https://registry.yarnpkg.com/@opencrvs/mosip/-/mosip-1.7.0-alpha.2.tgz#b3de70d993607142d739c7ada0c021b0d0f32b46" - integrity sha512-UiZUxuUiNGP/9CuQh1yPtmXmZTRAFiY4jQI2qlihCXZ8A0JbdXut2g3Dw3dRNvA9SvUnix1uuQpmWU/tAePWJA== - dependencies: - "@hapi/hapi" "^20.0.1" +"@opencrvs/mosip@^1.7.0-alpha.8": + version "1.7.0-alpha.8" + resolved "https://registry.yarnpkg.com/@opencrvs/mosip/-/mosip-1.7.0-alpha.8.tgz#cbddedda9c7f12a99935675e04b157a345182b84" + integrity sha512-uQnxlaiHBnnqsb+EA+KwZmgl2Xpmuxnu7OvVLwM89GCvTyB3XO36WdawZmfDlQxP+v3Ejuiwp4LLJiX4vDtFUQ== "@opencrvs/toolkit@0.0.6-events": version "0.0.6-events"