@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,893 @@
1
+ const { mongoDb } = require('@spencejs/spence-mongo-repos');
2
+ const { ObjectId } = require('mongodb');
3
+ const { omit } = require('lodash/fp');
4
+ const nock = require('nock');
5
+ const { jwtVerify, jwkFromSecp256k1Key } = require('@velocitycareerlabs/jwt');
6
+ require('@velocitycareerlabs/tests-helpers');
7
+ const {
8
+ generateKeyPair,
9
+ hashAndEncodeHex,
10
+ decrypt,
11
+ } = require('@velocitycareerlabs/crypto');
12
+ const { ISO_DATETIME_FORMAT } = require('@velocitycareerlabs/test-regexes');
13
+ const { errorResponseMatcher } = require('@velocitycareerlabs/tests-helpers');
14
+ const { hexFromJwk } = require('@velocitycareerlabs/jwt');
15
+ const buildFastify = require('./helpers/careerwallet-build-fastify');
16
+ const initDevicesFactory = require('./factories/devices-factory');
17
+ const devicesRepoPlugin = require('../src/controllers/api/v0.6/devices/repo');
18
+ const {
19
+ accountsRepoPlugin,
20
+ accountScopes,
21
+ adminScopes,
22
+ } = require('../src/entities');
23
+ const { createAccessToken } = require('./helpers/access-token');
24
+ const accountsFactory = require('./factories/accounts-factory');
25
+ const personaFactory = require('./factories/persona-factory');
26
+
27
+ const idToken =
28
+ // eslint-disable-next-line max-len
29
+ 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IktFWSJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlzcyI6Imh0dHBzOi8vbG9jYWxob3N0LyIsImF1ZCI6ImZvbyIsInNjb3BlIjoicmVhZDp1c2VycyJ9.VwIIUqx9T-AxqbfL_FyNRAeOxTwiC2JpcwtrqnEWN3DdF07ijUkF1WYy8Ahfr_p4R3KnoPbiefZnIbVANCt-lt0ej32rfil2yHhQEsvFxSOjcrx6ARmPp0YAfWlN-5Sotzkxy29jaOZMEDkmRFZg3jkdC7wosPW_S6M-olC4g3HHfylpZI8O3Jdd87Qr9wD_QtUzANwnPbl2Q-9NEyxVjAZIWg_HWK9JAAaf_2IY5VwHBvyp0oeQSEHKi4hogcM59EOc4FxdR5WH45B_PenVa6W4mHFBkH8sAXxt2Zs9s2efujkfWYfyXvgL_lN7vT-TEADlAPP2L6CpWpDISOMsQWUSgGHcN_KwRh_E7qJwahR6mv4QHY6ReEoyjkmSS3swrD1l74jNs7QLAdsMywvzCMDsHabs7DYcEMGQBdP14PJ_ucLFnkivZeBDAc6sS445ocbyrpyO40XMaMorD5khRd9ej89SxR7d_v0W6Ne2Nn4XgW3pAZzu5Rdc4JvqfzLFxkp95jxy1MTAddjWISPmNOYYyXHM9SSqSpqVECOFS0f4z2zycHRqXUcOytWrvED6VGo9x7-IVCgu8vFzj0zToIWQmsDs3UoH9RnV12z0PMwGXQzca1lT_zGwJxBF3e4zJjmcJ05OMF2JgZ2_G48O3M4Dtb0jlgWbKLd0kWlIFzQ';
30
+ const userIdForIdToken = '1234567890';
31
+
32
+ const testDID = 'test_did';
33
+
34
+ const testDidKeyMetadatum = {
35
+ publicJwk: {
36
+ crv: 'secp256k1',
37
+ kty: 'EC',
38
+ use: 'sig',
39
+ x: 'Lm972sWcSp9dMzkbsXvbUZ7-E24mreaZRIXAR9rIG94',
40
+ y: 'jbSpGUpx0g4ZrNS2heAuwDqmr-F2JIWvK29l-6vJBKA',
41
+ },
42
+ keyId: 'keyId',
43
+ kid: `${testDID}#0`,
44
+ did: testDID,
45
+ };
46
+
47
+ const emulateGenerateDid = (accountsRepo, id) => {
48
+ return accountsRepo.update(id, {
49
+ did: testDID,
50
+ didKeyMetadatum: [
51
+ {
52
+ ...testDidKeyMetadatum,
53
+ },
54
+ ],
55
+ });
56
+ };
57
+
58
+ describe('Accounts controller Test Suite', () => {
59
+ let accountsRepo;
60
+ let fastify;
61
+ let persistDevices;
62
+ let devicesRepo;
63
+ let privateKey;
64
+ let persistAccounts;
65
+ let persistPersona;
66
+ let publicKey;
67
+
68
+ const deviceId = 'test-device-id';
69
+
70
+ beforeAll(async () => {
71
+ ({ publicKey, privateKey } = generateKeyPair({ format: 'jwk' }));
72
+ fastify = buildFastify({
73
+ holderAppServerAccessTokenPublicKey: publicKey,
74
+ holderAppServerAccessTokenSigningKey: hexFromJwk(privateKey),
75
+ });
76
+ await fastify.ready();
77
+
78
+ ({ persistDevices } = initDevicesFactory(fastify));
79
+
80
+ accountsRepo = await accountsRepoPlugin(fastify)({
81
+ log: fastify.log,
82
+ config: fastify.config,
83
+ });
84
+
85
+ devicesRepo = devicesRepoPlugin(fastify)({
86
+ log: fastify.log,
87
+ config: fastify.config,
88
+ });
89
+ ({ persistAccounts } = accountsFactory(fastify));
90
+ ({ persistPersona } = personaFactory(fastify));
91
+ });
92
+
93
+ afterAll(async () => {
94
+ await mongoDb().collection('devices').deleteMany({});
95
+ await mongoDb().collection('personas').deleteMany({});
96
+ await mongoDb().collection('accounts').deleteMany({});
97
+ await fastify.close();
98
+ });
99
+
100
+ beforeEach(async () => {
101
+ await mongoDb().collection('devices').deleteMany({});
102
+ await mongoDb().collection('personas').deleteMany({});
103
+ await mongoDb().collection('accounts').deleteMany({});
104
+ });
105
+
106
+ describe('POST /accounts endpoint - Create account', () => {
107
+ describe('Account creation with device provided', () => {
108
+ it('Successful account creation scenario', async () => {
109
+ const device = await persistDevices({ deviceId });
110
+ const response = await fastify.injectJson({
111
+ method: 'POST',
112
+ url: '/api/v0.6/accounts',
113
+ payload: {
114
+ account: {
115
+ accountType: 'temp',
116
+ device: {
117
+ deviceId,
118
+ deviceType: 'phone',
119
+ deviceOS: 'ios',
120
+ },
121
+ },
122
+ },
123
+ });
124
+ expect(response.statusCode).toEqual(200);
125
+
126
+ expect(response.json).toEqual({
127
+ account: {
128
+ id: expect.any(String),
129
+ accountType: 'temp',
130
+ didKeyMetadatum: [],
131
+ devices: [expectedDevice(device)],
132
+ createdAt: expect.stringMatching(ISO_DATETIME_FORMAT),
133
+ updatedAt: expect.stringMatching(ISO_DATETIME_FORMAT),
134
+ },
135
+ access_token: expect.any(String),
136
+ refresh_token: expect.any(String),
137
+ });
138
+
139
+ const accountDevice = await devicesRepo.findOne({
140
+ filter: { deviceId },
141
+ });
142
+
143
+ const accountInfoFromDb = await accountsRepo.findOne({
144
+ filter: { _id: accountDevice.accountId },
145
+ });
146
+
147
+ expect({
148
+ ...accountInfoFromDb,
149
+ scope: accountInfoFromDb.scope.join(' '),
150
+ createdAt: accountInfoFromDb.createdAt.toISOString(),
151
+ updatedAt: accountInfoFromDb.updatedAt.toISOString(),
152
+ }).toEqual({
153
+ _id: expect.any(ObjectId),
154
+ accountType: response.json.account.accountType,
155
+ scope: 'key:create',
156
+ createdAt: response.json.account.createdAt,
157
+ updatedAt: response.json.account.updatedAt,
158
+ refreshToken: hashAndEncodeHex(response.json.refresh_token),
159
+ didKeyMetadatum: response.json.account.didKeyMetadatum,
160
+ });
161
+ });
162
+
163
+ it('Successful account creation with deviceId as push token scenario', async () => {
164
+ const persistedDevice = await persistDevices({ deviceId });
165
+ const response = await fastify.injectJson({
166
+ method: 'POST',
167
+ url: '/api/v0.6/accounts',
168
+ payload: {
169
+ account: {
170
+ accountType: 'temp',
171
+ device: {
172
+ deviceId: persistedDevice.pushToken,
173
+ deviceType: 'phone',
174
+ deviceOS: 'ios',
175
+ },
176
+ },
177
+ },
178
+ });
179
+ expect(response.statusCode).toEqual(200);
180
+
181
+ expect(response.json).toEqual({
182
+ account: {
183
+ id: expect.any(String),
184
+ accountType: 'temp',
185
+ devices: [expectedDevice(persistedDevice)],
186
+ didKeyMetadatum: [],
187
+ createdAt: expect.stringMatching(ISO_DATETIME_FORMAT),
188
+ updatedAt: expect.stringMatching(ISO_DATETIME_FORMAT),
189
+ },
190
+ access_token: expect.any(String),
191
+ refresh_token: expect.any(String),
192
+ });
193
+
194
+ const accountDevice = await devicesRepo.findOne({
195
+ filter: { pushToken: persistedDevice.pushToken },
196
+ });
197
+
198
+ const accountInfoFromDb = await accountsRepo.findOne({
199
+ filter: { _id: accountDevice.accountId },
200
+ });
201
+
202
+ expect({
203
+ ...accountInfoFromDb,
204
+ scope: accountInfoFromDb.scope.join(' '),
205
+ createdAt: accountInfoFromDb.createdAt.toISOString(),
206
+ updatedAt: accountInfoFromDb.updatedAt.toISOString(),
207
+ }).toEqual({
208
+ _id: expect.any(ObjectId),
209
+ accountType: response.json.account.accountType,
210
+ scope: 'key:create',
211
+ createdAt: response.json.account.createdAt,
212
+ updatedAt: response.json.account.updatedAt,
213
+ refreshToken: hashAndEncodeHex(response.json.refresh_token),
214
+ didKeyMetadatum: response.json.account.didKeyMetadatum,
215
+ });
216
+ });
217
+
218
+ it('Access token should have scope key:create', async () => {
219
+ const testAccessTokenSigningKeyPair = generateKeyPair();
220
+
221
+ fastify.overrides.reqConfig = (config) => ({
222
+ ...config,
223
+ holderAppServerAccessTokenSigningKey:
224
+ testAccessTokenSigningKeyPair.privateKey,
225
+ });
226
+
227
+ const response = await fastify.injectJson({
228
+ method: 'POST',
229
+ url: '/api/v0.6/accounts',
230
+ payload: {
231
+ account: {
232
+ accountType: 'temp',
233
+ device: {
234
+ deviceId,
235
+ deviceType: 'phone',
236
+ deviceOS: 'ios',
237
+ },
238
+ },
239
+ },
240
+ });
241
+
242
+ const { access_token: accessToken } = response.json;
243
+
244
+ const {
245
+ payload: { scope },
246
+ } = await jwtVerify(
247
+ accessToken,
248
+ jwkFromSecp256k1Key(testAccessTokenSigningKeyPair.publicKey, false)
249
+ );
250
+
251
+ expect(scope).toEqual('key:create');
252
+ });
253
+
254
+ it('Access token should have sub (account Id) in header equals account id in DB', async () => {
255
+ await persistDevices({ deviceId });
256
+ const testAccessTokenSigningKeyPair = generateKeyPair();
257
+
258
+ fastify.overrides.reqConfig = (config) => ({
259
+ ...config,
260
+ holderAppServerAccessTokenSigningKey:
261
+ testAccessTokenSigningKeyPair.privateKey,
262
+ });
263
+
264
+ const response = await fastify.injectJson({
265
+ method: 'POST',
266
+ url: '/api/v0.6/accounts',
267
+ payload: {
268
+ account: {
269
+ accountType: 'temp',
270
+ device: {
271
+ deviceId,
272
+ deviceType: 'phone',
273
+ deviceOS: 'ios',
274
+ },
275
+ },
276
+ },
277
+ });
278
+
279
+ const { access_token: accessToken } = response.json;
280
+
281
+ const {
282
+ payload: { sub },
283
+ } = await jwtVerify(
284
+ accessToken,
285
+ jwkFromSecp256k1Key(testAccessTokenSigningKeyPair.publicKey, false)
286
+ );
287
+
288
+ const connectedDevice = await devicesRepo.findOne({
289
+ filter: { deviceId },
290
+ });
291
+
292
+ const accountInfoFromDb = await accountsRepo.findOne({
293
+ filter: { _id: connectedDevice.accountId },
294
+ });
295
+
296
+ expect(sub).toEqual(accountInfoFromDb._id.toString());
297
+ });
298
+
299
+ it('Should return 400 if request is incorrect - wrong accountType', async () => {
300
+ const response = await fastify.injectJson({
301
+ method: 'POST',
302
+ url: '/api/v0.6/accounts',
303
+ payload: {
304
+ account: {
305
+ accountType: 'incorrect_account_type',
306
+ device: {
307
+ deviceId,
308
+ deviceType: 'phone',
309
+ deviceOS: 'ios',
310
+ },
311
+ },
312
+ },
313
+ });
314
+ expect(response.statusCode).toEqual(400);
315
+ });
316
+
317
+ it('Should return 400 if id_token and device are both missing', async () => {
318
+ const response = await fastify.injectJson({
319
+ method: 'POST',
320
+ url: '/api/v0.6/accounts',
321
+ payload: {
322
+ account: {
323
+ accountType: 'temp',
324
+ },
325
+ },
326
+ });
327
+
328
+ expect(response.statusCode).toEqual(400);
329
+ });
330
+ });
331
+
332
+ describe('Account creation with id_token provided', () => {
333
+ beforeEach(() => {
334
+ nock.cleanAll();
335
+ nock('https://localhost/')
336
+ .get('/.well-known/jwks.json')
337
+ .reply(200, jwks);
338
+ });
339
+
340
+ afterAll(() => {
341
+ nock.cleanAll();
342
+ nock.restore();
343
+ });
344
+
345
+ it('Successful account creation scenario', async () => {
346
+ const response = await fastify.injectJson({
347
+ method: 'POST',
348
+ url: '/api/v0.6/accounts',
349
+ payload: {
350
+ account: {
351
+ accountType: 'temp',
352
+ id_token: idToken,
353
+ },
354
+ },
355
+ });
356
+
357
+ expect(response.statusCode).toEqual(200);
358
+
359
+ expect(response.json).toEqual({
360
+ account: {
361
+ id: expect.any(String),
362
+ accountType: 'temp',
363
+ id_token_iss: fastify.config.auth0Domain,
364
+ id_token_sub: userIdForIdToken,
365
+ createdAt: expect.stringMatching(ISO_DATETIME_FORMAT),
366
+ updatedAt: expect.stringMatching(ISO_DATETIME_FORMAT),
367
+ didKeyMetadatum: expect.arrayContaining([]),
368
+ },
369
+ access_token: expect.any(String),
370
+ refresh_token: expect.any(String),
371
+ });
372
+
373
+ const accountInfoFromDb = await accountsRepo.findOne({
374
+ filter: { userId: userIdForIdToken },
375
+ });
376
+
377
+ expect({
378
+ ...accountInfoFromDb,
379
+ scope: accountInfoFromDb.scope.join(' '),
380
+ createdAt: accountInfoFromDb.createdAt.toISOString(),
381
+ updatedAt: accountInfoFromDb.updatedAt.toISOString(),
382
+ idTokenClaims: JSON.parse(
383
+ decrypt(accountInfoFromDb.idTokenClaims, fastify.config.mongoSecret)
384
+ ),
385
+ }).toEqual({
386
+ _id: expect.any(ObjectId),
387
+ accountType: response.json.account.accountType,
388
+ userId: response.json.account.id_token_sub,
389
+ scope: 'key:create',
390
+ createdAt: response.json.account.createdAt,
391
+ updatedAt: response.json.account.updatedAt,
392
+ refreshToken: hashAndEncodeHex(response.json.refresh_token),
393
+ idTokenClaims: {
394
+ idTokenIssuer: fastify.config.auth0Domain,
395
+ },
396
+ didKeyMetadatum: response.json.account.didKeyMetadatum,
397
+ });
398
+ });
399
+
400
+ it('Shouldn`t create new account if already exists', async () => {
401
+ const account = await accountsRepo.insert({
402
+ accountType: 'temp',
403
+ userId: userIdForIdToken,
404
+ didKeyMetadatum: [],
405
+ });
406
+
407
+ const response = await fastify.injectJson({
408
+ method: 'POST',
409
+ url: '/api/v0.6/accounts',
410
+ payload: {
411
+ account: {
412
+ accountType: 'temp',
413
+ id_token: idToken,
414
+ },
415
+ },
416
+ });
417
+
418
+ expect(response.statusCode).toEqual(200);
419
+
420
+ expect(response.json.account.createdAt).toEqual(
421
+ account.createdAt.toISOString()
422
+ );
423
+ expect(response.json.account.updatedAt).toEqual(
424
+ account.updatedAt.toISOString()
425
+ );
426
+
427
+ expect(await accountsRepo.count({})).toEqual(1);
428
+ });
429
+
430
+ it('Should have didKeyMetadatum in response next time after generation of did', async () => {
431
+ const response = await fastify.injectJson({
432
+ method: 'POST',
433
+ url: '/api/v0.6/accounts',
434
+ payload: {
435
+ account: {
436
+ accountType: 'temp',
437
+ id_token: idToken,
438
+ },
439
+ },
440
+ });
441
+
442
+ await emulateGenerateDid(accountsRepo, response.json.account.id);
443
+
444
+ const updatedResponse = await fastify.injectJson({
445
+ method: 'POST',
446
+ url: '/api/v0.6/accounts',
447
+ payload: {
448
+ account: {
449
+ accountType: 'temp',
450
+ id_token: idToken,
451
+ },
452
+ },
453
+ });
454
+
455
+ expect(updatedResponse.statusCode).toEqual(200);
456
+
457
+ expect(updatedResponse.json.account.didKeyMetadatum[0].did).toEqual(
458
+ 'test_did'
459
+ );
460
+ });
461
+
462
+ it('Should return 401 if token audience is different', async () => {
463
+ fastify.overrides.reqConfig = (config) => ({
464
+ ...config,
465
+ oauth0WebappClientId: 'another-audience',
466
+ });
467
+
468
+ const response = await fastify.injectJson({
469
+ method: 'POST',
470
+ url: '/api/v0.6/accounts',
471
+ payload: {
472
+ account: {
473
+ accountType: 'temp',
474
+ id_token: idToken,
475
+ },
476
+ },
477
+ });
478
+
479
+ expect(response.statusCode).toEqual(401);
480
+ });
481
+ });
482
+
483
+ describe('Account creation with personaEmail body param provided', () => {
484
+ it('Should return 200 and did metadatum of the related Test Persona if did exists', async () => {
485
+ const device = await persistDevices({ deviceId });
486
+
487
+ const didKeyMetadatum = {
488
+ did: 'did:example:123456789abcdefghi',
489
+ kid: 'did:example:123456789abcdefghi#0',
490
+ keyId: '65ba702ac8670c687d3b066f',
491
+ publicJwk: {
492
+ crv: 'secp256k1',
493
+ kty: 'EC',
494
+ x: 'e907f5476c57e73423f1c9b393bc1d0a4d56280ac155396cc879f2441a523cdc',
495
+ y: '89d4dcc86d540c6eea39d178eedc30213e782f251e03b76f4273106808dd0d4f',
496
+ },
497
+ };
498
+
499
+ const email = 'olivia.hafez@example.com';
500
+ await persistPersona({ email, ...didKeyMetadatum });
501
+
502
+ const response = await fastify.injectJson({
503
+ method: 'POST',
504
+ url: '/api/v0.6/accounts',
505
+ payload: {
506
+ account: {
507
+ accountType: 'temp',
508
+ device: {
509
+ deviceId,
510
+ deviceType: 'phone',
511
+ deviceOS: 'ios',
512
+ },
513
+ },
514
+ personaEmail: email,
515
+ },
516
+ });
517
+ expect(response.statusCode).toEqual(200);
518
+
519
+ expect(response.json).toEqual({
520
+ account: {
521
+ id: expect.any(String),
522
+ accountType: 'temp',
523
+ devices: [expectedDevice(device)],
524
+ didKeyMetadatum: [didKeyMetadatum],
525
+ createdAt: expect.stringMatching(ISO_DATETIME_FORMAT),
526
+ updatedAt: expect.stringMatching(ISO_DATETIME_FORMAT),
527
+ },
528
+ access_token: expect.any(String),
529
+ refresh_token: expect.any(String),
530
+ });
531
+
532
+ const accountInfoFromDb = await accountsRepo.findOne({
533
+ filter: { _id: response.json.account.id },
534
+ });
535
+
536
+ expect({
537
+ ...accountInfoFromDb,
538
+ createdAt: accountInfoFromDb.createdAt.toISOString(),
539
+ updatedAt: accountInfoFromDb.updatedAt.toISOString(),
540
+ }).toEqual({
541
+ _id: expect.any(ObjectId),
542
+ accountType: response.json.account.accountType,
543
+ scope: ['jwt', 'key:create', 'inbox', 'account'],
544
+ refreshToken: hashAndEncodeHex(response.json.refresh_token),
545
+ didKeyMetadatum: [didKeyMetadatum],
546
+ createdAt: response.json.account.createdAt,
547
+ updatedAt: response.json.account.updatedAt,
548
+ });
549
+ });
550
+ });
551
+
552
+ it('Should return 200 and empty metadatum array if Persona doesn`t have did', async () => {
553
+ const device = await persistDevices({ deviceId });
554
+
555
+ const email = 'carmen.johnson@example.com';
556
+ await persistPersona({ email });
557
+
558
+ const response = await fastify.injectJson({
559
+ method: 'POST',
560
+ url: '/api/v0.6/accounts',
561
+ payload: {
562
+ account: {
563
+ accountType: 'temp',
564
+ device: {
565
+ deviceId,
566
+ deviceType: 'phone',
567
+ deviceOS: 'ios',
568
+ },
569
+ },
570
+ personaEmail: email,
571
+ },
572
+ });
573
+ expect(response.statusCode).toEqual(200);
574
+
575
+ expect(response.json).toEqual({
576
+ account: {
577
+ id: expect.any(String),
578
+ accountType: 'temp',
579
+ devices: [expectedDevice(device)],
580
+ didKeyMetadatum: [],
581
+ createdAt: expect.stringMatching(ISO_DATETIME_FORMAT),
582
+ updatedAt: expect.stringMatching(ISO_DATETIME_FORMAT),
583
+ },
584
+ access_token: expect.any(String),
585
+ refresh_token: expect.any(String),
586
+ });
587
+
588
+ const accountInfoFromDb = await accountsRepo.findOne({
589
+ filter: { _id: response.json.account.id },
590
+ });
591
+
592
+ expect({
593
+ ...accountInfoFromDb,
594
+ createdAt: accountInfoFromDb.createdAt.toISOString(),
595
+ updatedAt: accountInfoFromDb.updatedAt.toISOString(),
596
+ }).toEqual({
597
+ _id: expect.any(ObjectId),
598
+ accountType: response.json.account.accountType,
599
+ scope: ['key:create'],
600
+ refreshToken: hashAndEncodeHex(response.json.refresh_token),
601
+ didKeyMetadatum: [],
602
+ createdAt: response.json.account.createdAt,
603
+ updatedAt: response.json.account.updatedAt,
604
+ });
605
+ });
606
+
607
+ it('Should return 400 if invalid Persona email specified', async () => {
608
+ await persistDevices({ deviceId });
609
+ await persistPersona({ email: 'olivia.hafez@example.com' });
610
+
611
+ const response = await fastify.injectJson({
612
+ method: 'POST',
613
+ url: '/api/v0.6/accounts',
614
+ payload: {
615
+ account: {
616
+ accountType: 'temp',
617
+ device: {
618
+ deviceId,
619
+ deviceType: 'phone',
620
+ deviceOS: 'ios',
621
+ },
622
+ },
623
+ personaEmail: 'wrong.email@example.com',
624
+ },
625
+ });
626
+ expect(response.statusCode).toEqual(400);
627
+
628
+ expect(response.json).toEqual(
629
+ errorResponseMatcher({
630
+ error: 'Bad Request',
631
+ errorCode: 'missing_error_code',
632
+ message: 'Invalid persona email',
633
+ statusCode: 400,
634
+ })
635
+ );
636
+ });
637
+ });
638
+ describe('GET Account endpoint', () => {
639
+ let serverClientAccessToken;
640
+
641
+ beforeAll(async () => {
642
+ serverClientAccessToken = await createAccessToken(
643
+ adminScopes.adminClient,
644
+ privateKey,
645
+ 'https://webwallet.velocitycareerlabs.com/'
646
+ );
647
+ });
648
+
649
+ it('Successful get account scenario with empty didKeyMetadatum', async () => {
650
+ const account = await persistAccounts();
651
+ const accountId = account._id;
652
+
653
+ const accessToken = await createAccessToken(
654
+ accountScopes.keyCreate,
655
+ privateKey,
656
+ accountId
657
+ );
658
+ const response = await fastify.injectJson({
659
+ method: 'GET',
660
+ url: `/api/v0.6/accounts/${accountId}`,
661
+ headers: {
662
+ authorization: `Bearer ${accessToken}`,
663
+ },
664
+ });
665
+
666
+ expect(response.statusCode).toEqual(200);
667
+
668
+ expect(response.json).toEqual({
669
+ account: {
670
+ id: expect.any(String),
671
+ accountType: 'temp',
672
+ devices: [],
673
+ didKeyMetadatum: [],
674
+ createdAt: expect.stringMatching(ISO_DATETIME_FORMAT),
675
+ updatedAt: expect.stringMatching(ISO_DATETIME_FORMAT),
676
+ },
677
+ });
678
+ });
679
+
680
+ it('Successful get account scenario with non empty didKeyMetadatum', async () => {
681
+ const account = await persistAccounts({
682
+ didKeyMetadatum: [testDidKeyMetadatum],
683
+ });
684
+ const device = await persistDevices({
685
+ accountId: new ObjectId(account._id),
686
+ });
687
+
688
+ const accessToken = await createAccessToken(
689
+ accountScopes.keyCreate,
690
+ privateKey,
691
+ account._id
692
+ );
693
+ const response = await fastify.injectJson({
694
+ method: 'GET',
695
+ url: `/api/v0.6/accounts/${account._id}`,
696
+ headers: {
697
+ authorization: `Bearer ${accessToken}`,
698
+ },
699
+ });
700
+
701
+ expect(response.statusCode).toEqual(200);
702
+
703
+ expect(response.json).toEqual({
704
+ account: {
705
+ id: expect.any(String),
706
+ accountType: 'temp',
707
+ devices: [expectedDevice(device)],
708
+ didKeyMetadatum: [testDidKeyMetadatum],
709
+ createdAt: expect.stringMatching(ISO_DATETIME_FORMAT),
710
+ updatedAt: expect.stringMatching(ISO_DATETIME_FORMAT),
711
+ },
712
+ });
713
+ });
714
+
715
+ it('if the access token account does not match the accountId then return 403', async () => {
716
+ const idForAnotherUser = 'idForAnotherUser';
717
+ const account = await persistAccounts();
718
+ const accountId = account._id;
719
+
720
+ const accessToken = await createAccessToken(
721
+ accountScopes.keyCreate,
722
+ privateKey,
723
+ accountId
724
+ );
725
+
726
+ const response = await fastify.injectJson({
727
+ method: 'GET',
728
+ url: `/api/v0.6/accounts/${idForAnotherUser}`,
729
+ headers: {
730
+ authorization: `Bearer ${accessToken}`,
731
+ },
732
+ });
733
+
734
+ expect(response.statusCode).toEqual(403);
735
+
736
+ expect(response.json).toStrictEqual(
737
+ errorResponseMatcher({
738
+ error: 'Forbidden',
739
+ message: 'Access token account does not match the accountId',
740
+ errorCode: 'missing_error_code',
741
+ statusCode: 403,
742
+ })
743
+ );
744
+ });
745
+
746
+ it('Successful get account if requested by the server client with valid M2M token', async () => {
747
+ const account = await persistAccounts();
748
+ const accountId = account._id;
749
+
750
+ const response = await fastify.injectJson({
751
+ method: 'GET',
752
+ url: `/api/v0.6/accounts/${accountId}`,
753
+ headers: {
754
+ authorization: `Bearer ${serverClientAccessToken}`,
755
+ },
756
+ });
757
+
758
+ expect(response.statusCode).toEqual(200);
759
+
760
+ expect(response.json).toEqual({
761
+ account: {
762
+ id: expect.any(String),
763
+ accountType: 'temp',
764
+ devices: [],
765
+ didKeyMetadatum: [],
766
+ createdAt: expect.stringMatching(ISO_DATETIME_FORMAT),
767
+ updatedAt: expect.stringMatching(ISO_DATETIME_FORMAT),
768
+ },
769
+ });
770
+ });
771
+ });
772
+
773
+ describe('POST /accounts/:accountId/push_token endpoint - Create push token', () => {
774
+ describe('Push token creation for DID', () => {
775
+ it('Successful token creation scenario', async () => {
776
+ const account = await persistAccounts();
777
+ const accountId = account._id;
778
+
779
+ const accessToken = await createAccessToken(
780
+ accountScopes.keyCreate,
781
+ privateKey,
782
+ accountId
783
+ );
784
+ const response = await fastify.injectJson({
785
+ method: 'POST',
786
+ url: `/api/v0.6/accounts/${accountId}/push_token`,
787
+ payload: {
788
+ did: testDID,
789
+ },
790
+ headers: {
791
+ authorization: `Bearer ${accessToken}`,
792
+ },
793
+ });
794
+
795
+ expect(response.statusCode).toEqual(200);
796
+
797
+ const filteredAccount = await accountsRepo.findOne({
798
+ [`pushTokens.${response.pushToken}`]: { $exists: true },
799
+ });
800
+ expect(filteredAccount).toBeDefined();
801
+ const { pushToken } = response.json;
802
+ expect(filteredAccount.pushTokens[pushToken].did).toEqual(testDID);
803
+ });
804
+
805
+ it('Unauthorized token creation scenario', async () => {
806
+ const account = await persistAccounts();
807
+ const accountId = account._id;
808
+ const response = await fastify.injectJson({
809
+ method: 'POST',
810
+ url: `/api/v0.6/accounts/${accountId}/push_token`,
811
+ payload: {
812
+ did: testDID,
813
+ },
814
+ headers: {
815
+ authorization: 'Bearer invalid-token',
816
+ },
817
+ });
818
+
819
+ expect(response.statusCode).toEqual(401);
820
+ });
821
+
822
+ it('Unauthorized userId creation scenario', async () => {
823
+ const account = await persistAccounts();
824
+ const wrongAccountId = 'another_user_id';
825
+ const accessToken = await createAccessToken(
826
+ accountScopes.keyCreate,
827
+ privateKey,
828
+ account._id
829
+ );
830
+ const response = await fastify.injectJson({
831
+ method: 'POST',
832
+ url: `/api/v0.6/accounts/${wrongAccountId}/push_token`,
833
+ payload: {
834
+ did: testDID,
835
+ },
836
+ headers: {
837
+ authorization: `Bearer ${accessToken}`,
838
+ },
839
+ });
840
+
841
+ expect(response.statusCode).toEqual(401);
842
+ });
843
+ });
844
+ });
845
+ });
846
+
847
+ const jwks = {
848
+ keys: [
849
+ {
850
+ alg: 'RS512',
851
+ kid: 'KEY',
852
+ x5c: ['UNUSED'],
853
+ },
854
+ {
855
+ alg: 'RS256',
856
+ kid: 'KEY',
857
+ x5c: [
858
+ `
859
+ MIIEnjCCAoYCCQDmnGII6qzGlTANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZ1
860
+ bnVzZWQwHhcNMjEwOTE5MDcxODQ2WhcNMjExMDE5MDcxODQ2WjARMQ8wDQYDVQQD
861
+ DAZ1bnVzZWQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDY2acJ8JAH
862
+ XjK8j3sAXOokSWwvaEg65UJS0C7KdnfbLTeaeYFHBRY0v9Jkk/PJSXv9hMWw1aD3
863
+ n7NrhVwXeRXi/7VZuW/S4ek+hK+IMDvpKqzn+XeCpaMoRpAgloADeNY0qhYKxpr2
864
+ L0SmRQDwVy1r/g31ECewD2WpEiRSmXsQ2Q2uYT3V60BmbhUw31KGEr4SLXL9pzmb
865
+ arOH/5Rhqg+YFMywNY6i01S3UdOlUtAyWT/mVRAkVTsUEou9tBw61zputPdMrl7p
866
+ d4InmlfCmXNTPFh9EDczPQiAVPq8MDyEdRGP134+HM9+YgQUZjy+WsxmGEvplJIf
867
+ KrYtlWe11//oAXC3690LUYtvg49lNY4+H0/nngjnCDXkZo6f+PEvnBZfYl596VTV
868
+ Fx4FGNOqLwg4bAyE5j5jXtEGW1oKo1pxBg7Oe3MteQUDwMrONB3CbxdxDiN3YH94
869
+ 2nWGW9Le+CeA1QUhfjQqSoZRJURGYGoztVuIXOElnkrgwcJreX4b8y+Uo5kpp2By
870
+ 6UUaD/mMj9XQ+Ygp/J8DlJlqDXOIp6JUJ75aSK5ZIjRtWq/Go5RUjW9IW0ldEehh
871
+ /4j0ZWC0vR1/le2UmAE6tXhkH4sdx9JM84V+qRzjiGqQx3Wn00uwMiHHhte17t41
872
+ vk+b75wuHbfiq9R8irL6wqWeeuzvCC37NwIDAQABMA0GCSqGSIb3DQEBCwUAA4IC
873
+ AQCZOT6S5HLUp0gBtWK6Fxyzb9lWPE+AJipjJ80lS3OnaOIyVtyJexJ2BjTKWldJ
874
+ 48zkzLNIRsTEGEipNS6NkrkElfmoaNBpdbDch2WBkME3UYlFIR9GgbXPMlACQlwJ
875
+ f4qT3iIZ9zjpVMP8F63TzRRr7KEYW2PGHEtVQktMPprGEfU4Sz0OIa9RRV+BLsfh
876
+ x8he2pimJEzoEaWPgyJXV1S+tLUif8A/CEkZVRZ2vADA7WMGl2ZTdbmsTjXh4bf2
877
+ A4l4Zec+jwOjUPiEk5lLJwv1KeYos9wuUczAk7ku8wRzyZbrjwgRam9VQA5qmRzJ
878
+ PegEQwJaKMRu8PPK0L4KFN4v3ma7Ux6D719nko8mZ0kA2oUs6phsFmoWQfsbRbsD
879
+ CdUGnM2fPp6V285r9w9Y6+1nVdtJpbAPFJR3SxIpfYVfx3tI6C3BR9bIMr8uCf81
880
+ G+Ebvo4qTuY6Cg/mTpPLZ4cKpwSvB6cE+xeSHvKIRYm6QUYEReRxQ3b4aUKBSNwl
881
+ FEQufVGhGzeblNC3fjP+mMtqbyC8c1zkHc6tjJYO5yesKf8bjO71by2jgSced7nN
882
+ 5JvJawfEcabgHJ1aAFLj0tlPMrViFzu6y8/A5aZLc5UMISXAZXfB4wIEdyUUXJh4
883
+ rJKE/ZCb2+W8g29N5cv2P6nhahT3mYatMiQ0U/gfaIrA0A==
884
+ `.trim(),
885
+ ],
886
+ },
887
+ ],
888
+ };
889
+
890
+ const expectedDevice = (device) => ({
891
+ ...omit(['_id', 'pushToken', 'accountId'], device),
892
+ updatedAt: expect.stringMatching(ISO_DATETIME_FORMAT),
893
+ });