@safepassage/sdk 3.0.3 → 3.0.5

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.
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Crypto utilities for SafePassage SDK - Browser compatible
3
+ *
4
+ * Provides cryptographic functions for the SafePassage SDK using Web Crypto API.
5
+ * These utilities handle HMAC signing, state parameter protection, and secure
6
+ * token generation for client-side security measures.
7
+ *
8
+ * IMPORTANT SECURITY NOTE:
9
+ * Client-side cryptography provides defense-in-depth but cannot be considered
10
+ * secure against determined attackers. True security comes from server-side
11
+ * validation. These functions are primarily for tamper resistance and
12
+ * integrity checking.
13
+ *
14
+ * Features:
15
+ * - HMAC-SHA256 signing and verification using Web Crypto API
16
+ * - Constant-time string comparison to prevent timing attacks
17
+ * - Secure random token generation
18
+ * - Signed state parameter creation and parsing
19
+ * - Environment-specific secret derivation
20
+ * - Timestamp and nonce-based replay protection
21
+ *
22
+ * @author SafePassage Engineering
23
+ * @version 1.0.0
24
+ */
25
+ /**
26
+ * Generate HMAC-SHA256 signature using Web Crypto API
27
+ *
28
+ * Creates an HMAC-SHA256 signature for the given data using the provided secret.
29
+ * Uses the browser's Web Crypto API for cryptographic operations.
30
+ *
31
+ * SECURITY NOTE: This is client-side HMAC where the secret is known to the client.
32
+ * It provides tamper resistance but not true security. Server-side verification
33
+ * is required for actual security.
34
+ *
35
+ * @param {string} data - Data to sign
36
+ * @param {string} secret - HMAC secret key
37
+ * @returns {Promise<string>} Promise resolving to hex-encoded HMAC signature
38
+ * @export
39
+ */
40
+ export declare function generateHMAC(data: string, secret: string): Promise<string>;
41
+ /**
42
+ * Verify HMAC-SHA256 signature using Web Crypto API
43
+ *
44
+ * Verifies an HMAC-SHA256 signature against the provided data and secret.
45
+ * Uses constant-time comparison to prevent timing attacks.
46
+ *
47
+ * @param {string} data - Original data that was signed
48
+ * @param {string} signature - HMAC signature to verify
49
+ * @param {string} secret - HMAC secret key
50
+ * @returns {Promise<boolean>} Promise resolving to true if signature is valid
51
+ * @export
52
+ */
53
+ export declare function verifyHMAC(data: string, signature: string, secret: string): Promise<boolean>;
54
+ /**
55
+ * Generate a secure random token for client-side use
56
+ *
57
+ * Creates a cryptographically secure random token using the browser's
58
+ * crypto.getRandomValues() function.
59
+ *
60
+ * @param {number} [length=32] - Token length in bytes
61
+ * @returns {string} Hex-encoded random token
62
+ * @export
63
+ */
64
+ export declare function generateSecureToken(length?: number): string;
65
+ /**
66
+ * Environment-specific secret derivation
67
+ *
68
+ * Returns environment-specific secrets for HMAC signing. In a real implementation,
69
+ * this would derive from environment variables. For client-side use, this provides
70
+ * basic tamper resistance only.
71
+ *
72
+ * WARNING: Client-side secrets are NOT secure against determined attackers.
73
+ * This is defense-in-depth only. Real security comes from server-side validation.
74
+ *
75
+ * @param {'production' | 'staging' | 'development'} environment - Target environment
76
+ * @returns {string} Environment-specific secret
77
+ * @export
78
+ */
79
+ export declare function getSigningSecret(environment: 'production' | 'staging' | 'development'): string;
80
+ /**
81
+ * Create signed state parameter with timestamp and integrity protection
82
+ *
83
+ * Creates a signed state parameter containing the payload with added timestamp
84
+ * and nonce for freshness and replay protection. The entire payload is signed
85
+ * using HMAC-SHA256 and base64 encoded for URL safety.
86
+ *
87
+ * @param {unknown} payload - Data to include in state parameter
88
+ * @param {'production' | 'staging' | 'development'} environment - Target environment
89
+ * @returns {Promise<string>} Promise resolving to base64-encoded signed state
90
+ * @export
91
+ */
92
+ export declare function createSignedState(payload: unknown, environment: 'production' | 'staging' | 'development'): Promise<string>;
93
+ /**
94
+ * Verify and parse signed state parameter
95
+ *
96
+ * Parses and verifies a signed state parameter, checking the HMAC signature
97
+ * and timestamp freshness. Returns the original payload if validation succeeds.
98
+ *
99
+ * @param {string} signedState - Base64-encoded signed state parameter
100
+ * @param {'production' | 'staging' | 'development'} environment - Source environment
101
+ * @param {number} [maxAge] - Maximum age in milliseconds
102
+ * @returns {Promise<unknown | null>} Promise resolving to payload or null if invalid
103
+ * @export
104
+ */
105
+ export declare function parseSignedState(signedState: string, environment: 'production' | 'staging' | 'development', maxAge?: number): Promise<unknown | null>;
@@ -0,0 +1,210 @@
1
+ /**
2
+ * Crypto utilities for SafePassage SDK - Browser compatible
3
+ *
4
+ * Provides cryptographic functions for the SafePassage SDK using Web Crypto API.
5
+ * These utilities handle HMAC signing, state parameter protection, and secure
6
+ * token generation for client-side security measures.
7
+ *
8
+ * IMPORTANT SECURITY NOTE:
9
+ * Client-side cryptography provides defense-in-depth but cannot be considered
10
+ * secure against determined attackers. True security comes from server-side
11
+ * validation. These functions are primarily for tamper resistance and
12
+ * integrity checking.
13
+ *
14
+ * Features:
15
+ * - HMAC-SHA256 signing and verification using Web Crypto API
16
+ * - Constant-time string comparison to prevent timing attacks
17
+ * - Secure random token generation
18
+ * - Signed state parameter creation and parsing
19
+ * - Environment-specific secret derivation
20
+ * - Timestamp and nonce-based replay protection
21
+ *
22
+ * @author SafePassage Engineering
23
+ * @version 1.0.0
24
+ */
25
+ import { STATE_EXPIRY_MS } from './validation';
26
+ /**
27
+ * Generate HMAC-SHA256 signature using Web Crypto API
28
+ *
29
+ * Creates an HMAC-SHA256 signature for the given data using the provided secret.
30
+ * Uses the browser's Web Crypto API for cryptographic operations.
31
+ *
32
+ * SECURITY NOTE: This is client-side HMAC where the secret is known to the client.
33
+ * It provides tamper resistance but not true security. Server-side verification
34
+ * is required for actual security.
35
+ *
36
+ * @param {string} data - Data to sign
37
+ * @param {string} secret - HMAC secret key
38
+ * @returns {Promise<string>} Promise resolving to hex-encoded HMAC signature
39
+ * @export
40
+ */
41
+ export async function generateHMAC(data, secret) {
42
+ // Encode the secret and data as Uint8Array
43
+ const encoder = new TextEncoder();
44
+ const keyData = encoder.encode(secret);
45
+ const dataBuffer = encoder.encode(data);
46
+ // Import the secret as a key
47
+ const key = await crypto.subtle.importKey('raw', keyData, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
48
+ // Generate the signature
49
+ const signature = await crypto.subtle.sign('HMAC', key, dataBuffer);
50
+ // Convert to hex string
51
+ return Array.from(new Uint8Array(signature))
52
+ .map((b) => b.toString(16).padStart(2, '0'))
53
+ .join('');
54
+ }
55
+ /**
56
+ * Verify HMAC-SHA256 signature using Web Crypto API
57
+ *
58
+ * Verifies an HMAC-SHA256 signature against the provided data and secret.
59
+ * Uses constant-time comparison to prevent timing attacks.
60
+ *
61
+ * @param {string} data - Original data that was signed
62
+ * @param {string} signature - HMAC signature to verify
63
+ * @param {string} secret - HMAC secret key
64
+ * @returns {Promise<boolean>} Promise resolving to true if signature is valid
65
+ * @export
66
+ */
67
+ export async function verifyHMAC(data, signature, secret) {
68
+ try {
69
+ const expectedSignature = await generateHMAC(data, secret);
70
+ return constantTimeCompare(signature, expectedSignature);
71
+ }
72
+ catch {
73
+ return false;
74
+ }
75
+ }
76
+ /**
77
+ * Constant-time string comparison to prevent timing attacks
78
+ *
79
+ * Compares two strings in constant time to prevent timing-based attacks
80
+ * that could leak information about the expected signature.
81
+ *
82
+ * @param {string} a - First string to compare
83
+ * @param {string} b - Second string to compare
84
+ * @returns {boolean} True if strings are equal
85
+ */
86
+ function constantTimeCompare(a, b) {
87
+ if (a.length !== b.length) {
88
+ return false;
89
+ }
90
+ let result = 0;
91
+ for (let i = 0; i < a.length; i++) {
92
+ result |= a.charCodeAt(i) ^ b.charCodeAt(i);
93
+ }
94
+ return result === 0;
95
+ }
96
+ /**
97
+ * Generate a secure random token for client-side use
98
+ *
99
+ * Creates a cryptographically secure random token using the browser's
100
+ * crypto.getRandomValues() function.
101
+ *
102
+ * @param {number} [length=32] - Token length in bytes
103
+ * @returns {string} Hex-encoded random token
104
+ * @export
105
+ */
106
+ export function generateSecureToken(length = 32) {
107
+ const array = new Uint8Array(length);
108
+ crypto.getRandomValues(array);
109
+ return Array.from(array, (b) => b.toString(16).padStart(2, '0')).join('');
110
+ }
111
+ /**
112
+ * Environment-specific secret derivation
113
+ *
114
+ * Returns environment-specific secrets for HMAC signing. In a real implementation,
115
+ * this would derive from environment variables. For client-side use, this provides
116
+ * basic tamper resistance only.
117
+ *
118
+ * WARNING: Client-side secrets are NOT secure against determined attackers.
119
+ * This is defense-in-depth only. Real security comes from server-side validation.
120
+ *
121
+ * @param {'production' | 'staging' | 'development'} environment - Target environment
122
+ * @returns {string} Environment-specific secret
123
+ * @export
124
+ */
125
+ export function getSigningSecret(environment) {
126
+ // WARNING: Client-side secrets are NOT secure against determined attackers
127
+ // This is defense-in-depth only. Real security comes from server-side validation.
128
+ const baseSecrets = {
129
+ production: 'safepassage-prod-hmac-2025',
130
+ staging: 'safepassage-prod-hmac-2025', // Use production secrets for staging
131
+ development: 'safepassage-dev-hmac-2025',
132
+ };
133
+ return baseSecrets[environment];
134
+ }
135
+ /**
136
+ * Create signed state parameter with timestamp and integrity protection
137
+ *
138
+ * Creates a signed state parameter containing the payload with added timestamp
139
+ * and nonce for freshness and replay protection. The entire payload is signed
140
+ * using HMAC-SHA256 and base64 encoded for URL safety.
141
+ *
142
+ * @param {unknown} payload - Data to include in state parameter
143
+ * @param {'production' | 'staging' | 'development'} environment - Target environment
144
+ * @returns {Promise<string>} Promise resolving to base64-encoded signed state
145
+ * @export
146
+ */
147
+ export async function createSignedState(payload, environment) {
148
+ // Add timestamp for freshness
149
+ const timestampedPayload = {
150
+ ...payload,
151
+ timestamp: Date.now(),
152
+ nonce: generateSecureToken(16), // Add nonce to prevent replay attacks
153
+ };
154
+ const dataString = JSON.stringify(timestampedPayload);
155
+ const secret = getSigningSecret(environment);
156
+ const signature = await generateHMAC(dataString, secret);
157
+ // Combine data and signature
158
+ const signedPayload = {
159
+ data: timestampedPayload,
160
+ signature,
161
+ };
162
+ return btoa(JSON.stringify(signedPayload));
163
+ }
164
+ /**
165
+ * Verify and parse signed state parameter
166
+ *
167
+ * Parses and verifies a signed state parameter, checking the HMAC signature
168
+ * and timestamp freshness. Returns the original payload if validation succeeds.
169
+ *
170
+ * @param {string} signedState - Base64-encoded signed state parameter
171
+ * @param {'production' | 'staging' | 'development'} environment - Source environment
172
+ * @param {number} [maxAge] - Maximum age in milliseconds
173
+ * @returns {Promise<unknown | null>} Promise resolving to payload or null if invalid
174
+ * @export
175
+ */
176
+ export async function parseSignedState(signedState, environment, maxAge = STATE_EXPIRY_MS) {
177
+ try {
178
+ const json = atob(signedState);
179
+ const signedPayload = JSON.parse(json);
180
+ if (!signedPayload.data || !signedPayload.signature) {
181
+ console.warn('SafePassage: Invalid signed state format');
182
+ return null;
183
+ }
184
+ const { data, signature } = signedPayload;
185
+ const dataString = JSON.stringify(data);
186
+ const secret = getSigningSecret(environment);
187
+ // Verify signature
188
+ const isValid = await verifyHMAC(dataString, signature, secret);
189
+ if (!isValid) {
190
+ console.warn('SafePassage: State signature verification failed');
191
+ return null;
192
+ }
193
+ // Check timestamp freshness
194
+ if (data.timestamp) {
195
+ const age = Date.now() - data.timestamp;
196
+ if (age > maxAge) {
197
+ console.warn('SafePassage: State parameter expired', { age, maxAge });
198
+ return null;
199
+ }
200
+ }
201
+ // Remove internal fields before returning
202
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
203
+ const { timestamp, nonce, ...payload } = data;
204
+ return payload;
205
+ }
206
+ catch (error) {
207
+ console.warn('SafePassage: Failed to parse signed state', error);
208
+ return null;
209
+ }
210
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Environment utilities for SafePassage SDK with security enforcement
3
+ */
4
+ export declare function getEnvironmentUrl(environment: 'production' | 'staging' | 'development'): string;
5
+ export declare function getApiUrl(environment: 'production' | 'staging' | 'development'): string;
6
+ /**
7
+ * Detect environment with security warnings
8
+ */
9
+ export declare function detectEnvironment(): 'production' | 'staging' | 'development';
10
+ /**
11
+ * Validate environment configuration on startup
12
+ */
13
+ export declare function validateEnvironmentSecurity(environment: 'production' | 'staging' | 'development'): void;
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Environment utilities for SafePassage SDK with security enforcement
3
+ */
4
+ const ENVIRONMENT_URLS = {
5
+ production: 'https://av.safepassageapp.com',
6
+ staging: 'https://av.safepassageapp.com',
7
+ development: 'http://localhost:5173',
8
+ };
9
+ export function getEnvironmentUrl(environment) {
10
+ const url = ENVIRONMENT_URLS[environment];
11
+ // Security check: Ensure production/staging always use HTTPS
12
+ if ((environment === 'production' || environment === 'staging') &&
13
+ !url.startsWith('https://')) {
14
+ throw new Error(`HTTPS required for ${environment} environment`);
15
+ }
16
+ return url;
17
+ }
18
+ export function getApiUrl(environment) {
19
+ const apiUrls = {
20
+ production: 'https://api.safepassageapp.com',
21
+ staging: 'https://api.safepassageapp.com',
22
+ development: 'http://localhost:3001',
23
+ };
24
+ const url = apiUrls[environment];
25
+ // Security check: Ensure production/staging always use HTTPS
26
+ if ((environment === 'production' || environment === 'staging') &&
27
+ !url.startsWith('https://')) {
28
+ throw new Error(`HTTPS required for API URLs in ${environment} environment`);
29
+ }
30
+ return url;
31
+ }
32
+ /**
33
+ * Detect environment with security warnings
34
+ */
35
+ export function detectEnvironment() {
36
+ const hostname = window.location.hostname;
37
+ if (hostname === 'localhost' ||
38
+ hostname === '127.0.0.1' ||
39
+ hostname.includes('.local')) {
40
+ // Warn if using HTTP in development with non-localhost domains
41
+ if (window.location.protocol === 'http:' &&
42
+ !hostname.match(/^(localhost|127\.0\.0\.1)$/)) {
43
+ console.warn('SafePassage Security Warning: Using HTTP with non-localhost domain in development');
44
+ }
45
+ return 'development';
46
+ }
47
+ if (hostname.includes('staging') || hostname.includes('stage')) {
48
+ // Enforce HTTPS in staging
49
+ if (window.location.protocol !== 'https:') {
50
+ console.error('SafePassage Security Error: HTTPS required in staging environment');
51
+ }
52
+ return 'staging';
53
+ }
54
+ // Production environment - strict HTTPS enforcement
55
+ if (window.location.protocol !== 'https:') {
56
+ console.error('SafePassage Security Error: HTTPS required in production environment');
57
+ }
58
+ return 'production';
59
+ }
60
+ /**
61
+ * Validate environment configuration on startup
62
+ */
63
+ export function validateEnvironmentSecurity(environment) {
64
+ // Check current page protocol
65
+ const isSecure = window.location.protocol === 'https:';
66
+ const hostname = window.location.hostname;
67
+ switch (environment) {
68
+ case 'production':
69
+ if (!isSecure) {
70
+ throw new Error('SafePassage requires HTTPS in production environment');
71
+ }
72
+ break;
73
+ case 'staging':
74
+ if (!isSecure) {
75
+ console.warn('SafePassage Warning: HTTPS strongly recommended in staging environment');
76
+ }
77
+ break;
78
+ case 'development': {
79
+ const isLocalhost = hostname === 'localhost' ||
80
+ hostname === '127.0.0.1' ||
81
+ hostname.includes('.local');
82
+ if (!isSecure && !isLocalhost) {
83
+ console.warn('SafePassage Warning: HTTPS recommended for non-localhost development');
84
+ }
85
+ break;
86
+ }
87
+ }
88
+ // Validate environment URLs
89
+ try {
90
+ getEnvironmentUrl(environment);
91
+ getApiUrl(environment);
92
+ }
93
+ catch (error) {
94
+ throw new Error(`Environment configuration validation failed: ${error}`);
95
+ }
96
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Polyfills for browser compatibility
3
+ */
4
+ /**
5
+ * Polyfill for crypto.randomUUID (not supported in older browsers)
6
+ * Generates a UUID v4 using crypto.getRandomValues
7
+ */
8
+ export declare function setupPolyfills(): void;
9
+ /**
10
+ * Check browser compatibility and warn about unsupported features
11
+ */
12
+ export declare function checkBrowserCompatibility(): void;
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Polyfills for browser compatibility
3
+ */
4
+ /**
5
+ * Polyfill for crypto.randomUUID (not supported in older browsers)
6
+ * Generates a UUID v4 using crypto.getRandomValues
7
+ */
8
+ export function setupPolyfills() {
9
+ // Polyfill crypto.randomUUID if not available
10
+ if (!crypto.randomUUID) {
11
+ crypto.randomUUID =
12
+ function () {
13
+ // Generate 16 random bytes
14
+ const array = new Uint8Array(16);
15
+ crypto.getRandomValues(array);
16
+ // Set version (4) and variant bits
17
+ array[6] = (array[6] & 0x0f) | 0x40; // Version 4
18
+ array[8] = (array[8] & 0x3f) | 0x80; // Variant 10
19
+ // Convert to hex string with dashes
20
+ const hex = Array.from(array)
21
+ .map((b) => b.toString(16).padStart(2, '0'))
22
+ .join('');
23
+ return [
24
+ hex.slice(0, 8),
25
+ hex.slice(8, 12),
26
+ hex.slice(12, 16),
27
+ hex.slice(16, 20),
28
+ hex.slice(20, 32),
29
+ ].join('-');
30
+ };
31
+ }
32
+ // Note: URLSearchParams is widely supported (IE 11 doesn't support it, but we're not targeting IE 11)
33
+ // If needed, a polyfill can be added here
34
+ }
35
+ /**
36
+ * Check browser compatibility and warn about unsupported features
37
+ */
38
+ export function checkBrowserCompatibility() {
39
+ const warnings = [];
40
+ // Check for required features
41
+ if (!window.crypto || !window.crypto.getRandomValues) {
42
+ throw new Error('SafePassage SDK requires Web Crypto API support');
43
+ }
44
+ if (!window.crypto.subtle) {
45
+ throw new Error('SafePassage SDK requires Web Crypto subtle API for HMAC operations');
46
+ }
47
+ // Check for features that we can polyfill
48
+ if (!crypto.randomUUID) {
49
+ warnings.push('crypto.randomUUID not supported, using polyfill');
50
+ }
51
+ if (!window.URLSearchParams) {
52
+ warnings.push('URLSearchParams not supported, consider adding a polyfill for IE 11 support');
53
+ }
54
+ // Check for modern JavaScript features
55
+ try {
56
+ // Test optional chaining
57
+ const test = { a: { b: 1 } };
58
+ const result = test?.a?.b;
59
+ if (result !== 1)
60
+ throw new Error();
61
+ }
62
+ catch {
63
+ warnings.push('Optional chaining (?.) not supported, ensure transpilation for older browsers');
64
+ }
65
+ // Log warnings if any
66
+ if (warnings.length > 0) {
67
+ console.warn('SafePassage SDK Browser Compatibility:', warnings.join('; '));
68
+ }
69
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Security utilities for SafePassage SDK
3
+ * Enhanced origin validation and security enforcement
4
+ */
5
+ /**
6
+ * Validate if an origin is trusted for the given environment
7
+ */
8
+ export declare function isOriginTrusted(origin: string, environment: 'production' | 'staging' | 'development'): boolean;
9
+ /**
10
+ * Enhanced origin validation with logging and strict allowlist
11
+ */
12
+ export declare function validatePostMessageOrigin(event: MessageEvent, environment: 'production' | 'staging' | 'development', allowedCustomOrigins?: string[]): boolean;
13
+ /**
14
+ * Validate SafePassage message format and content
15
+ */
16
+ export declare function validateSafePassageMessage(event: MessageEvent, expectedSessionId: string): {
17
+ isValid: boolean;
18
+ error?: string;
19
+ };
20
+ /**
21
+ * Enforce HTTPS in production environment
22
+ */
23
+ export declare function enforceHTTPS(environment: 'production' | 'staging' | 'development'): void;
24
+ /**
25
+ * Validate URL security for return/cancel URLs
26
+ */
27
+ export declare function validateReturnUrl(url: string, environment: 'production' | 'staging' | 'development'): {
28
+ isValid: boolean;
29
+ error?: string;
30
+ };
31
+ /**
32
+ * Generate secure session ID with entropy validation
33
+ */
34
+ export declare function generateSecureSessionId(): string;
35
+ /**
36
+ * Rate limiting for verification attempts
37
+ */
38
+ declare class VerificationRateLimit {
39
+ private attempts;
40
+ private readonly maxAttempts;
41
+ private readonly timeWindow;
42
+ isAllowed(identifier: string): boolean;
43
+ reset(identifier: string): void;
44
+ }
45
+ export declare const verificationRateLimit: VerificationRateLimit;
46
+ /**
47
+ * Security event logging for monitoring
48
+ */
49
+ export declare function logSecurityEvent(event: string, details: unknown): void;
50
+ export {};