@verii/server-credentialagent 1.0.0-pre.1752076816
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.localdev.e2e.env +40 -0
- package/.localdev.env +41 -0
- package/.standalone.env +5 -0
- package/LICENSE +202 -0
- package/NOTICE +1 -0
- package/README.md +19 -0
- package/docker/compose.yml +33 -0
- package/e2e/README.md +12 -0
- package/e2e/org-registration-and-issuing.e2e.test.js +624 -0
- package/jest.config.js +20 -0
- package/migrate-mongo.config.js +36 -0
- package/migrations/20210317133137-add-index-to-offers-repo.js +57 -0
- package/migrations/20210416145639-add-index-to-revocation-list.js +27 -0
- package/migrations/20210719120225-add_unique_did_index_to_tenant.js +45 -0
- package/migrations/20230524053029-add-vendorUserIdMappings-index.js +32 -0
- package/migrations/20230616111907-add-configuration-type-index.js +32 -0
- package/package.json +108 -0
- package/src/assets/public/favicon.ico +0 -0
- package/src/assets/public/logo192.png +0 -0
- package/src/assets/public/logo512.png +0 -0
- package/src/assets/public/manifest.json +28 -0
- package/src/assets/templates/app-redirect.hbs +16 -0
- package/src/config/config.js +44 -0
- package/src/config/core-config.js +143 -0
- package/src/config/holder-config.js +104 -0
- package/src/config/index.js +22 -0
- package/src/config/operator-config.js +64 -0
- package/src/controllers/autoload-holder-api-controllers.js +30 -0
- package/src/controllers/autoload-operator-api-controllers.js +31 -0
- package/src/controllers/autoload-root-api-controller.js +30 -0
- package/src/controllers/autoload-saasoperator-api-controllers.js +31 -0
- package/src/controllers/holder/autohooks.js +55 -0
- package/src/controllers/holder/get-exchange-progress/autohooks.js +27 -0
- package/src/controllers/holder/get-exchange-progress/controller.js +50 -0
- package/src/controllers/holder/inspect/autohooks.js +35 -0
- package/src/controllers/holder/inspect/get-presentation-request/controller.js +100 -0
- package/src/controllers/holder/inspect/schemas/holder-disclosure.schema.json +73 -0
- package/src/controllers/holder/inspect/schemas/index.js +33 -0
- package/src/controllers/holder/inspect/schemas/presentation-definition.v1.schema.json +461 -0
- package/src/controllers/holder/inspect/schemas/presentation-request.schema.json +279 -0
- package/src/controllers/holder/inspect/schemas/presentation-submission.schema.json +41 -0
- package/src/controllers/holder/inspect/schemas/siop-presentation-submission.schema.json +74 -0
- package/src/controllers/holder/inspect/schemas/velocity-presentation-submission.response.200.schema.json +36 -0
- package/src/controllers/holder/inspect/schemas/velocity-presentation-submission.schema.json +34 -0
- package/src/controllers/holder/inspect/submit-presentation/controller.js +89 -0
- package/src/controllers/holder/issue/autohooks.js +23 -0
- package/src/controllers/holder/issue/get-credential-manifest/controller.js +193 -0
- package/src/controllers/holder/issue/offers/autohooks.js +35 -0
- package/src/controllers/holder/issue/offers/controller.js +164 -0
- package/src/controllers/holder/issue/offers/credential-offers/controller.js +460 -0
- package/src/controllers/holder/issue/submit-identification/autohooks.js +37 -0
- package/src/controllers/holder/issue/submit-identification/controller.js +63 -0
- package/src/controllers/holder/oauth/autohooks.js +19 -0
- package/src/controllers/holder/oauth/controller.js +140 -0
- package/src/controllers/index.js +22 -0
- package/src/controllers/operator/tenants/_tenantId/autohooks.js +40 -0
- package/src/controllers/operator/tenants/_tenantId/check-credentials/autohooks.js +24 -0
- package/src/controllers/operator/tenants/_tenantId/check-credentials/controller-v0.8.js +200 -0
- package/src/controllers/operator/tenants/_tenantId/check-credentials/schemas/index.js +19 -0
- package/src/controllers/operator/tenants/_tenantId/check-credentials/schemas/vendor-credential.schema.json +244 -0
- package/src/controllers/operator/tenants/_tenantId/controller-v0.8.js +221 -0
- package/src/controllers/operator/tenants/_tenantId/disclosures/_id/autohooks.js +30 -0
- package/src/controllers/operator/tenants/_tenantId/disclosures/_id/controller-v0.8.js +271 -0
- package/src/controllers/operator/tenants/_tenantId/disclosures/_id/feeds/autohooks.js +45 -0
- package/src/controllers/operator/tenants/_tenantId/disclosures/_id/feeds/controller-v0.8.js +199 -0
- package/src/controllers/operator/tenants/_tenantId/disclosures/_id/feeds/schemas/add-feed.schema.js +14 -0
- package/src/controllers/operator/tenants/_tenantId/disclosures/_id/feeds/schemas/feed.schema.json +27 -0
- package/src/controllers/operator/tenants/_tenantId/disclosures/_id/feeds/schemas/index.js +25 -0
- package/src/controllers/operator/tenants/_tenantId/disclosures/_id/feeds/schemas/modify-feed-update-body.schema.js +18 -0
- package/src/controllers/operator/tenants/_tenantId/disclosures/_id/feeds/schemas/modify-feed.schema.json +19 -0
- package/src/controllers/operator/tenants/_tenantId/disclosures/autohooks.js +34 -0
- package/src/controllers/operator/tenants/_tenantId/disclosures/controller-v0.8.js +100 -0
- package/src/controllers/operator/tenants/_tenantId/disclosures/schemas/agent-disclosure-presentation-definition.schema.json +404 -0
- package/src/controllers/operator/tenants/_tenantId/disclosures/schemas/agent-disclosure.schema.js +24 -0
- package/src/controllers/operator/tenants/_tenantId/disclosures/schemas/index.js +29 -0
- package/src/controllers/operator/tenants/_tenantId/disclosures/schemas/new-agent-disclosure.schema.json +166 -0
- package/src/controllers/operator/tenants/_tenantId/disclosures/schemas/update-agent-disclosure.schema.js +20 -0
- package/src/controllers/operator/tenants/_tenantId/exchanges/_exchangeId/autohooks.js +30 -0
- package/src/controllers/operator/tenants/_tenantId/exchanges/_exchangeId/controller-v0.8.js +73 -0
- package/src/controllers/operator/tenants/_tenantId/exchanges/autohooks.js +19 -0
- package/src/controllers/operator/tenants/_tenantId/exchanges/controller-v0.8.js +150 -0
- package/src/controllers/operator/tenants/_tenantId/exchanges/schemas/get-exchange.response.body.json +147 -0
- package/src/controllers/operator/tenants/_tenantId/exchanges/schemas/index.js +21 -0
- package/src/controllers/operator/tenants/_tenantId/issued-credentials/autohooks.js +27 -0
- package/src/controllers/operator/tenants/_tenantId/issued-credentials/controller-v0.8.js +303 -0
- package/src/controllers/operator/tenants/_tenantId/issued-credentials/schemas/index.js +23 -0
- package/src/controllers/operator/tenants/_tenantId/issued-credentials/schemas/issued-credential.schema.json +115 -0
- package/src/controllers/operator/tenants/_tenantId/issued-credentials/schemas/revoke-credentials.schema.json +18 -0
- package/src/controllers/operator/tenants/_tenantId/keys/controller-v0.8.js +168 -0
- package/src/controllers/operator/tenants/_tenantId/offer-data/controller-v0.8.js +78 -0
- package/src/controllers/operator/tenants/_tenantId/offers/autohooks.js +34 -0
- package/src/controllers/operator/tenants/_tenantId/offers/controller-v0.8.js +253 -0
- package/src/controllers/operator/tenants/_tenantId/offers/schemas/index.js +23 -0
- package/src/controllers/operator/tenants/_tenantId/offers/schemas/new-vendor-offer.schema.js +47 -0
- package/src/controllers/operator/tenants/_tenantId/offers/schemas/vendor-offer.schema.json +56 -0
- package/src/controllers/operator/tenants/_tenantId/users/autohooks.js +24 -0
- package/src/controllers/operator/tenants/_tenantId/users/controller-v0.8.js +92 -0
- package/src/controllers/operator/tenants/_tenantId/users/schemas/index.js +23 -0
- package/src/controllers/operator/tenants/_tenantId/users/schemas/new-user.schema.json +13 -0
- package/src/controllers/operator/tenants/_tenantId/users/schemas/user.schema.json +16 -0
- package/src/controllers/operator/tenants/_tenantId/vc-api/credentials/autohooks.js +34 -0
- package/src/controllers/operator/tenants/_tenantId/vc-api/credentials/controller-v0.8.js +110 -0
- package/src/controllers/operator/tenants/_tenantId/vc-api/credentials/schemas/Credential.schema.js +18 -0
- package/src/controllers/operator/tenants/_tenantId/vc-api/credentials/schemas/IssueCredentialOptions.schema.json +42 -0
- package/src/controllers/operator/tenants/_tenantId/vc-api/credentials/schemas/IssueCredentialRequest.schema.json +13 -0
- package/src/controllers/operator/tenants/_tenantId/vc-api/credentials/schemas/IssueCredentialResponse.schema.json +19 -0
- package/src/controllers/operator/tenants/_tenantId/vc-api/credentials/schemas/LinkedDataProof.schema.json +43 -0
- package/src/controllers/operator/tenants/_tenantId/vc-api/credentials/schemas/VerifiableCredential.schema.js +16 -0
- package/src/controllers/operator/tenants/_tenantId/vc-api/credentials/schemas/index.js +31 -0
- package/src/controllers/operator/tenants/autohooks.js +65 -0
- package/src/controllers/operator/tenants/controller-v0.8.js +167 -0
- package/src/controllers/operator/tenants/schemas/index.js +41 -0
- package/src/controllers/operator/tenants/schemas/modify-secret.schema.json +11 -0
- package/src/controllers/operator/tenants/schemas/modify-tenant-v0.8.schema.json +44 -0
- package/src/controllers/operator/tenants/schemas/new-tenant-v0.8.schema.json +19 -0
- package/src/controllers/operator/tenants/schemas/new-tenant.response.200.schema.json +7 -0
- package/src/controllers/operator/tenants/schemas/secret-key-metadata.schema.json +31 -0
- package/src/controllers/operator/tenants/schemas/secret-key.schema.json +29 -0
- package/src/controllers/operator/tenants/schemas/secret-kid.schema.json +13 -0
- package/src/controllers/operator/tenants/schemas/secret-new-tenant-v0.8.schema.json +28 -0
- package/src/controllers/operator/tenants/schemas/secret-tenant-key-v0.8.schema.json +13 -0
- package/src/controllers/operator/tenants/schemas/tenant-key-v0.8.schema.json +14 -0
- package/src/controllers/operator/tenants/schemas/tenant-v0.8.schema.json +62 -0
- package/src/controllers/root/autohooks.js +23 -0
- package/src/controllers/root/controller.js +173 -0
- package/src/controllers/saasoperator/groups/_id/autohooks.js +9 -0
- package/src/controllers/saasoperator/groups/_id/controller.js +121 -0
- package/src/controllers/saasoperator/groups/autohooks.js +19 -0
- package/src/controllers/saasoperator/groups/controller.js +65 -0
- package/src/controllers/saasoperator/groups/schemas/group.schema.js +17 -0
- package/src/controllers/saasoperator/groups/schemas/index.js +4 -0
- package/src/controllers/saasoperator/groups/schemas/new-group.schema.js +13 -0
- package/src/entities/common/domains/get-json-at-path.js +28 -0
- package/src/entities/common/domains/index.js +17 -0
- package/src/entities/common/index.js +17 -0
- package/src/entities/credentials/domains/credential-format.js +22 -0
- package/src/entities/credentials/domains/index.js +19 -0
- package/src/entities/credentials/index.js +17 -0
- package/src/entities/deep-links/domains/extract-did.js +11 -0
- package/src/entities/deep-links/domains/index.js +20 -0
- package/src/entities/deep-links/domains/velocity-protocol-uri-to-http-uri.js +32 -0
- package/src/entities/deep-links/index.js +19 -0
- package/src/entities/disclosures/domains/assert-disclosure-active.js +21 -0
- package/src/entities/disclosures/domains/compute-disclosure-configuration-type.js +29 -0
- package/src/entities/disclosures/domains/constants.js +61 -0
- package/src/entities/disclosures/domains/errors.js +34 -0
- package/src/entities/disclosures/domains/get-disclosure-configuration-type.js +60 -0
- package/src/entities/disclosures/domains/index.js +32 -0
- package/src/entities/disclosures/domains/is-issuing-disclosure.js +23 -0
- package/src/entities/disclosures/domains/parse-body-to-disclosure.js +17 -0
- package/src/entities/disclosures/domains/validate-by-identification-method.js +69 -0
- package/src/entities/disclosures/domains/validate-commercial-entity.js +26 -0
- package/src/entities/disclosures/domains/validate-disclosure-by-configuration-type.js +47 -0
- package/src/entities/disclosures/domains/validate-disclosure-default-issuing.js +77 -0
- package/src/entities/disclosures/domains/validate-disclosure.js +37 -0
- package/src/entities/disclosures/domains/validate-feed.js +16 -0
- package/src/entities/disclosures/domains/validate-presentation-definition.js +54 -0
- package/src/entities/disclosures/domains/validate-vendor-endpoint.js +22 -0
- package/src/entities/disclosures/domains/validate-vendor-webhook.js +18 -0
- package/src/entities/disclosures/factories/disclosure-factory.js +94 -0
- package/src/entities/disclosures/factories/index.js +19 -0
- package/src/entities/disclosures/index.js +22 -0
- package/src/entities/disclosures/orchestrators/get-disclosure.js +18 -0
- package/src/entities/disclosures/orchestrators/index.js +20 -0
- package/src/entities/disclosures/orchestrators/update-disclosure-configuration-type.js +32 -0
- package/src/entities/disclosures/repos/index.js +20 -0
- package/src/entities/disclosures/repos/repo.js +118 -0
- package/src/entities/disclosures/repos/set-configuration-type.js +33 -0
- package/src/entities/exchanges/adapters/index.js +17 -0
- package/src/entities/exchanges/adapters/sign-exchange-response.js +45 -0
- package/src/entities/exchanges/domains/build-exchange-progress.js +56 -0
- package/src/entities/exchanges/domains/constants.js +24 -0
- package/src/entities/exchanges/domains/ensure-exchange-state-valid.js +35 -0
- package/src/entities/exchanges/domains/errors.js +33 -0
- package/src/entities/exchanges/domains/index.js +25 -0
- package/src/entities/exchanges/domains/states.js +43 -0
- package/src/entities/exchanges/domains/types.js +31 -0
- package/src/entities/exchanges/factories/disclosure-exchange-factory.js +46 -0
- package/src/entities/exchanges/factories/index.js +20 -0
- package/src/entities/exchanges/factories/offer-exchange-factory.js +48 -0
- package/src/entities/exchanges/index.js +23 -0
- package/src/entities/exchanges/orchestrators/build-exchange-request-deep-link.js +50 -0
- package/src/entities/exchanges/orchestrators/index.js +19 -0
- package/src/entities/exchanges/repos/exchange-repo-projections.js +45 -0
- package/src/entities/exchanges/repos/exchange-state-repo-extension.js +76 -0
- package/src/entities/exchanges/repos/index.js +20 -0
- package/src/entities/exchanges/repos/repo.js +44 -0
- package/src/entities/feeds/factories/feed-factory.js +47 -0
- package/src/entities/feeds/factories/index.js +19 -0
- package/src/entities/feeds/index.js +20 -0
- package/src/entities/feeds/repos/index.js +19 -0
- package/src/entities/feeds/repos/repo.js +95 -0
- package/src/entities/groups/domains/format-group.js +11 -0
- package/src/entities/groups/domains/index.js +3 -0
- package/src/entities/groups/factories/group-factory.js +40 -0
- package/src/entities/groups/factories/index.js +19 -0
- package/src/entities/groups/index.js +22 -0
- package/src/entities/groups/orchestrators/find-group-or-error.js +16 -0
- package/src/entities/groups/orchestrators/index.js +6 -0
- package/src/entities/groups/orchestrators/validate-did.js +24 -0
- package/src/entities/groups/orchestrators/validate-group-by-user.js +16 -0
- package/src/entities/groups/orchestrators/validate-group.js +39 -0
- package/src/entities/groups/repos/delete-tenant-extension.js +13 -0
- package/src/entities/groups/repos/index.js +19 -0
- package/src/entities/groups/repos/repo.js +38 -0
- package/src/entities/groups/repos/update-or-error-extension.js +46 -0
- package/src/entities/index.js +37 -0
- package/src/entities/keys/domains/constants.js +37 -0
- package/src/entities/keys/domains/index.js +21 -0
- package/src/entities/keys/domains/is-matching-private-key-kid.js +41 -0
- package/src/entities/keys/domains/validate-key.js +62 -0
- package/src/entities/keys/factories/index.js +19 -0
- package/src/entities/keys/factories/key-factory.js +56 -0
- package/src/entities/keys/index.js +22 -0
- package/src/entities/keys/orchestrators/index.js +3 -0
- package/src/entities/keys/orchestrators/validate-did-doc-keys.js +69 -0
- package/src/entities/metadata-list-allocations/index.js +19 -0
- package/src/entities/metadata-list-allocations/repos/index.js +19 -0
- package/src/entities/metadata-list-allocations/repos/repo.js +40 -0
- package/src/entities/notifications/domains/index.js +19 -0
- package/src/entities/notifications/domains/notification-types.js +25 -0
- package/src/entities/notifications/index.js +19 -0
- package/src/entities/offers/domains/build-clean-pii-filter.js +35 -0
- package/src/entities/offers/domains/build-deeplink-url.js +120 -0
- package/src/entities/offers/domains/build-offer.js +88 -0
- package/src/entities/offers/domains/build-qr-code-url.js +37 -0
- package/src/entities/offers/domains/constants.js +32 -0
- package/src/entities/offers/domains/filter-object-ids.js +34 -0
- package/src/entities/offers/domains/generate-issuing-challenge.js +26 -0
- package/src/entities/offers/domains/generate-link-code.js +35 -0
- package/src/entities/offers/domains/index.js +31 -0
- package/src/entities/offers/domains/post-validation-offers-handler.js +31 -0
- package/src/entities/offers/domains/prepare-linked-credentials-for-holder.js +36 -0
- package/src/entities/offers/domains/resolve-subject.js +142 -0
- package/src/entities/offers/domains/validate-offer-commercial-entity.js +24 -0
- package/src/entities/offers/domains/validate-offer.js +90 -0
- package/src/entities/offers/factories/index.js +19 -0
- package/src/entities/offers/factories/offer-factory.js +119 -0
- package/src/entities/offers/index.js +22 -0
- package/src/entities/offers/orchestrators/create-verifiable-credentials.js +131 -0
- package/src/entities/offers/orchestrators/finalize-exchange.js +44 -0
- package/src/entities/offers/orchestrators/index.js +23 -0
- package/src/entities/offers/orchestrators/load-credential-refs.js +57 -0
- package/src/entities/offers/orchestrators/load-credential-types-map.js +44 -0
- package/src/entities/offers/orchestrators/prepare-offers.js +35 -0
- package/src/entities/offers/orchestrators/trigger-issued-credentials-webhook.js +63 -0
- package/src/entities/offers/repos/clean-pii-extension.js +85 -0
- package/src/entities/offers/repos/index.js +20 -0
- package/src/entities/offers/repos/issued-credential-projection.js +44 -0
- package/src/entities/offers/repos/repo.js +177 -0
- package/src/entities/presentations/domains/build-identity-doc.js +120 -0
- package/src/entities/presentations/domains/build-request-response-schema.js +46 -0
- package/src/entities/presentations/domains/build-vendor-data.js +31 -0
- package/src/entities/presentations/domains/check-payment-requirement.js +30 -0
- package/src/entities/presentations/domains/errors.js +28 -0
- package/src/entities/presentations/domains/extract-fields-from-id-credential.js +35 -0
- package/src/entities/presentations/domains/index.js +26 -0
- package/src/entities/presentations/domains/merge-credential-check-results.js +24 -0
- package/src/entities/presentations/domains/validate-presentation.js +128 -0
- package/src/entities/presentations/index.js +20 -0
- package/src/entities/presentations/orchestrators/create-presentation-request.js +148 -0
- package/src/entities/presentations/orchestrators/deduplicate-disclosure-exchange.js +52 -0
- package/src/entities/presentations/orchestrators/handle-presentation-submission.js +47 -0
- package/src/entities/presentations/orchestrators/index.js +20 -0
- package/src/entities/presentations/orchestrators/match-identity-on-exchange.js +114 -0
- package/src/entities/presentations/orchestrators/share-identification-credentials.js +110 -0
- package/src/entities/presentations/orchestrators/share-presentation.js +234 -0
- package/src/entities/push-delegate/get-push-delegate.js +37 -0
- package/src/entities/push-delegate/index.js +17 -0
- package/src/entities/redirect/index.js +3 -0
- package/src/entities/redirect/orchestrators/index.js +3 -0
- package/src/entities/redirect/orchestrators/load-org-info.js +40 -0
- package/src/entities/revocation-list-allocations/index.js +19 -0
- package/src/entities/revocation-list-allocations/repos/index.js +19 -0
- package/src/entities/revocation-list-allocations/repos/repo.js +40 -0
- package/src/entities/schemas/index.js +19 -0
- package/src/entities/schemas/orchestrators/index.js +19 -0
- package/src/entities/schemas/orchestrators/load-schema-validation.js +73 -0
- package/src/entities/tenants/domains/build-service-ids.js +27 -0
- package/src/entities/tenants/domains/extract-service.js +27 -0
- package/src/entities/tenants/domains/index.js +21 -0
- package/src/entities/tenants/domains/validate-service-ids.js +35 -0
- package/src/entities/tenants/factories/index.js +19 -0
- package/src/entities/tenants/factories/tenant-factory.js +37 -0
- package/src/entities/tenants/index.js +22 -0
- package/src/entities/tenants/orchestrators/add-primary-address-to-tenant.js +47 -0
- package/src/entities/tenants/orchestrators/create-tenant.js +91 -0
- package/src/entities/tenants/orchestrators/index.js +22 -0
- package/src/entities/tenants/orchestrators/refresh-tenant-dids.js +146 -0
- package/src/entities/tenants/orchestrators/set-tenant-default-issuing-disclosure.js +31 -0
- package/src/entities/tenants/repos/index.js +20 -0
- package/src/entities/tenants/repos/insert-tenant-extension.js +33 -0
- package/src/entities/tenants/repos/repo.js +52 -0
- package/src/entities/tenants/repos/tenant-default-projection.js +33 -0
- package/src/entities/tokens/adapters/access-token.js +49 -0
- package/src/entities/tokens/adapters/index.js +19 -0
- package/src/entities/tokens/index.js +19 -0
- package/src/entities/users/factories/index.js +19 -0
- package/src/entities/users/factories/user-factory.js +36 -0
- package/src/entities/users/index.js +20 -0
- package/src/entities/users/repos/add-anonymous-user-repo-extension.js +23 -0
- package/src/entities/users/repos/find-or-insert-vendor-user-repo-extension.js +30 -0
- package/src/entities/users/repos/index.js +19 -0
- package/src/entities/users/repos/repo.js +50 -0
- package/src/fetchers/index.js +20 -0
- package/src/fetchers/operator/identify-fetcher.js +36 -0
- package/src/fetchers/operator/index.js +21 -0
- package/src/fetchers/operator/inspection-fetcher.js +35 -0
- package/src/fetchers/operator/issuing-fetcher.js +50 -0
- package/src/fetchers/operator/webhook-auth-header.js +45 -0
- package/src/fetchers/push-gateway/generate-push-gateway-token.js +40 -0
- package/src/fetchers/push-gateway/index.js +19 -0
- package/src/fetchers/push-gateway/push-fetcher.js +39 -0
- package/src/index.js +19 -0
- package/src/init-holder-server.js +108 -0
- package/src/init-operator-server.js +101 -0
- package/src/init-server.js +120 -0
- package/src/main-holder.js +18 -0
- package/src/main-operator.js +19 -0
- package/src/main.js +18 -0
- package/src/plugins/autoload-repos.js +28 -0
- package/src/plugins/disclosure-loader-plugin.js +56 -0
- package/src/plugins/ensure-disclosure-active-plugin.js +30 -0
- package/src/plugins/ensure-disclosure-configuration-type-plugin.js +29 -0
- package/src/plugins/ensure-tenant-default-issuing-disclosure-id-plugin.js +60 -0
- package/src/plugins/ensure-tenant-primary-address-plugin.js +44 -0
- package/src/plugins/exchange-error-handler-plugin.js +51 -0
- package/src/plugins/exchange-loader-plugin.js +50 -0
- package/src/plugins/group-loader-plugin.js +51 -0
- package/src/plugins/index.js +32 -0
- package/src/plugins/kms-plugin.js +57 -0
- package/src/plugins/tenant-loader-plugin.js +91 -0
- package/src/plugins/validate-cao-plugin.js +81 -0
- package/src/plugins/vendor-routes-auth-plugin.js +24 -0
- package/src/plugins/verify-access-token-plugin.js +88 -0
- package/src/standalone.js +24 -0
- package/src/start-app-server.js +38 -0
- package/test/combined/app-redirect.test.js +199 -0
- package/test/combined/helpers/credentialagent-build-fastify.js +29 -0
- package/test/combined/helpers/index.js +22 -0
- package/test/combined/helpers/nock-registrar-app-schema-name.js +50 -0
- package/test/combined/helpers/nock-registrar-get-organization-diddoc.js +26 -0
- package/test/combined/helpers/nock-registrar-get-organization-verified-profile.js +33 -0
- package/test/combined/manifest.json.test.js +55 -0
- package/test/combined/root-controller.test.js +42 -0
- package/test/combined/schemas/education-degree.schema.json +166 -0
- package/test/combined/schemas/employment-current-v1.1.schema.json +253 -0
- package/test/combined/schemas/open-badge-credential.schema.json +1285 -0
- package/test/combined/schemas/past-employment-position-with-uri-id.schema.js +22 -0
- package/test/combined/schemas/past-employment-position.schema.json +148 -0
- package/test/combined/schemas/will-always-validate.json +10 -0
- package/test/combined/validate-cao-plugin.test.js +155 -0
- package/test/get-push-delegate.test.js +54 -0
- package/test/helpers/jwt-vc-expectation.js +109 -0
- package/test/holder/build-request-response-schema.test.js +55 -0
- package/test/holder/credential-manifest-controller.test.js +3192 -0
- package/test/holder/e2e-issuing-controller.test.js +425 -0
- package/test/holder/get-exchange-progress-controller.test.js +521 -0
- package/test/holder/get-presentation-request.test.js +906 -0
- package/test/holder/helpers/credential-type-metadata.js +98 -0
- package/test/holder/helpers/credentialagent-holder-build-fastify.js +32 -0
- package/test/holder/helpers/generate-presentation.js +441 -0
- package/test/holder/helpers/generate-test-access-token.js +54 -0
- package/test/holder/helpers/jwt-access-token-expectation.js +32 -0
- package/test/holder/helpers/jwt-vc-expectation.js +115 -0
- package/test/holder/issuing-controller.test.js +7076 -0
- package/test/holder/oauth-token-controller.test.js +412 -0
- package/test/holder/presentation-submission.test.js +2365 -0
- package/test/holder/submit-identification.test.js +4815 -0
- package/test/operator/check-credentials-controller-v0.8.test.js +832 -0
- package/test/operator/credentials-revoke.test.js +536 -0
- package/test/operator/disclosures-controller-v0.8.test.js +4157 -0
- package/test/operator/exchanges-controller-v0.8.test.js +414 -0
- package/test/operator/exchanges-id-controller-v0.8.test.js +162 -0
- package/test/operator/feeds-controller-v0.8.test.js +659 -0
- package/test/operator/generate-push-gateway-token.test.js +116 -0
- package/test/operator/groups-controller.test.js +145 -0
- package/test/operator/groups-id-controller.test.js +287 -0
- package/test/operator/helpers/create-test-org-doc.js +60 -0
- package/test/operator/helpers/credentialagent-operator-build-fastify.js +32 -0
- package/test/operator/helpers/find-kms-key.js +31 -0
- package/test/operator/helpers/generate-primary-and-add-operator-to-primary.js +63 -0
- package/test/operator/helpers/init-agent-kms.js +22 -0
- package/test/operator/issued-credentials-controller-v0.8.test.js +398 -0
- package/test/operator/keys-controller-v0.8.test.js +1130 -0
- package/test/operator/offer-data-controller-v0.8.test.js +253 -0
- package/test/operator/offers-controller-v0.8.test.js +3026 -0
- package/test/operator/set-configuration-type-modifier.test.js +75 -0
- package/test/operator/swagger.test.js +37 -0
- package/test/operator/tenant-controller-v0.8.test.js +730 -0
- package/test/operator/tenant-loader-plugin.test.js +96 -0
- package/test/operator/tenants-controller-v0.8.test.js +2093 -0
- package/test/operator/users-controller-v0.8.test.js +137 -0
- package/test/operator/vc-api-credentials.test.js +963 -0
- package/verification.env +28 -0
|
@@ -0,0 +1,4815 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2023 Velocity Team
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
// eslint-disable-next-line import/order
|
|
18
|
+
const buildFastify = require('./helpers/credentialagent-holder-build-fastify');
|
|
19
|
+
const { generateKeyPair } = require('@verii/crypto');
|
|
20
|
+
const { VnfProtocolVersions } = require('@verii/vc-checks');
|
|
21
|
+
const { mongoDb } = require('@spencejs/spence-mongo-repos');
|
|
22
|
+
const { ObjectId } = require('mongodb');
|
|
23
|
+
const { decodeJwt } = require('jose');
|
|
24
|
+
const { map, omitBy } = require('lodash/fp');
|
|
25
|
+
const nock = require('nock');
|
|
26
|
+
const { ISO_DATETIME_FORMAT } = require('@verii/test-regexes');
|
|
27
|
+
const { mongoify, errorResponseMatcher } = require('@verii/tests-helpers');
|
|
28
|
+
const metadataRegistration = require('@verii/metadata-registration');
|
|
29
|
+
|
|
30
|
+
const DEFAULT_CREDENTIAL_CHECKS = {
|
|
31
|
+
TRUSTED_HOLDER: 'PASS',
|
|
32
|
+
TRUSTED_ISSUER: 'PASS',
|
|
33
|
+
UNEXPIRED: 'PASS',
|
|
34
|
+
UNTAMPERED: 'PASS',
|
|
35
|
+
UNREVOKED: 'PASS',
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
jest.mock('@verii/metadata-registration');
|
|
39
|
+
|
|
40
|
+
const mockVerifyCredentials = jest.fn();
|
|
41
|
+
jest.mock('@verii/verifiable-credentials', () => ({
|
|
42
|
+
...jest.requireActual('@verii/verifiable-credentials'),
|
|
43
|
+
verifyCredentials: (...args) => mockVerifyCredentials(...args),
|
|
44
|
+
}));
|
|
45
|
+
|
|
46
|
+
const { CredentialCheckResultValue } = require('@verii/verifiable-credentials');
|
|
47
|
+
const { getDidUriFromJwk } = require('@verii/did-doc');
|
|
48
|
+
const { holderConfig } = require('../../src/config/holder-config');
|
|
49
|
+
const {
|
|
50
|
+
initTenantFactory,
|
|
51
|
+
initKeysFactory,
|
|
52
|
+
initUserFactory,
|
|
53
|
+
initOfferExchangeFactory,
|
|
54
|
+
initDisclosureFactory,
|
|
55
|
+
ExchangeStates,
|
|
56
|
+
ExchangeTypes,
|
|
57
|
+
VendorEndpoint,
|
|
58
|
+
} = require('../../src/entities');
|
|
59
|
+
const {
|
|
60
|
+
generateKYCPresentation,
|
|
61
|
+
emailPayload,
|
|
62
|
+
phonePayload,
|
|
63
|
+
idDocPayload,
|
|
64
|
+
legacyIdDocPayload,
|
|
65
|
+
verificationIdentifierPayload,
|
|
66
|
+
} = require('./helpers/generate-presentation');
|
|
67
|
+
|
|
68
|
+
const setMockVerifyCredentials = (checkResults = DEFAULT_CREDENTIAL_CHECKS) => {
|
|
69
|
+
const { decodeCredentialJwt } = require('@verii/jwt');
|
|
70
|
+
// mockVerifyCredentials.reset();
|
|
71
|
+
mockVerifyCredentials.mockImplementation(async ({ credentials }) =>
|
|
72
|
+
Promise.resolve(
|
|
73
|
+
credentials.map((credential) => ({
|
|
74
|
+
credential: decodeCredentialJwt(credential),
|
|
75
|
+
credentialChecks: checkResults,
|
|
76
|
+
}))
|
|
77
|
+
)
|
|
78
|
+
);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const credentialTypesObject = { credentialTypes: ['PastEmploymentPosition'] };
|
|
82
|
+
|
|
83
|
+
describe('submit identification disclosure', () => {
|
|
84
|
+
let fastify;
|
|
85
|
+
let tenant;
|
|
86
|
+
let persistOfferExchange;
|
|
87
|
+
let persistTenant;
|
|
88
|
+
let persistKey;
|
|
89
|
+
let persistDisclosure;
|
|
90
|
+
let newVendorUserIdMapping;
|
|
91
|
+
let persistVendorUserIdMapping;
|
|
92
|
+
|
|
93
|
+
let holderKeys;
|
|
94
|
+
let holderDid;
|
|
95
|
+
let holderKid;
|
|
96
|
+
|
|
97
|
+
const idUrl = ({ did }) =>
|
|
98
|
+
`/api/holder/v0.6/org/${did}/issue/submit-identification`;
|
|
99
|
+
|
|
100
|
+
const mockVendorUrl = 'http://mockvendor.localhost.test';
|
|
101
|
+
|
|
102
|
+
const identifyUserOnVendorEndpoint = '/issuing/identify';
|
|
103
|
+
|
|
104
|
+
beforeAll(async () => {
|
|
105
|
+
fastify = buildFastify(holderConfig);
|
|
106
|
+
await fastify.ready();
|
|
107
|
+
({ persistOfferExchange } = initOfferExchangeFactory(fastify));
|
|
108
|
+
({ persistTenant } = initTenantFactory(fastify));
|
|
109
|
+
({ persistKey } = initKeysFactory(fastify));
|
|
110
|
+
({ newVendorUserIdMapping, persistVendorUserIdMapping } =
|
|
111
|
+
initUserFactory(fastify));
|
|
112
|
+
({ persistDisclosure } = initDisclosureFactory(fastify));
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
beforeEach(async () => {
|
|
116
|
+
fastify.resetOverrides();
|
|
117
|
+
nock.cleanAll();
|
|
118
|
+
jest.resetAllMocks();
|
|
119
|
+
setMockVerifyCredentials();
|
|
120
|
+
|
|
121
|
+
await mongoDb().collection('tenants').deleteMany({});
|
|
122
|
+
await mongoDb().collection('vendorUserIdMappings').deleteMany({});
|
|
123
|
+
await mongoDb().collection('exchanges').deleteMany({});
|
|
124
|
+
await mongoDb().collection('disclosures').deleteMany({});
|
|
125
|
+
tenant = await persistTenant();
|
|
126
|
+
const keyPair = generateKeyPair({ format: 'jwk' });
|
|
127
|
+
await persistKey({
|
|
128
|
+
tenant,
|
|
129
|
+
kidFragment: '#ID1',
|
|
130
|
+
keyPair,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
holderKeys = generateKeyPair({ format: 'jwk' });
|
|
134
|
+
holderDid = getDidUriFromJwk(holderKeys.publicKey);
|
|
135
|
+
holderKid = `${holderDid}#0`;
|
|
136
|
+
|
|
137
|
+
nock('http://oracle.localhost.test')
|
|
138
|
+
.get('/api/v0.6/credential-types', () => {
|
|
139
|
+
return true;
|
|
140
|
+
})
|
|
141
|
+
.reply(
|
|
142
|
+
200,
|
|
143
|
+
[
|
|
144
|
+
{
|
|
145
|
+
credentialType: 'Passport',
|
|
146
|
+
issuerCategory: 'ContactIssuer',
|
|
147
|
+
},
|
|
148
|
+
],
|
|
149
|
+
{ 'cache-control': 'max-age=3600' }
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
metadataRegistration.initVerificationCoupon.mockImplementation(() => ({
|
|
153
|
+
getCoupon: () => Promise.resolve(42),
|
|
154
|
+
}));
|
|
155
|
+
metadataRegistration.initRevocationRegistry.mockImplementation(() => ({
|
|
156
|
+
getRevokedStatus: () => Promise.resolve(0),
|
|
157
|
+
}));
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
afterAll(async () => {
|
|
161
|
+
await fastify.close();
|
|
162
|
+
nock.cleanAll();
|
|
163
|
+
nock.restore();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
describe('using integrated identification', () => {
|
|
167
|
+
it('should fail if the json path doesnt exist', async () => {
|
|
168
|
+
const disclosure = await persistDisclosure({
|
|
169
|
+
tenant,
|
|
170
|
+
description: 'Credential Issuance disclosure',
|
|
171
|
+
types: [{ type: 'EmailV1.0' }],
|
|
172
|
+
vendorEndpoint: VendorEndpoint.INTEGRATED_ISSUING_IDENTIFICATION,
|
|
173
|
+
purpose: 'Identification',
|
|
174
|
+
duration: '6y',
|
|
175
|
+
identityMatchers: {
|
|
176
|
+
rules: [
|
|
177
|
+
{
|
|
178
|
+
valueIndex: 0, // used for identifying the value
|
|
179
|
+
path: ['$.doesntexist'], // jsonPath within the credential
|
|
180
|
+
rule: 'pick', // Rule to execute can be pick, all (if the target is an array), equal (if the target is a singleValue)
|
|
181
|
+
},
|
|
182
|
+
],
|
|
183
|
+
vendorUserIdIndex: 0,
|
|
184
|
+
},
|
|
185
|
+
});
|
|
186
|
+
const exchange = await persistOfferExchange({
|
|
187
|
+
tenant,
|
|
188
|
+
disclosure,
|
|
189
|
+
identityMatcherValues: ['adam.smith@example.com'],
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
const presentationEmail = await generateKYCPresentation(
|
|
193
|
+
exchange,
|
|
194
|
+
'email'
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
const response = await fastify.injectJson({
|
|
198
|
+
method: 'POST',
|
|
199
|
+
url: idUrl(tenant),
|
|
200
|
+
payload: {
|
|
201
|
+
jwt_vp: await presentationEmail.selfSign(),
|
|
202
|
+
exchange_id: exchange._id,
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
expect(response.statusCode).toEqual(400);
|
|
207
|
+
expect(response.json).toEqual(
|
|
208
|
+
errorResponseMatcher({
|
|
209
|
+
error: 'Bad Request',
|
|
210
|
+
message: 'Presentation doesnt contain value at $.doesntexist',
|
|
211
|
+
statusCode: 400,
|
|
212
|
+
errorCode: 'presentation_credential_jsonpath_empty',
|
|
213
|
+
})
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
const exchangeDBResult = await mongoDb()
|
|
217
|
+
.collection('exchanges')
|
|
218
|
+
.findOne({ _id: new ObjectId(exchange._id) });
|
|
219
|
+
expect(exchangeDBResult).toEqual(
|
|
220
|
+
mongoify({
|
|
221
|
+
...exchange,
|
|
222
|
+
tenantId: tenant._id,
|
|
223
|
+
presentationId: presentationEmail.presentation.id,
|
|
224
|
+
disclosureConsentedAt: expect.any(Date),
|
|
225
|
+
err: 'Presentation doesnt contain value at $.doesntexist',
|
|
226
|
+
events: [
|
|
227
|
+
{ state: ExchangeStates.NEW, timestamp: expect.any(Date) },
|
|
228
|
+
{
|
|
229
|
+
state: ExchangeStates.DISCLOSURE_RECEIVED,
|
|
230
|
+
timestamp: expect.any(Date),
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
state: ExchangeStates.DISCLOSURE_CHECKED,
|
|
234
|
+
timestamp: expect.any(Date),
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
state: ExchangeStates.UNEXPECTED_ERROR,
|
|
238
|
+
timestamp: expect.any(Date),
|
|
239
|
+
},
|
|
240
|
+
],
|
|
241
|
+
offerHashes: [],
|
|
242
|
+
...credentialTypesObject,
|
|
243
|
+
createdAt: expect.any(Date),
|
|
244
|
+
updatedAt: expect.any(Date),
|
|
245
|
+
})
|
|
246
|
+
);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('should error if rule isnt "all", "pick" or "equal"', async () => {
|
|
250
|
+
const disclosure = await persistDisclosure({
|
|
251
|
+
tenant,
|
|
252
|
+
description: 'Credential Issuance disclosure',
|
|
253
|
+
types: [{ type: 'EmailV1.0' }],
|
|
254
|
+
vendorEndpoint: VendorEndpoint.INTEGRATED_ISSUING_IDENTIFICATION,
|
|
255
|
+
purpose: 'Identification',
|
|
256
|
+
duration: '6y',
|
|
257
|
+
identityMatchers: {
|
|
258
|
+
rules: [
|
|
259
|
+
{
|
|
260
|
+
valueIndex: 0, // used for identifying the value
|
|
261
|
+
path: ['$.emails'], // jsonPath within the credential
|
|
262
|
+
rule: 'notARule', // Rule to execute can be pick, all (if the target is an array), equal (if the target is a singleValue)
|
|
263
|
+
},
|
|
264
|
+
],
|
|
265
|
+
vendorUserIdIndex: 0,
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
const exchange = await persistOfferExchange({
|
|
270
|
+
tenant,
|
|
271
|
+
disclosure,
|
|
272
|
+
identityMatcherValues: ['adam.smith@example.com'],
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
const presentationEmail = await generateKYCPresentation(
|
|
276
|
+
exchange,
|
|
277
|
+
'email'
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
const response = await fastify.injectJson({
|
|
281
|
+
method: 'POST',
|
|
282
|
+
url: idUrl(tenant),
|
|
283
|
+
payload: {
|
|
284
|
+
jwt_vp: await presentationEmail.selfSign(),
|
|
285
|
+
exchange_id: exchange._id,
|
|
286
|
+
},
|
|
287
|
+
});
|
|
288
|
+
expect(response.statusCode).toEqual(500);
|
|
289
|
+
expect(response.json).toEqual(
|
|
290
|
+
errorResponseMatcher({
|
|
291
|
+
error: 'Internal Server Error',
|
|
292
|
+
message:
|
|
293
|
+
'Credential Agent only supports "pick" or "all" for "identityMatchers.rule"',
|
|
294
|
+
statusCode: 500,
|
|
295
|
+
errorCode: 'missing_error_code',
|
|
296
|
+
})
|
|
297
|
+
);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
describe('"pick" rule matching using email', () => {
|
|
301
|
+
let disclosure;
|
|
302
|
+
|
|
303
|
+
beforeEach(async () => {
|
|
304
|
+
disclosure = await persistDisclosure({
|
|
305
|
+
tenant,
|
|
306
|
+
description: 'Credential Issuance disclosure',
|
|
307
|
+
types: [{ type: 'EmailV1.0' }],
|
|
308
|
+
vendorEndpoint: VendorEndpoint.INTEGRATED_ISSUING_IDENTIFICATION,
|
|
309
|
+
purpose: 'Identification',
|
|
310
|
+
duration: '6y',
|
|
311
|
+
identityMatchers: {
|
|
312
|
+
rules: [
|
|
313
|
+
{
|
|
314
|
+
valueIndex: 0, // used for identifying the value
|
|
315
|
+
path: ['$.emails'], // jsonPath within the credential
|
|
316
|
+
rule: 'pick', // Rule to execute can be pick, all (if the target is an array), equal (if the target is a singleValue)
|
|
317
|
+
},
|
|
318
|
+
],
|
|
319
|
+
vendorUserIdIndex: 0,
|
|
320
|
+
},
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
it('email based matching should match an existent user', async () => {
|
|
325
|
+
const exchange = await persistOfferExchange({
|
|
326
|
+
tenant,
|
|
327
|
+
disclosure,
|
|
328
|
+
identityMatcherValues: ['Adam.smith@example.com'],
|
|
329
|
+
});
|
|
330
|
+
const vendorUserId = exchange.identityMatcherValues[0];
|
|
331
|
+
|
|
332
|
+
const presentationEmail = await generateKYCPresentation(
|
|
333
|
+
exchange,
|
|
334
|
+
'email'
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
const response = await fastify.injectJson({
|
|
338
|
+
method: 'POST',
|
|
339
|
+
url: idUrl(tenant),
|
|
340
|
+
payload: {
|
|
341
|
+
jwt_vp: await presentationEmail.selfSign(),
|
|
342
|
+
exchange_id: exchange._id,
|
|
343
|
+
},
|
|
344
|
+
});
|
|
345
|
+
expect(response.statusCode).toEqual(200);
|
|
346
|
+
expect(response.json).toEqual({
|
|
347
|
+
token: expect.any(String),
|
|
348
|
+
exchange: {
|
|
349
|
+
disclosureComplete: true,
|
|
350
|
+
exchangeComplete: false,
|
|
351
|
+
id: exchange._id,
|
|
352
|
+
type: ExchangeTypes.ISSUING,
|
|
353
|
+
},
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
const dbResult = await mongoDb()
|
|
357
|
+
.collection('vendorUserIdMappings')
|
|
358
|
+
.findOne({ vendorUserId });
|
|
359
|
+
const { sub } = decodeJwt(response.json.token);
|
|
360
|
+
expect(dbResult).toEqual({
|
|
361
|
+
_id: new ObjectId(sub),
|
|
362
|
+
vendorUserId,
|
|
363
|
+
tenantId: new ObjectId(tenant._id),
|
|
364
|
+
createdAt: expect.any(Date),
|
|
365
|
+
updatedAt: expect.any(Date),
|
|
366
|
+
});
|
|
367
|
+
const exchangeDBResult = await mongoDb()
|
|
368
|
+
.collection('exchanges')
|
|
369
|
+
.findOne({ _id: new ObjectId(exchange._id) });
|
|
370
|
+
expect(exchangeDBResult).toEqual(
|
|
371
|
+
mongoify({
|
|
372
|
+
...exchange,
|
|
373
|
+
tenantId: tenant._id,
|
|
374
|
+
presentationId: presentationEmail.presentation.id,
|
|
375
|
+
disclosureConsentedAt: expect.any(Date),
|
|
376
|
+
events: [
|
|
377
|
+
{ state: ExchangeStates.NEW, timestamp: expect.any(Date) },
|
|
378
|
+
{
|
|
379
|
+
state: ExchangeStates.DISCLOSURE_RECEIVED,
|
|
380
|
+
timestamp: expect.any(Date),
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
state: ExchangeStates.DISCLOSURE_CHECKED,
|
|
384
|
+
timestamp: expect.any(Date),
|
|
385
|
+
},
|
|
386
|
+
{ state: ExchangeStates.IDENTIFIED, timestamp: expect.any(Date) },
|
|
387
|
+
],
|
|
388
|
+
offerHashes: [],
|
|
389
|
+
...credentialTypesObject,
|
|
390
|
+
createdAt: expect.any(Date),
|
|
391
|
+
updatedAt: expect.any(Date),
|
|
392
|
+
})
|
|
393
|
+
);
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
it('email based matching should succeed if holder check is NOT_APPLICABLE', async () => {
|
|
397
|
+
setMockVerifyCredentials({
|
|
398
|
+
...DEFAULT_CREDENTIAL_CHECKS,
|
|
399
|
+
TRUSTED_HOLDER: CredentialCheckResultValue.NOT_APPLICABLE,
|
|
400
|
+
});
|
|
401
|
+
const exchange = await persistOfferExchange({
|
|
402
|
+
tenant,
|
|
403
|
+
disclosure,
|
|
404
|
+
identityMatcherValues: ['Adam.smith@example.com'],
|
|
405
|
+
});
|
|
406
|
+
const vendorUserId = exchange.identityMatcherValues[0];
|
|
407
|
+
|
|
408
|
+
const presentationEmail = await generateKYCPresentation(
|
|
409
|
+
exchange,
|
|
410
|
+
'email'
|
|
411
|
+
);
|
|
412
|
+
|
|
413
|
+
const response = await fastify.injectJson({
|
|
414
|
+
method: 'POST',
|
|
415
|
+
url: idUrl(tenant),
|
|
416
|
+
payload: {
|
|
417
|
+
jwt_vp: await presentationEmail.selfSign(),
|
|
418
|
+
exchange_id: exchange._id,
|
|
419
|
+
},
|
|
420
|
+
});
|
|
421
|
+
expect(response.statusCode).toEqual(200);
|
|
422
|
+
expect(response.json).toEqual({
|
|
423
|
+
token: expect.any(String),
|
|
424
|
+
exchange: {
|
|
425
|
+
disclosureComplete: true,
|
|
426
|
+
exchangeComplete: false,
|
|
427
|
+
id: exchange._id,
|
|
428
|
+
type: ExchangeTypes.ISSUING,
|
|
429
|
+
},
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
const dbResult = await mongoDb()
|
|
433
|
+
.collection('vendorUserIdMappings')
|
|
434
|
+
.findOne({ vendorUserId });
|
|
435
|
+
const { sub } = decodeJwt(response.json.token);
|
|
436
|
+
expect(dbResult).toEqual({
|
|
437
|
+
_id: new ObjectId(sub),
|
|
438
|
+
vendorUserId,
|
|
439
|
+
tenantId: new ObjectId(tenant._id),
|
|
440
|
+
createdAt: expect.any(Date),
|
|
441
|
+
updatedAt: expect.any(Date),
|
|
442
|
+
});
|
|
443
|
+
const exchangeDBResult = await mongoDb()
|
|
444
|
+
.collection('exchanges')
|
|
445
|
+
.findOne({ _id: new ObjectId(exchange._id) });
|
|
446
|
+
expect(exchangeDBResult).toEqual(
|
|
447
|
+
mongoify({
|
|
448
|
+
...exchange,
|
|
449
|
+
tenantId: tenant._id,
|
|
450
|
+
presentationId: presentationEmail.presentation.id,
|
|
451
|
+
disclosureConsentedAt: expect.any(Date),
|
|
452
|
+
events: [
|
|
453
|
+
{ state: ExchangeStates.NEW, timestamp: expect.any(Date) },
|
|
454
|
+
{
|
|
455
|
+
state: ExchangeStates.DISCLOSURE_RECEIVED,
|
|
456
|
+
timestamp: expect.any(Date),
|
|
457
|
+
},
|
|
458
|
+
{
|
|
459
|
+
state: ExchangeStates.DISCLOSURE_CHECKED,
|
|
460
|
+
timestamp: expect.any(Date),
|
|
461
|
+
},
|
|
462
|
+
{ state: ExchangeStates.IDENTIFIED, timestamp: expect.any(Date) },
|
|
463
|
+
],
|
|
464
|
+
offerHashes: [],
|
|
465
|
+
...credentialTypesObject,
|
|
466
|
+
createdAt: expect.any(Date),
|
|
467
|
+
updatedAt: expect.any(Date),
|
|
468
|
+
})
|
|
469
|
+
);
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
it('email based matching should 401 if user not found', async () => {
|
|
473
|
+
const exchange = await persistOfferExchange({
|
|
474
|
+
tenant,
|
|
475
|
+
disclosure,
|
|
476
|
+
identityMatcherValues: ['adam.nomatch@example.com'],
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
const presentationEmail = await generateKYCPresentation(
|
|
480
|
+
exchange,
|
|
481
|
+
'email'
|
|
482
|
+
);
|
|
483
|
+
|
|
484
|
+
const response = await fastify.injectJson({
|
|
485
|
+
method: 'POST',
|
|
486
|
+
url: idUrl(tenant),
|
|
487
|
+
payload: {
|
|
488
|
+
jwt_vp: await presentationEmail.selfSign(),
|
|
489
|
+
exchange_id: exchange._id,
|
|
490
|
+
},
|
|
491
|
+
});
|
|
492
|
+
expect(response.statusCode).toEqual(401);
|
|
493
|
+
expect(response.json).toEqual(
|
|
494
|
+
errorResponseMatcher({
|
|
495
|
+
error: 'Unauthorized',
|
|
496
|
+
errorCode: 'integrated_identification_user_not_found',
|
|
497
|
+
message: 'User Not Found',
|
|
498
|
+
statusCode: 401,
|
|
499
|
+
})
|
|
500
|
+
);
|
|
501
|
+
|
|
502
|
+
const exchangeDBResult = await mongoDb()
|
|
503
|
+
.collection('exchanges')
|
|
504
|
+
.findOne({ _id: new ObjectId(exchange._id) });
|
|
505
|
+
expect(exchangeDBResult).toEqual(
|
|
506
|
+
mongoify({
|
|
507
|
+
...exchange,
|
|
508
|
+
tenantId: tenant._id,
|
|
509
|
+
presentationId: presentationEmail.presentation.id,
|
|
510
|
+
disclosureConsentedAt: expect.any(Date),
|
|
511
|
+
err: 'User Not Found',
|
|
512
|
+
events: [
|
|
513
|
+
{ state: ExchangeStates.NEW, timestamp: expect.any(Date) },
|
|
514
|
+
{
|
|
515
|
+
state: ExchangeStates.DISCLOSURE_RECEIVED,
|
|
516
|
+
timestamp: expect.any(Date),
|
|
517
|
+
},
|
|
518
|
+
{
|
|
519
|
+
state: ExchangeStates.DISCLOSURE_CHECKED,
|
|
520
|
+
timestamp: expect.any(Date),
|
|
521
|
+
},
|
|
522
|
+
{
|
|
523
|
+
state: ExchangeStates.NOT_IDENTIFIED,
|
|
524
|
+
timestamp: expect.any(Date),
|
|
525
|
+
},
|
|
526
|
+
],
|
|
527
|
+
offerHashes: [],
|
|
528
|
+
...credentialTypesObject,
|
|
529
|
+
createdAt: expect.any(Date),
|
|
530
|
+
updatedAt: expect.any(Date),
|
|
531
|
+
})
|
|
532
|
+
);
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
it('email based matching should 401 if credential tampered', async () => {
|
|
536
|
+
setMockVerifyCredentials({
|
|
537
|
+
...DEFAULT_CREDENTIAL_CHECKS,
|
|
538
|
+
UNTAMPERED: CredentialCheckResultValue.FAIL,
|
|
539
|
+
});
|
|
540
|
+
const exchange = await persistOfferExchange({
|
|
541
|
+
tenant,
|
|
542
|
+
disclosure,
|
|
543
|
+
identityMatcherValues: ['adam.smith@example.com'],
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
const presentationEmail = await generateKYCPresentation(
|
|
547
|
+
exchange,
|
|
548
|
+
'email'
|
|
549
|
+
);
|
|
550
|
+
|
|
551
|
+
const response = await fastify.injectJson({
|
|
552
|
+
method: 'POST',
|
|
553
|
+
url: idUrl(tenant),
|
|
554
|
+
payload: {
|
|
555
|
+
jwt_vp: await presentationEmail.selfSign(),
|
|
556
|
+
exchange_id: exchange._id,
|
|
557
|
+
},
|
|
558
|
+
});
|
|
559
|
+
expect(response.statusCode).toEqual(401);
|
|
560
|
+
|
|
561
|
+
const exchangeDBResult = await mongoDb()
|
|
562
|
+
.collection('exchanges')
|
|
563
|
+
.findOne({ _id: new ObjectId(exchange._id) });
|
|
564
|
+
expect(exchangeDBResult).toEqual(
|
|
565
|
+
mongoify({
|
|
566
|
+
...exchange,
|
|
567
|
+
tenantId: tenant._id,
|
|
568
|
+
presentationId: presentationEmail.presentation.id,
|
|
569
|
+
disclosureConsentedAt: expect.any(Date),
|
|
570
|
+
err: 'presentation_credential_tampered',
|
|
571
|
+
events: [
|
|
572
|
+
{ state: ExchangeStates.NEW, timestamp: expect.any(Date) },
|
|
573
|
+
{
|
|
574
|
+
state: ExchangeStates.DISCLOSURE_RECEIVED,
|
|
575
|
+
timestamp: expect.any(Date),
|
|
576
|
+
},
|
|
577
|
+
{
|
|
578
|
+
state: ExchangeStates.DISCLOSURE_CHECKED,
|
|
579
|
+
timestamp: expect.any(Date),
|
|
580
|
+
},
|
|
581
|
+
{
|
|
582
|
+
state: ExchangeStates.NOT_IDENTIFIED,
|
|
583
|
+
timestamp: expect.any(Date),
|
|
584
|
+
},
|
|
585
|
+
],
|
|
586
|
+
offerHashes: [],
|
|
587
|
+
...credentialTypesObject,
|
|
588
|
+
createdAt: expect.any(Date),
|
|
589
|
+
updatedAt: expect.any(Date),
|
|
590
|
+
})
|
|
591
|
+
);
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
it('email based matching should 401 if credential not from a trusted issuer', async () => {
|
|
595
|
+
setMockVerifyCredentials({
|
|
596
|
+
...DEFAULT_CREDENTIAL_CHECKS,
|
|
597
|
+
TRUSTED_ISSUER: CredentialCheckResultValue.FAIL,
|
|
598
|
+
});
|
|
599
|
+
const exchange = await persistOfferExchange({
|
|
600
|
+
tenant,
|
|
601
|
+
disclosure,
|
|
602
|
+
identityMatcherValues: ['adam.smith@example.com'],
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
const presentationEmail = await generateKYCPresentation(
|
|
606
|
+
exchange,
|
|
607
|
+
'email'
|
|
608
|
+
);
|
|
609
|
+
|
|
610
|
+
const response = await fastify.injectJson({
|
|
611
|
+
method: 'POST',
|
|
612
|
+
url: idUrl(tenant),
|
|
613
|
+
payload: {
|
|
614
|
+
jwt_vp: await presentationEmail.selfSign(),
|
|
615
|
+
exchange_id: exchange._id,
|
|
616
|
+
},
|
|
617
|
+
});
|
|
618
|
+
expect(response.statusCode).toEqual(401);
|
|
619
|
+
expect(response.json).toEqual(
|
|
620
|
+
errorResponseMatcher({
|
|
621
|
+
error: 'Unauthorized',
|
|
622
|
+
errorCode: 'presentation_credential_bad_issuer',
|
|
623
|
+
message: 'presentation_credential_bad_issuer',
|
|
624
|
+
statusCode: 401,
|
|
625
|
+
})
|
|
626
|
+
);
|
|
627
|
+
|
|
628
|
+
const exchangeDBResult = await mongoDb()
|
|
629
|
+
.collection('exchanges')
|
|
630
|
+
.findOne({ _id: new ObjectId(exchange._id) });
|
|
631
|
+
expect(exchangeDBResult).toEqual(
|
|
632
|
+
mongoify({
|
|
633
|
+
...exchange,
|
|
634
|
+
tenantId: tenant._id,
|
|
635
|
+
presentationId: presentationEmail.presentation.id,
|
|
636
|
+
disclosureConsentedAt: expect.any(Date),
|
|
637
|
+
err: 'presentation_credential_bad_issuer',
|
|
638
|
+
events: [
|
|
639
|
+
{ state: ExchangeStates.NEW, timestamp: expect.any(Date) },
|
|
640
|
+
{
|
|
641
|
+
state: ExchangeStates.DISCLOSURE_RECEIVED,
|
|
642
|
+
timestamp: expect.any(Date),
|
|
643
|
+
},
|
|
644
|
+
{
|
|
645
|
+
state: ExchangeStates.DISCLOSURE_CHECKED,
|
|
646
|
+
timestamp: expect.any(Date),
|
|
647
|
+
},
|
|
648
|
+
{
|
|
649
|
+
state: ExchangeStates.NOT_IDENTIFIED,
|
|
650
|
+
timestamp: expect.any(Date),
|
|
651
|
+
},
|
|
652
|
+
],
|
|
653
|
+
offerHashes: [],
|
|
654
|
+
...credentialTypesObject,
|
|
655
|
+
createdAt: expect.any(Date),
|
|
656
|
+
updatedAt: expect.any(Date),
|
|
657
|
+
})
|
|
658
|
+
);
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
it('email based matching should 401 if credential not from a trusted holder', async () => {
|
|
662
|
+
setMockVerifyCredentials({
|
|
663
|
+
...DEFAULT_CREDENTIAL_CHECKS,
|
|
664
|
+
TRUSTED_HOLDER: CredentialCheckResultValue.FAIL,
|
|
665
|
+
});
|
|
666
|
+
const exchange = await persistOfferExchange({
|
|
667
|
+
tenant,
|
|
668
|
+
disclosure,
|
|
669
|
+
identityMatcherValues: ['adam.smith@example.com'],
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
const presentationEmail = await generateKYCPresentation(
|
|
673
|
+
exchange,
|
|
674
|
+
'email'
|
|
675
|
+
);
|
|
676
|
+
|
|
677
|
+
const response = await fastify.injectJson({
|
|
678
|
+
method: 'POST',
|
|
679
|
+
url: idUrl(tenant),
|
|
680
|
+
payload: {
|
|
681
|
+
jwt_vp: await presentationEmail.selfSign(),
|
|
682
|
+
exchange_id: exchange._id,
|
|
683
|
+
},
|
|
684
|
+
});
|
|
685
|
+
expect(response.statusCode).toEqual(401);
|
|
686
|
+
|
|
687
|
+
const exchangeDBResult = await mongoDb()
|
|
688
|
+
.collection('exchanges')
|
|
689
|
+
.findOne({ _id: new ObjectId(exchange._id) });
|
|
690
|
+
expect(exchangeDBResult).toEqual(
|
|
691
|
+
mongoify({
|
|
692
|
+
...exchange,
|
|
693
|
+
tenantId: tenant._id,
|
|
694
|
+
presentationId: presentationEmail.presentation.id,
|
|
695
|
+
disclosureConsentedAt: expect.any(Date),
|
|
696
|
+
err: 'presentation_credential_bad_holder',
|
|
697
|
+
events: [
|
|
698
|
+
{ state: ExchangeStates.NEW, timestamp: expect.any(Date) },
|
|
699
|
+
{
|
|
700
|
+
state: ExchangeStates.DISCLOSURE_RECEIVED,
|
|
701
|
+
timestamp: expect.any(Date),
|
|
702
|
+
},
|
|
703
|
+
{
|
|
704
|
+
state: ExchangeStates.DISCLOSURE_CHECKED,
|
|
705
|
+
timestamp: expect.any(Date),
|
|
706
|
+
},
|
|
707
|
+
{
|
|
708
|
+
state: ExchangeStates.NOT_IDENTIFIED,
|
|
709
|
+
timestamp: expect.any(Date),
|
|
710
|
+
},
|
|
711
|
+
],
|
|
712
|
+
offerHashes: [],
|
|
713
|
+
...credentialTypesObject,
|
|
714
|
+
createdAt: expect.any(Date),
|
|
715
|
+
updatedAt: expect.any(Date),
|
|
716
|
+
})
|
|
717
|
+
);
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
it('email based matching should 401 if credential revoked', async () => {
|
|
721
|
+
setMockVerifyCredentials({
|
|
722
|
+
...DEFAULT_CREDENTIAL_CHECKS,
|
|
723
|
+
UNREVOKED: CredentialCheckResultValue.FAIL,
|
|
724
|
+
});
|
|
725
|
+
const exchange = await persistOfferExchange({
|
|
726
|
+
tenant,
|
|
727
|
+
disclosure,
|
|
728
|
+
identityMatcherValues: ['adam.smith@example.com'],
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
const presentationEmail = await generateKYCPresentation(
|
|
732
|
+
exchange,
|
|
733
|
+
'email'
|
|
734
|
+
);
|
|
735
|
+
|
|
736
|
+
const response = await fastify.injectJson({
|
|
737
|
+
method: 'POST',
|
|
738
|
+
url: idUrl(tenant),
|
|
739
|
+
payload: {
|
|
740
|
+
jwt_vp: await presentationEmail.selfSign(),
|
|
741
|
+
exchange_id: exchange._id,
|
|
742
|
+
},
|
|
743
|
+
});
|
|
744
|
+
expect(response.statusCode).toEqual(401);
|
|
745
|
+
|
|
746
|
+
const exchangeDBResult = await mongoDb()
|
|
747
|
+
.collection('exchanges')
|
|
748
|
+
.findOne({ _id: new ObjectId(exchange._id) });
|
|
749
|
+
expect(exchangeDBResult).toEqual(
|
|
750
|
+
mongoify({
|
|
751
|
+
...exchange,
|
|
752
|
+
tenantId: tenant._id,
|
|
753
|
+
presentationId: presentationEmail.presentation.id,
|
|
754
|
+
disclosureConsentedAt: expect.any(Date),
|
|
755
|
+
err: 'presentation_credential_revoked',
|
|
756
|
+
events: [
|
|
757
|
+
{ state: ExchangeStates.NEW, timestamp: expect.any(Date) },
|
|
758
|
+
{
|
|
759
|
+
state: ExchangeStates.DISCLOSURE_RECEIVED,
|
|
760
|
+
timestamp: expect.any(Date),
|
|
761
|
+
},
|
|
762
|
+
{
|
|
763
|
+
state: ExchangeStates.DISCLOSURE_CHECKED,
|
|
764
|
+
timestamp: expect.any(Date),
|
|
765
|
+
},
|
|
766
|
+
{
|
|
767
|
+
state: ExchangeStates.NOT_IDENTIFIED,
|
|
768
|
+
timestamp: expect.any(Date),
|
|
769
|
+
},
|
|
770
|
+
],
|
|
771
|
+
offerHashes: [],
|
|
772
|
+
...credentialTypesObject,
|
|
773
|
+
createdAt: expect.any(Date),
|
|
774
|
+
updatedAt: expect.any(Date),
|
|
775
|
+
})
|
|
776
|
+
);
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
it('email based matching should 401 if credential expired', async () => {
|
|
780
|
+
setMockVerifyCredentials({
|
|
781
|
+
...DEFAULT_CREDENTIAL_CHECKS,
|
|
782
|
+
UNEXPIRED: CredentialCheckResultValue.FAIL,
|
|
783
|
+
});
|
|
784
|
+
const exchange = await persistOfferExchange({
|
|
785
|
+
tenant,
|
|
786
|
+
disclosure,
|
|
787
|
+
identityMatcherValues: ['adam.smith@example.com'],
|
|
788
|
+
});
|
|
789
|
+
|
|
790
|
+
const presentationEmail = await generateKYCPresentation(
|
|
791
|
+
exchange,
|
|
792
|
+
'email'
|
|
793
|
+
);
|
|
794
|
+
|
|
795
|
+
const response = await fastify.injectJson({
|
|
796
|
+
method: 'POST',
|
|
797
|
+
url: idUrl(tenant),
|
|
798
|
+
payload: {
|
|
799
|
+
jwt_vp: await presentationEmail.selfSign(),
|
|
800
|
+
exchange_id: exchange._id,
|
|
801
|
+
},
|
|
802
|
+
});
|
|
803
|
+
expect(response.statusCode).toEqual(401);
|
|
804
|
+
|
|
805
|
+
const exchangeDBResult = await mongoDb()
|
|
806
|
+
.collection('exchanges')
|
|
807
|
+
.findOne({ _id: new ObjectId(exchange._id) });
|
|
808
|
+
expect(exchangeDBResult).toEqual(
|
|
809
|
+
mongoify({
|
|
810
|
+
...exchange,
|
|
811
|
+
tenantId: tenant._id,
|
|
812
|
+
presentationId: presentationEmail.presentation.id,
|
|
813
|
+
disclosureConsentedAt: expect.any(Date),
|
|
814
|
+
err: 'presentation_credential_expired',
|
|
815
|
+
events: [
|
|
816
|
+
{ state: ExchangeStates.NEW, timestamp: expect.any(Date) },
|
|
817
|
+
{
|
|
818
|
+
state: ExchangeStates.DISCLOSURE_RECEIVED,
|
|
819
|
+
timestamp: expect.any(Date),
|
|
820
|
+
},
|
|
821
|
+
{
|
|
822
|
+
state: ExchangeStates.DISCLOSURE_CHECKED,
|
|
823
|
+
timestamp: expect.any(Date),
|
|
824
|
+
},
|
|
825
|
+
{
|
|
826
|
+
state: ExchangeStates.NOT_IDENTIFIED,
|
|
827
|
+
timestamp: expect.any(Date),
|
|
828
|
+
},
|
|
829
|
+
],
|
|
830
|
+
offerHashes: [],
|
|
831
|
+
...credentialTypesObject,
|
|
832
|
+
createdAt: expect.any(Date),
|
|
833
|
+
updatedAt: expect.any(Date),
|
|
834
|
+
})
|
|
835
|
+
);
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
it('email based matching should match an existent user on second try', async () => {
|
|
839
|
+
const exchange = await persistOfferExchange({
|
|
840
|
+
tenant,
|
|
841
|
+
disclosure,
|
|
842
|
+
identityMatcherValues: ['adam.smith@example.com'],
|
|
843
|
+
events: [
|
|
844
|
+
{ state: ExchangeStates.NEW, timestamp: new Date() },
|
|
845
|
+
{
|
|
846
|
+
state: ExchangeStates.DISCLOSURE_RECEIVED,
|
|
847
|
+
timestamp: new Date(),
|
|
848
|
+
},
|
|
849
|
+
{
|
|
850
|
+
state: ExchangeStates.DISCLOSURE_CHECKED,
|
|
851
|
+
timestamp: new Date(),
|
|
852
|
+
},
|
|
853
|
+
{
|
|
854
|
+
state: ExchangeStates.NOT_IDENTIFIED,
|
|
855
|
+
timestamp: new Date(),
|
|
856
|
+
},
|
|
857
|
+
],
|
|
858
|
+
});
|
|
859
|
+
const vendorUserId = exchange.identityMatcherValues[0];
|
|
860
|
+
|
|
861
|
+
const presentationEmail = await generateKYCPresentation(
|
|
862
|
+
exchange,
|
|
863
|
+
'email'
|
|
864
|
+
);
|
|
865
|
+
|
|
866
|
+
const response = await fastify.injectJson({
|
|
867
|
+
method: 'POST',
|
|
868
|
+
url: idUrl(tenant),
|
|
869
|
+
payload: {
|
|
870
|
+
jwt_vp: await presentationEmail.selfSign(),
|
|
871
|
+
exchange_id: exchange._id,
|
|
872
|
+
},
|
|
873
|
+
});
|
|
874
|
+
expect(response.statusCode).toEqual(200);
|
|
875
|
+
expect(response.json).toEqual({
|
|
876
|
+
token: expect.any(String),
|
|
877
|
+
exchange: {
|
|
878
|
+
disclosureComplete: true,
|
|
879
|
+
exchangeComplete: false,
|
|
880
|
+
id: exchange._id,
|
|
881
|
+
type: ExchangeTypes.ISSUING,
|
|
882
|
+
},
|
|
883
|
+
});
|
|
884
|
+
|
|
885
|
+
const dbResult = await mongoDb()
|
|
886
|
+
.collection('vendorUserIdMappings')
|
|
887
|
+
.findOne({ vendorUserId });
|
|
888
|
+
const { sub } = decodeJwt(response.json.token);
|
|
889
|
+
expect(dbResult).toEqual({
|
|
890
|
+
_id: new ObjectId(sub),
|
|
891
|
+
vendorUserId,
|
|
892
|
+
tenantId: new ObjectId(tenant._id),
|
|
893
|
+
createdAt: expect.any(Date),
|
|
894
|
+
updatedAt: expect.any(Date),
|
|
895
|
+
});
|
|
896
|
+
const exchangeDBResult = await mongoDb()
|
|
897
|
+
.collection('exchanges')
|
|
898
|
+
.findOne({ _id: new ObjectId(exchange._id) });
|
|
899
|
+
expect(exchangeDBResult).toEqual(
|
|
900
|
+
mongoify({
|
|
901
|
+
...exchange,
|
|
902
|
+
tenantId: tenant._id,
|
|
903
|
+
presentationId: presentationEmail.presentation.id,
|
|
904
|
+
disclosureConsentedAt: expect.any(Date),
|
|
905
|
+
events: [
|
|
906
|
+
{ state: ExchangeStates.NEW, timestamp: expect.any(Date) },
|
|
907
|
+
{
|
|
908
|
+
state: ExchangeStates.DISCLOSURE_RECEIVED,
|
|
909
|
+
timestamp: expect.any(Date),
|
|
910
|
+
},
|
|
911
|
+
{
|
|
912
|
+
state: ExchangeStates.DISCLOSURE_CHECKED,
|
|
913
|
+
timestamp: expect.any(Date),
|
|
914
|
+
},
|
|
915
|
+
{
|
|
916
|
+
state: ExchangeStates.NOT_IDENTIFIED,
|
|
917
|
+
timestamp: expect.any(Date),
|
|
918
|
+
},
|
|
919
|
+
{
|
|
920
|
+
state: ExchangeStates.DISCLOSURE_RECEIVED,
|
|
921
|
+
timestamp: expect.any(Date),
|
|
922
|
+
},
|
|
923
|
+
{
|
|
924
|
+
state: ExchangeStates.DISCLOSURE_CHECKED,
|
|
925
|
+
timestamp: expect.any(Date),
|
|
926
|
+
},
|
|
927
|
+
{ state: ExchangeStates.IDENTIFIED, timestamp: expect.any(Date) },
|
|
928
|
+
],
|
|
929
|
+
offerHashes: [],
|
|
930
|
+
...credentialTypesObject,
|
|
931
|
+
createdAt: expect.any(Date),
|
|
932
|
+
updatedAt: expect.any(Date),
|
|
933
|
+
})
|
|
934
|
+
);
|
|
935
|
+
});
|
|
936
|
+
});
|
|
937
|
+
|
|
938
|
+
describe('"pick" rule matching using id credential', () => {
|
|
939
|
+
let disclosure;
|
|
940
|
+
|
|
941
|
+
beforeEach(async () => {
|
|
942
|
+
disclosure = await persistDisclosure({
|
|
943
|
+
tenant,
|
|
944
|
+
description: 'Credential Issuance disclosure',
|
|
945
|
+
types: [{ type: 'DriversLicenseV1.0' }],
|
|
946
|
+
vendorEndpoint: VendorEndpoint.INTEGRATED_ISSUING_IDENTIFICATION,
|
|
947
|
+
purpose: 'Identification',
|
|
948
|
+
duration: '6y',
|
|
949
|
+
identityMatchers: {
|
|
950
|
+
rules: [
|
|
951
|
+
{
|
|
952
|
+
valueIndex: 0, // used for identifying the value
|
|
953
|
+
path: [
|
|
954
|
+
'$.idDocumentCredentials[*].credentialSubject.identifier',
|
|
955
|
+
], // jsonPath within the credential
|
|
956
|
+
rule: 'pick', // Rule to execute can be pick, all (if the target is an array), equal (if the target is a singleValue)
|
|
957
|
+
},
|
|
958
|
+
],
|
|
959
|
+
vendorUserIdIndex: 0,
|
|
960
|
+
},
|
|
961
|
+
});
|
|
962
|
+
});
|
|
963
|
+
|
|
964
|
+
it('identifier based matching should match an existent user', async () => {
|
|
965
|
+
const exchange = await persistOfferExchange({
|
|
966
|
+
tenant,
|
|
967
|
+
disclosure,
|
|
968
|
+
identityMatcherValues: ['2200221100'],
|
|
969
|
+
});
|
|
970
|
+
const vendorUserId = exchange.identityMatcherValues[0];
|
|
971
|
+
|
|
972
|
+
const presentationEmail = await generateKYCPresentation(
|
|
973
|
+
exchange,
|
|
974
|
+
'driversLicense'
|
|
975
|
+
);
|
|
976
|
+
|
|
977
|
+
const response = await fastify.injectJson({
|
|
978
|
+
method: 'POST',
|
|
979
|
+
url: idUrl(tenant),
|
|
980
|
+
payload: {
|
|
981
|
+
jwt_vp: await presentationEmail.selfSign(),
|
|
982
|
+
exchange_id: exchange._id,
|
|
983
|
+
},
|
|
984
|
+
});
|
|
985
|
+
expect(response.statusCode).toEqual(200);
|
|
986
|
+
expect(response.json).toEqual({
|
|
987
|
+
token: expect.any(String),
|
|
988
|
+
exchange: {
|
|
989
|
+
disclosureComplete: true,
|
|
990
|
+
exchangeComplete: false,
|
|
991
|
+
id: exchange._id,
|
|
992
|
+
type: ExchangeTypes.ISSUING,
|
|
993
|
+
},
|
|
994
|
+
});
|
|
995
|
+
|
|
996
|
+
const dbResult = await mongoDb()
|
|
997
|
+
.collection('vendorUserIdMappings')
|
|
998
|
+
.findOne({ vendorUserId });
|
|
999
|
+
const { sub } = decodeJwt(response.json.token);
|
|
1000
|
+
expect(dbResult).toEqual({
|
|
1001
|
+
_id: new ObjectId(sub),
|
|
1002
|
+
vendorUserId,
|
|
1003
|
+
tenantId: new ObjectId(tenant._id),
|
|
1004
|
+
createdAt: expect.any(Date),
|
|
1005
|
+
updatedAt: expect.any(Date),
|
|
1006
|
+
});
|
|
1007
|
+
const exchangeDBResult = await mongoDb()
|
|
1008
|
+
.collection('exchanges')
|
|
1009
|
+
.findOne({ _id: new ObjectId(exchange._id) });
|
|
1010
|
+
expect(exchangeDBResult).toEqual(
|
|
1011
|
+
mongoify({
|
|
1012
|
+
...exchange,
|
|
1013
|
+
tenantId: tenant._id,
|
|
1014
|
+
presentationId: presentationEmail.presentation.id,
|
|
1015
|
+
disclosureConsentedAt: expect.any(Date),
|
|
1016
|
+
events: [
|
|
1017
|
+
{ state: ExchangeStates.NEW, timestamp: expect.any(Date) },
|
|
1018
|
+
{
|
|
1019
|
+
state: ExchangeStates.DISCLOSURE_RECEIVED,
|
|
1020
|
+
timestamp: expect.any(Date),
|
|
1021
|
+
},
|
|
1022
|
+
{
|
|
1023
|
+
state: ExchangeStates.DISCLOSURE_CHECKED,
|
|
1024
|
+
timestamp: expect.any(Date),
|
|
1025
|
+
},
|
|
1026
|
+
{ state: ExchangeStates.IDENTIFIED, timestamp: expect.any(Date) },
|
|
1027
|
+
],
|
|
1028
|
+
offerHashes: [],
|
|
1029
|
+
...credentialTypesObject,
|
|
1030
|
+
createdAt: expect.any(Date),
|
|
1031
|
+
updatedAt: expect.any(Date),
|
|
1032
|
+
})
|
|
1033
|
+
);
|
|
1034
|
+
});
|
|
1035
|
+
it('identifier based matching should 401 if user not found', async () => {
|
|
1036
|
+
const exchange = await persistOfferExchange({
|
|
1037
|
+
tenant,
|
|
1038
|
+
disclosure,
|
|
1039
|
+
identityMatcherValues: ['foo'],
|
|
1040
|
+
});
|
|
1041
|
+
|
|
1042
|
+
const presentationEmail = await generateKYCPresentation(
|
|
1043
|
+
exchange,
|
|
1044
|
+
'driversLicense'
|
|
1045
|
+
);
|
|
1046
|
+
|
|
1047
|
+
const response = await fastify.injectJson({
|
|
1048
|
+
method: 'POST',
|
|
1049
|
+
url: idUrl(tenant),
|
|
1050
|
+
payload: {
|
|
1051
|
+
jwt_vp: await presentationEmail.selfSign(),
|
|
1052
|
+
exchange_id: exchange._id,
|
|
1053
|
+
},
|
|
1054
|
+
});
|
|
1055
|
+
expect(response.statusCode).toEqual(401);
|
|
1056
|
+
expect(response.json).toEqual(
|
|
1057
|
+
errorResponseMatcher({
|
|
1058
|
+
error: 'Unauthorized',
|
|
1059
|
+
errorCode: 'integrated_identification_user_not_found',
|
|
1060
|
+
message: 'User Not Found',
|
|
1061
|
+
statusCode: 401,
|
|
1062
|
+
})
|
|
1063
|
+
);
|
|
1064
|
+
|
|
1065
|
+
const exchangeDBResult = await mongoDb()
|
|
1066
|
+
.collection('exchanges')
|
|
1067
|
+
.findOne({ _id: new ObjectId(exchange._id) });
|
|
1068
|
+
expect(exchangeDBResult).toEqual(
|
|
1069
|
+
mongoify({
|
|
1070
|
+
...exchange,
|
|
1071
|
+
tenantId: tenant._id,
|
|
1072
|
+
presentationId: presentationEmail.presentation.id,
|
|
1073
|
+
disclosureConsentedAt: expect.any(Date),
|
|
1074
|
+
err: 'User Not Found',
|
|
1075
|
+
events: [
|
|
1076
|
+
{ state: ExchangeStates.NEW, timestamp: expect.any(Date) },
|
|
1077
|
+
{
|
|
1078
|
+
state: ExchangeStates.DISCLOSURE_RECEIVED,
|
|
1079
|
+
timestamp: expect.any(Date),
|
|
1080
|
+
},
|
|
1081
|
+
{
|
|
1082
|
+
state: ExchangeStates.DISCLOSURE_CHECKED,
|
|
1083
|
+
timestamp: expect.any(Date),
|
|
1084
|
+
},
|
|
1085
|
+
{
|
|
1086
|
+
state: ExchangeStates.NOT_IDENTIFIED,
|
|
1087
|
+
timestamp: expect.any(Date),
|
|
1088
|
+
},
|
|
1089
|
+
],
|
|
1090
|
+
offerHashes: [],
|
|
1091
|
+
...credentialTypesObject,
|
|
1092
|
+
createdAt: expect.any(Date),
|
|
1093
|
+
updatedAt: expect.any(Date),
|
|
1094
|
+
})
|
|
1095
|
+
);
|
|
1096
|
+
});
|
|
1097
|
+
});
|
|
1098
|
+
|
|
1099
|
+
describe('"all" rule for givenName matching', () => {
|
|
1100
|
+
let disclosure;
|
|
1101
|
+
let exchange;
|
|
1102
|
+
let vendorUserId;
|
|
1103
|
+
|
|
1104
|
+
beforeEach(async () => {
|
|
1105
|
+
disclosure = await persistDisclosure({
|
|
1106
|
+
tenant,
|
|
1107
|
+
description: 'Credential Issuance disclosure',
|
|
1108
|
+
types: [{ type: 'EmailV1.0' }],
|
|
1109
|
+
vendorEndpoint: VendorEndpoint.INTEGRATED_ISSUING_IDENTIFICATION,
|
|
1110
|
+
purpose: 'Identification',
|
|
1111
|
+
duration: '6y',
|
|
1112
|
+
identityMatchers: {
|
|
1113
|
+
rules: [
|
|
1114
|
+
{
|
|
1115
|
+
valueIndex: 0, // used for identifying the value
|
|
1116
|
+
path: [
|
|
1117
|
+
'$.idDocumentCredentials[*].credentialSubject.person.givenName',
|
|
1118
|
+
], // jsonPath within the credential
|
|
1119
|
+
rule: 'all', // Rule to execute can be pick, all (if the target is an array), equal (if the target is a singleValue)
|
|
1120
|
+
},
|
|
1121
|
+
],
|
|
1122
|
+
vendorUserIdIndex: 0,
|
|
1123
|
+
},
|
|
1124
|
+
});
|
|
1125
|
+
exchange = await persistOfferExchange({
|
|
1126
|
+
tenant,
|
|
1127
|
+
disclosure,
|
|
1128
|
+
identityMatcherValues: ['sam'],
|
|
1129
|
+
});
|
|
1130
|
+
vendorUserId = exchange.identityMatcherValues[0];
|
|
1131
|
+
});
|
|
1132
|
+
|
|
1133
|
+
it('given name matching should 200 if user found', async () => {
|
|
1134
|
+
const presentationEmail = await generateKYCPresentation(
|
|
1135
|
+
exchange,
|
|
1136
|
+
'idDocument'
|
|
1137
|
+
);
|
|
1138
|
+
|
|
1139
|
+
const response = await fastify.injectJson({
|
|
1140
|
+
method: 'POST',
|
|
1141
|
+
url: idUrl(tenant),
|
|
1142
|
+
headers: {
|
|
1143
|
+
'x-vnf-protocol-version': `${VnfProtocolVersions.VNF_PROTOCOL_VERSION_2}`,
|
|
1144
|
+
},
|
|
1145
|
+
payload: {
|
|
1146
|
+
jwt_vp: await presentationEmail.sign(
|
|
1147
|
+
holderKid,
|
|
1148
|
+
holderKeys.privateKey,
|
|
1149
|
+
holderDid
|
|
1150
|
+
),
|
|
1151
|
+
exchange_id: exchange._id,
|
|
1152
|
+
},
|
|
1153
|
+
});
|
|
1154
|
+
expect(response.statusCode).toEqual(200);
|
|
1155
|
+
expect(response.json).toEqual({
|
|
1156
|
+
token: expect.any(String),
|
|
1157
|
+
exchange: {
|
|
1158
|
+
disclosureComplete: true,
|
|
1159
|
+
exchangeComplete: false,
|
|
1160
|
+
id: exchange._id,
|
|
1161
|
+
type: ExchangeTypes.ISSUING,
|
|
1162
|
+
},
|
|
1163
|
+
});
|
|
1164
|
+
|
|
1165
|
+
const dbResult = await mongoDb()
|
|
1166
|
+
.collection('vendorUserIdMappings')
|
|
1167
|
+
.findOne({ vendorUserId });
|
|
1168
|
+
const { sub } = decodeJwt(response.json.token);
|
|
1169
|
+
expect(dbResult).toEqual({
|
|
1170
|
+
_id: new ObjectId(sub),
|
|
1171
|
+
vendorUserId,
|
|
1172
|
+
tenantId: new ObjectId(tenant._id),
|
|
1173
|
+
createdAt: expect.any(Date),
|
|
1174
|
+
updatedAt: expect.any(Date),
|
|
1175
|
+
});
|
|
1176
|
+
const exchangeDBResult = await mongoDb()
|
|
1177
|
+
.collection('exchanges')
|
|
1178
|
+
.findOne({ _id: new ObjectId(exchange._id) });
|
|
1179
|
+
expect(exchangeDBResult).toEqual(
|
|
1180
|
+
mongoify({
|
|
1181
|
+
...exchange,
|
|
1182
|
+
tenantId: tenant._id,
|
|
1183
|
+
presentationId: presentationEmail.presentation.id,
|
|
1184
|
+
disclosureConsentedAt: expect.any(Date),
|
|
1185
|
+
events: [
|
|
1186
|
+
{ state: ExchangeStates.NEW, timestamp: expect.any(Date) },
|
|
1187
|
+
{
|
|
1188
|
+
state: ExchangeStates.DISCLOSURE_RECEIVED,
|
|
1189
|
+
timestamp: expect.any(Date),
|
|
1190
|
+
},
|
|
1191
|
+
{
|
|
1192
|
+
state: ExchangeStates.DISCLOSURE_CHECKED,
|
|
1193
|
+
timestamp: expect.any(Date),
|
|
1194
|
+
},
|
|
1195
|
+
{ state: ExchangeStates.IDENTIFIED, timestamp: expect.any(Date) },
|
|
1196
|
+
],
|
|
1197
|
+
offerHashes: [],
|
|
1198
|
+
...credentialTypesObject,
|
|
1199
|
+
createdAt: expect.any(Date),
|
|
1200
|
+
updatedAt: expect.any(Date),
|
|
1201
|
+
})
|
|
1202
|
+
);
|
|
1203
|
+
});
|
|
1204
|
+
it('given name matching on all docs should 200 if user found', async () => {
|
|
1205
|
+
const presentationEmail = await generateKYCPresentation(exchange, [
|
|
1206
|
+
'idDocument',
|
|
1207
|
+
'driversLicense',
|
|
1208
|
+
]);
|
|
1209
|
+
|
|
1210
|
+
const response = await fastify.injectJson({
|
|
1211
|
+
method: 'POST',
|
|
1212
|
+
url: idUrl(tenant),
|
|
1213
|
+
headers: {
|
|
1214
|
+
'x-vnf-protocol-version': `${VnfProtocolVersions.VNF_PROTOCOL_VERSION_2}`,
|
|
1215
|
+
},
|
|
1216
|
+
payload: {
|
|
1217
|
+
jwt_vp: await presentationEmail.sign(
|
|
1218
|
+
holderKid,
|
|
1219
|
+
holderKeys.privateKey,
|
|
1220
|
+
holderDid
|
|
1221
|
+
),
|
|
1222
|
+
exchange_id: exchange._id,
|
|
1223
|
+
},
|
|
1224
|
+
});
|
|
1225
|
+
expect(response.statusCode).toEqual(200);
|
|
1226
|
+
expect(response.json).toEqual({
|
|
1227
|
+
token: expect.any(String),
|
|
1228
|
+
exchange: {
|
|
1229
|
+
disclosureComplete: true,
|
|
1230
|
+
exchangeComplete: false,
|
|
1231
|
+
id: exchange._id,
|
|
1232
|
+
type: ExchangeTypes.ISSUING,
|
|
1233
|
+
},
|
|
1234
|
+
});
|
|
1235
|
+
|
|
1236
|
+
const dbResult = await mongoDb()
|
|
1237
|
+
.collection('vendorUserIdMappings')
|
|
1238
|
+
.findOne({ vendorUserId });
|
|
1239
|
+
const { sub } = decodeJwt(response.json.token);
|
|
1240
|
+
expect(dbResult).toEqual({
|
|
1241
|
+
_id: new ObjectId(sub),
|
|
1242
|
+
vendorUserId,
|
|
1243
|
+
tenantId: new ObjectId(tenant._id),
|
|
1244
|
+
createdAt: expect.any(Date),
|
|
1245
|
+
updatedAt: expect.any(Date),
|
|
1246
|
+
});
|
|
1247
|
+
const exchangeDBResult = await mongoDb()
|
|
1248
|
+
.collection('exchanges')
|
|
1249
|
+
.findOne({ _id: new ObjectId(exchange._id) });
|
|
1250
|
+
expect(exchangeDBResult).toEqual(
|
|
1251
|
+
mongoify({
|
|
1252
|
+
...exchange,
|
|
1253
|
+
tenantId: tenant._id,
|
|
1254
|
+
presentationId: presentationEmail.presentation.id,
|
|
1255
|
+
disclosureConsentedAt: expect.any(Date),
|
|
1256
|
+
events: [
|
|
1257
|
+
{ state: ExchangeStates.NEW, timestamp: expect.any(Date) },
|
|
1258
|
+
{
|
|
1259
|
+
state: ExchangeStates.DISCLOSURE_RECEIVED,
|
|
1260
|
+
timestamp: expect.any(Date),
|
|
1261
|
+
},
|
|
1262
|
+
{
|
|
1263
|
+
state: ExchangeStates.DISCLOSURE_CHECKED,
|
|
1264
|
+
timestamp: expect.any(Date),
|
|
1265
|
+
},
|
|
1266
|
+
{ state: ExchangeStates.IDENTIFIED, timestamp: expect.any(Date) },
|
|
1267
|
+
],
|
|
1268
|
+
offerHashes: [],
|
|
1269
|
+
...credentialTypesObject,
|
|
1270
|
+
createdAt: expect.any(Date),
|
|
1271
|
+
updatedAt: expect.any(Date),
|
|
1272
|
+
})
|
|
1273
|
+
);
|
|
1274
|
+
});
|
|
1275
|
+
it('given name matching should 401 if user not found', async () => {
|
|
1276
|
+
const noMatchExchange = await persistOfferExchange({
|
|
1277
|
+
tenant,
|
|
1278
|
+
disclosure,
|
|
1279
|
+
identityMatcherValues: ['NotMatchingName'],
|
|
1280
|
+
});
|
|
1281
|
+
|
|
1282
|
+
const presentationEmail = await generateKYCPresentation(
|
|
1283
|
+
noMatchExchange,
|
|
1284
|
+
'idDocument'
|
|
1285
|
+
);
|
|
1286
|
+
|
|
1287
|
+
const response = await fastify.injectJson({
|
|
1288
|
+
method: 'POST',
|
|
1289
|
+
headers: {
|
|
1290
|
+
'x-vnf-protocol-version': `${VnfProtocolVersions.VNF_PROTOCOL_VERSION_2}`,
|
|
1291
|
+
},
|
|
1292
|
+
url: idUrl(tenant),
|
|
1293
|
+
payload: {
|
|
1294
|
+
jwt_vp: await presentationEmail.sign(
|
|
1295
|
+
holderKid,
|
|
1296
|
+
holderKeys.privateKey,
|
|
1297
|
+
holderDid
|
|
1298
|
+
),
|
|
1299
|
+
exchange_id: noMatchExchange._id,
|
|
1300
|
+
},
|
|
1301
|
+
});
|
|
1302
|
+
expect(response.statusCode).toEqual(401);
|
|
1303
|
+
expect(response.json).toEqual(
|
|
1304
|
+
errorResponseMatcher({
|
|
1305
|
+
error: 'Unauthorized',
|
|
1306
|
+
errorCode: 'integrated_identification_user_not_found',
|
|
1307
|
+
message: 'User Not Found',
|
|
1308
|
+
statusCode: 401,
|
|
1309
|
+
})
|
|
1310
|
+
);
|
|
1311
|
+
|
|
1312
|
+
const exchangeDBResult = await mongoDb()
|
|
1313
|
+
.collection('exchanges')
|
|
1314
|
+
.findOne({ _id: new ObjectId(noMatchExchange._id) });
|
|
1315
|
+
expect(exchangeDBResult).toEqual(
|
|
1316
|
+
mongoify({
|
|
1317
|
+
...noMatchExchange,
|
|
1318
|
+
tenantId: tenant._id,
|
|
1319
|
+
presentationId: presentationEmail.presentation.id,
|
|
1320
|
+
disclosureConsentedAt: expect.any(Date),
|
|
1321
|
+
err: 'User Not Found',
|
|
1322
|
+
events: [
|
|
1323
|
+
{ state: ExchangeStates.NEW, timestamp: expect.any(Date) },
|
|
1324
|
+
{
|
|
1325
|
+
state: ExchangeStates.DISCLOSURE_RECEIVED,
|
|
1326
|
+
timestamp: expect.any(Date),
|
|
1327
|
+
},
|
|
1328
|
+
{
|
|
1329
|
+
state: ExchangeStates.DISCLOSURE_CHECKED,
|
|
1330
|
+
timestamp: expect.any(Date),
|
|
1331
|
+
},
|
|
1332
|
+
{
|
|
1333
|
+
state: ExchangeStates.NOT_IDENTIFIED,
|
|
1334
|
+
timestamp: expect.any(Date),
|
|
1335
|
+
},
|
|
1336
|
+
],
|
|
1337
|
+
offerHashes: [],
|
|
1338
|
+
...credentialTypesObject,
|
|
1339
|
+
createdAt: expect.any(Date),
|
|
1340
|
+
updatedAt: expect.any(Date),
|
|
1341
|
+
})
|
|
1342
|
+
);
|
|
1343
|
+
});
|
|
1344
|
+
});
|
|
1345
|
+
|
|
1346
|
+
describe('associated exchanges test suite', () => {
|
|
1347
|
+
it('should error if rule isnt "all", "pick" or "equal"', async () => {
|
|
1348
|
+
const disclosure = await persistDisclosure({
|
|
1349
|
+
tenant,
|
|
1350
|
+
description: 'Credential Issuance disclosure',
|
|
1351
|
+
types: [{ type: 'EmailV1.0' }],
|
|
1352
|
+
vendorEndpoint: VendorEndpoint.INTEGRATED_ISSUING_IDENTIFICATION,
|
|
1353
|
+
purpose: 'Identification',
|
|
1354
|
+
duration: '6y',
|
|
1355
|
+
identityMatchers: {
|
|
1356
|
+
rules: [
|
|
1357
|
+
{
|
|
1358
|
+
valueIndex: 0, // used for identifying the value
|
|
1359
|
+
path: ['$.emails'], // jsonPath within the credential
|
|
1360
|
+
rule: 'notARule', // Rule to execute can be pick, all (if the target is an array), equal (if the target is a singleValue)
|
|
1361
|
+
},
|
|
1362
|
+
],
|
|
1363
|
+
vendorUserIdIndex: 0,
|
|
1364
|
+
},
|
|
1365
|
+
});
|
|
1366
|
+
|
|
1367
|
+
const exchange = await persistOfferExchange({
|
|
1368
|
+
tenant,
|
|
1369
|
+
disclosure,
|
|
1370
|
+
});
|
|
1371
|
+
await persistOfferExchange({
|
|
1372
|
+
tenant,
|
|
1373
|
+
disclosure,
|
|
1374
|
+
identityMatcherValues: ['adam.smith@example.com'],
|
|
1375
|
+
});
|
|
1376
|
+
|
|
1377
|
+
const presentationEmail = await generateKYCPresentation(
|
|
1378
|
+
exchange,
|
|
1379
|
+
'email'
|
|
1380
|
+
);
|
|
1381
|
+
|
|
1382
|
+
const response = await fastify.injectJson({
|
|
1383
|
+
method: 'POST',
|
|
1384
|
+
headers: {
|
|
1385
|
+
'x-vnf-protocol-version': `${VnfProtocolVersions.VNF_PROTOCOL_VERSION_2}`,
|
|
1386
|
+
},
|
|
1387
|
+
url: idUrl(tenant),
|
|
1388
|
+
payload: {
|
|
1389
|
+
jwt_vp: await presentationEmail.sign(
|
|
1390
|
+
holderKid,
|
|
1391
|
+
holderKeys.privateKey,
|
|
1392
|
+
holderDid
|
|
1393
|
+
),
|
|
1394
|
+
exchange_id: exchange._id,
|
|
1395
|
+
},
|
|
1396
|
+
});
|
|
1397
|
+
expect(response.statusCode).toEqual(500);
|
|
1398
|
+
expect(response.json).toEqual(
|
|
1399
|
+
errorResponseMatcher({
|
|
1400
|
+
error: 'Internal Server Error',
|
|
1401
|
+
message:
|
|
1402
|
+
'Credential Agent only supports "pick" or "all" for "identityMatchers.rule"',
|
|
1403
|
+
statusCode: 500,
|
|
1404
|
+
errorCode: 'missing_error_code',
|
|
1405
|
+
})
|
|
1406
|
+
);
|
|
1407
|
+
});
|
|
1408
|
+
|
|
1409
|
+
it('should match with email based matching and "pick" rule', async () => {
|
|
1410
|
+
const disclosure = await persistDisclosure({
|
|
1411
|
+
tenant,
|
|
1412
|
+
description: 'Credential Issuance disclosure',
|
|
1413
|
+
types: [{ type: 'EmailV1.0' }],
|
|
1414
|
+
vendorEndpoint: VendorEndpoint.INTEGRATED_ISSUING_IDENTIFICATION,
|
|
1415
|
+
purpose: 'Identification',
|
|
1416
|
+
duration: '6y',
|
|
1417
|
+
identityMatchers: {
|
|
1418
|
+
rules: [
|
|
1419
|
+
{
|
|
1420
|
+
valueIndex: 0, // used for identifying the value
|
|
1421
|
+
path: ['$.emails'], // jsonPath within the credential
|
|
1422
|
+
rule: 'pick', // Rule to execute can be pick, all (if the target is an array), equal (if the target is a singleValue)
|
|
1423
|
+
},
|
|
1424
|
+
],
|
|
1425
|
+
vendorUserIdIndex: 0,
|
|
1426
|
+
},
|
|
1427
|
+
});
|
|
1428
|
+
const exchangeWithoutIdentityMatcherValues = await persistOfferExchange(
|
|
1429
|
+
{
|
|
1430
|
+
tenant,
|
|
1431
|
+
disclosure,
|
|
1432
|
+
}
|
|
1433
|
+
);
|
|
1434
|
+
|
|
1435
|
+
await persistOfferExchange({
|
|
1436
|
+
tenant,
|
|
1437
|
+
disclosure,
|
|
1438
|
+
identityMatcherValues: ['not-match.olivia@example.com'],
|
|
1439
|
+
});
|
|
1440
|
+
await persistOfferExchange({
|
|
1441
|
+
tenant,
|
|
1442
|
+
disclosure,
|
|
1443
|
+
identityMatcherValues: ['Adam.smith@example.com'],
|
|
1444
|
+
});
|
|
1445
|
+
const vendorUserId = 'Adam.smith@example.com';
|
|
1446
|
+
|
|
1447
|
+
const presentationEmail = await generateKYCPresentation(
|
|
1448
|
+
exchangeWithoutIdentityMatcherValues,
|
|
1449
|
+
'email'
|
|
1450
|
+
);
|
|
1451
|
+
|
|
1452
|
+
const response = await fastify.injectJson({
|
|
1453
|
+
method: 'POST',
|
|
1454
|
+
headers: {
|
|
1455
|
+
'x-vnf-protocol-version': `${VnfProtocolVersions.VNF_PROTOCOL_VERSION_2}`,
|
|
1456
|
+
},
|
|
1457
|
+
url: idUrl(tenant),
|
|
1458
|
+
payload: {
|
|
1459
|
+
jwt_vp: await presentationEmail.sign(
|
|
1460
|
+
holderKid,
|
|
1461
|
+
holderKeys.privateKey,
|
|
1462
|
+
holderDid
|
|
1463
|
+
),
|
|
1464
|
+
exchange_id: exchangeWithoutIdentityMatcherValues._id,
|
|
1465
|
+
},
|
|
1466
|
+
});
|
|
1467
|
+
expect(response.statusCode).toEqual(200);
|
|
1468
|
+
expect(response.json).toEqual({
|
|
1469
|
+
token: expect.any(String),
|
|
1470
|
+
exchange: {
|
|
1471
|
+
disclosureComplete: true,
|
|
1472
|
+
exchangeComplete: false,
|
|
1473
|
+
id: exchangeWithoutIdentityMatcherValues._id,
|
|
1474
|
+
type: ExchangeTypes.ISSUING,
|
|
1475
|
+
},
|
|
1476
|
+
});
|
|
1477
|
+
|
|
1478
|
+
const dbResult = await mongoDb()
|
|
1479
|
+
.collection('vendorUserIdMappings')
|
|
1480
|
+
.findOne({ vendorUserId });
|
|
1481
|
+
const { sub } = decodeJwt(response.json.token);
|
|
1482
|
+
expect(dbResult).toEqual({
|
|
1483
|
+
_id: new ObjectId(sub),
|
|
1484
|
+
vendorUserId,
|
|
1485
|
+
tenantId: new ObjectId(tenant._id),
|
|
1486
|
+
createdAt: expect.any(Date),
|
|
1487
|
+
updatedAt: expect.any(Date),
|
|
1488
|
+
});
|
|
1489
|
+
const exchangeDBResult = await mongoDb()
|
|
1490
|
+
.collection('exchanges')
|
|
1491
|
+
.findOne({
|
|
1492
|
+
_id: new ObjectId(exchangeWithoutIdentityMatcherValues._id),
|
|
1493
|
+
});
|
|
1494
|
+
expect(exchangeDBResult).toEqual(
|
|
1495
|
+
mongoify({
|
|
1496
|
+
...exchangeWithoutIdentityMatcherValues,
|
|
1497
|
+
tenantId: tenant._id,
|
|
1498
|
+
presentationId: presentationEmail.presentation.id,
|
|
1499
|
+
disclosureConsentedAt: expect.any(Date),
|
|
1500
|
+
events: [
|
|
1501
|
+
{ state: ExchangeStates.NEW, timestamp: expect.any(Date) },
|
|
1502
|
+
{
|
|
1503
|
+
state: ExchangeStates.DISCLOSURE_RECEIVED,
|
|
1504
|
+
timestamp: expect.any(Date),
|
|
1505
|
+
},
|
|
1506
|
+
{
|
|
1507
|
+
state: ExchangeStates.DISCLOSURE_CHECKED,
|
|
1508
|
+
timestamp: expect.any(Date),
|
|
1509
|
+
},
|
|
1510
|
+
{ state: ExchangeStates.IDENTIFIED, timestamp: expect.any(Date) },
|
|
1511
|
+
],
|
|
1512
|
+
offerHashes: [],
|
|
1513
|
+
...credentialTypesObject,
|
|
1514
|
+
createdAt: expect.any(Date),
|
|
1515
|
+
updatedAt: expect.any(Date),
|
|
1516
|
+
})
|
|
1517
|
+
);
|
|
1518
|
+
});
|
|
1519
|
+
});
|
|
1520
|
+
});
|
|
1521
|
+
|
|
1522
|
+
describe('using the webhook', () => {
|
|
1523
|
+
let exchange;
|
|
1524
|
+
let disclosure;
|
|
1525
|
+
let vendorUserId;
|
|
1526
|
+
|
|
1527
|
+
beforeEach(async () => {
|
|
1528
|
+
fastify.resetOverrides();
|
|
1529
|
+
disclosure = await persistDisclosure({
|
|
1530
|
+
tenant,
|
|
1531
|
+
description: 'Credential Issuance disclosure',
|
|
1532
|
+
types: [{ type: 'EmailV1.0' }],
|
|
1533
|
+
vendorEndpoint: VendorEndpoint.ISSUING_IDENTIFICATION,
|
|
1534
|
+
purpose: 'Identification',
|
|
1535
|
+
duration: '6y',
|
|
1536
|
+
});
|
|
1537
|
+
({ vendorUserId } = await newVendorUserIdMapping());
|
|
1538
|
+
exchange = await persistOfferExchange({ tenant, disclosure });
|
|
1539
|
+
});
|
|
1540
|
+
|
|
1541
|
+
it('should 200 when the presentation is empty but the vendorOriginContext is sent', async () => {
|
|
1542
|
+
const preauthDisclosure = await persistDisclosure({
|
|
1543
|
+
tenant,
|
|
1544
|
+
description: 'Credential Issuance disclosure',
|
|
1545
|
+
identificationMethods: ['preauth'],
|
|
1546
|
+
vendorEndpoint: VendorEndpoint.ISSUING_IDENTIFICATION,
|
|
1547
|
+
purpose: 'Identification',
|
|
1548
|
+
duration: '6y',
|
|
1549
|
+
});
|
|
1550
|
+
const emptyDisclosureExchange = await persistOfferExchange({
|
|
1551
|
+
tenant,
|
|
1552
|
+
disclosure: preauthDisclosure,
|
|
1553
|
+
});
|
|
1554
|
+
const presentationIdDocument = await generateKYCPresentation(
|
|
1555
|
+
emptyDisclosureExchange,
|
|
1556
|
+
'nothing'
|
|
1557
|
+
);
|
|
1558
|
+
presentationIdDocument.presentation.vendorOriginContext = '123';
|
|
1559
|
+
|
|
1560
|
+
let identifyWebhookPayload;
|
|
1561
|
+
nock(mockVendorUrl)
|
|
1562
|
+
.post(identifyUserOnVendorEndpoint)
|
|
1563
|
+
.reply(200, (uri, body) => {
|
|
1564
|
+
identifyWebhookPayload = body;
|
|
1565
|
+
return {
|
|
1566
|
+
vendorUserId,
|
|
1567
|
+
};
|
|
1568
|
+
});
|
|
1569
|
+
|
|
1570
|
+
const response = await fastify.injectJson({
|
|
1571
|
+
method: 'POST',
|
|
1572
|
+
url: idUrl(tenant),
|
|
1573
|
+
headers: {
|
|
1574
|
+
'x-vnf-protocol-version': `${VnfProtocolVersions.VNF_PROTOCOL_VERSION_2}`,
|
|
1575
|
+
},
|
|
1576
|
+
payload: {
|
|
1577
|
+
exchange_id: emptyDisclosureExchange._id,
|
|
1578
|
+
jwt_vp: await presentationIdDocument.sign(
|
|
1579
|
+
holderKid,
|
|
1580
|
+
holderKeys.privateKey,
|
|
1581
|
+
holderDid
|
|
1582
|
+
),
|
|
1583
|
+
},
|
|
1584
|
+
});
|
|
1585
|
+
|
|
1586
|
+
expect(response.statusCode).toEqual(200);
|
|
1587
|
+
expect(response.json).toEqual({
|
|
1588
|
+
token: expect.any(String),
|
|
1589
|
+
exchange: {
|
|
1590
|
+
disclosureComplete: true,
|
|
1591
|
+
exchangeComplete: false,
|
|
1592
|
+
id: emptyDisclosureExchange._id,
|
|
1593
|
+
type: ExchangeTypes.ISSUING,
|
|
1594
|
+
},
|
|
1595
|
+
});
|
|
1596
|
+
const dbResult = await mongoDb()
|
|
1597
|
+
.collection('vendorUserIdMappings')
|
|
1598
|
+
.findOne({ vendorUserId });
|
|
1599
|
+
const { sub } = decodeJwt(response.json.token);
|
|
1600
|
+
expect(dbResult).toEqual({
|
|
1601
|
+
_id: new ObjectId(sub),
|
|
1602
|
+
vendorUserId,
|
|
1603
|
+
tenantId: new ObjectId(tenant._id),
|
|
1604
|
+
createdAt: expect.any(Date),
|
|
1605
|
+
updatedAt: expect.any(Date),
|
|
1606
|
+
});
|
|
1607
|
+
expect(
|
|
1608
|
+
await mongoDb()
|
|
1609
|
+
.collection('exchanges')
|
|
1610
|
+
.findOne({ _id: new ObjectId(emptyDisclosureExchange._id) })
|
|
1611
|
+
).toEqual(
|
|
1612
|
+
expectedExchange(
|
|
1613
|
+
tenant,
|
|
1614
|
+
emptyDisclosureExchange,
|
|
1615
|
+
presentationIdDocument,
|
|
1616
|
+
[
|
|
1617
|
+
ExchangeStates.NEW,
|
|
1618
|
+
ExchangeStates.DISCLOSURE_RECEIVED,
|
|
1619
|
+
ExchangeStates.DISCLOSURE_CHECKED,
|
|
1620
|
+
ExchangeStates.IDENTIFIED,
|
|
1621
|
+
]
|
|
1622
|
+
)
|
|
1623
|
+
);
|
|
1624
|
+
expect(identifyWebhookPayload).toEqual({
|
|
1625
|
+
emailCredentials: [],
|
|
1626
|
+
emails: [],
|
|
1627
|
+
exchangeId: emptyDisclosureExchange._id,
|
|
1628
|
+
idDocumentCredentials: [],
|
|
1629
|
+
phoneCredentials: [],
|
|
1630
|
+
phones: [],
|
|
1631
|
+
vendorOriginContext: '123',
|
|
1632
|
+
tenantDID: tenant.did,
|
|
1633
|
+
tenantId: tenant._id,
|
|
1634
|
+
});
|
|
1635
|
+
});
|
|
1636
|
+
|
|
1637
|
+
it('should 200 when sub is new', async () => {
|
|
1638
|
+
const presentationIdDocument = await generateKYCPresentation(
|
|
1639
|
+
exchange,
|
|
1640
|
+
'idDocument'
|
|
1641
|
+
);
|
|
1642
|
+
|
|
1643
|
+
let identifyWebhookPayload;
|
|
1644
|
+
nock(mockVendorUrl)
|
|
1645
|
+
.post(identifyUserOnVendorEndpoint)
|
|
1646
|
+
.reply(200, (uri, body) => {
|
|
1647
|
+
identifyWebhookPayload = body;
|
|
1648
|
+
return {
|
|
1649
|
+
vendorUserId,
|
|
1650
|
+
};
|
|
1651
|
+
});
|
|
1652
|
+
|
|
1653
|
+
const response = await fastify.injectJson({
|
|
1654
|
+
method: 'POST',
|
|
1655
|
+
url: idUrl(tenant),
|
|
1656
|
+
headers: {
|
|
1657
|
+
'x-vnf-protocol-version': `${VnfProtocolVersions.VNF_PROTOCOL_VERSION_2}`,
|
|
1658
|
+
},
|
|
1659
|
+
payload: {
|
|
1660
|
+
exchange_id: exchange._id,
|
|
1661
|
+
jwt_vp: await presentationIdDocument.sign(
|
|
1662
|
+
holderKid,
|
|
1663
|
+
holderKeys.privateKey,
|
|
1664
|
+
holderDid
|
|
1665
|
+
),
|
|
1666
|
+
},
|
|
1667
|
+
});
|
|
1668
|
+
|
|
1669
|
+
expect(response.statusCode).toEqual(200);
|
|
1670
|
+
expect(response.json).toEqual({
|
|
1671
|
+
token: expect.any(String),
|
|
1672
|
+
exchange: {
|
|
1673
|
+
disclosureComplete: true,
|
|
1674
|
+
exchangeComplete: false,
|
|
1675
|
+
id: exchange._id,
|
|
1676
|
+
type: ExchangeTypes.ISSUING,
|
|
1677
|
+
},
|
|
1678
|
+
});
|
|
1679
|
+
const dbResult = await mongoDb()
|
|
1680
|
+
.collection('vendorUserIdMappings')
|
|
1681
|
+
.findOne({ vendorUserId });
|
|
1682
|
+
const { sub } = decodeJwt(response.json.token);
|
|
1683
|
+
expect(dbResult).toEqual({
|
|
1684
|
+
_id: new ObjectId(sub),
|
|
1685
|
+
vendorUserId,
|
|
1686
|
+
tenantId: new ObjectId(tenant._id),
|
|
1687
|
+
createdAt: expect.any(Date),
|
|
1688
|
+
updatedAt: expect.any(Date),
|
|
1689
|
+
});
|
|
1690
|
+
expect(
|
|
1691
|
+
await mongoDb()
|
|
1692
|
+
.collection('exchanges')
|
|
1693
|
+
.findOne({ _id: new ObjectId(exchange._id) })
|
|
1694
|
+
).toEqual(
|
|
1695
|
+
expectedExchange(tenant, exchange, presentationIdDocument, [
|
|
1696
|
+
ExchangeStates.NEW,
|
|
1697
|
+
ExchangeStates.DISCLOSURE_RECEIVED,
|
|
1698
|
+
ExchangeStates.DISCLOSURE_CHECKED,
|
|
1699
|
+
ExchangeStates.IDENTIFIED,
|
|
1700
|
+
])
|
|
1701
|
+
);
|
|
1702
|
+
expect(identifyWebhookPayload).toEqual({
|
|
1703
|
+
emailCredentials: [],
|
|
1704
|
+
emails: [],
|
|
1705
|
+
exchangeId: exchange._id,
|
|
1706
|
+
idDocumentCredentials: [
|
|
1707
|
+
{
|
|
1708
|
+
id: idDocPayload.vc.id,
|
|
1709
|
+
type: ['IdDocumentV1.0', 'VerifiableCredential'],
|
|
1710
|
+
credentialSubject: idDocPayload.vc.credentialSubject,
|
|
1711
|
+
credentialType: 'IdDocumentV1.0',
|
|
1712
|
+
issuanceDate: expect.stringMatching(ISO_DATETIME_FORMAT),
|
|
1713
|
+
issuer: {
|
|
1714
|
+
id: 'did:velocity:0x0b154da48d0f213c26c4b1d040dc5ff1dbf99ffa',
|
|
1715
|
+
},
|
|
1716
|
+
validFrom: '2017-09-01',
|
|
1717
|
+
validUntil: '2021-09-01',
|
|
1718
|
+
credentialChecks: DEFAULT_CREDENTIAL_CHECKS,
|
|
1719
|
+
},
|
|
1720
|
+
],
|
|
1721
|
+
phoneCredentials: [],
|
|
1722
|
+
phones: [],
|
|
1723
|
+
tenantDID: tenant.did,
|
|
1724
|
+
tenantId: tenant._id,
|
|
1725
|
+
});
|
|
1726
|
+
});
|
|
1727
|
+
|
|
1728
|
+
it('should 200 when tenant has a custom webhook', async () => {
|
|
1729
|
+
const webhookUrl = 'http://cutomUrl.com';
|
|
1730
|
+
const customTenant = await persistTenant({
|
|
1731
|
+
webhookUrl,
|
|
1732
|
+
});
|
|
1733
|
+
|
|
1734
|
+
const keyPair = generateKeyPair({ format: 'jwk' });
|
|
1735
|
+
|
|
1736
|
+
await persistKey({
|
|
1737
|
+
tenant: customTenant,
|
|
1738
|
+
kidFragment: '#ID1',
|
|
1739
|
+
keyPair,
|
|
1740
|
+
});
|
|
1741
|
+
|
|
1742
|
+
const customDisclosure = await persistDisclosure({
|
|
1743
|
+
tenant: customTenant,
|
|
1744
|
+
description: 'Credential Issuance disclosure',
|
|
1745
|
+
types: [{ type: 'EmailV1.0' }],
|
|
1746
|
+
vendorEndpoint: VendorEndpoint.ISSUING_IDENTIFICATION,
|
|
1747
|
+
purpose: 'Identification',
|
|
1748
|
+
duration: '6y',
|
|
1749
|
+
});
|
|
1750
|
+
|
|
1751
|
+
const customExchange = await persistOfferExchange({
|
|
1752
|
+
tenant: customTenant,
|
|
1753
|
+
disclosure: customDisclosure,
|
|
1754
|
+
});
|
|
1755
|
+
|
|
1756
|
+
const presentationIdDocument = await generateKYCPresentation(
|
|
1757
|
+
customExchange,
|
|
1758
|
+
'idDocument'
|
|
1759
|
+
);
|
|
1760
|
+
|
|
1761
|
+
let identifyWebhookPayload;
|
|
1762
|
+
const nockWebhook = await nock(webhookUrl)
|
|
1763
|
+
.post(identifyUserOnVendorEndpoint)
|
|
1764
|
+
.reply(200, (uri, body) => {
|
|
1765
|
+
identifyWebhookPayload = body;
|
|
1766
|
+
return {
|
|
1767
|
+
vendorUserId,
|
|
1768
|
+
};
|
|
1769
|
+
});
|
|
1770
|
+
|
|
1771
|
+
const response = await fastify.injectJson({
|
|
1772
|
+
method: 'POST',
|
|
1773
|
+
url: idUrl(customTenant),
|
|
1774
|
+
headers: {
|
|
1775
|
+
'x-vnf-protocol-version': `${VnfProtocolVersions.VNF_PROTOCOL_VERSION_2}`,
|
|
1776
|
+
},
|
|
1777
|
+
payload: {
|
|
1778
|
+
exchange_id: customExchange._id,
|
|
1779
|
+
jwt_vp: await presentationIdDocument.sign(
|
|
1780
|
+
holderKid,
|
|
1781
|
+
holderKeys.privateKey,
|
|
1782
|
+
holderDid
|
|
1783
|
+
),
|
|
1784
|
+
},
|
|
1785
|
+
});
|
|
1786
|
+
|
|
1787
|
+
expect(response.statusCode).toEqual(200);
|
|
1788
|
+
expect(response.json).toEqual({
|
|
1789
|
+
token: expect.any(String),
|
|
1790
|
+
exchange: {
|
|
1791
|
+
disclosureComplete: true,
|
|
1792
|
+
exchangeComplete: false,
|
|
1793
|
+
id: customExchange._id,
|
|
1794
|
+
type: ExchangeTypes.ISSUING,
|
|
1795
|
+
},
|
|
1796
|
+
});
|
|
1797
|
+
const dbResult = await mongoDb()
|
|
1798
|
+
.collection('vendorUserIdMappings')
|
|
1799
|
+
.findOne({ vendorUserId });
|
|
1800
|
+
const { sub } = decodeJwt(response.json.token);
|
|
1801
|
+
expect(dbResult).toEqual({
|
|
1802
|
+
_id: new ObjectId(sub),
|
|
1803
|
+
vendorUserId,
|
|
1804
|
+
tenantId: new ObjectId(customTenant._id),
|
|
1805
|
+
createdAt: expect.any(Date),
|
|
1806
|
+
updatedAt: expect.any(Date),
|
|
1807
|
+
});
|
|
1808
|
+
expect(
|
|
1809
|
+
await mongoDb()
|
|
1810
|
+
.collection('exchanges')
|
|
1811
|
+
.findOne({ _id: new ObjectId(customExchange._id) })
|
|
1812
|
+
).toEqual(
|
|
1813
|
+
expectedExchange(customTenant, customExchange, presentationIdDocument, [
|
|
1814
|
+
ExchangeStates.NEW,
|
|
1815
|
+
ExchangeStates.DISCLOSURE_RECEIVED,
|
|
1816
|
+
ExchangeStates.DISCLOSURE_CHECKED,
|
|
1817
|
+
ExchangeStates.IDENTIFIED,
|
|
1818
|
+
])
|
|
1819
|
+
);
|
|
1820
|
+
expect(nockWebhook.isDone()).toEqual(true);
|
|
1821
|
+
expect(identifyWebhookPayload).toEqual({
|
|
1822
|
+
emailCredentials: [],
|
|
1823
|
+
emails: [],
|
|
1824
|
+
exchangeId: customExchange._id,
|
|
1825
|
+
idDocumentCredentials: [
|
|
1826
|
+
{
|
|
1827
|
+
id: idDocPayload.vc.id,
|
|
1828
|
+
type: ['IdDocumentV1.0', 'VerifiableCredential'],
|
|
1829
|
+
credentialSubject: idDocPayload.vc.credentialSubject,
|
|
1830
|
+
credentialType: 'IdDocumentV1.0',
|
|
1831
|
+
issuanceDate: expect.stringMatching(ISO_DATETIME_FORMAT),
|
|
1832
|
+
issuer: {
|
|
1833
|
+
id: 'did:velocity:0x0b154da48d0f213c26c4b1d040dc5ff1dbf99ffa',
|
|
1834
|
+
},
|
|
1835
|
+
validFrom: '2017-09-01',
|
|
1836
|
+
validUntil: '2021-09-01',
|
|
1837
|
+
credentialChecks: DEFAULT_CREDENTIAL_CHECKS,
|
|
1838
|
+
},
|
|
1839
|
+
],
|
|
1840
|
+
phoneCredentials: [],
|
|
1841
|
+
phones: [],
|
|
1842
|
+
tenantDID: customTenant.did,
|
|
1843
|
+
tenantId: customTenant._id,
|
|
1844
|
+
});
|
|
1845
|
+
});
|
|
1846
|
+
|
|
1847
|
+
it('should 200 when tenant has a custom webhook with custom webhookAuth', async () => {
|
|
1848
|
+
const webhookUrl = 'http://cutomUrl.com';
|
|
1849
|
+
const customTenant = await persistTenant({
|
|
1850
|
+
webhookUrl,
|
|
1851
|
+
webhookAuth: {
|
|
1852
|
+
type: 'bearer',
|
|
1853
|
+
bearerToken: 'secret',
|
|
1854
|
+
},
|
|
1855
|
+
});
|
|
1856
|
+
|
|
1857
|
+
const keyPair = generateKeyPair({ format: 'jwk' });
|
|
1858
|
+
|
|
1859
|
+
await persistKey({
|
|
1860
|
+
tenant: customTenant,
|
|
1861
|
+
kidFragment: '#ID1',
|
|
1862
|
+
keyPair,
|
|
1863
|
+
});
|
|
1864
|
+
|
|
1865
|
+
const customDisclosure = await persistDisclosure({
|
|
1866
|
+
tenant: customTenant,
|
|
1867
|
+
description: 'Credential Issuance disclosure',
|
|
1868
|
+
types: [{ type: 'EmailV1.0' }],
|
|
1869
|
+
vendorEndpoint: VendorEndpoint.ISSUING_IDENTIFICATION,
|
|
1870
|
+
purpose: 'Identification',
|
|
1871
|
+
duration: '6y',
|
|
1872
|
+
});
|
|
1873
|
+
|
|
1874
|
+
const customExchange = await persistOfferExchange({
|
|
1875
|
+
tenant: customTenant,
|
|
1876
|
+
disclosure: customDisclosure,
|
|
1877
|
+
});
|
|
1878
|
+
|
|
1879
|
+
const presentationIdDocument = await generateKYCPresentation(
|
|
1880
|
+
customExchange,
|
|
1881
|
+
'idDocument'
|
|
1882
|
+
);
|
|
1883
|
+
|
|
1884
|
+
let identifyWebhookPayload;
|
|
1885
|
+
let identityWebhookHeaders;
|
|
1886
|
+
const nockWebhook = await nock(webhookUrl)
|
|
1887
|
+
.post(identifyUserOnVendorEndpoint)
|
|
1888
|
+
// eslint-disable-next-line prefer-arrow-functions/prefer-arrow-functions
|
|
1889
|
+
.reply(200, function namedFn(uri, body) {
|
|
1890
|
+
identityWebhookHeaders = this.req.headers;
|
|
1891
|
+
identifyWebhookPayload = body;
|
|
1892
|
+
return {
|
|
1893
|
+
vendorUserId,
|
|
1894
|
+
};
|
|
1895
|
+
});
|
|
1896
|
+
|
|
1897
|
+
const response = await fastify.injectJson({
|
|
1898
|
+
method: 'POST',
|
|
1899
|
+
url: idUrl(customTenant),
|
|
1900
|
+
headers: {
|
|
1901
|
+
'x-vnf-protocol-version': `${VnfProtocolVersions.VNF_PROTOCOL_VERSION_2}`,
|
|
1902
|
+
},
|
|
1903
|
+
payload: {
|
|
1904
|
+
exchange_id: customExchange._id,
|
|
1905
|
+
jwt_vp: await presentationIdDocument.sign(
|
|
1906
|
+
holderKid,
|
|
1907
|
+
holderKeys.privateKey,
|
|
1908
|
+
holderDid
|
|
1909
|
+
),
|
|
1910
|
+
},
|
|
1911
|
+
});
|
|
1912
|
+
|
|
1913
|
+
expect(response.statusCode).toEqual(200);
|
|
1914
|
+
|
|
1915
|
+
expect(nockWebhook.isDone()).toEqual(true);
|
|
1916
|
+
expect(identifyWebhookPayload).toEqual({
|
|
1917
|
+
emailCredentials: [],
|
|
1918
|
+
emails: [],
|
|
1919
|
+
exchangeId: customExchange._id,
|
|
1920
|
+
idDocumentCredentials: [
|
|
1921
|
+
{
|
|
1922
|
+
id: idDocPayload.vc.id,
|
|
1923
|
+
type: ['IdDocumentV1.0', 'VerifiableCredential'],
|
|
1924
|
+
credentialSubject: idDocPayload.vc.credentialSubject,
|
|
1925
|
+
credentialType: 'IdDocumentV1.0',
|
|
1926
|
+
issuanceDate: expect.stringMatching(ISO_DATETIME_FORMAT),
|
|
1927
|
+
issuer: {
|
|
1928
|
+
id: 'did:velocity:0x0b154da48d0f213c26c4b1d040dc5ff1dbf99ffa',
|
|
1929
|
+
},
|
|
1930
|
+
validFrom: '2017-09-01',
|
|
1931
|
+
validUntil: '2021-09-01',
|
|
1932
|
+
credentialChecks: DEFAULT_CREDENTIAL_CHECKS,
|
|
1933
|
+
},
|
|
1934
|
+
],
|
|
1935
|
+
phoneCredentials: [],
|
|
1936
|
+
phones: [],
|
|
1937
|
+
tenantDID: customTenant.did,
|
|
1938
|
+
tenantId: customTenant._id,
|
|
1939
|
+
});
|
|
1940
|
+
expect(identityWebhookHeaders.authorization).toEqual('Bearer secret');
|
|
1941
|
+
});
|
|
1942
|
+
|
|
1943
|
+
it('should 200 when identifying email and a coupon was not provided', async () => {
|
|
1944
|
+
const presentationEmail = await generateKYCPresentation(
|
|
1945
|
+
exchange,
|
|
1946
|
+
'email'
|
|
1947
|
+
);
|
|
1948
|
+
metadataRegistration.initVerificationCoupon.mockImplementation(() => ({
|
|
1949
|
+
getCoupon: () => Promise.resolve(null),
|
|
1950
|
+
}));
|
|
1951
|
+
|
|
1952
|
+
let identifyWebhookPayload;
|
|
1953
|
+
nock(mockVendorUrl)
|
|
1954
|
+
.post(identifyUserOnVendorEndpoint)
|
|
1955
|
+
.reply(200, (uri, body) => {
|
|
1956
|
+
identifyWebhookPayload = body;
|
|
1957
|
+
return {
|
|
1958
|
+
vendorUserId,
|
|
1959
|
+
};
|
|
1960
|
+
});
|
|
1961
|
+
|
|
1962
|
+
const response = await fastify.injectJson({
|
|
1963
|
+
method: 'POST',
|
|
1964
|
+
url: idUrl(tenant),
|
|
1965
|
+
headers: {
|
|
1966
|
+
'x-vnf-protocol-version': `${VnfProtocolVersions.VNF_PROTOCOL_VERSION_2}`,
|
|
1967
|
+
},
|
|
1968
|
+
payload: {
|
|
1969
|
+
jwt_vp: await presentationEmail.sign(
|
|
1970
|
+
holderKid,
|
|
1971
|
+
holderKeys.privateKey,
|
|
1972
|
+
holderDid
|
|
1973
|
+
),
|
|
1974
|
+
exchange_id: exchange._id,
|
|
1975
|
+
},
|
|
1976
|
+
});
|
|
1977
|
+
|
|
1978
|
+
expect(response.statusCode).toEqual(200);
|
|
1979
|
+
expect(response.json).toEqual({
|
|
1980
|
+
token: expect.any(String),
|
|
1981
|
+
exchange: {
|
|
1982
|
+
disclosureComplete: true,
|
|
1983
|
+
exchangeComplete: false,
|
|
1984
|
+
id: exchange._id,
|
|
1985
|
+
type: ExchangeTypes.ISSUING,
|
|
1986
|
+
},
|
|
1987
|
+
});
|
|
1988
|
+
const dbResult = await mongoDb()
|
|
1989
|
+
.collection('vendorUserIdMappings')
|
|
1990
|
+
.findOne({ vendorUserId });
|
|
1991
|
+
const { sub } = decodeJwt(response.json.token);
|
|
1992
|
+
expect(dbResult).toEqual({
|
|
1993
|
+
_id: new ObjectId(sub),
|
|
1994
|
+
vendorUserId,
|
|
1995
|
+
tenantId: new ObjectId(tenant._id),
|
|
1996
|
+
createdAt: expect.any(Date),
|
|
1997
|
+
updatedAt: expect.any(Date),
|
|
1998
|
+
});
|
|
1999
|
+
expect(
|
|
2000
|
+
await mongoDb()
|
|
2001
|
+
.collection('exchanges')
|
|
2002
|
+
.findOne({ _id: new ObjectId(exchange._id) })
|
|
2003
|
+
).toEqual(
|
|
2004
|
+
expectedExchange(tenant, exchange, presentationEmail, [
|
|
2005
|
+
ExchangeStates.NEW,
|
|
2006
|
+
ExchangeStates.DISCLOSURE_RECEIVED,
|
|
2007
|
+
ExchangeStates.DISCLOSURE_CHECKED,
|
|
2008
|
+
ExchangeStates.IDENTIFIED,
|
|
2009
|
+
])
|
|
2010
|
+
);
|
|
2011
|
+
expect(identifyWebhookPayload).toEqual({
|
|
2012
|
+
emailCredentials: [
|
|
2013
|
+
{
|
|
2014
|
+
id: emailPayload.vc.id,
|
|
2015
|
+
type: ['EmailV1.0', 'VerifiableCredential'],
|
|
2016
|
+
credentialSubject: emailPayload.vc.credentialSubject,
|
|
2017
|
+
credentialType: 'EmailV1.0',
|
|
2018
|
+
issuer: {
|
|
2019
|
+
id: 'did:velocity:0x0b154da48d0f213c26c4b1d040dc5ff1dbf99ffa',
|
|
2020
|
+
},
|
|
2021
|
+
issuanceDate: expect.stringMatching(ISO_DATETIME_FORMAT),
|
|
2022
|
+
credentialChecks: DEFAULT_CREDENTIAL_CHECKS,
|
|
2023
|
+
},
|
|
2024
|
+
],
|
|
2025
|
+
idDocumentCredentials: [],
|
|
2026
|
+
phoneCredentials: [],
|
|
2027
|
+
emails: [emailPayload.vc.credentialSubject.email],
|
|
2028
|
+
phones: [],
|
|
2029
|
+
vendorOrganizationId: tenant.vendorOrganizationId,
|
|
2030
|
+
tenantDID: tenant.did,
|
|
2031
|
+
tenantId: tenant._id,
|
|
2032
|
+
exchangeId: exchange._id,
|
|
2033
|
+
});
|
|
2034
|
+
metadataRegistration.initVerificationCoupon.mockImplementation(() => ({
|
|
2035
|
+
getCoupon: () => Promise.resolve(42),
|
|
2036
|
+
}));
|
|
2037
|
+
});
|
|
2038
|
+
it('should 200 when identifying email', async () => {
|
|
2039
|
+
const presentationEmail = await generateKYCPresentation(
|
|
2040
|
+
exchange,
|
|
2041
|
+
'email'
|
|
2042
|
+
);
|
|
2043
|
+
|
|
2044
|
+
let identifyWebhookPayload;
|
|
2045
|
+
nock(mockVendorUrl)
|
|
2046
|
+
.post(identifyUserOnVendorEndpoint)
|
|
2047
|
+
.reply(200, (uri, body) => {
|
|
2048
|
+
identifyWebhookPayload = body;
|
|
2049
|
+
return {
|
|
2050
|
+
vendorUserId,
|
|
2051
|
+
};
|
|
2052
|
+
});
|
|
2053
|
+
|
|
2054
|
+
const response = await fastify.injectJson({
|
|
2055
|
+
method: 'POST',
|
|
2056
|
+
url: idUrl(tenant),
|
|
2057
|
+
headers: {
|
|
2058
|
+
'x-vnf-protocol-version': `${VnfProtocolVersions.VNF_PROTOCOL_VERSION_2}`,
|
|
2059
|
+
},
|
|
2060
|
+
payload: {
|
|
2061
|
+
jwt_vp: await presentationEmail.sign(
|
|
2062
|
+
holderKid,
|
|
2063
|
+
holderKeys.privateKey,
|
|
2064
|
+
holderDid
|
|
2065
|
+
),
|
|
2066
|
+
exchange_id: exchange._id,
|
|
2067
|
+
},
|
|
2068
|
+
});
|
|
2069
|
+
|
|
2070
|
+
expect(response.statusCode).toEqual(200);
|
|
2071
|
+
expect(response.json).toEqual({
|
|
2072
|
+
token: expect.any(String),
|
|
2073
|
+
exchange: {
|
|
2074
|
+
disclosureComplete: true,
|
|
2075
|
+
exchangeComplete: false,
|
|
2076
|
+
id: exchange._id,
|
|
2077
|
+
type: ExchangeTypes.ISSUING,
|
|
2078
|
+
},
|
|
2079
|
+
});
|
|
2080
|
+
const dbResult = await mongoDb()
|
|
2081
|
+
.collection('vendorUserIdMappings')
|
|
2082
|
+
.findOne({ vendorUserId });
|
|
2083
|
+
const { sub } = decodeJwt(response.json.token);
|
|
2084
|
+
expect(dbResult).toEqual({
|
|
2085
|
+
_id: new ObjectId(sub),
|
|
2086
|
+
vendorUserId,
|
|
2087
|
+
tenantId: new ObjectId(tenant._id),
|
|
2088
|
+
createdAt: expect.any(Date),
|
|
2089
|
+
updatedAt: expect.any(Date),
|
|
2090
|
+
});
|
|
2091
|
+
expect(
|
|
2092
|
+
await mongoDb()
|
|
2093
|
+
.collection('exchanges')
|
|
2094
|
+
.findOne({ _id: new ObjectId(exchange._id) })
|
|
2095
|
+
).toEqual(
|
|
2096
|
+
expectedExchange(tenant, exchange, presentationEmail, [
|
|
2097
|
+
ExchangeStates.NEW,
|
|
2098
|
+
ExchangeStates.DISCLOSURE_RECEIVED,
|
|
2099
|
+
ExchangeStates.DISCLOSURE_CHECKED,
|
|
2100
|
+
ExchangeStates.IDENTIFIED,
|
|
2101
|
+
])
|
|
2102
|
+
);
|
|
2103
|
+
expect(identifyWebhookPayload).toEqual({
|
|
2104
|
+
emailCredentials: [
|
|
2105
|
+
{
|
|
2106
|
+
id: emailPayload.vc.id,
|
|
2107
|
+
type: ['EmailV1.0', 'VerifiableCredential'],
|
|
2108
|
+
credentialSubject: emailPayload.vc.credentialSubject,
|
|
2109
|
+
credentialType: 'EmailV1.0',
|
|
2110
|
+
issuer: {
|
|
2111
|
+
id: 'did:velocity:0x0b154da48d0f213c26c4b1d040dc5ff1dbf99ffa',
|
|
2112
|
+
},
|
|
2113
|
+
issuanceDate: expect.stringMatching(ISO_DATETIME_FORMAT),
|
|
2114
|
+
credentialChecks: DEFAULT_CREDENTIAL_CHECKS,
|
|
2115
|
+
},
|
|
2116
|
+
],
|
|
2117
|
+
idDocumentCredentials: [],
|
|
2118
|
+
phoneCredentials: [],
|
|
2119
|
+
emails: [emailPayload.vc.credentialSubject.email],
|
|
2120
|
+
phones: [],
|
|
2121
|
+
tenantDID: tenant.did,
|
|
2122
|
+
tenantId: tenant._id,
|
|
2123
|
+
exchangeId: exchange._id,
|
|
2124
|
+
});
|
|
2125
|
+
});
|
|
2126
|
+
|
|
2127
|
+
it('should 200 when identifying whatever with logo and email', async () => {
|
|
2128
|
+
const presentationEmail = await generateKYCPresentation(
|
|
2129
|
+
exchange,
|
|
2130
|
+
'whateverWithLogoName'
|
|
2131
|
+
);
|
|
2132
|
+
|
|
2133
|
+
let identifyWebhookPayload;
|
|
2134
|
+
nock(mockVendorUrl)
|
|
2135
|
+
.post(identifyUserOnVendorEndpoint)
|
|
2136
|
+
.reply(200, (uri, body) => {
|
|
2137
|
+
identifyWebhookPayload = body;
|
|
2138
|
+
return {
|
|
2139
|
+
vendorUserId,
|
|
2140
|
+
};
|
|
2141
|
+
});
|
|
2142
|
+
|
|
2143
|
+
const response = await fastify.injectJson({
|
|
2144
|
+
method: 'POST',
|
|
2145
|
+
url: idUrl(tenant),
|
|
2146
|
+
headers: {
|
|
2147
|
+
'x-vnf-protocol-version': `${VnfProtocolVersions.VNF_PROTOCOL_VERSION_2}`,
|
|
2148
|
+
},
|
|
2149
|
+
payload: {
|
|
2150
|
+
jwt_vp: await presentationEmail.sign(
|
|
2151
|
+
holderKid,
|
|
2152
|
+
holderKeys.privateKey,
|
|
2153
|
+
holderDid
|
|
2154
|
+
),
|
|
2155
|
+
exchange_id: exchange._id,
|
|
2156
|
+
},
|
|
2157
|
+
});
|
|
2158
|
+
|
|
2159
|
+
expect(response.statusCode).toEqual(200);
|
|
2160
|
+
expect(response.json).toEqual({
|
|
2161
|
+
token: expect.any(String),
|
|
2162
|
+
exchange: {
|
|
2163
|
+
disclosureComplete: true,
|
|
2164
|
+
exchangeComplete: false,
|
|
2165
|
+
id: exchange._id,
|
|
2166
|
+
type: ExchangeTypes.ISSUING,
|
|
2167
|
+
},
|
|
2168
|
+
});
|
|
2169
|
+
const dbResult = await mongoDb()
|
|
2170
|
+
.collection('vendorUserIdMappings')
|
|
2171
|
+
.findOne({ vendorUserId });
|
|
2172
|
+
const { sub } = decodeJwt(response.json.token);
|
|
2173
|
+
expect(dbResult).toEqual({
|
|
2174
|
+
_id: new ObjectId(sub),
|
|
2175
|
+
vendorUserId,
|
|
2176
|
+
tenantId: new ObjectId(tenant._id),
|
|
2177
|
+
createdAt: expect.any(Date),
|
|
2178
|
+
updatedAt: expect.any(Date),
|
|
2179
|
+
});
|
|
2180
|
+
expect(
|
|
2181
|
+
await mongoDb()
|
|
2182
|
+
.collection('exchanges')
|
|
2183
|
+
.findOne({ _id: new ObjectId(exchange._id) })
|
|
2184
|
+
).toEqual(
|
|
2185
|
+
expectedExchange(tenant, exchange, presentationEmail, [
|
|
2186
|
+
ExchangeStates.NEW,
|
|
2187
|
+
ExchangeStates.DISCLOSURE_RECEIVED,
|
|
2188
|
+
ExchangeStates.DISCLOSURE_CHECKED,
|
|
2189
|
+
ExchangeStates.IDENTIFIED,
|
|
2190
|
+
])
|
|
2191
|
+
);
|
|
2192
|
+
expect(identifyWebhookPayload).toEqual({
|
|
2193
|
+
emailCredentials: [
|
|
2194
|
+
{
|
|
2195
|
+
id: emailPayload.vc.id,
|
|
2196
|
+
type: ['EmailV1.0', 'VerifiableCredential'],
|
|
2197
|
+
credentialSubject: emailPayload.vc.credentialSubject,
|
|
2198
|
+
credentialType: 'EmailV1.0',
|
|
2199
|
+
issuer: {
|
|
2200
|
+
id: 'did:velocity:0x0b154da48d0f213c26c4b1d040dc5ff1dbf99ffa',
|
|
2201
|
+
image: 'https://example.com/image.png',
|
|
2202
|
+
name: 'Whatever',
|
|
2203
|
+
},
|
|
2204
|
+
issuanceDate: expect.stringMatching(ISO_DATETIME_FORMAT),
|
|
2205
|
+
credentialChecks: DEFAULT_CREDENTIAL_CHECKS,
|
|
2206
|
+
},
|
|
2207
|
+
],
|
|
2208
|
+
idDocumentCredentials: [],
|
|
2209
|
+
phoneCredentials: [],
|
|
2210
|
+
emails: [emailPayload.vc.credentialSubject.email],
|
|
2211
|
+
phones: [],
|
|
2212
|
+
tenantDID: tenant.did,
|
|
2213
|
+
tenantId: tenant._id,
|
|
2214
|
+
exchangeId: exchange._id,
|
|
2215
|
+
});
|
|
2216
|
+
});
|
|
2217
|
+
|
|
2218
|
+
it('should 200 when identifying legacy email', async () => {
|
|
2219
|
+
const presentationEmail = await generateKYCPresentation(
|
|
2220
|
+
exchange,
|
|
2221
|
+
'legacyEmail'
|
|
2222
|
+
);
|
|
2223
|
+
|
|
2224
|
+
let identifyWebhookPayload;
|
|
2225
|
+
nock(mockVendorUrl)
|
|
2226
|
+
.post(identifyUserOnVendorEndpoint)
|
|
2227
|
+
.reply(200, (uri, body) => {
|
|
2228
|
+
identifyWebhookPayload = body;
|
|
2229
|
+
return {
|
|
2230
|
+
vendorUserId,
|
|
2231
|
+
};
|
|
2232
|
+
});
|
|
2233
|
+
|
|
2234
|
+
const response = await fastify.injectJson({
|
|
2235
|
+
method: 'POST',
|
|
2236
|
+
url: idUrl(tenant),
|
|
2237
|
+
headers: {
|
|
2238
|
+
'x-vnf-protocol-version': `${VnfProtocolVersions.VNF_PROTOCOL_VERSION_2}`,
|
|
2239
|
+
},
|
|
2240
|
+
payload: {
|
|
2241
|
+
jwt_vp: await presentationEmail.sign(
|
|
2242
|
+
holderKid,
|
|
2243
|
+
holderKeys.privateKey,
|
|
2244
|
+
holderDid
|
|
2245
|
+
),
|
|
2246
|
+
exchange_id: exchange._id,
|
|
2247
|
+
},
|
|
2248
|
+
});
|
|
2249
|
+
|
|
2250
|
+
expect(response.statusCode).toEqual(200);
|
|
2251
|
+
expect(response.json).toEqual({
|
|
2252
|
+
token: expect.any(String),
|
|
2253
|
+
exchange: {
|
|
2254
|
+
disclosureComplete: true,
|
|
2255
|
+
exchangeComplete: false,
|
|
2256
|
+
id: exchange._id,
|
|
2257
|
+
type: ExchangeTypes.ISSUING,
|
|
2258
|
+
},
|
|
2259
|
+
});
|
|
2260
|
+
const dbResult = await mongoDb()
|
|
2261
|
+
.collection('vendorUserIdMappings')
|
|
2262
|
+
.findOne({ vendorUserId });
|
|
2263
|
+
const { sub } = decodeJwt(response.json.token);
|
|
2264
|
+
expect(dbResult).toEqual({
|
|
2265
|
+
_id: new ObjectId(sub),
|
|
2266
|
+
vendorUserId,
|
|
2267
|
+
tenantId: new ObjectId(tenant._id),
|
|
2268
|
+
createdAt: expect.any(Date),
|
|
2269
|
+
updatedAt: expect.any(Date),
|
|
2270
|
+
});
|
|
2271
|
+
expect(
|
|
2272
|
+
await mongoDb()
|
|
2273
|
+
.collection('exchanges')
|
|
2274
|
+
.findOne({ _id: new ObjectId(exchange._id) })
|
|
2275
|
+
).toEqual(
|
|
2276
|
+
expectedExchange(tenant, exchange, presentationEmail, [
|
|
2277
|
+
ExchangeStates.NEW,
|
|
2278
|
+
ExchangeStates.DISCLOSURE_RECEIVED,
|
|
2279
|
+
ExchangeStates.DISCLOSURE_CHECKED,
|
|
2280
|
+
ExchangeStates.IDENTIFIED,
|
|
2281
|
+
])
|
|
2282
|
+
);
|
|
2283
|
+
expect(identifyWebhookPayload).toEqual({
|
|
2284
|
+
emailCredentials: [
|
|
2285
|
+
{
|
|
2286
|
+
id: emailPayload.vc.id,
|
|
2287
|
+
type: ['Email', 'VerifiableCredential'],
|
|
2288
|
+
credentialSubject: emailPayload.vc.credentialSubject,
|
|
2289
|
+
credentialType: 'Email',
|
|
2290
|
+
issuer: {
|
|
2291
|
+
id: 'did:velocity:0x0b154da48d0f213c26c4b1d040dc5ff1dbf99ffa',
|
|
2292
|
+
},
|
|
2293
|
+
issuanceDate: expect.stringMatching(ISO_DATETIME_FORMAT),
|
|
2294
|
+
credentialChecks: DEFAULT_CREDENTIAL_CHECKS,
|
|
2295
|
+
},
|
|
2296
|
+
],
|
|
2297
|
+
idDocumentCredentials: [],
|
|
2298
|
+
phoneCredentials: [],
|
|
2299
|
+
emails: [emailPayload.vc.credentialSubject.email],
|
|
2300
|
+
phones: [],
|
|
2301
|
+
tenantDID: tenant.did,
|
|
2302
|
+
tenantId: tenant._id,
|
|
2303
|
+
exchangeId: exchange._id,
|
|
2304
|
+
});
|
|
2305
|
+
});
|
|
2306
|
+
|
|
2307
|
+
it('should 200 when identifying phone', async () => {
|
|
2308
|
+
const presentationPhone = await generateKYCPresentation(
|
|
2309
|
+
exchange,
|
|
2310
|
+
'phone'
|
|
2311
|
+
);
|
|
2312
|
+
|
|
2313
|
+
let identifyWebhookPayload;
|
|
2314
|
+
nock(mockVendorUrl)
|
|
2315
|
+
.post(identifyUserOnVendorEndpoint)
|
|
2316
|
+
.reply(200, (uri, body) => {
|
|
2317
|
+
identifyWebhookPayload = body;
|
|
2318
|
+
return {
|
|
2319
|
+
vendorUserId,
|
|
2320
|
+
};
|
|
2321
|
+
});
|
|
2322
|
+
|
|
2323
|
+
const response = await fastify.injectJson({
|
|
2324
|
+
method: 'POST',
|
|
2325
|
+
url: idUrl(tenant),
|
|
2326
|
+
headers: {
|
|
2327
|
+
'x-vnf-protocol-version': `${VnfProtocolVersions.VNF_PROTOCOL_VERSION_2}`,
|
|
2328
|
+
},
|
|
2329
|
+
payload: {
|
|
2330
|
+
jwt_vp: await presentationPhone.sign(
|
|
2331
|
+
holderKid,
|
|
2332
|
+
holderKeys.privateKey,
|
|
2333
|
+
holderDid
|
|
2334
|
+
),
|
|
2335
|
+
exchange_id: exchange._id,
|
|
2336
|
+
},
|
|
2337
|
+
});
|
|
2338
|
+
|
|
2339
|
+
expect(response.statusCode).toEqual(200);
|
|
2340
|
+
expect(response.json).toEqual({
|
|
2341
|
+
token: expect.any(String),
|
|
2342
|
+
exchange: {
|
|
2343
|
+
disclosureComplete: true,
|
|
2344
|
+
exchangeComplete: false,
|
|
2345
|
+
id: exchange._id,
|
|
2346
|
+
type: ExchangeTypes.ISSUING,
|
|
2347
|
+
},
|
|
2348
|
+
});
|
|
2349
|
+
const dbResult = await mongoDb()
|
|
2350
|
+
.collection('vendorUserIdMappings')
|
|
2351
|
+
.findOne({ vendorUserId });
|
|
2352
|
+
const { sub } = decodeJwt(response.json.token);
|
|
2353
|
+
expect(dbResult).toEqual({
|
|
2354
|
+
_id: new ObjectId(sub),
|
|
2355
|
+
vendorUserId,
|
|
2356
|
+
tenantId: new ObjectId(tenant._id),
|
|
2357
|
+
createdAt: expect.any(Date),
|
|
2358
|
+
updatedAt: expect.any(Date),
|
|
2359
|
+
});
|
|
2360
|
+
expect(
|
|
2361
|
+
await mongoDb()
|
|
2362
|
+
.collection('exchanges')
|
|
2363
|
+
.findOne({ _id: new ObjectId(exchange._id) })
|
|
2364
|
+
).toEqual(
|
|
2365
|
+
expectedExchange(tenant, exchange, presentationPhone, [
|
|
2366
|
+
ExchangeStates.NEW,
|
|
2367
|
+
ExchangeStates.DISCLOSURE_RECEIVED,
|
|
2368
|
+
ExchangeStates.DISCLOSURE_CHECKED,
|
|
2369
|
+
ExchangeStates.IDENTIFIED,
|
|
2370
|
+
])
|
|
2371
|
+
);
|
|
2372
|
+
expect(identifyWebhookPayload).toEqual({
|
|
2373
|
+
phoneCredentials: [
|
|
2374
|
+
{
|
|
2375
|
+
id: phonePayload.vc.id,
|
|
2376
|
+
type: ['PhoneV1.0', 'VerifiableCredential'],
|
|
2377
|
+
credentialSubject: phonePayload.vc.credentialSubject,
|
|
2378
|
+
credentialType: 'PhoneV1.0',
|
|
2379
|
+
issuer: {
|
|
2380
|
+
id: 'did:velocity:0x0b154da48d0f213c26c4b1d040dc5ff1dbf99ffa',
|
|
2381
|
+
},
|
|
2382
|
+
issuanceDate: expect.stringMatching(ISO_DATETIME_FORMAT),
|
|
2383
|
+
credentialChecks: DEFAULT_CREDENTIAL_CHECKS,
|
|
2384
|
+
},
|
|
2385
|
+
],
|
|
2386
|
+
idDocumentCredentials: [],
|
|
2387
|
+
emailCredentials: [],
|
|
2388
|
+
emails: [],
|
|
2389
|
+
phones: [phonePayload.vc.credentialSubject.phone],
|
|
2390
|
+
tenantDID: tenant.did,
|
|
2391
|
+
tenantId: tenant._id,
|
|
2392
|
+
exchangeId: exchange._id,
|
|
2393
|
+
});
|
|
2394
|
+
});
|
|
2395
|
+
|
|
2396
|
+
it('should 200 when identifying VerificationIdentifier', async () => {
|
|
2397
|
+
const presentationVerificationIdentifier = await generateKYCPresentation(
|
|
2398
|
+
exchange,
|
|
2399
|
+
'verificationIdentifier'
|
|
2400
|
+
);
|
|
2401
|
+
|
|
2402
|
+
let identifyWebhookPayload;
|
|
2403
|
+
nock(mockVendorUrl)
|
|
2404
|
+
.post(identifyUserOnVendorEndpoint)
|
|
2405
|
+
.reply(200, (uri, body) => {
|
|
2406
|
+
identifyWebhookPayload = body;
|
|
2407
|
+
return {
|
|
2408
|
+
vendorUserId,
|
|
2409
|
+
};
|
|
2410
|
+
});
|
|
2411
|
+
|
|
2412
|
+
const response = await fastify.injectJson({
|
|
2413
|
+
method: 'POST',
|
|
2414
|
+
url: idUrl(tenant),
|
|
2415
|
+
headers: {
|
|
2416
|
+
'x-vnf-protocol-version': `${VnfProtocolVersions.VNF_PROTOCOL_VERSION_2}`,
|
|
2417
|
+
},
|
|
2418
|
+
payload: {
|
|
2419
|
+
jwt_vp: await presentationVerificationIdentifier.sign(
|
|
2420
|
+
holderKid,
|
|
2421
|
+
holderKeys.privateKey,
|
|
2422
|
+
holderDid
|
|
2423
|
+
),
|
|
2424
|
+
exchange_id: exchange._id,
|
|
2425
|
+
},
|
|
2426
|
+
});
|
|
2427
|
+
|
|
2428
|
+
expect(response.statusCode).toEqual(200);
|
|
2429
|
+
expect(response.json).toEqual({
|
|
2430
|
+
token: expect.any(String),
|
|
2431
|
+
exchange: {
|
|
2432
|
+
disclosureComplete: true,
|
|
2433
|
+
exchangeComplete: false,
|
|
2434
|
+
id: exchange._id,
|
|
2435
|
+
type: ExchangeTypes.ISSUING,
|
|
2436
|
+
},
|
|
2437
|
+
});
|
|
2438
|
+
const dbResult = await mongoDb()
|
|
2439
|
+
.collection('vendorUserIdMappings')
|
|
2440
|
+
.findOne({ vendorUserId });
|
|
2441
|
+
const { sub } = decodeJwt(response.json.token);
|
|
2442
|
+
expect(dbResult).toEqual({
|
|
2443
|
+
_id: new ObjectId(sub),
|
|
2444
|
+
vendorUserId,
|
|
2445
|
+
tenantId: new ObjectId(tenant._id),
|
|
2446
|
+
createdAt: expect.any(Date),
|
|
2447
|
+
updatedAt: expect.any(Date),
|
|
2448
|
+
});
|
|
2449
|
+
expect(
|
|
2450
|
+
await mongoDb()
|
|
2451
|
+
.collection('exchanges')
|
|
2452
|
+
.findOne({ _id: new ObjectId(exchange._id) })
|
|
2453
|
+
).toEqual(
|
|
2454
|
+
expectedExchange(tenant, exchange, presentationVerificationIdentifier, [
|
|
2455
|
+
ExchangeStates.NEW,
|
|
2456
|
+
ExchangeStates.DISCLOSURE_RECEIVED,
|
|
2457
|
+
ExchangeStates.DISCLOSURE_CHECKED,
|
|
2458
|
+
ExchangeStates.IDENTIFIED,
|
|
2459
|
+
])
|
|
2460
|
+
);
|
|
2461
|
+
expect(identifyWebhookPayload).toEqual({
|
|
2462
|
+
phones: [],
|
|
2463
|
+
idDocumentCredentials: [
|
|
2464
|
+
{
|
|
2465
|
+
id: verificationIdentifierPayload.vc.id,
|
|
2466
|
+
type: ['VerificationIdentifier', 'VerifiableCredential'],
|
|
2467
|
+
credentialSubject:
|
|
2468
|
+
verificationIdentifierPayload.vc.credentialSubject,
|
|
2469
|
+
credentialType: 'VerificationIdentifier',
|
|
2470
|
+
issuer: {
|
|
2471
|
+
id: 'did:velocity:0x0b154da48d0f213c26c4b1d040dc5ff1dbf99ffa',
|
|
2472
|
+
},
|
|
2473
|
+
issuanceDate: expect.stringMatching(ISO_DATETIME_FORMAT),
|
|
2474
|
+
credentialChecks: DEFAULT_CREDENTIAL_CHECKS,
|
|
2475
|
+
},
|
|
2476
|
+
],
|
|
2477
|
+
emails: [],
|
|
2478
|
+
phoneCredentials: [],
|
|
2479
|
+
emailCredentials: [],
|
|
2480
|
+
tenantDID: tenant.did,
|
|
2481
|
+
tenantId: tenant._id,
|
|
2482
|
+
exchangeId: exchange._id,
|
|
2483
|
+
});
|
|
2484
|
+
});
|
|
2485
|
+
|
|
2486
|
+
// TODO Remove after December 2024
|
|
2487
|
+
it('should 200 when email & verificationIdentifier included for the "issued" claim', async () => {
|
|
2488
|
+
fastify.overrides.reqConfig = (config) => ({
|
|
2489
|
+
...config,
|
|
2490
|
+
vendorCredentialsIncludeIssuedClaim: true,
|
|
2491
|
+
});
|
|
2492
|
+
|
|
2493
|
+
const presentation = await generateKYCPresentation(exchange, [
|
|
2494
|
+
'email',
|
|
2495
|
+
'verificationIdentifier',
|
|
2496
|
+
]);
|
|
2497
|
+
|
|
2498
|
+
let identifyWebhookPayload;
|
|
2499
|
+
nock(mockVendorUrl)
|
|
2500
|
+
.post(identifyUserOnVendorEndpoint)
|
|
2501
|
+
.reply(200, (uri, body) => {
|
|
2502
|
+
identifyWebhookPayload = body;
|
|
2503
|
+
return {
|
|
2504
|
+
vendorUserId,
|
|
2505
|
+
};
|
|
2506
|
+
});
|
|
2507
|
+
|
|
2508
|
+
const response = await fastify.injectJson({
|
|
2509
|
+
method: 'POST',
|
|
2510
|
+
url: idUrl(tenant),
|
|
2511
|
+
headers: {
|
|
2512
|
+
'x-vnf-protocol-version': `${VnfProtocolVersions.VNF_PROTOCOL_VERSION_2}`,
|
|
2513
|
+
},
|
|
2514
|
+
payload: {
|
|
2515
|
+
jwt_vp: await presentation.sign(
|
|
2516
|
+
holderKid,
|
|
2517
|
+
holderKeys.privateKey,
|
|
2518
|
+
holderDid
|
|
2519
|
+
),
|
|
2520
|
+
exchange_id: exchange._id,
|
|
2521
|
+
},
|
|
2522
|
+
});
|
|
2523
|
+
|
|
2524
|
+
expect(response.statusCode).toEqual(200);
|
|
2525
|
+
expect(response.json).toEqual({
|
|
2526
|
+
token: expect.any(String),
|
|
2527
|
+
exchange: {
|
|
2528
|
+
disclosureComplete: true,
|
|
2529
|
+
exchangeComplete: false,
|
|
2530
|
+
id: exchange._id,
|
|
2531
|
+
type: ExchangeTypes.ISSUING,
|
|
2532
|
+
},
|
|
2533
|
+
});
|
|
2534
|
+
const dbResult = await mongoDb()
|
|
2535
|
+
.collection('vendorUserIdMappings')
|
|
2536
|
+
.findOne({ vendorUserId });
|
|
2537
|
+
const { sub } = decodeJwt(response.json.token);
|
|
2538
|
+
expect(dbResult).toEqual({
|
|
2539
|
+
_id: new ObjectId(sub),
|
|
2540
|
+
vendorUserId,
|
|
2541
|
+
tenantId: new ObjectId(tenant._id),
|
|
2542
|
+
createdAt: expect.any(Date),
|
|
2543
|
+
updatedAt: expect.any(Date),
|
|
2544
|
+
});
|
|
2545
|
+
expect(
|
|
2546
|
+
await mongoDb()
|
|
2547
|
+
.collection('exchanges')
|
|
2548
|
+
.findOne({ _id: new ObjectId(exchange._id) })
|
|
2549
|
+
).toEqual(
|
|
2550
|
+
expectedExchange(tenant, exchange, presentation, [
|
|
2551
|
+
ExchangeStates.NEW,
|
|
2552
|
+
ExchangeStates.DISCLOSURE_RECEIVED,
|
|
2553
|
+
ExchangeStates.DISCLOSURE_CHECKED,
|
|
2554
|
+
ExchangeStates.IDENTIFIED,
|
|
2555
|
+
])
|
|
2556
|
+
);
|
|
2557
|
+
expect(identifyWebhookPayload).toEqual({
|
|
2558
|
+
emailCredentials: [
|
|
2559
|
+
{
|
|
2560
|
+
id: emailPayload.vc.id,
|
|
2561
|
+
type: ['EmailV1.0', 'VerifiableCredential'],
|
|
2562
|
+
credentialSubject: emailPayload.vc.credentialSubject,
|
|
2563
|
+
credentialType: 'EmailV1.0',
|
|
2564
|
+
issuer: {
|
|
2565
|
+
id: 'did:velocity:0x0b154da48d0f213c26c4b1d040dc5ff1dbf99ffa',
|
|
2566
|
+
},
|
|
2567
|
+
issued: expect.stringMatching(ISO_DATETIME_FORMAT),
|
|
2568
|
+
issuanceDate: expect.stringMatching(ISO_DATETIME_FORMAT),
|
|
2569
|
+
credentialChecks: DEFAULT_CREDENTIAL_CHECKS,
|
|
2570
|
+
},
|
|
2571
|
+
],
|
|
2572
|
+
idDocumentCredentials: [
|
|
2573
|
+
{
|
|
2574
|
+
id: verificationIdentifierPayload.vc.id,
|
|
2575
|
+
type: ['VerificationIdentifier', 'VerifiableCredential'],
|
|
2576
|
+
credentialSubject:
|
|
2577
|
+
verificationIdentifierPayload.vc.credentialSubject,
|
|
2578
|
+
credentialType: 'VerificationIdentifier',
|
|
2579
|
+
issuer: {
|
|
2580
|
+
id: 'did:velocity:0x0b154da48d0f213c26c4b1d040dc5ff1dbf99ffa',
|
|
2581
|
+
},
|
|
2582
|
+
issued: expect.stringMatching(ISO_DATETIME_FORMAT),
|
|
2583
|
+
issuanceDate: expect.stringMatching(ISO_DATETIME_FORMAT),
|
|
2584
|
+
credentialChecks: DEFAULT_CREDENTIAL_CHECKS,
|
|
2585
|
+
},
|
|
2586
|
+
],
|
|
2587
|
+
phoneCredentials: [],
|
|
2588
|
+
emails: [emailPayload.vc.credentialSubject.email],
|
|
2589
|
+
phones: [],
|
|
2590
|
+
tenantDID: tenant.did,
|
|
2591
|
+
tenantId: tenant._id,
|
|
2592
|
+
exchangeId: exchange._id,
|
|
2593
|
+
});
|
|
2594
|
+
});
|
|
2595
|
+
|
|
2596
|
+
it('should 200 when identifying unrecognized credential type', async () => {
|
|
2597
|
+
let bodyReceived;
|
|
2598
|
+
nock(mockVendorUrl)
|
|
2599
|
+
.post(identifyUserOnVendorEndpoint, (body) => {
|
|
2600
|
+
bodyReceived = body;
|
|
2601
|
+
return body;
|
|
2602
|
+
})
|
|
2603
|
+
.reply(200, {
|
|
2604
|
+
vendorUserId,
|
|
2605
|
+
});
|
|
2606
|
+
|
|
2607
|
+
const presentationWhatever = await generateKYCPresentation(
|
|
2608
|
+
exchange,
|
|
2609
|
+
'whateverIdentifier'
|
|
2610
|
+
);
|
|
2611
|
+
|
|
2612
|
+
const response = await fastify.injectJson({
|
|
2613
|
+
method: 'POST',
|
|
2614
|
+
url: idUrl(tenant),
|
|
2615
|
+
headers: {
|
|
2616
|
+
'x-vnf-protocol-version': `${VnfProtocolVersions.VNF_PROTOCOL_VERSION_2}`,
|
|
2617
|
+
},
|
|
2618
|
+
payload: {
|
|
2619
|
+
jwt_vp: await presentationWhatever.sign(
|
|
2620
|
+
holderKid,
|
|
2621
|
+
holderKeys.privateKey,
|
|
2622
|
+
holderDid
|
|
2623
|
+
),
|
|
2624
|
+
exchange_id: exchange._id,
|
|
2625
|
+
},
|
|
2626
|
+
});
|
|
2627
|
+
|
|
2628
|
+
expect(response.statusCode).toEqual(200);
|
|
2629
|
+
expect(bodyReceived).toEqual({
|
|
2630
|
+
phones: [],
|
|
2631
|
+
idDocumentCredentials: [
|
|
2632
|
+
{
|
|
2633
|
+
id: idDocPayload.vc.id,
|
|
2634
|
+
type: ['Whatever', 'VerifiableCredential'],
|
|
2635
|
+
credentialType: 'Whatever',
|
|
2636
|
+
credentialSubject: {
|
|
2637
|
+
email: 'adam.smith@example.com',
|
|
2638
|
+
},
|
|
2639
|
+
issuer: {
|
|
2640
|
+
id: 'did:velocity:0x0b154da48d0f213c26c4b1d040dc5ff1dbf99ffa',
|
|
2641
|
+
},
|
|
2642
|
+
issuanceDate: expect.stringMatching(ISO_DATETIME_FORMAT),
|
|
2643
|
+
credentialChecks: DEFAULT_CREDENTIAL_CHECKS,
|
|
2644
|
+
},
|
|
2645
|
+
],
|
|
2646
|
+
emails: [],
|
|
2647
|
+
phoneCredentials: [],
|
|
2648
|
+
emailCredentials: [],
|
|
2649
|
+
vendorOrganizationId: tenant.vendorOrganizationId,
|
|
2650
|
+
tenantDID: tenant.did,
|
|
2651
|
+
tenantId: tenant._id,
|
|
2652
|
+
exchangeId: exchange._id,
|
|
2653
|
+
});
|
|
2654
|
+
|
|
2655
|
+
expect(
|
|
2656
|
+
await mongoDb()
|
|
2657
|
+
.collection('exchanges')
|
|
2658
|
+
.findOne({ _id: new ObjectId(exchange._id) })
|
|
2659
|
+
).toEqual(
|
|
2660
|
+
expectedExchange(tenant, exchange, presentationWhatever, [
|
|
2661
|
+
ExchangeStates.NEW,
|
|
2662
|
+
ExchangeStates.DISCLOSURE_RECEIVED,
|
|
2663
|
+
ExchangeStates.DISCLOSURE_CHECKED,
|
|
2664
|
+
ExchangeStates.IDENTIFIED,
|
|
2665
|
+
])
|
|
2666
|
+
);
|
|
2667
|
+
});
|
|
2668
|
+
|
|
2669
|
+
it('should 200 if vcs credential not exist', async () => {
|
|
2670
|
+
const presentationMixed = await generateKYCPresentation(
|
|
2671
|
+
exchange,
|
|
2672
|
+
['idDocument', 'phone'],
|
|
2673
|
+
{ isBrokeVCS: true }
|
|
2674
|
+
);
|
|
2675
|
+
|
|
2676
|
+
let identifyWebhookPayload;
|
|
2677
|
+
nock(mockVendorUrl)
|
|
2678
|
+
.post(identifyUserOnVendorEndpoint)
|
|
2679
|
+
.reply(200, (uri, body) => {
|
|
2680
|
+
identifyWebhookPayload = body;
|
|
2681
|
+
return { vendorUserId };
|
|
2682
|
+
});
|
|
2683
|
+
|
|
2684
|
+
const response = await fastify.injectJson({
|
|
2685
|
+
method: 'POST',
|
|
2686
|
+
url: idUrl(tenant),
|
|
2687
|
+
headers: {
|
|
2688
|
+
'x-vnf-protocol-version': `${VnfProtocolVersions.VNF_PROTOCOL_VERSION_2}`,
|
|
2689
|
+
},
|
|
2690
|
+
payload: {
|
|
2691
|
+
jwt_vp: await presentationMixed.sign(
|
|
2692
|
+
holderKid,
|
|
2693
|
+
holderKeys.privateKey,
|
|
2694
|
+
holderDid
|
|
2695
|
+
),
|
|
2696
|
+
exchange_id: exchange._id,
|
|
2697
|
+
},
|
|
2698
|
+
});
|
|
2699
|
+
|
|
2700
|
+
expect(response.statusCode).toEqual(200);
|
|
2701
|
+
expect(response.json).toEqual({
|
|
2702
|
+
token: expect.any(String),
|
|
2703
|
+
exchange: {
|
|
2704
|
+
disclosureComplete: true,
|
|
2705
|
+
exchangeComplete: false,
|
|
2706
|
+
id: exchange._id,
|
|
2707
|
+
type: ExchangeTypes.ISSUING,
|
|
2708
|
+
},
|
|
2709
|
+
});
|
|
2710
|
+
const dbResult = await mongoDb()
|
|
2711
|
+
.collection('vendorUserIdMappings')
|
|
2712
|
+
.findOne({ vendorUserId });
|
|
2713
|
+
const { sub } = decodeJwt(response.json.token);
|
|
2714
|
+
expect(dbResult).toEqual({
|
|
2715
|
+
_id: new ObjectId(sub),
|
|
2716
|
+
vendorUserId,
|
|
2717
|
+
tenantId: new ObjectId(tenant._id),
|
|
2718
|
+
createdAt: expect.any(Date),
|
|
2719
|
+
updatedAt: expect.any(Date),
|
|
2720
|
+
});
|
|
2721
|
+
|
|
2722
|
+
expect(
|
|
2723
|
+
await mongoDb()
|
|
2724
|
+
.collection('exchanges')
|
|
2725
|
+
.findOne({ _id: new ObjectId(exchange._id) })
|
|
2726
|
+
).toEqual(
|
|
2727
|
+
expectedExchange(tenant, exchange, presentationMixed, [
|
|
2728
|
+
ExchangeStates.NEW,
|
|
2729
|
+
ExchangeStates.DISCLOSURE_RECEIVED,
|
|
2730
|
+
ExchangeStates.DISCLOSURE_CHECKED,
|
|
2731
|
+
ExchangeStates.IDENTIFIED,
|
|
2732
|
+
])
|
|
2733
|
+
);
|
|
2734
|
+
|
|
2735
|
+
expect(identifyWebhookPayload).toEqual({
|
|
2736
|
+
phoneCredentials: [],
|
|
2737
|
+
phones: [],
|
|
2738
|
+
emails: [],
|
|
2739
|
+
idDocumentCredentials: [
|
|
2740
|
+
{
|
|
2741
|
+
id: idDocPayload.vc.id,
|
|
2742
|
+
type: ['IdDocumentV1.0', 'VerifiableCredential'],
|
|
2743
|
+
credentialSubject: idDocPayload.vc.credentialSubject,
|
|
2744
|
+
credentialType: 'IdDocumentV1.0',
|
|
2745
|
+
issuer: {
|
|
2746
|
+
id: 'did:velocity:0x0b154da48d0f213c26c4b1d040dc5ff1dbf99ffa',
|
|
2747
|
+
},
|
|
2748
|
+
issuanceDate: expect.stringMatching(ISO_DATETIME_FORMAT),
|
|
2749
|
+
validFrom: '2017-09-01',
|
|
2750
|
+
validUntil: '2021-09-01',
|
|
2751
|
+
credentialChecks: DEFAULT_CREDENTIAL_CHECKS,
|
|
2752
|
+
},
|
|
2753
|
+
],
|
|
2754
|
+
emailCredentials: [],
|
|
2755
|
+
tenantDID: tenant.did,
|
|
2756
|
+
tenantId: tenant._id,
|
|
2757
|
+
exchangeId: exchange._id,
|
|
2758
|
+
});
|
|
2759
|
+
});
|
|
2760
|
+
|
|
2761
|
+
it('should 200 when identifying mixed ', async () => {
|
|
2762
|
+
const presentationMixed = await generateKYCPresentation(exchange, [
|
|
2763
|
+
'idDocument',
|
|
2764
|
+
'phone',
|
|
2765
|
+
'email',
|
|
2766
|
+
]);
|
|
2767
|
+
|
|
2768
|
+
let identifyWebhookPayload;
|
|
2769
|
+
nock(mockVendorUrl)
|
|
2770
|
+
.post(identifyUserOnVendorEndpoint)
|
|
2771
|
+
.reply(200, (uri, body) => {
|
|
2772
|
+
identifyWebhookPayload = body;
|
|
2773
|
+
return { vendorUserId };
|
|
2774
|
+
});
|
|
2775
|
+
|
|
2776
|
+
const response = await fastify.injectJson({
|
|
2777
|
+
method: 'POST',
|
|
2778
|
+
url: idUrl(tenant),
|
|
2779
|
+
headers: {
|
|
2780
|
+
'x-vnf-protocol-version': `${VnfProtocolVersions.VNF_PROTOCOL_VERSION_2}`,
|
|
2781
|
+
},
|
|
2782
|
+
payload: {
|
|
2783
|
+
jwt_vp: await presentationMixed.sign(
|
|
2784
|
+
holderKid,
|
|
2785
|
+
holderKeys.privateKey,
|
|
2786
|
+
holderDid
|
|
2787
|
+
),
|
|
2788
|
+
exchange_id: exchange._id,
|
|
2789
|
+
},
|
|
2790
|
+
});
|
|
2791
|
+
|
|
2792
|
+
expect(response.statusCode).toEqual(200);
|
|
2793
|
+
expect(response.json).toEqual({
|
|
2794
|
+
token: expect.any(String),
|
|
2795
|
+
exchange: {
|
|
2796
|
+
disclosureComplete: true,
|
|
2797
|
+
exchangeComplete: false,
|
|
2798
|
+
id: exchange._id,
|
|
2799
|
+
type: ExchangeTypes.ISSUING,
|
|
2800
|
+
},
|
|
2801
|
+
});
|
|
2802
|
+
const dbResult = await mongoDb()
|
|
2803
|
+
.collection('vendorUserIdMappings')
|
|
2804
|
+
.findOne({ vendorUserId });
|
|
2805
|
+
const { sub } = decodeJwt(response.json.token);
|
|
2806
|
+
expect(dbResult).toEqual({
|
|
2807
|
+
_id: new ObjectId(sub),
|
|
2808
|
+
vendorUserId,
|
|
2809
|
+
tenantId: new ObjectId(tenant._id),
|
|
2810
|
+
createdAt: expect.any(Date),
|
|
2811
|
+
updatedAt: expect.any(Date),
|
|
2812
|
+
});
|
|
2813
|
+
expect(
|
|
2814
|
+
await mongoDb()
|
|
2815
|
+
.collection('exchanges')
|
|
2816
|
+
.findOne({ _id: new ObjectId(exchange._id) })
|
|
2817
|
+
).toEqual(
|
|
2818
|
+
expectedExchange(tenant, exchange, presentationMixed, [
|
|
2819
|
+
ExchangeStates.NEW,
|
|
2820
|
+
ExchangeStates.DISCLOSURE_RECEIVED,
|
|
2821
|
+
ExchangeStates.DISCLOSURE_CHECKED,
|
|
2822
|
+
ExchangeStates.IDENTIFIED,
|
|
2823
|
+
])
|
|
2824
|
+
);
|
|
2825
|
+
|
|
2826
|
+
expect(identifyWebhookPayload).toEqual({
|
|
2827
|
+
phoneCredentials: [
|
|
2828
|
+
{
|
|
2829
|
+
id: phonePayload.vc.id,
|
|
2830
|
+
type: ['PhoneV1.0', 'VerifiableCredential'],
|
|
2831
|
+
credentialSubject: phonePayload.vc.credentialSubject,
|
|
2832
|
+
credentialType: 'PhoneV1.0',
|
|
2833
|
+
issuer: {
|
|
2834
|
+
id: 'did:velocity:0x0b154da48d0f213c26c4b1d040dc5ff1dbf99ffa',
|
|
2835
|
+
},
|
|
2836
|
+
issuanceDate: expect.stringMatching(ISO_DATETIME_FORMAT),
|
|
2837
|
+
credentialChecks: DEFAULT_CREDENTIAL_CHECKS,
|
|
2838
|
+
},
|
|
2839
|
+
],
|
|
2840
|
+
phones: [phonePayload.vc.credentialSubject.phone],
|
|
2841
|
+
emails: [emailPayload.vc.credentialSubject.email],
|
|
2842
|
+
idDocumentCredentials: [
|
|
2843
|
+
{
|
|
2844
|
+
id: idDocPayload.vc.id,
|
|
2845
|
+
type: ['IdDocumentV1.0', 'VerifiableCredential'],
|
|
2846
|
+
credentialSubject: idDocPayload.vc.credentialSubject,
|
|
2847
|
+
credentialType: 'IdDocumentV1.0',
|
|
2848
|
+
issuer: {
|
|
2849
|
+
id: 'did:velocity:0x0b154da48d0f213c26c4b1d040dc5ff1dbf99ffa',
|
|
2850
|
+
},
|
|
2851
|
+
issuanceDate: expect.stringMatching(ISO_DATETIME_FORMAT),
|
|
2852
|
+
validFrom: '2017-09-01',
|
|
2853
|
+
validUntil: '2021-09-01',
|
|
2854
|
+
credentialChecks: DEFAULT_CREDENTIAL_CHECKS,
|
|
2855
|
+
},
|
|
2856
|
+
],
|
|
2857
|
+
emailCredentials: [
|
|
2858
|
+
{
|
|
2859
|
+
id: emailPayload.vc.id,
|
|
2860
|
+
type: ['EmailV1.0', 'VerifiableCredential'],
|
|
2861
|
+
credentialSubject: emailPayload.vc.credentialSubject,
|
|
2862
|
+
credentialType: 'EmailV1.0',
|
|
2863
|
+
issuer: {
|
|
2864
|
+
id: 'did:velocity:0x0b154da48d0f213c26c4b1d040dc5ff1dbf99ffa',
|
|
2865
|
+
},
|
|
2866
|
+
issuanceDate: expect.stringMatching(ISO_DATETIME_FORMAT),
|
|
2867
|
+
credentialChecks: DEFAULT_CREDENTIAL_CHECKS,
|
|
2868
|
+
},
|
|
2869
|
+
],
|
|
2870
|
+
tenantDID: tenant.did,
|
|
2871
|
+
tenantId: tenant._id,
|
|
2872
|
+
exchangeId: exchange._id,
|
|
2873
|
+
});
|
|
2874
|
+
});
|
|
2875
|
+
|
|
2876
|
+
it('should 200 when identifying mixed of schemas legacy versions of docs', async () => {
|
|
2877
|
+
const presentationMixed = await generateKYCPresentation(exchange, [
|
|
2878
|
+
'legacyIdDocument',
|
|
2879
|
+
'phone',
|
|
2880
|
+
'legacyEmail',
|
|
2881
|
+
]);
|
|
2882
|
+
|
|
2883
|
+
let identifyWebhookPayload;
|
|
2884
|
+
nock(mockVendorUrl)
|
|
2885
|
+
.post(identifyUserOnVendorEndpoint)
|
|
2886
|
+
.reply(200, (uri, body) => {
|
|
2887
|
+
identifyWebhookPayload = body;
|
|
2888
|
+
return { vendorUserId };
|
|
2889
|
+
});
|
|
2890
|
+
|
|
2891
|
+
const response = await fastify.injectJson({
|
|
2892
|
+
method: 'POST',
|
|
2893
|
+
url: idUrl(tenant),
|
|
2894
|
+
headers: {
|
|
2895
|
+
'x-vnf-protocol-version': `${VnfProtocolVersions.VNF_PROTOCOL_VERSION_2}`,
|
|
2896
|
+
},
|
|
2897
|
+
payload: {
|
|
2898
|
+
jwt_vp: await presentationMixed.sign(
|
|
2899
|
+
holderKid,
|
|
2900
|
+
holderKeys.privateKey,
|
|
2901
|
+
holderDid
|
|
2902
|
+
),
|
|
2903
|
+
exchange_id: exchange._id,
|
|
2904
|
+
},
|
|
2905
|
+
});
|
|
2906
|
+
|
|
2907
|
+
expect(response.statusCode).toEqual(200);
|
|
2908
|
+
expect(response.json).toEqual({
|
|
2909
|
+
token: expect.any(String),
|
|
2910
|
+
exchange: {
|
|
2911
|
+
disclosureComplete: true,
|
|
2912
|
+
exchangeComplete: false,
|
|
2913
|
+
id: exchange._id,
|
|
2914
|
+
type: ExchangeTypes.ISSUING,
|
|
2915
|
+
},
|
|
2916
|
+
});
|
|
2917
|
+
const dbResult = await mongoDb()
|
|
2918
|
+
.collection('vendorUserIdMappings')
|
|
2919
|
+
.findOne({ vendorUserId });
|
|
2920
|
+
const { sub } = decodeJwt(response.json.token);
|
|
2921
|
+
expect(dbResult).toEqual({
|
|
2922
|
+
_id: new ObjectId(sub),
|
|
2923
|
+
vendorUserId,
|
|
2924
|
+
tenantId: new ObjectId(tenant._id),
|
|
2925
|
+
createdAt: expect.any(Date),
|
|
2926
|
+
updatedAt: expect.any(Date),
|
|
2927
|
+
});
|
|
2928
|
+
|
|
2929
|
+
const exchangeDBResult = await mongoDb()
|
|
2930
|
+
.collection('exchanges')
|
|
2931
|
+
.findOne({ _id: new ObjectId(exchange._id) });
|
|
2932
|
+
expect(exchangeDBResult).toEqual(
|
|
2933
|
+
expectedExchange(tenant, exchange, presentationMixed, [
|
|
2934
|
+
ExchangeStates.NEW,
|
|
2935
|
+
ExchangeStates.DISCLOSURE_RECEIVED,
|
|
2936
|
+
ExchangeStates.DISCLOSURE_CHECKED,
|
|
2937
|
+
ExchangeStates.IDENTIFIED,
|
|
2938
|
+
])
|
|
2939
|
+
);
|
|
2940
|
+
|
|
2941
|
+
expect(identifyWebhookPayload).toEqual({
|
|
2942
|
+
firstName: 'Sam',
|
|
2943
|
+
lastName: 'Smith',
|
|
2944
|
+
phoneCredentials: [
|
|
2945
|
+
{
|
|
2946
|
+
id: phonePayload.vc.id,
|
|
2947
|
+
type: ['PhoneV1.0', 'VerifiableCredential'],
|
|
2948
|
+
credentialSubject: phonePayload.vc.credentialSubject,
|
|
2949
|
+
credentialType: 'PhoneV1.0',
|
|
2950
|
+
issuer: {
|
|
2951
|
+
id: 'did:velocity:0x0b154da48d0f213c26c4b1d040dc5ff1dbf99ffa',
|
|
2952
|
+
},
|
|
2953
|
+
issuanceDate: expect.stringMatching(ISO_DATETIME_FORMAT),
|
|
2954
|
+
credentialChecks: DEFAULT_CREDENTIAL_CHECKS,
|
|
2955
|
+
},
|
|
2956
|
+
],
|
|
2957
|
+
phones: [phonePayload.vc.credentialSubject.phone],
|
|
2958
|
+
emails: [emailPayload.vc.credentialSubject.email],
|
|
2959
|
+
idDocumentCredentials: [
|
|
2960
|
+
{
|
|
2961
|
+
id: legacyIdDocPayload.vc.id,
|
|
2962
|
+
type: ['IdDocument', 'VerifiableCredential'],
|
|
2963
|
+
credentialSubject: legacyIdDocPayload.vc.credentialSubject,
|
|
2964
|
+
credentialType: 'IdDocument',
|
|
2965
|
+
issuer: {
|
|
2966
|
+
id: 'did:velocity:0x0b154da48d0f213c26c4b1d040dc5ff1dbf99ffa',
|
|
2967
|
+
},
|
|
2968
|
+
issuanceDate: expect.stringMatching(ISO_DATETIME_FORMAT),
|
|
2969
|
+
validFrom: '2017-09-01',
|
|
2970
|
+
validUntil: '2021-09-01',
|
|
2971
|
+
credentialChecks: DEFAULT_CREDENTIAL_CHECKS,
|
|
2972
|
+
},
|
|
2973
|
+
],
|
|
2974
|
+
emailCredentials: [
|
|
2975
|
+
{
|
|
2976
|
+
id: emailPayload.vc.id,
|
|
2977
|
+
type: ['Email', 'VerifiableCredential'],
|
|
2978
|
+
credentialSubject: emailPayload.vc.credentialSubject,
|
|
2979
|
+
credentialType: 'Email',
|
|
2980
|
+
issuer: {
|
|
2981
|
+
id: 'did:velocity:0x0b154da48d0f213c26c4b1d040dc5ff1dbf99ffa',
|
|
2982
|
+
},
|
|
2983
|
+
issuanceDate: expect.stringMatching(ISO_DATETIME_FORMAT),
|
|
2984
|
+
credentialChecks: DEFAULT_CREDENTIAL_CHECKS,
|
|
2985
|
+
},
|
|
2986
|
+
],
|
|
2987
|
+
tenantDID: tenant.did,
|
|
2988
|
+
tenantId: tenant._id,
|
|
2989
|
+
exchangeId: exchange._id,
|
|
2990
|
+
});
|
|
2991
|
+
});
|
|
2992
|
+
|
|
2993
|
+
it('should 200 when sub is existing', async () => {
|
|
2994
|
+
const presentationIdDocument = await generateKYCPresentation(
|
|
2995
|
+
exchange,
|
|
2996
|
+
'idDocument'
|
|
2997
|
+
);
|
|
2998
|
+
|
|
2999
|
+
const existingUser = await persistVendorUserIdMapping({ tenant });
|
|
3000
|
+
let identifyWebhookPayload;
|
|
3001
|
+
nock(mockVendorUrl)
|
|
3002
|
+
.post(identifyUserOnVendorEndpoint)
|
|
3003
|
+
.reply(200, (uri, body) => {
|
|
3004
|
+
identifyWebhookPayload = body;
|
|
3005
|
+
return {
|
|
3006
|
+
vendorUserId: existingUser.vendorUserId,
|
|
3007
|
+
};
|
|
3008
|
+
});
|
|
3009
|
+
|
|
3010
|
+
const response = await fastify.injectJson({
|
|
3011
|
+
method: 'POST',
|
|
3012
|
+
url: idUrl(tenant),
|
|
3013
|
+
headers: {
|
|
3014
|
+
'x-vnf-protocol-version': `${VnfProtocolVersions.VNF_PROTOCOL_VERSION_2}`,
|
|
3015
|
+
},
|
|
3016
|
+
payload: {
|
|
3017
|
+
jwt_vp: await presentationIdDocument.sign(
|
|
3018
|
+
holderKid,
|
|
3019
|
+
holderKeys.privateKey,
|
|
3020
|
+
holderDid
|
|
3021
|
+
),
|
|
3022
|
+
exchange_id: exchange._id,
|
|
3023
|
+
},
|
|
3024
|
+
});
|
|
3025
|
+
|
|
3026
|
+
expect(response.statusCode).toEqual(200);
|
|
3027
|
+
expect(response.json).toEqual({
|
|
3028
|
+
token: expect.any(String),
|
|
3029
|
+
exchange: {
|
|
3030
|
+
disclosureComplete: true,
|
|
3031
|
+
exchangeComplete: false,
|
|
3032
|
+
id: exchange._id,
|
|
3033
|
+
type: ExchangeTypes.ISSUING,
|
|
3034
|
+
},
|
|
3035
|
+
});
|
|
3036
|
+
|
|
3037
|
+
const dbResult = await mongoDb()
|
|
3038
|
+
.collection('vendorUserIdMappings')
|
|
3039
|
+
.findOne({ vendorUserId });
|
|
3040
|
+
const { sub } = decodeJwt(response.json.token);
|
|
3041
|
+
expect(sub).toEqual(existingUser._id.toString());
|
|
3042
|
+
expect(dbResult).toEqual({
|
|
3043
|
+
_id: new ObjectId(existingUser._id),
|
|
3044
|
+
vendorUserId,
|
|
3045
|
+
tenantId: new ObjectId(tenant._id),
|
|
3046
|
+
createdAt: expect.any(Date),
|
|
3047
|
+
updatedAt: expect.any(Date),
|
|
3048
|
+
});
|
|
3049
|
+
|
|
3050
|
+
const exchangeDBResult = await mongoDb()
|
|
3051
|
+
.collection('exchanges')
|
|
3052
|
+
.findOne({ _id: new ObjectId(exchange._id) });
|
|
3053
|
+
expect(exchangeDBResult).toEqual(
|
|
3054
|
+
expectedExchange(tenant, exchange, presentationIdDocument, [
|
|
3055
|
+
ExchangeStates.NEW,
|
|
3056
|
+
ExchangeStates.DISCLOSURE_RECEIVED,
|
|
3057
|
+
ExchangeStates.DISCLOSURE_CHECKED,
|
|
3058
|
+
ExchangeStates.IDENTIFIED,
|
|
3059
|
+
])
|
|
3060
|
+
);
|
|
3061
|
+
expect(identifyWebhookPayload).toEqual({
|
|
3062
|
+
emailCredentials: [],
|
|
3063
|
+
emails: [],
|
|
3064
|
+
exchangeId: exchange._id,
|
|
3065
|
+
idDocumentCredentials: [
|
|
3066
|
+
{
|
|
3067
|
+
id: idDocPayload.vc.id,
|
|
3068
|
+
type: ['IdDocumentV1.0', 'VerifiableCredential'],
|
|
3069
|
+
credentialSubject: idDocPayload.vc.credentialSubject,
|
|
3070
|
+
credentialType: 'IdDocumentV1.0',
|
|
3071
|
+
issuanceDate: expect.stringMatching(ISO_DATETIME_FORMAT),
|
|
3072
|
+
issuer: {
|
|
3073
|
+
id: 'did:velocity:0x0b154da48d0f213c26c4b1d040dc5ff1dbf99ffa',
|
|
3074
|
+
},
|
|
3075
|
+
validFrom: '2017-09-01',
|
|
3076
|
+
validUntil: '2021-09-01',
|
|
3077
|
+
credentialChecks: DEFAULT_CREDENTIAL_CHECKS,
|
|
3078
|
+
},
|
|
3079
|
+
],
|
|
3080
|
+
phoneCredentials: [],
|
|
3081
|
+
phones: [],
|
|
3082
|
+
tenantDID: tenant.did,
|
|
3083
|
+
tenantId: tenant._id,
|
|
3084
|
+
});
|
|
3085
|
+
const countResult = await mongoDb()
|
|
3086
|
+
.collection('vendorUserIdMappings')
|
|
3087
|
+
.countDocuments({ vendorUserId });
|
|
3088
|
+
expect(countResult).toEqual(1);
|
|
3089
|
+
});
|
|
3090
|
+
|
|
3091
|
+
it('should use another prefix url if tenant has webhookUrl', async () => {
|
|
3092
|
+
const webhookUrl = 'http://cutomUrl.com';
|
|
3093
|
+
const customTenant = await persistTenant({
|
|
3094
|
+
webhookUrl,
|
|
3095
|
+
});
|
|
3096
|
+
|
|
3097
|
+
const keyPair = generateKeyPair({ format: 'jwk' });
|
|
3098
|
+
|
|
3099
|
+
await persistKey({
|
|
3100
|
+
tenant: customTenant,
|
|
3101
|
+
kidFragment: '#ID1',
|
|
3102
|
+
keyPair,
|
|
3103
|
+
});
|
|
3104
|
+
|
|
3105
|
+
const customDisclosure = await persistDisclosure({
|
|
3106
|
+
tenant: customTenant,
|
|
3107
|
+
description: 'Credential Issuance disclosure',
|
|
3108
|
+
types: [{ type: 'EmailV1.0' }],
|
|
3109
|
+
vendorEndpoint: VendorEndpoint.ISSUING_IDENTIFICATION,
|
|
3110
|
+
purpose: 'Identification',
|
|
3111
|
+
duration: '6y',
|
|
3112
|
+
});
|
|
3113
|
+
|
|
3114
|
+
const customExchange = await persistOfferExchange({
|
|
3115
|
+
tenant: customTenant,
|
|
3116
|
+
disclosure: customDisclosure,
|
|
3117
|
+
});
|
|
3118
|
+
|
|
3119
|
+
const presentationIdDocument = await generateKYCPresentation(
|
|
3120
|
+
customExchange,
|
|
3121
|
+
'idDocument'
|
|
3122
|
+
);
|
|
3123
|
+
|
|
3124
|
+
const existingUser = await persistVendorUserIdMapping({
|
|
3125
|
+
tenant: customTenant,
|
|
3126
|
+
});
|
|
3127
|
+
let identifyWebhookPayload;
|
|
3128
|
+
const nockWebhook = await nock(webhookUrl)
|
|
3129
|
+
.post(identifyUserOnVendorEndpoint)
|
|
3130
|
+
.reply(200, (uri, body) => {
|
|
3131
|
+
identifyWebhookPayload = body;
|
|
3132
|
+
return {
|
|
3133
|
+
vendorUserId: existingUser.vendorUserId,
|
|
3134
|
+
};
|
|
3135
|
+
});
|
|
3136
|
+
|
|
3137
|
+
const response = await fastify.injectJson({
|
|
3138
|
+
method: 'POST',
|
|
3139
|
+
url: idUrl(customTenant),
|
|
3140
|
+
headers: {
|
|
3141
|
+
'x-vnf-protocol-version': `${VnfProtocolVersions.VNF_PROTOCOL_VERSION_2}`,
|
|
3142
|
+
},
|
|
3143
|
+
payload: {
|
|
3144
|
+
jwt_vp: await presentationIdDocument.sign(
|
|
3145
|
+
holderKid,
|
|
3146
|
+
holderKeys.privateKey,
|
|
3147
|
+
holderDid
|
|
3148
|
+
),
|
|
3149
|
+
exchange_id: customExchange._id,
|
|
3150
|
+
},
|
|
3151
|
+
});
|
|
3152
|
+
|
|
3153
|
+
expect(nockWebhook.isDone()).toEqual(true);
|
|
3154
|
+
expect(response.statusCode).toEqual(200);
|
|
3155
|
+
expect(response.json).toEqual({
|
|
3156
|
+
token: expect.any(String),
|
|
3157
|
+
exchange: {
|
|
3158
|
+
disclosureComplete: true,
|
|
3159
|
+
exchangeComplete: false,
|
|
3160
|
+
id: customExchange._id,
|
|
3161
|
+
type: ExchangeTypes.ISSUING,
|
|
3162
|
+
},
|
|
3163
|
+
});
|
|
3164
|
+
|
|
3165
|
+
const dbResult = await mongoDb()
|
|
3166
|
+
.collection('vendorUserIdMappings')
|
|
3167
|
+
.findOne({ vendorUserId });
|
|
3168
|
+
const { sub } = decodeJwt(response.json.token);
|
|
3169
|
+
expect(sub).toEqual(existingUser._id.toString());
|
|
3170
|
+
expect(dbResult).toEqual({
|
|
3171
|
+
_id: new ObjectId(existingUser._id),
|
|
3172
|
+
vendorUserId,
|
|
3173
|
+
tenantId: new ObjectId(customTenant._id),
|
|
3174
|
+
createdAt: expect.any(Date),
|
|
3175
|
+
updatedAt: expect.any(Date),
|
|
3176
|
+
});
|
|
3177
|
+
const exchangeDBResult = await mongoDb()
|
|
3178
|
+
.collection('exchanges')
|
|
3179
|
+
.findOne({ _id: new ObjectId(customExchange._id) });
|
|
3180
|
+
expect(exchangeDBResult).toEqual(
|
|
3181
|
+
expectedExchange(customTenant, customExchange, presentationIdDocument, [
|
|
3182
|
+
ExchangeStates.NEW,
|
|
3183
|
+
ExchangeStates.DISCLOSURE_RECEIVED,
|
|
3184
|
+
ExchangeStates.DISCLOSURE_CHECKED,
|
|
3185
|
+
ExchangeStates.IDENTIFIED,
|
|
3186
|
+
])
|
|
3187
|
+
);
|
|
3188
|
+
expect(identifyWebhookPayload).toEqual({
|
|
3189
|
+
emailCredentials: [],
|
|
3190
|
+
emails: [],
|
|
3191
|
+
exchangeId: customExchange._id,
|
|
3192
|
+
idDocumentCredentials: [
|
|
3193
|
+
{
|
|
3194
|
+
id: idDocPayload.vc.id,
|
|
3195
|
+
type: ['IdDocumentV1.0', 'VerifiableCredential'],
|
|
3196
|
+
credentialSubject: idDocPayload.vc.credentialSubject,
|
|
3197
|
+
credentialType: 'IdDocumentV1.0',
|
|
3198
|
+
issuanceDate: expect.stringMatching(ISO_DATETIME_FORMAT),
|
|
3199
|
+
issuer: {
|
|
3200
|
+
id: 'did:velocity:0x0b154da48d0f213c26c4b1d040dc5ff1dbf99ffa',
|
|
3201
|
+
},
|
|
3202
|
+
validFrom: '2017-09-01',
|
|
3203
|
+
validUntil: '2021-09-01',
|
|
3204
|
+
credentialChecks: DEFAULT_CREDENTIAL_CHECKS,
|
|
3205
|
+
},
|
|
3206
|
+
],
|
|
3207
|
+
phoneCredentials: [],
|
|
3208
|
+
phones: [],
|
|
3209
|
+
tenantDID: customTenant.did,
|
|
3210
|
+
tenantId: customTenant._id,
|
|
3211
|
+
});
|
|
3212
|
+
const countResult = await mongoDb()
|
|
3213
|
+
.collection('vendorUserIdMappings')
|
|
3214
|
+
.countDocuments({ vendorUserId });
|
|
3215
|
+
expect(countResult).toEqual(1);
|
|
3216
|
+
});
|
|
3217
|
+
|
|
3218
|
+
it('should return 400 when jwt_vp verification failed', async () => {
|
|
3219
|
+
const presentationIdDocument = await generateKYCPresentation(
|
|
3220
|
+
exchange,
|
|
3221
|
+
'idDocument'
|
|
3222
|
+
);
|
|
3223
|
+
|
|
3224
|
+
nock(mockVendorUrl).post(identifyUserOnVendorEndpoint).reply(200, {});
|
|
3225
|
+
|
|
3226
|
+
const response = await fastify.injectJson({
|
|
3227
|
+
method: 'POST',
|
|
3228
|
+
url: idUrl(tenant),
|
|
3229
|
+
headers: {
|
|
3230
|
+
'x-vnf-protocol-version': `${VnfProtocolVersions.VNF_PROTOCOL_VERSION_2}`,
|
|
3231
|
+
},
|
|
3232
|
+
payload: {
|
|
3233
|
+
jwt_vp: await presentationIdDocument.sign(
|
|
3234
|
+
'WRONG',
|
|
3235
|
+
holderKeys.privateKey,
|
|
3236
|
+
'MISSING'
|
|
3237
|
+
),
|
|
3238
|
+
exchange_id: exchange._id,
|
|
3239
|
+
},
|
|
3240
|
+
});
|
|
3241
|
+
|
|
3242
|
+
expect(response.statusCode).toEqual(400);
|
|
3243
|
+
expect(response.json).toEqual(
|
|
3244
|
+
errorResponseMatcher({
|
|
3245
|
+
error: 'Bad Request',
|
|
3246
|
+
errorCode: 'presentation_malformed',
|
|
3247
|
+
message: 'Malformed jwt_vp property: must_be_did',
|
|
3248
|
+
statusCode: 400,
|
|
3249
|
+
})
|
|
3250
|
+
);
|
|
3251
|
+
});
|
|
3252
|
+
|
|
3253
|
+
it('should return 400 when no exchange', async () => {
|
|
3254
|
+
const presentationIdDocument = await generateKYCPresentation(
|
|
3255
|
+
exchange,
|
|
3256
|
+
'idDocument'
|
|
3257
|
+
);
|
|
3258
|
+
const response = await fastify.injectJson({
|
|
3259
|
+
method: 'POST',
|
|
3260
|
+
url: idUrl(tenant),
|
|
3261
|
+
headers: {
|
|
3262
|
+
'x-vnf-protocol-version': `${VnfProtocolVersions.VNF_PROTOCOL_VERSION_2}`,
|
|
3263
|
+
},
|
|
3264
|
+
payload: {
|
|
3265
|
+
jwt_vp: await presentationIdDocument.sign(
|
|
3266
|
+
holderKid,
|
|
3267
|
+
holderKeys.privateKey,
|
|
3268
|
+
holderDid
|
|
3269
|
+
),
|
|
3270
|
+
exchange_id: 'no-exchange',
|
|
3271
|
+
},
|
|
3272
|
+
});
|
|
3273
|
+
expect(response.statusCode).toEqual(404);
|
|
3274
|
+
expect(response.json).toEqual(
|
|
3275
|
+
errorResponseMatcher({
|
|
3276
|
+
error: 'Not Found',
|
|
3277
|
+
errorCode: 'exchange_not_found',
|
|
3278
|
+
message: 'Exchange no-exchange not found',
|
|
3279
|
+
statusCode: 404,
|
|
3280
|
+
})
|
|
3281
|
+
);
|
|
3282
|
+
});
|
|
3283
|
+
|
|
3284
|
+
it('should return 401 when preauth disclosure and no vendorOriginContext', async () => {
|
|
3285
|
+
const preauthDisclosure = await persistDisclosure({
|
|
3286
|
+
tenant,
|
|
3287
|
+
description: 'Preauth identification disclosure',
|
|
3288
|
+
identificationMethods: ['preauth'],
|
|
3289
|
+
vendorEndpoint: VendorEndpoint.ISSUING_IDENTIFICATION,
|
|
3290
|
+
purpose: 'Identification',
|
|
3291
|
+
duration: '6y',
|
|
3292
|
+
});
|
|
3293
|
+
({ vendorUserId } = await newVendorUserIdMapping());
|
|
3294
|
+
const preAuthExchange = await persistOfferExchange({
|
|
3295
|
+
tenant,
|
|
3296
|
+
disclosure: preauthDisclosure,
|
|
3297
|
+
});
|
|
3298
|
+
|
|
3299
|
+
const presentationIdDocument = await generateKYCPresentation(
|
|
3300
|
+
preAuthExchange,
|
|
3301
|
+
'idDocument'
|
|
3302
|
+
); // .override({ vendorOriginContext: 'foo' });
|
|
3303
|
+
|
|
3304
|
+
const identifyWebhookNock = nock(mockVendorUrl)
|
|
3305
|
+
.post(identifyUserOnVendorEndpoint)
|
|
3306
|
+
.reply(200, {});
|
|
3307
|
+
|
|
3308
|
+
const response = await fastify.injectJson({
|
|
3309
|
+
method: 'POST',
|
|
3310
|
+
url: idUrl(tenant),
|
|
3311
|
+
headers: {
|
|
3312
|
+
'x-vnf-protocol-version': `${VnfProtocolVersions.VNF_PROTOCOL_VERSION_2}`,
|
|
3313
|
+
},
|
|
3314
|
+
payload: {
|
|
3315
|
+
jwt_vp: await presentationIdDocument.sign(
|
|
3316
|
+
holderKid,
|
|
3317
|
+
holderKeys.privateKey,
|
|
3318
|
+
holderDid
|
|
3319
|
+
),
|
|
3320
|
+
exchange_id: preAuthExchange._id,
|
|
3321
|
+
},
|
|
3322
|
+
});
|
|
3323
|
+
|
|
3324
|
+
expect(response.statusCode).toEqual(401);
|
|
3325
|
+
expect(response.json).toEqual(
|
|
3326
|
+
errorResponseMatcher({
|
|
3327
|
+
statusCode: 401,
|
|
3328
|
+
error: 'Unauthorized',
|
|
3329
|
+
errorCode: 'presentation_request_invalid',
|
|
3330
|
+
message:
|
|
3331
|
+
'Presentation for a preauth disclosure must contain a vendorOriginContext',
|
|
3332
|
+
})
|
|
3333
|
+
);
|
|
3334
|
+
|
|
3335
|
+
expect(
|
|
3336
|
+
await mongoDb()
|
|
3337
|
+
.collection('exchanges')
|
|
3338
|
+
.findOne({ _id: new ObjectId(preAuthExchange._id) })
|
|
3339
|
+
).toEqual(
|
|
3340
|
+
expectedExchange(
|
|
3341
|
+
tenant,
|
|
3342
|
+
preAuthExchange,
|
|
3343
|
+
null,
|
|
3344
|
+
[ExchangeStates.NEW, ExchangeStates.NOT_IDENTIFIED],
|
|
3345
|
+
'Presentation for a preauth disclosure must contain a vendorOriginContext'
|
|
3346
|
+
)
|
|
3347
|
+
);
|
|
3348
|
+
expect(identifyWebhookNock.isDone()).toEqual(false);
|
|
3349
|
+
});
|
|
3350
|
+
|
|
3351
|
+
it('should return 401 when vendor responds with no user', async () => {
|
|
3352
|
+
const presentationIdDocument = await generateKYCPresentation(
|
|
3353
|
+
exchange,
|
|
3354
|
+
'idDocument'
|
|
3355
|
+
);
|
|
3356
|
+
|
|
3357
|
+
nock(mockVendorUrl).post(identifyUserOnVendorEndpoint).reply(200, {});
|
|
3358
|
+
|
|
3359
|
+
const response = await fastify.injectJson({
|
|
3360
|
+
method: 'POST',
|
|
3361
|
+
url: idUrl(tenant),
|
|
3362
|
+
headers: {
|
|
3363
|
+
'x-vnf-protocol-version': `${VnfProtocolVersions.VNF_PROTOCOL_VERSION_2}`,
|
|
3364
|
+
},
|
|
3365
|
+
payload: {
|
|
3366
|
+
jwt_vp: await presentationIdDocument.sign(
|
|
3367
|
+
holderKid,
|
|
3368
|
+
holderKeys.privateKey,
|
|
3369
|
+
holderDid
|
|
3370
|
+
),
|
|
3371
|
+
exchange_id: exchange._id,
|
|
3372
|
+
},
|
|
3373
|
+
});
|
|
3374
|
+
|
|
3375
|
+
expect(response.statusCode).toEqual(401);
|
|
3376
|
+
|
|
3377
|
+
expect(
|
|
3378
|
+
await mongoDb()
|
|
3379
|
+
.collection('exchanges')
|
|
3380
|
+
.findOne({ _id: new ObjectId(exchange._id) })
|
|
3381
|
+
).toEqual(
|
|
3382
|
+
expectedExchange(
|
|
3383
|
+
tenant,
|
|
3384
|
+
exchange,
|
|
3385
|
+
presentationIdDocument,
|
|
3386
|
+
[
|
|
3387
|
+
ExchangeStates.NEW,
|
|
3388
|
+
ExchangeStates.DISCLOSURE_RECEIVED,
|
|
3389
|
+
ExchangeStates.DISCLOSURE_CHECKED,
|
|
3390
|
+
ExchangeStates.NOT_IDENTIFIED,
|
|
3391
|
+
],
|
|
3392
|
+
'user not found - vendorUserId property should be a string value'
|
|
3393
|
+
)
|
|
3394
|
+
);
|
|
3395
|
+
});
|
|
3396
|
+
|
|
3397
|
+
it('should return 401 when vendor responds with no vendorUserId', async () => {
|
|
3398
|
+
const presentationIdDocument = await generateKYCPresentation(
|
|
3399
|
+
exchange,
|
|
3400
|
+
'idDocument'
|
|
3401
|
+
);
|
|
3402
|
+
|
|
3403
|
+
nock(mockVendorUrl)
|
|
3404
|
+
.post(identifyUserOnVendorEndpoint)
|
|
3405
|
+
.reply(200, { foo: 'foo' });
|
|
3406
|
+
|
|
3407
|
+
const response = await fastify.injectJson({
|
|
3408
|
+
method: 'POST',
|
|
3409
|
+
url: idUrl(tenant),
|
|
3410
|
+
headers: {
|
|
3411
|
+
'x-vnf-protocol-version': `${VnfProtocolVersions.VNF_PROTOCOL_VERSION_2}`,
|
|
3412
|
+
},
|
|
3413
|
+
payload: {
|
|
3414
|
+
jwt_vp: await presentationIdDocument.sign(
|
|
3415
|
+
holderKid,
|
|
3416
|
+
holderKeys.privateKey,
|
|
3417
|
+
holderDid
|
|
3418
|
+
),
|
|
3419
|
+
exchange_id: exchange._id,
|
|
3420
|
+
},
|
|
3421
|
+
});
|
|
3422
|
+
|
|
3423
|
+
expect(response.statusCode).toEqual(401);
|
|
3424
|
+
expect(
|
|
3425
|
+
await mongoDb()
|
|
3426
|
+
.collection('exchanges')
|
|
3427
|
+
.findOne({ _id: new ObjectId(exchange._id) })
|
|
3428
|
+
).toEqual(
|
|
3429
|
+
expectedExchange(
|
|
3430
|
+
tenant,
|
|
3431
|
+
exchange,
|
|
3432
|
+
presentationIdDocument,
|
|
3433
|
+
[
|
|
3434
|
+
ExchangeStates.NEW,
|
|
3435
|
+
ExchangeStates.DISCLOSURE_RECEIVED,
|
|
3436
|
+
ExchangeStates.DISCLOSURE_CHECKED,
|
|
3437
|
+
ExchangeStates.NOT_IDENTIFIED,
|
|
3438
|
+
],
|
|
3439
|
+
'user not found - vendorUserId property should be a string value'
|
|
3440
|
+
)
|
|
3441
|
+
);
|
|
3442
|
+
});
|
|
3443
|
+
|
|
3444
|
+
it('should return 401 when vendor responds with non-string vendorUserId', async () => {
|
|
3445
|
+
const presentationIdDocument = await generateKYCPresentation(
|
|
3446
|
+
exchange,
|
|
3447
|
+
'idDocument'
|
|
3448
|
+
);
|
|
3449
|
+
|
|
3450
|
+
nock(mockVendorUrl)
|
|
3451
|
+
.post(identifyUserOnVendorEndpoint)
|
|
3452
|
+
.reply(200, { vendorUserId: 1 });
|
|
3453
|
+
|
|
3454
|
+
const response = await fastify.injectJson({
|
|
3455
|
+
method: 'POST',
|
|
3456
|
+
url: idUrl(tenant),
|
|
3457
|
+
headers: {
|
|
3458
|
+
'x-vnf-protocol-version': `${VnfProtocolVersions.VNF_PROTOCOL_VERSION_2}`,
|
|
3459
|
+
},
|
|
3460
|
+
payload: {
|
|
3461
|
+
jwt_vp: await presentationIdDocument.sign(
|
|
3462
|
+
holderKid,
|
|
3463
|
+
holderKeys.privateKey,
|
|
3464
|
+
holderDid
|
|
3465
|
+
),
|
|
3466
|
+
exchange_id: exchange._id,
|
|
3467
|
+
},
|
|
3468
|
+
});
|
|
3469
|
+
|
|
3470
|
+
expect(response.statusCode).toEqual(401);
|
|
3471
|
+
|
|
3472
|
+
expect(
|
|
3473
|
+
await mongoDb()
|
|
3474
|
+
.collection('exchanges')
|
|
3475
|
+
.findOne({ _id: new ObjectId(exchange._id) })
|
|
3476
|
+
).toEqual(
|
|
3477
|
+
expectedExchange(
|
|
3478
|
+
tenant,
|
|
3479
|
+
exchange,
|
|
3480
|
+
presentationIdDocument,
|
|
3481
|
+
[
|
|
3482
|
+
ExchangeStates.NEW,
|
|
3483
|
+
ExchangeStates.DISCLOSURE_RECEIVED,
|
|
3484
|
+
ExchangeStates.DISCLOSURE_CHECKED,
|
|
3485
|
+
ExchangeStates.NOT_IDENTIFIED,
|
|
3486
|
+
],
|
|
3487
|
+
'user not found - vendorUserId property should be a string value'
|
|
3488
|
+
)
|
|
3489
|
+
);
|
|
3490
|
+
});
|
|
3491
|
+
|
|
3492
|
+
it('should 401 in case user not found', async () => {
|
|
3493
|
+
const presentationIdDocument = await generateKYCPresentation(
|
|
3494
|
+
exchange,
|
|
3495
|
+
'idDocument'
|
|
3496
|
+
);
|
|
3497
|
+
|
|
3498
|
+
nock(mockVendorUrl).post(identifyUserOnVendorEndpoint).reply(404);
|
|
3499
|
+
|
|
3500
|
+
const response = await fastify.injectJson({
|
|
3501
|
+
method: 'POST',
|
|
3502
|
+
url: idUrl(tenant),
|
|
3503
|
+
headers: {
|
|
3504
|
+
'x-vnf-protocol-version': `${VnfProtocolVersions.VNF_PROTOCOL_VERSION_2}`,
|
|
3505
|
+
},
|
|
3506
|
+
payload: {
|
|
3507
|
+
jwt_vp: await presentationIdDocument.sign(
|
|
3508
|
+
holderKid,
|
|
3509
|
+
holderKeys.privateKey,
|
|
3510
|
+
holderDid
|
|
3511
|
+
),
|
|
3512
|
+
exchange_id: exchange._id,
|
|
3513
|
+
},
|
|
3514
|
+
});
|
|
3515
|
+
|
|
3516
|
+
expect(response.statusCode).toEqual(401);
|
|
3517
|
+
|
|
3518
|
+
expect(
|
|
3519
|
+
await mongoDb()
|
|
3520
|
+
.collection('exchanges')
|
|
3521
|
+
.findOne({ _id: new ObjectId(exchange._id) })
|
|
3522
|
+
).toEqual(
|
|
3523
|
+
expectedExchange(
|
|
3524
|
+
tenant,
|
|
3525
|
+
exchange,
|
|
3526
|
+
presentationIdDocument,
|
|
3527
|
+
[
|
|
3528
|
+
ExchangeStates.NEW,
|
|
3529
|
+
ExchangeStates.DISCLOSURE_RECEIVED,
|
|
3530
|
+
ExchangeStates.DISCLOSURE_CHECKED,
|
|
3531
|
+
ExchangeStates.NOT_IDENTIFIED,
|
|
3532
|
+
],
|
|
3533
|
+
'user not found'
|
|
3534
|
+
)
|
|
3535
|
+
);
|
|
3536
|
+
});
|
|
3537
|
+
|
|
3538
|
+
it('should 400 in case exchangeId not passed', async () => {
|
|
3539
|
+
const existingUser = await persistVendorUserIdMapping({ tenant });
|
|
3540
|
+
const presentationIdDocument = await generateKYCPresentation(
|
|
3541
|
+
exchange,
|
|
3542
|
+
'idDocument'
|
|
3543
|
+
);
|
|
3544
|
+
|
|
3545
|
+
nock(mockVendorUrl).post(identifyUserOnVendorEndpoint).reply(200, {
|
|
3546
|
+
vendorUserId: existingUser.vendorUserId,
|
|
3547
|
+
});
|
|
3548
|
+
|
|
3549
|
+
const response = await fastify.injectJson({
|
|
3550
|
+
method: 'POST',
|
|
3551
|
+
url: idUrl(tenant),
|
|
3552
|
+
headers: {
|
|
3553
|
+
'x-vnf-protocol-version': `${VnfProtocolVersions.VNF_PROTOCOL_VERSION_2}`,
|
|
3554
|
+
},
|
|
3555
|
+
payload: {
|
|
3556
|
+
jwt_vp: await presentationIdDocument.sign(
|
|
3557
|
+
holderKid,
|
|
3558
|
+
holderKeys.privateKey,
|
|
3559
|
+
holderDid
|
|
3560
|
+
),
|
|
3561
|
+
},
|
|
3562
|
+
});
|
|
3563
|
+
|
|
3564
|
+
expect(response.statusCode).toEqual(400);
|
|
3565
|
+
|
|
3566
|
+
expect(
|
|
3567
|
+
await mongoDb()
|
|
3568
|
+
.collection('exchanges')
|
|
3569
|
+
.findOne({ _id: new ObjectId(exchange._id) })
|
|
3570
|
+
).toEqual(
|
|
3571
|
+
expectedExchange(tenant, exchange, undefined, [ExchangeStates.NEW])
|
|
3572
|
+
);
|
|
3573
|
+
});
|
|
3574
|
+
|
|
3575
|
+
it('should 500 in other cases', async () => {
|
|
3576
|
+
const presentationIdDocument = await generateKYCPresentation(
|
|
3577
|
+
exchange,
|
|
3578
|
+
'idDocument'
|
|
3579
|
+
);
|
|
3580
|
+
|
|
3581
|
+
nock(mockVendorUrl).post(identifyUserOnVendorEndpoint).reply(500);
|
|
3582
|
+
|
|
3583
|
+
const response = await fastify.injectJson({
|
|
3584
|
+
method: 'POST',
|
|
3585
|
+
url: idUrl(tenant),
|
|
3586
|
+
headers: {
|
|
3587
|
+
'x-vnf-protocol-version': `${VnfProtocolVersions.VNF_PROTOCOL_VERSION_2}`,
|
|
3588
|
+
},
|
|
3589
|
+
payload: {
|
|
3590
|
+
jwt_vp: await presentationIdDocument.sign(
|
|
3591
|
+
holderKid,
|
|
3592
|
+
holderKeys.privateKey,
|
|
3593
|
+
holderDid
|
|
3594
|
+
),
|
|
3595
|
+
exchange_id: exchange._id,
|
|
3596
|
+
},
|
|
3597
|
+
});
|
|
3598
|
+
|
|
3599
|
+
expect(response.statusCode).toEqual(502);
|
|
3600
|
+
|
|
3601
|
+
expect(
|
|
3602
|
+
await mongoDb()
|
|
3603
|
+
.collection('exchanges')
|
|
3604
|
+
.findOne({ _id: new ObjectId(exchange._id) })
|
|
3605
|
+
).toEqual(
|
|
3606
|
+
expectedExchange(
|
|
3607
|
+
tenant,
|
|
3608
|
+
exchange,
|
|
3609
|
+
presentationIdDocument,
|
|
3610
|
+
[
|
|
3611
|
+
ExchangeStates.NEW,
|
|
3612
|
+
ExchangeStates.DISCLOSURE_RECEIVED,
|
|
3613
|
+
ExchangeStates.DISCLOSURE_CHECKED,
|
|
3614
|
+
ExchangeStates.UNEXPECTED_ERROR,
|
|
3615
|
+
],
|
|
3616
|
+
'Response code 500 (Internal Server Error)'
|
|
3617
|
+
)
|
|
3618
|
+
);
|
|
3619
|
+
});
|
|
3620
|
+
|
|
3621
|
+
it('should 409 if the exact same presentation twice', async () => {
|
|
3622
|
+
const presentationEmail = await generateKYCPresentation(
|
|
3623
|
+
exchange,
|
|
3624
|
+
'email'
|
|
3625
|
+
);
|
|
3626
|
+
|
|
3627
|
+
nock(mockVendorUrl)
|
|
3628
|
+
.post(identifyUserOnVendorEndpoint)
|
|
3629
|
+
.reply(200, () => {
|
|
3630
|
+
return {
|
|
3631
|
+
vendorUserId,
|
|
3632
|
+
};
|
|
3633
|
+
});
|
|
3634
|
+
|
|
3635
|
+
const headers = {
|
|
3636
|
+
'x-vnf-protocol-version': `${VnfProtocolVersions.VNF_PROTOCOL_VERSION_2}`,
|
|
3637
|
+
};
|
|
3638
|
+
const payload = {
|
|
3639
|
+
jwt_vp: await presentationEmail.sign(
|
|
3640
|
+
holderKid,
|
|
3641
|
+
holderKeys.privateKey,
|
|
3642
|
+
holderDid
|
|
3643
|
+
),
|
|
3644
|
+
exchange_id: exchange._id,
|
|
3645
|
+
};
|
|
3646
|
+
|
|
3647
|
+
const response = await fastify.injectJson({
|
|
3648
|
+
method: 'POST',
|
|
3649
|
+
url: idUrl(tenant),
|
|
3650
|
+
headers,
|
|
3651
|
+
payload,
|
|
3652
|
+
});
|
|
3653
|
+
|
|
3654
|
+
expect(response.statusCode).toEqual(200);
|
|
3655
|
+
expect(response.json).toEqual({
|
|
3656
|
+
token: expect.any(String),
|
|
3657
|
+
exchange: {
|
|
3658
|
+
disclosureComplete: true,
|
|
3659
|
+
exchangeComplete: false,
|
|
3660
|
+
id: exchange._id,
|
|
3661
|
+
type: ExchangeTypes.ISSUING,
|
|
3662
|
+
},
|
|
3663
|
+
});
|
|
3664
|
+
|
|
3665
|
+
const response2 = await fastify.injectJson({
|
|
3666
|
+
method: 'POST',
|
|
3667
|
+
url: idUrl(tenant),
|
|
3668
|
+
headers,
|
|
3669
|
+
payload,
|
|
3670
|
+
});
|
|
3671
|
+
|
|
3672
|
+
expect(response2.statusCode).toEqual(409);
|
|
3673
|
+
expect(response2.json).toEqual(
|
|
3674
|
+
errorResponseMatcher({
|
|
3675
|
+
error: 'Conflict',
|
|
3676
|
+
errorCode: 'presentation_duplicate',
|
|
3677
|
+
message: 'Presentation has already been submitted',
|
|
3678
|
+
statusCode: 409,
|
|
3679
|
+
})
|
|
3680
|
+
);
|
|
3681
|
+
});
|
|
3682
|
+
|
|
3683
|
+
it('should be able to identify twice on the same exchange', async () => {
|
|
3684
|
+
const presentations = await Promise.all(
|
|
3685
|
+
map(() => generateKYCPresentation(exchange, 'email'), [1, 1])
|
|
3686
|
+
);
|
|
3687
|
+
let identifyWebhookPayload;
|
|
3688
|
+
nock(mockVendorUrl)
|
|
3689
|
+
.post(identifyUserOnVendorEndpoint)
|
|
3690
|
+
.twice()
|
|
3691
|
+
.reply(200, (uri, body) => {
|
|
3692
|
+
identifyWebhookPayload = body;
|
|
3693
|
+
return {
|
|
3694
|
+
vendorUserId,
|
|
3695
|
+
};
|
|
3696
|
+
});
|
|
3697
|
+
|
|
3698
|
+
const headers = {
|
|
3699
|
+
'x-vnf-protocol-version': `${VnfProtocolVersions.VNF_PROTOCOL_VERSION_2}`,
|
|
3700
|
+
};
|
|
3701
|
+
|
|
3702
|
+
const response = await fastify.injectJson({
|
|
3703
|
+
method: 'POST',
|
|
3704
|
+
url: idUrl(tenant),
|
|
3705
|
+
headers,
|
|
3706
|
+
payload: {
|
|
3707
|
+
jwt_vp: await presentations[0].sign(
|
|
3708
|
+
holderKid,
|
|
3709
|
+
holderKeys.privateKey,
|
|
3710
|
+
holderDid
|
|
3711
|
+
),
|
|
3712
|
+
exchange_id: exchange._id,
|
|
3713
|
+
},
|
|
3714
|
+
});
|
|
3715
|
+
|
|
3716
|
+
expect(response.statusCode).toEqual(200);
|
|
3717
|
+
expect(response.json).toEqual({
|
|
3718
|
+
token: expect.any(String),
|
|
3719
|
+
exchange: {
|
|
3720
|
+
disclosureComplete: true,
|
|
3721
|
+
exchangeComplete: false,
|
|
3722
|
+
id: exchange._id,
|
|
3723
|
+
type: ExchangeTypes.ISSUING,
|
|
3724
|
+
},
|
|
3725
|
+
});
|
|
3726
|
+
|
|
3727
|
+
const response2 = await fastify.injectJson({
|
|
3728
|
+
method: 'POST',
|
|
3729
|
+
url: idUrl(tenant),
|
|
3730
|
+
headers,
|
|
3731
|
+
payload: {
|
|
3732
|
+
jwt_vp: await presentations[1].sign(
|
|
3733
|
+
holderKid,
|
|
3734
|
+
holderKeys.privateKey,
|
|
3735
|
+
holderDid
|
|
3736
|
+
),
|
|
3737
|
+
exchange_id: exchange._id,
|
|
3738
|
+
},
|
|
3739
|
+
});
|
|
3740
|
+
|
|
3741
|
+
expect(response2.statusCode).toEqual(200);
|
|
3742
|
+
expect(response2.json).toEqual({
|
|
3743
|
+
token: expect.any(String),
|
|
3744
|
+
exchange: {
|
|
3745
|
+
disclosureComplete: true,
|
|
3746
|
+
exchangeComplete: false,
|
|
3747
|
+
id: exchange._id,
|
|
3748
|
+
type: ExchangeTypes.ISSUING,
|
|
3749
|
+
},
|
|
3750
|
+
});
|
|
3751
|
+
const dbResult = await mongoDb()
|
|
3752
|
+
.collection('vendorUserIdMappings')
|
|
3753
|
+
.findOne({ vendorUserId });
|
|
3754
|
+
const { sub } = decodeJwt(response2.json.token);
|
|
3755
|
+
expect(dbResult).toEqual({
|
|
3756
|
+
_id: new ObjectId(sub),
|
|
3757
|
+
vendorUserId,
|
|
3758
|
+
tenantId: new ObjectId(tenant._id),
|
|
3759
|
+
createdAt: expect.any(Date),
|
|
3760
|
+
updatedAt: expect.any(Date),
|
|
3761
|
+
});
|
|
3762
|
+
|
|
3763
|
+
expect(
|
|
3764
|
+
await mongoDb()
|
|
3765
|
+
.collection('exchanges')
|
|
3766
|
+
.findOne({ _id: new ObjectId(exchange._id) })
|
|
3767
|
+
).toEqual(
|
|
3768
|
+
expectedExchange(tenant, exchange, presentations[1], [
|
|
3769
|
+
ExchangeStates.NEW,
|
|
3770
|
+
ExchangeStates.DISCLOSURE_RECEIVED,
|
|
3771
|
+
ExchangeStates.DISCLOSURE_CHECKED,
|
|
3772
|
+
ExchangeStates.IDENTIFIED,
|
|
3773
|
+
ExchangeStates.DISCLOSURE_RECEIVED,
|
|
3774
|
+
ExchangeStates.DISCLOSURE_CHECKED,
|
|
3775
|
+
ExchangeStates.IDENTIFIED,
|
|
3776
|
+
])
|
|
3777
|
+
);
|
|
3778
|
+
|
|
3779
|
+
expect(identifyWebhookPayload).toEqual({
|
|
3780
|
+
emailCredentials: [
|
|
3781
|
+
{
|
|
3782
|
+
id: emailPayload.vc.id,
|
|
3783
|
+
type: ['EmailV1.0', 'VerifiableCredential'],
|
|
3784
|
+
credentialSubject: emailPayload.vc.credentialSubject,
|
|
3785
|
+
credentialType: 'EmailV1.0',
|
|
3786
|
+
issuer: {
|
|
3787
|
+
id: 'did:velocity:0x0b154da48d0f213c26c4b1d040dc5ff1dbf99ffa',
|
|
3788
|
+
},
|
|
3789
|
+
issuanceDate: expect.stringMatching(ISO_DATETIME_FORMAT),
|
|
3790
|
+
credentialChecks: DEFAULT_CREDENTIAL_CHECKS,
|
|
3791
|
+
},
|
|
3792
|
+
],
|
|
3793
|
+
idDocumentCredentials: [],
|
|
3794
|
+
phoneCredentials: [],
|
|
3795
|
+
emails: [emailPayload.vc.credentialSubject.email],
|
|
3796
|
+
phones: [],
|
|
3797
|
+
tenantDID: tenant.did,
|
|
3798
|
+
tenantId: tenant._id,
|
|
3799
|
+
exchangeId: exchange._id,
|
|
3800
|
+
});
|
|
3801
|
+
});
|
|
3802
|
+
|
|
3803
|
+
it('should be able found on second attempt', async () => {
|
|
3804
|
+
const presentations = await Promise.all(
|
|
3805
|
+
map(() => generateKYCPresentation(exchange, 'email'), [1, 1])
|
|
3806
|
+
);
|
|
3807
|
+
|
|
3808
|
+
nock(mockVendorUrl).post(identifyUserOnVendorEndpoint).reply(404);
|
|
3809
|
+
const headers = {
|
|
3810
|
+
'x-vnf-protocol-version': `${VnfProtocolVersions.VNF_PROTOCOL_VERSION_2}`,
|
|
3811
|
+
};
|
|
3812
|
+
|
|
3813
|
+
const response = await fastify.injectJson({
|
|
3814
|
+
method: 'POST',
|
|
3815
|
+
url: idUrl(tenant),
|
|
3816
|
+
headers,
|
|
3817
|
+
payload: {
|
|
3818
|
+
jwt_vp: await presentations[0].sign(
|
|
3819
|
+
holderKid,
|
|
3820
|
+
holderKeys.privateKey,
|
|
3821
|
+
holderDid
|
|
3822
|
+
),
|
|
3823
|
+
exchange_id: exchange._id,
|
|
3824
|
+
},
|
|
3825
|
+
});
|
|
3826
|
+
|
|
3827
|
+
expect(response.statusCode).toEqual(401);
|
|
3828
|
+
|
|
3829
|
+
let identifyWebhookPayload;
|
|
3830
|
+
nock(mockVendorUrl)
|
|
3831
|
+
.post(identifyUserOnVendorEndpoint)
|
|
3832
|
+
.reply(200, (uri, body) => {
|
|
3833
|
+
identifyWebhookPayload = body;
|
|
3834
|
+
return {
|
|
3835
|
+
vendorUserId,
|
|
3836
|
+
};
|
|
3837
|
+
});
|
|
3838
|
+
|
|
3839
|
+
const response2 = await fastify.injectJson({
|
|
3840
|
+
method: 'POST',
|
|
3841
|
+
url: idUrl(tenant),
|
|
3842
|
+
headers,
|
|
3843
|
+
payload: {
|
|
3844
|
+
jwt_vp: await presentations[1].sign(
|
|
3845
|
+
holderKid,
|
|
3846
|
+
holderKeys.privateKey,
|
|
3847
|
+
holderDid
|
|
3848
|
+
),
|
|
3849
|
+
exchange_id: exchange._id,
|
|
3850
|
+
},
|
|
3851
|
+
});
|
|
3852
|
+
|
|
3853
|
+
expect(response2.statusCode).toEqual(200);
|
|
3854
|
+
expect(response2.json).toEqual({
|
|
3855
|
+
token: expect.any(String),
|
|
3856
|
+
exchange: {
|
|
3857
|
+
disclosureComplete: true,
|
|
3858
|
+
exchangeComplete: false,
|
|
3859
|
+
id: exchange._id,
|
|
3860
|
+
type: ExchangeTypes.ISSUING,
|
|
3861
|
+
},
|
|
3862
|
+
});
|
|
3863
|
+
const dbResult = await mongoDb()
|
|
3864
|
+
.collection('vendorUserIdMappings')
|
|
3865
|
+
.findOne({ vendorUserId });
|
|
3866
|
+
const { sub } = decodeJwt(response2.json.token);
|
|
3867
|
+
expect(dbResult).toEqual({
|
|
3868
|
+
_id: new ObjectId(sub),
|
|
3869
|
+
vendorUserId,
|
|
3870
|
+
tenantId: new ObjectId(tenant._id),
|
|
3871
|
+
createdAt: expect.any(Date),
|
|
3872
|
+
updatedAt: expect.any(Date),
|
|
3873
|
+
});
|
|
3874
|
+
expect(
|
|
3875
|
+
await mongoDb()
|
|
3876
|
+
.collection('exchanges')
|
|
3877
|
+
.findOne({ _id: new ObjectId(exchange._id) })
|
|
3878
|
+
).toEqual(
|
|
3879
|
+
expectedExchange(
|
|
3880
|
+
tenant,
|
|
3881
|
+
exchange,
|
|
3882
|
+
presentations[1],
|
|
3883
|
+
[
|
|
3884
|
+
ExchangeStates.NEW,
|
|
3885
|
+
ExchangeStates.DISCLOSURE_RECEIVED,
|
|
3886
|
+
ExchangeStates.DISCLOSURE_CHECKED,
|
|
3887
|
+
ExchangeStates.NOT_IDENTIFIED,
|
|
3888
|
+
ExchangeStates.DISCLOSURE_RECEIVED,
|
|
3889
|
+
ExchangeStates.DISCLOSURE_CHECKED,
|
|
3890
|
+
ExchangeStates.IDENTIFIED,
|
|
3891
|
+
],
|
|
3892
|
+
'user not found'
|
|
3893
|
+
)
|
|
3894
|
+
);
|
|
3895
|
+
expect(identifyWebhookPayload).toEqual({
|
|
3896
|
+
emailCredentials: [
|
|
3897
|
+
{
|
|
3898
|
+
id: emailPayload.vc.id,
|
|
3899
|
+
type: ['EmailV1.0', 'VerifiableCredential'],
|
|
3900
|
+
credentialSubject: emailPayload.vc.credentialSubject,
|
|
3901
|
+
credentialType: 'EmailV1.0',
|
|
3902
|
+
issuer: {
|
|
3903
|
+
id: 'did:velocity:0x0b154da48d0f213c26c4b1d040dc5ff1dbf99ffa',
|
|
3904
|
+
},
|
|
3905
|
+
issuanceDate: expect.stringMatching(ISO_DATETIME_FORMAT),
|
|
3906
|
+
credentialChecks: DEFAULT_CREDENTIAL_CHECKS,
|
|
3907
|
+
},
|
|
3908
|
+
],
|
|
3909
|
+
idDocumentCredentials: [],
|
|
3910
|
+
phoneCredentials: [],
|
|
3911
|
+
emails: [emailPayload.vc.credentialSubject.email],
|
|
3912
|
+
phones: [],
|
|
3913
|
+
tenantDID: tenant.did,
|
|
3914
|
+
tenantId: tenant._id,
|
|
3915
|
+
exchangeId: exchange._id,
|
|
3916
|
+
});
|
|
3917
|
+
});
|
|
3918
|
+
|
|
3919
|
+
it('should be able to recover from a 500 and identify the second time', async () => {
|
|
3920
|
+
const presentations = await Promise.all(
|
|
3921
|
+
map(() => generateKYCPresentation(exchange, 'email'), [1, 1])
|
|
3922
|
+
);
|
|
3923
|
+
|
|
3924
|
+
nock(mockVendorUrl).post(identifyUserOnVendorEndpoint).reply(500);
|
|
3925
|
+
const headers = {
|
|
3926
|
+
'x-vnf-protocol-version': `${VnfProtocolVersions.VNF_PROTOCOL_VERSION_2}`,
|
|
3927
|
+
};
|
|
3928
|
+
|
|
3929
|
+
const response = await fastify.injectJson({
|
|
3930
|
+
method: 'POST',
|
|
3931
|
+
url: idUrl(tenant),
|
|
3932
|
+
headers,
|
|
3933
|
+
payload: {
|
|
3934
|
+
jwt_vp: await presentations[0].sign(
|
|
3935
|
+
holderKid,
|
|
3936
|
+
holderKeys.privateKey,
|
|
3937
|
+
holderDid
|
|
3938
|
+
),
|
|
3939
|
+
exchange_id: exchange._id,
|
|
3940
|
+
},
|
|
3941
|
+
});
|
|
3942
|
+
|
|
3943
|
+
expect(response.statusCode).toEqual(502);
|
|
3944
|
+
|
|
3945
|
+
let identifyWebhookPayload;
|
|
3946
|
+
nock(mockVendorUrl)
|
|
3947
|
+
.post(identifyUserOnVendorEndpoint)
|
|
3948
|
+
.reply(200, (uri, body) => {
|
|
3949
|
+
identifyWebhookPayload = body;
|
|
3950
|
+
return {
|
|
3951
|
+
vendorUserId,
|
|
3952
|
+
};
|
|
3953
|
+
});
|
|
3954
|
+
|
|
3955
|
+
const response2 = await fastify.injectJson({
|
|
3956
|
+
method: 'POST',
|
|
3957
|
+
url: idUrl(tenant),
|
|
3958
|
+
headers,
|
|
3959
|
+
payload: {
|
|
3960
|
+
jwt_vp: await presentations[1].sign(
|
|
3961
|
+
holderKid,
|
|
3962
|
+
holderKeys.privateKey,
|
|
3963
|
+
holderDid
|
|
3964
|
+
),
|
|
3965
|
+
exchange_id: exchange._id,
|
|
3966
|
+
},
|
|
3967
|
+
});
|
|
3968
|
+
|
|
3969
|
+
expect(response2.statusCode).toEqual(200);
|
|
3970
|
+
expect(response2.json).toEqual({
|
|
3971
|
+
token: expect.any(String),
|
|
3972
|
+
exchange: {
|
|
3973
|
+
disclosureComplete: true,
|
|
3974
|
+
exchangeComplete: false,
|
|
3975
|
+
id: exchange._id,
|
|
3976
|
+
type: ExchangeTypes.ISSUING,
|
|
3977
|
+
},
|
|
3978
|
+
});
|
|
3979
|
+
const dbResult = await mongoDb()
|
|
3980
|
+
.collection('vendorUserIdMappings')
|
|
3981
|
+
.findOne({ vendorUserId });
|
|
3982
|
+
const { sub } = decodeJwt(response2.json.token);
|
|
3983
|
+
expect(dbResult).toEqual({
|
|
3984
|
+
_id: new ObjectId(sub),
|
|
3985
|
+
vendorUserId,
|
|
3986
|
+
tenantId: new ObjectId(tenant._id),
|
|
3987
|
+
createdAt: expect.any(Date),
|
|
3988
|
+
updatedAt: expect.any(Date),
|
|
3989
|
+
});
|
|
3990
|
+
expect(
|
|
3991
|
+
await mongoDb()
|
|
3992
|
+
.collection('exchanges')
|
|
3993
|
+
.findOne({ _id: new ObjectId(exchange._id) })
|
|
3994
|
+
).toEqual(
|
|
3995
|
+
expectedExchange(
|
|
3996
|
+
tenant,
|
|
3997
|
+
exchange,
|
|
3998
|
+
presentations[1],
|
|
3999
|
+
[
|
|
4000
|
+
ExchangeStates.NEW,
|
|
4001
|
+
ExchangeStates.DISCLOSURE_RECEIVED,
|
|
4002
|
+
ExchangeStates.DISCLOSURE_CHECKED,
|
|
4003
|
+
ExchangeStates.UNEXPECTED_ERROR,
|
|
4004
|
+
ExchangeStates.DISCLOSURE_RECEIVED,
|
|
4005
|
+
ExchangeStates.DISCLOSURE_CHECKED,
|
|
4006
|
+
ExchangeStates.IDENTIFIED,
|
|
4007
|
+
],
|
|
4008
|
+
'Response code 500 (Internal Server Error)'
|
|
4009
|
+
)
|
|
4010
|
+
);
|
|
4011
|
+
expect(identifyWebhookPayload).toEqual({
|
|
4012
|
+
emailCredentials: [
|
|
4013
|
+
{
|
|
4014
|
+
id: emailPayload.vc.id,
|
|
4015
|
+
type: ['EmailV1.0', 'VerifiableCredential'],
|
|
4016
|
+
credentialSubject: emailPayload.vc.credentialSubject,
|
|
4017
|
+
credentialType: 'EmailV1.0',
|
|
4018
|
+
issuer: {
|
|
4019
|
+
id: 'did:velocity:0x0b154da48d0f213c26c4b1d040dc5ff1dbf99ffa',
|
|
4020
|
+
},
|
|
4021
|
+
issuanceDate: expect.stringMatching(ISO_DATETIME_FORMAT),
|
|
4022
|
+
credentialChecks: DEFAULT_CREDENTIAL_CHECKS,
|
|
4023
|
+
},
|
|
4024
|
+
],
|
|
4025
|
+
idDocumentCredentials: [],
|
|
4026
|
+
phoneCredentials: [],
|
|
4027
|
+
emails: [emailPayload.vc.credentialSubject.email],
|
|
4028
|
+
phones: [],
|
|
4029
|
+
tenantDID: tenant.did,
|
|
4030
|
+
tenantId: tenant._id,
|
|
4031
|
+
exchangeId: exchange._id,
|
|
4032
|
+
});
|
|
4033
|
+
});
|
|
4034
|
+
|
|
4035
|
+
describe('version1 of the webhook. Remove by 01/12/2023', () => {
|
|
4036
|
+
beforeEach(() => {
|
|
4037
|
+
fastify.overrides.reqConfig = (config) => ({
|
|
4038
|
+
...config,
|
|
4039
|
+
identifyWebhookVersion: 1,
|
|
4040
|
+
});
|
|
4041
|
+
});
|
|
4042
|
+
afterAll(() => {
|
|
4043
|
+
fastify.overrides.reqConfig = (config) => ({
|
|
4044
|
+
...config,
|
|
4045
|
+
identifyWebhookVersion: 2,
|
|
4046
|
+
});
|
|
4047
|
+
});
|
|
4048
|
+
|
|
4049
|
+
it('should 200 when identifying phone with version 1 of the webhook', async () => {
|
|
4050
|
+
const presentationPhone = await generateKYCPresentation(
|
|
4051
|
+
exchange,
|
|
4052
|
+
'phone'
|
|
4053
|
+
);
|
|
4054
|
+
|
|
4055
|
+
let identifyWebhookPayload;
|
|
4056
|
+
nock(mockVendorUrl)
|
|
4057
|
+
.post(identifyUserOnVendorEndpoint)
|
|
4058
|
+
.reply(200, (uri, body) => {
|
|
4059
|
+
identifyWebhookPayload = body;
|
|
4060
|
+
return {
|
|
4061
|
+
vendorUserId,
|
|
4062
|
+
};
|
|
4063
|
+
});
|
|
4064
|
+
|
|
4065
|
+
const response = await fastify.injectJson({
|
|
4066
|
+
method: 'POST',
|
|
4067
|
+
url: idUrl(tenant),
|
|
4068
|
+
payload: {
|
|
4069
|
+
jwt_vp: await presentationPhone.selfSign(),
|
|
4070
|
+
exchange_id: exchange._id,
|
|
4071
|
+
},
|
|
4072
|
+
});
|
|
4073
|
+
|
|
4074
|
+
expect(response.statusCode).toEqual(200);
|
|
4075
|
+
expect(response.json).toEqual({
|
|
4076
|
+
exchange: {
|
|
4077
|
+
disclosureComplete: true,
|
|
4078
|
+
exchangeComplete: false,
|
|
4079
|
+
id: exchange._id,
|
|
4080
|
+
type: 'ISSUING',
|
|
4081
|
+
},
|
|
4082
|
+
token: expect.any(String),
|
|
4083
|
+
});
|
|
4084
|
+
const dbResult = await mongoDb()
|
|
4085
|
+
.collection('vendorUserIdMappings')
|
|
4086
|
+
.findOne({ vendorUserId });
|
|
4087
|
+
const { sub } = decodeJwt(response.json.token);
|
|
4088
|
+
expect(dbResult).toEqual({
|
|
4089
|
+
_id: new ObjectId(sub),
|
|
4090
|
+
vendorUserId,
|
|
4091
|
+
tenantId: new ObjectId(tenant._id),
|
|
4092
|
+
createdAt: expect.any(Date),
|
|
4093
|
+
updatedAt: expect.any(Date),
|
|
4094
|
+
});
|
|
4095
|
+
const exchangeDBResult = await mongoDb()
|
|
4096
|
+
.collection('exchanges')
|
|
4097
|
+
.findOne({ _id: new ObjectId(exchange._id) });
|
|
4098
|
+
expect(exchangeDBResult).toEqual(
|
|
4099
|
+
mongoify({
|
|
4100
|
+
_id: exchange._id,
|
|
4101
|
+
type: ExchangeTypes.ISSUING,
|
|
4102
|
+
tenantId: tenant._id,
|
|
4103
|
+
disclosureConsentedAt: expect.any(Date),
|
|
4104
|
+
disclosureId: disclosure._id,
|
|
4105
|
+
presentationId: presentationPhone.presentation.id,
|
|
4106
|
+
events: [
|
|
4107
|
+
{ state: ExchangeStates.NEW, timestamp: expect.any(Date) },
|
|
4108
|
+
{
|
|
4109
|
+
state: ExchangeStates.DISCLOSURE_RECEIVED,
|
|
4110
|
+
timestamp: expect.any(Date),
|
|
4111
|
+
},
|
|
4112
|
+
{
|
|
4113
|
+
state: ExchangeStates.DISCLOSURE_CHECKED,
|
|
4114
|
+
timestamp: expect.any(Date),
|
|
4115
|
+
},
|
|
4116
|
+
{ state: ExchangeStates.IDENTIFIED, timestamp: expect.any(Date) },
|
|
4117
|
+
],
|
|
4118
|
+
offerHashes: [],
|
|
4119
|
+
...credentialTypesObject,
|
|
4120
|
+
createdAt: expect.any(Date),
|
|
4121
|
+
updatedAt: expect.any(Date),
|
|
4122
|
+
})
|
|
4123
|
+
);
|
|
4124
|
+
expect(identifyWebhookPayload).toEqual({
|
|
4125
|
+
phoneCredentials: [
|
|
4126
|
+
{
|
|
4127
|
+
phone: phonePayload.vc.credentialSubject.phone,
|
|
4128
|
+
credentialType: 'PhoneV1.0',
|
|
4129
|
+
issuer: {
|
|
4130
|
+
id: 'did:velocity:0x0b154da48d0f213c26c4b1d040dc5ff1dbf99ffa',
|
|
4131
|
+
},
|
|
4132
|
+
issuanceDate: expect.stringMatching(ISO_DATETIME_FORMAT),
|
|
4133
|
+
},
|
|
4134
|
+
],
|
|
4135
|
+
idDocumentCredentials: [],
|
|
4136
|
+
emailCredentials: [],
|
|
4137
|
+
emails: [],
|
|
4138
|
+
phones: [phonePayload.vc.credentialSubject.phone],
|
|
4139
|
+
tenantDID: tenant.did,
|
|
4140
|
+
tenantId: tenant._id,
|
|
4141
|
+
exchangeId: exchange._id,
|
|
4142
|
+
});
|
|
4143
|
+
});
|
|
4144
|
+
|
|
4145
|
+
it('should 200 when identifying mixed with version 1 of the webhook', async () => {
|
|
4146
|
+
const presentationMixed = await generateKYCPresentation(exchange, [
|
|
4147
|
+
'idDocument',
|
|
4148
|
+
'phone',
|
|
4149
|
+
'email',
|
|
4150
|
+
]);
|
|
4151
|
+
|
|
4152
|
+
let identifyWebhookPayload;
|
|
4153
|
+
nock(mockVendorUrl)
|
|
4154
|
+
.post(identifyUserOnVendorEndpoint)
|
|
4155
|
+
.reply(200, (uri, body) => {
|
|
4156
|
+
identifyWebhookPayload = body;
|
|
4157
|
+
return {
|
|
4158
|
+
vendorUserId,
|
|
4159
|
+
};
|
|
4160
|
+
});
|
|
4161
|
+
|
|
4162
|
+
const response = await fastify.injectJson({
|
|
4163
|
+
method: 'POST',
|
|
4164
|
+
url: idUrl(tenant),
|
|
4165
|
+
payload: {
|
|
4166
|
+
jwt_vp: await presentationMixed.selfSign(),
|
|
4167
|
+
exchange_id: exchange._id,
|
|
4168
|
+
},
|
|
4169
|
+
});
|
|
4170
|
+
|
|
4171
|
+
expect(response.statusCode).toEqual(200);
|
|
4172
|
+
expect(response.json).toEqual({
|
|
4173
|
+
exchange: {
|
|
4174
|
+
disclosureComplete: true,
|
|
4175
|
+
exchangeComplete: false,
|
|
4176
|
+
id: exchange._id,
|
|
4177
|
+
type: 'ISSUING',
|
|
4178
|
+
},
|
|
4179
|
+
token: expect.any(String),
|
|
4180
|
+
});
|
|
4181
|
+
const dbResult = await mongoDb()
|
|
4182
|
+
.collection('vendorUserIdMappings')
|
|
4183
|
+
.findOne({ vendorUserId });
|
|
4184
|
+
const { sub } = decodeJwt(response.json.token);
|
|
4185
|
+
expect(dbResult).toEqual({
|
|
4186
|
+
_id: new ObjectId(sub),
|
|
4187
|
+
vendorUserId,
|
|
4188
|
+
tenantId: new ObjectId(tenant._id),
|
|
4189
|
+
createdAt: expect.any(Date),
|
|
4190
|
+
updatedAt: expect.any(Date),
|
|
4191
|
+
});
|
|
4192
|
+
const exchangeDBResult = await mongoDb()
|
|
4193
|
+
.collection('exchanges')
|
|
4194
|
+
.findOne({ _id: new ObjectId(exchange._id) });
|
|
4195
|
+
expect(exchangeDBResult).toEqual(
|
|
4196
|
+
mongoify({
|
|
4197
|
+
_id: exchange._id,
|
|
4198
|
+
type: ExchangeTypes.ISSUING,
|
|
4199
|
+
tenantId: tenant._id,
|
|
4200
|
+
disclosureConsentedAt: expect.any(Date),
|
|
4201
|
+
disclosureId: disclosure._id,
|
|
4202
|
+
presentationId: presentationMixed.presentation.id,
|
|
4203
|
+
events: [
|
|
4204
|
+
{ state: ExchangeStates.NEW, timestamp: expect.any(Date) },
|
|
4205
|
+
{
|
|
4206
|
+
state: ExchangeStates.DISCLOSURE_RECEIVED,
|
|
4207
|
+
timestamp: expect.any(Date),
|
|
4208
|
+
},
|
|
4209
|
+
{
|
|
4210
|
+
state: ExchangeStates.DISCLOSURE_CHECKED,
|
|
4211
|
+
timestamp: expect.any(Date),
|
|
4212
|
+
},
|
|
4213
|
+
{ state: ExchangeStates.IDENTIFIED, timestamp: expect.any(Date) },
|
|
4214
|
+
],
|
|
4215
|
+
offerHashes: [],
|
|
4216
|
+
...credentialTypesObject,
|
|
4217
|
+
createdAt: expect.any(Date),
|
|
4218
|
+
updatedAt: expect.any(Date),
|
|
4219
|
+
})
|
|
4220
|
+
);
|
|
4221
|
+
expect(identifyWebhookPayload).toEqual({
|
|
4222
|
+
phoneCredentials: [
|
|
4223
|
+
{
|
|
4224
|
+
phone: phonePayload.vc.credentialSubject.phone,
|
|
4225
|
+
credentialType: 'PhoneV1.0',
|
|
4226
|
+
issuer: {
|
|
4227
|
+
id: 'did:velocity:0x0b154da48d0f213c26c4b1d040dc5ff1dbf99ffa',
|
|
4228
|
+
},
|
|
4229
|
+
issuanceDate: expect.stringMatching(ISO_DATETIME_FORMAT),
|
|
4230
|
+
},
|
|
4231
|
+
],
|
|
4232
|
+
phones: [phonePayload.vc.credentialSubject.phone],
|
|
4233
|
+
emails: [emailPayload.vc.credentialSubject.email],
|
|
4234
|
+
idDocumentCredentials: [
|
|
4235
|
+
{
|
|
4236
|
+
...idDocPayload.vc.credentialSubject,
|
|
4237
|
+
credentialType: 'IdDocumentV1.0',
|
|
4238
|
+
issuer: {
|
|
4239
|
+
id: 'did:velocity:0x0b154da48d0f213c26c4b1d040dc5ff1dbf99ffa',
|
|
4240
|
+
},
|
|
4241
|
+
issuanceDate: expect.stringMatching(ISO_DATETIME_FORMAT),
|
|
4242
|
+
validFrom: '2017-09-01',
|
|
4243
|
+
validUntil: '2021-09-01',
|
|
4244
|
+
},
|
|
4245
|
+
],
|
|
4246
|
+
emailCredentials: [
|
|
4247
|
+
{
|
|
4248
|
+
email: emailPayload.vc.credentialSubject.email,
|
|
4249
|
+
credentialType: 'EmailV1.0',
|
|
4250
|
+
issuer: {
|
|
4251
|
+
id: 'did:velocity:0x0b154da48d0f213c26c4b1d040dc5ff1dbf99ffa',
|
|
4252
|
+
},
|
|
4253
|
+
issuanceDate: expect.stringMatching(ISO_DATETIME_FORMAT),
|
|
4254
|
+
},
|
|
4255
|
+
],
|
|
4256
|
+
tenantDID: tenant.did,
|
|
4257
|
+
tenantId: tenant._id,
|
|
4258
|
+
exchangeId: exchange._id,
|
|
4259
|
+
});
|
|
4260
|
+
});
|
|
4261
|
+
});
|
|
4262
|
+
|
|
4263
|
+
describe('autoIdentityCheck Test Suite', () => {
|
|
4264
|
+
it('should 401 when autoIdentityCheck is on and credential has invalid check result', async () => {
|
|
4265
|
+
setMockVerifyCredentials({
|
|
4266
|
+
...DEFAULT_CREDENTIAL_CHECKS,
|
|
4267
|
+
TRUSTED_ISSUER: CredentialCheckResultValue.FAIL,
|
|
4268
|
+
});
|
|
4269
|
+
const presentationEmail = await generateKYCPresentation(
|
|
4270
|
+
exchange,
|
|
4271
|
+
'email'
|
|
4272
|
+
);
|
|
4273
|
+
|
|
4274
|
+
const response = await fastify.injectJson({
|
|
4275
|
+
method: 'POST',
|
|
4276
|
+
url: idUrl(tenant),
|
|
4277
|
+
headers: {
|
|
4278
|
+
'x-vnf-protocol-version': `${VnfProtocolVersions.VNF_PROTOCOL_VERSION_2}`,
|
|
4279
|
+
},
|
|
4280
|
+
payload: {
|
|
4281
|
+
jwt_vp: await presentationEmail.sign(
|
|
4282
|
+
holderKid,
|
|
4283
|
+
holderKeys.privateKey,
|
|
4284
|
+
holderDid
|
|
4285
|
+
),
|
|
4286
|
+
exchange_id: exchange._id,
|
|
4287
|
+
},
|
|
4288
|
+
});
|
|
4289
|
+
|
|
4290
|
+
expect(response.statusCode).toEqual(401);
|
|
4291
|
+
expect(response.json).toEqual(
|
|
4292
|
+
errorResponseMatcher({
|
|
4293
|
+
error: 'Unauthorized',
|
|
4294
|
+
message: 'presentation_credential_bad_issuer',
|
|
4295
|
+
statusCode: 401,
|
|
4296
|
+
errorCode: 'presentation_credential_bad_issuer',
|
|
4297
|
+
})
|
|
4298
|
+
);
|
|
4299
|
+
expect(
|
|
4300
|
+
await mongoDb()
|
|
4301
|
+
.collection('exchanges')
|
|
4302
|
+
.findOne({ _id: new ObjectId(exchange._id) })
|
|
4303
|
+
).toEqual(
|
|
4304
|
+
expectedExchange(
|
|
4305
|
+
tenant,
|
|
4306
|
+
exchange,
|
|
4307
|
+
presentationEmail,
|
|
4308
|
+
[
|
|
4309
|
+
ExchangeStates.NEW,
|
|
4310
|
+
ExchangeStates.DISCLOSURE_RECEIVED,
|
|
4311
|
+
ExchangeStates.DISCLOSURE_CHECKED,
|
|
4312
|
+
ExchangeStates.NOT_IDENTIFIED,
|
|
4313
|
+
],
|
|
4314
|
+
'presentation_credential_bad_issuer'
|
|
4315
|
+
)
|
|
4316
|
+
);
|
|
4317
|
+
});
|
|
4318
|
+
it('should 200 when autoIdentityCheck is off, vendorEndpoint is issuing-identification and credential has invalid check result', async () => {
|
|
4319
|
+
setMockVerifyCredentials({
|
|
4320
|
+
...DEFAULT_CREDENTIAL_CHECKS,
|
|
4321
|
+
TRUSTED_ISSUER: CredentialCheckResultValue.FAIL,
|
|
4322
|
+
});
|
|
4323
|
+
fastify.overrides.reqConfig = (config) => ({
|
|
4324
|
+
...config,
|
|
4325
|
+
autoIdentityCheck: false,
|
|
4326
|
+
});
|
|
4327
|
+
const presentationEmail = await generateKYCPresentation(
|
|
4328
|
+
exchange,
|
|
4329
|
+
'email'
|
|
4330
|
+
);
|
|
4331
|
+
|
|
4332
|
+
let identifyWebhookPayload;
|
|
4333
|
+
nock(mockVendorUrl)
|
|
4334
|
+
.post(identifyUserOnVendorEndpoint)
|
|
4335
|
+
.reply(200, (uri, body) => {
|
|
4336
|
+
identifyWebhookPayload = body;
|
|
4337
|
+
return {
|
|
4338
|
+
vendorUserId,
|
|
4339
|
+
};
|
|
4340
|
+
});
|
|
4341
|
+
|
|
4342
|
+
const response = await fastify.injectJson({
|
|
4343
|
+
method: 'POST',
|
|
4344
|
+
url: idUrl(tenant),
|
|
4345
|
+
headers: {
|
|
4346
|
+
'x-vnf-protocol-version': `${VnfProtocolVersions.VNF_PROTOCOL_VERSION_2}`,
|
|
4347
|
+
},
|
|
4348
|
+
payload: {
|
|
4349
|
+
jwt_vp: await presentationEmail.sign(
|
|
4350
|
+
holderKid,
|
|
4351
|
+
holderKeys.privateKey,
|
|
4352
|
+
holderDid
|
|
4353
|
+
),
|
|
4354
|
+
exchange_id: exchange._id,
|
|
4355
|
+
},
|
|
4356
|
+
});
|
|
4357
|
+
|
|
4358
|
+
expect(response.statusCode).toEqual(200);
|
|
4359
|
+
expect(response.json).toEqual({
|
|
4360
|
+
token: expect.any(String),
|
|
4361
|
+
exchange: {
|
|
4362
|
+
disclosureComplete: true,
|
|
4363
|
+
exchangeComplete: false,
|
|
4364
|
+
id: exchange._id,
|
|
4365
|
+
type: ExchangeTypes.ISSUING,
|
|
4366
|
+
},
|
|
4367
|
+
});
|
|
4368
|
+
const dbResult = await mongoDb()
|
|
4369
|
+
.collection('vendorUserIdMappings')
|
|
4370
|
+
.findOne({ vendorUserId });
|
|
4371
|
+
const { sub } = decodeJwt(response.json.token);
|
|
4372
|
+
expect(dbResult).toEqual({
|
|
4373
|
+
_id: new ObjectId(sub),
|
|
4374
|
+
vendorUserId,
|
|
4375
|
+
tenantId: new ObjectId(tenant._id),
|
|
4376
|
+
createdAt: expect.any(Date),
|
|
4377
|
+
updatedAt: expect.any(Date),
|
|
4378
|
+
});
|
|
4379
|
+
expect(
|
|
4380
|
+
await mongoDb()
|
|
4381
|
+
.collection('exchanges')
|
|
4382
|
+
.findOne({ _id: new ObjectId(exchange._id) })
|
|
4383
|
+
).toEqual(
|
|
4384
|
+
expectedExchange(tenant, exchange, presentationEmail, [
|
|
4385
|
+
ExchangeStates.NEW,
|
|
4386
|
+
ExchangeStates.DISCLOSURE_RECEIVED,
|
|
4387
|
+
ExchangeStates.DISCLOSURE_CHECKED,
|
|
4388
|
+
ExchangeStates.IDENTIFIED,
|
|
4389
|
+
])
|
|
4390
|
+
);
|
|
4391
|
+
expect(identifyWebhookPayload).toEqual({
|
|
4392
|
+
emailCredentials: [
|
|
4393
|
+
{
|
|
4394
|
+
id: emailPayload.vc.id,
|
|
4395
|
+
type: ['EmailV1.0', 'VerifiableCredential'],
|
|
4396
|
+
credentialSubject: emailPayload.vc.credentialSubject,
|
|
4397
|
+
credentialType: 'EmailV1.0',
|
|
4398
|
+
issuer: {
|
|
4399
|
+
id: 'did:velocity:0x0b154da48d0f213c26c4b1d040dc5ff1dbf99ffa',
|
|
4400
|
+
},
|
|
4401
|
+
issuanceDate: expect.stringMatching(ISO_DATETIME_FORMAT),
|
|
4402
|
+
credentialChecks: {
|
|
4403
|
+
...DEFAULT_CREDENTIAL_CHECKS,
|
|
4404
|
+
TRUSTED_ISSUER: CredentialCheckResultValue.FAIL,
|
|
4405
|
+
},
|
|
4406
|
+
},
|
|
4407
|
+
],
|
|
4408
|
+
idDocumentCredentials: [],
|
|
4409
|
+
phoneCredentials: [],
|
|
4410
|
+
emails: [emailPayload.vc.credentialSubject.email],
|
|
4411
|
+
phones: [],
|
|
4412
|
+
tenantDID: tenant.did,
|
|
4413
|
+
tenantId: tenant._id,
|
|
4414
|
+
exchangeId: exchange._id,
|
|
4415
|
+
});
|
|
4416
|
+
});
|
|
4417
|
+
// eslint-disable-next-line max-len
|
|
4418
|
+
it('should 200 when autoIdentityCheck is off, vendorEndpoint is integrated-issuing-identification and credential has invalid check result', async () => {
|
|
4419
|
+
await mongoDb().collection('vendorUserIdMappings').deleteMany({});
|
|
4420
|
+
await mongoDb().collection('exchanges').deleteMany({});
|
|
4421
|
+
await mongoDb().collection('disclosures').deleteMany({});
|
|
4422
|
+
disclosure = await persistDisclosure({
|
|
4423
|
+
tenant,
|
|
4424
|
+
description: 'Credential Issuance disclosure',
|
|
4425
|
+
types: [{ type: 'EmailV1.0' }],
|
|
4426
|
+
vendorEndpoint: VendorEndpoint.INTEGRATED_ISSUING_IDENTIFICATION,
|
|
4427
|
+
purpose: 'Identification',
|
|
4428
|
+
duration: '6y',
|
|
4429
|
+
identityMatchers: {
|
|
4430
|
+
rules: [
|
|
4431
|
+
{
|
|
4432
|
+
valueIndex: 0, // used for identifying the value
|
|
4433
|
+
path: ['$.emails'], // jsonPath within the credential
|
|
4434
|
+
rule: 'pick', // Rule to execute can be pick, all (if the target is an array), equal (if the target is a singleValue)
|
|
4435
|
+
},
|
|
4436
|
+
],
|
|
4437
|
+
vendorUserIdIndex: 0,
|
|
4438
|
+
},
|
|
4439
|
+
});
|
|
4440
|
+
const exchangeWithoutIdentityMatcherValues = await persistOfferExchange(
|
|
4441
|
+
{
|
|
4442
|
+
tenant,
|
|
4443
|
+
disclosure,
|
|
4444
|
+
}
|
|
4445
|
+
);
|
|
4446
|
+
|
|
4447
|
+
await persistOfferExchange({
|
|
4448
|
+
tenant,
|
|
4449
|
+
disclosure,
|
|
4450
|
+
identityMatcherValues: ['not-match.olivia@example.com'],
|
|
4451
|
+
});
|
|
4452
|
+
await persistOfferExchange({
|
|
4453
|
+
tenant,
|
|
4454
|
+
disclosure,
|
|
4455
|
+
identityMatcherValues: ['Adam.smith@example.com'],
|
|
4456
|
+
});
|
|
4457
|
+
vendorUserId = 'Adam.smith@example.com';
|
|
4458
|
+
|
|
4459
|
+
const presentationEmail = await generateKYCPresentation(
|
|
4460
|
+
exchangeWithoutIdentityMatcherValues,
|
|
4461
|
+
'email'
|
|
4462
|
+
);
|
|
4463
|
+
setMockVerifyCredentials({
|
|
4464
|
+
...DEFAULT_CREDENTIAL_CHECKS,
|
|
4465
|
+
TRUSTED_HOLDER: CredentialCheckResultValue.FAIL,
|
|
4466
|
+
});
|
|
4467
|
+
fastify.overrides.reqConfig = (config) => ({
|
|
4468
|
+
...config,
|
|
4469
|
+
autoIdentityCheck: false,
|
|
4470
|
+
});
|
|
4471
|
+
|
|
4472
|
+
const response = await fastify.injectJson({
|
|
4473
|
+
method: 'POST',
|
|
4474
|
+
url: idUrl(tenant),
|
|
4475
|
+
headers: {
|
|
4476
|
+
'x-vnf-protocol-version': `${VnfProtocolVersions.VNF_PROTOCOL_VERSION_2}`,
|
|
4477
|
+
},
|
|
4478
|
+
payload: {
|
|
4479
|
+
jwt_vp: await presentationEmail.sign(
|
|
4480
|
+
holderKid,
|
|
4481
|
+
holderKeys.privateKey,
|
|
4482
|
+
holderDid
|
|
4483
|
+
),
|
|
4484
|
+
exchange_id: exchangeWithoutIdentityMatcherValues._id,
|
|
4485
|
+
},
|
|
4486
|
+
});
|
|
4487
|
+
|
|
4488
|
+
expect(response.statusCode).toEqual(200);
|
|
4489
|
+
expect(response.json).toEqual({
|
|
4490
|
+
token: expect.any(String),
|
|
4491
|
+
exchange: {
|
|
4492
|
+
disclosureComplete: true,
|
|
4493
|
+
exchangeComplete: false,
|
|
4494
|
+
id: exchangeWithoutIdentityMatcherValues._id,
|
|
4495
|
+
type: ExchangeTypes.ISSUING,
|
|
4496
|
+
},
|
|
4497
|
+
});
|
|
4498
|
+
const dbResult = await mongoDb()
|
|
4499
|
+
.collection('vendorUserIdMappings')
|
|
4500
|
+
.findOne({ vendorUserId });
|
|
4501
|
+
const { sub } = decodeJwt(response.json.token);
|
|
4502
|
+
expect(dbResult).toEqual({
|
|
4503
|
+
_id: new ObjectId(sub),
|
|
4504
|
+
vendorUserId,
|
|
4505
|
+
tenantId: new ObjectId(tenant._id),
|
|
4506
|
+
createdAt: expect.any(Date),
|
|
4507
|
+
updatedAt: expect.any(Date),
|
|
4508
|
+
});
|
|
4509
|
+
expect(
|
|
4510
|
+
await mongoDb()
|
|
4511
|
+
.collection('exchanges')
|
|
4512
|
+
.findOne({
|
|
4513
|
+
_id: new ObjectId(exchangeWithoutIdentityMatcherValues._id),
|
|
4514
|
+
})
|
|
4515
|
+
).toEqual(
|
|
4516
|
+
expectedExchange(
|
|
4517
|
+
tenant,
|
|
4518
|
+
exchangeWithoutIdentityMatcherValues,
|
|
4519
|
+
presentationEmail,
|
|
4520
|
+
[
|
|
4521
|
+
ExchangeStates.NEW,
|
|
4522
|
+
ExchangeStates.DISCLOSURE_RECEIVED,
|
|
4523
|
+
ExchangeStates.DISCLOSURE_CHECKED,
|
|
4524
|
+
ExchangeStates.IDENTIFIED,
|
|
4525
|
+
]
|
|
4526
|
+
)
|
|
4527
|
+
);
|
|
4528
|
+
});
|
|
4529
|
+
it('should 200 when autoIdentityCheck is on and credential has N/A expiration', async () => {
|
|
4530
|
+
setMockVerifyCredentials({
|
|
4531
|
+
...DEFAULT_CREDENTIAL_CHECKS,
|
|
4532
|
+
UNEXPIRED: CredentialCheckResultValue.NOT_APPLICABLE,
|
|
4533
|
+
});
|
|
4534
|
+
const presentationEmail = await generateKYCPresentation(
|
|
4535
|
+
exchange,
|
|
4536
|
+
'email'
|
|
4537
|
+
);
|
|
4538
|
+
|
|
4539
|
+
let identifyWebhookPayload;
|
|
4540
|
+
nock(mockVendorUrl)
|
|
4541
|
+
.post(identifyUserOnVendorEndpoint)
|
|
4542
|
+
.reply(200, (uri, body) => {
|
|
4543
|
+
identifyWebhookPayload = body;
|
|
4544
|
+
return {
|
|
4545
|
+
vendorUserId,
|
|
4546
|
+
};
|
|
4547
|
+
});
|
|
4548
|
+
|
|
4549
|
+
const response = await fastify.injectJson({
|
|
4550
|
+
method: 'POST',
|
|
4551
|
+
url: idUrl(tenant),
|
|
4552
|
+
headers: {
|
|
4553
|
+
'x-vnf-protocol-version': `${VnfProtocolVersions.VNF_PROTOCOL_VERSION_2}`,
|
|
4554
|
+
},
|
|
4555
|
+
payload: {
|
|
4556
|
+
jwt_vp: await presentationEmail.sign(
|
|
4557
|
+
holderKid,
|
|
4558
|
+
holderKeys.privateKey,
|
|
4559
|
+
holderDid
|
|
4560
|
+
),
|
|
4561
|
+
exchange_id: exchange._id,
|
|
4562
|
+
},
|
|
4563
|
+
});
|
|
4564
|
+
|
|
4565
|
+
expect(response.statusCode).toEqual(200);
|
|
4566
|
+
expect(response.json).toEqual({
|
|
4567
|
+
token: expect.any(String),
|
|
4568
|
+
exchange: {
|
|
4569
|
+
disclosureComplete: true,
|
|
4570
|
+
exchangeComplete: false,
|
|
4571
|
+
id: exchange._id,
|
|
4572
|
+
type: ExchangeTypes.ISSUING,
|
|
4573
|
+
},
|
|
4574
|
+
});
|
|
4575
|
+
const dbResult = await mongoDb()
|
|
4576
|
+
.collection('vendorUserIdMappings')
|
|
4577
|
+
.findOne({ vendorUserId });
|
|
4578
|
+
const { sub } = decodeJwt(response.json.token);
|
|
4579
|
+
expect(dbResult).toEqual({
|
|
4580
|
+
_id: new ObjectId(sub),
|
|
4581
|
+
vendorUserId,
|
|
4582
|
+
tenantId: new ObjectId(tenant._id),
|
|
4583
|
+
createdAt: expect.any(Date),
|
|
4584
|
+
updatedAt: expect.any(Date),
|
|
4585
|
+
});
|
|
4586
|
+
expect(
|
|
4587
|
+
await mongoDb()
|
|
4588
|
+
.collection('exchanges')
|
|
4589
|
+
.findOne({ _id: new ObjectId(exchange._id) })
|
|
4590
|
+
).toEqual(
|
|
4591
|
+
expectedExchange(tenant, exchange, presentationEmail, [
|
|
4592
|
+
ExchangeStates.NEW,
|
|
4593
|
+
ExchangeStates.DISCLOSURE_RECEIVED,
|
|
4594
|
+
ExchangeStates.DISCLOSURE_CHECKED,
|
|
4595
|
+
ExchangeStates.IDENTIFIED,
|
|
4596
|
+
])
|
|
4597
|
+
);
|
|
4598
|
+
expect(identifyWebhookPayload).toEqual({
|
|
4599
|
+
emailCredentials: [
|
|
4600
|
+
{
|
|
4601
|
+
id: emailPayload.vc.id,
|
|
4602
|
+
type: ['EmailV1.0', 'VerifiableCredential'],
|
|
4603
|
+
credentialSubject: emailPayload.vc.credentialSubject,
|
|
4604
|
+
credentialType: 'EmailV1.0',
|
|
4605
|
+
issuer: {
|
|
4606
|
+
id: 'did:velocity:0x0b154da48d0f213c26c4b1d040dc5ff1dbf99ffa',
|
|
4607
|
+
},
|
|
4608
|
+
issuanceDate: expect.stringMatching(ISO_DATETIME_FORMAT),
|
|
4609
|
+
credentialChecks: {
|
|
4610
|
+
...DEFAULT_CREDENTIAL_CHECKS,
|
|
4611
|
+
UNEXPIRED: CredentialCheckResultValue.NOT_APPLICABLE,
|
|
4612
|
+
},
|
|
4613
|
+
},
|
|
4614
|
+
],
|
|
4615
|
+
idDocumentCredentials: [],
|
|
4616
|
+
phoneCredentials: [],
|
|
4617
|
+
emails: [emailPayload.vc.credentialSubject.email],
|
|
4618
|
+
phones: [],
|
|
4619
|
+
tenantDID: tenant.did,
|
|
4620
|
+
tenantId: tenant._id,
|
|
4621
|
+
exchangeId: exchange._id,
|
|
4622
|
+
});
|
|
4623
|
+
});
|
|
4624
|
+
});
|
|
4625
|
+
});
|
|
4626
|
+
|
|
4627
|
+
describe('identity presentation @context validation enabled test suite', () => {
|
|
4628
|
+
let exchange;
|
|
4629
|
+
let disclosure;
|
|
4630
|
+
let vendorUserId;
|
|
4631
|
+
|
|
4632
|
+
beforeEach(async () => {
|
|
4633
|
+
fastify.overrides.reqConfig = (config) => ({
|
|
4634
|
+
...config,
|
|
4635
|
+
enablePresentationContextValidation: true,
|
|
4636
|
+
});
|
|
4637
|
+
disclosure = await persistDisclosure({
|
|
4638
|
+
tenant,
|
|
4639
|
+
description: 'Credential Issuance disclosure',
|
|
4640
|
+
types: [{ type: 'EmailV1.0' }],
|
|
4641
|
+
vendorEndpoint: VendorEndpoint.ISSUING_IDENTIFICATION,
|
|
4642
|
+
purpose: 'Identification',
|
|
4643
|
+
duration: '6y',
|
|
4644
|
+
});
|
|
4645
|
+
({ vendorUserId } = await newVendorUserIdMapping());
|
|
4646
|
+
exchange = await persistOfferExchange({ tenant, disclosure });
|
|
4647
|
+
});
|
|
4648
|
+
|
|
4649
|
+
it('should 400 when @context is a string and is incorrect value', async () => {
|
|
4650
|
+
const presentationIdDocument = (
|
|
4651
|
+
await generateKYCPresentation(exchange, 'idDocument')
|
|
4652
|
+
).override({ '@context': 'foo' });
|
|
4653
|
+
|
|
4654
|
+
const response = await fastify.injectJson({
|
|
4655
|
+
method: 'POST',
|
|
4656
|
+
url: idUrl(tenant),
|
|
4657
|
+
headers: {
|
|
4658
|
+
'x-vnf-protocol-version': `${VnfProtocolVersions.VNF_PROTOCOL_VERSION_2}`,
|
|
4659
|
+
},
|
|
4660
|
+
payload: {
|
|
4661
|
+
exchange_id: exchange._id,
|
|
4662
|
+
jwt_vp: await presentationIdDocument.sign(
|
|
4663
|
+
holderKid,
|
|
4664
|
+
holderKeys.privateKey,
|
|
4665
|
+
holderDid
|
|
4666
|
+
),
|
|
4667
|
+
},
|
|
4668
|
+
});
|
|
4669
|
+
|
|
4670
|
+
expect(response.statusCode).toEqual(400);
|
|
4671
|
+
expect(response.json).toEqual(
|
|
4672
|
+
errorResponseMatcher({
|
|
4673
|
+
error: 'Bad Request',
|
|
4674
|
+
message: 'presentation @context is not set correctly',
|
|
4675
|
+
statusCode: 400,
|
|
4676
|
+
errorCode: 'presentation_invalid',
|
|
4677
|
+
})
|
|
4678
|
+
);
|
|
4679
|
+
});
|
|
4680
|
+
it('should 400 when @context is an array and is incorrect value', async () => {
|
|
4681
|
+
const presentationIdDocument = (
|
|
4682
|
+
await generateKYCPresentation(exchange, 'idDocument')
|
|
4683
|
+
).override({ '@context': ['foo'] });
|
|
4684
|
+
|
|
4685
|
+
const response = await fastify.injectJson({
|
|
4686
|
+
method: 'POST',
|
|
4687
|
+
url: idUrl(tenant),
|
|
4688
|
+
headers: {
|
|
4689
|
+
'x-vnf-protocol-version': `${VnfProtocolVersions.VNF_PROTOCOL_VERSION_2}`,
|
|
4690
|
+
},
|
|
4691
|
+
payload: {
|
|
4692
|
+
exchange_id: exchange._id,
|
|
4693
|
+
jwt_vp: await presentationIdDocument.sign(
|
|
4694
|
+
holderKid,
|
|
4695
|
+
holderKeys.privateKey,
|
|
4696
|
+
holderDid
|
|
4697
|
+
),
|
|
4698
|
+
},
|
|
4699
|
+
});
|
|
4700
|
+
|
|
4701
|
+
expect(response.statusCode).toEqual(400);
|
|
4702
|
+
expect(response.json).toEqual(
|
|
4703
|
+
errorResponseMatcher({
|
|
4704
|
+
error: 'Bad Request',
|
|
4705
|
+
message: 'presentation @context is not set correctly',
|
|
4706
|
+
statusCode: 400,
|
|
4707
|
+
errorCode: 'presentation_invalid',
|
|
4708
|
+
})
|
|
4709
|
+
);
|
|
4710
|
+
});
|
|
4711
|
+
|
|
4712
|
+
it('should 400 when @context is an empty array', async () => {
|
|
4713
|
+
const presentationIdDocument = (
|
|
4714
|
+
await generateKYCPresentation(exchange, 'idDocument')
|
|
4715
|
+
).override({ '@context': [] });
|
|
4716
|
+
|
|
4717
|
+
const response = await fastify.injectJson({
|
|
4718
|
+
method: 'POST',
|
|
4719
|
+
url: idUrl(tenant),
|
|
4720
|
+
headers: {
|
|
4721
|
+
'x-vnf-protocol-version': `${VnfProtocolVersions.VNF_PROTOCOL_VERSION_2}`,
|
|
4722
|
+
},
|
|
4723
|
+
payload: {
|
|
4724
|
+
exchange_id: exchange._id,
|
|
4725
|
+
jwt_vp: await presentationIdDocument.sign(
|
|
4726
|
+
holderKid,
|
|
4727
|
+
holderKeys.privateKey,
|
|
4728
|
+
holderDid
|
|
4729
|
+
),
|
|
4730
|
+
},
|
|
4731
|
+
});
|
|
4732
|
+
|
|
4733
|
+
expect(response.statusCode).toEqual(400);
|
|
4734
|
+
expect(response.json).toEqual(
|
|
4735
|
+
errorResponseMatcher({
|
|
4736
|
+
error: 'Bad Request',
|
|
4737
|
+
message: 'presentation @context is not set correctly',
|
|
4738
|
+
statusCode: 400,
|
|
4739
|
+
errorCode: 'presentation_invalid',
|
|
4740
|
+
})
|
|
4741
|
+
);
|
|
4742
|
+
});
|
|
4743
|
+
|
|
4744
|
+
it('should 200 when @context is a string and is correct value', async () => {
|
|
4745
|
+
const presentationIdDocument = (
|
|
4746
|
+
await generateKYCPresentation(exchange, 'idDocument')
|
|
4747
|
+
).override({ '@context': 'https://www.w3.org/2018/credentials/v1' });
|
|
4748
|
+
|
|
4749
|
+
nock(mockVendorUrl).post(identifyUserOnVendorEndpoint).reply(200, {
|
|
4750
|
+
vendorUserId,
|
|
4751
|
+
});
|
|
4752
|
+
|
|
4753
|
+
const response = await fastify.injectJson({
|
|
4754
|
+
method: 'POST',
|
|
4755
|
+
url: idUrl(tenant),
|
|
4756
|
+
headers: {
|
|
4757
|
+
'x-vnf-protocol-version': `${VnfProtocolVersions.VNF_PROTOCOL_VERSION_2}`,
|
|
4758
|
+
},
|
|
4759
|
+
payload: {
|
|
4760
|
+
exchange_id: exchange._id,
|
|
4761
|
+
jwt_vp: await presentationIdDocument.sign(
|
|
4762
|
+
holderKid,
|
|
4763
|
+
holderKeys.privateKey,
|
|
4764
|
+
holderDid
|
|
4765
|
+
),
|
|
4766
|
+
},
|
|
4767
|
+
});
|
|
4768
|
+
|
|
4769
|
+
expect(response.statusCode).toEqual(200);
|
|
4770
|
+
});
|
|
4771
|
+
it('should 200 when @context is an array and is correct value', async () => {
|
|
4772
|
+
const presentationIdDocument = (
|
|
4773
|
+
await generateKYCPresentation(exchange, 'idDocument')
|
|
4774
|
+
).override({ '@context': ['https://www.w3.org/2018/credentials/v1'] });
|
|
4775
|
+
|
|
4776
|
+
nock(mockVendorUrl).post(identifyUserOnVendorEndpoint).reply(200, {
|
|
4777
|
+
vendorUserId,
|
|
4778
|
+
});
|
|
4779
|
+
|
|
4780
|
+
const response = await fastify.injectJson({
|
|
4781
|
+
method: 'POST',
|
|
4782
|
+
url: idUrl(tenant),
|
|
4783
|
+
headers: {
|
|
4784
|
+
'x-vnf-protocol-version': `${VnfProtocolVersions.VNF_PROTOCOL_VERSION_2}`,
|
|
4785
|
+
},
|
|
4786
|
+
payload: {
|
|
4787
|
+
exchange_id: exchange._id,
|
|
4788
|
+
jwt_vp: await presentationIdDocument.sign(
|
|
4789
|
+
holderKid,
|
|
4790
|
+
holderKeys.privateKey,
|
|
4791
|
+
holderDid
|
|
4792
|
+
),
|
|
4793
|
+
},
|
|
4794
|
+
});
|
|
4795
|
+
|
|
4796
|
+
expect(response.statusCode).toEqual(200);
|
|
4797
|
+
});
|
|
4798
|
+
});
|
|
4799
|
+
});
|
|
4800
|
+
|
|
4801
|
+
const expectedExchange = (tenant, exchange, presentationWrapper, states, err) =>
|
|
4802
|
+
mongoify(
|
|
4803
|
+
omitBy((v) => v == null, {
|
|
4804
|
+
...exchange,
|
|
4805
|
+
tenantId: tenant._id,
|
|
4806
|
+
type: ExchangeTypes.ISSUING,
|
|
4807
|
+
presentationId: presentationWrapper?.presentation?.id,
|
|
4808
|
+
disclosureConsentedAt:
|
|
4809
|
+
presentationWrapper == null ? undefined : expect.any(Date),
|
|
4810
|
+
events: map((state) => ({ state, timestamp: expect.any(Date) }), states),
|
|
4811
|
+
...credentialTypesObject,
|
|
4812
|
+
err,
|
|
4813
|
+
updatedAt: expect.any(Date),
|
|
4814
|
+
})
|
|
4815
|
+
);
|