@geolonia/geonicdb-sdk 0.5.0 → 0.7.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/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
- if (dpopSupported) {
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
- /** Ensure a valid token is available, refreshing or acquiring as needed. */
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.