@witnium-tech/witniumchain 0.12.0 → 0.12.2

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/dist/index.d.mts CHANGED
@@ -12044,12 +12044,12 @@ declare class WitniumchainClient {
12044
12044
  private refreshToken;
12045
12045
  private hasRefreshCookie;
12046
12046
  private readonly oauthClientId;
12047
- private pendingLogins;
12048
12047
  private readonly verifierStorage;
12049
12048
  private discoveryCache;
12050
12049
  private refreshInFlight;
12051
12050
  private accessTokenFromSession;
12052
12051
  private sessionExchangeInFlight;
12052
+ private credentialGeneration;
12053
12053
  /** Subscriptions / billing helpers. See {@link Subscriptions}. */
12054
12054
  readonly subscriptions: Subscriptions;
12055
12055
  /** Delegated-key namespace including the one-call {@link DelegatedKeys.provision} flow. */
@@ -12161,8 +12161,9 @@ declare class WitniumchainClient {
12161
12161
  enrollTotp(): Promise<TotpEnrollResponse>;
12162
12162
  /**
12163
12163
  * Confirming bumps the user's security epoch server-side, which ends every
12164
- * session-minted access token. A cached one is dropped so the next bearer
12165
- * call mints a fresh token.
12164
+ * session-minted access token and OAuth artifact of the user. Cached and
12165
+ * in-flight tokens are dropped so the next bearer call mints a fresh one
12166
+ * from the (surviving) session.
12166
12167
  */
12167
12168
  confirmTotp(body: TotpConfirmRequest): Promise<TotpConfirmResponse>;
12168
12169
  /** Step-up: 403 `mfa_step_up_failed` on a wrong proof, 429 `mfa_rate_limited`. */
@@ -12280,6 +12281,12 @@ declare class WitniumchainClient {
12280
12281
  * valid session.
12281
12282
  */
12282
12283
  exchangeSessionForToken(): Promise<OAuthTokenSnapshot>;
12284
+ private assertCredentialGeneration;
12285
+ /**
12286
+ * Forget every cached credential and orphan in-flight mints/refreshes, so
12287
+ * none of them can store a token afterwards.
12288
+ */
12289
+ private invalidateCredentials;
12283
12290
  /**
12284
12291
  * Best-effort transparent mint for the `BearerJWT` path: if no access token
12285
12292
  * is cached, try the session exchange (single-flight). Swallows failure so
package/dist/index.d.ts CHANGED
@@ -12044,12 +12044,12 @@ declare class WitniumchainClient {
12044
12044
  private refreshToken;
12045
12045
  private hasRefreshCookie;
12046
12046
  private readonly oauthClientId;
12047
- private pendingLogins;
12048
12047
  private readonly verifierStorage;
12049
12048
  private discoveryCache;
12050
12049
  private refreshInFlight;
12051
12050
  private accessTokenFromSession;
12052
12051
  private sessionExchangeInFlight;
12052
+ private credentialGeneration;
12053
12053
  /** Subscriptions / billing helpers. See {@link Subscriptions}. */
12054
12054
  readonly subscriptions: Subscriptions;
12055
12055
  /** Delegated-key namespace including the one-call {@link DelegatedKeys.provision} flow. */
@@ -12161,8 +12161,9 @@ declare class WitniumchainClient {
12161
12161
  enrollTotp(): Promise<TotpEnrollResponse>;
12162
12162
  /**
12163
12163
  * Confirming bumps the user's security epoch server-side, which ends every
12164
- * session-minted access token. A cached one is dropped so the next bearer
12165
- * call mints a fresh token.
12164
+ * session-minted access token and OAuth artifact of the user. Cached and
12165
+ * in-flight tokens are dropped so the next bearer call mints a fresh one
12166
+ * from the (surviving) session.
12166
12167
  */
12167
12168
  confirmTotp(body: TotpConfirmRequest): Promise<TotpConfirmResponse>;
12168
12169
  /** Step-up: 403 `mfa_step_up_failed` on a wrong proof, 429 `mfa_rate_limited`. */
@@ -12280,6 +12281,12 @@ declare class WitniumchainClient {
12280
12281
  * valid session.
12281
12282
  */
12282
12283
  exchangeSessionForToken(): Promise<OAuthTokenSnapshot>;
12284
+ private assertCredentialGeneration;
12285
+ /**
12286
+ * Forget every cached credential and orphan in-flight mints/refreshes, so
12287
+ * none of them can store a token afterwards.
12288
+ */
12289
+ private invalidateCredentials;
12283
12290
  /**
12284
12291
  * Best-effort transparent mint for the `BearerJWT` path: if no access token
12285
12292
  * is cached, try the session exchange (single-flight). Swallows failure so
package/dist/index.js CHANGED
@@ -94,11 +94,6 @@ var WitniumchainClient = class {
94
94
  // refresh might work even when the in-memory refresh token is undefined.
95
95
  hasRefreshCookie = false;
96
96
  oauthClientId;
97
- // The redirect URI the most recent `beginOAuthLogin` issued, alongside the
98
- // PKCE verifier. `completeOAuthLogin` reads it back so the token-exchange
99
- // request matches the original /auth request (RFC 6749 §4.1.3 requires
100
- // redirect_uri at /token to equal the one at /auth). Keyed by state.
101
- pendingLogins = /* @__PURE__ */ new Map();
102
97
  verifierStorage;
103
98
  // Cache of the parsed OIDC discovery document keyed by issuer URL. Saves a
104
99
  // round trip on every OAuth call after the first. oidc-provider's discovery
@@ -121,6 +116,11 @@ var WitniumchainClient = class {
121
116
  // first BearerJWT call (and any concurrent fan-out) awaits one in-flight
122
117
  // mint rather than minting N tokens.
123
118
  sessionExchangeInFlight;
119
+ // Bumped whenever the cached credentials stop being valid (sign-out, the
120
+ // TOTP-confirm epoch bump). A mint or refresh that started under an older
121
+ // generation must not store its result: it could be a previous user's
122
+ // token, or one the server has already ended.
123
+ credentialGeneration = 0;
124
124
  /** Subscriptions / billing helpers. See {@link Subscriptions}. */
125
125
  subscriptions;
126
126
  /** Delegated-key namespace including the one-call {@link DelegatedKeys.provision} flow. */
@@ -502,18 +502,16 @@ var WitniumchainClient = class {
502
502
  }
503
503
  /**
504
504
  * Confirming bumps the user's security epoch server-side, which ends every
505
- * session-minted access token. A cached one is dropped so the next bearer
506
- * call mints a fresh token.
505
+ * session-minted access token and OAuth artifact of the user. Cached and
506
+ * in-flight tokens are dropped so the next bearer call mints a fresh one
507
+ * from the (surviving) session.
507
508
  */
508
509
  async confirmTotp(body) {
509
510
  const res = await this.req("POST", "/v1/account/mfa/totp/confirm", {
510
511
  auth: "SessionCookie",
511
512
  body
512
513
  });
513
- if (this.accessTokenFromSession) {
514
- this.accessToken = void 0;
515
- this.accessTokenFromSession = false;
516
- }
514
+ this.invalidateCredentials();
517
515
  return res;
518
516
  }
519
517
  /** Step-up: 403 `mfa_step_up_failed` on a wrong proof, 429 `mfa_rate_limited`. */
@@ -563,7 +561,9 @@ var WitniumchainClient = class {
563
561
  // Routing: every method here passes `service: 'chain'` to `req()`. Calls
564
562
  // throw at call time if `chainBaseUrl` wasn't configured.
565
563
  //
566
- // Auth: BearerJWT. The accounts-issued OAuth access token already carries
564
+ // Auth: the contract, witness, transaction and wallet reads are public on
565
+ // chain-api (anyone can check a witness), so they send no credential. The
566
+ // dashboard reads are BearerJWT: the accounts-issued token already carries
567
567
  // `aud=https://api.witniumchain.com`, so the same `accessToken` works
568
568
  // unchanged against both services.
569
569
  //
@@ -577,49 +577,49 @@ var WitniumchainClient = class {
577
577
  return this.req(
578
578
  "GET",
579
579
  `/v5/contracts/${encodeURIComponent(contractAddress)}/info`,
580
- { auth: "BearerJWT", service: "chain" }
580
+ { auth: "Public", service: "chain" }
581
581
  );
582
582
  }
583
583
  getContractVerification(contractAddress) {
584
584
  return this.req(
585
585
  "GET",
586
586
  `/v5/contracts/${encodeURIComponent(contractAddress)}/verify`,
587
- { auth: "BearerJWT", service: "chain" }
587
+ { auth: "Public", service: "chain" }
588
588
  );
589
589
  }
590
590
  listWitnessesV5(contractAddress, params) {
591
591
  return this.req(
592
592
  "GET",
593
593
  `/v5/contracts/${encodeURIComponent(contractAddress)}/witnesses`,
594
- { auth: "BearerJWT", service: "chain", query: params }
594
+ { auth: "Public", service: "chain", query: params }
595
595
  );
596
596
  }
597
597
  getWitnessV5(contractAddress, witnessId) {
598
598
  return this.req(
599
599
  "GET",
600
600
  `/v5/contracts/${encodeURIComponent(contractAddress)}/witnesses/${encodeURIComponent(witnessId)}`,
601
- { auth: "BearerJWT", service: "chain" }
601
+ { auth: "Public", service: "chain" }
602
602
  );
603
603
  }
604
604
  getContractTransaction(contractAddress, txHash) {
605
605
  return this.req(
606
606
  "GET",
607
607
  `/v5/contracts/${encodeURIComponent(contractAddress)}/transactions/${encodeURIComponent(txHash)}`,
608
- { auth: "BearerJWT", service: "chain" }
608
+ { auth: "Public", service: "chain" }
609
609
  );
610
610
  }
611
611
  getTransaction(txHash) {
612
612
  return this.req(
613
613
  "GET",
614
614
  `/v5/transactions/${encodeURIComponent(txHash)}`,
615
- { auth: "BearerJWT", service: "chain" }
615
+ { auth: "Public", service: "chain" }
616
616
  );
617
617
  }
618
618
  getWalletBalance(address) {
619
619
  return this.req(
620
620
  "GET",
621
621
  `/v5/wallets/${encodeURIComponent(address)}/balance`,
622
- { auth: "BearerJWT", service: "chain" }
622
+ { auth: "Public", service: "chain" }
623
623
  );
624
624
  }
625
625
  getDashboardContract() {
@@ -699,8 +699,9 @@ var WitniumchainClient = class {
699
699
  url.searchParams.set("code_challenge", challenge);
700
700
  url.searchParams.set("code_challenge_method", "S256");
701
701
  if (args.prompt) url.searchParams.set("prompt", args.prompt);
702
- this.verifierStorageOrDefault().set(state, verifier);
703
- this.pendingLogins.set(state, { redirectUri: args.redirectUri });
702
+ const storage = this.verifierStorageOrDefault();
703
+ storage.set(state, verifier);
704
+ storage.set(redirectUriKey(state), args.redirectUri);
704
705
  return { authorizationUrl: url.toString(), state };
705
706
  }
706
707
  /**
@@ -753,24 +754,24 @@ var WitniumchainClient = class {
753
754
  message: "WitniumchainClient: no PKCE verifier stored for this `state`. The login flow either was not started in this tab, was already completed, or the sessionStorage entry was cleared."
754
755
  });
755
756
  }
756
- const pending = this.pendingLogins.get(state);
757
- if (!pending) {
757
+ const redirectUri = storage.get(redirectUriKey(state));
758
+ if (!redirectUri) {
758
759
  storage.remove(state);
759
760
  throw new WitniumchainApiError({
760
761
  status: 0,
761
- message: "WitniumchainClient: PKCE verifier exists but the redirectUri for this `state` was not found in client memory (the client instance was replaced between beginOAuthLogin and completeOAuthLogin)."
762
+ message: "WitniumchainClient: PKCE verifier exists but the redirectUri stored with it for this `state` is missing."
762
763
  });
763
764
  }
764
765
  const discovery = await this.fetchDiscovery();
765
766
  const form = new URLSearchParams();
766
767
  form.set("grant_type", "authorization_code");
767
768
  form.set("code", code);
768
- form.set("redirect_uri", pending.redirectUri);
769
+ form.set("redirect_uri", redirectUri);
769
770
  form.set("client_id", this.oauthClientId);
770
771
  form.set("code_verifier", verifier);
771
772
  const tokens = await this.postTokenEndpoint(discovery.token_endpoint, form);
772
773
  storage.remove(state);
773
- this.pendingLogins.delete(state);
774
+ storage.remove(redirectUriKey(state));
774
775
  return this.persistTokenResponse(tokens);
775
776
  }
776
777
  /**
@@ -791,14 +792,16 @@ var WitniumchainClient = class {
791
792
  */
792
793
  async refreshAccessToken() {
793
794
  if (this.refreshInFlight) return this.refreshInFlight;
794
- this.refreshInFlight = (async () => {
795
+ let flight = void 0;
796
+ flight = (async () => {
795
797
  try {
796
798
  return await this.refreshAccessTokenInternal();
797
799
  } finally {
798
- this.refreshInFlight = void 0;
800
+ if (this.refreshInFlight === flight) this.refreshInFlight = void 0;
799
801
  }
800
802
  })();
801
- return this.refreshInFlight;
803
+ this.refreshInFlight = flight;
804
+ return flight;
802
805
  }
803
806
  /**
804
807
  * Exchange the logged-in session (the `wac_session` cookie) for a short-lived
@@ -823,9 +826,12 @@ var WitniumchainClient = class {
823
826
  */
824
827
  async exchangeSessionForToken() {
825
828
  if (this.sessionExchangeInFlight) return this.sessionExchangeInFlight;
826
- this.sessionExchangeInFlight = (async () => {
829
+ const generation = this.credentialGeneration;
830
+ let flight = void 0;
831
+ flight = (async () => {
827
832
  try {
828
833
  const res = await this.req("POST", "/v1/auth/access-token", { auth: "SessionCookie" });
834
+ this.assertCredentialGeneration(generation);
829
835
  this.accessToken = res.accessToken;
830
836
  this.accessTokenFromSession = true;
831
837
  this.refreshToken = void 0;
@@ -835,10 +841,32 @@ var WitniumchainClient = class {
835
841
  expiresAt: Math.floor(new Date(res.expiresAt).getTime() / 1e3)
836
842
  };
837
843
  } finally {
838
- this.sessionExchangeInFlight = void 0;
844
+ if (this.sessionExchangeInFlight === flight) this.sessionExchangeInFlight = void 0;
839
845
  }
840
846
  })();
841
- return this.sessionExchangeInFlight;
847
+ this.sessionExchangeInFlight = flight;
848
+ return flight;
849
+ }
850
+ assertCredentialGeneration(generation) {
851
+ if (generation !== this.credentialGeneration) {
852
+ throw new WitniumchainApiError({
853
+ status: 0,
854
+ message: "WitniumchainClient: the session changed (sign-out or MFA confirmation) while a token request was in flight; its token was discarded."
855
+ });
856
+ }
857
+ }
858
+ /**
859
+ * Forget every cached credential and orphan in-flight mints/refreshes, so
860
+ * none of them can store a token afterwards.
861
+ */
862
+ invalidateCredentials() {
863
+ this.credentialGeneration++;
864
+ this.accessToken = void 0;
865
+ this.accessTokenFromSession = false;
866
+ this.refreshToken = void 0;
867
+ this.hasRefreshCookie = false;
868
+ this.sessionExchangeInFlight = void 0;
869
+ this.refreshInFlight = void 0;
842
870
  }
843
871
  /**
844
872
  * Best-effort transparent mint for the `BearerJWT` path: if no access token
@@ -850,7 +878,8 @@ var WitniumchainClient = class {
850
878
  if (this.accessToken) return;
851
879
  try {
852
880
  await this.exchangeSessionForToken();
853
- } catch {
881
+ } catch (err) {
882
+ if (!(err instanceof WitniumchainApiError && err.status === 401)) throw err;
854
883
  }
855
884
  }
856
885
  /**
@@ -891,11 +920,7 @@ var WitniumchainClient = class {
891
920
  * dropped — which it is, here.
892
921
  */
893
922
  signOut() {
894
- this.accessToken = void 0;
895
- this.accessTokenFromSession = false;
896
- this.refreshToken = void 0;
897
- this.hasRefreshCookie = false;
898
- this.pendingLogins.clear();
923
+ this.invalidateCredentials();
899
924
  }
900
925
  // ────────────────────────────────────────────────────────────────────────
901
926
  // Internal: OAuth flow helpers
@@ -1014,6 +1039,7 @@ var WitniumchainClient = class {
1014
1039
  message: "WitniumchainClient: no refresh credential available. Call `completeOAuthLogin` first \u2014 the server delivers the refresh token either in the response body (Node) or as an HttpOnly cookie (browser)."
1015
1040
  });
1016
1041
  }
1042
+ const generation = this.credentialGeneration;
1017
1043
  const discovery = await this.fetchDiscovery();
1018
1044
  const form = new URLSearchParams();
1019
1045
  form.set("grant_type", "refresh_token");
@@ -1021,15 +1047,18 @@ var WitniumchainClient = class {
1021
1047
  if (this.refreshToken) {
1022
1048
  form.set("refresh_token", this.refreshToken);
1023
1049
  }
1050
+ let tokens;
1024
1051
  try {
1025
- const tokens = await this.postTokenEndpoint(discovery.token_endpoint, form);
1026
- return this.persistTokenResponse(tokens);
1052
+ tokens = await this.postTokenEndpoint(discovery.token_endpoint, form);
1027
1053
  } catch (err) {
1054
+ this.assertCredentialGeneration(generation);
1028
1055
  this.accessToken = void 0;
1029
1056
  this.refreshToken = void 0;
1030
1057
  this.hasRefreshCookie = false;
1031
1058
  throw err;
1032
1059
  }
1060
+ this.assertCredentialGeneration(generation);
1061
+ return this.persistTokenResponse(tokens);
1033
1062
  }
1034
1063
  // ────────────────────────────────────────────────────────────────────────
1035
1064
  // Internal: fetch wrapper that maps non-2xx → WitniumchainApiError
@@ -1472,6 +1501,9 @@ var MfaRecoveryCodes = class {
1472
1501
  return this.client.regenerateRecoveryCodes(proof);
1473
1502
  }
1474
1503
  };
1504
+ function redirectUriKey(state) {
1505
+ return `${state}.redirect_uri`;
1506
+ }
1475
1507
  function sleep(ms) {
1476
1508
  return new Promise((resolve) => setTimeout(resolve, ms));
1477
1509
  }