@metamask-previews/kyc-controller 0.0.0-preview-e68ab1498 → 0.0.0-preview-4b37525
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.
- package/CHANGELOG.md +7 -1
- package/dist/KycController-method-action-types.cjs.map +1 -1
- package/dist/KycController-method-action-types.d.cts +12 -6
- package/dist/KycController-method-action-types.d.cts.map +1 -1
- package/dist/KycController-method-action-types.d.mts +12 -6
- package/dist/KycController-method-action-types.d.mts.map +1 -1
- package/dist/KycController-method-action-types.mjs.map +1 -1
- package/dist/KycController.cjs +313 -72
- package/dist/KycController.cjs.map +1 -1
- package/dist/KycController.d.cts +27 -7
- package/dist/KycController.d.cts.map +1 -1
- package/dist/KycController.d.mts +27 -7
- package/dist/KycController.d.mts.map +1 -1
- package/dist/KycController.mjs +314 -73
- package/dist/KycController.mjs.map +1 -1
- package/dist/KycService-method-action-types.cjs.map +1 -1
- package/dist/KycService-method-action-types.d.cts +60 -31
- package/dist/KycService-method-action-types.d.cts.map +1 -1
- package/dist/KycService-method-action-types.d.mts +60 -31
- package/dist/KycService-method-action-types.d.mts.map +1 -1
- package/dist/KycService-method-action-types.mjs.map +1 -1
- package/dist/KycService.cjs +140 -75
- package/dist/KycService.cjs.map +1 -1
- package/dist/KycService.d.cts +181 -74
- package/dist/KycService.d.cts.map +1 -1
- package/dist/KycService.d.mts +181 -74
- package/dist/KycService.d.mts.map +1 -1
- package/dist/KycService.mjs +140 -75
- package/dist/KycService.mjs.map +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +3 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.cts +49 -4
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.mts +49 -4
- package/dist/types.d.mts.map +1 -1
- package/dist/types.mjs.map +1 -1
- package/dist/ukyc/jwtChain.cjs +1 -1
- package/dist/ukyc/jwtChain.cjs.map +1 -1
- package/dist/ukyc/jwtChain.d.cts +2 -2
- package/dist/ukyc/jwtChain.d.mts +2 -2
- package/dist/ukyc/jwtChain.mjs +1 -1
- package/dist/ukyc/jwtChain.mjs.map +1 -1
- package/dist/ukyc/wrapEncryptionKey.cjs +16 -7
- package/dist/ukyc/wrapEncryptionKey.cjs.map +1 -1
- package/dist/ukyc/wrapEncryptionKey.d.cts +23 -16
- package/dist/ukyc/wrapEncryptionKey.d.cts.map +1 -1
- package/dist/ukyc/wrapEncryptionKey.d.mts +23 -16
- package/dist/ukyc/wrapEncryptionKey.d.mts.map +1 -1
- package/dist/ukyc/wrapEncryptionKey.mjs +16 -7
- package/dist/ukyc/wrapEncryptionKey.mjs.map +1 -1
- package/package.json +1 -1
package/dist/KycController.cjs
CHANGED
|
@@ -10,13 +10,13 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
10
10
|
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
11
11
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
12
12
|
};
|
|
13
|
-
var _KycController_instances, _KycController_sumsubLauncher, _KycController_keypair, _KycController_authClientToken, _KycController_generation, _KycController_sessionStatusPollIntervalMs, _KycController_pollTimer, _KycController_pollToken, _KycController_userStatusPollIntervalMs, _KycController_userStatusPollTimer, _KycController_userStatusPolling, _KycController_userStatusPollToken, _KycController_localUserSecretStore, _KycController_startConsentsSession, _KycController_createSession, _KycController_clearAcceptedTerms, _KycController_clearMoonPaySession, _KycController_dropTermsUnlessForVendor, _KycController_hasTermsForVendor, _KycController_handleCheckOutcome, _KycController_handleAuthOutcome, _KycController_continueAfterAuthentication, _KycController_requireTermsReacceptance, _KycController_buildUkycSessionVendorFields, _KycController_fetchAndApplyUserStatus, _KycController_applyUserStatus, _KycController_ensureUserStatusPolling, _KycController_stopUserStatusPolling, _KycController_startSessionStatusPolling, _KycController_pollSessionStatusOnce, _KycController_stopPolling, _KycController_cancelPendingSession, _KycController_updateIfCurrent, _KycController_applyUpdate, _KycController_fail;
|
|
13
|
+
var _KycController_instances, _KycController_sumsubLauncher, _KycController_keypair, _KycController_authClientToken, _KycController_generation, _KycController_sessionStatusPollIntervalMs, _KycController_pollTimer, _KycController_pollToken, _KycController_userStatusPollIntervalMs, _KycController_userStatusPollTimer, _KycController_userStatusPolling, _KycController_userStatusPollToken, _KycController_localUserSecretStore, _KycController_startConsentsSession, _KycController_recordSessionDisclaimers, _KycController_createSession, _KycController_clearAcceptedTerms, _KycController_clearMoonPaySession, _KycController_dropTermsUnlessForVendor, _KycController_hasTermsForVendor, _KycController_handleCheckOutcome, _KycController_handleAuthOutcome, _KycController_continueAfterAuthentication, _KycController_requireTermsReacceptance, _KycController_buildUkycSessionVendorFields, _KycController_createUkycSession, _KycController_fetchAndApplyUserStatus, _KycController_applyUserStatus, _KycController_ensureUserStatusPolling, _KycController_stopUserStatusPolling, _KycController_startSessionStatusPolling, _KycController_pollSessionStatusOnce, _KycController_stopPolling, _KycController_cancelPendingSession, _KycController_updateIfCurrent, _KycController_applyUpdate, _KycController_assertAttestedServerPublicKey, _KycController_fail;
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
15
|
exports.KycController = exports.getDefaultKycControllerState = exports.controllerName = void 0;
|
|
16
16
|
const base_controller_1 = require("@metamask/base-controller");
|
|
17
|
+
const utils_1 = require("@metamask/utils");
|
|
17
18
|
const ed25519_1 = require("@noble/curves/ed25519");
|
|
18
19
|
const crypto_js_1 = require("./crypto.cjs");
|
|
19
|
-
const encoding_js_1 = require("./encoding.cjs");
|
|
20
20
|
const deriveClientMaterial_js_1 = require("./ukyc/deriveClientMaterial.cjs");
|
|
21
21
|
const jwtChain_js_1 = require("./ukyc/jwtChain.cjs");
|
|
22
22
|
const localUserSecret_js_1 = require("./ukyc/localUserSecret.cjs");
|
|
@@ -148,6 +148,12 @@ const kycControllerMetadata = {
|
|
|
148
148
|
persist: true,
|
|
149
149
|
usedInUi: false,
|
|
150
150
|
},
|
|
151
|
+
credentialReusabilityConsentGiven: {
|
|
152
|
+
includeInDebugSnapshot: true,
|
|
153
|
+
includeInStateLogs: true,
|
|
154
|
+
persist: false,
|
|
155
|
+
usedInUi: false,
|
|
156
|
+
},
|
|
151
157
|
disclaimers: {
|
|
152
158
|
includeInDebugSnapshot: false,
|
|
153
159
|
includeInStateLogs: false,
|
|
@@ -160,6 +166,12 @@ const kycControllerMetadata = {
|
|
|
160
166
|
persist: false,
|
|
161
167
|
usedInUi: true,
|
|
162
168
|
},
|
|
169
|
+
sessionDisclaimers: {
|
|
170
|
+
includeInDebugSnapshot: false,
|
|
171
|
+
includeInStateLogs: false,
|
|
172
|
+
persist: false,
|
|
173
|
+
usedInUi: true,
|
|
174
|
+
},
|
|
163
175
|
geoCountry: {
|
|
164
176
|
includeInDebugSnapshot: true,
|
|
165
177
|
includeInStateLogs: true,
|
|
@@ -249,8 +261,10 @@ function getDefaultKycControllerState() {
|
|
|
249
261
|
termsAcceptedVendor: null,
|
|
250
262
|
sumsubTncAccepted: null,
|
|
251
263
|
idosTncAccepted: null,
|
|
264
|
+
credentialReusabilityConsentGiven: null,
|
|
252
265
|
disclaimers: [],
|
|
253
266
|
disclaimersError: null,
|
|
267
|
+
sessionDisclaimers: null,
|
|
254
268
|
geoCountry: null,
|
|
255
269
|
sessionToken: null,
|
|
256
270
|
accessToken: null,
|
|
@@ -284,6 +298,60 @@ exports.getDefaultKycControllerState = getDefaultKycControllerState;
|
|
|
284
298
|
function isSessionAlreadyCompletedError(error) {
|
|
285
299
|
return String(error).includes(SESSION_NOT_IN_VALID_STATE);
|
|
286
300
|
}
|
|
301
|
+
/**
|
|
302
|
+
* Whether recording session disclaimers failed because those document
|
|
303
|
+
* versions were already consented for the session (`409 Conflict`).
|
|
304
|
+
*
|
|
305
|
+
* @param error - The caught error.
|
|
306
|
+
* @returns `true` when the error is an HTTP 409.
|
|
307
|
+
*/
|
|
308
|
+
function isConsentConflictError(error) {
|
|
309
|
+
return (typeof error === 'object' &&
|
|
310
|
+
error !== null &&
|
|
311
|
+
typeof error.httpStatus === 'number' &&
|
|
312
|
+
error.httpStatus === 409);
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Maps a session-disclaimer catalog into the `{ key, version }` records the
|
|
316
|
+
* record-consents API expects, or an empty list when the user declined that
|
|
317
|
+
* category.
|
|
318
|
+
*
|
|
319
|
+
* @param documents - Catalog documents for one consent category.
|
|
320
|
+
* @param accepted - Whether the user accepted that category.
|
|
321
|
+
* @returns Consent records, or `[]` when not accepted.
|
|
322
|
+
*/
|
|
323
|
+
function consentRecordsFromCatalog(documents, accepted) {
|
|
324
|
+
if (!accepted) {
|
|
325
|
+
return [];
|
|
326
|
+
}
|
|
327
|
+
return documents
|
|
328
|
+
.filter((document) => !document.consented)
|
|
329
|
+
.map(({ key, version }) => ({ key, version }));
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Whether an accepted T&C2 category has no catalog documents. An empty list
|
|
333
|
+
* would otherwise skip the POST and count as success.
|
|
334
|
+
*
|
|
335
|
+
* @param documents - Catalog documents for one consent category.
|
|
336
|
+
* @param accepted - Whether the user accepted that category.
|
|
337
|
+
* @returns `true` when the user accepted and the catalog is empty.
|
|
338
|
+
*/
|
|
339
|
+
function isAcceptedCategoryEmpty(documents, accepted) {
|
|
340
|
+
return accepted && documents.length === 0;
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Whether an accepted category is still missing consent after a 409 re-GET:
|
|
344
|
+
* empty catalog or any document still unconsented.
|
|
345
|
+
*
|
|
346
|
+
* @param documents - Latest catalog documents for one consent category.
|
|
347
|
+
* @param accepted - Whether the user accepted that category.
|
|
348
|
+
* @returns `true` when accepted documents are not fully consented.
|
|
349
|
+
*/
|
|
350
|
+
function acceptedCategoryStillMissing(documents, accepted) {
|
|
351
|
+
return (accepted &&
|
|
352
|
+
(documents.length === 0 ||
|
|
353
|
+
documents.some((document) => !document.consented)));
|
|
354
|
+
}
|
|
287
355
|
/**
|
|
288
356
|
* Vendors other than MoonPay skip Check/Auth frames and use the empty-shell
|
|
289
357
|
* customer + consents path instead.
|
|
@@ -495,6 +563,7 @@ class KycController extends base_controller_1.BaseController {
|
|
|
495
563
|
await __classPrivateFieldGet(this, _KycController_instances, "m", _KycController_startConsentsSession).call(this, {
|
|
496
564
|
sumsubTncSigned,
|
|
497
565
|
idosTncSigned,
|
|
566
|
+
credentialReusabilityConsentGiven: this.state.credentialReusabilityConsentGiven ?? false,
|
|
498
567
|
});
|
|
499
568
|
}
|
|
500
569
|
else {
|
|
@@ -601,6 +670,9 @@ class KycController extends base_controller_1.BaseController {
|
|
|
601
670
|
* Required for every vendor so callers explicitly declare acceptance.
|
|
602
671
|
* @param params.idosTncSigned - Whether idOS T&C were accepted (T&C2).
|
|
603
672
|
* Required for every vendor so callers explicitly declare acceptance.
|
|
673
|
+
* @param params.credentialReusabilityConsentGiven - Whether the customer
|
|
674
|
+
* consented to reuse existing idOS credentials. Used when recording
|
|
675
|
+
* session-scoped disclaimers on the consents path. Defaults to `false`.
|
|
604
676
|
*/
|
|
605
677
|
async acceptTermsAndStartSession(params) {
|
|
606
678
|
const sumsubTncSigned = params?.sumsubTncSigned;
|
|
@@ -610,6 +682,7 @@ class KycController extends base_controller_1.BaseController {
|
|
|
610
682
|
__classPrivateFieldGet(this, _KycController_instances, "m", _KycController_fail).call(this, 'Missing T&C2 acceptance flags.');
|
|
611
683
|
return;
|
|
612
684
|
}
|
|
685
|
+
const credentialReusabilityConsentGiven = params.credentialReusabilityConsentGiven ?? false;
|
|
613
686
|
const termsAcceptedAt = new Date().toISOString();
|
|
614
687
|
const disclaimerIds = this.state.disclaimers.map((disclaimer) => disclaimer.id);
|
|
615
688
|
__classPrivateFieldGet(this, _KycController_instances, "m", _KycController_applyUpdate).call(this, (state) => {
|
|
@@ -624,9 +697,15 @@ class KycController extends base_controller_1.BaseController {
|
|
|
624
697
|
state.termsAcceptedVendor = state.activeVendor;
|
|
625
698
|
state.sumsubTncAccepted = sumsubTncSigned;
|
|
626
699
|
state.idosTncAccepted = idosTncSigned;
|
|
700
|
+
state.credentialReusabilityConsentGiven =
|
|
701
|
+
credentialReusabilityConsentGiven;
|
|
627
702
|
});
|
|
628
703
|
if (usesConsentsFlow(this.state.activeVendor)) {
|
|
629
|
-
await __classPrivateFieldGet(this, _KycController_instances, "m", _KycController_startConsentsSession).call(this, {
|
|
704
|
+
await __classPrivateFieldGet(this, _KycController_instances, "m", _KycController_startConsentsSession).call(this, {
|
|
705
|
+
sumsubTncSigned,
|
|
706
|
+
idosTncSigned,
|
|
707
|
+
credentialReusabilityConsentGiven,
|
|
708
|
+
});
|
|
630
709
|
return;
|
|
631
710
|
}
|
|
632
711
|
await __classPrivateFieldGet(this, _KycController_instances, "m", _KycController_createSession).call(this);
|
|
@@ -840,17 +919,20 @@ class KycController extends base_controller_1.BaseController {
|
|
|
840
919
|
/**
|
|
841
920
|
* Runs the SumSub document-verification sub-flow end to end:
|
|
842
921
|
*
|
|
843
|
-
* 1.
|
|
844
|
-
* 2. verifies
|
|
845
|
-
* attested session server public key;
|
|
922
|
+
* 1. creates a UKYC session, receiving per-secret encryption schemas;
|
|
923
|
+
* 2. verifies each schema's `jwtChain` against the Fractal JWKS and confirms
|
|
924
|
+
* the attested session server public key;
|
|
846
925
|
* 3. derives the `data_encryption_key` from the wallet's UKYC
|
|
847
926
|
* `local_user_secret` and wraps it for the session server;
|
|
848
|
-
* 4. mints a client-signed, read-only `ukyc_capability_token
|
|
849
|
-
*
|
|
927
|
+
* 4. mints a client-signed, read-only `ukyc_capability_token`, wraps it the
|
|
928
|
+
* same way as the encryption key, and submits both via authorizations;
|
|
850
929
|
* 5. fetches the SumSub applicant access token; and
|
|
851
930
|
* 6. presents the SDK via the injected launcher.
|
|
852
931
|
*
|
|
853
|
-
* If session
|
|
932
|
+
* If a UKYC session already exists (the consents path creates it before
|
|
933
|
+
* recording session disclaimers), steps 1–4 are skipped.
|
|
934
|
+
*
|
|
935
|
+
* If authorizations report the applicant is already approved on the relay
|
|
854
936
|
* while the vendor is still finalizing (`kycStatus: approved`,
|
|
855
937
|
* `finalStatus: pending`), the sub-flow stops at step 4 with a
|
|
856
938
|
* `vendorProcessing` status and a message rather than launching the SDK.
|
|
@@ -876,65 +958,32 @@ class KycController extends base_controller_1.BaseController {
|
|
|
876
958
|
// presenting the SDK) on a controller that is now idle.
|
|
877
959
|
const generation = __classPrivateFieldGet(this, _KycController_generation, "f");
|
|
878
960
|
try {
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
const jwtToken = MOCK_JWT_TOKEN;
|
|
885
|
-
// Establish a per-session X25519 keypair and exchange our public half for
|
|
886
|
-
// the server's wrapping key. The private half stays on the device and is
|
|
887
|
-
// used to derive the shared secret that seals the data_encryption_key.
|
|
888
|
-
const sessionClientPrivateKey = ed25519_1.x25519.utils.randomSecretKey();
|
|
889
|
-
const sessionClientPublicKey = ed25519_1.x25519.getPublicKey(sessionClientPrivateKey);
|
|
890
|
-
const wrappingKey = await this.messenger.call('KycService:getWrappingKey', { sessionClientPublicKey: (0, encoding_js_1.toBase64Url)(sessionClientPublicKey) });
|
|
891
|
-
// Verify the jwtChain against Fractal's JWKS, then confirm the returned
|
|
892
|
-
// sessionServerPublicKey matches the value attested inside the verified
|
|
893
|
-
// JWT payload before trusting it for key wrapping.
|
|
894
|
-
const { keys } = await this.messenger.call('KycService:fetchJwks');
|
|
895
|
-
const jwtChainPayload = (0, jwtChain_js_1.verifyJwtChain)(keys, wrappingKey.jwtChain);
|
|
896
|
-
if (jwtChainPayload.sessionServerPublicKeyX !==
|
|
897
|
-
wrappingKey.sessionServerPublicKey.x) {
|
|
898
|
-
throw new Error('sessionServerPublicKey does not match the verified jwtChain payload (sessionServerPublicKeyX).');
|
|
899
|
-
}
|
|
900
|
-
// Derive the data_encryption_key from the local_user_secret and wrap it
|
|
901
|
-
// for the session server. Only the wrapped (encrypted) key ever leaves
|
|
902
|
-
// the device.
|
|
903
|
-
const localUserSecret = await (0, localUserSecret_js_1.getOrCreateLocalUserSecret)(__classPrivateFieldGet(this, _KycController_instances, "m", _KycController_localUserSecretStore).call(this));
|
|
904
|
-
const clientMaterial = (0, deriveClientMaterial_js_1.deriveClientMaterial)(localUserSecret);
|
|
905
|
-
const wrappedEncryptionKey = {
|
|
906
|
-
sessionId: wrappingKey.id,
|
|
907
|
-
...(0, wrapEncryptionKey_js_1.wrapEncryptionKey)(sessionClientPrivateKey, wrappingKey.sessionServerPublicKey.x, clientMaterial.dataEncryptionKey),
|
|
908
|
-
};
|
|
909
|
-
// Mint a read-only `ukyc_capability_token` for the session. Only the
|
|
910
|
-
// client holds the signing key derived from `local_user_secret`, so only
|
|
911
|
-
// the client can mint it; scoping it to `read` means it authorizes later
|
|
912
|
-
// storage reads without granting write or delete access.
|
|
913
|
-
const ukycCapabilityToken = (0, storageAccessToken_js_1.signStorageAccessToken)({
|
|
914
|
-
material: clientMaterial,
|
|
915
|
-
operations: ['read'],
|
|
916
|
-
expiresAt: new Date(Date.now() + UKYC_CAPABILITY_TOKEN_TTL_MS),
|
|
917
|
-
});
|
|
918
|
-
const { sessionId, kycStatus, finalStatus } = await this.messenger.call('KycService:createUkycSession', {
|
|
919
|
-
jwtToken,
|
|
920
|
-
...__classPrivateFieldGet(this, _KycController_instances, "m", _KycController_buildUkycSessionVendorFields).call(this),
|
|
921
|
-
wrappedEncryptionKey,
|
|
922
|
-
ukycCapabilityToken,
|
|
923
|
-
});
|
|
924
|
-
// A user who already finished the journey can return to a session the
|
|
925
|
-
// relay has already approved (`kycStatus`) while the vendor is still
|
|
926
|
-
// finalizing its own decision (`finalStatus`). There is nothing left to
|
|
927
|
-
// verify, so stop here and surface a message rather than launching the
|
|
928
|
-
// SDK again.
|
|
929
|
-
if (kycStatus === KYC_STATUSES.approved &&
|
|
930
|
-
finalStatus === KYC_STATUSES.pending) {
|
|
931
|
-
const stillCurrent = __classPrivateFieldGet(this, _KycController_instances, "m", _KycController_updateIfCurrent).call(this, generation, (state) => {
|
|
932
|
-
state.sumsub.status = 'vendorProcessing';
|
|
933
|
-
state.sumsub.sessionId = sessionId;
|
|
934
|
-
state.statusMessage = VENDOR_PROCESSING_MESSAGE;
|
|
961
|
+
if (!this.state.sumsub.sessionId) {
|
|
962
|
+
__classPrivateFieldGet(this, _KycController_instances, "m", _KycController_applyUpdate).call(this, (state) => {
|
|
963
|
+
state.sumsub.status = 'creatingSession';
|
|
964
|
+
state.sumsub.result = null;
|
|
965
|
+
state.sumsub.sessionStatus = null;
|
|
935
966
|
});
|
|
936
|
-
|
|
967
|
+
const created = await __classPrivateFieldGet(this, _KycController_instances, "m", _KycController_createUkycSession).call(this, generation);
|
|
968
|
+
if (!created) {
|
|
969
|
+
return {};
|
|
970
|
+
}
|
|
971
|
+
// A user who already finished the journey can return to a session the
|
|
972
|
+
// relay has already approved (`kycStatus`) while the vendor is still
|
|
973
|
+
// finalizing its own decision (`finalStatus`). There is nothing left to
|
|
974
|
+
// verify, so stop here and surface a message rather than launching the
|
|
975
|
+
// SDK again.
|
|
976
|
+
if (created.vendorProcessing) {
|
|
977
|
+
return {
|
|
978
|
+
kycStatus: created.kycStatus,
|
|
979
|
+
finalStatus: created.finalStatus,
|
|
980
|
+
};
|
|
981
|
+
}
|
|
937
982
|
}
|
|
983
|
+
// Empty string is a valid "no id to poll" session id used by tests and
|
|
984
|
+
// must not be coalesced away as missing.
|
|
985
|
+
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
|
986
|
+
const sessionId = this.state.sumsub.sessionId || '';
|
|
938
987
|
__classPrivateFieldGet(this, _KycController_instances, "m", _KycController_updateIfCurrent).call(this, generation, (state) => {
|
|
939
988
|
state.sumsub.status = 'fetchingToken';
|
|
940
989
|
state.sumsub.sessionId = sessionId;
|
|
@@ -1096,6 +1145,8 @@ class KycController extends base_controller_1.BaseController {
|
|
|
1096
1145
|
state.error = null;
|
|
1097
1146
|
state.disclaimers = [];
|
|
1098
1147
|
state.disclaimersError = null;
|
|
1148
|
+
state.sessionDisclaimers = null;
|
|
1149
|
+
state.credentialReusabilityConsentGiven = null;
|
|
1099
1150
|
state.sessionToken = null;
|
|
1100
1151
|
state.accessToken = null;
|
|
1101
1152
|
state.moonpayCustomerId = null;
|
|
@@ -1133,12 +1184,15 @@ _KycController_sumsubLauncher = new WeakMap(), _KycController_keypair = new Weak
|
|
|
1133
1184
|
};
|
|
1134
1185
|
}, _KycController_startConsentsSession =
|
|
1135
1186
|
/**
|
|
1136
|
-
* Consents-path vendors:
|
|
1137
|
-
*
|
|
1187
|
+
* Consents-path vendors: record vendor T&Cs, create a UKYC session,
|
|
1188
|
+
* record session-scoped idOS / KYC-provider disclaimers, then launch
|
|
1189
|
+
* SumSub — skipping MoonPay Check/Auth frames.
|
|
1138
1190
|
*
|
|
1139
|
-
* @param consents - T&C2
|
|
1191
|
+
* @param consents - T&C2 flags mapped onto the session disclaimer catalog.
|
|
1140
1192
|
* @param consents.sumsubTncSigned - Whether Sumsub T&C were accepted.
|
|
1141
1193
|
* @param consents.idosTncSigned - Whether idOS T&C were accepted.
|
|
1194
|
+
* @param consents.credentialReusabilityConsentGiven - Whether credential
|
|
1195
|
+
* reuse was accepted.
|
|
1142
1196
|
*/
|
|
1143
1197
|
async function _KycController_startConsentsSession(consents) {
|
|
1144
1198
|
const { email, acceptedDisclaimerIds } = this.state;
|
|
@@ -1155,18 +1209,44 @@ async function _KycController_startConsentsSession(consents) {
|
|
|
1155
1209
|
state.error = null;
|
|
1156
1210
|
state.phase = 'session';
|
|
1157
1211
|
state.statusMessage = 'Submitting consents...';
|
|
1212
|
+
state.sumsub.status = 'creatingSession';
|
|
1213
|
+
state.sumsub.result = null;
|
|
1214
|
+
state.sumsub.sessionStatus = null;
|
|
1158
1215
|
// Consents-path vendors have no MoonPay session/access tokens.
|
|
1159
1216
|
__classPrivateFieldGet(this, _KycController_instances, "m", _KycController_clearMoonPaySession).call(this, state);
|
|
1160
1217
|
});
|
|
1161
1218
|
try {
|
|
1162
|
-
await this.messenger.call('KycService:
|
|
1219
|
+
await this.messenger.call('KycService:submitVendorDisclaimers', {
|
|
1220
|
+
vendor: this.state.activeVendor,
|
|
1163
1221
|
disclaimerIds: acceptedDisclaimerIds,
|
|
1164
|
-
sumsubTncSigned: consents.sumsubTncSigned,
|
|
1165
|
-
idosTncSigned: consents.idosTncSigned,
|
|
1166
1222
|
});
|
|
1167
1223
|
if (__classPrivateFieldGet(this, _KycController_generation, "f") !== generation) {
|
|
1168
1224
|
return;
|
|
1169
1225
|
}
|
|
1226
|
+
__classPrivateFieldGet(this, _KycController_instances, "m", _KycController_updateIfCurrent).call(this, generation, (state) => {
|
|
1227
|
+
state.statusMessage = 'Creating session...';
|
|
1228
|
+
});
|
|
1229
|
+
const created = await __classPrivateFieldGet(this, _KycController_instances, "m", _KycController_createUkycSession).call(this, generation);
|
|
1230
|
+
if (!created || __classPrivateFieldGet(this, _KycController_generation, "f") !== generation) {
|
|
1231
|
+
return;
|
|
1232
|
+
}
|
|
1233
|
+
await __classPrivateFieldGet(this, _KycController_instances, "m", _KycController_recordSessionDisclaimers).call(this, created.sessionId, consents, generation);
|
|
1234
|
+
if (__classPrivateFieldGet(this, _KycController_generation, "f") !== generation) {
|
|
1235
|
+
return;
|
|
1236
|
+
}
|
|
1237
|
+
if (created.vendorProcessing) {
|
|
1238
|
+
try {
|
|
1239
|
+
await this.refreshKycStatus();
|
|
1240
|
+
}
|
|
1241
|
+
catch (statusError) {
|
|
1242
|
+
console.error('KYC status refresh failed:', statusError);
|
|
1243
|
+
}
|
|
1244
|
+
__classPrivateFieldGet(this, _KycController_instances, "m", _KycController_updateIfCurrent).call(this, generation, (state) => {
|
|
1245
|
+
state.phase = 'done';
|
|
1246
|
+
state.statusMessage = VENDOR_PROCESSING_MESSAGE;
|
|
1247
|
+
});
|
|
1248
|
+
return;
|
|
1249
|
+
}
|
|
1170
1250
|
__classPrivateFieldGet(this, _KycController_instances, "m", _KycController_applyUpdate).call(this, (state) => {
|
|
1171
1251
|
state.phase = 'submit';
|
|
1172
1252
|
state.statusMessage = 'Starting document verification...';
|
|
@@ -1205,6 +1285,24 @@ async function _KycController_startConsentsSession(consents) {
|
|
|
1205
1285
|
});
|
|
1206
1286
|
}
|
|
1207
1287
|
catch (error) {
|
|
1288
|
+
if (isSessionAlreadyCompletedError(error)) {
|
|
1289
|
+
if (__classPrivateFieldGet(this, _KycController_generation, "f") !== generation) {
|
|
1290
|
+
return;
|
|
1291
|
+
}
|
|
1292
|
+
__classPrivateFieldGet(this, _KycController_instances, "m", _KycController_applyUserStatus).call(this, {
|
|
1293
|
+
status: 'completed',
|
|
1294
|
+
sumsubSessionId: null,
|
|
1295
|
+
errorCode: null,
|
|
1296
|
+
});
|
|
1297
|
+
__classPrivateFieldGet(this, _KycController_instances, "m", _KycController_updateIfCurrent).call(this, generation, (state) => {
|
|
1298
|
+
state.sumsub.status = 'complete';
|
|
1299
|
+
state.sumsub.result = { alreadyCompleted: true };
|
|
1300
|
+
state.statusMessage = 'KYC already completed.';
|
|
1301
|
+
state.phase = 'done';
|
|
1302
|
+
state.error = null;
|
|
1303
|
+
});
|
|
1304
|
+
return;
|
|
1305
|
+
}
|
|
1208
1306
|
console.error('Consents session failed:', error);
|
|
1209
1307
|
if (__classPrivateFieldGet(this, _KycController_generation, "f") !== generation) {
|
|
1210
1308
|
return;
|
|
@@ -1212,6 +1310,10 @@ async function _KycController_startConsentsSession(consents) {
|
|
|
1212
1310
|
__classPrivateFieldGet(this, _KycController_instances, "m", _KycController_applyUpdate).call(this, (state) => {
|
|
1213
1311
|
__classPrivateFieldGet(this, _KycController_instances, "m", _KycController_clearAcceptedTerms).call(this, state);
|
|
1214
1312
|
state.activeProduct = null;
|
|
1313
|
+
state.sessionDisclaimers = null;
|
|
1314
|
+
// Session create ran before recording disclaimers. Drop the leftover
|
|
1315
|
+
// UKYC session so a later `startSumSub` cannot skip consent recording.
|
|
1316
|
+
state.sumsub = { ...getDefaultKycControllerState().sumsub };
|
|
1215
1317
|
state.error = `Consents session failed: ${String(error)}`;
|
|
1216
1318
|
state.statusMessage =
|
|
1217
1319
|
'Consent / verification failed — accept the terms to try again.';
|
|
@@ -1219,6 +1321,74 @@ async function _KycController_startConsentsSession(consents) {
|
|
|
1219
1321
|
});
|
|
1220
1322
|
await this.loadDisclaimers();
|
|
1221
1323
|
}
|
|
1324
|
+
}, _KycController_recordSessionDisclaimers =
|
|
1325
|
+
/**
|
|
1326
|
+
* Fetches the session-scoped disclaimer catalog and records consents
|
|
1327
|
+
* derived from the T&C2 flags. Already-consented catalog rows are omitted
|
|
1328
|
+
* from the POST. A 409 is re-checked with a GET: continue only when every
|
|
1329
|
+
* accepted document is now consented, otherwise fail closed.
|
|
1330
|
+
*
|
|
1331
|
+
* @param sessionId - The UKYC session id.
|
|
1332
|
+
* @param consents - T&C2 flags mapped onto catalog documents.
|
|
1333
|
+
* @param consents.sumsubTncSigned - Whether Sumsub T&C were accepted.
|
|
1334
|
+
* @param consents.idosTncSigned - Whether idOS T&C were accepted.
|
|
1335
|
+
* @param consents.credentialReusabilityConsentGiven - Whether credential
|
|
1336
|
+
* reuse was accepted.
|
|
1337
|
+
* @param generation - Flow generation captured by the caller.
|
|
1338
|
+
*/
|
|
1339
|
+
async function _KycController_recordSessionDisclaimers(sessionId, consents, generation) {
|
|
1340
|
+
const catalog = await this.messenger.call('KycService:fetchSessionDisclaimers', { sessionId });
|
|
1341
|
+
if (__classPrivateFieldGet(this, _KycController_generation, "f") !== generation) {
|
|
1342
|
+
return;
|
|
1343
|
+
}
|
|
1344
|
+
__classPrivateFieldGet(this, _KycController_instances, "m", _KycController_applyUpdate).call(this, (state) => {
|
|
1345
|
+
state.sessionDisclaimers = catalog;
|
|
1346
|
+
state.statusMessage = 'Submitting consents...';
|
|
1347
|
+
});
|
|
1348
|
+
if (isAcceptedCategoryEmpty(catalog.idOS, consents.idosTncSigned) ||
|
|
1349
|
+
isAcceptedCategoryEmpty(catalog.kycProvider, consents.sumsubTncSigned)) {
|
|
1350
|
+
throw new Error('Session disclaimer catalog is missing documents for an accepted category.');
|
|
1351
|
+
}
|
|
1352
|
+
const idOS = consentRecordsFromCatalog(catalog.idOS, consents.idosTncSigned);
|
|
1353
|
+
const kycProvider = consentRecordsFromCatalog(catalog.kycProvider, consents.sumsubTncSigned);
|
|
1354
|
+
const reuseUnchanged = catalog.credentialReusabilityConsentGiven ===
|
|
1355
|
+
consents.credentialReusabilityConsentGiven;
|
|
1356
|
+
if (idOS.length === 0 && kycProvider.length === 0 && reuseUnchanged) {
|
|
1357
|
+
return;
|
|
1358
|
+
}
|
|
1359
|
+
try {
|
|
1360
|
+
const recorded = await this.messenger.call('KycService:submitSessionDisclaimers', {
|
|
1361
|
+
sessionId,
|
|
1362
|
+
idOS,
|
|
1363
|
+
kycProvider,
|
|
1364
|
+
credentialReusabilityConsentGiven: consents.credentialReusabilityConsentGiven,
|
|
1365
|
+
});
|
|
1366
|
+
__classPrivateFieldGet(this, _KycController_instances, "m", _KycController_updateIfCurrent).call(this, generation, (state) => {
|
|
1367
|
+
state.sessionDisclaimers = recorded;
|
|
1368
|
+
});
|
|
1369
|
+
}
|
|
1370
|
+
catch (error) {
|
|
1371
|
+
if (!isConsentConflictError(error)) {
|
|
1372
|
+
throw error;
|
|
1373
|
+
}
|
|
1374
|
+
// 409 means some document version was already recorded. Re-fetch and
|
|
1375
|
+
// continue only when every document the user accepted is now consented;
|
|
1376
|
+
// otherwise fail closed so a version bump cannot skip new docs.
|
|
1377
|
+
const latest = await this.messenger.call('KycService:fetchSessionDisclaimers', { sessionId });
|
|
1378
|
+
if (__classPrivateFieldGet(this, _KycController_generation, "f") !== generation) {
|
|
1379
|
+
return;
|
|
1380
|
+
}
|
|
1381
|
+
__classPrivateFieldGet(this, _KycController_instances, "m", _KycController_applyUpdate).call(this, (state) => {
|
|
1382
|
+
state.sessionDisclaimers = latest;
|
|
1383
|
+
});
|
|
1384
|
+
const stillMissingIdos = acceptedCategoryStillMissing(latest.idOS, consents.idosTncSigned);
|
|
1385
|
+
const stillMissingProvider = acceptedCategoryStillMissing(latest.kycProvider, consents.sumsubTncSigned);
|
|
1386
|
+
const stillMissingReuse = consents.credentialReusabilityConsentGiven &&
|
|
1387
|
+
!latest.credentialReusabilityConsentGiven;
|
|
1388
|
+
if (stillMissingIdos || stillMissingProvider || stillMissingReuse) {
|
|
1389
|
+
throw error;
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1222
1392
|
}, _KycController_createSession =
|
|
1223
1393
|
/**
|
|
1224
1394
|
* Creates a vendor session from the currently stored terms + email.
|
|
@@ -1290,6 +1460,7 @@ async function _KycController_createSession() {
|
|
|
1290
1460
|
state.termsAcceptedVendor = null;
|
|
1291
1461
|
state.sumsubTncAccepted = null;
|
|
1292
1462
|
state.idosTncAccepted = null;
|
|
1463
|
+
state.credentialReusabilityConsentGiven = null;
|
|
1293
1464
|
}, _KycController_clearMoonPaySession = function _KycController_clearMoonPaySession(state) {
|
|
1294
1465
|
state.moonpayCustomerId = null;
|
|
1295
1466
|
state.sessionToken = null;
|
|
@@ -1416,6 +1587,71 @@ async function _KycController_continueAfterAuthentication() {
|
|
|
1416
1587
|
};
|
|
1417
1588
|
}
|
|
1418
1589
|
return { vendor: this.state.activeVendor };
|
|
1590
|
+
}, _KycController_createUkycSession =
|
|
1591
|
+
/**
|
|
1592
|
+
* Creates a UKYC session, wraps the `data_encryption_key` and
|
|
1593
|
+
* `ukyc_capability_token` against the returned encryption schemas, and
|
|
1594
|
+
* submits both via authorizations. Stores `sumsub.sessionId`. Returns `null`
|
|
1595
|
+
* when a `reset()` superseded the flow.
|
|
1596
|
+
*
|
|
1597
|
+
* @param generation - Flow generation captured by the caller.
|
|
1598
|
+
* @returns The created session, or `null` if superseded.
|
|
1599
|
+
*/
|
|
1600
|
+
async function _KycController_createUkycSession(generation) {
|
|
1601
|
+
const jwtToken = MOCK_JWT_TOKEN;
|
|
1602
|
+
// Establish a per-session X25519 keypair used to seal both secrets. The
|
|
1603
|
+
// private half stays on the device; each encryption schema from session
|
|
1604
|
+
// creation supplies the matching server public key.
|
|
1605
|
+
const sessionClientPrivateKey = ed25519_1.x25519.utils.randomSecretKey();
|
|
1606
|
+
const { sessionId, encryptionDataKey, ukycCapabilityToken: capabilityTokenSchema, } = await this.messenger.call('KycService:createUkycSession', {
|
|
1607
|
+
jwtToken,
|
|
1608
|
+
...__classPrivateFieldGet(this, _KycController_instances, "m", _KycController_buildUkycSessionVendorFields).call(this),
|
|
1609
|
+
});
|
|
1610
|
+
if (__classPrivateFieldGet(this, _KycController_generation, "f") !== generation) {
|
|
1611
|
+
return null;
|
|
1612
|
+
}
|
|
1613
|
+
// Verify each jwtChain against Fractal's JWKS, then confirm the returned
|
|
1614
|
+
// server public key matches the value attested inside the verified JWT
|
|
1615
|
+
// payload before trusting it for wrapping.
|
|
1616
|
+
const { keys } = await this.messenger.call('KycService:fetchJwks');
|
|
1617
|
+
__classPrivateFieldGet(this, _KycController_instances, "m", _KycController_assertAttestedServerPublicKey).call(this, keys, encryptionDataKey);
|
|
1618
|
+
__classPrivateFieldGet(this, _KycController_instances, "m", _KycController_assertAttestedServerPublicKey).call(this, keys, capabilityTokenSchema);
|
|
1619
|
+
// Derive the data_encryption_key from the local_user_secret, mint a
|
|
1620
|
+
// read-only capability token, and wrap both for the session server. Only
|
|
1621
|
+
// the wrapped (encrypted) material ever leaves the device.
|
|
1622
|
+
const localUserSecret = await (0, localUserSecret_js_1.getOrCreateLocalUserSecret)(__classPrivateFieldGet(this, _KycController_instances, "m", _KycController_localUserSecretStore).call(this));
|
|
1623
|
+
const clientMaterial = (0, deriveClientMaterial_js_1.deriveClientMaterial)(localUserSecret);
|
|
1624
|
+
const wrappedEncryptionDataKey = (0, wrapEncryptionKey_js_1.wrapEncryptionKey)(sessionClientPrivateKey, encryptionDataKey.serverPublicKey.x, clientMaterial.dataEncryptionKey);
|
|
1625
|
+
// Only the client holds the signing key derived from `local_user_secret`,
|
|
1626
|
+
// so only the client can mint the token; scoping it to `read` means it
|
|
1627
|
+
// authorizes later storage reads without granting write or delete access.
|
|
1628
|
+
const ukycCapabilityToken = (0, storageAccessToken_js_1.signStorageAccessToken)({
|
|
1629
|
+
material: clientMaterial,
|
|
1630
|
+
operations: ['read'],
|
|
1631
|
+
expiresAt: new Date(Date.now() + UKYC_CAPABILITY_TOKEN_TTL_MS),
|
|
1632
|
+
});
|
|
1633
|
+
const wrappedUkycCapabilityToken = (0, wrapEncryptionKey_js_1.wrapEncryptionKey)(sessionClientPrivateKey, capabilityTokenSchema.serverPublicKey.x, (0, utils_1.stringToBytes)((0, storageAccessToken_js_1.encodeStorageAccessTokenForHeader)(ukycCapabilityToken)));
|
|
1634
|
+
if (__classPrivateFieldGet(this, _KycController_generation, "f") !== generation) {
|
|
1635
|
+
return null;
|
|
1636
|
+
}
|
|
1637
|
+
const { kycStatus, finalStatus } = await this.messenger.call('KycService:setAuthorizations', {
|
|
1638
|
+
sessionId,
|
|
1639
|
+
wrappedEncryptionDataKey,
|
|
1640
|
+
wrappedUkycCapabilityToken,
|
|
1641
|
+
});
|
|
1642
|
+
const vendorProcessing = kycStatus === KYC_STATUSES.approved &&
|
|
1643
|
+
finalStatus === KYC_STATUSES.pending;
|
|
1644
|
+
const stillCurrent = __classPrivateFieldGet(this, _KycController_instances, "m", _KycController_updateIfCurrent).call(this, generation, (state) => {
|
|
1645
|
+
state.sumsub.sessionId = sessionId;
|
|
1646
|
+
if (vendorProcessing) {
|
|
1647
|
+
state.sumsub.status = 'vendorProcessing';
|
|
1648
|
+
state.statusMessage = VENDOR_PROCESSING_MESSAGE;
|
|
1649
|
+
}
|
|
1650
|
+
});
|
|
1651
|
+
if (!stillCurrent) {
|
|
1652
|
+
return null;
|
|
1653
|
+
}
|
|
1654
|
+
return { sessionId, kycStatus, finalStatus, vendorProcessing };
|
|
1419
1655
|
}, _KycController_fetchAndApplyUserStatus =
|
|
1420
1656
|
/**
|
|
1421
1657
|
* Fetches `GET /kyc/status` and applies it to state without managing the
|
|
@@ -1593,6 +1829,11 @@ async function _KycController_pollSessionStatusOnce(sessionId, token) {
|
|
|
1593
1829
|
// @ts-expect-error Avoid "type instantiation is excessively deep".
|
|
1594
1830
|
updater(state);
|
|
1595
1831
|
});
|
|
1832
|
+
}, _KycController_assertAttestedServerPublicKey = function _KycController_assertAttestedServerPublicKey(keys, schema) {
|
|
1833
|
+
const jwtChainPayload = (0, jwtChain_js_1.verifyJwtChain)(keys, schema.jwtChain);
|
|
1834
|
+
if (jwtChainPayload.sessionServerPublicKeyX !== schema.serverPublicKey.x) {
|
|
1835
|
+
throw new Error('sessionServerPublicKey does not match the verified jwtChain payload (sessionServerPublicKeyX).');
|
|
1836
|
+
}
|
|
1596
1837
|
}, _KycController_fail = function _KycController_fail(message) {
|
|
1597
1838
|
__classPrivateFieldGet(this, _KycController_instances, "m", _KycController_applyUpdate).call(this, (state) => {
|
|
1598
1839
|
state.error = message;
|