@geolonia/geonicdb-sdk 0.10.1 → 0.11.0
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 +30 -44
- package/geonicdb.cjs +66 -54
- package/geonicdb.cjs.map +2 -2
- package/geonicdb.d.ts +22 -45
- package/geonicdb.iife.js +66 -54
- package/geonicdb.iife.js.map +2 -2
- package/geonicdb.mjs +66 -54
- package/geonicdb.mjs.map +2 -2
- package/package.json +1 -1
package/geonicdb.mjs
CHANGED
|
@@ -443,11 +443,12 @@ var _AuthManager = class _AuthManager {
|
|
|
443
443
|
__publicField(this, "_dpopReady", null);
|
|
444
444
|
__publicField(this, "_dpopNonce", null);
|
|
445
445
|
/**
|
|
446
|
-
*
|
|
447
|
-
*
|
|
448
|
-
*
|
|
446
|
+
* When true, every DPoP token mutation (login bind, refresh) is mirrored to
|
|
447
|
+
* IndexedDB so the session survives a page reload (#1230). Toggled by
|
|
448
|
+
* `login(email, password, { rememberSession: true })` and
|
|
449
|
+
* `restoreSession()` (#1244).
|
|
449
450
|
*/
|
|
450
|
-
__publicField(this, "
|
|
451
|
+
__publicField(this, "_rememberSession", false);
|
|
451
452
|
/** Email captured at login, persisted for UI display. */
|
|
452
453
|
__publicField(this, "_email", null);
|
|
453
454
|
__publicField(this, "_tokenPromise", null);
|
|
@@ -512,22 +513,22 @@ var _AuthManager = class _AuthManager {
|
|
|
512
513
|
this._emitCacheEvent?.(name, payload);
|
|
513
514
|
}
|
|
514
515
|
/**
|
|
515
|
-
* Login with email and password
|
|
516
|
+
* Login with email and password.
|
|
516
517
|
*
|
|
517
|
-
*
|
|
518
|
-
*
|
|
519
|
-
*
|
|
520
|
-
*
|
|
521
|
-
*
|
|
518
|
+
* #1244: DPoP (RFC 9449) is transparent and always-on — there is no `dpop`
|
|
519
|
+
* option to set. After the Bearer login succeeds, the SDK automatically
|
|
520
|
+
* exchanges it for a DPoP sender-constrained session so a token leaked from
|
|
521
|
+
* storage (e.g. localStorage XSS) cannot be replayed from another origin.
|
|
522
|
+
*
|
|
523
|
+
* When the runtime lacks Web Crypto (`crypto.subtle.generateKey`, e.g. a
|
|
524
|
+
* non-HTTPS context), DPoP cannot be used and the SDK silently keeps the
|
|
525
|
+
* Bearer session — login still succeeds. Callers never deal with DPoP
|
|
526
|
+
* directly; pass `{ rememberSession: true }` only to keep the user signed in
|
|
527
|
+
* across reloads.
|
|
522
528
|
*/
|
|
523
529
|
async login(email, password, options) {
|
|
524
530
|
this._log("login", email);
|
|
525
|
-
const
|
|
526
|
-
if (wantsDpopPersist && !options?.dpop) {
|
|
527
|
-
throw new AuthenticationError(
|
|
528
|
-
"dpopPersist: true requires dpop: true. Persisting Bearer tokens is the caller's responsibility (localStorage / sessionStorage)."
|
|
529
|
-
);
|
|
530
|
-
}
|
|
531
|
+
const wantsRemember = options?.rememberSession === true;
|
|
531
532
|
const headers = {
|
|
532
533
|
"Content-Type": "application/json"
|
|
533
534
|
};
|
|
@@ -550,14 +551,30 @@ var _AuthManager = class _AuthManager {
|
|
|
550
551
|
this._tokenType = "Bearer";
|
|
551
552
|
this._refreshToken = data.refreshToken;
|
|
552
553
|
this._email = email;
|
|
553
|
-
this.
|
|
554
|
+
this._rememberSession = wantsRemember;
|
|
554
555
|
this._invalidateAuthScopedState();
|
|
555
|
-
if (
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
556
|
+
if (!wantsRemember && this._tenant) {
|
|
557
|
+
void clearDPoPSession(this._tenant);
|
|
558
|
+
}
|
|
559
|
+
if (this._dpopReady) await this._dpopReady;
|
|
560
|
+
if (this._dpopKeyPair) {
|
|
561
|
+
try {
|
|
562
|
+
const bound = await this._bindDpopSession();
|
|
563
|
+
if (this._rememberSession) {
|
|
564
|
+
await this._persistDpopSession();
|
|
565
|
+
}
|
|
566
|
+
return bound;
|
|
567
|
+
} catch (err) {
|
|
568
|
+
this._token = null;
|
|
569
|
+
this._tokenExpiry = 0;
|
|
570
|
+
this._tokenType = "Bearer";
|
|
571
|
+
this._refreshToken = null;
|
|
572
|
+
this._tokenPromise = null;
|
|
573
|
+
this._rememberSession = false;
|
|
574
|
+
this._email = null;
|
|
575
|
+
this._invalidateAuthScopedState();
|
|
576
|
+
throw err;
|
|
559
577
|
}
|
|
560
|
-
return bound;
|
|
561
578
|
}
|
|
562
579
|
return data;
|
|
563
580
|
}
|
|
@@ -651,18 +668,19 @@ var _AuthManager = class _AuthManager {
|
|
|
651
668
|
this._refreshToken = null;
|
|
652
669
|
this._tokenPromise = null;
|
|
653
670
|
this._email = null;
|
|
654
|
-
if (this.
|
|
671
|
+
if (this._tenant) {
|
|
655
672
|
void clearDPoPSession(this._tenant);
|
|
656
673
|
}
|
|
657
|
-
this.
|
|
674
|
+
this._rememberSession = false;
|
|
658
675
|
this._anonymous = this._initialAnonymous;
|
|
659
676
|
this._invalidateAuthScopedState();
|
|
660
677
|
}
|
|
661
678
|
/**
|
|
662
|
-
* Restore a
|
|
663
|
-
*
|
|
664
|
-
*
|
|
665
|
-
*
|
|
679
|
+
* Restore a session previously persisted via
|
|
680
|
+
* `login(email, password, { rememberSession: true })`. Returns true if a
|
|
681
|
+
* usable session was found and loaded into the
|
|
682
|
+
* SDK, false otherwise (no entry, IndexedDB unavailable, or the entry was
|
|
683
|
+
* for a different tenant).
|
|
666
684
|
*
|
|
667
685
|
* After a successful restore the SDK behaves as if `login()` had just
|
|
668
686
|
* succeeded: tokens are set, the DPoP key pair is rehydrated as a
|
|
@@ -690,19 +708,19 @@ var _AuthManager = class _AuthManager {
|
|
|
690
708
|
this._dpopKeyPair = persisted.keyPair;
|
|
691
709
|
this._dpopReady = Promise.resolve();
|
|
692
710
|
this._dpopNonce = null;
|
|
693
|
-
this.
|
|
711
|
+
this._rememberSession = true;
|
|
694
712
|
this._email = persisted.email ?? null;
|
|
695
713
|
this._tokenPromise = null;
|
|
696
714
|
this._invalidateAuthScopedState();
|
|
697
715
|
return true;
|
|
698
716
|
}
|
|
699
717
|
/**
|
|
700
|
-
* Whether this AuthManager currently
|
|
701
|
-
* Exposed for the `GeonicDB` wrapper
|
|
702
|
-
* consumers should read `
|
|
718
|
+
* Whether this AuthManager currently keeps the session persisted across
|
|
719
|
+
* reloads (`rememberSession`). Exposed for the `GeonicDB` wrapper; internal
|
|
720
|
+
* consumers should read `_rememberSession` directly.
|
|
703
721
|
*/
|
|
704
|
-
|
|
705
|
-
return this.
|
|
722
|
+
isSessionRemembered() {
|
|
723
|
+
return this._rememberSession;
|
|
706
724
|
}
|
|
707
725
|
/**
|
|
708
726
|
* Persist the current DPoP session to IndexedDB. Best-effort: a failed
|
|
@@ -712,7 +730,7 @@ var _AuthManager = class _AuthManager {
|
|
|
712
730
|
* @internal
|
|
713
731
|
*/
|
|
714
732
|
async _persistDpopSession() {
|
|
715
|
-
if (!this.
|
|
733
|
+
if (!this._rememberSession || !this._tenant || !this._dpopKeyPair || !this._token || !this._refreshToken) {
|
|
716
734
|
return;
|
|
717
735
|
}
|
|
718
736
|
const ok = await saveDPoPSession({
|
|
@@ -795,7 +813,7 @@ var _AuthManager = class _AuthManager {
|
|
|
795
813
|
expiresIn: data.expiresIn,
|
|
796
814
|
refreshToken: this._refreshToken
|
|
797
815
|
});
|
|
798
|
-
if (this.
|
|
816
|
+
if (this._rememberSession) void this._persistDpopSession();
|
|
799
817
|
return this._token;
|
|
800
818
|
} catch (err) {
|
|
801
819
|
this._tokenPromise = null;
|
|
@@ -1376,35 +1394,29 @@ var GeonicDB = class extends EventEmitter {
|
|
|
1376
1394
|
}
|
|
1377
1395
|
// --- Authentication ---
|
|
1378
1396
|
/**
|
|
1379
|
-
* Login with email and password
|
|
1397
|
+
* Login with email and password.
|
|
1380
1398
|
*
|
|
1381
|
-
*
|
|
1382
|
-
*
|
|
1383
|
-
*
|
|
1384
|
-
*
|
|
1385
|
-
*
|
|
1399
|
+
* The session is sender-constrained with DPoP (RFC 9449) automatically and
|
|
1400
|
+
* transparently — there is no DPoP option to set. A token leaked from
|
|
1401
|
+
* storage (e.g. localStorage XSS) is therefore unusable from another origin.
|
|
1402
|
+
* On runtimes without Web Crypto, the SDK silently keeps a plain Bearer
|
|
1403
|
+
* session and login still succeeds.
|
|
1386
1404
|
*
|
|
1387
1405
|
* @example
|
|
1388
1406
|
* ```ts
|
|
1389
|
-
* //
|
|
1407
|
+
* // Standard login — DPoP applied transparently when supported.
|
|
1390
1408
|
* await db.login('user@example.com', 'password');
|
|
1391
1409
|
*
|
|
1392
|
-
* //
|
|
1393
|
-
* await db.login('user@example.com', 'password', {
|
|
1394
|
-
*
|
|
1395
|
-
* // DPoP-bound + persisted across reloads (browser SPA UX)
|
|
1396
|
-
* await db.login('user@example.com', 'password', {
|
|
1397
|
-
* dpop: true,
|
|
1398
|
-
* dpopPersist: true,
|
|
1399
|
-
* });
|
|
1410
|
+
* // Keep the user signed in across page reloads (browser SPA UX).
|
|
1411
|
+
* await db.login('user@example.com', 'password', { rememberSession: true });
|
|
1400
1412
|
* ```
|
|
1401
1413
|
*/
|
|
1402
1414
|
async login(email, password, options) {
|
|
1403
1415
|
return this._auth.login(email, password, options);
|
|
1404
1416
|
}
|
|
1405
1417
|
/**
|
|
1406
|
-
* Rehydrate a
|
|
1407
|
-
* `login(
|
|
1418
|
+
* Rehydrate a session previously persisted via
|
|
1419
|
+
* `login(email, password, { rememberSession: true })`.
|
|
1408
1420
|
*
|
|
1409
1421
|
* Returns true if a session for this tenant was found and loaded; false
|
|
1410
1422
|
* if there was no entry, IndexedDB is unavailable, or the persisted
|
|
@@ -1415,7 +1427,7 @@ var GeonicDB = class extends EventEmitter {
|
|
|
1415
1427
|
* ```ts
|
|
1416
1428
|
* const db = new GeonicDB({ baseUrl, tenant });
|
|
1417
1429
|
* if (await db.restoreSession()) {
|
|
1418
|
-
* //
|
|
1430
|
+
* // Session (tokens + non-extractable key) recovered — boot the app.
|
|
1419
1431
|
* } else {
|
|
1420
1432
|
* // No session; show the login form.
|
|
1421
1433
|
* }
|