@velocitycareerlabs/server-webwallet 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 (193) hide show
  1. package/.localdev.env +57 -0
  2. package/.standalone.env +22 -0
  3. package/LICENSE +248 -0
  4. package/README.md +11 -0
  5. package/jest.config.js +20 -0
  6. package/migrate-mongo.config.js +23 -0
  7. package/package.json +72 -0
  8. package/src/config/config.js +74 -0
  9. package/src/controllers/accounts/autohooks.js +28 -0
  10. package/src/controllers/accounts/controller.js +209 -0
  11. package/src/controllers/accounts/schemas/index.js +6 -0
  12. package/src/controllers/accounts/schemas/webwallet-accounts-request.schema.js +25 -0
  13. package/src/controllers/accounts/schemas/webwallet-accounts-response.schema.js +72 -0
  14. package/src/controllers/accounts/schemas/webwallet-accounts-update-request.schema.js +23 -0
  15. package/src/controllers/accounts/schemas/webwallet-test-personas-response.schema.js +110 -0
  16. package/src/controllers/consent/autohooks.js +14 -0
  17. package/src/controllers/consent/controller.js +91 -0
  18. package/src/controllers/consent/schemas/index.js +3 -0
  19. package/src/controllers/consent/schemas/webwallet-consent-response.schema.js +23 -0
  20. package/src/controllers/credentials/autohooks.js +36 -0
  21. package/src/controllers/credentials/controller.js +92 -0
  22. package/src/controllers/credentials/schemas/index.js +4 -0
  23. package/src/controllers/credentials/schemas/webwallet-credential-categories-response.schema.js +21 -0
  24. package/src/controllers/credentials/schemas/webwallet-credentials-response.schema.js +29 -0
  25. package/src/controllers/disclosures/autohooks.js +21 -0
  26. package/src/controllers/disclosures/controller.js +168 -0
  27. package/src/controllers/disclosures/schemas/index.js +7 -0
  28. package/src/controllers/disclosures/schemas/webwallet-accept-presentation-request-body.schema.js +28 -0
  29. package/src/controllers/disclosures/schemas/webwallet-accept-presentation-response.schema.js +16 -0
  30. package/src/controllers/disclosures/schemas/webwallet-get-disclosures-response.schema.js +18 -0
  31. package/src/controllers/disclosures/schemas/webwallet-get-presentation-request-query.schema.js +21 -0
  32. package/src/controllers/disclosures/schemas/webwallet-get-presentation-request-response.schema.js +16 -0
  33. package/src/controllers/feedback/autohooks.js +11 -0
  34. package/src/controllers/feedback/controller.js +48 -0
  35. package/src/controllers/issuing/common-schemas/index.js +10 -0
  36. package/src/controllers/issuing/common-schemas/webwallet-get-credential-manifest-query.schema.js +27 -0
  37. package/src/controllers/issuing/common-schemas/webwallet-get-credential-manifest-response.schema.js +31 -0
  38. package/src/controllers/issuing/deep-link/autohooks.js +51 -0
  39. package/src/controllers/issuing/deep-link/controller.js +192 -0
  40. package/src/controllers/issuing/deep-link/schemas/index.js +6 -0
  41. package/src/controllers/issuing/deep-link/schemas/webwallet-deep-link-accept-offers-request.schema.js +30 -0
  42. package/src/controllers/issuing/deep-link/schemas/webwallet-deep-link-accept-offers-response.schema.js +36 -0
  43. package/src/controllers/issuing/deep-link/schemas/webwallet-deep-link-offers-request.schema.js +28 -0
  44. package/src/controllers/issuing/deep-link/schemas/webwallet-deep-link-offers-response.schema.js +48 -0
  45. package/src/controllers/issuing/identity-credentials/autohooks.js +42 -0
  46. package/src/controllers/issuing/identity-credentials/controller.js +188 -0
  47. package/src/controllers/issuing/identity-credentials/schemas/index.js +6 -0
  48. package/src/controllers/issuing/identity-credentials/schemas/webwallet-identity-credentials-confirm-body.schema.js +20 -0
  49. package/src/controllers/issuing/identity-credentials/schemas/webwallet-identity-credentials-confirm-response.schema.js +37 -0
  50. package/src/controllers/issuing/identity-credentials/schemas/webwallet-identity-credentials-request-code-body.schema.js +14 -0
  51. package/src/controllers/issuing/identity-credentials/schemas/webwallet-identity-credentials-request-code-params.schema.js +19 -0
  52. package/src/controllers/linkedin/autohooks.js +16 -0
  53. package/src/controllers/linkedin/controller.js +276 -0
  54. package/src/controllers/linkedin/schemas/index.js +3 -0
  55. package/src/controllers/linkedin/schemas/webwallet-linkedin-me-response.schema.js +14 -0
  56. package/src/controllers/root/controller.js +29 -0
  57. package/src/entities/.gitkeep +0 -0
  58. package/src/entities/accounts/constants.js +5 -0
  59. package/src/entities/accounts/domains/extract-auth0-id-token-or-access-token.js +22 -0
  60. package/src/entities/accounts/domains/index.js +5 -0
  61. package/src/entities/accounts/domains/validate-account-permissions.js +36 -0
  62. package/src/entities/accounts/domains/validate-logo-size.js +25 -0
  63. package/src/entities/accounts/index.js +5 -0
  64. package/src/entities/accounts/repos/accounts.repo.js +63 -0
  65. package/src/entities/credentials/domains/index.js +3 -0
  66. package/src/entities/credentials/domains/offer-to-credential-mongo-dto.js +43 -0
  67. package/src/entities/credentials/index.js +6 -0
  68. package/src/entities/credentials/orchestrators/index.js +3 -0
  69. package/src/entities/credentials/orchestrators/load-additional-render-info.js +65 -0
  70. package/src/entities/credentials/repos/credentials.repo.js +132 -0
  71. package/src/entities/credentials/schemas/index.js +4 -0
  72. package/src/entities/credentials/schemas/webwallet-credential-response.schema.js +138 -0
  73. package/src/entities/credentials/schemas/webwallet-credentials-response.schemas.js +95 -0
  74. package/src/entities/credentials/schemas/webwallet-display-descriptor-response.schema.js +106 -0
  75. package/src/entities/disclosures/index.js +4 -0
  76. package/src/entities/disclosures/repos/disclosures.repo.js +31 -0
  77. package/src/entities/disclosures/schemas/index.js +10 -0
  78. package/src/entities/disclosures/schemas/webwallet-disclosure.schema.js +39 -0
  79. package/src/entities/disclosures/schemas/webwallet-presentation-request-field.schema.js +45 -0
  80. package/src/entities/disclosures/schemas/webwallet-presentation-request-filter.schema.js +30 -0
  81. package/src/entities/disclosures/schemas/webwallet-presentation-request-format.schema.js +36 -0
  82. package/src/entities/disclosures/schemas/webwallet-presentation-request-input-descriptors.schema.js +111 -0
  83. package/src/entities/disclosures/schemas/webwallet-presentation-request-schema.schema.js +14 -0
  84. package/src/entities/disclosures/schemas/webwallet-presentation-request-submission-requirements.schema.js +37 -0
  85. package/src/entities/disclosures/schemas/webwallet-presentation-request.schema.js +79 -0
  86. package/src/entities/index.js +6 -0
  87. package/src/entities/issuing/domain/constants.js +8 -0
  88. package/src/entities/issuing/domain/does-user-have-fresh-pending-verification.js +26 -0
  89. package/src/entities/issuing/domain/get-credentials-from-offers.js +18 -0
  90. package/src/entities/issuing/domain/index.js +5 -0
  91. package/src/entities/issuing/index.js +5 -0
  92. package/src/entities/issuing/orchestrators/build-issuing-input-credentials.js +35 -0
  93. package/src/entities/issuing/orchestrators/generate-offers-by-deep-link.js +74 -0
  94. package/src/entities/issuing/orchestrators/get-identity-issuer.js +35 -0
  95. package/src/entities/issuing/orchestrators/get-identity-offers-by-deeplink.js +74 -0
  96. package/src/entities/issuing/orchestrators/index.js +6 -0
  97. package/src/entities/issuing/schemas/index.js +9 -0
  98. package/src/entities/issuing/schemas/webwallet-credential-manifest.schema.js +198 -0
  99. package/src/errors/error-codes.js +12 -0
  100. package/src/fetchers/career-wallet/create-account-fetcher.js +7 -0
  101. package/src/fetchers/career-wallet/create-did-fetcher.js +17 -0
  102. package/src/fetchers/career-wallet/get-app-config.js +11 -0
  103. package/src/fetchers/career-wallet/get-consents.js +13 -0
  104. package/src/fetchers/career-wallet/get-credential-categories.js +7 -0
  105. package/src/fetchers/career-wallet/get-personas.js +7 -0
  106. package/src/fetchers/career-wallet/index.js +12 -0
  107. package/src/fetchers/career-wallet/post-consent.js +9 -0
  108. package/src/fetchers/career-wallet/send-feedback.js +14 -0
  109. package/src/fetchers/career-wallet/sign-fetcher.js +14 -0
  110. package/src/fetchers/career-wallet/verify-id-credential-confirm-code.js +19 -0
  111. package/src/fetchers/career-wallet/verify-id-credential-request-code.js +20 -0
  112. package/src/fetchers/index.js +4 -0
  113. package/src/fetchers/lib-api/get-credential-display-schema.js +12 -0
  114. package/src/fetchers/lib-api/index.js +3 -0
  115. package/src/fetchers/linkedin/create-linkedin-post.js +45 -0
  116. package/src/fetchers/linkedin/get-access-token.js +26 -0
  117. package/src/fetchers/linkedin/get-linkedin-user-email.js +13 -0
  118. package/src/fetchers/linkedin/get-linkedin-user-id.js +16 -0
  119. package/src/fetchers/linkedin/index.js +9 -0
  120. package/src/fetchers/linkedin/register-image-to-upload.js +30 -0
  121. package/src/fetchers/linkedin/revoke-linkedin-access.js +20 -0
  122. package/src/fetchers/linkedin/upload-image-to-linkedin.js +16 -0
  123. package/src/index.js +15 -0
  124. package/src/init-server.js +108 -0
  125. package/src/plugins/crypto-services/index.js +5 -0
  126. package/src/plugins/crypto-services/jwt-sign-service-impl.js +72 -0
  127. package/src/plugins/crypto-services/jwt-verify-service-impl.js +21 -0
  128. package/src/plugins/crypto-services/key-service-impl.js +28 -0
  129. package/src/plugins/fetch-errors-handler-plugin.js +64 -0
  130. package/src/plugins/index.js +4 -0
  131. package/src/plugins/vnf-sdk-plugin.js +53 -0
  132. package/src/standalone.js +8 -0
  133. package/test/accounts-controller.test.js +618 -0
  134. package/test/consent-controller.test.js +185 -0
  135. package/test/credentials-controller.test.js +307 -0
  136. package/test/crypro-services/jwt-sign-service-impl.test.js +83 -0
  137. package/test/crypro-services/jwt-verify-service-impl.test.js +27 -0
  138. package/test/crypro-services/key-service-impl.test.js +76 -0
  139. package/test/crypro-services/mocks/index.js +4 -0
  140. package/test/crypro-services/mocks/jwt-mock.js +15 -0
  141. package/test/crypro-services/mocks/public-jwk.js +14 -0
  142. package/test/disclosures-controller/disclosure-credentials.test.js +428 -0
  143. package/test/disclosures-controller/get-disclosures.test.js +169 -0
  144. package/test/disclosures-controller/mocks/get-credential-manifest.js +20 -0
  145. package/test/disclosures-controller/mocks/index.js +6 -0
  146. package/test/disclosures-controller/mocks/presentation-request.js +32 -0
  147. package/test/disclosures-controller/mocks/presentation-submission.js +21 -0
  148. package/test/disclosures-controller/mocks/submission-result.js +19 -0
  149. package/test/factories/accounts.js +25 -0
  150. package/test/factories/credentials.js +66 -0
  151. package/test/factories/disclosures.js +106 -0
  152. package/test/feedback-controller.test.js +125 -0
  153. package/test/fetch-errors-handler-plugin.test.js +97 -0
  154. package/test/filter-deleted-credentials-extension.test.js +82 -0
  155. package/test/helpers/.env.test +10 -0
  156. package/test/helpers/nock-consent-add.js +16 -0
  157. package/test/helpers/nock-consents-get.js +15 -0
  158. package/test/helpers/nock-feedback.js +9 -0
  159. package/test/helpers/nock-linkedin-access-token.js +9 -0
  160. package/test/helpers/nock-linkedin-email.js +15 -0
  161. package/test/helpers/nock-linkedin-image-register.js +28 -0
  162. package/test/helpers/nock-linkedin-me.js +13 -0
  163. package/test/helpers/nock-linkedin-post.js +37 -0
  164. package/test/helpers/nock-linkedin-revoke.js +9 -0
  165. package/test/helpers/nock-test-personas.js +13 -0
  166. package/test/helpers/webwallet-build-fastify.js +17 -0
  167. package/test/issuing-controller/issuing-by-deeplink-empty-offers.test.js +142 -0
  168. package/test/issuing-controller/issuing-by-deeplink-failed-offers.test.js +142 -0
  169. package/test/issuing-controller/issuing-by-deeplink.test.js +492 -0
  170. package/test/issuing-controller/issuing-identity-credentials.test.js +377 -0
  171. package/test/issuing-controller/mocks/accept-offers-response.js +90 -0
  172. package/test/issuing-controller/mocks/accepted-credentials.js +47 -0
  173. package/test/issuing-controller/mocks/credential-manifest-presentation.js +72 -0
  174. package/test/issuing-controller/mocks/credential-manifest.js +45 -0
  175. package/test/issuing-controller/mocks/identity-issuing/accept-offers.js +22 -0
  176. package/test/issuing-controller/mocks/identity-issuing/confirm-verification-code.js +5 -0
  177. package/test/issuing-controller/mocks/identity-issuing/get-app-config.js +52 -0
  178. package/test/issuing-controller/mocks/identity-issuing/get-organization.js +118 -0
  179. package/test/issuing-controller/mocks/identity-issuing/index.js +6 -0
  180. package/test/issuing-controller/mocks/index.js +6 -0
  181. package/test/issuing-controller/mocks/issuers.js +126 -0
  182. package/test/issuing-controller/mocks/offers.js +48 -0
  183. package/test/issuing-controller/mocks/schema.js +107 -0
  184. package/test/linkedin-controller.test.js +452 -0
  185. package/test/mocks/credential-categories.js +115 -0
  186. package/test/mocks/credentials.js +15 -0
  187. package/test/mocks/didjwk.js +25 -0
  188. package/test/mocks/index.js +7 -0
  189. package/test/mocks/issuers.js +88 -0
  190. package/test/mocks/jwk.js +53 -0
  191. package/test/mocks/schema.js +107 -0
  192. package/test/root.test.js +42 -0
  193. package/test/vcl-sdk-plugin.test.js +86 -0
@@ -0,0 +1,618 @@
1
+ const usersDid = 'did:example:123';
2
+ const mockGenerateDidJwk = jest.fn().mockResolvedValue(
3
+ new Promise((resolve) => {
4
+ resolve({
5
+ did: usersDid,
6
+ publicJwk: {},
7
+ kid: '',
8
+ keyId: '',
9
+ });
10
+ })
11
+ );
12
+
13
+ jest.mock('@velocitycareerlabs/vnf-nodejs-wallet-sdk', () => {
14
+ const originalModule = jest.requireActual(
15
+ '@velocitycareerlabs/vnf-nodejs-wallet-sdk'
16
+ );
17
+ return {
18
+ ...originalModule,
19
+ VCLProvider: {
20
+ getInstance: jest.fn().mockReturnValue({
21
+ initialize: jest.fn().mockResolvedValue(null),
22
+ generateDidJwk: mockGenerateDidJwk,
23
+ }),
24
+ },
25
+ };
26
+ });
27
+
28
+ const { mongoDb } = require('@spencejs/spence-mongo-repos');
29
+ const nock = require('nock');
30
+ const { merge } = require('lodash/fp');
31
+ const { errorResponseMatcher } = require('@velocitycareerlabs/tests-helpers');
32
+ const { generateKeyPair } = require('@velocitycareerlabs/crypto');
33
+ const { generateDocJwt } = require('@velocitycareerlabs/jwt/src/docs');
34
+ const { ObjectId } = require('mongodb');
35
+ const buildFastify = require('./helpers/webwallet-build-fastify');
36
+ const { WebWalletServerError } = require('../src/errors/error-codes');
37
+ const { nockTestPersonas } = require('./helpers/nock-test-personas');
38
+
39
+ const idToken =
40
+ // eslint-disable-next-line max-len
41
+ 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IktFWSJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlzcyI6Imh0dHBzOi8vbG9jYWxob3N0LyIsImF1ZCI6ImZvbyIsInNjb3BlIjoicmVhZDp1c2VycyJ9.VwIIUqx9T-AxqbfL_FyNRAeOxTwiC2JpcwtrqnEWN3DdF07ijUkF1WYy8Ahfr_p4R3KnoPbiefZnIbVANCt-lt0ej32rfil2yHhQEsvFxSOjcrx6ARmPp0YAfWlN-5Sotzkxy29jaOZMEDkmRFZg3jkdC7wosPW_S6M-olC4g3HHfylpZI8O3Jdd87Qr9wD_QtUzANwnPbl2Q-9NEyxVjAZIWg_HWK9JAAaf_2IY5VwHBvyp0oeQSEHKi4hogcM59EOc4FxdR5WH45B_PenVa6W4mHFBkH8sAXxt2Zs9s2efujkfWYfyXvgL_lN7vT-TEADlAPP2L6CpWpDISOMsQWUSgGHcN_KwRh_E7qJwahR6mv4QHY6ReEoyjkmSS3swrD1l74jNs7QLAdsMywvzCMDsHabs7DYcEMGQBdP14PJ_ucLFnkivZeBDAc6sS445ocbyrpyO40XMaMorD5khRd9ej89SxR7d_v0W6Ne2Nn4XgW3pAZzu5Rdc4JvqfzLFxkp95jxy1MTAddjWISPmNOYYyXHM9SSqSpqVECOFS0f4z2zycHRqXUcOytWrvED6VGo9x7-IVCgu8vFzj0zToIWQmsDs3UoH9RnV12z0PMwGXQzca1lT_zGwJxBF3e4zJjmcJ05OMF2JgZ2_G48O3M4Dtb0jlgWbKLd0kWlIFzQ';
42
+
43
+ const auth0Token =
44
+ // eslint-disable-next-line max-len
45
+ 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IktFWSJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlzcyI6Imh0dHBzOi8vbG9jYWxob3N0LyIsImF1ZCI6ImZvbyIsInNjb3BlIjoicmVhZDp1c2VycyJ9.VwIIUqx9T-AxqbfL_FyNRAeOxTwiC2JpcwtrqnEWN3DdF07ijUkF1WYy8Ahfr_p4R3KnoPbiefZnIbVANCt-lt0ej32rfil2yHhQEsvFxSOjcrx6ARmPp0YAfWlN-5Sotzkxy29jaOZMEDkmRFZg3jkdC7wosPW_S6M-olC4g3HHfylpZI8O3Jdd87Qr9wD_QtUzANwnPbl2Q-9NEyxVjAZIWg_HWK9JAAaf_2IY5VwHBvyp0oeQSEHKi4hogcM59EOc4FxdR5WH45B_PenVa6W4mHFBkH8sAXxt2Zs9s2efujkfWYfyXvgL_lN7vT-TEADlAPP2L6CpWpDISOMsQWUSgGHcN_KwRh_E7qJwahR6mv4QHY6ReEoyjkmSS3swrD1l74jNs7QLAdsMywvzCMDsHabs7DYcEMGQBdP14PJ_ucLFnkivZeBDAc6sS445ocbyrpyO40XMaMorD5khRd9ej89SxR7d_v0W6Ne2Nn4XgW3pAZzu5Rdc4JvqfzLFxkp95jxy1MTAddjWISPmNOYYyXHM9SSqSpqVECOFS0f4z2zycHRqXUcOytWrvED6VGo9x7-IVCgu8vFzj0zToIWQmsDs3UoH9RnV12z0PMwGXQzca1lT_zGwJxBF3e4zJjmcJ05OMF2JgZ2_G48O3M4Dtb0jlgWbKLd0kWlIFzQ;';
46
+
47
+ const auth0userId = '1234567890';
48
+ describe('Test accounts controller', () => {
49
+ let fastify;
50
+
51
+ beforeAll(async () => {
52
+ fastify = buildFastify();
53
+ nock('https://localhost/').get('/.well-known/jwks.json').reply(200, jwks);
54
+ await fastify.ready();
55
+ });
56
+
57
+ beforeEach(async () => {
58
+ await mongoDb().collection('accounts').deleteMany({});
59
+ await mongoDb().collection('credentials').deleteMany({});
60
+ jest.clearAllMocks();
61
+ });
62
+
63
+ afterEach(async () => {
64
+ nock.cleanAll();
65
+ });
66
+
67
+ afterAll(async () => {
68
+ nock.restore();
69
+ await fastify.close();
70
+ });
71
+
72
+ describe('POST /accounts - create or retrieve account', () => {
73
+ it('should create an account', async () => {
74
+ const { accountsNock } = nockOnceSuccessfulAccountCreation(fastify);
75
+
76
+ const response = await fastify.injectJson({
77
+ method: 'POST',
78
+ url: '/accounts',
79
+ payload: {
80
+ account: {
81
+ accountType: 'temp',
82
+ id_token: idToken,
83
+ },
84
+ },
85
+ });
86
+
87
+ expect(response.statusCode).toBe(200);
88
+
89
+ expect(response.json).toEqual({
90
+ account: {
91
+ accountType: 'temp',
92
+ careerWalletAccountId: 'careerwallet-account-id',
93
+ createdAt: expect.any(String),
94
+ did: usersDid,
95
+ id: expect.any(String),
96
+ updatedAt: expect.any(String),
97
+ userId: auth0userId,
98
+ verificationInfo: {
99
+ createAccountMessageDisplayed: false,
100
+ },
101
+ },
102
+ });
103
+ expect(accountsNock.isDone()).toBe(true);
104
+ expect(mockGenerateDidJwk.mock.calls).toEqual([
105
+ [
106
+ {
107
+ signatureAlgorithm: 'P-256',
108
+ remoteCryptoServicesToken: 'fooAccessToken',
109
+ },
110
+ ],
111
+ ]);
112
+ const dbCredentials = await mongoDb()
113
+ .collection('credentials')
114
+ .find()
115
+ .toArray();
116
+ expect(dbCredentials).toEqual([]);
117
+ });
118
+
119
+ it('should return an existing account and call generateDidJwk if did key is missing', async () => {
120
+ const { accountsNock } = nockOnceSuccessfulAccountCreation(fastify);
121
+
122
+ const firstResponse = await fastify.injectJson({
123
+ method: 'POST',
124
+ url: '/accounts',
125
+ payload: {
126
+ account: {
127
+ accountType: 'temp',
128
+ id_token: idToken,
129
+ },
130
+ },
131
+ });
132
+
133
+ const secondResponse = await fastify.injectJson({
134
+ method: 'POST',
135
+ url: '/accounts',
136
+ payload: {
137
+ account: {
138
+ accountType: 'temp',
139
+ id_token: idToken,
140
+ },
141
+ },
142
+ });
143
+
144
+ expect(firstResponse.json).toEqual(secondResponse.json);
145
+
146
+ expect(accountsNock.isDone()).toBe(true);
147
+ expect(mockGenerateDidJwk.mock.calls).toEqual([
148
+ [
149
+ {
150
+ signatureAlgorithm: 'P-256',
151
+ remoteCryptoServicesToken: 'fooAccessToken',
152
+ },
153
+ ],
154
+ ]);
155
+ });
156
+
157
+ it('should return an existing account and not call generateDidJwk if did key exists', async () => {
158
+ const { accountsNock } = nockOnceSuccessfulAccountCreation(fastify);
159
+
160
+ const firstResponse = await fastify.injectJson({
161
+ method: 'POST',
162
+ url: '/accounts',
163
+ payload: {
164
+ account: {
165
+ accountType: 'temp',
166
+ id_token: idToken,
167
+ },
168
+ },
169
+ });
170
+
171
+ const secondResponse = await fastify.injectJson({
172
+ method: 'POST',
173
+ url: '/accounts',
174
+ payload: {
175
+ account: {
176
+ accountType: 'temp',
177
+ id_token: idToken,
178
+ },
179
+ },
180
+ });
181
+
182
+ expect(firstResponse.json).toEqual(secondResponse.json);
183
+
184
+ expect(accountsNock.isDone()).toBe(true);
185
+ expect(mockGenerateDidJwk.mock.calls).toEqual([
186
+ [
187
+ {
188
+ signatureAlgorithm: 'P-256',
189
+ remoteCryptoServicesToken: 'fooAccessToken',
190
+ },
191
+ ],
192
+ ]);
193
+ });
194
+
195
+ it("should 502 fail to create account if personaEmail is specified and personas can't be loaded", async () => {
196
+ const personasNock = nock(fastify.config.careerWalletUrl)
197
+ .get('/reference/personas')
198
+ .once()
199
+ .reply(404);
200
+ const { accountsNock } = nockOnceSuccessfulAccountCreation(fastify);
201
+
202
+ const response = await fastify.injectJson({
203
+ method: 'POST',
204
+ url: '/accounts',
205
+ payload: {
206
+ account: {
207
+ accountType: 'temp',
208
+ id_token: idToken,
209
+ },
210
+ personaEmail: 'foo',
211
+ },
212
+ });
213
+
214
+ expect(response.statusCode).toBe(502);
215
+
216
+ expect(response.json).toEqual(
217
+ errorResponseMatcher({
218
+ statusCode: 502,
219
+ error: 'Bad Gateway',
220
+ message: 'failed to get personas',
221
+ })
222
+ );
223
+ expect(accountsNock.isDone()).toBe(false);
224
+ expect(personasNock.isDone()).toBe(true);
225
+ expect(mockGenerateDidJwk.mock.calls).toEqual([]);
226
+
227
+ const dbCredentials = await mongoDb()
228
+ .collection('credentials')
229
+ .find()
230
+ .toArray();
231
+ expect(dbCredentials).toEqual([]);
232
+ });
233
+
234
+ it("should 400 if personaEmail doesn't match an existing persona", async () => {
235
+ const email = 'adam.smith@example.com';
236
+ const keyPair = generateKeyPair();
237
+ const doc = { vc: { credentialSubject: { email } } };
238
+ const jwt = await generateDocJwt(doc, keyPair.privateKey, {});
239
+ const personasNock = nock(fastify.config.careerWalletUrl)
240
+ .get('/reference/personas')
241
+ .once()
242
+ .reply(200, [
243
+ {
244
+ name: 'Adam Smith',
245
+ email: 'adam.smith@example.com',
246
+ vcs: [{ jwt_vc: jwt }],
247
+ },
248
+ ]);
249
+ const { accountsNock } = nockOnceSuccessfulAccountCreation(fastify);
250
+
251
+ const response = await fastify.injectJson({
252
+ method: 'POST',
253
+ url: '/accounts',
254
+ payload: {
255
+ account: {
256
+ accountType: 'temp',
257
+ id_token: idToken,
258
+ },
259
+ personaEmail: 'foo',
260
+ },
261
+ });
262
+
263
+ expect(response.statusCode).toBe(400);
264
+
265
+ expect(response.json).toEqual(
266
+ errorResponseMatcher({
267
+ statusCode: 400,
268
+ error: 'Bad Request',
269
+ message: 'unrecognized persona',
270
+ })
271
+ );
272
+ expect(accountsNock.isDone()).toBe(false);
273
+ expect(personasNock.isDone()).toBe(true);
274
+ expect(mockGenerateDidJwk.mock.calls).toEqual([]);
275
+
276
+ const dbCredentials = await mongoDb()
277
+ .collection('credentials')
278
+ .find()
279
+ .toArray();
280
+ expect(dbCredentials).toEqual([]);
281
+ });
282
+
283
+ it('should create a persona account and initialize with id credentials', async () => {
284
+ const email = 'adam.smith@example.com';
285
+ const keyPair = generateKeyPair();
286
+ const doc = { vc: { credentialSubject: { email } } };
287
+ const jwt = await generateDocJwt(doc, keyPair.privateKey, {});
288
+ const personasNock = nock(fastify.config.careerWalletUrl)
289
+ .get('/reference/personas')
290
+ .once()
291
+ .reply(200, [
292
+ {
293
+ name: 'Adam Smith',
294
+ email: 'adam.smith@example.com',
295
+ vcs: [{ jwt_vc: jwt }],
296
+ },
297
+ ]);
298
+ const { accountsNock } = nockOnceSuccessfulAccountCreation(fastify, {
299
+ account: { didKeyMetadatum: [{ did: usersDid }] },
300
+ });
301
+
302
+ const response = await fastify.injectJson({
303
+ method: 'POST',
304
+ url: '/accounts',
305
+ payload: {
306
+ account: {
307
+ accountType: 'temp',
308
+ id_token: idToken,
309
+ },
310
+ personaEmail: 'adam.smith@example.com',
311
+ },
312
+ });
313
+
314
+ expect(response.statusCode).toBe(200);
315
+
316
+ expect(response.json).toEqual({
317
+ account: {
318
+ accountType: 'temp',
319
+ careerWalletAccountId: 'careerwallet-account-id',
320
+ createdAt: expect.any(String),
321
+ did: usersDid,
322
+ id: expect.any(String),
323
+ updatedAt: expect.any(String),
324
+ userId: auth0userId,
325
+ verificationInfo: {
326
+ createAccountMessageDisplayed: false,
327
+ },
328
+ },
329
+ });
330
+ expect(accountsNock.isDone()).toBe(true);
331
+ expect(personasNock.isDone()).toBe(true);
332
+ expect(mockGenerateDidJwk.mock.calls).toEqual([]);
333
+
334
+ const [credential] = await mongoDb()
335
+ .collection('credentials')
336
+ .find()
337
+ .toArray();
338
+ expect(credential).toEqual({
339
+ _id: expect.any(ObjectId),
340
+ auth0UserId: auth0userId,
341
+ userDid: usersDid,
342
+ credentialSubject: { email },
343
+ encodedCredential: jwt,
344
+ createdAt: expect.any(Date),
345
+ updatedAt: expect.any(Date),
346
+ });
347
+ });
348
+ });
349
+
350
+ describe('PATCH /accounts - update account endpoint', () => {
351
+ it('should update allowed properties only', async () => {
352
+ nockOnceSuccessfulAccountCreation(fastify);
353
+
354
+ const {
355
+ json: {
356
+ account: { id },
357
+ },
358
+ } = await fastify.injectJson({
359
+ method: 'POST',
360
+ url: '/accounts',
361
+ payload: {
362
+ account: {
363
+ accountType: 'temp',
364
+ id_token: auth0Token,
365
+ },
366
+ },
367
+ });
368
+
369
+ const updateResponse = await fastify.injectJson({
370
+ method: 'PATCH',
371
+ url: '/accounts',
372
+ headers: {
373
+ authorization: `Bearer ${auth0Token}`,
374
+ },
375
+ payload: {
376
+ account: {
377
+ profileName: 'New Profile Name',
378
+ logo: 'https://example.com/new-logo.png',
379
+ 'verificationInfo.createAccountMessageDisplayed': true,
380
+ 'verificationInfo.phone.status': 'skipped',
381
+ },
382
+ },
383
+ });
384
+
385
+ expect(updateResponse.statusCode).toBe(200);
386
+
387
+ expect(updateResponse.json).toEqual({
388
+ account: {
389
+ id,
390
+ accountType: 'temp',
391
+ careerWalletAccountId: 'careerwallet-account-id',
392
+ createdAt: expect.any(String),
393
+ did: usersDid,
394
+ profileName: 'New Profile Name',
395
+ logo: 'https://example.com/new-logo.png',
396
+ verificationInfo: {
397
+ createAccountMessageDisplayed: true,
398
+ phone: {
399
+ status: 'skipped',
400
+ },
401
+ },
402
+ updatedAt: expect.any(String),
403
+ userId: auth0userId,
404
+ },
405
+ });
406
+ });
407
+
408
+ it('should return 401 if there is no token', async () => {
409
+ const response = await fastify.injectJson({
410
+ method: 'PATCH',
411
+ url: '/accounts',
412
+ payload: {
413
+ account: {
414
+ profileName: 'New Profile Name',
415
+ },
416
+ },
417
+ });
418
+
419
+ expect(response.statusCode).toBe(401);
420
+
421
+ expect(response.json).toEqual(
422
+ errorResponseMatcher({
423
+ error: 'Unauthorized',
424
+ message: 'No authorization token provided',
425
+ statusCode: 401,
426
+ })
427
+ );
428
+ });
429
+
430
+ it('should return 400 if unallowed properties are sent', async () => {
431
+ const response = await fastify.injectJson({
432
+ method: 'PATCH',
433
+ url: '/accounts',
434
+ headers: {
435
+ authorization: `Bearer ${auth0Token}`,
436
+ },
437
+ payload: {
438
+ account: {
439
+ profileName: 'New Profile Name',
440
+ 'verificationInfo.createAccountMessageDisplayed': true,
441
+ 'verificationInfo.phone.status': 'verified',
442
+ },
443
+ },
444
+ });
445
+
446
+ expect(response.statusCode).toBe(400);
447
+
448
+ expect(response.json).toEqual(
449
+ errorResponseMatcher({
450
+ error: 'Bad Request',
451
+ errorCode: 'request_validation_failed',
452
+ message:
453
+ 'body/account/verificationInfo.phone.status must be equal to one of the allowed values',
454
+ statusCode: 400,
455
+ })
456
+ );
457
+ });
458
+
459
+ it('should accept logo image < 100KB', async () => {
460
+ const buffer = Buffer.alloc(1024 * 100);
461
+ const base64Image = buffer.toString('base64');
462
+
463
+ nockOnceSuccessfulAccountCreation(fastify);
464
+
465
+ const {
466
+ json: {
467
+ account: { id },
468
+ },
469
+ } = await fastify.injectJson({
470
+ method: 'POST',
471
+ url: '/accounts',
472
+ payload: {
473
+ account: {
474
+ accountType: 'temp',
475
+ id_token: auth0Token,
476
+ },
477
+ },
478
+ });
479
+
480
+ const response = await fastify.injectJson({
481
+ method: 'PATCH',
482
+ url: '/accounts',
483
+ headers: {
484
+ authorization: `Bearer ${auth0Token}`,
485
+ },
486
+ payload: {
487
+ account: {
488
+ logo: base64Image,
489
+ },
490
+ },
491
+ });
492
+
493
+ expect(response.statusCode).toBe(200);
494
+
495
+ expect(response.json).toEqual({
496
+ account: {
497
+ id,
498
+ accountType: 'temp',
499
+ careerWalletAccountId: 'careerwallet-account-id',
500
+ createdAt: expect.any(String),
501
+ did: usersDid,
502
+ logo: base64Image,
503
+ updatedAt: expect.any(String),
504
+ userId: auth0userId,
505
+ verificationInfo: {
506
+ createAccountMessageDisplayed: false,
507
+ },
508
+ },
509
+ });
510
+ });
511
+
512
+ it('should not accept logo image > 100KB', async () => {
513
+ const buffer = Buffer.alloc(1024 * 101);
514
+ const base64Image = buffer.toString('base64');
515
+
516
+ const response = await fastify.injectJson({
517
+ method: 'PATCH',
518
+ url: '/accounts',
519
+ headers: {
520
+ authorization: `Bearer ${auth0Token}`,
521
+ },
522
+ payload: {
523
+ account: {
524
+ logo: base64Image,
525
+ },
526
+ },
527
+ });
528
+
529
+ expect(response.statusCode).toBe(400);
530
+
531
+ expect(response.json).toEqual(
532
+ errorResponseMatcher({
533
+ error: 'Bad Request',
534
+ errorCode: WebWalletServerError,
535
+ message: 'Logo size is too large. Maximum size is 100 KB.',
536
+ statusCode: 400,
537
+ })
538
+ );
539
+ });
540
+ });
541
+
542
+ describe('GET /accounts/test-personas', () => {
543
+ it('should return personas', async () => {
544
+ const nockInstance = nock(fastify.config.careerWalletUrl);
545
+ nockTestPersonas(nockInstance);
546
+ const response = await fastify.inject({
547
+ method: 'GET',
548
+ url: '/accounts/test-personas',
549
+ });
550
+
551
+ expect(response.statusCode).toBe(200);
552
+ expect(response.json()).toEqual(expect.any(Array));
553
+ });
554
+ });
555
+ });
556
+
557
+ const nockOnceSuccessfulAccountCreation = (fastify, replyOverrides) => {
558
+ const reply = merge(replyOverrides, {
559
+ account: {
560
+ id: 'careerwallet-account-id',
561
+ accountType: 'temp',
562
+ id_token_iss: 'http://localhost/',
563
+ id_token_sub: auth0userId,
564
+ createdAt: '2023-09-13T10:35:38.522Z',
565
+ updatedAt: '2023-09-13T10:35:38.522Z',
566
+ },
567
+ access_token: 'fooAccessToken',
568
+ });
569
+ const accountsNock = nock(fastify.config.careerWalletUrl)
570
+ .post('/api/v0.6/accounts')
571
+ .once()
572
+ .reply(200, reply);
573
+
574
+ return { accountsNock };
575
+ };
576
+
577
+ const jwks = {
578
+ keys: [
579
+ {
580
+ alg: 'RS512',
581
+ kid: 'KEY',
582
+ x5c: ['UNUSED'],
583
+ },
584
+ {
585
+ alg: 'RS256',
586
+ kid: 'KEY',
587
+ x5c: [
588
+ `
589
+ MIIEnjCCAoYCCQDmnGII6qzGlTANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZ1
590
+ bnVzZWQwHhcNMjEwOTE5MDcxODQ2WhcNMjExMDE5MDcxODQ2WjARMQ8wDQYDVQQD
591
+ DAZ1bnVzZWQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDY2acJ8JAH
592
+ XjK8j3sAXOokSWwvaEg65UJS0C7KdnfbLTeaeYFHBRY0v9Jkk/PJSXv9hMWw1aD3
593
+ n7NrhVwXeRXi/7VZuW/S4ek+hK+IMDvpKqzn+XeCpaMoRpAgloADeNY0qhYKxpr2
594
+ L0SmRQDwVy1r/g31ECewD2WpEiRSmXsQ2Q2uYT3V60BmbhUw31KGEr4SLXL9pzmb
595
+ arOH/5Rhqg+YFMywNY6i01S3UdOlUtAyWT/mVRAkVTsUEou9tBw61zputPdMrl7p
596
+ d4InmlfCmXNTPFh9EDczPQiAVPq8MDyEdRGP134+HM9+YgQUZjy+WsxmGEvplJIf
597
+ KrYtlWe11//oAXC3690LUYtvg49lNY4+H0/nngjnCDXkZo6f+PEvnBZfYl596VTV
598
+ Fx4FGNOqLwg4bAyE5j5jXtEGW1oKo1pxBg7Oe3MteQUDwMrONB3CbxdxDiN3YH94
599
+ 2nWGW9Le+CeA1QUhfjQqSoZRJURGYGoztVuIXOElnkrgwcJreX4b8y+Uo5kpp2By
600
+ 6UUaD/mMj9XQ+Ygp/J8DlJlqDXOIp6JUJ75aSK5ZIjRtWq/Go5RUjW9IW0ldEehh
601
+ /4j0ZWC0vR1/le2UmAE6tXhkH4sdx9JM84V+qRzjiGqQx3Wn00uwMiHHhte17t41
602
+ vk+b75wuHbfiq9R8irL6wqWeeuzvCC37NwIDAQABMA0GCSqGSIb3DQEBCwUAA4IC
603
+ AQCZOT6S5HLUp0gBtWK6Fxyzb9lWPE+AJipjJ80lS3OnaOIyVtyJexJ2BjTKWldJ
604
+ 48zkzLNIRsTEGEipNS6NkrkElfmoaNBpdbDch2WBkME3UYlFIR9GgbXPMlACQlwJ
605
+ f4qT3iIZ9zjpVMP8F63TzRRr7KEYW2PGHEtVQktMPprGEfU4Sz0OIa9RRV+BLsfh
606
+ x8he2pimJEzoEaWPgyJXV1S+tLUif8A/CEkZVRZ2vADA7WMGl2ZTdbmsTjXh4bf2
607
+ A4l4Zec+jwOjUPiEk5lLJwv1KeYos9wuUczAk7ku8wRzyZbrjwgRam9VQA5qmRzJ
608
+ PegEQwJaKMRu8PPK0L4KFN4v3ma7Ux6D719nko8mZ0kA2oUs6phsFmoWQfsbRbsD
609
+ CdUGnM2fPp6V285r9w9Y6+1nVdtJpbAPFJR3SxIpfYVfx3tI6C3BR9bIMr8uCf81
610
+ G+Ebvo4qTuY6Cg/mTpPLZ4cKpwSvB6cE+xeSHvKIRYm6QUYEReRxQ3b4aUKBSNwl
611
+ FEQufVGhGzeblNC3fjP+mMtqbyC8c1zkHc6tjJYO5yesKf8bjO71by2jgSced7nN
612
+ 5JvJawfEcabgHJ1aAFLj0tlPMrViFzu6y8/A5aZLc5UMISXAZXfB4wIEdyUUXJh4
613
+ rJKE/ZCb2+W8g29N5cv2P6nhahT3mYatMiQ0U/gfaIrA0A==
614
+ `.trim(),
615
+ ],
616
+ },
617
+ ],
618
+ };