@safepassage/sdk 3.5.4 → 3.5.6
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/README.md +18 -13
- package/brands/safepassage/index.d.ts +1 -1
- package/core/VerificationSDK.d.ts +3 -17
- package/dist/safepassage.min.js +2 -2
- package/index.js +132 -343
- package/package.json +1 -1
- package/safepassage.min.js +2 -2
- package/sdk.min.js +2 -2
- package/types/base.d.ts +12 -40
- package/utils/validation.d.ts +1 -4
- package/utils/crypto.d.ts +0 -31
package/index.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
2
|
var __defProps = Object.defineProperties;
|
|
3
3
|
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
4
|
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
6
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
6
|
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
@@ -18,25 +17,43 @@ var __spreadValues = (a, b) => {
|
|
|
18
17
|
return a;
|
|
19
18
|
};
|
|
20
19
|
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
20
|
+
|
|
21
|
+
// src-redirect/utils/polyfills.ts
|
|
22
|
+
function setupPolyfills() {
|
|
23
|
+
if (!crypto.randomUUID) {
|
|
24
|
+
crypto.randomUUID = function() {
|
|
25
|
+
const array = new Uint8Array(16);
|
|
26
|
+
crypto.getRandomValues(array);
|
|
27
|
+
array[6] = array[6] & 15 | 64;
|
|
28
|
+
array[8] = array[8] & 63 | 128;
|
|
29
|
+
const hex = Array.from(array).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
30
|
+
return [
|
|
31
|
+
hex.slice(0, 8),
|
|
32
|
+
hex.slice(8, 12),
|
|
33
|
+
hex.slice(12, 16),
|
|
34
|
+
hex.slice(16, 20),
|
|
35
|
+
hex.slice(20, 32)
|
|
36
|
+
].join("-");
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function checkBrowserCompatibility(logLabel = "SDK") {
|
|
41
|
+
const warnings = [];
|
|
42
|
+
if (!window.crypto || !window.crypto.getRandomValues) {
|
|
43
|
+
throw new Error(`${logLabel} requires Web Crypto API support`);
|
|
44
|
+
}
|
|
45
|
+
if (!crypto.randomUUID) {
|
|
46
|
+
warnings.push("crypto.randomUUID not supported, using polyfill");
|
|
47
|
+
}
|
|
48
|
+
if (!window.URLSearchParams) {
|
|
49
|
+
warnings.push(
|
|
50
|
+
"URLSearchParams not supported, consider adding a polyfill for IE 11 support"
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
if (warnings.length > 0) {
|
|
54
|
+
console.warn(`${logLabel} Browser Compatibility:`, warnings.join("; "));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
40
57
|
|
|
41
58
|
// src-redirect/utils/security.ts
|
|
42
59
|
function isOriginTrusted(origin, trustedOrigins) {
|
|
@@ -130,6 +147,34 @@ function validateReturnUrl(url, environment, _logLabel = "SDK") {
|
|
|
130
147
|
return { isValid: false, error: "Invalid URL format" };
|
|
131
148
|
}
|
|
132
149
|
}
|
|
150
|
+
var VerificationRateLimit = class {
|
|
151
|
+
constructor() {
|
|
152
|
+
this.attempts = /* @__PURE__ */ new Map();
|
|
153
|
+
this.maxAttempts = 5;
|
|
154
|
+
this.timeWindow = 6e4;
|
|
155
|
+
}
|
|
156
|
+
// 1 minute
|
|
157
|
+
isAllowed(identifier, logLabel = "SDK") {
|
|
158
|
+
const now = Date.now();
|
|
159
|
+
const attempts = this.attempts.get(identifier) || [];
|
|
160
|
+
const recentAttempts = attempts.filter(
|
|
161
|
+
(time) => now - time < this.timeWindow
|
|
162
|
+
);
|
|
163
|
+
if (recentAttempts.length >= this.maxAttempts) {
|
|
164
|
+
console.warn(
|
|
165
|
+
`${logLabel} Security: Rate limit exceeded for ${identifier}`
|
|
166
|
+
);
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
recentAttempts.push(now);
|
|
170
|
+
this.attempts.set(identifier, recentAttempts);
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
reset(identifier) {
|
|
174
|
+
this.attempts.delete(identifier);
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
var verificationRateLimit = new VerificationRateLimit();
|
|
133
178
|
function logSecurityEvent(event, metadata, logLabel = "SDK") {
|
|
134
179
|
const context = {
|
|
135
180
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -138,139 +183,13 @@ function logSecurityEvent(event, metadata, logLabel = "SDK") {
|
|
|
138
183
|
};
|
|
139
184
|
console.warn(`${logLabel} Security Event: ${event}`, __spreadValues(__spreadValues({}, context), metadata));
|
|
140
185
|
}
|
|
141
|
-
var VerificationRateLimit, verificationRateLimit;
|
|
142
|
-
var init_security = __esm({
|
|
143
|
-
"src-redirect/utils/security.ts"() {
|
|
144
|
-
"use strict";
|
|
145
|
-
VerificationRateLimit = class {
|
|
146
|
-
constructor() {
|
|
147
|
-
this.attempts = /* @__PURE__ */ new Map();
|
|
148
|
-
this.maxAttempts = 5;
|
|
149
|
-
this.timeWindow = 6e4;
|
|
150
|
-
}
|
|
151
|
-
// 1 minute
|
|
152
|
-
isAllowed(identifier, logLabel = "SDK") {
|
|
153
|
-
const now = Date.now();
|
|
154
|
-
const attempts = this.attempts.get(identifier) || [];
|
|
155
|
-
const recentAttempts = attempts.filter(
|
|
156
|
-
(time) => now - time < this.timeWindow
|
|
157
|
-
);
|
|
158
|
-
if (recentAttempts.length >= this.maxAttempts) {
|
|
159
|
-
console.warn(
|
|
160
|
-
`${logLabel} Security: Rate limit exceeded for ${identifier}`
|
|
161
|
-
);
|
|
162
|
-
return false;
|
|
163
|
-
}
|
|
164
|
-
recentAttempts.push(now);
|
|
165
|
-
this.attempts.set(identifier, recentAttempts);
|
|
166
|
-
return true;
|
|
167
|
-
}
|
|
168
|
-
reset(identifier) {
|
|
169
|
-
this.attempts.delete(identifier);
|
|
170
|
-
}
|
|
171
|
-
};
|
|
172
|
-
verificationRateLimit = new VerificationRateLimit();
|
|
173
|
-
}
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
// src-redirect/utils/crypto.ts
|
|
177
|
-
var crypto_exports = {};
|
|
178
|
-
__export(crypto_exports, {
|
|
179
|
-
createSignedState: () => createSignedState,
|
|
180
|
-
generateHMAC: () => generateHMAC,
|
|
181
|
-
generateSecureToken: () => generateSecureToken,
|
|
182
|
-
parseSignedState: () => parseSignedState,
|
|
183
|
-
verifyHMAC: () => verifyHMAC
|
|
184
|
-
});
|
|
185
|
-
async function generateHMAC(data, secret) {
|
|
186
|
-
const encoder = new TextEncoder();
|
|
187
|
-
const keyData = encoder.encode(secret);
|
|
188
|
-
const dataBuffer = encoder.encode(data);
|
|
189
|
-
const key = await crypto.subtle.importKey(
|
|
190
|
-
"raw",
|
|
191
|
-
keyData,
|
|
192
|
-
{ name: "HMAC", hash: "SHA-256" },
|
|
193
|
-
false,
|
|
194
|
-
["sign"]
|
|
195
|
-
);
|
|
196
|
-
const signature = await crypto.subtle.sign("HMAC", key, dataBuffer);
|
|
197
|
-
return Array.from(new Uint8Array(signature)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
198
|
-
}
|
|
199
|
-
async function verifyHMAC(data, signature, secret) {
|
|
200
|
-
try {
|
|
201
|
-
const expectedSignature = await generateHMAC(data, secret);
|
|
202
|
-
return constantTimeCompare(signature, expectedSignature);
|
|
203
|
-
} catch (e) {
|
|
204
|
-
return false;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
function constantTimeCompare(a, b) {
|
|
208
|
-
if (a.length !== b.length) {
|
|
209
|
-
return false;
|
|
210
|
-
}
|
|
211
|
-
let result = 0;
|
|
212
|
-
for (let i = 0; i < a.length; i++) {
|
|
213
|
-
result |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
214
|
-
}
|
|
215
|
-
return result === 0;
|
|
216
|
-
}
|
|
217
|
-
function generateSecureToken(length = 32) {
|
|
218
|
-
const array = new Uint8Array(length);
|
|
219
|
-
crypto.getRandomValues(array);
|
|
220
|
-
return Array.from(array, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
221
|
-
}
|
|
222
|
-
async function createSignedState(payload, hmacSecret) {
|
|
223
|
-
const timestampedPayload = __spreadProps(__spreadValues({}, payload), {
|
|
224
|
-
timestamp: Date.now(),
|
|
225
|
-
nonce: generateSecureToken(16)
|
|
226
|
-
});
|
|
227
|
-
const dataString = JSON.stringify(timestampedPayload);
|
|
228
|
-
const signature = await generateHMAC(dataString, hmacSecret);
|
|
229
|
-
const signedPayload = {
|
|
230
|
-
data: timestampedPayload,
|
|
231
|
-
signature
|
|
232
|
-
};
|
|
233
|
-
return btoa(JSON.stringify(signedPayload));
|
|
234
|
-
}
|
|
235
|
-
async function parseSignedState(signedState, hmacSecret, maxAge = STATE_EXPIRY_MS, logLabel = "SDK") {
|
|
236
|
-
try {
|
|
237
|
-
const json = atob(signedState);
|
|
238
|
-
const signedPayload = JSON.parse(json);
|
|
239
|
-
if (!signedPayload.data || !signedPayload.signature) {
|
|
240
|
-
console.warn(`${logLabel}: Invalid signed state format`);
|
|
241
|
-
return null;
|
|
242
|
-
}
|
|
243
|
-
const { data, signature } = signedPayload;
|
|
244
|
-
const dataString = JSON.stringify(data);
|
|
245
|
-
const isValid = await verifyHMAC(dataString, signature, hmacSecret);
|
|
246
|
-
if (!isValid) {
|
|
247
|
-
console.warn(`${logLabel}: State signature verification failed`);
|
|
248
|
-
return null;
|
|
249
|
-
}
|
|
250
|
-
if (data.timestamp) {
|
|
251
|
-
const age = Date.now() - data.timestamp;
|
|
252
|
-
if (age > maxAge) {
|
|
253
|
-
console.warn(`${logLabel}: State parameter expired`, { age, maxAge });
|
|
254
|
-
return null;
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
const _a = data, { timestamp, nonce } = _a, payload = __objRest(_a, ["timestamp", "nonce"]);
|
|
258
|
-
void timestamp;
|
|
259
|
-
void nonce;
|
|
260
|
-
return payload;
|
|
261
|
-
} catch (error) {
|
|
262
|
-
console.warn(`${logLabel}: Failed to parse signed state`, error);
|
|
263
|
-
return null;
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
var init_crypto = __esm({
|
|
267
|
-
"src-redirect/utils/crypto.ts"() {
|
|
268
|
-
"use strict";
|
|
269
|
-
init_validation();
|
|
270
|
-
}
|
|
271
|
-
});
|
|
272
186
|
|
|
273
187
|
// src-redirect/utils/validation.ts
|
|
188
|
+
var MINIMUM_AGE = 25;
|
|
189
|
+
var MAXIMUM_AGE = 150;
|
|
190
|
+
var MAX_URL_LENGTH = 2048;
|
|
191
|
+
var MAX_API_KEY_LENGTH = 128;
|
|
192
|
+
var API_KEY_PATTERN = /^(pk_|sk_)[a-zA-Z0-9_]+$/;
|
|
274
193
|
function validateConfig(config, context) {
|
|
275
194
|
var _a;
|
|
276
195
|
if (!config.apiKey) {
|
|
@@ -340,68 +259,6 @@ function validateConfig(config, context) {
|
|
|
340
259
|
throw new Error("newTabTarget must be popup or tab");
|
|
341
260
|
}
|
|
342
261
|
}
|
|
343
|
-
async function generateState(payload, environment, hmacSecret, _logLabel = "SDK") {
|
|
344
|
-
const { createSignedState: createSignedState2 } = await Promise.resolve().then(() => (init_crypto(), crypto_exports));
|
|
345
|
-
return createSignedState2(payload, hmacSecret);
|
|
346
|
-
}
|
|
347
|
-
var MINIMUM_AGE, MAXIMUM_AGE, MAX_URL_LENGTH, MAX_API_KEY_LENGTH, STATE_EXPIRY_MS, API_KEY_PATTERN;
|
|
348
|
-
var init_validation = __esm({
|
|
349
|
-
"src-redirect/utils/validation.ts"() {
|
|
350
|
-
"use strict";
|
|
351
|
-
init_security();
|
|
352
|
-
MINIMUM_AGE = 25;
|
|
353
|
-
MAXIMUM_AGE = 150;
|
|
354
|
-
MAX_URL_LENGTH = 2048;
|
|
355
|
-
MAX_API_KEY_LENGTH = 128;
|
|
356
|
-
STATE_EXPIRY_MS = 6e5;
|
|
357
|
-
API_KEY_PATTERN = /^(pk_|sk_)[a-zA-Z0-9_]+$/;
|
|
358
|
-
}
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
// src-redirect/utils/polyfills.ts
|
|
362
|
-
function setupPolyfills() {
|
|
363
|
-
if (!crypto.randomUUID) {
|
|
364
|
-
crypto.randomUUID = function() {
|
|
365
|
-
const array = new Uint8Array(16);
|
|
366
|
-
crypto.getRandomValues(array);
|
|
367
|
-
array[6] = array[6] & 15 | 64;
|
|
368
|
-
array[8] = array[8] & 63 | 128;
|
|
369
|
-
const hex = Array.from(array).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
370
|
-
return [
|
|
371
|
-
hex.slice(0, 8),
|
|
372
|
-
hex.slice(8, 12),
|
|
373
|
-
hex.slice(12, 16),
|
|
374
|
-
hex.slice(16, 20),
|
|
375
|
-
hex.slice(20, 32)
|
|
376
|
-
].join("-");
|
|
377
|
-
};
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
function checkBrowserCompatibility(logLabel = "SDK") {
|
|
381
|
-
const warnings = [];
|
|
382
|
-
if (!window.crypto || !window.crypto.getRandomValues) {
|
|
383
|
-
throw new Error(`${logLabel} requires Web Crypto API support`);
|
|
384
|
-
}
|
|
385
|
-
if (!window.crypto.subtle) {
|
|
386
|
-
throw new Error(
|
|
387
|
-
`${logLabel} requires Web Crypto subtle API for HMAC operations`
|
|
388
|
-
);
|
|
389
|
-
}
|
|
390
|
-
if (!crypto.randomUUID) {
|
|
391
|
-
warnings.push("crypto.randomUUID not supported, using polyfill");
|
|
392
|
-
}
|
|
393
|
-
if (!window.URLSearchParams) {
|
|
394
|
-
warnings.push(
|
|
395
|
-
"URLSearchParams not supported, consider adding a polyfill for IE 11 support"
|
|
396
|
-
);
|
|
397
|
-
}
|
|
398
|
-
if (warnings.length > 0) {
|
|
399
|
-
console.warn(`${logLabel} Browser Compatibility:`, warnings.join("; "));
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
// src-redirect/core/VerificationSDK.ts
|
|
404
|
-
init_validation();
|
|
405
262
|
|
|
406
263
|
// src-redirect/utils/environment.ts
|
|
407
264
|
function getEnvironmentUrl(environment, urls) {
|
|
@@ -520,7 +377,6 @@ function validateEnvironmentSecurity(environment, urls, logLabel = "SDK") {
|
|
|
520
377
|
}
|
|
521
378
|
|
|
522
379
|
// src-redirect/core/VerificationSDK.ts
|
|
523
|
-
init_security();
|
|
524
380
|
var _VerificationSDK = class _VerificationSDK {
|
|
525
381
|
/**
|
|
526
382
|
* Initialize SDK
|
|
@@ -537,16 +393,10 @@ var _VerificationSDK = class _VerificationSDK {
|
|
|
537
393
|
this.isVerificationInProgress = false;
|
|
538
394
|
this.currentSessionId = null;
|
|
539
395
|
this.hasReceivedResult = false;
|
|
540
|
-
// Server-provided verify URL (includes
|
|
396
|
+
// Server-provided verify URL (includes authoritative session context)
|
|
541
397
|
this.lastVerifyUrl = null;
|
|
542
|
-
// Server-provided session token (WS auth)
|
|
543
|
-
this.lastSessionToken = null;
|
|
544
398
|
// External user ID provided during verify() for cancellation redirects
|
|
545
399
|
this.lastExternalUserId = null;
|
|
546
|
-
// Sandbox mode flag from session creation (for UI labeling)
|
|
547
|
-
this.lastSandboxMode = null;
|
|
548
|
-
// Temporary storage for QR handoff token to include in state
|
|
549
|
-
this.temporaryHandoffToken = null;
|
|
550
400
|
this.brandUrls = brandUrls;
|
|
551
401
|
this.brandConstants = brandConstants;
|
|
552
402
|
const environment = resolveEnvironment(config.environment, this.brandUrls);
|
|
@@ -565,6 +415,7 @@ var _VerificationSDK = class _VerificationSDK {
|
|
|
565
415
|
mode: config.mode || "redirect",
|
|
566
416
|
newTabTarget: config.newTabTarget || "popup"
|
|
567
417
|
});
|
|
418
|
+
this.validateServiceOverrides();
|
|
568
419
|
validateEnvironmentSecurity(this.config.environment, this.getUrlConfig(), this.brandConstants.name);
|
|
569
420
|
enforceHTTPS(this.config.environment, this.brandConstants.name);
|
|
570
421
|
logSecurityEvent("SDK_INITIALIZED", {
|
|
@@ -643,82 +494,32 @@ var _VerificationSDK = class _VerificationSDK {
|
|
|
643
494
|
throw error;
|
|
644
495
|
}
|
|
645
496
|
}
|
|
646
|
-
/**
|
|
647
|
-
* Build verification URL with HMAC-signed state
|
|
648
|
-
*/
|
|
497
|
+
/** Build a launch URL from the Portal-issued authoritative verify URL. */
|
|
649
498
|
async buildVerificationUrl(options, sessionId) {
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
const hasExplicitVerificationMode = options.verificationMode !== void 0;
|
|
654
|
-
const hasExplicitFaceMatchEnabled = options.faceMatchEnabled !== void 0 || this.config.faceMatchEnabled !== void 0;
|
|
655
|
-
const hasOverrides = hasExplicitChallengeAge || hasExplicitVerificationMode || hasExplicitFaceMatchEnabled;
|
|
656
|
-
const faceMatchEnabled = (_a = options.faceMatchEnabled) != null ? _a : this.config.faceMatchEnabled;
|
|
657
|
-
const state = await generateState(
|
|
658
|
-
{
|
|
659
|
-
merchantId: this.config.apiKey,
|
|
660
|
-
sessionId,
|
|
661
|
-
returnUrl: this.config.returnUrl,
|
|
662
|
-
cancelUrl: this.config.cancelUrl,
|
|
663
|
-
challengeAge: options.challengeAge || this.config.defaultChallengeAge,
|
|
664
|
-
verificationMode: options.verificationMode || this.config.defaultVerificationMode,
|
|
665
|
-
faceMatchEnabled,
|
|
666
|
-
hasOverrides,
|
|
667
|
-
// Flag to indicate explicit overrides
|
|
668
|
-
externalUserId: options.externalUserId,
|
|
669
|
-
timestamp: Date.now(),
|
|
670
|
-
// Additional config for self-contained verify-ui
|
|
671
|
-
apiUrl: this.getPortalApiUrl(),
|
|
672
|
-
engineUrl: this.getEngineUrl(),
|
|
673
|
-
wsUrl: this.getWebSocketUrl(),
|
|
674
|
-
environment: this.config.environment,
|
|
675
|
-
features: {
|
|
676
|
-
testMode: false,
|
|
677
|
-
warmupPeriodMs: 500,
|
|
678
|
-
qualityThreshold: 0.6,
|
|
679
|
-
sandboxMode: (_b = this.lastSandboxMode) != null ? _b : false
|
|
680
|
-
},
|
|
681
|
-
// Include handoffToken if available (for QR code desktop flow)
|
|
682
|
-
handoffToken: this.temporaryHandoffToken || void 0,
|
|
683
|
-
// Include sessionToken and verifyUrl to make UI auth deterministic
|
|
684
|
-
sessionToken: this.lastSessionToken || void 0,
|
|
685
|
-
verifyUrl: this.lastVerifyUrl || void 0
|
|
686
|
-
},
|
|
687
|
-
this.config.environment,
|
|
688
|
-
this.getHmacSecret(),
|
|
689
|
-
this.brandConstants.name
|
|
690
|
-
);
|
|
499
|
+
if (!this.lastVerifyUrl) {
|
|
500
|
+
throw new Error("Server did not return an authoritative verification URL");
|
|
501
|
+
}
|
|
691
502
|
const language = options.language || this.config.language;
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
url.searchParams.set("state", state);
|
|
697
|
-
url.searchParams.set("mode", this.config.mode);
|
|
698
|
-
if (options.skipIntro) {
|
|
699
|
-
url.searchParams.set("skip_intro", "true");
|
|
700
|
-
}
|
|
701
|
-
if (options.autoReturn) {
|
|
702
|
-
url.searchParams.set("auto_return", "true");
|
|
703
|
-
}
|
|
704
|
-
if (language) {
|
|
705
|
-
url.searchParams.set("lang", language);
|
|
706
|
-
}
|
|
707
|
-
return url.toString();
|
|
708
|
-
} catch (e) {
|
|
709
|
-
}
|
|
503
|
+
const serverUrl = new URL(this.lastVerifyUrl);
|
|
504
|
+
const trustedVerifyOrigin = new URL(this.getUrlConfig().verifyUiUrl).origin;
|
|
505
|
+
if (serverUrl.origin !== trustedVerifyOrigin) {
|
|
506
|
+
throw new Error("Server returned an untrusted verification origin");
|
|
710
507
|
}
|
|
711
|
-
|
|
508
|
+
if (serverUrl.searchParams.get("sessionId") !== sessionId || !serverUrl.searchParams.get("sessionToken")) {
|
|
509
|
+
throw new Error("Server verification URL is missing authoritative session context");
|
|
510
|
+
}
|
|
511
|
+
const url = new URL(this.applyLocalVerifyOverride(serverUrl.toString()));
|
|
512
|
+
url.searchParams.set("mode", this.config.mode);
|
|
712
513
|
if (options.skipIntro) {
|
|
713
|
-
|
|
514
|
+
url.searchParams.set("skip_intro", "true");
|
|
714
515
|
}
|
|
715
516
|
if (options.autoReturn) {
|
|
716
|
-
|
|
517
|
+
url.searchParams.set("auto_return", "true");
|
|
717
518
|
}
|
|
718
519
|
if (language) {
|
|
719
|
-
|
|
520
|
+
url.searchParams.set("lang", language);
|
|
720
521
|
}
|
|
721
|
-
return
|
|
522
|
+
return url.toString();
|
|
722
523
|
}
|
|
723
524
|
/**
|
|
724
525
|
* Redirect in same tab
|
|
@@ -976,22 +777,33 @@ var _VerificationSDK = class _VerificationSDK {
|
|
|
976
777
|
* Get Portal API URL based on environment and brand
|
|
977
778
|
*/
|
|
978
779
|
getPortalApiUrl() {
|
|
979
|
-
|
|
780
|
+
const trustedApiUrl = this.getUrlConfig().apiUrl;
|
|
781
|
+
if (!this.config.apiUrl) {
|
|
782
|
+
return trustedApiUrl;
|
|
783
|
+
}
|
|
784
|
+
const configuredOrigin = this.getUrlOrigin(this.config.apiUrl);
|
|
785
|
+
const trustedOrigin = this.getUrlOrigin(trustedApiUrl);
|
|
786
|
+
if (configuredOrigin === trustedOrigin || this.getLocalOrigin(this.config.apiUrl)) {
|
|
980
787
|
return this.config.apiUrl;
|
|
981
788
|
}
|
|
982
|
-
|
|
789
|
+
throw new Error("apiUrl must use the selected brand/environment service");
|
|
983
790
|
}
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
791
|
+
validateServiceOverrides() {
|
|
792
|
+
const trusted = this.getUrlConfig();
|
|
793
|
+
if (this.config.apiUrl) {
|
|
794
|
+
const configuredOrigin = this.getUrlOrigin(this.config.apiUrl);
|
|
795
|
+
const trustedOrigin = this.getUrlOrigin(trusted.apiUrl);
|
|
796
|
+
if (configuredOrigin !== trustedOrigin && !this.getLocalOrigin(this.config.apiUrl)) {
|
|
797
|
+
throw new Error("apiUrl must use the selected brand/environment service");
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
if (this.config.verifyUrl) {
|
|
801
|
+
const configuredOrigin = this.getUrlOrigin(this.config.verifyUrl);
|
|
802
|
+
const trustedOrigin = this.getUrlOrigin(trusted.verifyUiUrl);
|
|
803
|
+
if (configuredOrigin !== trustedOrigin && !this.getLocalOrigin(this.config.verifyUrl)) {
|
|
804
|
+
throw new Error("verifyUrl must use the selected brand/environment service");
|
|
805
|
+
}
|
|
806
|
+
}
|
|
995
807
|
}
|
|
996
808
|
/**
|
|
997
809
|
* Detect if this is a public key (pk_ prefix) vs private key (sk_ prefix)
|
|
@@ -1003,7 +815,7 @@ var _VerificationSDK = class _VerificationSDK {
|
|
|
1003
815
|
* Create session internally for public keys
|
|
1004
816
|
*/
|
|
1005
817
|
async createInternalSession(options) {
|
|
1006
|
-
var _a, _b
|
|
818
|
+
var _a, _b;
|
|
1007
819
|
try {
|
|
1008
820
|
const portalApiUrl = this.getPortalApiUrl();
|
|
1009
821
|
const response = await fetch(`${portalApiUrl}/api/v1/sessions/create`, {
|
|
@@ -1016,9 +828,6 @@ var _VerificationSDK = class _VerificationSDK {
|
|
|
1016
828
|
merchantId: this.config.apiKey,
|
|
1017
829
|
returnUrl: this.config.returnUrl,
|
|
1018
830
|
cancelUrl: this.config.cancelUrl,
|
|
1019
|
-
challengeAge: options.challengeAge,
|
|
1020
|
-
verificationMode: options.verificationMode,
|
|
1021
|
-
faceMatchEnabled: (_a = options.faceMatchEnabled) != null ? _a : this.config.faceMatchEnabled,
|
|
1022
831
|
merchantName: document.title || window.location.hostname,
|
|
1023
832
|
externalUserId: options.externalUserId
|
|
1024
833
|
})
|
|
@@ -1028,7 +837,7 @@ var _VerificationSDK = class _VerificationSDK {
|
|
|
1028
837
|
const errorCode = errorData == null ? void 0 : errorData.code;
|
|
1029
838
|
if (this.isBillingBlockError(errorCode)) {
|
|
1030
839
|
const language = options.language || this.config.language;
|
|
1031
|
-
this.openBillingBlockPage(errorCode,
|
|
840
|
+
this.openBillingBlockPage(errorCode, language);
|
|
1032
841
|
}
|
|
1033
842
|
throw new Error(
|
|
1034
843
|
`Failed to create session: ${response.status} ${response.statusText}. ${errorData.message || ""}`
|
|
@@ -1042,17 +851,6 @@ var _VerificationSDK = class _VerificationSDK {
|
|
|
1042
851
|
if (sessionData.verifyUrl) {
|
|
1043
852
|
this.lastVerifyUrl = sessionData.verifyUrl;
|
|
1044
853
|
}
|
|
1045
|
-
if (sessionData.sessionToken) {
|
|
1046
|
-
this.lastSessionToken = sessionData.sessionToken;
|
|
1047
|
-
}
|
|
1048
|
-
if (sessionData.handoffToken) {
|
|
1049
|
-
this.temporaryHandoffToken = sessionData.handoffToken;
|
|
1050
|
-
}
|
|
1051
|
-
if (typeof sessionData.sandboxMode === "boolean") {
|
|
1052
|
-
this.lastSandboxMode = sessionData.sandboxMode;
|
|
1053
|
-
} else {
|
|
1054
|
-
this.lastSandboxMode = null;
|
|
1055
|
-
}
|
|
1056
854
|
logSecurityEvent("INTERNAL_SESSION_CREATED", {
|
|
1057
855
|
sessionId: sessionId.substring(0, 8) + "...",
|
|
1058
856
|
environment: this.config.environment,
|
|
@@ -1066,23 +864,20 @@ var _VerificationSDK = class _VerificationSDK {
|
|
|
1066
864
|
environment: this.config.environment,
|
|
1067
865
|
apiKeyType: "public"
|
|
1068
866
|
}, this.brandConstants.name);
|
|
1069
|
-
(
|
|
867
|
+
(_b = (_a = this.config).onError) == null ? void 0 : _b.call(_a, error);
|
|
1070
868
|
throw new Error(`Failed to create verification session: ${errorMessage}`);
|
|
1071
869
|
}
|
|
1072
870
|
}
|
|
1073
871
|
isBillingBlockError(code) {
|
|
1074
872
|
return code === "SUBSCRIPTION_REQUIRED" || code === "PLAN_LIMIT_REACHED" || code === "SANDBOX_LIMIT_REACHED";
|
|
1075
873
|
}
|
|
1076
|
-
openBillingBlockPage(code,
|
|
874
|
+
openBillingBlockPage(code, language) {
|
|
1077
875
|
var _a, _b, _c, _d;
|
|
1078
876
|
try {
|
|
1079
|
-
const baseUrl =
|
|
877
|
+
const baseUrl = getEnvironmentUrl(this.config.environment, this.getUrlConfig());
|
|
1080
878
|
const resolvedUrl = this.applyLocalVerifyOverride(baseUrl);
|
|
1081
879
|
const url = new URL(resolvedUrl);
|
|
1082
880
|
url.searchParams.set("blocked", code);
|
|
1083
|
-
if (portalUrl) {
|
|
1084
|
-
url.searchParams.set("portalUrl", portalUrl);
|
|
1085
|
-
}
|
|
1086
881
|
const effectiveLanguage = language || this.config.language;
|
|
1087
882
|
if (effectiveLanguage) {
|
|
1088
883
|
url.searchParams.set("lang", effectiveLanguage);
|
|
@@ -1145,6 +940,13 @@ var _VerificationSDK = class _VerificationSDK {
|
|
|
1145
940
|
}
|
|
1146
941
|
return null;
|
|
1147
942
|
}
|
|
943
|
+
getUrlOrigin(urlValue) {
|
|
944
|
+
try {
|
|
945
|
+
return new URL(urlValue).origin;
|
|
946
|
+
} catch (e) {
|
|
947
|
+
return null;
|
|
948
|
+
}
|
|
949
|
+
}
|
|
1148
950
|
applyLocalVerifyOverride(rawUrl) {
|
|
1149
951
|
const overrideOrigin = this.getLocalOrigin(this.config.verifyUrl || null);
|
|
1150
952
|
if (!overrideOrigin) {
|
|
@@ -1160,9 +962,6 @@ var _VerificationSDK = class _VerificationSDK {
|
|
|
1160
962
|
return rawUrl;
|
|
1161
963
|
}
|
|
1162
964
|
}
|
|
1163
|
-
getHmacSecret() {
|
|
1164
|
-
return this.config.environment === "staging" ? this.brandConstants.hmacSecretStaging : this.brandConstants.hmacSecretProd;
|
|
1165
|
-
}
|
|
1166
965
|
};
|
|
1167
966
|
// Local override hostnames allowed for internal testing
|
|
1168
967
|
_VerificationSDK.LOCAL_HOSTNAMES = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1"]);
|
|
@@ -1175,28 +974,18 @@ var BRAND_URLS = {
|
|
|
1175
974
|
verifyUiUrl: "https://av.safepassageapp.com",
|
|
1176
975
|
engineUrl: "https://engine.safepassageapp.com",
|
|
1177
976
|
wsUrl: "wss://engine.safepassageapp.com/api/websocket/stream",
|
|
1178
|
-
trustedOrigins: [
|
|
1179
|
-
"https://av.safepassageapp.com",
|
|
1180
|
-
"https://portal.safepassageapp.com",
|
|
1181
|
-
"https://api.safepassageapp.com"
|
|
1182
|
-
]
|
|
977
|
+
trustedOrigins: ["https://av.safepassageapp.com"]
|
|
1183
978
|
},
|
|
1184
979
|
staging: {
|
|
1185
|
-
apiUrl: "https://api.
|
|
1186
|
-
verifyUiUrl: "https://
|
|
1187
|
-
engineUrl: "https://engine.
|
|
1188
|
-
wsUrl: "wss://engine.
|
|
1189
|
-
trustedOrigins: [
|
|
1190
|
-
"https://av.verityav-staging-usw1a.safepassageapp.com",
|
|
1191
|
-
"https://portal.verityav-staging-usw1a.safepassageapp.com",
|
|
1192
|
-
"https://api.verityav-staging-usw1a.safepassageapp.com"
|
|
1193
|
-
]
|
|
980
|
+
apiUrl: "https://api.staging.safepassageapp.com",
|
|
981
|
+
verifyUiUrl: "https://verify.staging.safepassageapp.com",
|
|
982
|
+
engineUrl: "https://engine.staging.safepassageapp.com",
|
|
983
|
+
wsUrl: "wss://engine.staging.safepassageapp.com/api/websocket/stream",
|
|
984
|
+
trustedOrigins: ["https://verify.staging.safepassageapp.com"]
|
|
1194
985
|
}
|
|
1195
986
|
};
|
|
1196
987
|
var BRAND_CONSTANTS = {
|
|
1197
988
|
name: "SafePassage",
|
|
1198
|
-
hmacSecretProd: "safepassage-prod-hmac-2025",
|
|
1199
|
-
hmacSecretStaging: "safepassage-stage-hmac-2025",
|
|
1200
989
|
messageType: "safepassage:verification:complete",
|
|
1201
990
|
legacyMessageType: "safepassage-verification",
|
|
1202
991
|
popupName: "safepassage-verify",
|
|
@@ -1209,7 +998,7 @@ var SafePassage = class extends VerificationSDK {
|
|
|
1209
998
|
super(config, BRAND_URLS, BRAND_CONSTANTS);
|
|
1210
999
|
}
|
|
1211
1000
|
};
|
|
1212
|
-
var VERSION = "3.5.
|
|
1001
|
+
var VERSION = "3.5.6";
|
|
1213
1002
|
SafePassage.VERSION = VERSION;
|
|
1214
1003
|
if (typeof window !== "undefined") {
|
|
1215
1004
|
setupPolyfills();
|