@geolonia/geonicdb-sdk 0.5.0 → 0.6.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 +35 -3
- package/geonicdb.cjs +95 -12
- package/geonicdb.cjs.map +2 -2
- package/geonicdb.iife.js +95 -12
- package/geonicdb.iife.js.map +2 -2
- package/geonicdb.mjs +95 -12
- package/geonicdb.mjs.map +2 -2
- package/package.json +1 -1
package/geonicdb.iife.js
CHANGED
|
@@ -347,11 +347,27 @@ var GeonicDBModule = (() => {
|
|
|
347
347
|
|
|
348
348
|
// src/sdk/auth.ts
|
|
349
349
|
var _AuthManager = class _AuthManager {
|
|
350
|
-
constructor(baseUrl, apiKey, tenant, debug = false) {
|
|
350
|
+
constructor(baseUrl, apiKey, tenant, debug = false, anonymous = false) {
|
|
351
351
|
__publicField(this, "_baseUrl");
|
|
352
352
|
__publicField(this, "_apiKey");
|
|
353
353
|
__publicField(this, "_tenant");
|
|
354
354
|
__publicField(this, "_debug");
|
|
355
|
+
/**
|
|
356
|
+
* #1105: anonymous モード。true の場合、トークンを取得せず Authorization
|
|
357
|
+
* ヘッダ無しで送信する。サーバー側 (`optionalAuth`) で role='anonymous' として
|
|
358
|
+
* 通り、XACML が認可判定する。
|
|
359
|
+
*
|
|
360
|
+
* `_initialAnonymous` はコンストラクタで指定された希望モード (immutable)。
|
|
361
|
+
* `_anonymous` は現在のモード状態で、`login()` / `setCredentials()` で false
|
|
362
|
+
* に降り、`logout()` で `_initialAnonymous` の値に戻る。
|
|
363
|
+
*
|
|
364
|
+
* トークン有無 (`_token === null`) からは推論しない。`_token` は refresh
|
|
365
|
+
* 失敗時に null へ落ちるが、ユーザが明示的に `logout()` していない以上、
|
|
366
|
+
* 次のリクエストを勝手に Authorization ヘッダ無しで送ってはならない
|
|
367
|
+
* (#1113 review)。
|
|
368
|
+
*/
|
|
369
|
+
__publicField(this, "_initialAnonymous");
|
|
370
|
+
__publicField(this, "_anonymous");
|
|
355
371
|
__publicField(this, "_token", null);
|
|
356
372
|
__publicField(this, "_tokenExpiry", 0);
|
|
357
373
|
__publicField(this, "_tokenType", "Bearer");
|
|
@@ -372,11 +388,18 @@ var GeonicDBModule = (() => {
|
|
|
372
388
|
__publicField(this, "_emitCacheEvent", null);
|
|
373
389
|
/** In-flight request map keyed by `${METHOD}:${path}` for request deduplication. */
|
|
374
390
|
__publicField(this, "_inFlight", /* @__PURE__ */ new Map());
|
|
391
|
+
if (anonymous && apiKey) {
|
|
392
|
+
throw new Error(
|
|
393
|
+
"GeonicDB SDK: `anonymous: true` cannot be combined with `apiKey`. Anonymous mode skips token acquisition entirely."
|
|
394
|
+
);
|
|
395
|
+
}
|
|
375
396
|
this._baseUrl = baseUrl;
|
|
376
397
|
this._apiKey = apiKey;
|
|
377
398
|
this._tenant = tenant;
|
|
378
399
|
this._debug = debug;
|
|
379
|
-
|
|
400
|
+
this._initialAnonymous = anonymous;
|
|
401
|
+
this._anonymous = anonymous;
|
|
402
|
+
if (!anonymous && dpopSupported) {
|
|
380
403
|
this._dpopReady = generateDPoPKeyPair().then((kp) => {
|
|
381
404
|
this._dpopKeyPair = kp;
|
|
382
405
|
}).catch(() => {
|
|
@@ -384,6 +407,16 @@ var GeonicDBModule = (() => {
|
|
|
384
407
|
});
|
|
385
408
|
}
|
|
386
409
|
}
|
|
410
|
+
/**
|
|
411
|
+
* True when running unauthenticated.
|
|
412
|
+
*
|
|
413
|
+
* Returns the explicit mode flag, NOT derived from `_token === null`. After
|
|
414
|
+
* a refresh failure clears `_token`, the SDK must NOT silently switch to
|
|
415
|
+
* anonymous; the caller must explicitly `logout()` to revert (#1113 review).
|
|
416
|
+
*/
|
|
417
|
+
isAnonymous() {
|
|
418
|
+
return this._anonymous;
|
|
419
|
+
}
|
|
387
420
|
_log(...args) {
|
|
388
421
|
if (this._debug) console.log("[GeonicDB]", ...args);
|
|
389
422
|
}
|
|
@@ -422,6 +455,7 @@ var GeonicDBModule = (() => {
|
|
|
422
455
|
);
|
|
423
456
|
}
|
|
424
457
|
const data = await res.json();
|
|
458
|
+
this._anonymous = false;
|
|
425
459
|
this._token = data.accessToken;
|
|
426
460
|
this._tokenExpiry = Date.now() + (data.expiresIn - 60) * 1e3;
|
|
427
461
|
this._tokenType = "Bearer";
|
|
@@ -436,6 +470,12 @@ var GeonicDBModule = (() => {
|
|
|
436
470
|
*/
|
|
437
471
|
setCredentials(opts) {
|
|
438
472
|
if (!opts || !opts.token) throw new Error("token is required");
|
|
473
|
+
if (this._initialAnonymous && opts.tokenType === "DPoP") {
|
|
474
|
+
throw new Error(
|
|
475
|
+
"DPoP credentials cannot be set on an SDK instance constructed with `anonymous: true` (no DPoP key pair is generated for anonymous instances). Use Bearer credentials, or construct the SDK in non-anonymous mode."
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
this._anonymous = false;
|
|
439
479
|
this._token = opts.token;
|
|
440
480
|
this._tokenType = opts.tokenType || "Bearer";
|
|
441
481
|
this._tokenExpiry = opts.expiresIn != null ? Date.now() + (opts.expiresIn - 60) * 1e3 : Date.now() + DEFAULT_TOKEN_TTL_SEC * 1e3;
|
|
@@ -449,6 +489,7 @@ var GeonicDBModule = (() => {
|
|
|
449
489
|
this._tokenExpiry = 0;
|
|
450
490
|
this._refreshToken = null;
|
|
451
491
|
this._tokenPromise = null;
|
|
492
|
+
this._anonymous = this._initialAnonymous;
|
|
452
493
|
this._invalidateAuthScopedState();
|
|
453
494
|
}
|
|
454
495
|
/**
|
|
@@ -461,7 +502,13 @@ var GeonicDBModule = (() => {
|
|
|
461
502
|
this._cache?.clear();
|
|
462
503
|
this._inFlight.clear();
|
|
463
504
|
}
|
|
464
|
-
/**
|
|
505
|
+
/**
|
|
506
|
+
* Ensure a valid token is available, refreshing or acquiring as needed.
|
|
507
|
+
*
|
|
508
|
+
* #1105: In anonymous mode (no apiKey, no Bearer credentials) this throws
|
|
509
|
+
* `AuthenticationError`. Callers that may run anonymously should branch on
|
|
510
|
+
* `isAnonymous()` first instead of calling `ensureToken()` blindly.
|
|
511
|
+
*/
|
|
465
512
|
async ensureToken() {
|
|
466
513
|
if (this._token && Date.now() < this._tokenExpiry) {
|
|
467
514
|
return this._token;
|
|
@@ -473,6 +520,16 @@ var GeonicDBModule = (() => {
|
|
|
473
520
|
this._tokenPromise = this._refreshBearerToken();
|
|
474
521
|
return this._tokenPromise;
|
|
475
522
|
}
|
|
523
|
+
if (this._anonymous) {
|
|
524
|
+
throw new AuthenticationError(
|
|
525
|
+
"Anonymous mode: no token available. Call login() or setCredentials() to authenticate."
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
if (!this._apiKey) {
|
|
529
|
+
throw new AuthenticationError(
|
|
530
|
+
"No authentication method available. Call login() / setCredentials() to authenticate, or construct the SDK with an apiKey."
|
|
531
|
+
);
|
|
532
|
+
}
|
|
476
533
|
this._tokenPromise = this._acquireTokenViaPow();
|
|
477
534
|
return this._tokenPromise;
|
|
478
535
|
}
|
|
@@ -593,7 +650,7 @@ var GeonicDBModule = (() => {
|
|
|
593
650
|
async request(method, path, body) {
|
|
594
651
|
this._log(method, path);
|
|
595
652
|
if (!this._cache || !isCacheableMethod(method) || body !== void 0) {
|
|
596
|
-
const token = await this.ensureToken();
|
|
653
|
+
const token = this.isAnonymous() ? null : await this.ensureToken();
|
|
597
654
|
const res = await this._doAuthenticatedRequest(method, path, body, token);
|
|
598
655
|
this._log(method, path, "\u2192", res.status);
|
|
599
656
|
return res;
|
|
@@ -614,14 +671,14 @@ var GeonicDBModule = (() => {
|
|
|
614
671
|
async _cachedRequest(method, path, key) {
|
|
615
672
|
const cache = this._cache;
|
|
616
673
|
if (!cache) {
|
|
617
|
-
const token2 = await this.ensureToken();
|
|
674
|
+
const token2 = this.isAnonymous() ? null : await this.ensureToken();
|
|
618
675
|
return this._doAuthenticatedRequest(method, path, void 0, token2);
|
|
619
676
|
}
|
|
620
677
|
const cached = cache.get(key);
|
|
621
678
|
const conditional = {};
|
|
622
679
|
if (cached?.etag) conditional["If-None-Match"] = cached.etag;
|
|
623
680
|
if (cached?.lastModified) conditional["If-Modified-Since"] = cached.lastModified;
|
|
624
|
-
const token = await this.ensureToken();
|
|
681
|
+
const token = this.isAnonymous() ? null : await this.ensureToken();
|
|
625
682
|
const res = await this._doAuthenticatedRequest(method, path, void 0, token, 0, conditional);
|
|
626
683
|
this._log(method, path, "\u2192", res.status);
|
|
627
684
|
if (res.status === 304 && cached) {
|
|
@@ -662,11 +719,19 @@ var GeonicDBModule = (() => {
|
|
|
662
719
|
return res;
|
|
663
720
|
}
|
|
664
721
|
async _doAuthenticatedRequest(method, path, body, token, retryCount = 0, extraHeaders = {}) {
|
|
722
|
+
if (this._tokenType === "DPoP" && token !== null) {
|
|
723
|
+
if (this._dpopReady) await this._dpopReady;
|
|
724
|
+
if (!this._dpopKeyPair) {
|
|
725
|
+
throw new AuthenticationError(
|
|
726
|
+
"DPoP credentials require a generated DPoP key pair, but key generation was unavailable or failed."
|
|
727
|
+
);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
665
730
|
const url = this._baseUrl + path;
|
|
666
|
-
const isDPoP = this._tokenType === "DPoP" && !!this._dpopKeyPair;
|
|
731
|
+
const isDPoP = this._tokenType === "DPoP" && !!this._dpopKeyPair && token !== null;
|
|
667
732
|
const bodyStr = body !== void 0 ? JSON.stringify(body) : void 0;
|
|
668
733
|
const doRequest = async (reqToken, dpopNonce) => {
|
|
669
|
-
const reqIsDPoP = this._tokenType === "DPoP" && !!this._dpopKeyPair;
|
|
734
|
+
const reqIsDPoP = this._tokenType === "DPoP" && !!this._dpopKeyPair && reqToken !== null;
|
|
670
735
|
const dpopProof = reqIsDPoP ? await createDPoPProof(
|
|
671
736
|
this._dpopKeyPair,
|
|
672
737
|
method,
|
|
@@ -675,11 +740,13 @@ var GeonicDBModule = (() => {
|
|
|
675
740
|
dpopNonce
|
|
676
741
|
) : null;
|
|
677
742
|
const headers = {
|
|
678
|
-
Authorization: (reqIsDPoP ? "DPoP " : "Bearer ") + reqToken,
|
|
679
743
|
"Content-Type": "application/ld+json",
|
|
680
744
|
Accept: "application/ld+json",
|
|
681
745
|
...extraHeaders
|
|
682
746
|
};
|
|
747
|
+
if (reqToken !== null) {
|
|
748
|
+
headers["Authorization"] = (reqIsDPoP ? "DPoP " : "Bearer ") + reqToken;
|
|
749
|
+
}
|
|
683
750
|
if (dpopProof) headers["DPoP"] = dpopProof;
|
|
684
751
|
if (this._tenant) headers["Fiware-Service"] = this._tenant;
|
|
685
752
|
return fetch(url, { method, headers, body: bodyStr });
|
|
@@ -689,11 +756,12 @@ var GeonicDBModule = (() => {
|
|
|
689
756
|
const previousNonce = this._dpopNonce;
|
|
690
757
|
const newNonce = res.headers.get("DPoP-Nonce");
|
|
691
758
|
if (newNonce) this._dpopNonce = newNonce;
|
|
759
|
+
const canRetry401 = currentToken !== null;
|
|
692
760
|
if (res.status === 401 && isDPoP && newNonce && newNonce !== previousNonce) {
|
|
693
761
|
res = await doRequest(currentToken, newNonce);
|
|
694
762
|
const rn = res.headers.get("DPoP-Nonce");
|
|
695
763
|
if (rn) this._dpopNonce = rn;
|
|
696
|
-
if (res.status === 401) {
|
|
764
|
+
if (res.status === 401 && canRetry401) {
|
|
697
765
|
this._token = null;
|
|
698
766
|
this._tokenPromise = null;
|
|
699
767
|
currentToken = await this.ensureToken();
|
|
@@ -701,7 +769,7 @@ var GeonicDBModule = (() => {
|
|
|
701
769
|
const fn = res.headers.get("DPoP-Nonce");
|
|
702
770
|
if (fn) this._dpopNonce = fn;
|
|
703
771
|
}
|
|
704
|
-
} else if (res.status === 401) {
|
|
772
|
+
} else if (res.status === 401 && canRetry401) {
|
|
705
773
|
this._token = null;
|
|
706
774
|
this._tokenPromise = null;
|
|
707
775
|
currentToken = await this.ensureToken();
|
|
@@ -750,6 +818,13 @@ var GeonicDBModule = (() => {
|
|
|
750
818
|
}
|
|
751
819
|
/** Establish WebSocket connection (authentication is automatic). */
|
|
752
820
|
async connect() {
|
|
821
|
+
if (this._auth.isAnonymous()) {
|
|
822
|
+
const err = new Error(
|
|
823
|
+
"WebSocket connect() is not supported in anonymous mode. Authenticate via login() or setCredentials() before connect()."
|
|
824
|
+
);
|
|
825
|
+
this._emit("error", err);
|
|
826
|
+
throw err;
|
|
827
|
+
}
|
|
753
828
|
if (this._reconnectTimer) {
|
|
754
829
|
clearTimeout(this._reconnectTimer);
|
|
755
830
|
this._reconnectTimer = null;
|
|
@@ -985,7 +1060,7 @@ var GeonicDBModule = (() => {
|
|
|
985
1060
|
if (!tenant) tenant = script?.getAttribute?.("data-tenant") || "";
|
|
986
1061
|
if (!baseUrl) baseUrl = script?.getAttribute?.("data-base-url") || "";
|
|
987
1062
|
}
|
|
988
|
-
this._auth = new AuthManager(baseUrl, apiKey, tenant, opts.debug);
|
|
1063
|
+
this._auth = new AuthManager(baseUrl, apiKey, tenant, opts.debug, opts.anonymous);
|
|
989
1064
|
this._auth.onTokenRefresh = (creds) => {
|
|
990
1065
|
this.onTokenRefresh?.(creds);
|
|
991
1066
|
this.emit("tokenRefresh", creds);
|
|
@@ -1026,6 +1101,14 @@ var GeonicDBModule = (() => {
|
|
|
1026
1101
|
async login(email, password) {
|
|
1027
1102
|
return this._auth.login(email, password);
|
|
1028
1103
|
}
|
|
1104
|
+
/**
|
|
1105
|
+
* Whether the SDK is currently operating in anonymous mode (no token held).
|
|
1106
|
+
* Returns true only when `anonymous: true` was passed to the constructor
|
|
1107
|
+
* AND no credentials have been set via `login()` / `setCredentials()`.
|
|
1108
|
+
*/
|
|
1109
|
+
isAnonymous() {
|
|
1110
|
+
return this._auth.isAnonymous();
|
|
1111
|
+
}
|
|
1029
1112
|
/**
|
|
1030
1113
|
* Set credentials externally (e.g. from a login API response).
|
|
1031
1114
|
* When tokenType is 'Bearer' with a refreshToken, DPoP/PoW is bypassed entirely.
|