@verii/server-credentialing-hub 2.0.0

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.
Files changed (237) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +13 -0
  3. package/package.json +87 -0
  4. package/src/assets/templates/app-redirect.hbs +22 -0
  5. package/src/config/config.js +118 -0
  6. package/src/config/index.js +19 -0
  7. package/src/controllers/app-redirect/controller.js +85 -0
  8. package/src/controllers/openid4vc/autohooks.js +22 -0
  9. package/src/controllers/openid4vc/oauth-controller.js +96 -0
  10. package/src/controllers/openid4vc/openid4vc-metadata-controller.js +66 -0
  11. package/src/controllers/openid4vc/openid4vci-controller.js +125 -0
  12. package/src/controllers/openid4vp/autohooks.js +22 -0
  13. package/src/controllers/openid4vp/openid4vp-controller.js +177 -0
  14. package/src/controllers/operator/autohooks.js +31 -0
  15. package/src/controllers/operator/credentials/credentials-controller.js +212 -0
  16. package/src/controllers/operator/credentials/schemas/credential.schema.js +46 -0
  17. package/src/controllers/operator/credentials/schemas/index.js +7 -0
  18. package/src/controllers/operator/credentials/schemas/new-credential.schema.json +23 -0
  19. package/src/controllers/operator/depots/depots-controller.js +154 -0
  20. package/src/controllers/operator/depots/schemas/depot.schema.js +34 -0
  21. package/src/controllers/operator/depots/schemas/index.js +24 -0
  22. package/src/controllers/operator/depots/schemas/new-depot.schema.json +30 -0
  23. package/src/controllers/operator/issue-links/issue-links-controller.js +74 -0
  24. package/src/controllers/operator/issuer-services/issuer-services-controller.js +200 -0
  25. package/src/controllers/operator/issuer-services/schemas/index.js +7 -0
  26. package/src/controllers/operator/issuer-services/schemas/issuer-service.schema.js +26 -0
  27. package/src/controllers/operator/issuer-services/schemas/new-issuer-service.schema.js +137 -0
  28. package/src/controllers/operator/presentation-links/presentation-links-controller.js +76 -0
  29. package/src/controllers/operator/presentations/presentations-controller.js +174 -0
  30. package/src/controllers/operator/presentations/schemas/base-presentation.schema.js +39 -0
  31. package/src/controllers/operator/presentations/schemas/credential-verification.schema.js +83 -0
  32. package/src/controllers/operator/presentations/schemas/index.js +23 -0
  33. package/src/controllers/operator/presentations/schemas/presentation-verification.schema.js +42 -0
  34. package/src/controllers/operator/presentations/schemas/presentation.schema.js +53 -0
  35. package/src/controllers/operator/presentations/schemas/w3c-presentation.js +52 -0
  36. package/src/controllers/operator/relying-party-services/relying-party-services-controller.js +202 -0
  37. package/src/controllers/operator/relying-party-services/schemas/index.js +7 -0
  38. package/src/controllers/operator/relying-party-services/schemas/new-relying-party-service.schema.js +91 -0
  39. package/src/controllers/operator/relying-party-services/schemas/relying-party-service.schema.js +26 -0
  40. package/src/controllers/operator/tenants/schemas/index.js +33 -0
  41. package/src/controllers/operator/tenants/schemas/jwk.schema.json +56 -0
  42. package/src/controllers/operator/tenants/schemas/key-metadata.schema.json +12 -0
  43. package/src/controllers/operator/tenants/schemas/new-key-metadata.schema.json +42 -0
  44. package/src/controllers/operator/tenants/schemas/new-key.schema.json +12 -0
  45. package/src/controllers/operator/tenants/schemas/new-tenant.schema.json +33 -0
  46. package/src/controllers/operator/tenants/schemas/secret-key.schema.json +33 -0
  47. package/src/controllers/operator/tenants/schemas/tenant.schema.json +44 -0
  48. package/src/controllers/operator/tenants/tenants-controller.js +165 -0
  49. package/src/controllers/root-controller.js +43 -0
  50. package/src/controllers/vn-api/schemas/index.js +25 -0
  51. package/src/controllers/vn-api/schemas/presentation-submission.schema.json +41 -0
  52. package/src/controllers/vn-api/schemas/velocity-presentation-submission.response.200.schema.json +36 -0
  53. package/src/controllers/vn-api/schemas/velocity-presentation-submission.schema.json +31 -0
  54. package/src/controllers/vn-api/vn-api-presentation-controller.js +158 -0
  55. package/src/controllers/vn-api/vn-issuing-controller.js +295 -0
  56. package/src/entities/credential-types/fetchers/fetch-credential-type-descriptors.js +36 -0
  57. package/src/entities/credential-types/fetchers/index.js +19 -0
  58. package/src/entities/credential-types/index.js +19 -0
  59. package/src/entities/credentials/domain/canonicalize-credentials.js +70 -0
  60. package/src/entities/credentials/domain/credential-errors.js +25 -0
  61. package/src/entities/credentials/domain/credential-format.js +21 -0
  62. package/src/entities/credentials/domain/index.js +22 -0
  63. package/src/entities/credentials/domain/validate-credential.js +74 -0
  64. package/src/entities/credentials/facades/anchor-verii-credentials-facade.js +45 -0
  65. package/src/entities/credentials/facades/build-verii-issuer.js +44 -0
  66. package/src/entities/credentials/facades/index.js +22 -0
  67. package/src/entities/credentials/facades/issue-verii-credentials-facade.js +62 -0
  68. package/src/entities/credentials/facades/sign-verii-credentials-facade.js +56 -0
  69. package/src/entities/credentials/factories/credential-factory.js +53 -0
  70. package/src/entities/credentials/factories/index.js +19 -0
  71. package/src/entities/credentials/index.js +23 -0
  72. package/src/entities/credentials/orchestrators/create-credentials.js +118 -0
  73. package/src/entities/credentials/orchestrators/index.js +19 -0
  74. package/src/entities/credentials/repo/finders-extension.js +94 -0
  75. package/src/entities/credentials/repo/index.js +19 -0
  76. package/src/entities/credentials/repo/repo.js +62 -0
  77. package/src/entities/credentials/repo/update-issued-credential.js +60 -0
  78. package/src/entities/depots/domain/depot-errors.js +23 -0
  79. package/src/entities/depots/domain/index.js +20 -0
  80. package/src/entities/depots/domain/validate-depot.js +38 -0
  81. package/src/entities/depots/factories/depot-factory.js +50 -0
  82. package/src/entities/depots/factories/index.js +19 -0
  83. package/src/entities/depots/index.js +22 -0
  84. package/src/entities/depots/orchestrators/create-depot.js +31 -0
  85. package/src/entities/depots/orchestrators/delete-depot.js +43 -0
  86. package/src/entities/depots/orchestrators/index.js +20 -0
  87. package/src/entities/depots/repo/find-depot-by-preauth-code.js +30 -0
  88. package/src/entities/depots/repo/find-depots-extension.js +34 -0
  89. package/src/entities/depots/repo/index.js +19 -0
  90. package/src/entities/depots/repo/refresh-preauth-code.js +30 -0
  91. package/src/entities/depots/repo/repo.js +61 -0
  92. package/src/entities/exchanges/adapters/index.js +17 -0
  93. package/src/entities/exchanges/adapters/sign-exchange-response.js +37 -0
  94. package/src/entities/exchanges/domain/authorize-exchange.js +29 -0
  95. package/src/entities/exchanges/domain/build-exchange-event.js +19 -0
  96. package/src/entities/exchanges/domain/build-presentation-request.js +174 -0
  97. package/src/entities/exchanges/domain/exchange-errors.js +28 -0
  98. package/src/entities/exchanges/domain/exchange-protocols.js +29 -0
  99. package/src/entities/exchanges/domain/exchange-states.js +53 -0
  100. package/src/entities/exchanges/domain/exchange-types.js +22 -0
  101. package/src/entities/exchanges/domain/generate-issuing-challenge.js +48 -0
  102. package/src/entities/exchanges/domain/index.js +32 -0
  103. package/src/entities/exchanges/domain/validate-message-settings.js +33 -0
  104. package/src/entities/exchanges/domain/validate-new-exchange.js +25 -0
  105. package/src/entities/exchanges/domain/validate-preauth.js +45 -0
  106. package/src/entities/exchanges/domain/validate-referenced-service.js +33 -0
  107. package/src/entities/exchanges/domain/verify-issuing-challenge.js +42 -0
  108. package/src/entities/exchanges/domain/verify-proof-of-key-possession.js +128 -0
  109. package/src/entities/exchanges/factories/index.js +19 -0
  110. package/src/entities/exchanges/factories/init-exchange-factory.js +58 -0
  111. package/src/entities/exchanges/hooks/exchange-error-hook.js +30 -0
  112. package/src/entities/exchanges/hooks/index.js +17 -0
  113. package/src/entities/exchanges/index.js +24 -0
  114. package/src/entities/exchanges/orchestrators/authenticate.js +67 -0
  115. package/src/entities/exchanges/orchestrators/credential-offer-request.js +70 -0
  116. package/src/entities/exchanges/orchestrators/get-credential-manifest.js +84 -0
  117. package/src/entities/exchanges/orchestrators/get-presentation-request.js +114 -0
  118. package/src/entities/exchanges/orchestrators/index.js +24 -0
  119. package/src/entities/exchanges/orchestrators/issue-credentials.js +132 -0
  120. package/src/entities/exchanges/orchestrators/post-presentation.js +58 -0
  121. package/src/entities/exchanges/repo/exchange-state-repo-extension.js +78 -0
  122. package/src/entities/exchanges/repo/index.js +19 -0
  123. package/src/entities/exchanges/repo/repo.js +52 -0
  124. package/src/entities/issuer-services/domain/index.js +20 -0
  125. package/src/entities/issuer-services/domain/issuer-services-errors.js +23 -0
  126. package/src/entities/issuer-services/domain/validate-issuer-service.js +23 -0
  127. package/src/entities/issuer-services/factories/index.js +19 -0
  128. package/src/entities/issuer-services/factories/issuer-service-factory.js +44 -0
  129. package/src/entities/issuer-services/index.js +21 -0
  130. package/src/entities/issuer-services/repo/find-issuer-services-extension.js +30 -0
  131. package/src/entities/issuer-services/repo/index.js +19 -0
  132. package/src/entities/issuer-services/repo/repo.js +59 -0
  133. package/src/entities/keys/adapters/index.js +19 -0
  134. package/src/entities/keys/adapters/load-primary-account.js +39 -0
  135. package/src/entities/keys/domain/cih-key-purposes.js +23 -0
  136. package/src/entities/keys/domain/get-key-with-purpose.js +22 -0
  137. package/src/entities/keys/domain/index.js +22 -0
  138. package/src/entities/keys/domain/key-errors.js +25 -0
  139. package/src/entities/keys/domain/normalize-tenant-key.js +33 -0
  140. package/src/entities/keys/domain/validate-tenant-keys.js +78 -0
  141. package/src/entities/keys/factories/index.js +19 -0
  142. package/src/entities/keys/factories/key-factory.js +61 -0
  143. package/src/entities/keys/index.js +22 -0
  144. package/src/entities/keys/plugins/index.js +17 -0
  145. package/src/entities/keys/plugins/kms-plugin.js +61 -0
  146. package/src/entities/links/domain/build-deep-link-url.js +58 -0
  147. package/src/entities/links/domain/build-openid4vci-credential-offer-link.js +31 -0
  148. package/src/entities/links/domain/build-openid4vp-authorization-request-link.js +36 -0
  149. package/src/entities/links/domain/build-redirect-url.js +29 -0
  150. package/src/entities/links/domain/build-vn-api-issuing-link.js +54 -0
  151. package/src/entities/links/domain/build-vn-api-presentation-link.js +49 -0
  152. package/src/entities/links/domain/index.js +28 -0
  153. package/src/entities/links/domain/is-preauth-code-auth.js +19 -0
  154. package/src/entities/links/domain/link-errors.js +23 -0
  155. package/src/entities/links/domain/validate-issue-link.js +39 -0
  156. package/src/entities/links/domain/validate-presentation-link.js +39 -0
  157. package/src/entities/links/index.js +20 -0
  158. package/src/entities/links/orchestrators/index.js +17 -0
  159. package/src/entities/links/orchestrators/refresh-links.js +139 -0
  160. package/src/entities/openid4vci/domain/index.js +20 -0
  161. package/src/entities/openid4vci/domain/openid4vci-errors.js +24 -0
  162. package/src/entities/openid4vci/domain/to-credential-configuration-id.js +21 -0
  163. package/src/entities/openid4vci/index.js +21 -0
  164. package/src/entities/openid4vci/orchestrators/create-access-token.js +85 -0
  165. package/src/entities/openid4vci/orchestrators/create-credential.js +108 -0
  166. package/src/entities/openid4vci/orchestrators/get-authorization-server-metadata.js +22 -0
  167. package/src/entities/openid4vci/orchestrators/get-credential-issuer-metadata.js +22 -0
  168. package/src/entities/openid4vci/orchestrators/handle-notification.js +94 -0
  169. package/src/entities/openid4vci/orchestrators/index.js +24 -0
  170. package/src/entities/openid4vci/plugins/index.js +19 -0
  171. package/src/entities/openid4vci/plugins/openid4vci-plugin.js +427 -0
  172. package/src/entities/openid4vp/domain/index.js +21 -0
  173. package/src/entities/openid4vp/domain/openid4vp-request-id.js +47 -0
  174. package/src/entities/openid4vp/domain/presentation-request-expiry.js +37 -0
  175. package/src/entities/openid4vp/domain/wallet-metadata.js +30 -0
  176. package/src/entities/openid4vp/index.js +21 -0
  177. package/src/entities/openid4vp/orchestrators/create-authorization-request.js +183 -0
  178. package/src/entities/openid4vp/orchestrators/index.js +20 -0
  179. package/src/entities/openid4vp/orchestrators/post-authorization-response.js +353 -0
  180. package/src/entities/openid4vp/plugins/index.js +19 -0
  181. package/src/entities/openid4vp/plugins/openid4vp-plugin.js +174 -0
  182. package/src/entities/presentations/domain/index.js +20 -0
  183. package/src/entities/presentations/domain/presentation-format.js +20 -0
  184. package/src/entities/presentations/domain/validate-presentation.js +178 -0
  185. package/src/entities/presentations/factories/index.js +19 -0
  186. package/src/entities/presentations/factories/presentation-factory.js +96 -0
  187. package/src/entities/presentations/index.js +21 -0
  188. package/src/entities/presentations/orchestrators/index.js +19 -0
  189. package/src/entities/presentations/orchestrators/verify-presentation.js +131 -0
  190. package/src/entities/presentations/repo/add-verification.js +31 -0
  191. package/src/entities/presentations/repo/finders-extension.js +50 -0
  192. package/src/entities/presentations/repo/index.js +19 -0
  193. package/src/entities/presentations/repo/repo.js +58 -0
  194. package/src/entities/relying-party-services/factories/index.js +19 -0
  195. package/src/entities/relying-party-services/factories/relying-party-service-factory.js +43 -0
  196. package/src/entities/relying-party-services/index.js +20 -0
  197. package/src/entities/relying-party-services/repo/find-relying-party-services-extension.js +30 -0
  198. package/src/entities/relying-party-services/repo/index.js +19 -0
  199. package/src/entities/relying-party-services/repo/repo.js +55 -0
  200. package/src/entities/service-configurations/domain/canonicalize-service-configuration.js +12 -0
  201. package/src/entities/service-configurations/domain/index.js +22 -0
  202. package/src/entities/service-configurations/domain/service-configuration-errors.js +24 -0
  203. package/src/entities/service-configurations/domain/validate-service-configuration.js +31 -0
  204. package/src/entities/service-configurations/domain/validate-update-service-configuration.js +19 -0
  205. package/src/entities/service-configurations/index.js +20 -0
  206. package/src/entities/service-configurations/orchestrators/create-service-configuration.js +34 -0
  207. package/src/entities/service-configurations/orchestrators/delete-service-configuration.js +41 -0
  208. package/src/entities/service-configurations/orchestrators/index.js +21 -0
  209. package/src/entities/service-configurations/orchestrators/update-service-configuration.js +40 -0
  210. package/src/entities/tenants/adapters/index.js +20 -0
  211. package/src/entities/tenants/adapters/load-tenant-did-doc.js +47 -0
  212. package/src/entities/tenants/adapters/load-tenant-profile.js +40 -0
  213. package/src/entities/tenants/domain/index.js +20 -0
  214. package/src/entities/tenants/domain/tenant-errors.js +29 -0
  215. package/src/entities/tenants/domain/validate-new-tenant.js +49 -0
  216. package/src/entities/tenants/factories/index.js +19 -0
  217. package/src/entities/tenants/factories/tenant-factory.js +41 -0
  218. package/src/entities/tenants/index.js +24 -0
  219. package/src/entities/tenants/orchestrators/create-tenant.js +116 -0
  220. package/src/entities/tenants/orchestrators/delete-tenant.js +45 -0
  221. package/src/entities/tenants/orchestrators/find-tenants.js +22 -0
  222. package/src/entities/tenants/orchestrators/index.js +21 -0
  223. package/src/entities/tenants/plugins/index.js +3 -0
  224. package/src/entities/tenants/plugins/tenant-loader-plugin.js +64 -0
  225. package/src/entities/tenants/repo/index.js +19 -0
  226. package/src/entities/tenants/repo/insert-tenant-extension.js +36 -0
  227. package/src/entities/tenants/repo/repo.js +51 -0
  228. package/src/entities/tokens/domains/access-token.js +74 -0
  229. package/src/entities/tokens/domains/index.js +19 -0
  230. package/src/entities/tokens/index.js +19 -0
  231. package/src/index.js +3 -0
  232. package/src/init-server.js +88 -0
  233. package/src/main.js +25 -0
  234. package/src/schemas/index.js +20 -0
  235. package/src/schemas/oauth-error-response.schema.js +27 -0
  236. package/src/standalone.js +8 -0
  237. package/src/start-app-server.js +27 -0
@@ -0,0 +1,183 @@
1
+ /*
2
+ * Copyright 2026 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
+
18
+ const crypto = require('crypto');
19
+ const { ObjectId } = require('mongodb');
20
+ const { map } = require('lodash/fp');
21
+ const newError = require('http-errors');
22
+ const {
23
+ ExchangeProtocols,
24
+ ExchangeStates,
25
+ ExchangeTypes,
26
+ buildPresentationRequest,
27
+ } = require('../../exchanges');
28
+ const {
29
+ validateReferencedService,
30
+ } = require('../../exchanges/domain/validate-referenced-service');
31
+ const { fetchCredentialTypeDescriptors } = require('../../credential-types');
32
+ const {
33
+ buildPresentationRequestExpiresAt,
34
+ getPresentationRequestsExpireIn,
35
+ parseOpenid4vpRequestId,
36
+ parseWalletMetadata,
37
+ } = require('../domain');
38
+
39
+ const createAuthorizationRequest = async (
40
+ requestId,
41
+ { walletMetadata, walletNonce },
42
+ context,
43
+ ) => {
44
+ const { tenant, repos } = context;
45
+ parseWalletMetadata(walletMetadata);
46
+
47
+ const { relyingPartyService, depot: existingDepot } =
48
+ await loadRequestReference(requestId, context);
49
+
50
+ validateReferencedService(relyingPartyService);
51
+ const presentationRequestsExpireIn =
52
+ getPresentationRequestsExpireIn(relyingPartyService);
53
+
54
+ const depot =
55
+ existingDepot ??
56
+ (await repos.depots.insert({
57
+ serviceId: new ObjectId(relyingPartyService._id),
58
+ }));
59
+ const exchangeId = new ObjectId();
60
+ const protocolMetadata = {
61
+ protocol: ExchangeProtocols.OPENID4VP,
62
+ nonce: generateNonce(),
63
+ walletNonce,
64
+ };
65
+ const presentationRequest = await buildVelocityPresentationRequest(
66
+ tenant,
67
+ relyingPartyService,
68
+ {
69
+ _id: exchangeId,
70
+ serviceId: new ObjectId(relyingPartyService._id),
71
+ protocolMetadata,
72
+ depotId: new ObjectId(depot._id),
73
+ },
74
+ context,
75
+ );
76
+
77
+ const exchange = await repos.exchanges.insertWithInitialState(
78
+ {
79
+ _id: exchangeId,
80
+ type: ExchangeTypes.RELYING_PARTY,
81
+ serviceId: new ObjectId(relyingPartyService._id),
82
+ protocolMetadata: {
83
+ ...protocolMetadata,
84
+ presentationDefinition: presentationRequest.presentation_definition,
85
+ presentationRequestExpiresAt:
86
+ buildPresentationRequestExpiresAt(relyingPartyService),
87
+ },
88
+ depotId: new ObjectId(depot._id),
89
+ },
90
+ [ExchangeStates.PRESENTATION_REQUEST_REQUESTED],
91
+ );
92
+ // eslint-disable-next-line better-mutation/no-mutation
93
+ context.exchange = exchange;
94
+
95
+ const openid4vpVerifier = await context.getOpenId4VpVerifier();
96
+
97
+ return openid4vpVerifier.createAuthorizationRequestJwt({
98
+ requestId,
99
+ authorizationRequestPayload: {
100
+ response_type: 'vp_token',
101
+ response_mode: 'direct_post',
102
+ client_id: `decentralized_identifier:${tenant.did}`,
103
+ response_uri: `${tenant.hostUrl}/r/${tenant._id}/openid4vp/direct-post`,
104
+ nonce: exchange.protocolMetadata.nonce,
105
+ wallet_nonce: walletNonce,
106
+ presentation_definition: presentationRequest.presentation_definition,
107
+ client_metadata: {
108
+ client_name: tenant.name,
109
+ logo_uri: tenant.logo,
110
+ vp_formats_supported: {
111
+ jwt_vc_json: { alg_values: ['ES256', 'ES256K'] },
112
+ },
113
+ },
114
+ state: exchange._id.toString(),
115
+ },
116
+ requestUri: `${tenant.hostUrl}/r/${tenant._id}/openid4vp/authorization-request/${requestId}`,
117
+ expiresInSeconds: presentationRequestsExpireIn,
118
+ walletNonce,
119
+ });
120
+ };
121
+
122
+ const loadRequestReference = async (requestId, context) => {
123
+ const parsedRequestId = parseOpenid4vpRequestId(requestId);
124
+ if (parsedRequestId.type === 'service') {
125
+ return loadServiceRequestReference(parsedRequestId.id, context);
126
+ }
127
+ return loadDepotRequestReference(parsedRequestId.id, context);
128
+ };
129
+
130
+ const loadServiceRequestReference = async (serviceId, { repos }) => {
131
+ const relyingPartyService = await repos.relyingPartyServices.findOne({
132
+ filter: { _id: serviceId },
133
+ });
134
+
135
+ return { relyingPartyService };
136
+ };
137
+
138
+ const loadDepotRequestReference = async (depotId, { repos }) => {
139
+ const depot = await repos.depots.findOne({
140
+ filter: { _id: depotId },
141
+ });
142
+ if (!depot) {
143
+ throw newError(400, 'referenced_depot_not_found', {
144
+ errorCode: 'referenced_depot_not_found',
145
+ });
146
+ }
147
+
148
+ const relyingPartyService = await repos.relyingPartyServices.findOne({
149
+ filter: { _id: depot.serviceId },
150
+ });
151
+
152
+ return { relyingPartyService, depot };
153
+ };
154
+
155
+ const buildVelocityPresentationRequest = async (
156
+ tenant,
157
+ relyingPartyService,
158
+ exchange,
159
+ context,
160
+ ) => {
161
+ const inputDescriptors = relyingPartyService.presentationDefinition
162
+ ? []
163
+ : await fetchCredentialTypeDescriptors(
164
+ map('type', relyingPartyService?.disclosureRequest?.types),
165
+ false,
166
+ undefined,
167
+ context,
168
+ );
169
+
170
+ return buildPresentationRequest(
171
+ tenant,
172
+ relyingPartyService,
173
+ exchange,
174
+ inputDescriptors,
175
+ );
176
+ };
177
+
178
+ const generateNonce = () => crypto.randomBytes(16).toString('base64url');
179
+
180
+ module.exports = {
181
+ createAuthorizationRequest,
182
+ buildVelocityPresentationRequest,
183
+ };
@@ -0,0 +1,20 @@
1
+ /*
2
+ * Copyright 2026 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
+ module.exports = {
18
+ ...require('./create-authorization-request'),
19
+ ...require('./post-authorization-response'),
20
+ };
@@ -0,0 +1,353 @@
1
+ /*
2
+ * Copyright 2026 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
+ const newError = require('http-errors');
18
+ const { isNil, omitBy } = require('lodash/fp');
19
+ const {
20
+ verifyVerifiablePresentationJwt,
21
+ } = require('@verii/verii-verification');
22
+ const {
23
+ ExchangeProtocols,
24
+ ExchangeStates,
25
+ validateReferencedService,
26
+ } = require('../../exchanges');
27
+ const {
28
+ PresentationFormat,
29
+ validatePresentation,
30
+ } = require('../../presentations');
31
+
32
+ const postAuthorizationResponse = async (authorizationResponse, context) => {
33
+ const { repos, tenant } = context;
34
+ const exchange = await repos.exchanges.findOne({
35
+ filter: { _id: authorizationResponse.state },
36
+ });
37
+ validateOpenid4vpExchangeExists(exchange);
38
+
39
+ try {
40
+ validateOpenid4vpExchange(exchange);
41
+
42
+ if (authorizationResponse.error) {
43
+ return handleErrorResponse(authorizationResponse, exchange, context);
44
+ }
45
+
46
+ const [depot, relyingPartyService] = await Promise.all([
47
+ repos.depots.findOne({
48
+ filter: { _id: exchange.depotId },
49
+ }),
50
+ repos.relyingPartyServices.findOne({
51
+ filter: { _id: exchange.serviceId },
52
+ }),
53
+ ]);
54
+ validateOpenid4vpDepot(depot, exchange);
55
+ validateReferencedService(relyingPartyService);
56
+
57
+ const openid4vpVerifier = await context.getOpenId4VpVerifier();
58
+ const parsedAuthorizationResponse =
59
+ await openid4vpVerifier.parseAuthorizationResponse({
60
+ authorizationResponse,
61
+ authorizationRequestPayload: {
62
+ response_type: 'vp_token',
63
+ response_mode: 'direct_post',
64
+ client_id: `decentralized_identifier:${tenant.did}`,
65
+ response_uri: `${tenant.hostUrl}/r/${tenant._id}/openid4vp/direct-post`,
66
+ nonce: exchange.protocolMetadata.nonce,
67
+ presentation_definition:
68
+ exchange.protocolMetadata.presentationDefinition,
69
+ state: exchange._id.toString(),
70
+ },
71
+ });
72
+ validateOpenid4vpVpTokenCount(
73
+ parsedAuthorizationResponse.pex.presentations,
74
+ exchange.protocolMetadata.presentationDefinition,
75
+ );
76
+
77
+ const validatedPresentations = await Promise.all(
78
+ parsedAuthorizationResponse.pex.presentations.map((jwtPresentation) =>
79
+ validateOpenid4vpPresentation(
80
+ jwtPresentation,
81
+ parsedAuthorizationResponse.pex.presentationSubmission,
82
+ exchange,
83
+ context,
84
+ ),
85
+ ),
86
+ );
87
+
88
+ const presentations = await Promise.all(
89
+ validatedPresentations.map((validatedPresentation) =>
90
+ storePresentation(validatedPresentation, exchange, context),
91
+ ),
92
+ );
93
+
94
+ const updatedExchange = await repos.exchanges.addState(
95
+ exchange._id,
96
+ ExchangeStates.PRESENTATION_SUBMISSION_RECEIVED,
97
+ {
98
+ disclosureConsentedAt: new Date(),
99
+ },
100
+ );
101
+
102
+ return { relyingPartyService, exchange: updatedExchange, presentations };
103
+ } catch (error) {
104
+ await recordOpenid4vpFailure(exchange, error, context);
105
+ throw error;
106
+ }
107
+ };
108
+
109
+ const handleErrorResponse = async (
110
+ authorizationResponse,
111
+ exchange,
112
+ { repos },
113
+ ) => {
114
+ const updatedExchange = await repos.exchanges.addState(
115
+ exchange._id,
116
+ ExchangeStates.CLIENT_ERROR,
117
+ omitBy(isNil, {
118
+ err: authorizationResponse.error,
119
+ errorDescription: authorizationResponse.error_description,
120
+ }),
121
+ );
122
+
123
+ return { exchange: updatedExchange, presentations: [] };
124
+ };
125
+
126
+ const validateOpenid4vpExchangeExists = (exchange) => {
127
+ if (!exchange) {
128
+ throw newError(400, 'Referenced exchange not found', {
129
+ errorCode: 'referenced_exchange_not_found',
130
+ });
131
+ }
132
+ };
133
+
134
+ const validateOpenid4vpExchange = (exchange) => {
135
+ validateOpenid4vpProtocol(exchange.protocolMetadata);
136
+ validateOpenid4vpExchangeNotProcessed(exchange);
137
+ validateOpenid4vpRequestMetadata(exchange.protocolMetadata);
138
+ validateOpenid4vpExchangeReferences(exchange);
139
+ validateOpenid4vpPresentationRequestNotExpired(exchange.protocolMetadata);
140
+ };
141
+
142
+ const validateOpenid4vpProtocol = ({ protocol } = {}) => {
143
+ if (protocol !== ExchangeProtocols.OPENID4VP) {
144
+ throw newError(400, 'Referenced exchange is not an OpenID4VP exchange', {
145
+ errorCode: 'openid4vp_exchange_invalid',
146
+ });
147
+ }
148
+ };
149
+
150
+ const validateOpenid4vpExchangeNotProcessed = ({ events = [] }) => {
151
+ if (!hasTerminalOpenid4vpEvent(events)) {
152
+ return;
153
+ }
154
+
155
+ throw newError(400, 'OpenID4VP authorization response already processed', {
156
+ errorCode: 'openid4vp_authorization_response_already_processed',
157
+ });
158
+ };
159
+
160
+ const hasTerminalOpenid4vpEvent = (events = []) =>
161
+ events.some(({ state }) =>
162
+ [
163
+ ExchangeStates.PRESENTATION_SUBMISSION_RECEIVED,
164
+ ExchangeStates.CLIENT_ERROR,
165
+ ExchangeStates.UNEXPECTED_ERROR,
166
+ ].includes(state),
167
+ );
168
+
169
+ const validateOpenid4vpRequestMetadata = ({
170
+ nonce,
171
+ presentationDefinition,
172
+ presentationRequestExpiresAt,
173
+ } = {}) => {
174
+ if (!nonce) {
175
+ throw newError(400, 'OpenID4VP exchange nonce is missing', {
176
+ errorCode: 'openid4vp_exchange_nonce_missing',
177
+ });
178
+ }
179
+ if (!presentationDefinition) {
180
+ throw newError(400, 'OpenID4VP presentation definition is missing', {
181
+ errorCode: 'openid4vp_presentation_definition_missing',
182
+ });
183
+ }
184
+ if (!presentationRequestExpiresAt) {
185
+ throw newError(400, 'OpenID4VP presentation request expiry is missing', {
186
+ errorCode: 'openid4vp_presentation_request_expiry_missing',
187
+ });
188
+ }
189
+ };
190
+
191
+ const validateOpenid4vpExchangeReferences = ({ depotId }) => {
192
+ if (!depotId) {
193
+ throw newError(400, 'OpenID4VP exchange depot is missing', {
194
+ errorCode: 'openid4vp_exchange_depot_missing',
195
+ });
196
+ }
197
+ };
198
+
199
+ const validateOpenid4vpPresentationRequestNotExpired = ({
200
+ presentationRequestExpiresAt,
201
+ }) => {
202
+ if (presentationRequestExpiresAt > new Date()) {
203
+ return;
204
+ }
205
+
206
+ throw newError(400, 'OpenID4VP presentation request has expired', {
207
+ errorCode: 'openid4vp_presentation_request_expired',
208
+ });
209
+ };
210
+
211
+ const recordOpenid4vpFailure = async (exchange, error, { log, repos }) => {
212
+ if (
213
+ exchange.protocolMetadata?.protocol !== ExchangeProtocols.OPENID4VP ||
214
+ hasTerminalOpenid4vpEvent(exchange.events)
215
+ ) {
216
+ return;
217
+ }
218
+
219
+ const failureMetadata = getOpenid4vpFailureMetadata(error);
220
+ await repos.exchanges
221
+ .addState(exchange._id, getOpenid4vpFailureState(error), failureMetadata)
222
+ .catch((recordingError) => {
223
+ log.error({
224
+ err: recordingError,
225
+ exchangeId: exchange._id,
226
+ openid4vpErrorCode: failureMetadata.err,
227
+ });
228
+ });
229
+ };
230
+
231
+ const getOpenid4vpFailureState = (error) =>
232
+ hasExplicitErrorCode(error)
233
+ ? ExchangeStates.CLIENT_ERROR
234
+ : ExchangeStates.UNEXPECTED_ERROR;
235
+
236
+ const getOpenid4vpFailureMetadata = (error) =>
237
+ hasExplicitErrorCode(error)
238
+ ? {
239
+ err: error.errorCode,
240
+ errorDescription: error.message,
241
+ }
242
+ : {
243
+ err: 'openid4vp_internal_error',
244
+ errorDescription: 'Unexpected OpenID4VP processing error',
245
+ };
246
+
247
+ const hasExplicitErrorCode = (error) => error.errorCode != null;
248
+
249
+ const validateOpenid4vpDepot = (depot, { serviceId }) => {
250
+ if (depot?.serviceId?.toString() === serviceId.toString()) {
251
+ return;
252
+ }
253
+
254
+ throw newError(400, 'referenced_depot_not_found', {
255
+ errorCode: 'referenced_depot_not_found',
256
+ });
257
+ };
258
+
259
+ const validateOpenid4vpVpTokenCount = (
260
+ presentations,
261
+ presentationDefinition,
262
+ ) => {
263
+ const inputDescriptorCount =
264
+ presentationDefinition.input_descriptors?.length ?? 0;
265
+ if (presentations.length < 1) {
266
+ throwOpenid4vpVpTokenMissing();
267
+ }
268
+ if (presentations.length <= inputDescriptorCount) {
269
+ return;
270
+ }
271
+
272
+ throw newError(
273
+ 400,
274
+ 'OpenID4VP VP token count exceeds presentation definition input descriptor count',
275
+ {
276
+ errorCode: 'openid4vp_vp_token_count_exceeds_input_descriptors',
277
+ },
278
+ );
279
+ };
280
+
281
+ const throwOpenid4vpVpTokenMissing = () => {
282
+ throw newError(400, 'OpenID4VP VP token is missing', {
283
+ errorCode: 'openid4vp_vp_token_missing',
284
+ });
285
+ };
286
+
287
+ const validateOpenid4vpPresentation = async (
288
+ jwtPresentation,
289
+ presentationSubmission,
290
+ exchange,
291
+ context,
292
+ ) => {
293
+ const verifiablePresentation =
294
+ await verifyOpenid4vpPresentationJwt(jwtPresentation);
295
+ validateOpenid4vpPresentationClaims(
296
+ verifiablePresentation,
297
+ exchange,
298
+ context,
299
+ );
300
+ // Current wallets use the same PEX v1 descriptor-map shape as VN API, with
301
+ // paths rooted at the decoded VP. OpenID4VP draft 25 PEX uses response-level
302
+ // VP-token paths ($ or $[n]) plus path_nested for credentials; full draft-25
303
+ // multi-VP response satisfaction is deferred to DCQL/query support.
304
+ validatePresentation(
305
+ verifiablePresentation,
306
+ exchange,
307
+ context,
308
+ presentationSubmission,
309
+ );
310
+
311
+ return { jwtPresentation };
312
+ };
313
+
314
+ const verifyOpenid4vpPresentationJwt = async (jwtPresentation) => {
315
+ try {
316
+ return await verifyVerifiablePresentationJwt(jwtPresentation, {
317
+ vnfProtocolVersion: 2,
318
+ });
319
+ } catch (error) {
320
+ throw newError(400, 'Invalid OpenID4VP presentation', {
321
+ errorCode: 'openid4vp_presentation_invalid',
322
+ cause: error,
323
+ });
324
+ }
325
+ };
326
+
327
+ const storePresentation = ({ jwtPresentation }, exchange, { repos }) =>
328
+ repos.presentations.insert({
329
+ depotId: exchange.depotId,
330
+ exchangeId: exchange._id,
331
+ format: PresentationFormat.JWT_VP,
332
+ presentation: jwtPresentation,
333
+ });
334
+
335
+ const validateOpenid4vpPresentationClaims = (
336
+ { nonce, verifier },
337
+ exchange,
338
+ { tenant },
339
+ ) => {
340
+ if (nonce !== exchange.protocolMetadata.nonce) {
341
+ throw newError(400, 'OpenID4VP presentation nonce mismatch', {
342
+ errorCode: 'openid4vp_presentation_nonce_mismatch',
343
+ });
344
+ }
345
+ const expectedVerifier = `decentralized_identifier:${tenant.did}`;
346
+ if (verifier !== expectedVerifier) {
347
+ throw newError(400, 'OpenID4VP presentation verifier mismatch', {
348
+ errorCode: 'openid4vp_presentation_verifier_mismatch',
349
+ });
350
+ }
351
+ };
352
+
353
+ module.exports = { postAuthorizationResponse };
@@ -0,0 +1,19 @@
1
+ /*
2
+ * Copyright 2026 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
+ module.exports = {
18
+ ...require('./openid4vp-plugin'),
19
+ };