@explorins/pers-sdk 1.6.32 → 1.6.36
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 +50 -3
- package/dist/chunks/{pers-sdk-BbflloQE.cjs → pers-sdk-BGDZS3-x.cjs} +393 -19
- package/dist/chunks/pers-sdk-BGDZS3-x.cjs.map +1 -0
- package/dist/chunks/{pers-sdk-Cb3gtslf.js → pers-sdk-Ca36Z7SO.js} +389 -20
- package/dist/chunks/pers-sdk-Ca36Z7SO.js.map +1 -0
- package/dist/core/auth/auth-provider.interface.d.ts +12 -0
- package/dist/core/auth/auth-provider.interface.d.ts.map +1 -1
- package/dist/core/auth/default-auth-provider.d.ts +2 -0
- package/dist/core/auth/default-auth-provider.d.ts.map +1 -1
- package/dist/core/auth/dpop/dpop-manager.d.ts +32 -0
- package/dist/core/auth/dpop/dpop-manager.d.ts.map +1 -0
- package/dist/core/auth/dpop/dpop.types.d.ts +30 -0
- package/dist/core/auth/dpop/dpop.types.d.ts.map +1 -0
- package/dist/core/auth/dpop/index.d.ts +4 -0
- package/dist/core/auth/dpop/index.d.ts.map +1 -0
- package/dist/core/auth/dpop/web-crypto-dpop-provider.d.ts +13 -0
- package/dist/core/auth/dpop/web-crypto-dpop-provider.d.ts.map +1 -0
- package/dist/core/auth/index.d.ts +1 -0
- package/dist/core/auth/index.d.ts.map +1 -1
- package/dist/core/auth/indexed-db-storage.d.ts +24 -0
- package/dist/core/auth/indexed-db-storage.d.ts.map +1 -0
- package/dist/core/auth/token-storage.d.ts +19 -5
- package/dist/core/auth/token-storage.d.ts.map +1 -1
- package/dist/core/index.d.ts +3 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/pers-api-client.d.ts +2 -0
- package/dist/core/pers-api-client.d.ts.map +1 -1
- package/dist/core/pers-config.d.ts +12 -1
- package/dist/core/pers-config.d.ts.map +1 -1
- package/dist/core.cjs +6 -1
- package/dist/core.cjs.map +1 -1
- package/dist/core.js +1 -1
- package/dist/index.cjs +6 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/package.json +2 -2
- package/package.json +2 -2
- package/dist/chunks/pers-sdk-BbflloQE.cjs.map +0 -1
- package/dist/chunks/pers-sdk-Cb3gtslf.js.map +0 -1
- package/dist/legacy-pers-sdk.d.ts +0 -79
- package/dist/legacy-pers-sdk.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -120,9 +120,11 @@ sdk.campaigns.getCampaignService()
|
|
|
120
120
|
## Core Features
|
|
121
121
|
|
|
122
122
|
### Authentication
|
|
123
|
+
- **Secure DPoP (Demonstrating Proof-of-Possession)**: Enabled by default to bind tokens to the client, preventing replay attacks.
|
|
123
124
|
- External JWT integration (Firebase, Auth0, etc.)
|
|
124
125
|
- Native token validation and refresh
|
|
125
126
|
- User and admin authentication flows
|
|
127
|
+
- Flexible storage strategies (LocalStorage, IndexedDB, Memory)
|
|
126
128
|
|
|
127
129
|
### Business Management
|
|
128
130
|
- Business registration and profiles
|
|
@@ -152,16 +154,61 @@ sdk.campaigns.getCampaignService()
|
|
|
152
154
|
## Configuration
|
|
153
155
|
|
|
154
156
|
```typescript
|
|
157
|
+
import { IndexedDBTokenStorage } from '@explorins/pers-sdk/core';
|
|
158
|
+
|
|
155
159
|
interface PersConfig {
|
|
156
160
|
environment?: 'development' | 'staging' | 'production';
|
|
157
|
-
apiVersion?: 'v2';
|
|
158
161
|
apiProjectKey: string;
|
|
159
|
-
|
|
160
|
-
|
|
162
|
+
|
|
163
|
+
// Storage Strategy (Recommended: IndexedDB)
|
|
164
|
+
authStorage?: TokenStorage; // Defaults to LocalStorage if omitted
|
|
165
|
+
|
|
166
|
+
// DPoP Security (Default: enabled)
|
|
167
|
+
dpop?: {
|
|
168
|
+
enabled: boolean;
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
// Advanced overrides
|
|
161
172
|
authProvider?: PersAuthProvider;
|
|
162
173
|
}
|
|
163
174
|
```
|
|
164
175
|
|
|
176
|
+
## Advanced Authentication
|
|
177
|
+
|
|
178
|
+
### Using IndexedDB (Recommended)
|
|
179
|
+
|
|
180
|
+
For better security and performance, use the built-in `IndexedDBTokenStorage` instead of the default LocalStorage.
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
import { PersSDK } from '@explorins/pers-sdk';
|
|
184
|
+
import { IndexedDBTokenStorage } from '@explorins/pers-sdk/core';
|
|
185
|
+
|
|
186
|
+
const sdk = new PersSDK(adapter, {
|
|
187
|
+
environment: 'production',
|
|
188
|
+
apiProjectKey: 'your-key',
|
|
189
|
+
// Activates IndexedDB for tokens and keys
|
|
190
|
+
authStorage: new IndexedDBTokenStorage()
|
|
191
|
+
});
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Custom Storage & Fallbacks
|
|
195
|
+
|
|
196
|
+
You can implement your own storage strategy (e.g., hybrid Fallback) by implementing the `TokenStorage` interface. The SDK provides type-safe key constants.
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
import { TokenStorage, AUTH_STORAGE_KEYS, DPOP_STORAGE_KEYS } from '@explorins/pers-sdk/core';
|
|
200
|
+
|
|
201
|
+
class HybridStorage implements TokenStorage {
|
|
202
|
+
// IMPORTANT: Set to 'false' if your storage backend only supports strings (like LocalStorage).
|
|
203
|
+
// This ensures the SDK generates exportable keys that can be serialized.
|
|
204
|
+
// Set to 'true' (like IndexedDB) to enable high-security non-extractable CryptoKey objects.
|
|
205
|
+
readonly supportsObjects = false;
|
|
206
|
+
|
|
207
|
+
// Your custom implementation...
|
|
208
|
+
// Use AUTH_STORAGE_KEYS.ACCESS_TOKEN, DPOP_STORAGE_KEYS.PRIVATE, etc.
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
165
212
|
## Error Handling
|
|
166
213
|
|
|
167
214
|
```typescript
|
|
@@ -335,14 +335,19 @@ const AUTH_STORAGE_KEYS = {
|
|
|
335
335
|
};
|
|
336
336
|
/**
|
|
337
337
|
* LocalStorage implementation
|
|
338
|
+
* Note: Forces string conversion for compatibility
|
|
338
339
|
*/
|
|
339
340
|
class LocalStorageTokenStorage {
|
|
341
|
+
constructor() {
|
|
342
|
+
this.supportsObjects = false;
|
|
343
|
+
}
|
|
340
344
|
async get(key) {
|
|
341
345
|
return typeof localStorage !== 'undefined' ? localStorage.getItem(key) : null;
|
|
342
346
|
}
|
|
343
347
|
async set(key, value) {
|
|
344
348
|
if (typeof localStorage !== 'undefined') {
|
|
345
|
-
|
|
349
|
+
const stringValue = typeof value === 'string' ? value : JSON.stringify(value);
|
|
350
|
+
localStorage.setItem(key, stringValue);
|
|
346
351
|
}
|
|
347
352
|
}
|
|
348
353
|
async remove(key) {
|
|
@@ -363,6 +368,7 @@ class LocalStorageTokenStorage {
|
|
|
363
368
|
*/
|
|
364
369
|
class MemoryTokenStorage {
|
|
365
370
|
constructor() {
|
|
371
|
+
this.supportsObjects = true;
|
|
366
372
|
this.store = new Map();
|
|
367
373
|
}
|
|
368
374
|
async get(key) {
|
|
@@ -386,26 +392,30 @@ class AuthTokenManager {
|
|
|
386
392
|
this.storage = storage;
|
|
387
393
|
}
|
|
388
394
|
async getAccessToken() {
|
|
389
|
-
|
|
395
|
+
const val = await this.storage.get(AUTH_STORAGE_KEYS.ACCESS_TOKEN);
|
|
396
|
+
// Ensure we return string for tokens
|
|
397
|
+
return typeof val === 'string' ? val : null;
|
|
390
398
|
}
|
|
391
399
|
async setAccessToken(token) {
|
|
392
400
|
await this.storage.set(AUTH_STORAGE_KEYS.ACCESS_TOKEN, token);
|
|
393
401
|
}
|
|
394
402
|
async getRefreshToken() {
|
|
395
|
-
|
|
403
|
+
const val = await this.storage.get(AUTH_STORAGE_KEYS.REFRESH_TOKEN);
|
|
404
|
+
return typeof val === 'string' ? val : null;
|
|
396
405
|
}
|
|
397
406
|
async setRefreshToken(token) {
|
|
398
407
|
await this.storage.set(AUTH_STORAGE_KEYS.REFRESH_TOKEN, token);
|
|
399
408
|
}
|
|
400
409
|
async getProviderToken() {
|
|
401
|
-
|
|
410
|
+
const val = await this.storage.get(AUTH_STORAGE_KEYS.PROVIDER_TOKEN);
|
|
411
|
+
return typeof val === 'string' ? val : null;
|
|
402
412
|
}
|
|
403
413
|
async setProviderToken(token) {
|
|
404
414
|
await this.storage.set(AUTH_STORAGE_KEYS.PROVIDER_TOKEN, token);
|
|
405
415
|
}
|
|
406
416
|
async getAuthType() {
|
|
407
417
|
const authType = await this.storage.get(AUTH_STORAGE_KEYS.AUTH_TYPE);
|
|
408
|
-
return authType;
|
|
418
|
+
return typeof authType === 'string' ? authType : null;
|
|
409
419
|
}
|
|
410
420
|
async setAuthType(authType) {
|
|
411
421
|
await this.storage.set(AUTH_STORAGE_KEYS.AUTH_TYPE, authType);
|
|
@@ -430,6 +440,218 @@ class AuthTokenManager {
|
|
|
430
440
|
}
|
|
431
441
|
}
|
|
432
442
|
|
|
443
|
+
class WebDPoPCryptoProvider {
|
|
444
|
+
get crypto() {
|
|
445
|
+
// Basic environment check
|
|
446
|
+
if (typeof crypto !== 'undefined')
|
|
447
|
+
return crypto;
|
|
448
|
+
if (typeof window !== 'undefined' && window.crypto)
|
|
449
|
+
return window.crypto;
|
|
450
|
+
if (typeof globalThis !== 'undefined' && globalThis.crypto)
|
|
451
|
+
return globalThis.crypto;
|
|
452
|
+
throw new Error('WebCrypto API not found. Please polyfill crypto.subtle or use a different DPoPCryptoProvider.');
|
|
453
|
+
}
|
|
454
|
+
async generateKeyPair(options) {
|
|
455
|
+
const extractable = options?.extractable ?? true;
|
|
456
|
+
const keyPair = await this.crypto.subtle.generateKey({
|
|
457
|
+
name: 'ECDSA',
|
|
458
|
+
namedCurve: 'P-256',
|
|
459
|
+
}, extractable, // Configurable extractability
|
|
460
|
+
['sign', 'verify']);
|
|
461
|
+
const publicKey = await this.crypto.subtle.exportKey('jwk', keyPair.publicKey);
|
|
462
|
+
// If extractable, export private key as JWK (plain object)
|
|
463
|
+
// If NOT extractable, keep it as CryptoKey (native handle)
|
|
464
|
+
// However, DPoPKeyPair interface currently expects JsonWebKey | CryptoKey
|
|
465
|
+
// We need to check DPoPKeyPair type definition.
|
|
466
|
+
// Wait, DPoPKeyPair definition in dpop.types.ts says:
|
|
467
|
+
// publicKey: JsonWebKey; privateKey: JsonWebKey;
|
|
468
|
+
// I need to change that to support CryptoKey.
|
|
469
|
+
// Let's re-read dpop.types.ts
|
|
470
|
+
// I assumed it might be compatible but let's verify.
|
|
471
|
+
// If I change privateKey to unknown or JsonWebKey | CryptoKey, it will be safer.
|
|
472
|
+
let privateKey;
|
|
473
|
+
if (extractable) {
|
|
474
|
+
privateKey = await this.crypto.subtle.exportKey('jwk', keyPair.privateKey);
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
privateKey = keyPair.privateKey;
|
|
478
|
+
}
|
|
479
|
+
return { publicKey, privateKey };
|
|
480
|
+
}
|
|
481
|
+
async signProof(payload, keyPair) {
|
|
482
|
+
// 1. Prepare Header
|
|
483
|
+
const header = {
|
|
484
|
+
typ: 'dpop+jwt',
|
|
485
|
+
alg: 'ES256',
|
|
486
|
+
jwk: keyPair.publicKey
|
|
487
|
+
};
|
|
488
|
+
// 2. Prepare Payload
|
|
489
|
+
// Ensure jti and iat if not present (though Manager usually provides them)
|
|
490
|
+
const finalPayload = {
|
|
491
|
+
...payload,
|
|
492
|
+
iat: payload.iat || Math.floor(Date.now() / 1000),
|
|
493
|
+
jti: payload.jti || this.generateUUID()
|
|
494
|
+
};
|
|
495
|
+
// 3. Encode segments
|
|
496
|
+
const encodedHeader = this.base64UrlEncode(JSON.stringify(header));
|
|
497
|
+
const encodedPayload = this.base64UrlEncode(JSON.stringify(finalPayload));
|
|
498
|
+
const signingInput = `${encodedHeader}.${encodedPayload}`;
|
|
499
|
+
// 4. Import private key for signing
|
|
500
|
+
let privateKey;
|
|
501
|
+
// Check if it's already a CryptoKey (non-extractable handle)
|
|
502
|
+
// We check for 'algorithm' property which signals a CryptoKey object
|
|
503
|
+
if (keyPair.privateKey.algorithm) {
|
|
504
|
+
privateKey = keyPair.privateKey;
|
|
505
|
+
}
|
|
506
|
+
else {
|
|
507
|
+
// Otherwise import from JWK
|
|
508
|
+
privateKey = await this.crypto.subtle.importKey('jwk', keyPair.privateKey, {
|
|
509
|
+
name: 'ECDSA',
|
|
510
|
+
namedCurve: 'P-256',
|
|
511
|
+
}, false, ['sign']);
|
|
512
|
+
}
|
|
513
|
+
// 5. Sign
|
|
514
|
+
const signatureBuffer = await this.crypto.subtle.sign({
|
|
515
|
+
name: 'ECDSA',
|
|
516
|
+
hash: { name: 'SHA-256' },
|
|
517
|
+
}, privateKey, new TextEncoder().encode(signingInput));
|
|
518
|
+
const encodedSignature = this.base64UrlEncodeBuffer(signatureBuffer);
|
|
519
|
+
return `${signingInput}.${encodedSignature}`;
|
|
520
|
+
}
|
|
521
|
+
async hashAccessToken(accessToken) {
|
|
522
|
+
const encoder = new TextEncoder();
|
|
523
|
+
const data = encoder.encode(accessToken);
|
|
524
|
+
const hashBuffer = await this.crypto.subtle.digest('SHA-256', data);
|
|
525
|
+
return this.base64UrlEncodeBuffer(hashBuffer);
|
|
526
|
+
}
|
|
527
|
+
// --- Helpers ---
|
|
528
|
+
generateUUID() {
|
|
529
|
+
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
|
|
530
|
+
return crypto.randomUUID();
|
|
531
|
+
}
|
|
532
|
+
// Fallback for older envs
|
|
533
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
|
534
|
+
const r = (Math.random() * 16) | 0;
|
|
535
|
+
const v = c === 'x' ? r : (r & 0x3) | 0x8;
|
|
536
|
+
return v.toString(16);
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
base64UrlEncode(str) {
|
|
540
|
+
// Use TextEncoder to handle UTF-8 properly
|
|
541
|
+
const encoder = new TextEncoder();
|
|
542
|
+
return this.base64UrlEncodeBuffer(encoder.encode(str));
|
|
543
|
+
}
|
|
544
|
+
base64UrlEncodeBuffer(buffer) {
|
|
545
|
+
const bytes = new Uint8Array(buffer);
|
|
546
|
+
let binary = '';
|
|
547
|
+
const len = bytes.byteLength;
|
|
548
|
+
// Chunk processing for large buffers if needed, but proofs are small
|
|
549
|
+
for (let i = 0; i < len; i++) {
|
|
550
|
+
binary += String.fromCharCode(bytes[i]);
|
|
551
|
+
}
|
|
552
|
+
// btoa is widely available in browsers.
|
|
553
|
+
// In Node (non-globals), we might need Buffer.
|
|
554
|
+
// For universal 'platform agnostic' aiming at standard web-like envs:
|
|
555
|
+
let base64 = '';
|
|
556
|
+
if (typeof btoa === 'function') {
|
|
557
|
+
base64 = btoa(binary);
|
|
558
|
+
}
|
|
559
|
+
else if (typeof Buffer !== 'undefined') {
|
|
560
|
+
base64 = Buffer.from(binary, 'binary').toString('base64');
|
|
561
|
+
}
|
|
562
|
+
else {
|
|
563
|
+
throw new Error('Base64 encoding not supported in this environment');
|
|
564
|
+
}
|
|
565
|
+
return base64
|
|
566
|
+
.replace(/\+/g, '-')
|
|
567
|
+
.replace(/\//g, '_')
|
|
568
|
+
.replace(/=+$/, '');
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
const DPOP_STORAGE_KEYS = {
|
|
573
|
+
PUBLIC: 'pers_dpop_public_key',
|
|
574
|
+
PRIVATE: 'pers_dpop_private_key'
|
|
575
|
+
};
|
|
576
|
+
class DPoPManager {
|
|
577
|
+
constructor(storage, cryptoProvider) {
|
|
578
|
+
this.storage = storage;
|
|
579
|
+
this.memoryKeyPair = null;
|
|
580
|
+
this.cryptoProvider = cryptoProvider || new WebDPoPCryptoProvider();
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Ensures a DPoP key pair exists, loading from storage or generating new one.
|
|
584
|
+
*/
|
|
585
|
+
async ensureKeyPair() {
|
|
586
|
+
if (this.memoryKeyPair)
|
|
587
|
+
return this.memoryKeyPair;
|
|
588
|
+
const storedPublic = await this.storage.get(DPOP_STORAGE_KEYS.PUBLIC);
|
|
589
|
+
const storedPrivate = await this.storage.get(DPOP_STORAGE_KEYS.PRIVATE);
|
|
590
|
+
if (storedPublic && storedPrivate) {
|
|
591
|
+
try {
|
|
592
|
+
// Handle polymorphic storage: String (JSON) or Object (CryptoKey)
|
|
593
|
+
const publicKey = typeof storedPublic === 'string' ? JSON.parse(storedPublic) : storedPublic;
|
|
594
|
+
const privateKey = typeof storedPrivate === 'string' ? JSON.parse(storedPrivate) : storedPrivate;
|
|
595
|
+
this.memoryKeyPair = { publicKey, privateKey };
|
|
596
|
+
return this.memoryKeyPair;
|
|
597
|
+
}
|
|
598
|
+
catch (e) {
|
|
599
|
+
console.warn('Corrupted DPoP keys in storage, regenerating.');
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
// Generate new key pair
|
|
603
|
+
// Adaptation: If storage supports raw objects (like IndexedDB or Native Keychain),
|
|
604
|
+
// we can generate Non-Extractable keys for maximum security.
|
|
605
|
+
// If storage is text-only (LocalStorage), we must use Extractable keys to serialize them.
|
|
606
|
+
const useHighSecurity = !!this.storage.supportsObjects;
|
|
607
|
+
const keyPair = await this.cryptoProvider.generateKeyPair({
|
|
608
|
+
extractable: !useHighSecurity
|
|
609
|
+
});
|
|
610
|
+
// Save to storage
|
|
611
|
+
await this.storage.set(DPOP_STORAGE_KEYS.PUBLIC, keyPair.publicKey);
|
|
612
|
+
await this.storage.set(DPOP_STORAGE_KEYS.PRIVATE, keyPair.privateKey);
|
|
613
|
+
this.memoryKeyPair = keyPair;
|
|
614
|
+
return keyPair;
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Generates the DPoP proof header value.
|
|
618
|
+
* @param method HTTP method
|
|
619
|
+
* @param url Request URL
|
|
620
|
+
* @param accessToken Optional access token to bind the proof to (ath claim)
|
|
621
|
+
*/
|
|
622
|
+
async generateProofHeader(method, url, accessToken) {
|
|
623
|
+
const keyPair = await this.ensureKeyPair();
|
|
624
|
+
const payload = {
|
|
625
|
+
htm: method,
|
|
626
|
+
htu: url,
|
|
627
|
+
};
|
|
628
|
+
if (accessToken) {
|
|
629
|
+
payload.ath = await this.cryptoProvider.hashAccessToken(accessToken);
|
|
630
|
+
}
|
|
631
|
+
return this.cryptoProvider.signProof(payload, keyPair);
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Clears DPoP keys (e.g. on logout)
|
|
635
|
+
*/
|
|
636
|
+
async clearKeys() {
|
|
637
|
+
this.memoryKeyPair = null;
|
|
638
|
+
await this.storage.remove(DPOP_STORAGE_KEYS.PUBLIC);
|
|
639
|
+
await this.storage.remove(DPOP_STORAGE_KEYS.PRIVATE);
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Gets the public key (for debugging/inspection)
|
|
643
|
+
*/
|
|
644
|
+
async getPublicKey() {
|
|
645
|
+
try {
|
|
646
|
+
const kp = await this.ensureKeyPair();
|
|
647
|
+
return kp.publicKey;
|
|
648
|
+
}
|
|
649
|
+
catch {
|
|
650
|
+
return null;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
433
655
|
/**
|
|
434
656
|
* Default Authentication Provider
|
|
435
657
|
* Simple implementation of PERS authentication with token lifecycle management
|
|
@@ -443,6 +665,9 @@ class DefaultAuthProvider {
|
|
|
443
665
|
this.authType = config.authType || 'user';
|
|
444
666
|
const storage = config.storage || this.createStorage();
|
|
445
667
|
this.tokenManager = new AuthTokenManager(storage);
|
|
668
|
+
if (config.dpop?.enabled) {
|
|
669
|
+
this.dpopManager = new DPoPManager(storage, config.dpop.cryptoProvider);
|
|
670
|
+
}
|
|
446
671
|
}
|
|
447
672
|
createStorage() {
|
|
448
673
|
return typeof window === 'undefined' || typeof localStorage === 'undefined'
|
|
@@ -452,6 +677,26 @@ class DefaultAuthProvider {
|
|
|
452
677
|
getToken() {
|
|
453
678
|
return this.tokenManager.getAccessToken();
|
|
454
679
|
}
|
|
680
|
+
async getAuthHeaders(token, method, url) {
|
|
681
|
+
const headers = {};
|
|
682
|
+
if (this.dpopManager && method && url) {
|
|
683
|
+
// Generate DPoP Proof
|
|
684
|
+
// Note: For 'bypassAuth' requests (token is null), we still generate proof (bound to public key, no ath)
|
|
685
|
+
// This covers Token Requests (Login) which need DPoP header to bind the issued token.
|
|
686
|
+
const proof = await this.dpopManager.generateProofHeader(method, url, token || undefined);
|
|
687
|
+
headers['DPoP'] = proof;
|
|
688
|
+
if (token) {
|
|
689
|
+
headers['Authorization'] = `DPoP ${token}`;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
else {
|
|
693
|
+
// Standard Bearer
|
|
694
|
+
if (token) {
|
|
695
|
+
headers['Authorization'] = `Bearer ${token}`;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
return headers;
|
|
699
|
+
}
|
|
455
700
|
async getProjectKey() {
|
|
456
701
|
return this.config.projectKey || null;
|
|
457
702
|
}
|
|
@@ -486,6 +731,9 @@ class DefaultAuthProvider {
|
|
|
486
731
|
}
|
|
487
732
|
async clearTokens() {
|
|
488
733
|
await this.tokenManager.clearAllTokens();
|
|
734
|
+
if (this.dpopManager) {
|
|
735
|
+
await this.dpopManager.clearKeys();
|
|
736
|
+
}
|
|
489
737
|
}
|
|
490
738
|
async setTokens(accessToken, refreshToken, providerToken) {
|
|
491
739
|
await this.tokenManager.setTokens(accessToken, refreshToken, providerToken);
|
|
@@ -534,7 +782,11 @@ class PersApiClient {
|
|
|
534
782
|
this.mergedConfig.authProvider = new DefaultAuthProvider({
|
|
535
783
|
authType: this.mergedConfig.authType || 'user',
|
|
536
784
|
projectKey: this.mergedConfig.apiProjectKey,
|
|
537
|
-
storage: this.mergedConfig.authStorage // Support custom storage
|
|
785
|
+
storage: this.mergedConfig.authStorage, // Support custom storage
|
|
786
|
+
dpop: {
|
|
787
|
+
enabled: this.mergedConfig.dpop?.enabled ?? true, // Enable DPoP by default
|
|
788
|
+
cryptoProvider: this.mergedConfig.dpop?.cryptoProvider
|
|
789
|
+
}
|
|
538
790
|
});
|
|
539
791
|
}
|
|
540
792
|
this.authService = new AuthService(this.authApi, this.mergedConfig.authProvider);
|
|
@@ -597,7 +849,7 @@ class PersApiClient {
|
|
|
597
849
|
});
|
|
598
850
|
}
|
|
599
851
|
const requestOptions = {
|
|
600
|
-
headers: await this.getHeaders(!bypassAuth),
|
|
852
|
+
headers: await this.getHeaders(!bypassAuth, method, url),
|
|
601
853
|
timeout: this.mergedConfig.timeout,
|
|
602
854
|
responseType
|
|
603
855
|
};
|
|
@@ -692,26 +944,45 @@ class PersApiClient {
|
|
|
692
944
|
* Get request headers with optional auth token and project key
|
|
693
945
|
*
|
|
694
946
|
* @param includeAuth - Whether to include the Authorization header (default: true)
|
|
947
|
+
* @param method - HTTP Method (required for DPoP)
|
|
948
|
+
* @param url - Full Request URL (required for DPoP)
|
|
695
949
|
*/
|
|
696
|
-
async getHeaders(includeAuth = true) {
|
|
950
|
+
async getHeaders(includeAuth = true, method, url) {
|
|
697
951
|
const headers = {
|
|
698
952
|
'Content-Type': 'application/json',
|
|
699
953
|
};
|
|
700
|
-
if (
|
|
701
|
-
|
|
702
|
-
if (
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
954
|
+
if (this.mergedConfig.authProvider) {
|
|
955
|
+
let token = null;
|
|
956
|
+
if (includeAuth) {
|
|
957
|
+
token = await this.mergedConfig.authProvider.getToken();
|
|
958
|
+
}
|
|
959
|
+
// Handle Authentication Headers (Bearer or DPoP)
|
|
960
|
+
if (this.mergedConfig.authProvider.getAuthHeaders) {
|
|
961
|
+
// Provider supports advanced headers (DPoP)
|
|
962
|
+
// We pass token (null if includeAuth=false) so it can generate DPoP proofs for login requests too
|
|
963
|
+
try {
|
|
964
|
+
const authHeaders = await this.mergedConfig.authProvider.getAuthHeaders(token, method, url);
|
|
965
|
+
Object.assign(headers, authHeaders);
|
|
706
966
|
}
|
|
707
|
-
|
|
708
|
-
console.warn('[PersApiClient]
|
|
967
|
+
catch (e) {
|
|
968
|
+
console.warn('[PersApiClient] Failed to generate auth headers:', e);
|
|
969
|
+
// Fallback if token exists but DPoP generation failed?
|
|
970
|
+
// Better to fail or let standardized fallback below handle it?
|
|
971
|
+
// If DPoP fails, falling back to Bearer might be insecure if server enforces DPoP.
|
|
972
|
+
// For now, valid token -> Bearer fallback logic mostly for non-DPoP legacy providers.
|
|
709
973
|
}
|
|
710
974
|
}
|
|
711
|
-
|
|
712
|
-
|
|
975
|
+
// Fallback: If provider didn't give headers but we have token and NO Authorization header yet
|
|
976
|
+
if (includeAuth && token && !headers['Authorization']) {
|
|
977
|
+
headers['Authorization'] = `Bearer ${token}`;
|
|
978
|
+
}
|
|
979
|
+
else if (includeAuth && !token) {
|
|
980
|
+
console.warn('[PersApiClient] No token available from auth provider');
|
|
713
981
|
}
|
|
714
982
|
}
|
|
983
|
+
else if (includeAuth) {
|
|
984
|
+
console.warn('[PersApiClient] No auth provider configured');
|
|
985
|
+
}
|
|
715
986
|
// Add project key
|
|
716
987
|
if (this.mergedConfig.authProvider) {
|
|
717
988
|
const projectKey = await this.mergedConfig.authProvider.getProjectKey();
|
|
@@ -747,6 +1018,104 @@ class PersApiClient {
|
|
|
747
1018
|
}
|
|
748
1019
|
}
|
|
749
1020
|
|
|
1021
|
+
class IndexedDBTokenStorage {
|
|
1022
|
+
constructor() {
|
|
1023
|
+
this.supportsObjects = true;
|
|
1024
|
+
this.dbPromise = null;
|
|
1025
|
+
if (typeof indexedDB === 'undefined') {
|
|
1026
|
+
console.warn('IndexedDB is not available in this environment');
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
getDB() {
|
|
1030
|
+
if (this.dbPromise)
|
|
1031
|
+
return this.dbPromise;
|
|
1032
|
+
this.dbPromise = new Promise((resolve, reject) => {
|
|
1033
|
+
if (typeof indexedDB === 'undefined') {
|
|
1034
|
+
return reject(new Error('IndexedDB not supported'));
|
|
1035
|
+
}
|
|
1036
|
+
const request = indexedDB.open(IndexedDBTokenStorage.DB_NAME, IndexedDBTokenStorage.DB_VERSION);
|
|
1037
|
+
request.onupgradeneeded = (event) => {
|
|
1038
|
+
const db = event.target.result;
|
|
1039
|
+
if (!db.objectStoreNames.contains(IndexedDBTokenStorage.STORE_NAME)) {
|
|
1040
|
+
db.createObjectStore(IndexedDBTokenStorage.STORE_NAME);
|
|
1041
|
+
}
|
|
1042
|
+
};
|
|
1043
|
+
request.onsuccess = (event) => {
|
|
1044
|
+
resolve(event.target.result);
|
|
1045
|
+
};
|
|
1046
|
+
request.onerror = (event) => {
|
|
1047
|
+
reject(event.target.error);
|
|
1048
|
+
};
|
|
1049
|
+
});
|
|
1050
|
+
return this.dbPromise;
|
|
1051
|
+
}
|
|
1052
|
+
async get(key) {
|
|
1053
|
+
try {
|
|
1054
|
+
const db = await this.getDB();
|
|
1055
|
+
return new Promise((resolve, reject) => {
|
|
1056
|
+
const transaction = db.transaction(IndexedDBTokenStorage.STORE_NAME, 'readonly');
|
|
1057
|
+
const store = transaction.objectStore(IndexedDBTokenStorage.STORE_NAME);
|
|
1058
|
+
const request = store.get(key);
|
|
1059
|
+
request.onsuccess = () => resolve(request.result || null);
|
|
1060
|
+
request.onerror = () => reject(request.error);
|
|
1061
|
+
});
|
|
1062
|
+
}
|
|
1063
|
+
catch (e) {
|
|
1064
|
+
console.warn('[IndexedDBTokenStorage] Failed to get key', key, e);
|
|
1065
|
+
return null;
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
async set(key, value) {
|
|
1069
|
+
try {
|
|
1070
|
+
const db = await this.getDB();
|
|
1071
|
+
return new Promise((resolve, reject) => {
|
|
1072
|
+
const transaction = db.transaction(IndexedDBTokenStorage.STORE_NAME, 'readwrite');
|
|
1073
|
+
const store = transaction.objectStore(IndexedDBTokenStorage.STORE_NAME);
|
|
1074
|
+
const request = store.put(value, key);
|
|
1075
|
+
request.onsuccess = () => resolve();
|
|
1076
|
+
request.onerror = () => reject(request.error);
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
catch (e) {
|
|
1080
|
+
console.error('[IndexedDBTokenStorage] Failed to set key', key, e);
|
|
1081
|
+
throw e;
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
async remove(key) {
|
|
1085
|
+
try {
|
|
1086
|
+
const db = await this.getDB();
|
|
1087
|
+
return new Promise((resolve, reject) => {
|
|
1088
|
+
const transaction = db.transaction(IndexedDBTokenStorage.STORE_NAME, 'readwrite');
|
|
1089
|
+
const store = transaction.objectStore(IndexedDBTokenStorage.STORE_NAME);
|
|
1090
|
+
const request = store.delete(key);
|
|
1091
|
+
request.onsuccess = () => resolve();
|
|
1092
|
+
request.onerror = () => reject(request.error);
|
|
1093
|
+
});
|
|
1094
|
+
}
|
|
1095
|
+
catch (e) {
|
|
1096
|
+
console.warn('[IndexedDBTokenStorage] Failed to remove key', key, e);
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
async clear() {
|
|
1100
|
+
try {
|
|
1101
|
+
const db = await this.getDB();
|
|
1102
|
+
return new Promise((resolve, reject) => {
|
|
1103
|
+
const transaction = db.transaction(IndexedDBTokenStorage.STORE_NAME, 'readwrite');
|
|
1104
|
+
const store = transaction.objectStore(IndexedDBTokenStorage.STORE_NAME);
|
|
1105
|
+
const request = store.clear();
|
|
1106
|
+
request.onsuccess = () => resolve();
|
|
1107
|
+
request.onerror = () => reject(request.error);
|
|
1108
|
+
});
|
|
1109
|
+
}
|
|
1110
|
+
catch (e) {
|
|
1111
|
+
console.warn('[IndexedDBTokenStorage] Failed to clear storage', e);
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
IndexedDBTokenStorage.DB_NAME = 'pers-sdk-storage';
|
|
1116
|
+
IndexedDBTokenStorage.STORE_NAME = 'auth-tokens';
|
|
1117
|
+
IndexedDBTokenStorage.DB_VERSION = 1;
|
|
1118
|
+
|
|
750
1119
|
/**
|
|
751
1120
|
* Environment Detection Utility
|
|
752
1121
|
* Detects runtime environment for platform-specific behaviors
|
|
@@ -5928,6 +6297,7 @@ function createPersSDK(httpClient, config) {
|
|
|
5928
6297
|
return new PersSDK(httpClient, config);
|
|
5929
6298
|
}
|
|
5930
6299
|
|
|
6300
|
+
exports.AUTH_STORAGE_KEYS = AUTH_STORAGE_KEYS;
|
|
5931
6301
|
exports.AnalyticsManager = AnalyticsManager;
|
|
5932
6302
|
exports.ApiKeyApi = ApiKeyApi;
|
|
5933
6303
|
exports.ApiKeyManager = ApiKeyManager;
|
|
@@ -5938,11 +6308,14 @@ exports.AuthTokenManager = AuthTokenManager;
|
|
|
5938
6308
|
exports.BusinessManager = BusinessManager;
|
|
5939
6309
|
exports.CampaignManager = CampaignManager;
|
|
5940
6310
|
exports.DEFAULT_PERS_CONFIG = DEFAULT_PERS_CONFIG;
|
|
6311
|
+
exports.DPOP_STORAGE_KEYS = DPOP_STORAGE_KEYS;
|
|
6312
|
+
exports.DPoPManager = DPoPManager;
|
|
5941
6313
|
exports.DefaultAuthProvider = DefaultAuthProvider;
|
|
5942
6314
|
exports.DonationManager = DonationManager;
|
|
5943
6315
|
exports.FileApi = FileApi;
|
|
5944
6316
|
exports.FileManager = FileManager;
|
|
5945
6317
|
exports.FileService = FileService;
|
|
6318
|
+
exports.IndexedDBTokenStorage = IndexedDBTokenStorage;
|
|
5946
6319
|
exports.LocalStorageTokenStorage = LocalStorageTokenStorage;
|
|
5947
6320
|
exports.MemoryTokenStorage = MemoryTokenStorage;
|
|
5948
6321
|
exports.PersApiClient = PersApiClient;
|
|
@@ -5955,10 +6328,11 @@ exports.TransactionManager = TransactionManager;
|
|
|
5955
6328
|
exports.UserManager = UserManager;
|
|
5956
6329
|
exports.UserStatusManager = UserStatusManager;
|
|
5957
6330
|
exports.Web3Manager = Web3Manager;
|
|
6331
|
+
exports.WebDPoPCryptoProvider = WebDPoPCryptoProvider;
|
|
5958
6332
|
exports.buildApiRoot = buildApiRoot;
|
|
5959
6333
|
exports.createPersSDK = createPersSDK;
|
|
5960
6334
|
exports.detectEnvironment = detectEnvironment;
|
|
5961
6335
|
exports.environment = environment;
|
|
5962
6336
|
exports.mergeWithDefaults = mergeWithDefaults;
|
|
5963
6337
|
exports.warnIfProblematicEnvironment = warnIfProblematicEnvironment;
|
|
5964
|
-
//# sourceMappingURL=pers-sdk-
|
|
6338
|
+
//# sourceMappingURL=pers-sdk-BGDZS3-x.cjs.map
|