@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,131 @@
1
+ const newError = require('http-errors');
2
+ const { values } = require('lodash/fp');
3
+ const { initBuildRefreshToken } = require('@velocitycareerlabs/crypto');
4
+ const {
5
+ TokenTypes,
6
+ GrantTypes,
7
+ initBuildAccessToken,
8
+ initValidateScopes,
9
+ initValidateClientId,
10
+ initValidateAudience,
11
+ initVerifyPresentation,
12
+ validateRefreshToken,
13
+ validatePresentation,
14
+ validateCredential,
15
+ } = require('../../../../entities');
16
+
17
+ const buildRefreshToken = initBuildRefreshToken();
18
+ const careerWalletOauthController = async (fastify) => {
19
+ const { config } = fastify;
20
+
21
+ const validateScopes = initValidateScopes(config.holderAppServerOAuthScopes);
22
+ const validateClientId = initValidateClientId(
23
+ config.holderAppServerOAuthHolderAppClientId
24
+ );
25
+ const validateAudience = initValidateAudience(
26
+ config.holderAppServerOAuthAudience
27
+ );
28
+
29
+ fastify.post(
30
+ '/token',
31
+ {
32
+ schema: fastify.autoSchema({
33
+ tags: ['careerwallet'],
34
+ body: {
35
+ type: 'object',
36
+ properties: {
37
+ grant_type: {
38
+ type: 'string',
39
+ enum: values(GrantTypes),
40
+ },
41
+ presentation: { type: 'string' },
42
+ refresh_token: { type: 'string' },
43
+ scope: { type: 'string' },
44
+ audience: { type: 'string' },
45
+ client_id: { type: 'string' },
46
+ },
47
+ required: ['grant_type', 'scope', 'audience', 'client_id'],
48
+ },
49
+ response: {
50
+ 200: {
51
+ type: 'object',
52
+ properties: {
53
+ access_token: { type: 'string' },
54
+ refresh_token: { type: 'string' },
55
+ token_type: { type: 'string', enum: values(TokenTypes) },
56
+ },
57
+ required: ['access_token', 'token_type'],
58
+ },
59
+ ...fastify.UnauthorizedResponse,
60
+ },
61
+ }),
62
+ },
63
+ async (req) => {
64
+ const { body, config: reqConfig } = req;
65
+ const {
66
+ grant_type: grantType,
67
+ presentation,
68
+ refresh_token: refreshToken,
69
+ client_id: clientId,
70
+ scope,
71
+ audience,
72
+ } = body;
73
+
74
+ const buildAccessToken = initBuildAccessToken(
75
+ reqConfig.holderAppServerAccessTokenSigningKey,
76
+ config.holderAppServerHolderAppProviderDid,
77
+ config.holderAppServerAccessTokenExpiresIn
78
+ );
79
+
80
+ const verifyPresentation = initVerifyPresentation(
81
+ reqConfig.oauthVclCredentialVerificationPublicKey
82
+ );
83
+ validateRefreshToken(grantType, refreshToken);
84
+ validatePresentation(grantType, presentation);
85
+ validateClientId(clientId);
86
+ validateScopes(scope);
87
+ validateAudience(audience);
88
+
89
+ let filterToFindUser;
90
+
91
+ if (grantType === GrantTypes.VNF_PRESENTATION) {
92
+ const decodedCredential = await verifyPresentation(presentation);
93
+ validateCredential(decodedCredential);
94
+ const userDid = decodedCredential.payload.vc.credentialSubject.id;
95
+
96
+ filterToFindUser = { 'didKeyMetadatum.did': userDid };
97
+ } else {
98
+ filterToFindUser = { refreshToken };
99
+ }
100
+
101
+ const { refreshToken: newRefreshToken, accountId } =
102
+ await findUserAndUpdateRefreshToken(filterToFindUser, scope, req);
103
+
104
+ return {
105
+ access_token: await buildAccessToken(scope, accountId),
106
+ token_type: TokenTypes.BEARER,
107
+ refresh_token: newRefreshToken,
108
+ };
109
+ }
110
+ );
111
+ };
112
+
113
+ const findUserAndUpdateRefreshToken = async (filter, scope, { repos }) => {
114
+ const refreshToken = buildRefreshToken();
115
+ const scopeValues = scope.split(' ');
116
+ const account = await repos.accounts.findAccountAndSetRefreshToken(
117
+ {
118
+ ...filter,
119
+ scope: { $all: scopeValues },
120
+ },
121
+ refreshToken
122
+ );
123
+
124
+ if (!account) {
125
+ throw newError.Unauthorized('Unknown refresh_token, user id or scope');
126
+ }
127
+
128
+ return { refreshToken, accountId: account._id };
129
+ };
130
+
131
+ module.exports = careerWalletOauthController;
@@ -0,0 +1,296 @@
1
+ const newError = require('http-errors');
2
+ const { map, isEmpty } = require('lodash/fp');
3
+ const { ObjectId } = require('mongodb');
4
+
5
+ const { pushGatewayAuth } = require('./push-gateway-auth');
6
+ const { initFirebase } = require('./firebase-initializer');
7
+ const {
8
+ NotificationTypes,
9
+ generateNotification,
10
+ } = require('./notification-types');
11
+
12
+ const icon = 'https://docs.velocitycareerlabs.io/Logos/vcl-app-push-logo.png';
13
+ const sound = 'default';
14
+
15
+ const deviceController = async (fastify) => {
16
+ const firebase = initFirebase(fastify.config);
17
+ const ttlSeconds = 60 * 60 * 24;
18
+ const notificationOptions = {
19
+ android: {
20
+ ttl: ttlSeconds * 1000,
21
+ priority: 'high',
22
+ notification: {
23
+ sound,
24
+ icon,
25
+ },
26
+ },
27
+ apns: {
28
+ headers: {
29
+ 'apns-priority': '5',
30
+ },
31
+ payload: {
32
+ aps: {
33
+ sound,
34
+ icon,
35
+ 'content-available': 1,
36
+ },
37
+ },
38
+ },
39
+ webpush: {
40
+ headers: {
41
+ TTL: `${ttlSeconds}`,
42
+ Urgency: 'high',
43
+ },
44
+ },
45
+ };
46
+
47
+ fastify.post(
48
+ '/generate',
49
+ {
50
+ preHandler: fastify.verifyAdmin,
51
+ schema: fastify.autoSchema({
52
+ tags: ['careerwallet'],
53
+ body: {
54
+ type: 'object',
55
+ properties: {
56
+ pushToken: { type: 'string' },
57
+ content: { type: 'object' },
58
+ },
59
+ },
60
+ response: {
61
+ 200: {
62
+ type: 'object',
63
+ properties: {
64
+ content: { type: 'object' },
65
+ },
66
+ },
67
+ },
68
+ }),
69
+ },
70
+ async (req, reply) => {
71
+ const {
72
+ body,
73
+ repos: { devices, accounts },
74
+ } = req;
75
+ const { pushToken } = body;
76
+
77
+ const account = await accounts.findOne({
78
+ filter: { [`pushTokens.${pushToken}`]: { $exists: true } },
79
+ });
80
+
81
+ if (isEmpty(account)) {
82
+ const device = await devices.findOne({
83
+ filter: { pushToken },
84
+ });
85
+
86
+ if (isEmpty(device) || isEmpty(device?.deviceId)) {
87
+ throw new newError.NotFound(
88
+ `No device registered for token ${pushToken}`
89
+ );
90
+ }
91
+
92
+ await sendNotificationsToDevicesForGenerateRequest(
93
+ [device],
94
+ body.content,
95
+ firebase,
96
+ notificationOptions
97
+ );
98
+
99
+ return reply.status(200).send();
100
+ }
101
+
102
+ const connectedDevices = await devices.find({
103
+ filter: { accountId: account._id },
104
+ });
105
+
106
+ if (isEmpty(connectedDevices)) {
107
+ throw new newError.NotFound(
108
+ `No device registered for token ${pushToken}`
109
+ );
110
+ }
111
+
112
+ await sendNotificationsToDevicesForGenerateRequest(
113
+ connectedDevices,
114
+ body.content,
115
+ firebase,
116
+ notificationOptions
117
+ );
118
+
119
+ return reply.status(200).send();
120
+ }
121
+ );
122
+
123
+ fastify.post(
124
+ '/',
125
+ {
126
+ preHandler: pushGatewayAuth,
127
+ schema: fastify.autoSchema({
128
+ tags: ['careerwallet'],
129
+ body: {
130
+ type: 'object',
131
+ properties: {
132
+ id: { type: 'string' },
133
+ pushToken: { type: 'string' },
134
+ message: { type: 'string' },
135
+ messageParams: {
136
+ type: 'object',
137
+ properties: {
138
+ priority: { type: 'string' },
139
+ timeToLeave: { type: 'integer' },
140
+ },
141
+ },
142
+ data: {
143
+ type: 'object',
144
+ properties: {
145
+ serviceEndpoint: { type: 'string' },
146
+ count: { type: 'number' },
147
+ credentialId: { type: 'string' },
148
+ exchangeId: { type: 'string' },
149
+ issuer: { type: 'string' },
150
+ replacementCredentialType: { type: 'string' },
151
+ credentialTypes: { type: 'array' },
152
+ notificationType: {
153
+ type: 'string',
154
+ enum: Object.values(NotificationTypes),
155
+ },
156
+ },
157
+ required: ['notificationType'],
158
+ },
159
+ },
160
+ required: ['id', 'pushToken', 'data'],
161
+ },
162
+ response: {
163
+ 204: {
164
+ type: 'null',
165
+ },
166
+ ...fastify.NotFoundResponse,
167
+ },
168
+ }),
169
+ },
170
+ async (req, reply) => {
171
+ const {
172
+ body,
173
+ repos: { devices, notifications, accounts },
174
+ } = req;
175
+ const { pushToken, data, message } = body;
176
+ const account = await accounts.findOne({
177
+ filter: { [`pushTokens.${pushToken}`]: { $exists: true } },
178
+ });
179
+
180
+ if (isEmpty(account)) {
181
+ const device = await devices.findOne({
182
+ filter: { pushToken },
183
+ });
184
+
185
+ if (isEmpty(device) || isEmpty(device?.deviceId)) {
186
+ throw new newError.NotFound(
187
+ `No device registered for token ${pushToken}`
188
+ );
189
+ }
190
+
191
+ await sendNotificationsToDevices(
192
+ [device],
193
+ data,
194
+ message,
195
+ firebase,
196
+ notifications,
197
+ notificationOptions
198
+ );
199
+
200
+ return reply.status(204).send();
201
+ }
202
+
203
+ const connectedDevices = await devices.find({
204
+ filter: { accountId: account._id },
205
+ });
206
+
207
+ if (isEmpty(connectedDevices)) {
208
+ throw new newError.NotFound(
209
+ `No device registered for token ${pushToken}`
210
+ );
211
+ }
212
+
213
+ await sendNotificationsToDevices(
214
+ connectedDevices,
215
+ data,
216
+ message,
217
+ firebase,
218
+ notifications,
219
+ notificationOptions
220
+ );
221
+
222
+ return reply.status(204).send();
223
+ }
224
+ );
225
+ };
226
+
227
+ const generateMessagingPayload = ({ data, message }) => {
228
+ const credentialTypes =
229
+ data && data.credentialTypes ? data.credentialTypes : [];
230
+ const count = data && data.count ? data.count : 0;
231
+ return {
232
+ notification: generateNotification(data, message),
233
+ data: {
234
+ ...data,
235
+ notificationId: `${new ObjectId()}`,
236
+ credentialTypes: JSON.stringify(credentialTypes),
237
+ count: `${count}`,
238
+ },
239
+ };
240
+ };
241
+
242
+ const sendNotificationsToDevices = async (
243
+ devices,
244
+ data,
245
+ message,
246
+ firebase,
247
+ notifications,
248
+ notificationOptions
249
+ ) => {
250
+ // eslint-disable-next-line consistent-return
251
+ const notificationPromises = map(async (device) => {
252
+ const payload = generateMessagingPayload({ data, message });
253
+ const deviceId = device?.deviceId;
254
+
255
+ await notifications.insert({
256
+ _id: new ObjectId(payload.data.notificationId),
257
+ deviceId,
258
+ linkedDevice: device._id,
259
+ ...payload,
260
+ });
261
+
262
+ if (device?.pushActivated) {
263
+ await firebase.messaging().send({
264
+ notification: payload.notification,
265
+ data: payload.data,
266
+ token: deviceId,
267
+ ...notificationOptions,
268
+ });
269
+ }
270
+ }, devices);
271
+
272
+ await Promise.allSettled(notificationPromises);
273
+ };
274
+
275
+ const sendNotificationsToDevicesForGenerateRequest = async (
276
+ devices,
277
+ content,
278
+ firebase,
279
+ notificationOptions
280
+ ) => {
281
+ // eslint-disable-next-line consistent-return
282
+ const notificationPromises = map(async (device) => {
283
+ const deviceId = device?.deviceId;
284
+ if (device?.pushActivated) {
285
+ firebase.messaging().send({
286
+ notification: content,
287
+ token: deviceId,
288
+ ...notificationOptions,
289
+ });
290
+ }
291
+ }, devices);
292
+
293
+ await Promise.allSettled(notificationPromises);
294
+ };
295
+
296
+ module.exports = deviceController;
@@ -0,0 +1,21 @@
1
+ const fbAdmin = require('firebase-admin');
2
+
3
+ const initFirebase = (config) => {
4
+ fbAdmin.initializeApp({
5
+ credential: fbAdmin.credential.cert({
6
+ type: config.firebaseType,
7
+ project_id: config.firebaseProjectId,
8
+ private_key_id: config.firebasePrivateKeyId,
9
+ private_key: config.firebasePrivateKey,
10
+ client_email: config.firebaseClientEmail,
11
+ client_id: config.firebaseClientId,
12
+ auth_uri: config.firebaseAuthUri,
13
+ token_uri: config.firebaseTokenUri,
14
+ auth_provider_x509_cert_url: config.firebaseAuthProviderX509CertUrl,
15
+ client_x509_cert_url: config.firebaseClientX509CertUrl,
16
+ }),
17
+ });
18
+ return fbAdmin;
19
+ };
20
+
21
+ module.exports = { initFirebase };
@@ -0,0 +1,37 @@
1
+ const NotificationTypes = Object.freeze({
2
+ CredentialRevoked: 'CredentialRevoked',
3
+ CredentialReplaced: 'CredentialReplaced',
4
+ NewOffersReady: 'NewOffersReady',
5
+ NoOffersFound: 'NoOffersFound',
6
+ PresentationVerified: 'PresentationVerified',
7
+ });
8
+
9
+ const generateNotification = (data, message) =>
10
+ ({
11
+ [NotificationTypes.CredentialRevoked]: {
12
+ title: 'Credential revoked',
13
+ body:
14
+ message ||
15
+ `Your ${data.credentialTypes?.[0]} credential has been revoked.`,
16
+ },
17
+ [NotificationTypes.CredentialReplaced]: {
18
+ title: 'Updated credential available',
19
+ body:
20
+ message ||
21
+ `Your ${data.credentialTypes?.[0]} credential can be updated. Tap here to receive the update.`,
22
+ },
23
+ [NotificationTypes.NewOffersReady]: {
24
+ title: 'New credential offer',
25
+ body: 'You have new credential offers.',
26
+ },
27
+ [NotificationTypes.NoOffersFound]: {
28
+ title: 'No offers found',
29
+ body: 'The issuer has no credential offers for you.',
30
+ },
31
+ [NotificationTypes.PresentationVerified]: {
32
+ title: 'Credentials verified',
33
+ body: 'Credentials that you shared have been verified',
34
+ },
35
+ }[data.notificationType]);
36
+
37
+ module.exports = { NotificationTypes, generateNotification };
@@ -0,0 +1,123 @@
1
+ const canonicalize = require('canonicalize');
2
+ const newError = require('http-errors');
3
+ const { isEmpty, intersection } = require('lodash/fp');
4
+ const {
5
+ CredentialCheckResultValue,
6
+ } = require('@velocitycareerlabs/verifiable-credentials');
7
+ const {
8
+ ServiceCategories,
9
+ } = require('@velocitycareerlabs/organizations-registry');
10
+
11
+ const { createCommitment } = require('@velocitycareerlabs/crypto');
12
+ const { jwtVerify, jwtDecode } = require('@velocitycareerlabs/jwt');
13
+ const {
14
+ resolveKid,
15
+ getOrganizationVerifiedProfile,
16
+ } = require('@velocitycareerlabs/common-fetchers');
17
+ const { ErrorsUnauthorizedPushes } = require('../../../../entities');
18
+
19
+ const RequiredServiceCategories = [
20
+ ServiceCategories.IdentityIssuer,
21
+ ServiceCategories.IdDocumentIssuer,
22
+ ServiceCategories.NotaryIdDocumentIssuer,
23
+ ServiceCategories.ContactIssuer,
24
+ ServiceCategories.NotaryContactIssuer,
25
+ ServiceCategories.NotaryIssuer,
26
+ ServiceCategories.Issuer,
27
+ ServiceCategories.Inspector,
28
+ ];
29
+
30
+ const pushGatewayAuth = async (context) => {
31
+ const {
32
+ config: { hostUrl },
33
+ traceId,
34
+ body: {
35
+ data: { exchangeId },
36
+ },
37
+ } = context;
38
+ const token = context.headers?.authorization?.slice(7);
39
+ const {
40
+ header: { kid },
41
+ payload: { hash, iss },
42
+ } = jwtDecode(token);
43
+
44
+ const publicKey = await loadKey(kid, context);
45
+ await jwtVerify(token, publicKey, {
46
+ subject: exchangeId,
47
+ audience: hostUrl,
48
+ jti: traceId,
49
+ });
50
+ if (hash !== createCommitment(canonicalize(context.body))) {
51
+ throw newError.Unauthorized(ErrorsUnauthorizedPushes.INVALID_HASH);
52
+ }
53
+
54
+ const organizationVerifiedProfile = await loadOrganizationVerifiedProfile(
55
+ iss,
56
+ context
57
+ );
58
+ validateOrganizationVerifiedProfile(organizationVerifiedProfile);
59
+ };
60
+
61
+ const loadKey = async (kid, context) => {
62
+ try {
63
+ return await resolveKid({ kid }, context);
64
+ } catch (error) {
65
+ if (error.response?.statusCode === 404) {
66
+ throw newError.Unauthorized('kid_not_found');
67
+ }
68
+ throw newError.Unauthorized('kid_resolution_error');
69
+ }
70
+ };
71
+
72
+ const loadOrganizationVerifiedProfile = async (did, context) => {
73
+ try {
74
+ return await getOrganizationVerifiedProfile(did, context);
75
+ } catch (error) {
76
+ if (error.response?.statusCode === 404) {
77
+ throw newError.Unauthorized('issuer_not_found');
78
+ }
79
+ throw newError.Unauthorized('issuer_resolution_error');
80
+ }
81
+ };
82
+
83
+ const validateOrganizationVerifiedProfile = ({
84
+ credentialChecks,
85
+ ...orgVc
86
+ }) => {
87
+ if (credentialChecks.UNTAMPERED === CredentialCheckResultValue.FAIL) {
88
+ throw pushGatewayAuthError('tampered_credential');
89
+ }
90
+ if (credentialChecks.UNREVOKED === CredentialCheckResultValue.FAIL) {
91
+ throw pushGatewayAuthError('revoked_credential');
92
+ }
93
+ if (credentialChecks.UNEXPIRED === CredentialCheckResultValue.FAIL) {
94
+ throw pushGatewayAuthError('expired_credential');
95
+ }
96
+ if (credentialChecks.TRUSTED_ISSUER === CredentialCheckResultValue.FAIL) {
97
+ throw pushGatewayAuthError('untrusted_issuer');
98
+ }
99
+
100
+ if (
101
+ isEmpty(
102
+ intersection(
103
+ RequiredServiceCategories,
104
+ orgVc.credentialSubject.permittedVelocityServiceCategory
105
+ )
106
+ )
107
+ ) {
108
+ throw pushGatewayAuthError(
109
+ `permitted service categories do not include one of ${RequiredServiceCategories.join(
110
+ ','
111
+ )}`
112
+ );
113
+ }
114
+ };
115
+
116
+ const pushGatewayAuthError = (reason) =>
117
+ newError.Unauthorized(
118
+ ErrorsUnauthorizedPushes.INVALID_ORG_ERROR_TEMPLATE({
119
+ reason,
120
+ })
121
+ );
122
+
123
+ module.exports = { pushGatewayAuth };
@@ -0,0 +1,24 @@
1
+ const {
2
+ autoboxIdsExtension,
3
+ repoFactory,
4
+ } = require('@spencejs/spence-mongo-repos');
5
+
6
+ module.exports = (app, options, next = () => {}) => {
7
+ next();
8
+ return repoFactory(
9
+ {
10
+ name: 'notifications',
11
+ entityName: 'notifications',
12
+ defaultProjection: {
13
+ _id: 1,
14
+ createdAt: 1,
15
+ linkedDevice: 1,
16
+ deviceId: 1,
17
+ data: 1,
18
+ notification: 1,
19
+ },
20
+ extensions: [autoboxIdsExtension],
21
+ },
22
+ app
23
+ );
24
+ };
@@ -0,0 +1,25 @@
1
+ const {
2
+ repoFactory,
3
+ autoboxIdsExtension,
4
+ } = require('@spencejs/spence-mongo-repos');
5
+
6
+ module.exports = (app, options, next = () => {}) => {
7
+ next();
8
+ return repoFactory(
9
+ {
10
+ name: 'verificationOffers',
11
+ entityName: 'verificationOffer',
12
+ defaultProjection: {
13
+ _id: 1,
14
+ type: 1,
15
+ value: 1,
16
+ offerId: 1,
17
+ offerCreationDate: 1,
18
+ offerExpirationDate: 1,
19
+ claimed: 1,
20
+ },
21
+ extensions: [autoboxIdsExtension],
22
+ },
23
+ app
24
+ );
25
+ };
@@ -0,0 +1,15 @@
1
+ const {
2
+ identifyWebhookRequestBodySchema,
3
+ verificationIdentifyResponse200Schema,
4
+ } = require('@velocitycareerlabs/common-schemas');
5
+ const { verifyAccessTokenPlugin } = require('../../../../plugins');
6
+
7
+ module.exports = async (fastify) => {
8
+ fastify
9
+ .addSchema(identifyWebhookRequestBodySchema)
10
+ .addSchema(verificationIdentifyResponse200Schema)
11
+ .autoSchemaPreset({
12
+ tags: ['credential_issuer_contact'],
13
+ })
14
+ .register(verifyAccessTokenPlugin);
15
+ };