@safepassage/sdk 3.0.8 → 3.0.10
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/dist/core/SafePassageSDK.d.ts +15 -0
- package/dist/core/SafePassageSDK.js +48 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/safepassage.min.js +2 -2
- package/dist/utils/crypto.js +1 -1
- package/dist/utils/environment.js +2 -2
- package/dist/utils/security.js +3 -3
- package/dist/utils/validation.js +6 -1
- package/package.json +1 -1
|
@@ -131,6 +131,11 @@ export declare class SafePassage {
|
|
|
131
131
|
* @private
|
|
132
132
|
*/
|
|
133
133
|
private detectEnvironment;
|
|
134
|
+
/**
|
|
135
|
+
* Get the current environment
|
|
136
|
+
* @returns {string} The current environment (production, staging, or development)
|
|
137
|
+
*/
|
|
138
|
+
getEnvironment(): 'production' | 'staging' | 'development';
|
|
134
139
|
/**
|
|
135
140
|
* Unlock verification process to allow new verifications
|
|
136
141
|
*
|
|
@@ -175,6 +180,16 @@ export declare class SafePassage {
|
|
|
175
180
|
* @private
|
|
176
181
|
*/
|
|
177
182
|
private getPortalApiUrl;
|
|
183
|
+
/**
|
|
184
|
+
* Fetch verify configuration from portal-api
|
|
185
|
+
*
|
|
186
|
+
* Fetches the public verify configuration from portal-api to get
|
|
187
|
+
* environment-specific settings instead of using hardcoded values.
|
|
188
|
+
*
|
|
189
|
+
* @returns {Promise<any>} Verify configuration
|
|
190
|
+
* @private
|
|
191
|
+
*/
|
|
192
|
+
private fetchVerifyConfig;
|
|
178
193
|
/**
|
|
179
194
|
* Get Engine URL based on environment
|
|
180
195
|
*
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
*/
|
|
30
30
|
import { generateState, validateConfig } from '../utils/validation';
|
|
31
31
|
import { getEnvironmentUrl, validateEnvironmentSecurity, } from '../utils/environment';
|
|
32
|
-
import { validatePostMessageOrigin, validateSafePassageMessage, enforceHTTPS, verificationRateLimit, logSecurityEvent, } from '../utils/security';
|
|
32
|
+
import { validatePostMessageOrigin, validateSafePassageMessage, enforceHTTPS, verificationRateLimit, logSecurityEvent, generateSecureSessionId, } from '../utils/security';
|
|
33
33
|
/**
|
|
34
34
|
* SafePassage SDK Main Class
|
|
35
35
|
*
|
|
@@ -186,6 +186,8 @@ export class SafePassage {
|
|
|
186
186
|
const hasExplicitChallengeAge = options.challengeAge !== undefined;
|
|
187
187
|
const hasExplicitVerificationMode = options.verificationMode !== undefined;
|
|
188
188
|
const hasOverrides = hasExplicitChallengeAge || hasExplicitVerificationMode;
|
|
189
|
+
// Fetch verify configuration from portal-api
|
|
190
|
+
const verifyConfig = await this.fetchVerifyConfig();
|
|
189
191
|
const state = await generateState({
|
|
190
192
|
merchantId: this.config.apiKey,
|
|
191
193
|
sessionId: options.sessionId,
|
|
@@ -201,7 +203,7 @@ export class SafePassage {
|
|
|
201
203
|
engineUrl: this.getEngineUrl(),
|
|
202
204
|
wsUrl: this.getWebSocketUrl(),
|
|
203
205
|
environment: this.config.environment,
|
|
204
|
-
features: {
|
|
206
|
+
features: verifyConfig.features || {
|
|
205
207
|
captureMode: 'enhanced_verification',
|
|
206
208
|
testMode: false,
|
|
207
209
|
warmupPeriodMs: 2000,
|
|
@@ -376,6 +378,13 @@ export class SafePassage {
|
|
|
376
378
|
}
|
|
377
379
|
return 'production';
|
|
378
380
|
}
|
|
381
|
+
/**
|
|
382
|
+
* Get the current environment
|
|
383
|
+
* @returns {string} The current environment (production, staging, or development)
|
|
384
|
+
*/
|
|
385
|
+
getEnvironment() {
|
|
386
|
+
return this.config.environment;
|
|
387
|
+
}
|
|
379
388
|
/**
|
|
380
389
|
* Unlock verification process to allow new verifications
|
|
381
390
|
*
|
|
@@ -473,6 +482,42 @@ export class SafePassage {
|
|
|
473
482
|
return 'https://api.safepassageapp.com';
|
|
474
483
|
}
|
|
475
484
|
}
|
|
485
|
+
/**
|
|
486
|
+
* Fetch verify configuration from portal-api
|
|
487
|
+
*
|
|
488
|
+
* Fetches the public verify configuration from portal-api to get
|
|
489
|
+
* environment-specific settings instead of using hardcoded values.
|
|
490
|
+
*
|
|
491
|
+
* @returns {Promise<any>} Verify configuration
|
|
492
|
+
* @private
|
|
493
|
+
*/
|
|
494
|
+
async fetchVerifyConfig() {
|
|
495
|
+
try {
|
|
496
|
+
const portalApiUrl = this.getPortalApiUrl();
|
|
497
|
+
const response = await fetch(`${portalApiUrl}/api/v1/config/verify`, {
|
|
498
|
+
method: 'GET',
|
|
499
|
+
headers: {
|
|
500
|
+
'Content-Type': 'application/json',
|
|
501
|
+
},
|
|
502
|
+
});
|
|
503
|
+
if (!response.ok) {
|
|
504
|
+
throw new Error(`Failed to fetch verify config: ${response.status}`);
|
|
505
|
+
}
|
|
506
|
+
return await response.json();
|
|
507
|
+
}
|
|
508
|
+
catch (error) {
|
|
509
|
+
// If config fetch fails, return default values
|
|
510
|
+
console.warn('Failed to fetch verify config, using defaults:', error);
|
|
511
|
+
return {
|
|
512
|
+
features: {
|
|
513
|
+
captureMode: 'enhanced_verification',
|
|
514
|
+
testMode: false,
|
|
515
|
+
warmupPeriodMs: 2000,
|
|
516
|
+
qualityThreshold: 0.6,
|
|
517
|
+
},
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
}
|
|
476
521
|
/**
|
|
477
522
|
* Get Engine URL based on environment
|
|
478
523
|
*
|
|
@@ -541,7 +586,7 @@ export class SafePassage {
|
|
|
541
586
|
* @private
|
|
542
587
|
*/
|
|
543
588
|
async createInternalSession(options) {
|
|
544
|
-
const sessionId =
|
|
589
|
+
const sessionId = generateSecureSessionId();
|
|
545
590
|
try {
|
|
546
591
|
const portalApiUrl = this.getPortalApiUrl();
|
|
547
592
|
const response = await fetch(`${portalApiUrl}/api/v1/sessions/create`, {
|
package/dist/index.d.ts
CHANGED
|
@@ -18,4 +18,4 @@
|
|
|
18
18
|
*/
|
|
19
19
|
export { SafePassage, SafePassage as default } from './core/SafePassageSDK';
|
|
20
20
|
export type { SafePassageConfig, VerificationOptions, VerificationResult, SessionValidationResponse, } from './types';
|
|
21
|
-
export declare const VERSION = "3.0.
|
|
21
|
+
export declare const VERSION = "3.0.9";
|
package/dist/index.js
CHANGED
|
@@ -25,7 +25,7 @@ if (typeof window !== 'undefined') {
|
|
|
25
25
|
}
|
|
26
26
|
export { SafePassage, SafePassage as default } from './core/SafePassageSDK';
|
|
27
27
|
// Version - Updated to reflect security improvements
|
|
28
|
-
export const VERSION = '3.0.
|
|
28
|
+
export const VERSION = '3.0.9';
|
|
29
29
|
// For UMD builds
|
|
30
30
|
if (typeof window !== 'undefined' && window) {
|
|
31
31
|
// Dynamic import for UMD builds
|
package/dist/safepassage.min.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
/* SafePassage SDK v3.0.
|
|
2
|
-
"use strict";var SafePassageSDK=(()=>{var u=Object.defineProperty;var G=Object.getOwnPropertyDescriptor;var J=Object.getOwnPropertyNames;var X=Object.prototype.hasOwnProperty;var p=(t,e)=>()=>(t&&(e=t(t=0)),e);var g=(t,e)=>{for(var n in e)u(t,n,{get:e[n],enumerable:!0})},Y=(t,e,n,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of J(e))!X.call(t,r)&&r!==n&&u(t,r,{get:()=>e[r],enumerable:!(i=G(e,r))||i.enumerable});return t};var P=t=>Y(u({},"__esModule",{value:!0}),t);function z(t,e){return b[e].includes(t)}function T(t,e,n=[]){let{origin:i}=t;return z(i,e)||n.length>0&&n.some(o=>{if(o.startsWith("*.")){let a=o.slice(2);return i.endsWith(`.${a}`)||i===`https://${a}`||i===`http://${a}`}return i===o})?!0:(console.warn(`SafePassage Security: Blocked PostMessage from untrusted origin: ${i}`,{environment:e,trustedOrigins:b[e],allowedCustomOrigins:n,eventType:t.data?.type}),!1)}function x(t,e){let{data:n}=t;return!n||typeof n!="object"?{isValid:!1,error:"Invalid message format"}:n.type!=="safepassage:verification:complete"?{isValid:!1,error:"Invalid message type"}:!n.sessionId||n.sessionId!==e?{isValid:!1,error:"Session ID mismatch"}:!n.status||!["verified","failed","cancelled"].includes(n.status)?{isValid:!1,error:"Invalid status value"}:{isValid:!0}}function V(t){if(t==="production"&&window.location.protocol!=="https:"){let e=window.location.href.replace("http:","https:");console.error("SafePassage Security: HTTPS required in production. Redirecting...",{current:window.location.href,redirect:e}),window.location.replace(e)}}function h(t,e){try{let n=new URL(t);if(e==="production"&&n.protocol!=="https:")return{isValid:!1,error:"HTTPS required for return URLs in production"};if(e==="development"&&!(n.hostname==="localhost"||n.hostname==="127.0.0.1"||n.hostname.endsWith(".local"))&&n.protocol!=="https:")return{isValid:!1,error:"Non-localhost URLs must use HTTPS"};if(e==="staging"&&n.protocol!=="https:")return{isValid:!1,error:"HTTPS required for return URLs in staging"};let i=[/data:/i,/javascript:/i,/vbscript:/i,/file:/i,/ftp:/i];for(let r of i)if(r.test(t))return{isValid:!1,error:"Blocked suspicious URL scheme"};return{isValid:!0}}catch{return{isValid:!1,error:"Invalid URL format"}}}function s(t,e){console.warn(`SafePassage Security Event: ${t}`,{timestamp:new Date().toISOString(),userAgent:navigator.userAgent,url:window.location.href,...e})}var b,f,L,m=p(()=>{"use strict";b={production:["https://av.safepassageapp.com","https://portal.safepassageapp.com","https://api.safepassageapp.com"],staging:["https://av.safepassageapp.com","https://portal.safepassageapp.com","https://api.safepassageapp.com"],development:["http://localhost:5173","http://localhost:3000","http://localhost:3001","http://localhost:3002","http://127.0.0.1:5173","http://127.0.0.1:3000","http://127.0.0.1:3001","http://127.0.0.1:3002"]};f=class{constructor(){this.attempts=new Map;this.maxAttempts=5;this.timeWindow=6e4}isAllowed(e){let n=Date.now(),r=(this.attempts.get(e)||[]).filter(o=>n-o<this.timeWindow);return r.length>=this.maxAttempts?(console.warn(`SafePassage Security: Rate limit exceeded for ${e}`),!1):(r.push(n),this.attempts.set(e,r),!0)}reset(e){this.attempts.delete(e)}},L=new f});var C={};g(C,{createSignedState:()=>Q,generateHMAC:()=>w,generateSecureToken:()=>R,getSigningSecret:()=>v,parseSignedState:()=>ee,verifyHMAC:()=>M});async function w(t,e){let n=new TextEncoder,i=n.encode(e),r=n.encode(t),o=await crypto.subtle.importKey("raw",i,{name:"HMAC",hash:"SHA-256"},!1,["sign"]),a=await crypto.subtle.sign("HMAC",o,r);return Array.from(new Uint8Array(a)).map(l=>l.toString(16).padStart(2,"0")).join("")}async function M(t,e,n){try{let i=await w(t,n);return Z(e,i)}catch{return!1}}function Z(t,e){if(t.length!==e.length)return!1;let n=0;for(let i=0;i<t.length;i++)n|=t.charCodeAt(i)^e.charCodeAt(i);return n===0}function R(t=32){let e=new Uint8Array(t);return crypto.getRandomValues(e),Array.from(e,n=>n.toString(16).padStart(2,"0")).join("")}function v(t){return{production:"safepassage-prod-hmac-2025",staging:"safepassage-prod-hmac-2025",development:"safepassage-dev-hmac-2025"}[t]}async function Q(t,e){let n={...t,timestamp:Date.now(),nonce:R(16)},i=JSON.stringify(n),r=v(e),o=await w(i,r);return btoa(JSON.stringify({data:n,signature:o}))}async function ee(t,e,n=k){try{let i=atob(t),r=JSON.parse(i);if(!r.data||!r.signature)return console.warn("SafePassage: Invalid signed state format"),null;let{data:o,signature:a}=r,l=JSON.stringify(o),F=v(e);if(!await M(l,a,F))return console.warn("SafePassage: State signature verification failed"),null;if(o.timestamp){let E=Date.now()-o.timestamp;if(E>n)return console.warn("SafePassage: State parameter expired",{age:E,maxAge:n}),null}let{timestamp:ce,nonce:le,...B}=o;return B}catch(i){return console.warn("SafePassage: Failed to parse signed state",i),null}}var _=p(()=>{"use strict";S()});function N(t){if(!t.apiKey)throw new Error("apiKey is required");if(t.apiKey.length>D)throw new Error(`apiKey exceeds maximum length of ${D} characters`);if(!te.test(t.apiKey))throw new Error("Invalid apiKey format. Expected pk_xxx (public) or sk_xxx (private)");if(typeof window<"u"&&t.apiKey.startsWith("sk_"))throw new Error("Secret keys (sk_) should never be used in browser code for security reasons. Secret keys expose your account to unauthorized access if used client-side. Please use your public key (pk_) instead. If you need to use features that require a secret key (like custom challenge age), create the session server-side and pass the sessionId to startVerificationWithSession(). See: https://docs.safepassageapp.com/server-side-sessions");if(!t.returnUrl)throw new Error("returnUrl is required");if(t.returnUrl.length>d)throw new Error(`returnUrl exceeds maximum length of ${d} characters`);if(!t.cancelUrl)throw new Error("cancelUrl is required");if(t.cancelUrl.length>d)throw new Error(`cancelUrl exceeds maximum length of ${d} characters`);let e=ne(),n=h(t.returnUrl,e);if(!n.isValid)throw new Error(`returnUrl validation failed: ${n.error}`);let i=h(t.cancelUrl,e);if(!i.isValid)throw new Error(`cancelUrl validation failed: ${i.error}`);if(t.defaultChallengeAge!==void 0){if(t.defaultChallengeAge<O)throw new Error(`defaultChallengeAge must be at least ${O}`);if(t.defaultChallengeAge>$)throw new Error(`defaultChallengeAge cannot exceed ${$}`)}if(t.defaultVerificationMode&&!["L1","L2"].includes(t.defaultVerificationMode))throw new Error("defaultVerificationMode must be L1 or L2");if(t.mode&&!["redirect","new-tab"].includes(t.mode))throw new Error("mode must be redirect or new-tab")}function ne(){let t=window.location.hostname;return t==="localhost"||t==="127.0.0.1"||t.includes(".local")?"development":t.includes("staging")||t.includes("stage")?"staging":"production"}async function K(t,e){let{createSignedState:n}=await Promise.resolve().then(()=>(_(),C));return n(t,e)}var O,$,d,D,k,te,S=p(()=>{"use strict";m();O=25,$=150,d=2048,D=128,k=6e5,te=/^(pk_|sk_)[a-zA-Z0-9]+$/});function y(t){let e=ie[t];if((t==="production"||t==="staging")&&!e.startsWith("https://"))throw new Error(`HTTPS required for ${t} environment`);return e}function re(t){let n={production:"https://api.safepassageapp.com",staging:"https://api.staging.safepassageapp.com",development:"http://localhost:3001"}[t];if((t==="production"||t==="staging")&&!n.startsWith("https://"))throw new Error(`HTTPS required for API URLs in ${t} environment`);return n}function W(t){let e=window.location.protocol==="https:",n=window.location.hostname;switch(t){case"production":if(!e)throw new Error("SafePassage requires HTTPS in production environment");break;case"staging":e||console.warn("SafePassage Warning: HTTPS strongly recommended in staging environment");break;case"development":{let i=n==="localhost"||n==="127.0.0.1"||n.includes(".local");!e&&!i&&console.warn("SafePassage Warning: HTTPS recommended for non-localhost development");break}}try{y(t),re(t)}catch(i){throw new Error(`Environment configuration validation failed: ${i}`)}}var ie,H=p(()=>{"use strict";ie={production:"https://av.safepassageapp.com",staging:"https://av.staging.safepassageapp.com",development:"http://localhost:5173"}});var q={};g(q,{SafePassage:()=>c,default:()=>oe});var c,oe,I=p(()=>{"use strict";S();H();m();c=class{constructor(e){this.popupWindow=null;this.messageListener=null;this.popupMonitorInterval=null;this.unloadListener=null;this.isVerificationInProgress=!1;this.currentSessionId=null;N(e),this.config={...e,environment:e.environment||this.detectEnvironment(),mode:e.mode||"redirect"},W(this.config.environment),V(this.config.environment),s("SDK_INITIALIZED",{environment:this.config.environment,mode:this.config.mode,origin:window.location.origin,protocol:window.location.protocol,hostname:window.location.hostname}),this.setupAutoCleanup()}async verify(e={}){let n=this.isPublicKey(),i=e.sessionId;if(n&&!i&&(i=await this.createInternalSession(e)),!n&&!i)throw new Error("sessionId is required for private API keys - must be a merchant-generated UUID v4");if(!i)throw new Error("Failed to create or obtain sessionId");if(this.isVerificationInProgress){let r=new Error(`Verification already in progress for session ${this.currentSessionId?.substring(0,8)}...`);throw s("RACE_CONDITION_PREVENTED",{currentSession:this.currentSessionId?.substring(0,8)+"...",attemptedSession:e.sessionId?e.sessionId.substring(0,8)+"...":"undefined",origin:window.location.origin}),this.config.onError?.(r),r}this.isVerificationInProgress=!0,this.currentSessionId=i;try{let r=`${this.config.apiKey}:${window.location.origin}`;if(!L.isAllowed(r)){let a=new Error("Too many verification attempts. Please wait before trying again.");throw s("RATE_LIMIT_EXCEEDED",{apiKey:this.config.apiKey.substring(0,8)+"...",origin:window.location.origin,sessionId:i?i.substring(0,8)+"...":"undefined"}),this.config.onError?.(a),a}let o=await this.buildVerificationUrl({...e,sessionId:i});s("VERIFICATION_INITIATED",{environment:this.config.environment,mode:this.config.mode,sessionId:i?i.substring(0,8)+"...":"undefined",origin:window.location.origin}),this.config.mode==="new-tab"?this.openNewTab(o,i):(this.unlockVerification(),this.redirect(o))}catch(r){throw this.unlockVerification(),r}}async buildVerificationUrl(e){let n=y(this.config.environment),i=e.challengeAge!==void 0,r=e.verificationMode!==void 0,o=i||r,a=await K({merchantId:this.config.apiKey,sessionId:e.sessionId,returnUrl:this.config.returnUrl,cancelUrl:this.config.cancelUrl,challengeAge:e.challengeAge||this.config.defaultChallengeAge,verificationMode:e.verificationMode||this.config.defaultVerificationMode,hasOverrides:o,externalUserId:e.externalUserId,timestamp:Date.now(),apiUrl:this.getPortalApiUrl(),engineUrl:this.getEngineUrl(),wsUrl:this.getWebSocketUrl(),environment:this.config.environment,features:{captureMode:"enhanced_verification",testMode:!1,warmupPeriodMs:2e3,qualityThreshold:.6}},this.config.environment),l=new URLSearchParams({state:a,sessionId:e.sessionId,mode:this.config.mode});return`${n}/?${l.toString()}`}redirect(e){window.location.href=e}openNewTab(e,n){if(this.cleanup(),this.popupMonitorInterval&&(clearInterval(this.popupMonitorInterval),this.popupMonitorInterval=null),this.popupWindow=window.open(e,"safepassage-verify","width=600,height=700"),!this.popupWindow){this.config.onError?.(new Error("Failed to open verification window. Please check popup blocker settings."));return}this.messageListener=i=>{if(!T(i,this.config.environment)){s("POSTMESSAGE_ORIGIN_BLOCKED",{origin:i.origin,environment:this.config.environment,expectedOrigins:`SafePassage trusted origins for ${this.config.environment}`,messageType:i.data?.type});return}let r=x(i,n);if(!r.isValid){s("POSTMESSAGE_VALIDATION_FAILED",{error:r.error,origin:i.origin,sessionId:n.substring(0,8)+"...",messageType:i.data?.type});return}let o={sessionId:i.data.sessionId,status:i.data.status};s("VERIFICATION_COMPLETED",{status:o.status,sessionId:n.substring(0,8)+"...",origin:i.origin}),this.cleanup(),this.unlockVerification(),this.popupMonitorInterval&&(clearInterval(this.popupMonitorInterval),this.popupMonitorInterval=null),o.status==="verified"?this.config.onComplete?.(o):o.status==="cancelled"?this.config.onCancel?.():this.config.onError?.(new Error(`Verification failed: ${o.status}`))},window.addEventListener("message",this.messageListener),this.popupMonitorInterval=setInterval(()=>{this.popupWindow&&this.popupWindow.closed&&(s("POPUP_CLOSED_BY_USER",{sessionId:n.substring(0,8)+"...",environment:this.config.environment}),this.cleanup(),this.unlockVerification(),this.config.onCancel?.())},500)}setupAutoCleanup(){if(this.unloadListener=()=>{s("SDK_AUTO_CLEANUP",{environment:this.config.environment,trigger:"page_unload"}),this.cleanup(),this.unlockVerification()},window.addEventListener("beforeunload",this.unloadListener),window.addEventListener("pagehide",this.unloadListener),window.history&&window.history.pushState){let e=window.history.pushState;window.history.pushState=(...n)=>(this.cleanup(),this.unlockVerification(),e.apply(window.history,n))}}detectEnvironment(){let e=window.location.hostname;return e==="localhost"||e==="127.0.0.1"||e.includes(".local")?"development":e.includes("staging")||e.includes("stage")?"staging":"production"}unlockVerification(){this.isVerificationInProgress=!1,this.currentSessionId=null,s("VERIFICATION_UNLOCKED",{environment:this.config.environment,origin:window.location.origin})}cleanup(){this.popupWindow&&!this.popupWindow.closed&&this.popupWindow.close(),this.popupWindow=null,this.messageListener&&(window.removeEventListener("message",this.messageListener),this.messageListener=null),this.popupMonitorInterval&&(clearInterval(this.popupMonitorInterval),this.popupMonitorInterval=null)}removeAutoCleanupListeners(){this.unloadListener&&(window.removeEventListener("beforeunload",this.unloadListener),window.removeEventListener("pagehide",this.unloadListener),this.unloadListener=null)}destroy(){s("SDK_DESTROYED",{environment:this.config.environment,origin:window.location.origin}),this.cleanup(),this.unlockVerification(),this.removeAutoCleanupListeners()}getPortalApiUrl(){switch(this.config.environment){case"development":return"http://localhost:3001";case"staging":return"https://api.staging.safepassageapp.com";case"production":return"https://api.safepassageapp.com";default:return"https://api.safepassageapp.com"}}getEngineUrl(){switch(this.config.environment){case"development":return"http://localhost:8000";case"staging":return"https://engine.staging.safepassageapp.com";case"production":return"https://engine.safepassageapp.com";default:return"https://engine.safepassageapp.com"}}getWebSocketUrl(){switch(this.config.environment){case"development":return"ws://localhost:8000/safe-passage-llm/api/v1/websocket/stream";case"staging":return"wss://engine.staging.safepassageapp.com/safe-passage-llm/api/v1/websocket/stream";case"production":return"wss://engine.safepassageapp.com/safe-passage-llm/api/v1/websocket/stream";default:return"wss://engine.safepassageapp.com/safe-passage-llm/api/v1/websocket/stream"}}isPublicKey(){return this.config.apiKey.startsWith("pk_")}async createInternalSession(e){let n=crypto.randomUUID();try{let i=this.getPortalApiUrl(),r=await fetch(`${i}/api/v1/sessions/create`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.apiKey}`},body:JSON.stringify({merchantId:this.config.apiKey,sessionId:n,returnUrl:this.config.returnUrl,cancelUrl:this.config.cancelUrl,challengeAge:e.challengeAge,verificationMode:e.verificationMode,merchantName:document.title||window.location.hostname,externalUserId:e.externalUserId})});if(!r.ok){let o=await r.json().catch(()=>({}));throw new Error(`Failed to create session: ${r.status} ${r.statusText}. ${o.message||""}`)}return await r.json(),s("INTERNAL_SESSION_CREATED",{sessionId:n.substring(0,8)+"...",environment:this.config.environment,apiKeyType:"public"}),n}catch(i){let r=i instanceof Error?i.message:String(i);throw s("INTERNAL_SESSION_FAILED",{error:r,environment:this.config.environment,apiKeyType:"public"}),this.config.onError?.(i),new Error(`Failed to create verification session: ${r}`)}}},oe=c});var se={};g(se,{SafePassage:()=>c,VERSION:()=>j,default:()=>c});function U(){crypto.randomUUID||(crypto.randomUUID=function(){let t=new Uint8Array(16);crypto.getRandomValues(t),t[6]=t[6]&15|64,t[8]=t[8]&63|128;let e=Array.from(t).map(n=>n.toString(16).padStart(2,"0")).join("");return[e.slice(0,8),e.slice(8,12),e.slice(12,16),e.slice(16,20),e.slice(20,32)].join("-")})}function A(){let t=[];if(!window.crypto||!window.crypto.getRandomValues)throw new Error("SafePassage SDK requires Web Crypto API support");if(!window.crypto.subtle)throw new Error("SafePassage SDK requires Web Crypto subtle API for HMAC operations");crypto.randomUUID||t.push("crypto.randomUUID not supported, using polyfill"),window.URLSearchParams||t.push("URLSearchParams not supported, consider adding a polyfill for IE 11 support");try{if({a:{b:1}}?.a?.b!==1)throw new Error}catch{t.push("Optional chaining (?.) not supported, ensure transpilation for older browsers")}t.length>0&&console.warn("SafePassage SDK Browser Compatibility:",t.join("; "))}I();typeof window<"u"&&(U(),A());var j="3.0.6";if(typeof window<"u"&&window){let{SafePassage:t}=(I(),P(q)),e=window;e.SafePassage=t,e.SafePassage&&(e.SafePassage.VERSION=j)}return P(se);})();
|
|
1
|
+
/* SafePassage SDK v3.0.10 - Redirect Implementation with Enhanced Security */
|
|
2
|
+
"use strict";var SafePassageSDK=(()=>{var g=Object.defineProperty;var J=Object.getOwnPropertyDescriptor;var X=Object.getOwnPropertyNames;var Y=Object.prototype.hasOwnProperty;var p=(t,e)=>()=>(t&&(e=t(t=0)),e);var f=(t,e)=>{for(var n in e)g(t,n,{get:e[n],enumerable:!0})},z=(t,e,n,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of X(e))!Y.call(t,r)&&r!==n&&g(t,r,{get:()=>e[r],enumerable:!(i=J(e,r))||i.enumerable});return t};var U=t=>z(g({},"__esModule",{value:!0}),t);function Z(t,e){return T[e].includes(t)}function x(t,e,n=[]){let{origin:i}=t;return Z(i,e)||n.length>0&&n.some(o=>{if(o.startsWith("*.")){let a=o.slice(2);return i.endsWith(`.${a}`)||i===`https://${a}`||i===`http://${a}`}return i===o})?!0:(console.warn(`SafePassage Security: Blocked PostMessage from untrusted origin: ${i}`,{environment:e,trustedOrigins:T[e],allowedCustomOrigins:n,eventType:t.data?.type}),!1)}function V(t,e){let{data:n}=t;return!n||typeof n!="object"?{isValid:!1,error:"Invalid message format"}:n.type!=="safepassage:verification:complete"?{isValid:!1,error:"Invalid message type"}:!n.sessionId||n.sessionId!==e?{isValid:!1,error:"Session ID mismatch"}:!n.status||!["verified","failed","cancelled"].includes(n.status)?{isValid:!1,error:"Invalid status value"}:{isValid:!0}}function M(t){if(t==="production"&&window.location.protocol!=="https:"){let e=window.location.href.replace("http:","https:");console.error("SafePassage Security: HTTPS required in production. Redirecting...",{current:window.location.href,redirect:e}),window.location.replace(e)}}function m(t,e){try{let n=new URL(t);if(e==="production"&&n.protocol!=="https:")return{isValid:!1,error:"HTTPS required for return URLs in production"};if(e==="development"&&!(n.hostname==="localhost"||n.hostname==="127.0.0.1"||n.hostname.endsWith(".local"))&&n.protocol!=="https:")return{isValid:!1,error:"Non-localhost URLs must use HTTPS"};if(e==="staging"&&n.protocol!=="https:")return{isValid:!1,error:"HTTPS required for return URLs in staging"};let i=[/data:/i,/javascript:/i,/vbscript:/i,/file:/i,/ftp:/i];for(let r of i)if(r.test(t))return{isValid:!1,error:"Blocked suspicious URL scheme"};return{isValid:!0}}catch{return{isValid:!1,error:"Invalid URL format"}}}function L(){if(crypto.randomUUID)return crypto.randomUUID();let t=new Uint8Array(16);crypto.getRandomValues(t);let e=Array.from(t).map(n=>n.toString(16).padStart(2,"0")).join("");return[e.slice(0,8),e.slice(8,12),"4"+e.slice(13,16),(parseInt(e.slice(16,17),16)&3|8).toString(16)+e.slice(17,20),e.slice(20,32)].join("-")}function s(t,e){console.warn(`SafePassage Security Event: ${t}`,{timestamp:new Date().toISOString(),userAgent:navigator.userAgent,url:window.location.href,...e})}var T,h,C,w=p(()=>{"use strict";T={production:["https://av.safepassageapp.com","https://portal.safepassageapp.com","https://api.safepassageapp.com"],staging:["https://av-staging.safepassageapp.com","https://portal-staging.safepassageapp.com","https://api-staging.safepassageapp.com"],development:["http://localhost:5173","http://localhost:3000","http://localhost:3001","http://localhost:3002","http://127.0.0.1:5173","http://127.0.0.1:3000","http://127.0.0.1:3001","http://127.0.0.1:3002"]};h=class{constructor(){this.attempts=new Map;this.maxAttempts=5;this.timeWindow=6e4}isAllowed(e){let n=Date.now(),r=(this.attempts.get(e)||[]).filter(o=>n-o<this.timeWindow);return r.length>=this.maxAttempts?(console.warn(`SafePassage Security: Rate limit exceeded for ${e}`),!1):(r.push(n),this.attempts.set(e,r),!0)}reset(e){this.attempts.delete(e)}},C=new h});var k={};f(k,{createSignedState:()=>ee,generateHMAC:()=>v,generateSecureToken:()=>_,getSigningSecret:()=>S,parseSignedState:()=>te,verifyHMAC:()=>R});async function v(t,e){let n=new TextEncoder,i=n.encode(e),r=n.encode(t),o=await crypto.subtle.importKey("raw",i,{name:"HMAC",hash:"SHA-256"},!1,["sign"]),a=await crypto.subtle.sign("HMAC",o,r);return Array.from(new Uint8Array(a)).map(l=>l.toString(16).padStart(2,"0")).join("")}async function R(t,e,n){try{let i=await v(t,n);return Q(e,i)}catch{return!1}}function Q(t,e){if(t.length!==e.length)return!1;let n=0;for(let i=0;i<t.length;i++)n|=t.charCodeAt(i)^e.charCodeAt(i);return n===0}function _(t=32){let e=new Uint8Array(t);return crypto.getRandomValues(e),Array.from(e,n=>n.toString(16).padStart(2,"0")).join("")}function S(t){return{production:"safepassage-prod-hmac-2025",staging:"safepassage-stage-hmac-2025",development:"safepassage-dev-hmac-2025"}[t]}async function ee(t,e){let n={...t,timestamp:Date.now(),nonce:_(16)},i=JSON.stringify(n),r=S(e),o=await v(i,r);return btoa(JSON.stringify({data:n,signature:o}))}async function te(t,e,n=O){try{let i=atob(t),r=JSON.parse(i);if(!r.data||!r.signature)return console.warn("SafePassage: Invalid signed state format"),null;let{data:o,signature:a}=r,l=JSON.stringify(o),u=S(e);if(!await R(l,a,u))return console.warn("SafePassage: State signature verification failed"),null;if(o.timestamp){let P=Date.now()-o.timestamp;if(P>n)return console.warn("SafePassage: State parameter expired",{age:P,maxAge:n}),null}let{timestamp:le,nonce:pe,...G}=o;return G}catch(i){return console.warn("SafePassage: Failed to parse signed state",i),null}}var $=p(()=>{"use strict";y()});function W(t){if(!t.apiKey)throw new Error("apiKey is required");if(t.apiKey.length>K)throw new Error(`apiKey exceeds maximum length of ${K} characters`);if(!ne.test(t.apiKey))throw new Error("Invalid apiKey format. Expected pk_xxx (public) or sk_xxx (private)");if(typeof window<"u"&&t.apiKey.startsWith("sk_"))throw new Error("Secret keys (sk_) should never be used in browser code for security reasons. Secret keys expose your account to unauthorized access if used client-side. Please use your public key (pk_) instead. If you need to use features that require a secret key (like custom challenge age), create the session server-side and pass the sessionId to startVerificationWithSession(). See: https://docs.safepassageapp.com/server-side-sessions");if(!t.returnUrl)throw new Error("returnUrl is required");if(t.returnUrl.length>d)throw new Error(`returnUrl exceeds maximum length of ${d} characters`);if(!t.cancelUrl)throw new Error("cancelUrl is required");if(t.cancelUrl.length>d)throw new Error(`cancelUrl exceeds maximum length of ${d} characters`);let e=ie(),n=m(t.returnUrl,e);if(!n.isValid)throw new Error(`returnUrl validation failed: ${n.error}`);let i=m(t.cancelUrl,e);if(!i.isValid)throw new Error(`cancelUrl validation failed: ${i.error}`);if(t.defaultChallengeAge!==void 0){if(t.defaultChallengeAge<D)throw new Error(`defaultChallengeAge must be at least ${D}`);if(t.defaultChallengeAge>N)throw new Error(`defaultChallengeAge cannot exceed ${N}`)}if(t.defaultVerificationMode&&!["L1","L2"].includes(t.defaultVerificationMode))throw new Error("defaultVerificationMode must be L1 or L2");if(t.mode&&!["redirect","new-tab"].includes(t.mode))throw new Error("mode must be redirect or new-tab")}function ie(){if(typeof window>"u")return"production";let t=window.location.hostname;return t==="localhost"||t==="127.0.0.1"||t.includes(".local")?"development":t.includes("staging")||t.includes("stage")?"staging":"production"}async function H(t,e){let{createSignedState:n}=await Promise.resolve().then(()=>($(),k));return n(t,e)}var D,N,d,K,O,ne,y=p(()=>{"use strict";w();D=25,N=150,d=2048,K=128,O=6e5,ne=/^(pk_|sk_)[a-zA-Z0-9_]+$/});function E(t){let e=re[t];if((t==="production"||t==="staging")&&!e.startsWith("https://"))throw new Error(`HTTPS required for ${t} environment`);return e}function oe(t){let n={production:"https://api.safepassageapp.com",staging:"https://api-staging.safepassageapp.com",development:"http://localhost:3001"}[t];if((t==="production"||t==="staging")&&!n.startsWith("https://"))throw new Error(`HTTPS required for API URLs in ${t} environment`);return n}function q(t){let e=window.location.protocol==="https:",n=window.location.hostname;switch(t){case"production":if(!e)throw new Error("SafePassage requires HTTPS in production environment");break;case"staging":e||console.warn("SafePassage Warning: HTTPS strongly recommended in staging environment");break;case"development":{let i=n==="localhost"||n==="127.0.0.1"||n.includes(".local");!e&&!i&&console.warn("SafePassage Warning: HTTPS recommended for non-localhost development");break}}try{E(t),oe(t)}catch(i){throw new Error(`Environment configuration validation failed: ${i}`)}}var re,j=p(()=>{"use strict";re={production:"https://av.safepassageapp.com",staging:"https://av-staging.safepassageapp.com",development:"http://localhost:5173"}});var F={};f(F,{SafePassage:()=>c,default:()=>se});var c,se,I=p(()=>{"use strict";y();j();w();c=class{constructor(e){this.popupWindow=null;this.messageListener=null;this.popupMonitorInterval=null;this.unloadListener=null;this.isVerificationInProgress=!1;this.currentSessionId=null;W(e),this.config={...e,environment:e.environment||this.detectEnvironment(),mode:e.mode||"redirect"},q(this.config.environment),M(this.config.environment),s("SDK_INITIALIZED",{environment:this.config.environment,mode:this.config.mode,origin:window.location.origin,protocol:window.location.protocol,hostname:window.location.hostname}),this.setupAutoCleanup()}async verify(e={}){let n=this.isPublicKey(),i=e.sessionId;if(n&&!i&&(i=await this.createInternalSession(e)),!n&&!i)throw new Error("sessionId is required for private API keys - must be a merchant-generated UUID v4");if(!i)throw new Error("Failed to create or obtain sessionId");if(this.isVerificationInProgress){let r=new Error(`Verification already in progress for session ${this.currentSessionId?.substring(0,8)}...`);throw s("RACE_CONDITION_PREVENTED",{currentSession:this.currentSessionId?.substring(0,8)+"...",attemptedSession:e.sessionId?e.sessionId.substring(0,8)+"...":"undefined",origin:window.location.origin}),this.config.onError?.(r),r}this.isVerificationInProgress=!0,this.currentSessionId=i;try{let r=`${this.config.apiKey}:${window.location.origin}`;if(!C.isAllowed(r)){let a=new Error("Too many verification attempts. Please wait before trying again.");throw s("RATE_LIMIT_EXCEEDED",{apiKey:this.config.apiKey.substring(0,8)+"...",origin:window.location.origin,sessionId:i?i.substring(0,8)+"...":"undefined"}),this.config.onError?.(a),a}let o=await this.buildVerificationUrl({...e,sessionId:i});s("VERIFICATION_INITIATED",{environment:this.config.environment,mode:this.config.mode,sessionId:i?i.substring(0,8)+"...":"undefined",origin:window.location.origin}),this.config.mode==="new-tab"?this.openNewTab(o,i):(this.unlockVerification(),this.redirect(o))}catch(r){throw this.unlockVerification(),r}}async buildVerificationUrl(e){let n=E(this.config.environment),i=e.challengeAge!==void 0,r=e.verificationMode!==void 0,o=i||r,a=await this.fetchVerifyConfig(),l=await H({merchantId:this.config.apiKey,sessionId:e.sessionId,returnUrl:this.config.returnUrl,cancelUrl:this.config.cancelUrl,challengeAge:e.challengeAge||this.config.defaultChallengeAge,verificationMode:e.verificationMode||this.config.defaultVerificationMode,hasOverrides:o,externalUserId:e.externalUserId,timestamp:Date.now(),apiUrl:this.getPortalApiUrl(),engineUrl:this.getEngineUrl(),wsUrl:this.getWebSocketUrl(),environment:this.config.environment,features:a.features||{captureMode:"enhanced_verification",testMode:!1,warmupPeriodMs:2e3,qualityThreshold:.6}},this.config.environment),u=new URLSearchParams({state:l,sessionId:e.sessionId,mode:this.config.mode});return`${n}/?${u.toString()}`}redirect(e){window.location.href=e}openNewTab(e,n){if(this.cleanup(),this.popupMonitorInterval&&(clearInterval(this.popupMonitorInterval),this.popupMonitorInterval=null),this.popupWindow=window.open(e,"safepassage-verify","width=600,height=700"),!this.popupWindow){this.config.onError?.(new Error("Failed to open verification window. Please check popup blocker settings."));return}this.messageListener=i=>{if(!x(i,this.config.environment)){s("POSTMESSAGE_ORIGIN_BLOCKED",{origin:i.origin,environment:this.config.environment,expectedOrigins:`SafePassage trusted origins for ${this.config.environment}`,messageType:i.data?.type});return}let r=V(i,n);if(!r.isValid){s("POSTMESSAGE_VALIDATION_FAILED",{error:r.error,origin:i.origin,sessionId:n.substring(0,8)+"...",messageType:i.data?.type});return}let o={sessionId:i.data.sessionId,status:i.data.status};s("VERIFICATION_COMPLETED",{status:o.status,sessionId:n.substring(0,8)+"...",origin:i.origin}),this.cleanup(),this.unlockVerification(),this.popupMonitorInterval&&(clearInterval(this.popupMonitorInterval),this.popupMonitorInterval=null),o.status==="verified"?this.config.onComplete?.(o):o.status==="cancelled"?this.config.onCancel?.():this.config.onError?.(new Error(`Verification failed: ${o.status}`))},window.addEventListener("message",this.messageListener),this.popupMonitorInterval=setInterval(()=>{this.popupWindow&&this.popupWindow.closed&&(s("POPUP_CLOSED_BY_USER",{sessionId:n.substring(0,8)+"...",environment:this.config.environment}),this.cleanup(),this.unlockVerification(),this.config.onCancel?.())},500)}setupAutoCleanup(){if(this.unloadListener=()=>{s("SDK_AUTO_CLEANUP",{environment:this.config.environment,trigger:"page_unload"}),this.cleanup(),this.unlockVerification()},window.addEventListener("beforeunload",this.unloadListener),window.addEventListener("pagehide",this.unloadListener),window.history&&window.history.pushState){let e=window.history.pushState;window.history.pushState=(...n)=>(this.cleanup(),this.unlockVerification(),e.apply(window.history,n))}}detectEnvironment(){let e=window.location.hostname;return e==="localhost"||e==="127.0.0.1"||e.includes(".local")?"development":e.includes("staging")||e.includes("stage")?"staging":"production"}getEnvironment(){return this.config.environment}unlockVerification(){this.isVerificationInProgress=!1,this.currentSessionId=null,s("VERIFICATION_UNLOCKED",{environment:this.config.environment,origin:window.location.origin})}cleanup(){this.popupWindow&&!this.popupWindow.closed&&this.popupWindow.close(),this.popupWindow=null,this.messageListener&&(window.removeEventListener("message",this.messageListener),this.messageListener=null),this.popupMonitorInterval&&(clearInterval(this.popupMonitorInterval),this.popupMonitorInterval=null)}removeAutoCleanupListeners(){this.unloadListener&&(window.removeEventListener("beforeunload",this.unloadListener),window.removeEventListener("pagehide",this.unloadListener),this.unloadListener=null)}destroy(){s("SDK_DESTROYED",{environment:this.config.environment,origin:window.location.origin}),this.cleanup(),this.unlockVerification(),this.removeAutoCleanupListeners()}getPortalApiUrl(){switch(this.config.environment){case"development":return"http://localhost:3001";case"staging":return"https://api.staging.safepassageapp.com";case"production":return"https://api.safepassageapp.com";default:return"https://api.safepassageapp.com"}}async fetchVerifyConfig(){try{let e=this.getPortalApiUrl(),n=await fetch(`${e}/api/v1/config/verify`,{method:"GET",headers:{"Content-Type":"application/json"}});if(!n.ok)throw new Error(`Failed to fetch verify config: ${n.status}`);return await n.json()}catch(e){return console.warn("Failed to fetch verify config, using defaults:",e),{features:{captureMode:"enhanced_verification",testMode:!1,warmupPeriodMs:2e3,qualityThreshold:.6}}}}getEngineUrl(){switch(this.config.environment){case"development":return"http://localhost:8000";case"staging":return"https://engine.staging.safepassageapp.com";case"production":return"https://engine.safepassageapp.com";default:return"https://engine.safepassageapp.com"}}getWebSocketUrl(){switch(this.config.environment){case"development":return"ws://localhost:8000/safe-passage-llm/api/v1/websocket/stream";case"staging":return"wss://engine.staging.safepassageapp.com/safe-passage-llm/api/v1/websocket/stream";case"production":return"wss://engine.safepassageapp.com/safe-passage-llm/api/v1/websocket/stream";default:return"wss://engine.safepassageapp.com/safe-passage-llm/api/v1/websocket/stream"}}isPublicKey(){return this.config.apiKey.startsWith("pk_")}async createInternalSession(e){let n=L();try{let i=this.getPortalApiUrl(),r=await fetch(`${i}/api/v1/sessions/create`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.apiKey}`},body:JSON.stringify({merchantId:this.config.apiKey,sessionId:n,returnUrl:this.config.returnUrl,cancelUrl:this.config.cancelUrl,challengeAge:e.challengeAge,verificationMode:e.verificationMode,merchantName:document.title||window.location.hostname,externalUserId:e.externalUserId})});if(!r.ok){let o=await r.json().catch(()=>({}));throw new Error(`Failed to create session: ${r.status} ${r.statusText}. ${o.message||""}`)}return await r.json(),s("INTERNAL_SESSION_CREATED",{sessionId:n.substring(0,8)+"...",environment:this.config.environment,apiKeyType:"public"}),n}catch(i){let r=i instanceof Error?i.message:String(i);throw s("INTERNAL_SESSION_FAILED",{error:r,environment:this.config.environment,apiKeyType:"public"}),this.config.onError?.(i),new Error(`Failed to create verification session: ${r}`)}}},se=c});var ae={};f(ae,{SafePassage:()=>c,VERSION:()=>B,default:()=>c});function A(){crypto.randomUUID||(crypto.randomUUID=function(){let t=new Uint8Array(16);crypto.getRandomValues(t),t[6]=t[6]&15|64,t[8]=t[8]&63|128;let e=Array.from(t).map(n=>n.toString(16).padStart(2,"0")).join("");return[e.slice(0,8),e.slice(8,12),e.slice(12,16),e.slice(16,20),e.slice(20,32)].join("-")})}function b(){let t=[];if(!window.crypto||!window.crypto.getRandomValues)throw new Error("SafePassage SDK requires Web Crypto API support");if(!window.crypto.subtle)throw new Error("SafePassage SDK requires Web Crypto subtle API for HMAC operations");crypto.randomUUID||t.push("crypto.randomUUID not supported, using polyfill"),window.URLSearchParams||t.push("URLSearchParams not supported, consider adding a polyfill for IE 11 support");try{if({a:{b:1}}?.a?.b!==1)throw new Error}catch{t.push("Optional chaining (?.) not supported, ensure transpilation for older browsers")}t.length>0&&console.warn("SafePassage SDK Browser Compatibility:",t.join("; "))}I();typeof window<"u"&&(A(),b());var B="3.0.9";if(typeof window<"u"&&window){let{SafePassage:t}=(I(),U(F)),e=window;e.SafePassage=t,e.SafePassage&&(e.SafePassage.VERSION=B)}return U(ae);})();
|
|
3
3
|
if(typeof SafePassageSDK !== "undefined" && SafePassageSDK.SafePassage) { window.SafePassage = SafePassageSDK.SafePassage; window.SafePassage.VERSION = SafePassageSDK.VERSION; }
|
package/dist/utils/crypto.js
CHANGED
|
@@ -127,7 +127,7 @@ export function getSigningSecret(environment) {
|
|
|
127
127
|
// This is defense-in-depth only. Real security comes from server-side validation.
|
|
128
128
|
const baseSecrets = {
|
|
129
129
|
production: 'safepassage-prod-hmac-2025',
|
|
130
|
-
staging: 'safepassage-
|
|
130
|
+
staging: 'safepassage-stage-hmac-2025',
|
|
131
131
|
development: 'safepassage-dev-hmac-2025',
|
|
132
132
|
};
|
|
133
133
|
return baseSecrets[environment];
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
const ENVIRONMENT_URLS = {
|
|
5
5
|
production: 'https://av.safepassageapp.com',
|
|
6
|
-
staging: 'https://av
|
|
6
|
+
staging: 'https://av-staging.safepassageapp.com',
|
|
7
7
|
development: 'http://localhost:5173',
|
|
8
8
|
};
|
|
9
9
|
export function getEnvironmentUrl(environment) {
|
|
@@ -18,7 +18,7 @@ export function getEnvironmentUrl(environment) {
|
|
|
18
18
|
export function getApiUrl(environment) {
|
|
19
19
|
const apiUrls = {
|
|
20
20
|
production: 'https://api.safepassageapp.com',
|
|
21
|
-
staging: 'https://api
|
|
21
|
+
staging: 'https://api-staging.safepassageapp.com',
|
|
22
22
|
development: 'http://localhost:3001',
|
|
23
23
|
};
|
|
24
24
|
const url = apiUrls[environment];
|
package/dist/utils/security.js
CHANGED
|
@@ -13,9 +13,9 @@ const TRUSTED_ORIGINS = {
|
|
|
13
13
|
'https://api.safepassageapp.com',
|
|
14
14
|
],
|
|
15
15
|
staging: [
|
|
16
|
-
'https://av.safepassageapp.com',
|
|
17
|
-
'https://portal.safepassageapp.com',
|
|
18
|
-
'https://api.safepassageapp.com',
|
|
16
|
+
'https://av-staging.safepassageapp.com',
|
|
17
|
+
'https://portal-staging.safepassageapp.com',
|
|
18
|
+
'https://api-staging.safepassageapp.com',
|
|
19
19
|
],
|
|
20
20
|
development: [
|
|
21
21
|
'http://localhost:5173',
|
package/dist/utils/validation.js
CHANGED
|
@@ -8,7 +8,7 @@ export const MAX_URL_LENGTH = 2048;
|
|
|
8
8
|
export const MAX_API_KEY_LENGTH = 128;
|
|
9
9
|
export const STATE_EXPIRY_MS = 600000; // 10 minutes
|
|
10
10
|
// SafePassage supports both public (pk_) and private (sk_) API keys
|
|
11
|
-
const API_KEY_PATTERN = /^(pk_|sk_)[a-zA-Z0-
|
|
11
|
+
const API_KEY_PATTERN = /^(pk_|sk_)[a-zA-Z0-9_]+$/;
|
|
12
12
|
const UUID_V4_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
13
13
|
export function validateConfig(config) {
|
|
14
14
|
if (!config.apiKey) {
|
|
@@ -71,6 +71,10 @@ export function validateConfig(config) {
|
|
|
71
71
|
* Detect environment based on current URL
|
|
72
72
|
*/
|
|
73
73
|
function detectEnvironment() {
|
|
74
|
+
// If no window (server environment), default to production for safety
|
|
75
|
+
if (typeof window === 'undefined') {
|
|
76
|
+
return 'production';
|
|
77
|
+
}
|
|
74
78
|
const hostname = window.location.hostname;
|
|
75
79
|
if (hostname === 'localhost' ||
|
|
76
80
|
hostname === '127.0.0.1' ||
|
|
@@ -129,6 +133,7 @@ export async function parseState(state, environment) {
|
|
|
129
133
|
!payload.cancelUrl) {
|
|
130
134
|
return null;
|
|
131
135
|
}
|
|
136
|
+
// Cast to unknown first to satisfy TypeScript's type checking
|
|
132
137
|
return payload;
|
|
133
138
|
}
|
|
134
139
|
// Fallback to legacy base64 format for backwards compatibility
|