@witnium-tech/witniumchain 0.12.0 → 0.12.1

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.mjs CHANGED
@@ -119,6 +119,11 @@ var WitniumchainClient = class {
119
119
  // first BearerJWT call (and any concurrent fan-out) awaits one in-flight
120
120
  // mint rather than minting N tokens.
121
121
  sessionExchangeInFlight;
122
+ // Bumped whenever the cached credentials stop being valid (sign-out, the
123
+ // TOTP-confirm epoch bump). A mint or refresh that started under an older
124
+ // generation must not store its result: it could be a previous user's
125
+ // token, or one the server has already ended.
126
+ credentialGeneration = 0;
122
127
  /** Subscriptions / billing helpers. See {@link Subscriptions}. */
123
128
  subscriptions;
124
129
  /** Delegated-key namespace including the one-call {@link DelegatedKeys.provision} flow. */
@@ -500,18 +505,16 @@ var WitniumchainClient = class {
500
505
  }
501
506
  /**
502
507
  * Confirming bumps the user's security epoch server-side, which ends every
503
- * session-minted access token. A cached one is dropped so the next bearer
504
- * call mints a fresh token.
508
+ * session-minted access token and OAuth artifact of the user. Cached and
509
+ * in-flight tokens are dropped so the next bearer call mints a fresh one
510
+ * from the (surviving) session.
505
511
  */
506
512
  async confirmTotp(body) {
507
513
  const res = await this.req("POST", "/v1/account/mfa/totp/confirm", {
508
514
  auth: "SessionCookie",
509
515
  body
510
516
  });
511
- if (this.accessTokenFromSession) {
512
- this.accessToken = void 0;
513
- this.accessTokenFromSession = false;
514
- }
517
+ this.invalidateCredentials();
515
518
  return res;
516
519
  }
517
520
  /** Step-up: 403 `mfa_step_up_failed` on a wrong proof, 429 `mfa_rate_limited`. */
@@ -789,14 +792,16 @@ var WitniumchainClient = class {
789
792
  */
790
793
  async refreshAccessToken() {
791
794
  if (this.refreshInFlight) return this.refreshInFlight;
792
- this.refreshInFlight = (async () => {
795
+ let flight = void 0;
796
+ flight = (async () => {
793
797
  try {
794
798
  return await this.refreshAccessTokenInternal();
795
799
  } finally {
796
- this.refreshInFlight = void 0;
800
+ if (this.refreshInFlight === flight) this.refreshInFlight = void 0;
797
801
  }
798
802
  })();
799
- return this.refreshInFlight;
803
+ this.refreshInFlight = flight;
804
+ return flight;
800
805
  }
801
806
  /**
802
807
  * Exchange the logged-in session (the `wac_session` cookie) for a short-lived
@@ -821,9 +826,12 @@ var WitniumchainClient = class {
821
826
  */
822
827
  async exchangeSessionForToken() {
823
828
  if (this.sessionExchangeInFlight) return this.sessionExchangeInFlight;
824
- this.sessionExchangeInFlight = (async () => {
829
+ const generation = this.credentialGeneration;
830
+ let flight = void 0;
831
+ flight = (async () => {
825
832
  try {
826
833
  const res = await this.req("POST", "/v1/auth/access-token", { auth: "SessionCookie" });
834
+ this.assertCredentialGeneration(generation);
827
835
  this.accessToken = res.accessToken;
828
836
  this.accessTokenFromSession = true;
829
837
  this.refreshToken = void 0;
@@ -833,10 +841,32 @@ var WitniumchainClient = class {
833
841
  expiresAt: Math.floor(new Date(res.expiresAt).getTime() / 1e3)
834
842
  };
835
843
  } finally {
836
- this.sessionExchangeInFlight = void 0;
844
+ if (this.sessionExchangeInFlight === flight) this.sessionExchangeInFlight = void 0;
837
845
  }
838
846
  })();
839
- 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;
840
870
  }
841
871
  /**
842
872
  * Best-effort transparent mint for the `BearerJWT` path: if no access token
@@ -848,7 +878,8 @@ var WitniumchainClient = class {
848
878
  if (this.accessToken) return;
849
879
  try {
850
880
  await this.exchangeSessionForToken();
851
- } catch {
881
+ } catch (err) {
882
+ if (!(err instanceof WitniumchainApiError && err.status === 401)) throw err;
852
883
  }
853
884
  }
854
885
  /**
@@ -889,10 +920,7 @@ var WitniumchainClient = class {
889
920
  * dropped — which it is, here.
890
921
  */
891
922
  signOut() {
892
- this.accessToken = void 0;
893
- this.accessTokenFromSession = false;
894
- this.refreshToken = void 0;
895
- this.hasRefreshCookie = false;
923
+ this.invalidateCredentials();
896
924
  this.pendingLogins.clear();
897
925
  }
898
926
  // ────────────────────────────────────────────────────────────────────────
@@ -1012,6 +1040,7 @@ var WitniumchainClient = class {
1012
1040
  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)."
1013
1041
  });
1014
1042
  }
1043
+ const generation = this.credentialGeneration;
1015
1044
  const discovery = await this.fetchDiscovery();
1016
1045
  const form = new URLSearchParams();
1017
1046
  form.set("grant_type", "refresh_token");
@@ -1019,15 +1048,18 @@ var WitniumchainClient = class {
1019
1048
  if (this.refreshToken) {
1020
1049
  form.set("refresh_token", this.refreshToken);
1021
1050
  }
1051
+ let tokens;
1022
1052
  try {
1023
- const tokens = await this.postTokenEndpoint(discovery.token_endpoint, form);
1024
- return this.persistTokenResponse(tokens);
1053
+ tokens = await this.postTokenEndpoint(discovery.token_endpoint, form);
1025
1054
  } catch (err) {
1055
+ this.assertCredentialGeneration(generation);
1026
1056
  this.accessToken = void 0;
1027
1057
  this.refreshToken = void 0;
1028
1058
  this.hasRefreshCookie = false;
1029
1059
  throw err;
1030
1060
  }
1061
+ this.assertCredentialGeneration(generation);
1062
+ return this.persistTokenResponse(tokens);
1031
1063
  }
1032
1064
  // ────────────────────────────────────────────────────────────────────────
1033
1065
  // Internal: fetch wrapper that maps non-2xx → WitniumchainApiError