@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,427 @@
1
+ /**
2
+ * Copyright 2025 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 crypto = require('crypto');
18
+ const fp = require('fastify-plugin');
19
+ const newError = require('http-errors');
20
+ const { Openid4vciIssuer } = require('@openid4vc/openid4vci');
21
+ const {
22
+ Oauth2AuthorizationServer,
23
+ Oauth2ErrorCodes,
24
+ } = require('@openid4vc/oauth2');
25
+ const { castArray, isEmpty } = require('lodash/fp');
26
+ const {
27
+ compact,
28
+ filter,
29
+ flow,
30
+ includes,
31
+ map,
32
+ split,
33
+ toLower,
34
+ fromPairs,
35
+ uniq,
36
+ } = require('lodash/fp');
37
+ const {
38
+ getCredentialTypeMetadata,
39
+ getOrganizationVerifiedProfile,
40
+ } = require('@verii/common-fetchers');
41
+ const { ServiceCategories } = require('@verii/organizations-registry');
42
+ const { KeyPurposes } = require('@verii/crypto');
43
+ const { getJwkFromDidUri } = require('@verii/did-doc');
44
+ const { jwtVerify } = require('@verii/jwt');
45
+
46
+ const openid4vciPlugin = async (fastify) => {
47
+ fastify.decorateRequest(
48
+ 'getOpenId4VciIssuer',
49
+ async function getOpenId4VciIssuer() {
50
+ return buildIssuer(this);
51
+ },
52
+ );
53
+ fastify.decorate('openid4vcErrorHandler', (error, request, reply) => {
54
+ if (error.statusCode === 401) {
55
+ reply.status(error.statusCode);
56
+ return {
57
+ error: error.message,
58
+ error_description: error.message,
59
+ };
60
+ }
61
+ if (error.code === 'FST_ERR_VALIDATION') {
62
+ reply.status(error.statusCode);
63
+ return {
64
+ error: Oauth2ErrorCodes.InvalidRequest,
65
+ error_description: error.message,
66
+ };
67
+ }
68
+ if (error.errorResponse == null) {
69
+ throw error;
70
+ }
71
+ reply.status(error.status);
72
+ return error.errorResponse;
73
+ });
74
+ fastify.decorate('openid4vcBearerAuth', async (req) => {
75
+ const throwInvalidToken = () => {
76
+ throw newError(401, 'invalid_token', {
77
+ error: 'invalid_token',
78
+ error_description: 'invalid_token',
79
+ });
80
+ };
81
+ const authorizationHeader = req.headers.authorization;
82
+ if (authorizationHeader == null) {
83
+ throwInvalidToken();
84
+ }
85
+ const [bearerType, bearerValue] = split(' ', authorizationHeader);
86
+ if (toLower(bearerType) !== 'bearer') {
87
+ throwInvalidToken();
88
+ }
89
+ try {
90
+ const claims = { subject: getIssuerUri(req) };
91
+ await jwtVerify(
92
+ bearerValue,
93
+ req.tenant.keysByPurpose.EXCHANGES.publicJwk,
94
+ claims,
95
+ );
96
+ } catch (e) {
97
+ req.log.warn(e);
98
+ throwInvalidToken();
99
+ }
100
+ });
101
+ };
102
+
103
+ const buildIssuer = async (context) => {
104
+ const {
105
+ tenant,
106
+ kms,
107
+ config: { credentialExtensionsContextUrl },
108
+ } = context;
109
+
110
+ const generateRandom = (bytes) => crypto.randomBytes(bytes);
111
+ const signJwt = async (signer, { payload }) => {
112
+ const exchangesKey = tenant.keysByPurpose[KeyPurposes.EXCHANGES];
113
+ const jwt = await kms.signJwt(payload, exchangesKey._id);
114
+ return {
115
+ jwt,
116
+ signerJwk: signer.publicJwk,
117
+ };
118
+ };
119
+ const verifyJwt = async (signer, { compact: jwt }) => {
120
+ try {
121
+ const signerJwk = getJwkFromDidUri(signer.didUrl);
122
+ await jwtVerify(jwt, signerJwk);
123
+ return { verified: true, signerJwk };
124
+ } catch {
125
+ return { verified: false };
126
+ }
127
+ };
128
+
129
+ const encryptString = (data) => {
130
+ const exchangesKey = tenant.keysByPurpose[KeyPurposes.EXCHANGES];
131
+ return kms.encryptString(data, exchangesKey._id);
132
+ };
133
+
134
+ const hash = (data, alg) =>
135
+ crypto.createHash(alg.replace('-', '').toLowerCase()).update(data).digest();
136
+ const clientAuthentication = (
137
+ (_options) =>
138
+ ({ body }) => {
139
+ // eslint-disable-next-line better-mutation/no-mutation
140
+ body.client_id = _options.clientId;
141
+ }
142
+ )({ clientId: 'some-random-client-id' });
143
+ const issuer = new Openid4vciIssuer({
144
+ callbacks: {
145
+ signJwt,
146
+ verifyJwt,
147
+ parseXwwwFormUrlEncoded: (text) =>
148
+ Object.fromEntries(Array.from(new URLSearchParams(text).entries())),
149
+ hash,
150
+ generateRandom,
151
+ clientAuthentication,
152
+ },
153
+ });
154
+ const authorizationServer = new Oauth2AuthorizationServer({
155
+ callbacks: {
156
+ signJwt,
157
+ hash,
158
+ verifyJwt,
159
+ generateRandom,
160
+ clientAuthentication,
161
+ },
162
+ });
163
+
164
+ const openIdIssuer = getIssuerUri(context);
165
+ const openIdOauthRoot = `${openIdIssuer}/oauth`;
166
+
167
+ const getCredentialIssuerMetadata = async () => {
168
+ const { credentialMetadataList, profile } =
169
+ await fetchCredentialTypesAndProfile(context);
170
+ const permittedIssuerCategories = toIssuerCategories(
171
+ profile.permittedVelocityServiceCategory,
172
+ );
173
+
174
+ const credentialConfigurationsSupported = flow(
175
+ filter((credentialMetadata) =>
176
+ includes(credentialMetadata.issuerCategory, permittedIssuerCategories),
177
+ ),
178
+ map((credentialMetadata) => [
179
+ toCredentialConfigurationId(credentialMetadata.credentialType),
180
+ {
181
+ format: 'jwt_vc_json-ld',
182
+ cryptographic_binding_methods_supported: ['did:jwk'],
183
+ credential_signing_alg_values_supported: ['ES256K'],
184
+ credential_definition: {
185
+ '@context': [
186
+ 'https://www.w3.org/2018/credentials/v1',
187
+ credentialExtensionsContextUrl,
188
+ ],
189
+ type: ['VerifiableCredential', credentialMetadata.credentialType],
190
+ },
191
+ proof_types_supported: {
192
+ jwt: {
193
+ proof_signing_alg_values_supported: ['ES256', 'ES256K'],
194
+ },
195
+ },
196
+ },
197
+ ]),
198
+ fromPairs,
199
+ )(credentialMetadataList);
200
+ const credentialIssuerMetadata = issuer.createCredentialIssuerMetadata({
201
+ credential_issuer: openIdIssuer,
202
+ credential_endpoint: `${openIdIssuer}/credential`,
203
+ nonce_endpoint: `${openIdIssuer}/nonce`,
204
+ deferred_credential_endpoint: `${openIdIssuer}/deferred_credential`,
205
+ notification_endpoint: `${openIdIssuer}/notification`,
206
+ display: [
207
+ {
208
+ name: tenant.name,
209
+ logo: {
210
+ uri: tenant.logo,
211
+ alt_text: tenant.name,
212
+ },
213
+ locale: 'en',
214
+ },
215
+ ],
216
+ credential_configurations_supported: credentialConfigurationsSupported,
217
+ authorization_servers: [openIdOauthRoot],
218
+ });
219
+ return credentialIssuerMetadata;
220
+ };
221
+
222
+ const getAuthorizationServerMetadata = () => ({
223
+ issuer: `${openIdIssuer}`,
224
+ token_endpoint: `${openIdOauthRoot}/token`,
225
+ token_endpoint_auth_methods_supported: ['attest_jwt_client_auth'],
226
+ authorization_endpoint: `${openIdOauthRoot}/authorize`,
227
+ jwks_uri: `${openIdOauthRoot}/jwks.json`,
228
+ code_challenge_methods_supported: ['S256'],
229
+ dpop_signing_alg_values_supported: ['ES256'],
230
+ require_pushed_authorization_requests: true,
231
+ 'pre-authorized_grant_anonymous_access_supported': true,
232
+ pushed_authorization_request_endpoint: `${openIdOauthRoot}/par`,
233
+ });
234
+
235
+ const createCredentialOffer = async (offerCredentialTypes, preauthCode) => {
236
+ const credentialConfigurationIds = map(
237
+ toCredentialConfigurationId,
238
+ offerCredentialTypes,
239
+ );
240
+ const createdCredentialOffer = await issuer.createCredentialOffer({
241
+ credentialConfigurationIds,
242
+ grants: {
243
+ 'urn:ietf:params:oauth:grant-type:pre-authorized_code': {
244
+ 'pre-authorized_code': preauthCode,
245
+ },
246
+ },
247
+ issuerMetadata: {
248
+ credentialIssuer: await getCredentialIssuerMetadata(),
249
+ authorizationServers: [await getAuthorizationServerMetadata()],
250
+ },
251
+ });
252
+ return createdCredentialOffer.credentialOffer;
253
+ };
254
+
255
+ const getCredentialRequest = async (parameters) => {
256
+ const credentialRequest = issuer.parseCredentialRequest({
257
+ credentialRequest: {
258
+ credential_identifier: parameters.credential_identifier,
259
+ proofs: {
260
+ jwt: castArray(parameters.proofs?.jwt),
261
+ },
262
+ },
263
+ });
264
+ const issuerMetadata = {
265
+ credentialIssuer: openIdIssuer,
266
+ };
267
+ if (isEmpty(credentialRequest.proofs.jwt)) {
268
+ await issuer.verifyCredentialRequestJwtProof({
269
+ jwt: '',
270
+ issuerMetadata,
271
+ callbacks: {
272
+ verifyJwt,
273
+ },
274
+ });
275
+ }
276
+ await Promise.all(
277
+ map(
278
+ async (jwt) =>
279
+ issuer.verifyCredentialRequestJwtProof({
280
+ jwt,
281
+ issuerMetadata,
282
+ callbacks: {
283
+ verifyJwt,
284
+ },
285
+ }),
286
+ credentialRequest.proofs.jwt,
287
+ ),
288
+ );
289
+ return credentialRequest;
290
+ };
291
+
292
+ const createNonce = async () =>
293
+ issuer.createNonceResponse({
294
+ cNonce: await encryptString(`${Date.now()}`),
295
+ });
296
+ const generateAccessToken = async (
297
+ accessTokenParams,
298
+ authorizationDetails,
299
+ expiresInSeconds,
300
+ ) => {
301
+ const authorizationServerMetadata = getAuthorizationServerMetadata();
302
+ const accessTokenResponse =
303
+ await authorizationServer.createAccessTokenResponse({
304
+ callbacks: {
305
+ signJwt,
306
+ generateRandom,
307
+ hash,
308
+ },
309
+ authorizationServer: authorizationServerMetadata.authorization_endpoint,
310
+ signer: {
311
+ method: 'jwk',
312
+ publicJwk: tenant.keysByPurpose[KeyPurposes.EXCHANGES].publicJwk,
313
+ alg: tenant.keysByPurpose[KeyPurposes.EXCHANGES].algorithm,
314
+ },
315
+ expiresInSeconds,
316
+ audience: authorizationServerMetadata.issuer,
317
+ subject: openIdIssuer,
318
+ additionalAccessTokenResponsePayload: {
319
+ authorization_details: authorizationDetails,
320
+ },
321
+ });
322
+ return accessTokenResponse;
323
+ };
324
+ const validateAccessTokenParams = async (accessTokenParams, { raw: req }) => {
325
+ authorizationServer.parseAccessTokenRequest({
326
+ request: {
327
+ method: req.method,
328
+ url: req.url,
329
+ headers: new Headers(req.headers),
330
+ },
331
+ accessTokenRequest: accessTokenParams,
332
+ });
333
+ };
334
+
335
+ const verifyPreauthCode = async (
336
+ preauthCode,
337
+ expectedPreAuthorizedCode,
338
+ accessTokenParams,
339
+ _context,
340
+ ) =>
341
+ authorizationServer.verifyPreAuthorizedCodeAccessTokenRequest({
342
+ grant: {
343
+ grantType: accessTokenParams.grant_type,
344
+ preAuthorizedCode: preauthCode,
345
+ txCode: accessTokenParams.tx_code,
346
+ },
347
+ accessTokenRequest: accessTokenParams,
348
+ request: {
349
+ method: _context.raw.method,
350
+ url: _context.raw.url,
351
+ headers: new Headers(_context.raw.headers),
352
+ },
353
+ expectedPreAuthorizedCode,
354
+ expectedTxCode: undefined,
355
+ preAuthorizedCodeExpiresAt: undefined,
356
+ });
357
+
358
+ return {
359
+ createCredentialOffer,
360
+ createNonce,
361
+ generateAccessToken,
362
+ validateAccessTokenParams,
363
+ verifyPreauthCode,
364
+ getAuthorizationServerMetadata,
365
+ getCredentialIssuerMetadata,
366
+ getCredentialRequest,
367
+ };
368
+ };
369
+
370
+ const fetchWithErrorHandling = async (fetcherFunc, message, context) => {
371
+ try {
372
+ return await fetcherFunc();
373
+ } catch (error) {
374
+ context.log.error(message, error);
375
+ throw newError(502, error.message);
376
+ }
377
+ };
378
+
379
+ const ServiceCategoryToIssuerCategoryMap = {
380
+ [ServiceCategories.Issuer]: 'RegularIssuer',
381
+ [ServiceCategories.NotaryIssuer]: 'RegularIssuer',
382
+ [ServiceCategories.IdDocumentIssuer]: 'IdDocumentIssuer',
383
+ [ServiceCategories.NotaryIdDocumentIssuer]: 'IdDocumentIssuer',
384
+ [ServiceCategories.ContactIssuer]: 'ContactIssuer',
385
+ [ServiceCategories.NotaryContactIssuer]: 'ContactIssuer',
386
+ };
387
+
388
+ const toIssuerCategories = (permittedVelocityServiceCategory) =>
389
+ flow(
390
+ map(
391
+ (serviceCategory) => ServiceCategoryToIssuerCategoryMap[serviceCategory],
392
+ ),
393
+ compact,
394
+ uniq,
395
+ )(permittedVelocityServiceCategory);
396
+
397
+ const toCredentialConfigurationId = (credentialType) =>
398
+ `foundation.velocitynetwork.${credentialType}`;
399
+
400
+ const fetchCredentialTypesAndProfile = async (context) => {
401
+ const { tenant } = context;
402
+ const [credentialMetadataList, { credentialSubject: profile }] =
403
+ await Promise.all([
404
+ fetchWithErrorHandling(
405
+ () => getCredentialTypeMetadata([], context),
406
+ 'Error loading credential metadata',
407
+ context,
408
+ ),
409
+
410
+ fetchWithErrorHandling(
411
+ () => getOrganizationVerifiedProfile(tenant.did, context),
412
+ 'Error loading profile',
413
+ context,
414
+ ),
415
+ ]);
416
+ return { credentialMetadataList, profile };
417
+ };
418
+
419
+ const getIssuerUri = (context) =>
420
+ `${context.config.hostUrl}/r/${context.tenant._id}`;
421
+
422
+ module.exports = {
423
+ openid4vciPlugin: fp(openid4vciPlugin, {
424
+ name: 'openid4vci-plugin',
425
+ dependencies: [],
426
+ }),
427
+ };
@@ -0,0 +1,21 @@
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-request-id'),
19
+ ...require('./presentation-request-expiry'),
20
+ ...require('./wallet-metadata'),
21
+ };
@@ -0,0 +1,47 @@
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 newError = require('http-errors');
19
+
20
+ const OPENID4VP_SERVICE_REQUEST_PREFIX = 's-';
21
+ const OPENID4VP_DEPOT_REQUEST_PREFIX = 'd-';
22
+
23
+ const parseOpenid4vpRequestId = (requestId) => {
24
+ if (requestId?.startsWith(OPENID4VP_SERVICE_REQUEST_PREFIX)) {
25
+ return {
26
+ type: 'service',
27
+ id: requestId.substring(OPENID4VP_SERVICE_REQUEST_PREFIX.length),
28
+ };
29
+ }
30
+
31
+ if (requestId?.startsWith(OPENID4VP_DEPOT_REQUEST_PREFIX)) {
32
+ return {
33
+ type: 'depot',
34
+ id: requestId.substring(OPENID4VP_DEPOT_REQUEST_PREFIX.length),
35
+ };
36
+ }
37
+
38
+ throw newError(400, 'openid4vp_request_id_invalid', {
39
+ errorCode: 'openid4vp_request_id_invalid',
40
+ });
41
+ };
42
+
43
+ module.exports = {
44
+ OPENID4VP_DEPOT_REQUEST_PREFIX,
45
+ OPENID4VP_SERVICE_REQUEST_PREFIX,
46
+ parseOpenid4vpRequestId,
47
+ };
@@ -0,0 +1,37 @@
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 PRESENTATION_REQUESTS_EXPIRE_IN_SECONDS = 600;
19
+
20
+ const getPresentationRequestsExpireIn = (relyingPartyService) =>
21
+ relyingPartyService.presentationRequestsExpireIn ??
22
+ PRESENTATION_REQUESTS_EXPIRE_IN_SECONDS;
23
+
24
+ const buildPresentationRequestExpiresAt = (
25
+ relyingPartyService,
26
+ requestedAt = new Date(),
27
+ ) =>
28
+ new Date(
29
+ requestedAt.getTime() +
30
+ getPresentationRequestsExpireIn(relyingPartyService) * 1000,
31
+ );
32
+
33
+ module.exports = {
34
+ buildPresentationRequestExpiresAt,
35
+ getPresentationRequestsExpireIn,
36
+ PRESENTATION_REQUESTS_EXPIRE_IN_SECONDS,
37
+ };
@@ -0,0 +1,30 @@
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 newError = require('http-errors');
19
+
20
+ const parseWalletMetadata = (walletMetadata) => {
21
+ try {
22
+ return JSON.parse(walletMetadata);
23
+ } catch {
24
+ throw newError(400, 'wallet_metadata_invalid', {
25
+ errorCode: 'wallet_metadata_invalid',
26
+ });
27
+ }
28
+ };
29
+
30
+ module.exports = { parseWalletMetadata };
@@ -0,0 +1,21 @@
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('./domain'),
19
+ ...require('./orchestrators'),
20
+ ...require('./plugins'),
21
+ };