@velocitycareerlabs/server-careerwallet 1.25.0-dev-build.12642c864

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 (252) hide show
  1. package/.localdev.env +47 -0
  2. package/.standalone.env +13 -0
  3. package/LICENSE +248 -0
  4. package/jest.config.js +20 -0
  5. package/migrate-mongo.config.js +25 -0
  6. package/migrations/20211017180227-create-personas.js +478 -0
  7. package/migrations/20211026185916-create-vanessa.js +79 -0
  8. package/migrations/20211026185917-update-personas.js +30 -0
  9. package/migrations/20211108124410-remove-surplus-personas.js +33 -0
  10. package/migrations/20211108132353-fix-vanessa-and-sheila.js +25 -0
  11. package/migrations/20220222123110-add-career-wallet-app-config.js +6 -0
  12. package/migrations/20220411104157-add-min-app-versions.js +15 -0
  13. package/migrations/20220419131726-create-nicole-flores-persona.js +63 -0
  14. package/migrations/20220515114034-update-persona-id-credentials.js +628 -0
  15. package/migrations/20220608093743-disable-mainnet-holderapp-id-verification.js +21 -0
  16. package/migrations/20220609063708-enable-mainnet-holderapp-id-verification.js +21 -0
  17. package/migrations/20220623091507-add-push-url.js +43 -0
  18. package/migrations/20220624133205-set-min-app-versions-to-11.js +16 -0
  19. package/migrations/20220710125326-set-min-app-version-to-0.10.7.js +16 -0
  20. package/migrations/20220811103500-add-verification-service-disclosure-deeplink.js +45 -0
  21. package/migrations/20220811123751-add-holderapp-dids-to-config.js +74 -0
  22. package/migrations/20220818072306-add-holderapp-endpoints-to-config.js +21 -0
  23. package/migrations/20220825090656-update-deeplink.js +46 -0
  24. package/migrations/20221003151823-app-config-add-public-verification-api.js +18 -0
  25. package/migrations/20221116085242-add-holderapp-sdk-to-config.js +15 -0
  26. package/migrations/20221121091030-update-holderapp-deeplink.js +19 -0
  27. package/migrations/20221128103425-update-holderapp-presentation-template.js +19 -0
  28. package/migrations/20221221091436-app-config-add-oauth.js +13 -0
  29. package/migrations/20221226205900-app-config-add-presentation-extension-api.js +18 -0
  30. package/migrations/20230120113141-update-holderapp-cashSiquence.js +15 -0
  31. package/migrations/20230123084103-update-holderapp-cacheSequence.js +15 -0
  32. package/migrations/20230214083430-update-holderapp-cache-Sequence.js +15 -0
  33. package/migrations/20230225173335-set-min-app-version-1.5.1.js +30 -0
  34. package/migrations/20230323120629-add-sunil-singh-persona.js +87 -0
  35. package/migrations/20230329081529-add-personas-by-env.js +294 -0
  36. package/migrations/20230329103219-remove-unused-personas.js +14 -0
  37. package/migrations/20230504090208-disable-yoti-migration.js +21 -0
  38. package/migrations/20230504123425-set-min-app-version-1.8.1.js +38 -0
  39. package/migrations/20230504185047-enable-yoti-migration.js +21 -0
  40. package/migrations/20230524053203-add-devices-index.js +16 -0
  41. package/migrations/20230704000002-add-common-holder-endpoints-to-config.js +16 -0
  42. package/migrations/20230704000003-add-linkedin-holder-endpoints-to-config.js +18 -0
  43. package/migrations/20230704104055-update-push-url-and-yoti-url.js +18 -0
  44. package/migrations/20230705000001-add-liburl-to-holderapp-config.js +20 -0
  45. package/migrations/20230814113134-app-config-add-oauth-client-id.js +18 -0
  46. package/migrations/20230821154136-yoti-new-session-url-fix.js +19 -0
  47. package/migrations/20230907134442-update-holderapp-cache-Sequence.js +15 -0
  48. package/migrations/20230919180000-set-holderapp-min-app-versions-1.15.0.js +16 -0
  49. package/migrations/20231011083137-update-holderapp-cache-sequence-6.js +15 -0
  50. package/migrations/20231102083252-set-holderapp-min-app-versions-1.14.0.js +16 -0
  51. package/migrations/20231108202229-set-holderapp-min-app-versions-1.15.0.js +16 -0
  52. package/migrations/20231115143332-holderapp-isDirectIssuerCheckOn-true.js +16 -0
  53. package/migrations/20231120100020-insert-didKeyMetadatum-for-old-accounts.js +59 -0
  54. package/migrations/20231207142742-remove-devices-from-accounts.js +46 -0
  55. package/migrations/20231226090805-app-config-set-direct-issuer-check.js +13 -0
  56. package/migrations/202312271524-set-holderapp-app-version-1.15.1.js +16 -0
  57. package/migrations/20232211171700-app-config-update-base-urls.js +18 -0
  58. package/migrations/20240102093506-holderapp-revert-version-1.15.0.js +16 -0
  59. package/migrations/202401041618111-holderapp-isDebugOn-false.js +17 -0
  60. package/migrations/20240131095122-test-personas-add-did.js +102 -0
  61. package/migrations/202402051547000-update-sdk.js +18 -0
  62. package/migrations/20240206101448-update-personas-vc-to-v2.js +137 -0
  63. package/migrations/202402061233000-set-xVnfProtocolVersion-2.js +13 -0
  64. package/migrations/202402081240000-set-xVnfProtocolVersion-1.js +13 -0
  65. package/migrations/202402131319-set-holderapp-min-versions-1.17.js +16 -0
  66. package/migrations/202402141152-set-holderapp-min-version-1.16.js +16 -0
  67. package/migrations/20240221123501-transform-account-keys-to-stringified-jwk.js +81 -0
  68. package/migrations/202402290955-update-holderapp-to-verifyMyCreds.js +36 -0
  69. package/migrations/20240311134223-update-keyid-persona.js +66 -0
  70. package/migrations/20240312141618-vl-7409-persona-update-maria-williams.js +82 -0
  71. package/migrations/202403181733000-remove-devices-from-all-accounts.js +27 -0
  72. package/migrations/20240401091041-set-holderapp-min-version-1.18.1.js +16 -0
  73. package/migrations/202404071847-app-config-update-yoti-url.js +19 -0
  74. package/migrations/20240724063405-vl-3827-new-yoti-session-url.js +19 -0
  75. package/migrations/20240731112302-vl-8160-add-persona-keys.js +101 -0
  76. package/migrations/20240911115206-vanessa-lin-id-credentials.js +57 -0
  77. package/migrations/202409201219-add-isWalletAvailable-field.js +13 -0
  78. package/migrations/202409221012-set-isWalletAvailable-true.js +13 -0
  79. package/migrations/20240922114643-holderapp-isWalletAvailable-refactor.js +36 -0
  80. package/migrations/20240923132213-update-android-ios-app-config.js +36 -0
  81. package/migrations/20240926061732-inc-cacheSequence-to-7.js +15 -0
  82. package/migrations/20240926073339-inc-cacheSequence-to-7-fix.js +18 -0
  83. package/migrations/202410081217-adam-smith-id-credntials.js +57 -0
  84. package/migrations/202410101243-adam_smith-id-credential.js +57 -0
  85. package/migrations/20241015124307-persona-key-id-type-fix.js +26 -0
  86. package/migrations/202501291027000-set-holderapp-did-web.js +37 -0
  87. package/migrations/environments/dev.env +34 -0
  88. package/migrations/environments/localdev.env +22 -0
  89. package/migrations/environments/prod.env +18 -0
  90. package/migrations/environments/qa.env +54 -0
  91. package/migrations/environments/staging.env +31 -0
  92. package/migrations/environments/test.env +18 -0
  93. package/package.json +84 -0
  94. package/src/assets/category-icons/assessment.png +0 -0
  95. package/src/assets/category-icons/badge.png +0 -0
  96. package/src/assets/category-icons/certification.png +0 -0
  97. package/src/assets/category-icons/education.png +0 -0
  98. package/src/assets/category-icons/employment.png +0 -0
  99. package/src/assets/category-icons/gig.png +0 -0
  100. package/src/assets/category-icons/identity.png +0 -0
  101. package/src/assets/category-icons/pharmacy.png +0 -0
  102. package/src/assets/category-icons/training.png +0 -0
  103. package/src/assets/credentialCategories.json +119 -0
  104. package/src/config/config.js +156 -0
  105. package/src/controllers/api/v0.6/accounts/autohooks.js +36 -0
  106. package/src/controllers/api/v0.6/accounts/controller.js +288 -0
  107. package/src/controllers/api/v0.6/accounts/schemas/careerwallet-accounts-didkeymetadatum-response.schema.js +41 -0
  108. package/src/controllers/api/v0.6/accounts/schemas/careerwallet-accounts-request.schema.js +49 -0
  109. package/src/controllers/api/v0.6/accounts/schemas/careerwallet-accounts-response.schema.js +74 -0
  110. package/src/controllers/api/v0.6/accounts/schemas/careerwallet-get-account-response.schema.js +72 -0
  111. package/src/controllers/api/v0.6/accounts/schemas/index.js +6 -0
  112. package/src/controllers/api/v0.6/careerwallet/appconfig/controller.js +25 -0
  113. package/src/controllers/api/v0.6/careerwallet/appconfig/repo.js +40 -0
  114. package/src/controllers/api/v0.6/careerwallet/autohooks.js +5 -0
  115. package/src/controllers/api/v0.6/careerwallet/consents/controller.js +66 -0
  116. package/src/controllers/api/v0.6/careerwallet/consents/latest/autohooks.js +7 -0
  117. package/src/controllers/api/v0.6/careerwallet/consents/latest/controller.js +76 -0
  118. package/src/controllers/api/v0.6/careerwallet/consents/repo.js +24 -0
  119. package/src/controllers/api/v0.6/careerwallet/consents/schemas/careerwallet-consent-response.schema.js +23 -0
  120. package/src/controllers/api/v0.6/careerwallet/consents/schemas/index.js +3 -0
  121. package/src/controllers/api/v0.6/create_did_key/autohooks.js +10 -0
  122. package/src/controllers/api/v0.6/create_did_key/controller.js +84 -0
  123. package/src/controllers/api/v0.6/create_did_key/schemas/index.js +4 -0
  124. package/src/controllers/api/v0.6/create_did_key/schemas/jwk-did-request.schema.js +20 -0
  125. package/src/controllers/api/v0.6/create_did_key/schemas/jwk-did-response.schema.js +41 -0
  126. package/src/controllers/api/v0.6/create_jwk/autohooks.js +10 -0
  127. package/src/controllers/api/v0.6/create_jwk/controller.js +46 -0
  128. package/src/controllers/api/v0.6/create_jwk/schemas/index.js +3 -0
  129. package/src/controllers/api/v0.6/create_jwk/schemas/jwk-response.schema.js +33 -0
  130. package/src/controllers/api/v0.6/credential-categories/controller.js +35 -0
  131. package/src/controllers/api/v0.6/devices/autohooks.js +11 -0
  132. package/src/controllers/api/v0.6/devices/controller.js +323 -0
  133. package/src/controllers/api/v0.6/devices/repo.js +27 -0
  134. package/src/controllers/api/v0.6/devices/schemas/device.schema.json +43 -0
  135. package/src/controllers/api/v0.6/devices/schemas/index.js +3 -0
  136. package/src/controllers/api/v0.6/feedback/controller.js +24 -0
  137. package/src/controllers/api/v0.6/feedback/schemas/index.js +3 -0
  138. package/src/controllers/api/v0.6/feedback/schemas/new-feedback.schema.js +47 -0
  139. package/src/controllers/api/v0.6/jwt/autohooks.js +10 -0
  140. package/src/controllers/api/v0.6/jwt/controller.js +44 -0
  141. package/src/controllers/api/v0.6/jwt/schemas/index.js +4 -0
  142. package/src/controllers/api/v0.6/jwt/schemas/jwt-request.schema.js +40 -0
  143. package/src/controllers/api/v0.6/jwt/schemas/jwt-response.schema.js +14 -0
  144. package/src/controllers/api/v0.6/oauth/controller.js +131 -0
  145. package/src/controllers/api/v0.6/push/controller.js +296 -0
  146. package/src/controllers/api/v0.6/push/firebase-initializer.js +21 -0
  147. package/src/controllers/api/v0.6/push/notification-types.js +37 -0
  148. package/src/controllers/api/v0.6/push/push-gateway-auth.js +123 -0
  149. package/src/controllers/api/v0.6/push/repo.js +24 -0
  150. package/src/controllers/api/v0.6/verification-offers/repo.js +25 -0
  151. package/src/controllers/api/v0.6/verify/autohooks.js +15 -0
  152. package/src/controllers/api/v0.6/verify/controller.js +348 -0
  153. package/src/controllers/api/v0.6/verify/repo.js +23 -0
  154. package/src/controllers/api/v0.6/verify/verification-credential-types.js +6 -0
  155. package/src/controllers/jwt/autohooks.js +10 -0
  156. package/src/controllers/jwt/controller.js +106 -0
  157. package/src/controllers/jwt/schemas/index.js +31 -0
  158. package/src/controllers/jwt/schemas/jwt-decode.response.200.schema.json +20 -0
  159. package/src/controllers/jwt/schemas/jwt-decode.schema.json +15 -0
  160. package/src/controllers/jwt/schemas/jwt-sign.response.200.schema.json +14 -0
  161. package/src/controllers/jwt/schemas/jwt-sign.schema.json +40 -0
  162. package/src/controllers/jwt/schemas/jwt-verify.response.200.schema.json +17 -0
  163. package/src/controllers/jwt/schemas/jwt-verify.schema.json +19 -0
  164. package/src/controllers/reference/autohooks.js +5 -0
  165. package/src/controllers/reference/countries/controller.js +81 -0
  166. package/src/controllers/reference/personas/controller.js +91 -0
  167. package/src/controllers/reference/personas/repo.js +29 -0
  168. package/src/controllers/reference/personas/schemas/index.js +3 -0
  169. package/src/controllers/reference/personas/schemas/persona.schema.json +108 -0
  170. package/src/controllers/root/controller.js +27 -0
  171. package/src/entities/accounts/constants.js +18 -0
  172. package/src/entities/accounts/domain/index.js +3 -0
  173. package/src/entities/accounts/domain/merge-scopes.js +9 -0
  174. package/src/entities/accounts/index.js +5 -0
  175. package/src/entities/accounts/repos/accounts.repo.js +64 -0
  176. package/src/entities/accounts/repos/id-token-claims-extension.js +31 -0
  177. package/src/entities/accounts/repos/index.js +3 -0
  178. package/src/entities/devices/constants.js +7 -0
  179. package/src/entities/devices/index.js +3 -0
  180. package/src/entities/feedback/domain/generate-feedback-email.js +23 -0
  181. package/src/entities/feedback/domain/index.js +3 -0
  182. package/src/entities/feedback/index.js +3 -0
  183. package/src/entities/index.js +9 -0
  184. package/src/entities/key-pairs/index.js +4 -0
  185. package/src/entities/key-pairs/orchestrators/generate-jwk.js +28 -0
  186. package/src/entities/key-pairs/orchestrators/get-key-pair.js +58 -0
  187. package/src/entities/key-pairs/orchestrators/index.js +4 -0
  188. package/src/entities/key-pairs/repos/index.js +3 -0
  189. package/src/entities/key-pairs/repos/key-pairs.repo.js +23 -0
  190. package/src/entities/oauth/constants.js +13 -0
  191. package/src/entities/oauth/domain/build-access-token.js +14 -0
  192. package/src/entities/oauth/domain/index.js +10 -0
  193. package/src/entities/oauth/domain/validate-audience.js +13 -0
  194. package/src/entities/oauth/domain/validate-client-id.js +13 -0
  195. package/src/entities/oauth/domain/validate-credential.js +16 -0
  196. package/src/entities/oauth/domain/validate-presentation.js +17 -0
  197. package/src/entities/oauth/domain/validate-refresh-token.js +17 -0
  198. package/src/entities/oauth/domain/validate-scope.js +30 -0
  199. package/src/entities/oauth/domain/verify-presentation.js +24 -0
  200. package/src/entities/oauth/index.js +4 -0
  201. package/src/entities/pushes/domains/errors.js +11 -0
  202. package/src/entities/pushes/domains/index.js +3 -0
  203. package/src/entities/pushes/index.js +3 -0
  204. package/src/entities/verification-code-attempts/index.js +3 -0
  205. package/src/entities/verification-code-attempts/orchestrators/index.js +3 -0
  206. package/src/entities/verification-code-attempts/orchestrators/validate-verification-code-attempts.js +79 -0
  207. package/src/entities/verification-code-attempts/repo.js +99 -0
  208. package/src/entities/verifications/constants.js +8 -0
  209. package/src/entities/verifications/index.js +3 -0
  210. package/src/helpers/caching-constants.js +6 -0
  211. package/src/helpers/index.js +3 -0
  212. package/src/index.js +15 -0
  213. package/src/init-server.js +88 -0
  214. package/src/plugins/index.js +4 -0
  215. package/src/plugins/vcl-verification-version-plugin.js +10 -0
  216. package/src/plugins/verify-access-token-plugin.js +116 -0
  217. package/src/standalone.js +8 -0
  218. package/test/accounts-controller.test.js +893 -0
  219. package/test/accounts-repo.test.js +92 -0
  220. package/test/careerwallet-config-controller.test.js +142 -0
  221. package/test/careerwallet-consents-controller.test.js +409 -0
  222. package/test/create_did_key-controller.test.js +397 -0
  223. package/test/create_jwk-controller.test.js +188 -0
  224. package/test/credential-categories-controller.test.js +29 -0
  225. package/test/credential-icons-controller.test.js +36 -0
  226. package/test/devices-controller.test.js +1025 -0
  227. package/test/factories/accounts-factory.js +15 -0
  228. package/test/factories/career-wallet-config-factory.js +60 -0
  229. package/test/factories/consents-career-wallet-factory.js +21 -0
  230. package/test/factories/devices-factory.js +16 -0
  231. package/test/factories/key-pairs-factory.js +18 -0
  232. package/test/factories/notifications-factory.js +16 -0
  233. package/test/factories/persona-factory.js +17 -0
  234. package/test/factories/refresh-tokens-factory.js +14 -0
  235. package/test/factories/verification-code-attempts-factory.js +17 -0
  236. package/test/factories/verification-factory.js +15 -0
  237. package/test/factories/verification-offer-factory.js +20 -0
  238. package/test/feedback-controller.test.js +225 -0
  239. package/test/helpers/.env.test +39 -0
  240. package/test/helpers/access-token.js +59 -0
  241. package/test/helpers/careerwallet-build-fastify.js +20 -0
  242. package/test/helpers/yoti.js +96 -0
  243. package/test/id-verification-controller.test.js +27 -0
  244. package/test/jwt-controller.test.js +1519 -0
  245. package/test/oauth-controller.test.js +639 -0
  246. package/test/push-controller.test.js +733 -0
  247. package/test/push-gateway-auth.test.js +208 -0
  248. package/test/reference-countries-controller.test.js +45 -0
  249. package/test/reference-personas-controller.test.js +179 -0
  250. package/test/root.test.js +21 -0
  251. package/test/swagger.test.js +21 -0
  252. package/test/verification-controller.test.js +1372 -0
@@ -0,0 +1,639 @@
1
+ const { map, slice, size, set } = require('lodash/fp');
2
+ const {
3
+ generateCredentialJwt,
4
+ generatePresentationJwt,
5
+ tamperJwt,
6
+ jwkFromSecp256k1Key,
7
+ jwtVerify,
8
+ } = require('@velocitycareerlabs/jwt');
9
+
10
+ const {
11
+ generateKeyPair,
12
+ hashAndEncodeHex,
13
+ } = require('@velocitycareerlabs/crypto');
14
+ const { nanoid } = require('nanoid/non-secure');
15
+ const { mapWithIndex } = require('@velocitycareerlabs/common-functions');
16
+ const { errorResponseMatcher } = require('@velocitycareerlabs/tests-helpers');
17
+ const { mongoDb } = require('@spencejs/spence-mongo-repos');
18
+ const buildFastify = require('./helpers/careerwallet-build-fastify');
19
+ const { accountsRepoPlugin } = require('../src/entities');
20
+ const initAccountsFactory = require('./factories/accounts-factory');
21
+
22
+ const oauthUrl = '/api/v0.6/oauth/token';
23
+ const vnfPresentationGrantType =
24
+ 'https://velocitynetwork.foundation/oauth/presentation';
25
+ const audience = 'walletapi.velocitycareerlabs.io';
26
+
27
+ const testUserDid = 'did:kid-it-is-test_user_did';
28
+
29
+ const phonePayload = {
30
+ sub: 'did:velocity:0x0a63c18d09d5430363b2f3b270698a677fb513e4',
31
+ vc: {
32
+ '@context': ['https://www.w3.org/2018/credentials/v1'],
33
+ id: 'did:velocity:0x0a63c18d09d5430363b2f3b270698a677fb513e4',
34
+ issuer: { id: 'did:velocity:0x0b154da48d0f213c26c4b1d040dc5ff1dbf99ffa' },
35
+ issuanceDate: '2020-08-17T11:27:06.000Z',
36
+ type: ['PhoneV1.0', 'VerifiableCredential'],
37
+ credentialSubject: {
38
+ phone: '+447309917830',
39
+ id: testUserDid,
40
+ },
41
+ },
42
+ };
43
+
44
+ const generatePresentation = async (
45
+ credentials,
46
+ {
47
+ isBrokeVCS = false,
48
+ privateKeyToSignCredential = generateKeyPair().privateKey,
49
+ privateKeyToSignPresentation = generateKeyPair().privateKey,
50
+ tamperCredentials = false,
51
+ }
52
+ ) => {
53
+ const signedCredentials = await Promise.all(
54
+ map(async (c) => {
55
+ const signedCredential = await generateCredentialJwt(
56
+ c,
57
+ privateKeyToSignCredential
58
+ );
59
+ if (!tamperCredentials) {
60
+ return signedCredential;
61
+ }
62
+ return tamperJwt(signedCredential, { foo: 'foo' });
63
+ }, credentials)
64
+ );
65
+
66
+ const presentation = {
67
+ id: nanoid(),
68
+ verifiableCredential: isBrokeVCS
69
+ ? [
70
+ ...slice(0, 1, signedCredentials),
71
+ ...Array(size(signedCredentials) - 1).fill(''),
72
+ ]
73
+ : signedCredentials,
74
+ presentation_submission: {
75
+ id: nanoid(),
76
+ definition_id: nanoid(),
77
+ descriptor_map: mapWithIndex(
78
+ (c, i) => ({
79
+ id: nanoid(),
80
+ path: `$.verifiableCredential[${i}]`,
81
+ format: 'jwt_vc',
82
+ }),
83
+ signedCredentials
84
+ ),
85
+ },
86
+ };
87
+
88
+ return {
89
+ presentation,
90
+ credentials,
91
+ selfSign() {
92
+ return generatePresentationJwt(
93
+ this.presentation,
94
+ privateKeyToSignPresentation
95
+ );
96
+ },
97
+ };
98
+ };
99
+
100
+ const accessTokenJwtMatcher = async (
101
+ accessTokenJwt,
102
+ verificationKey,
103
+ iss,
104
+ sub
105
+ ) => {
106
+ const decodedJwt = await jwtVerify(
107
+ accessTokenJwt,
108
+ jwkFromSecp256k1Key(verificationKey, false)
109
+ );
110
+ expect(decodedJwt).toEqual({
111
+ header: {
112
+ alg: 'ES256K',
113
+ jwk: jwkFromSecp256k1Key(verificationKey, false),
114
+ typ: 'JWT',
115
+ },
116
+ payload: {
117
+ exp: expect.any(Number),
118
+ iat: expect.any(Number),
119
+ nbf: expect.any(Number),
120
+ nonce: expect.any(String),
121
+ scope: expect.any(String),
122
+ sub,
123
+ iss,
124
+ },
125
+ });
126
+ expect(decodedJwt.payload.exp).toBeGreaterThan(decodedJwt.payload.iat);
127
+ expect(decodedJwt.payload.exp).toBeGreaterThan(decodedJwt.payload.nbf);
128
+ expect(decodedJwt.payload.nonce).toHaveLength(8);
129
+ };
130
+ describe('oauth test suite', () => {
131
+ let fastify;
132
+ let accountsCollection;
133
+ let accountsRepo;
134
+ let persistAccounts;
135
+ let validScopes;
136
+
137
+ beforeAll(async () => {
138
+ fastify = await buildFastify();
139
+ await fastify.ready();
140
+ const {
141
+ config: { holderAppServerOAuthScopes },
142
+ } = fastify;
143
+
144
+ validScopes = holderAppServerOAuthScopes.split(' ');
145
+
146
+ accountsCollection = mongoDb().collection('accounts');
147
+
148
+ accountsRepo = accountsRepoPlugin(fastify)({
149
+ log: fastify.log,
150
+ config: fastify.config,
151
+ });
152
+
153
+ ({ persistAccounts } = initAccountsFactory(fastify));
154
+ });
155
+
156
+ beforeEach(async () => {
157
+ await accountsCollection.deleteMany({});
158
+ });
159
+
160
+ afterAll(async () => {
161
+ await fastify.close();
162
+ });
163
+
164
+ it('should 400 if grant_type is not equal to one of the allowed values', async () => {
165
+ const response = await fastify.injectJson({
166
+ method: 'POST',
167
+ url: oauthUrl,
168
+ payload: { grant_type: 'foo' },
169
+ });
170
+ expect(response.statusCode).toBe(400);
171
+ });
172
+ it('should 400 if refresh_token grant type and no refresh_token value', async () => {
173
+ const response = await fastify.injectJson({
174
+ method: 'POST',
175
+ url: oauthUrl,
176
+ payload: {
177
+ grant_type: 'refresh_token',
178
+ client_id: 'foo',
179
+ scope: 'foo',
180
+ audience: 'foo',
181
+ },
182
+ });
183
+ expect(response.statusCode).toBe(400);
184
+ expect(response.json).toEqual(
185
+ errorResponseMatcher({
186
+ statusCode: 400,
187
+ errorCode: 'missing_error_code',
188
+ message: 'Refresh token required with grant type "refresh_token"',
189
+ error: 'Bad Request',
190
+ })
191
+ );
192
+ });
193
+ it('should 400 if vnf presentation grant type and no presentation value', async () => {
194
+ const response = await fastify.injectJson({
195
+ method: 'POST',
196
+ url: oauthUrl,
197
+ payload: {
198
+ grant_type: vnfPresentationGrantType,
199
+ client_id: 'foo',
200
+ scope: 'foo',
201
+ audience: 'foo',
202
+ },
203
+ });
204
+ expect(response.statusCode).toBe(400);
205
+ expect(response.json).toEqual(
206
+ errorResponseMatcher({
207
+ statusCode: 400,
208
+ errorCode: 'missing_error_code',
209
+ message: `Presentation required with grant type "${vnfPresentationGrantType}"`,
210
+ error: 'Bad Request',
211
+ })
212
+ );
213
+ });
214
+ it('should 401 if client id is not valid', async () => {
215
+ const response = await fastify.injectJson({
216
+ method: 'POST',
217
+ url: oauthUrl,
218
+ payload: {
219
+ grant_type: 'refresh_token',
220
+ client_id: 'foo',
221
+ scope: 'foo',
222
+ audience: 'foo',
223
+ refresh_token: 'foo',
224
+ },
225
+ });
226
+ expect(response.statusCode).toBe(401);
227
+ expect(response.json).toEqual(
228
+ errorResponseMatcher({
229
+ statusCode: 401,
230
+ errorCode: 'missing_error_code',
231
+ message: 'Unknown client id: foo',
232
+ error: 'Unauthorized',
233
+ })
234
+ );
235
+ });
236
+ it('should 401 if unrecognized scopes are provided', async () => {
237
+ const response = await fastify.injectJson({
238
+ method: 'POST',
239
+ url: oauthUrl,
240
+ payload: {
241
+ grant_type: 'refresh_token',
242
+ client_id: 'abc',
243
+ scope: 'foo',
244
+ audience: 'foo',
245
+ refresh_token: 'foo',
246
+ },
247
+ });
248
+ expect(response.statusCode).toBe(401);
249
+ expect(response.json).toEqual(
250
+ errorResponseMatcher({
251
+ statusCode: 401,
252
+ errorCode: 'missing_error_code',
253
+ message: 'Unrecognized scopes: ["foo"]',
254
+ error: 'Unauthorized',
255
+ })
256
+ );
257
+ });
258
+ it('should 401 if unknown audience is provided', async () => {
259
+ const response = await fastify.injectJson({
260
+ method: 'POST',
261
+ url: oauthUrl,
262
+ payload: {
263
+ grant_type: 'refresh_token',
264
+ client_id: 'abc',
265
+ scope: validScopes[0],
266
+ audience: 'foo',
267
+ refresh_token: 'foo',
268
+ },
269
+ });
270
+ expect(response.statusCode).toBe(401);
271
+ expect(response.json).toEqual(
272
+ errorResponseMatcher({
273
+ statusCode: 401,
274
+ message: 'Unknown audience: foo',
275
+ errorCode: 'missing_error_code',
276
+ error: 'Unauthorized',
277
+ })
278
+ );
279
+ });
280
+ it("should 401 if credential in presentation doesn't verify", async () => {
281
+ const tempVclKeyPair = generateKeyPair();
282
+ const presentationPhone = await generatePresentation([phonePayload], {
283
+ tamperCredentials: true,
284
+ privateKeyToSignCredential: tempVclKeyPair.privateKey,
285
+ });
286
+ fastify.overrides.reqConfig = (config) => ({
287
+ ...config,
288
+ oauthVclCredentialVerificationPublicKey: tempVclKeyPair.publicKey,
289
+ });
290
+ const response = await fastify.injectJson({
291
+ method: 'POST',
292
+ url: oauthUrl,
293
+ payload: {
294
+ grant_type: vnfPresentationGrantType,
295
+ client_id: 'abc',
296
+ scope: validScopes[0],
297
+ audience,
298
+ presentation: await presentationPhone.selfSign(),
299
+ },
300
+ });
301
+ expect(response.statusCode).toBe(401);
302
+ expect(response.json).toEqual(
303
+ errorResponseMatcher({
304
+ statusCode: 401,
305
+ errorCode: 'missing_error_code',
306
+ message: 'Credential verification failed',
307
+ error: 'Unauthorized',
308
+ })
309
+ );
310
+ });
311
+ it('should 401 if credential is not of type PhoneV1.0', async () => {
312
+ const tempVclKeyPair = generateKeyPair({ format: 'jwk' });
313
+ const phonePayloadWithInvalidType = set('vc.type', ['foo'], phonePayload);
314
+ const presentationPhone = await generatePresentation(
315
+ [phonePayloadWithInvalidType],
316
+ {
317
+ privateKeyToSignCredential: tempVclKeyPair.privateKey,
318
+ }
319
+ );
320
+ fastify.overrides.reqConfig = (config) => ({
321
+ ...config,
322
+ oauthVclCredentialVerificationPublicKey: tempVclKeyPair.publicKey,
323
+ });
324
+ const response = await fastify.injectJson({
325
+ method: 'POST',
326
+ url: oauthUrl,
327
+ payload: {
328
+ grant_type: vnfPresentationGrantType,
329
+ client_id: 'abc',
330
+ scope: validScopes[0],
331
+ audience,
332
+ presentation: await presentationPhone.selfSign(),
333
+ },
334
+ });
335
+ expect(response.statusCode).toBe(401);
336
+ expect(response.json).toEqual(
337
+ errorResponseMatcher({
338
+ statusCode: 401,
339
+ errorCode: 'missing_error_code',
340
+ message: 'Credential is not of type PhoneV1.0',
341
+ error: 'Unauthorized',
342
+ })
343
+ );
344
+ });
345
+ it('should 401 with refresh_token grant type and unknown refresh token', async () => {
346
+ const response = await fastify.injectJson({
347
+ method: 'POST',
348
+ url: oauthUrl,
349
+ payload: {
350
+ grant_type: 'refresh_token',
351
+ client_id: 'abc',
352
+ scope: validScopes[0],
353
+ audience,
354
+ refresh_token: 'foo',
355
+ },
356
+ });
357
+ expect(response.statusCode).toBe(401);
358
+ expect(response.json).toEqual(
359
+ errorResponseMatcher({
360
+ statusCode: 401,
361
+ errorCode: 'missing_error_code',
362
+ message: 'Unknown refresh_token, user id or scope',
363
+ error: 'Unauthorized',
364
+ })
365
+ );
366
+ });
367
+ it('should 200 with presentation grant type and proper credential when account has single did value', async () => {
368
+ const testCredentialVerificationKeyPair = generateKeyPair({
369
+ format: 'jwk',
370
+ });
371
+ const testAccessTokenSigningKeyPair = generateKeyPair();
372
+ const presentationPhone = await generatePresentation([phonePayload], {
373
+ privateKeyToSignCredential: testCredentialVerificationKeyPair.privateKey,
374
+ });
375
+
376
+ await persistAccounts({
377
+ didKeyMetadatum: [
378
+ {
379
+ did: testUserDid,
380
+ },
381
+ ],
382
+ scope: [validScopes[0], validScopes[1]],
383
+ });
384
+
385
+ fastify.overrides.reqConfig = (config) => ({
386
+ ...config,
387
+ oauthVclCredentialVerificationPublicKey:
388
+ testCredentialVerificationKeyPair.publicKey,
389
+ holderAppServerAccessTokenSigningKey:
390
+ testAccessTokenSigningKeyPair.privateKey,
391
+ });
392
+
393
+ const response = await fastify.injectJson({
394
+ method: 'POST',
395
+ url: oauthUrl,
396
+ payload: {
397
+ grant_type: vnfPresentationGrantType,
398
+ client_id: 'abc',
399
+ scope: validScopes[0],
400
+ audience,
401
+ presentation: await presentationPhone.selfSign(),
402
+ },
403
+ });
404
+ expect(response.statusCode).toBe(200);
405
+ expect(response.json).toEqual({
406
+ access_token: expect.any(String),
407
+ token_type: 'Bearer',
408
+ refresh_token: expect.any(String),
409
+ });
410
+ expect(response.json.refresh_token).toHaveLength(128);
411
+
412
+ const accountDocument = await accountsRepo.findOne();
413
+
414
+ await accessTokenJwtMatcher(
415
+ response.json.access_token,
416
+ testAccessTokenSigningKeyPair.publicKey,
417
+ fastify.config.holderAppServerHolderAppProviderDid,
418
+ accountDocument._id.toString()
419
+ );
420
+
421
+ expect(accountDocument.refreshToken).toEqual(
422
+ hashAndEncodeHex(response.json.refresh_token)
423
+ );
424
+ });
425
+ it('should 200 with presentation grant type and proper credential when account has array did value', async () => {
426
+ const testCredentialVerificationKeyPair = generateKeyPair({
427
+ format: 'jwk',
428
+ });
429
+ const testAccessTokenSigningKeyPair = generateKeyPair();
430
+ const presentationPhone = await generatePresentation([phonePayload], {
431
+ privateKeyToSignCredential: testCredentialVerificationKeyPair.privateKey,
432
+ });
433
+
434
+ await persistAccounts({
435
+ didKeyMetadatum: [
436
+ {
437
+ did: testUserDid,
438
+ },
439
+ {
440
+ did: 'foo',
441
+ },
442
+ ],
443
+ scope: [validScopes[0], validScopes[1]],
444
+ });
445
+
446
+ fastify.overrides.reqConfig = (config) => ({
447
+ ...config,
448
+ oauthVclCredentialVerificationPublicKey:
449
+ testCredentialVerificationKeyPair.publicKey,
450
+ holderAppServerAccessTokenSigningKey:
451
+ testAccessTokenSigningKeyPair.privateKey,
452
+ });
453
+
454
+ const response = await fastify.injectJson({
455
+ method: 'POST',
456
+ url: oauthUrl,
457
+ payload: {
458
+ grant_type: vnfPresentationGrantType,
459
+ client_id: 'abc',
460
+ scope: validScopes[0],
461
+ audience,
462
+ presentation: await presentationPhone.selfSign(),
463
+ },
464
+ });
465
+ expect(response.statusCode).toBe(200);
466
+ expect(response.json).toEqual({
467
+ access_token: expect.any(String),
468
+ token_type: 'Bearer',
469
+ refresh_token: expect.any(String),
470
+ });
471
+ expect(response.json.refresh_token).toHaveLength(128);
472
+
473
+ const accountDocument = await accountsRepo.findOne();
474
+
475
+ await accessTokenJwtMatcher(
476
+ response.json.access_token,
477
+ testAccessTokenSigningKeyPair.publicKey,
478
+ fastify.config.holderAppServerHolderAppProviderDid,
479
+ accountDocument._id.toString()
480
+ );
481
+
482
+ expect(accountDocument.refreshToken).toEqual(
483
+ hashAndEncodeHex(response.json.refresh_token)
484
+ );
485
+ });
486
+ it('should 200 and update refresh_token with refresh_token grant type and known refresh_token', async () => {
487
+ const testAccessTokenSigningKeyPair = generateKeyPair();
488
+ fastify.overrides.reqConfig = (config) => ({
489
+ ...config,
490
+ holderAppServerAccessTokenSigningKey:
491
+ testAccessTokenSigningKeyPair.privateKey,
492
+ });
493
+
494
+ await persistAccounts({
495
+ did: testUserDid,
496
+ scope: [validScopes[0], validScopes[1]],
497
+ });
498
+
499
+ const response = await fastify.injectJson({
500
+ method: 'POST',
501
+ url: oauthUrl,
502
+ payload: {
503
+ grant_type: 'refresh_token',
504
+ client_id: 'abc',
505
+ scope: validScopes[0],
506
+ audience,
507
+ refresh_token: 'test_refresh_token',
508
+ },
509
+ });
510
+ expect(response.statusCode).toBe(200);
511
+ expect(response.json).toEqual({
512
+ access_token: expect.any(String),
513
+ token_type: 'Bearer',
514
+ refresh_token: expect.any(String),
515
+ });
516
+
517
+ const accountDocument = await accountsRepo.findOne();
518
+
519
+ await accessTokenJwtMatcher(
520
+ response.json.access_token,
521
+ testAccessTokenSigningKeyPair.publicKey,
522
+ fastify.config.holderAppServerHolderAppProviderDid,
523
+ accountDocument._id.toString()
524
+ );
525
+
526
+ expect(accountDocument.refreshToken).toEqual(
527
+ hashAndEncodeHex(response.json.refresh_token)
528
+ );
529
+ });
530
+
531
+ it('Should 401 when account`s not allowed scope (stored in DB) is requested', async () => {
532
+ const testAccessTokenSigningKeyPair = generateKeyPair();
533
+ fastify.overrides.reqConfig = (config) => ({
534
+ ...config,
535
+ holderAppServerAccessTokenSigningKey:
536
+ testAccessTokenSigningKeyPair.privateKey,
537
+ });
538
+
539
+ const initialScopeValue = [validScopes[0], validScopes[1]];
540
+ const requestedScope = `${validScopes[0]} ${validScopes[1]} ${validScopes[2]}`;
541
+
542
+ await persistAccounts({ did: testUserDid, scopes: initialScopeValue });
543
+
544
+ const response = await fastify.injectJson({
545
+ method: 'POST',
546
+ url: oauthUrl,
547
+ payload: {
548
+ grant_type: 'refresh_token',
549
+ client_id: 'abc',
550
+ scope: requestedScope,
551
+ audience,
552
+ refresh_token: 'test_refresh_token',
553
+ },
554
+ });
555
+
556
+ expect(response.statusCode).toBe(401);
557
+
558
+ expect(response.json).toEqual(
559
+ errorResponseMatcher({
560
+ statusCode: 401,
561
+ errorCode: 'missing_error_code',
562
+ message: 'Unknown refresh_token, user id or scope',
563
+ error: 'Unauthorized',
564
+ })
565
+ );
566
+ });
567
+
568
+ it('Should return 401 if incorrect refresh_token in request', async () => {
569
+ const testAccessTokenSigningKeyPair = generateKeyPair();
570
+ fastify.overrides.reqConfig = (config) => ({
571
+ ...config,
572
+ holderAppServerAccessTokenSigningKey:
573
+ testAccessTokenSigningKeyPair.privateKey,
574
+ });
575
+
576
+ await persistAccounts({ did: testUserDid });
577
+
578
+ const response = await fastify.injectJson({
579
+ method: 'POST',
580
+ url: oauthUrl,
581
+ payload: {
582
+ grant_type: 'refresh_token',
583
+ client_id: 'abc',
584
+ scope: validScopes[0],
585
+ audience,
586
+ refresh_token: 'incorrect_test_refresh_token',
587
+ },
588
+ });
589
+
590
+ expect(response.statusCode).toBe(401);
591
+
592
+ expect(response.json).toEqual(
593
+ errorResponseMatcher({
594
+ statusCode: 401,
595
+ errorCode: 'missing_error_code',
596
+ message: 'Unknown refresh_token, user id or scope',
597
+ error: 'Unauthorized',
598
+ })
599
+ );
600
+ });
601
+
602
+ it('Should return requested scope only in access_token', async () => {
603
+ const testAccessTokenSigningKeyPair = generateKeyPair();
604
+ fastify.overrides.reqConfig = (config) => ({
605
+ ...config,
606
+ holderAppServerAccessTokenSigningKey:
607
+ testAccessTokenSigningKeyPair.privateKey,
608
+ });
609
+
610
+ await persistAccounts({
611
+ did: testUserDid,
612
+ scope: [validScopes[0], validScopes[1]],
613
+ });
614
+
615
+ const response = await fastify.injectJson({
616
+ method: 'POST',
617
+ url: oauthUrl,
618
+ payload: {
619
+ grant_type: 'refresh_token',
620
+ client_id: 'abc',
621
+ scope: validScopes[1],
622
+ audience,
623
+ refresh_token: 'test_refresh_token',
624
+ },
625
+ });
626
+ expect(response.statusCode).toBe(200);
627
+
628
+ const { access_token: accessToken } = response.json;
629
+
630
+ const {
631
+ payload: { scope },
632
+ } = await jwtVerify(
633
+ accessToken,
634
+ jwkFromSecp256k1Key(testAccessTokenSigningKeyPair.publicKey, false)
635
+ );
636
+
637
+ expect(scope).toEqual(validScopes[1]);
638
+ });
639
+ });