@witnium-tech/witniumchain 0.9.0 → 0.11.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.js CHANGED
@@ -111,6 +111,16 @@ var WitniumchainClient = class {
111
111
  // tokens rotate on use (D10), so a second concurrent refresh would race the
112
112
  // first and one of them would 401.
113
113
  refreshInFlight;
114
+ // True when the current `accessToken` was minted by the session→token
115
+ // exchange (`exchangeSessionForToken`) rather than the OAuth code/refresh
116
+ // flow. Drives the 401-retry: a session-exchanged token has no refresh
117
+ // credential, so on expiry the SDK re-mints from the cookie instead of
118
+ // refreshing. Reset to false whenever an OAuth /token response lands.
119
+ accessTokenFromSession = false;
120
+ // Single-flight gate for the session→token exchange. A cookie-only client's
121
+ // first BearerJWT call (and any concurrent fan-out) awaits one in-flight
122
+ // mint rather than minting N tokens.
123
+ sessionExchangeInFlight;
114
124
  /** Subscriptions / billing helpers. See {@link Subscriptions}. */
115
125
  subscriptions;
116
126
  /** Delegated-key namespace including the one-call {@link DelegatedKeys.provision} flow. */
@@ -265,43 +275,6 @@ var WitniumchainClient = class {
265
275
  return this.req("GET", "/v1/orgs/me/users", { auth: "OrgApiKey" });
266
276
  }
267
277
  // ────────────────────────────────────────────────────────────────────────
268
- // Admin (/v1/admin/organizations/*)
269
- // ────────────────────────────────────────────────────────────────────────
270
- createOrganization(body) {
271
- return this.req("POST", "/v1/admin/organizations", {
272
- auth: "AdminToken",
273
- body
274
- });
275
- }
276
- setOrgAccountType(id, body) {
277
- return this.req(
278
- "PATCH",
279
- `/v1/admin/organizations/${encodeURIComponent(id)}/account-type`,
280
- { auth: "AdminToken", body }
281
- );
282
- }
283
- verifyOrganization(id) {
284
- return this.req(
285
- "PATCH",
286
- `/v1/admin/organizations/${encodeURIComponent(id)}/verify`,
287
- { auth: "AdminToken" }
288
- );
289
- }
290
- rotateOrgApiKey(id) {
291
- return this.req(
292
- "POST",
293
- `/v1/admin/organizations/${encodeURIComponent(id)}/rotate-key`,
294
- { auth: "AdminToken" }
295
- );
296
- }
297
- adjustOrgCredits(id, body) {
298
- return this.req(
299
- "POST",
300
- `/v1/admin/organizations/${encodeURIComponent(id)}/adjust-credits`,
301
- { auth: "AdminToken", body }
302
- );
303
- }
304
- // ────────────────────────────────────────────────────────────────────────
305
278
  // Delegated keys (/v1/users/me/delegated-keys/*)
306
279
  // ────────────────────────────────────────────────────────────────────────
307
280
  listDelegatedKeys(query) {
@@ -787,6 +760,84 @@ var WitniumchainClient = class {
787
760
  })();
788
761
  return this.refreshInFlight;
789
762
  }
763
+ /**
764
+ * Exchange the logged-in session (the `wac_session` cookie) for a short-lived
765
+ * bearer access token and cache it as the client's `accessToken`.
766
+ *
767
+ * This is the SDK side of the session→token model (accounts'
768
+ * `POST /v1/auth/access-token`): the dashboard authenticates with the
769
+ * cookie, but the operational API (delegated keys, witness writes, chain
770
+ * reads) is bearer-only — no endpoint accepts the ambient cookie, so there's
771
+ * no CSRF surface even with `SameSite=None`. The minted token is
772
+ * byte-compatible with an OAuth user token (same signing key, `iss`, `aud`,
773
+ * scope + org/role/contracts claims), so every `BearerJWT` route accepts it.
774
+ *
775
+ * Callers rarely invoke this directly — {@link applyAuth} mints transparently
776
+ * on the first `BearerJWT` call when no token is set and re-mints on a 401.
777
+ * It's exposed so a dashboard can pre-warm the token right after login.
778
+ *
779
+ * Requires a session: either `sessionCookie` on the constructor (Node) or an
780
+ * ambient `wac_session` cookie carried by the browser (`credentials:
781
+ * 'include'`). Throws {@link WitniumchainApiError} (401) when there's no
782
+ * valid session.
783
+ */
784
+ async exchangeSessionForToken() {
785
+ if (this.sessionExchangeInFlight) return this.sessionExchangeInFlight;
786
+ this.sessionExchangeInFlight = (async () => {
787
+ try {
788
+ const res = await this.req("POST", "/v1/auth/access-token", { auth: "SessionCookie" });
789
+ this.accessToken = res.accessToken;
790
+ this.accessTokenFromSession = true;
791
+ this.refreshToken = void 0;
792
+ this.hasRefreshCookie = false;
793
+ return {
794
+ accessToken: res.accessToken,
795
+ expiresAt: Math.floor(new Date(res.expiresAt).getTime() / 1e3)
796
+ };
797
+ } finally {
798
+ this.sessionExchangeInFlight = void 0;
799
+ }
800
+ })();
801
+ return this.sessionExchangeInFlight;
802
+ }
803
+ /**
804
+ * Best-effort transparent mint for the `BearerJWT` path: if no access token
805
+ * is cached, try the session exchange (single-flight). Swallows failure so
806
+ * the caller in {@link applyAuth} surfaces the actionable "no token / no
807
+ * session" guidance rather than a raw 401 from the exchange endpoint.
808
+ */
809
+ async ensureSessionAccessToken() {
810
+ if (this.accessToken) return;
811
+ try {
812
+ await this.exchangeSessionForToken();
813
+ } catch {
814
+ }
815
+ }
816
+ /**
817
+ * Obtain a fresh access token for a 401-retry, picking the right mechanism:
818
+ * an OAuth refresh when a refresh credential exists, else a session re-mint
819
+ * for a session-exchanged token. Returns true when a fresh token is in hand.
820
+ */
821
+ async reauthenticateForRetry() {
822
+ if (this.refreshToken || this.hasRefreshCookie) {
823
+ try {
824
+ await this.refreshAccessToken();
825
+ return true;
826
+ } catch {
827
+ return false;
828
+ }
829
+ }
830
+ if (this.accessTokenFromSession) {
831
+ this.accessToken = void 0;
832
+ try {
833
+ await this.exchangeSessionForToken();
834
+ return true;
835
+ } catch {
836
+ return false;
837
+ }
838
+ }
839
+ return false;
840
+ }
790
841
  /**
791
842
  * End the OAuth session.
792
843
  *
@@ -801,6 +852,7 @@ var WitniumchainClient = class {
801
852
  */
802
853
  signOut() {
803
854
  this.accessToken = void 0;
855
+ this.accessTokenFromSession = false;
804
856
  this.refreshToken = void 0;
805
857
  this.hasRefreshCookie = false;
806
858
  this.pendingLogins.clear();
@@ -898,6 +950,7 @@ var WitniumchainClient = class {
898
950
  });
899
951
  }
900
952
  this.accessToken = tokens.access_token;
953
+ this.accessTokenFromSession = false;
901
954
  if (tokens.refresh_token) {
902
955
  this.refreshToken = tokens.refresh_token;
903
956
  } else {
@@ -1028,13 +1081,9 @@ var WitniumchainClient = class {
1028
1081
  if (opts._isRetry) return false;
1029
1082
  if (opts.auth !== "BearerJWT") return false;
1030
1083
  if (res.status !== 401) return false;
1031
- if (!this.refreshToken && !this.hasRefreshCookie) return false;
1032
- try {
1033
- await this.refreshAccessToken();
1034
- return true;
1035
- } catch {
1084
+ if (!this.refreshToken && !this.hasRefreshCookie && !this.accessTokenFromSession)
1036
1085
  return false;
1037
- }
1086
+ return this.reauthenticateForRetry();
1038
1087
  }
1039
1088
  /**
1040
1089
  * Same decision as `shouldRefreshAndRetry` but for the JSON-parsed path,
@@ -1048,18 +1097,14 @@ var WitniumchainClient = class {
1048
1097
  if (opts._isRetry) return false;
1049
1098
  if (opts.auth !== "BearerJWT") return false;
1050
1099
  if (status !== 401) return false;
1051
- if (!this.refreshToken && !this.hasRefreshCookie) return false;
1100
+ if (!this.refreshToken && !this.hasRefreshCookie && !this.accessTokenFromSession)
1101
+ return false;
1052
1102
  const body = parsed;
1053
1103
  const label = typeof body?.error === "string" ? body.error : void 0;
1054
1104
  if (label !== void 0 && label !== "token_expired" && label !== "invalid_token") {
1055
1105
  return false;
1056
1106
  }
1057
- try {
1058
- await this.refreshAccessToken();
1059
- return true;
1060
- } catch {
1061
- return false;
1062
- }
1107
+ return this.reauthenticateForRetry();
1063
1108
  }
1064
1109
  /**
1065
1110
  * Resolve which base URL to call. Default is accounts (`baseUrl`). When a
@@ -1099,9 +1144,12 @@ var WitniumchainClient = class {
1099
1144
  return;
1100
1145
  }
1101
1146
  case "BearerJWT": {
1147
+ if (!this.accessToken) {
1148
+ await this.ensureSessionAccessToken();
1149
+ }
1102
1150
  if (!this.accessToken) {
1103
1151
  throw new Error(
1104
- `WitniumchainClient: ${method} ${path} requires an OAuth access token. Pass \`accessToken\` to the constructor, or call \`beginOAuthLogin\`/\`completeOAuthLogin\` to obtain one.`
1152
+ `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.`
1105
1153
  );
1106
1154
  }
1107
1155
  headers["authorization"] = `Bearer ${this.accessToken}`;
@@ -1116,15 +1164,6 @@ var WitniumchainClient = class {
1116
1164
  headers["authorization"] = `Bearer ${this.cfg.orgApiKey}`;
1117
1165
  return;
1118
1166
  }
1119
- case "AdminToken": {
1120
- if (!this.cfg.adminToken) {
1121
- throw new Error(
1122
- `WitniumchainClient: ${method} ${path} requires an admin token. Pass \`adminToken\` to the constructor.`
1123
- );
1124
- }
1125
- headers["authorization"] = `Bearer ${this.cfg.adminToken}`;
1126
- return;
1127
- }
1128
1167
  case "SignedRequest": {
1129
1168
  if (!this.cfg.signedRequest) {
1130
1169
  throw new Error(
@@ -1411,60 +1450,6 @@ async function sha256Hex(input) {
1411
1450
  return out;
1412
1451
  }
1413
1452
 
1414
- // src/admin-client.ts
1415
- var WitniumchainAdminClient = class {
1416
- inner;
1417
- constructor(config) {
1418
- if (!config.adminToken) {
1419
- throw new Error("WitniumchainAdminClient: adminToken is required");
1420
- }
1421
- this.inner = new WitniumchainClient({
1422
- baseUrl: config.baseUrl,
1423
- adminToken: config.adminToken,
1424
- timeout: config.timeout,
1425
- fetch: config.fetch
1426
- });
1427
- }
1428
- /**
1429
- * Mint a new organisation. The returned `apiKey` is shown ONCE — the server
1430
- * only retains its SHA-256 hash. Persist it before the response leaves
1431
- * scope; there is no recovery path.
1432
- *
1433
- * @param body Organisation seed: name, email, optional accountType and
1434
- * signup credit grant, optional skip-email-verification flag.
1435
- */
1436
- createOrganization(body) {
1437
- return this.inner.createOrganization(body);
1438
- }
1439
- /** Flip an org between `metered` (Stripe checkout + credit ledger) and `unlimited` (flat-fee). */
1440
- setAccountType(orgId, accountType) {
1441
- return this.inner.setOrgAccountType(orgId, { accountType });
1442
- }
1443
- /** Mark an org's email as verified. Prerequisite for the org to create users. */
1444
- verifyEmail(orgId) {
1445
- return this.inner.verifyOrganization(orgId);
1446
- }
1447
- /**
1448
- * Rotate the org's API key. The previous `wcorg_live_…` is invalidated and
1449
- * the new key is returned ONCE — same one-time-secret semantics as
1450
- * {@link createOrganization}.
1451
- */
1452
- rotateApiKey(orgId) {
1453
- return this.inner.rotateOrgApiKey(orgId);
1454
- }
1455
- /**
1456
- * Apply a signed credit delta to an org's ledger. Positive `delta` grants
1457
- * credits (goodwill, migration backfill); negative claws them back.
1458
- * Recorded as `reason: adjustment` with the supplied `note`.
1459
- *
1460
- * Use sparingly — this bypasses Stripe and should be auditable from the
1461
- * `note` alone.
1462
- */
1463
- adjustCredits(orgId, delta, note) {
1464
- return this.inner.adjustOrgCredits(orgId, { delta, note });
1465
- }
1466
- };
1467
-
1468
1453
  // src/chain-admin-client.ts
1469
1454
  var WitniumchainChainAdminClient = class {
1470
1455
  baseUrl;
@@ -1666,7 +1651,6 @@ exports.OrgMembers = OrgMembers;
1666
1651
  exports.OrgUsers = OrgUsers;
1667
1652
  exports.SigningKeys = SigningKeys;
1668
1653
  exports.Subscriptions = Subscriptions;
1669
- exports.WitniumchainAdminClient = WitniumchainAdminClient;
1670
1654
  exports.WitniumchainApiError = WitniumchainApiError;
1671
1655
  exports.WitniumchainChainAdminClient = WitniumchainChainAdminClient;
1672
1656
  exports.WitniumchainClient = WitniumchainClient;