@witnium-tech/witniumchain 0.8.0 → 0.10.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/dist/index.mjs CHANGED
@@ -109,6 +109,16 @@ var WitniumchainClient = class {
109
109
  // tokens rotate on use (D10), so a second concurrent refresh would race the
110
110
  // first and one of them would 401.
111
111
  refreshInFlight;
112
+ // True when the current `accessToken` was minted by the session→token
113
+ // exchange (`exchangeSessionForToken`) rather than the OAuth code/refresh
114
+ // flow. Drives the 401-retry: a session-exchanged token has no refresh
115
+ // credential, so on expiry the SDK re-mints from the cookie instead of
116
+ // refreshing. Reset to false whenever an OAuth /token response lands.
117
+ accessTokenFromSession = false;
118
+ // Single-flight gate for the session→token exchange. A cookie-only client's
119
+ // first BearerJWT call (and any concurrent fan-out) awaits one in-flight
120
+ // mint rather than minting N tokens.
121
+ sessionExchangeInFlight;
112
122
  /** Subscriptions / billing helpers. See {@link Subscriptions}. */
113
123
  subscriptions;
114
124
  /** Delegated-key namespace including the one-call {@link DelegatedKeys.provision} flow. */
@@ -473,6 +483,17 @@ var WitniumchainClient = class {
473
483
  deleteAccount() {
474
484
  return this.req("DELETE", "/v1/account", { auth: "SessionCookie" });
475
485
  }
486
+ /**
487
+ * Re-mint a provisioning token for a verified-but-not-yet-provisioned
488
+ * account. Use when the token from `verifyEmail` was lost (e.g. the verify
489
+ * link was opened on a different device). Throws 409 if the email isn't
490
+ * verified or a contract already exists; rate-limited per account.
491
+ */
492
+ reissueProvisioningToken() {
493
+ return this.req("POST", "/v1/account/provisioning-token/reissue", {
494
+ auth: "SessionCookie"
495
+ });
496
+ }
476
497
  // ────────────────────────────────────────────────────────────────────────
477
498
  // MFA (/v1/account/mfa/*)
478
499
  //
@@ -774,6 +795,84 @@ var WitniumchainClient = class {
774
795
  })();
775
796
  return this.refreshInFlight;
776
797
  }
798
+ /**
799
+ * Exchange the logged-in session (the `wac_session` cookie) for a short-lived
800
+ * bearer access token and cache it as the client's `accessToken`.
801
+ *
802
+ * This is the SDK side of the session→token model (accounts'
803
+ * `POST /v1/auth/access-token`): the dashboard authenticates with the
804
+ * cookie, but the operational API (delegated keys, witness writes, chain
805
+ * reads) is bearer-only — no endpoint accepts the ambient cookie, so there's
806
+ * no CSRF surface even with `SameSite=None`. The minted token is
807
+ * byte-compatible with an OAuth user token (same signing key, `iss`, `aud`,
808
+ * scope + org/role/contracts claims), so every `BearerJWT` route accepts it.
809
+ *
810
+ * Callers rarely invoke this directly — {@link applyAuth} mints transparently
811
+ * on the first `BearerJWT` call when no token is set and re-mints on a 401.
812
+ * It's exposed so a dashboard can pre-warm the token right after login.
813
+ *
814
+ * Requires a session: either `sessionCookie` on the constructor (Node) or an
815
+ * ambient `wac_session` cookie carried by the browser (`credentials:
816
+ * 'include'`). Throws {@link WitniumchainApiError} (401) when there's no
817
+ * valid session.
818
+ */
819
+ async exchangeSessionForToken() {
820
+ if (this.sessionExchangeInFlight) return this.sessionExchangeInFlight;
821
+ this.sessionExchangeInFlight = (async () => {
822
+ try {
823
+ const res = await this.req("POST", "/v1/auth/access-token", { auth: "SessionCookie" });
824
+ this.accessToken = res.accessToken;
825
+ this.accessTokenFromSession = true;
826
+ this.refreshToken = void 0;
827
+ this.hasRefreshCookie = false;
828
+ return {
829
+ accessToken: res.accessToken,
830
+ expiresAt: Math.floor(new Date(res.expiresAt).getTime() / 1e3)
831
+ };
832
+ } finally {
833
+ this.sessionExchangeInFlight = void 0;
834
+ }
835
+ })();
836
+ return this.sessionExchangeInFlight;
837
+ }
838
+ /**
839
+ * Best-effort transparent mint for the `BearerJWT` path: if no access token
840
+ * is cached, try the session exchange (single-flight). Swallows failure so
841
+ * the caller in {@link applyAuth} surfaces the actionable "no token / no
842
+ * session" guidance rather than a raw 401 from the exchange endpoint.
843
+ */
844
+ async ensureSessionAccessToken() {
845
+ if (this.accessToken) return;
846
+ try {
847
+ await this.exchangeSessionForToken();
848
+ } catch {
849
+ }
850
+ }
851
+ /**
852
+ * Obtain a fresh access token for a 401-retry, picking the right mechanism:
853
+ * an OAuth refresh when a refresh credential exists, else a session re-mint
854
+ * for a session-exchanged token. Returns true when a fresh token is in hand.
855
+ */
856
+ async reauthenticateForRetry() {
857
+ if (this.refreshToken || this.hasRefreshCookie) {
858
+ try {
859
+ await this.refreshAccessToken();
860
+ return true;
861
+ } catch {
862
+ return false;
863
+ }
864
+ }
865
+ if (this.accessTokenFromSession) {
866
+ this.accessToken = void 0;
867
+ try {
868
+ await this.exchangeSessionForToken();
869
+ return true;
870
+ } catch {
871
+ return false;
872
+ }
873
+ }
874
+ return false;
875
+ }
777
876
  /**
778
877
  * End the OAuth session.
779
878
  *
@@ -788,6 +887,7 @@ var WitniumchainClient = class {
788
887
  */
789
888
  signOut() {
790
889
  this.accessToken = void 0;
890
+ this.accessTokenFromSession = false;
791
891
  this.refreshToken = void 0;
792
892
  this.hasRefreshCookie = false;
793
893
  this.pendingLogins.clear();
@@ -885,6 +985,7 @@ var WitniumchainClient = class {
885
985
  });
886
986
  }
887
987
  this.accessToken = tokens.access_token;
988
+ this.accessTokenFromSession = false;
888
989
  if (tokens.refresh_token) {
889
990
  this.refreshToken = tokens.refresh_token;
890
991
  } else {
@@ -1015,13 +1116,9 @@ var WitniumchainClient = class {
1015
1116
  if (opts._isRetry) return false;
1016
1117
  if (opts.auth !== "BearerJWT") return false;
1017
1118
  if (res.status !== 401) return false;
1018
- if (!this.refreshToken && !this.hasRefreshCookie) return false;
1019
- try {
1020
- await this.refreshAccessToken();
1021
- return true;
1022
- } catch {
1119
+ if (!this.refreshToken && !this.hasRefreshCookie && !this.accessTokenFromSession)
1023
1120
  return false;
1024
- }
1121
+ return this.reauthenticateForRetry();
1025
1122
  }
1026
1123
  /**
1027
1124
  * Same decision as `shouldRefreshAndRetry` but for the JSON-parsed path,
@@ -1035,18 +1132,14 @@ var WitniumchainClient = class {
1035
1132
  if (opts._isRetry) return false;
1036
1133
  if (opts.auth !== "BearerJWT") return false;
1037
1134
  if (status !== 401) return false;
1038
- if (!this.refreshToken && !this.hasRefreshCookie) return false;
1135
+ if (!this.refreshToken && !this.hasRefreshCookie && !this.accessTokenFromSession)
1136
+ return false;
1039
1137
  const body = parsed;
1040
1138
  const label = typeof body?.error === "string" ? body.error : void 0;
1041
1139
  if (label !== void 0 && label !== "token_expired" && label !== "invalid_token") {
1042
1140
  return false;
1043
1141
  }
1044
- try {
1045
- await this.refreshAccessToken();
1046
- return true;
1047
- } catch {
1048
- return false;
1049
- }
1142
+ return this.reauthenticateForRetry();
1050
1143
  }
1051
1144
  /**
1052
1145
  * Resolve which base URL to call. Default is accounts (`baseUrl`). When a
@@ -1086,9 +1179,12 @@ var WitniumchainClient = class {
1086
1179
  return;
1087
1180
  }
1088
1181
  case "BearerJWT": {
1182
+ if (!this.accessToken) {
1183
+ await this.ensureSessionAccessToken();
1184
+ }
1089
1185
  if (!this.accessToken) {
1090
1186
  throw new Error(
1091
- `WitniumchainClient: ${method} ${path} requires an OAuth access token. Pass \`accessToken\` to the constructor, or call \`beginOAuthLogin\`/\`completeOAuthLogin\` to obtain one.`
1187
+ `WitniumchainClient: ${method} ${path} requires an OAuth access token. Pass \`accessToken\` to the constructor, call \`beginOAuthLogin\`/\`completeOAuthLogin\`, or \u2014 for a logged-in dashboard \u2014 provide \`sessionCookie\` (Node) or rely on the browser \`wac_session\` cookie so the SDK can mint one.`
1092
1188
  );
1093
1189
  }
1094
1190
  headers["authorization"] = `Bearer ${this.accessToken}`;