@witnium-tech/witniumchain 0.9.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/FRONTEND-INTEGRATION.md +30 -0
- package/dist/index.d.mts +37 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.js +99 -14
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +99 -14
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/FRONTEND-INTEGRATION.md
CHANGED
|
@@ -118,6 +118,36 @@ await client.login({ email, password }); // sets wac_session cookie
|
|
|
118
118
|
In a browser the `wac_session` cookie is set automatically (the SDK uses
|
|
119
119
|
`credentials: 'include'`). Server-side callers must capture + replay the cookie.
|
|
120
120
|
|
|
121
|
+
### Cookie-only dashboard → bearer calls (automatic, no OAuth needed)
|
|
122
|
+
|
|
123
|
+
You do **not** need to run the OAuth code flow to call the operational API
|
|
124
|
+
(delegated keys, witness writes, chain reads). After `login()`, just call them:
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
await client.login({ email, password }); // sets wac_session
|
|
128
|
+
await client.listDelegatedKeys(); // ← works; SDK mints a bearer token
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
How: those endpoints are bearer-only (they never accept the ambient cookie, so
|
|
132
|
+
there's **no CSRF surface** even with `SameSite=None`). On the first such call
|
|
133
|
+
the SDK transparently exchanges the session cookie for a short-lived access
|
|
134
|
+
token via `POST /v1/auth/access-token`, caches it, and re-mints on a 401. You
|
|
135
|
+
never see the token unless you want it:
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
const { accessToken, expiresAt } = await client.exchangeSessionForToken(); // optional pre-warm
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Requirements: in the browser the `wac_session` cookie rides automatically
|
|
142
|
+
(`credentials: 'include'`). Node/SSR callers pass `sessionCookie: '<value>'` to
|
|
143
|
+
the constructor. If there's no valid session, the call throws a descriptive
|
|
144
|
+
"requires an OAuth access token … or provide `sessionCookie`" error — that's
|
|
145
|
+
your cue to send the user back to login.
|
|
146
|
+
|
|
147
|
+
This is the recommended path for a first-party dashboard. Reach for the full
|
|
148
|
+
OAuth flow below only when the dashboard is itself a registered OAuth client
|
|
149
|
+
(e.g. third-party apps, or you want long-lived refresh tokens).
|
|
150
|
+
|
|
121
151
|
### Magic-link sign-in
|
|
122
152
|
|
|
123
153
|
`beginOAuthLogin` / passwordless paths exist; see the SDK's `OauthNamespace`
|
package/dist/index.d.mts
CHANGED
|
@@ -10580,6 +10580,8 @@ declare class WitniumchainClient {
|
|
|
10580
10580
|
private readonly verifierStorage;
|
|
10581
10581
|
private discoveryCache;
|
|
10582
10582
|
private refreshInFlight;
|
|
10583
|
+
private accessTokenFromSession;
|
|
10584
|
+
private sessionExchangeInFlight;
|
|
10583
10585
|
/** Subscriptions / billing helpers. See {@link Subscriptions}. */
|
|
10584
10586
|
readonly subscriptions: Subscriptions;
|
|
10585
10587
|
/** Delegated-key namespace including the one-call {@link DelegatedKeys.provision} flow. */
|
|
@@ -10754,6 +10756,41 @@ declare class WitniumchainClient {
|
|
|
10754
10756
|
* instead of retrying with a now-invalid token.
|
|
10755
10757
|
*/
|
|
10756
10758
|
refreshAccessToken(): Promise<OAuthTokenSnapshot>;
|
|
10759
|
+
/**
|
|
10760
|
+
* Exchange the logged-in session (the `wac_session` cookie) for a short-lived
|
|
10761
|
+
* bearer access token and cache it as the client's `accessToken`.
|
|
10762
|
+
*
|
|
10763
|
+
* This is the SDK side of the session→token model (accounts'
|
|
10764
|
+
* `POST /v1/auth/access-token`): the dashboard authenticates with the
|
|
10765
|
+
* cookie, but the operational API (delegated keys, witness writes, chain
|
|
10766
|
+
* reads) is bearer-only — no endpoint accepts the ambient cookie, so there's
|
|
10767
|
+
* no CSRF surface even with `SameSite=None`. The minted token is
|
|
10768
|
+
* byte-compatible with an OAuth user token (same signing key, `iss`, `aud`,
|
|
10769
|
+
* scope + org/role/contracts claims), so every `BearerJWT` route accepts it.
|
|
10770
|
+
*
|
|
10771
|
+
* Callers rarely invoke this directly — {@link applyAuth} mints transparently
|
|
10772
|
+
* on the first `BearerJWT` call when no token is set and re-mints on a 401.
|
|
10773
|
+
* It's exposed so a dashboard can pre-warm the token right after login.
|
|
10774
|
+
*
|
|
10775
|
+
* Requires a session: either `sessionCookie` on the constructor (Node) or an
|
|
10776
|
+
* ambient `wac_session` cookie carried by the browser (`credentials:
|
|
10777
|
+
* 'include'`). Throws {@link WitniumchainApiError} (401) when there's no
|
|
10778
|
+
* valid session.
|
|
10779
|
+
*/
|
|
10780
|
+
exchangeSessionForToken(): Promise<OAuthTokenSnapshot>;
|
|
10781
|
+
/**
|
|
10782
|
+
* Best-effort transparent mint for the `BearerJWT` path: if no access token
|
|
10783
|
+
* is cached, try the session exchange (single-flight). Swallows failure so
|
|
10784
|
+
* the caller in {@link applyAuth} surfaces the actionable "no token / no
|
|
10785
|
+
* session" guidance rather than a raw 401 from the exchange endpoint.
|
|
10786
|
+
*/
|
|
10787
|
+
private ensureSessionAccessToken;
|
|
10788
|
+
/**
|
|
10789
|
+
* Obtain a fresh access token for a 401-retry, picking the right mechanism:
|
|
10790
|
+
* an OAuth refresh when a refresh credential exists, else a session re-mint
|
|
10791
|
+
* for a session-exchanged token. Returns true when a fresh token is in hand.
|
|
10792
|
+
*/
|
|
10793
|
+
private reauthenticateForRetry;
|
|
10757
10794
|
/**
|
|
10758
10795
|
* End the OAuth session.
|
|
10759
10796
|
*
|
package/dist/index.d.ts
CHANGED
|
@@ -10580,6 +10580,8 @@ declare class WitniumchainClient {
|
|
|
10580
10580
|
private readonly verifierStorage;
|
|
10581
10581
|
private discoveryCache;
|
|
10582
10582
|
private refreshInFlight;
|
|
10583
|
+
private accessTokenFromSession;
|
|
10584
|
+
private sessionExchangeInFlight;
|
|
10583
10585
|
/** Subscriptions / billing helpers. See {@link Subscriptions}. */
|
|
10584
10586
|
readonly subscriptions: Subscriptions;
|
|
10585
10587
|
/** Delegated-key namespace including the one-call {@link DelegatedKeys.provision} flow. */
|
|
@@ -10754,6 +10756,41 @@ declare class WitniumchainClient {
|
|
|
10754
10756
|
* instead of retrying with a now-invalid token.
|
|
10755
10757
|
*/
|
|
10756
10758
|
refreshAccessToken(): Promise<OAuthTokenSnapshot>;
|
|
10759
|
+
/**
|
|
10760
|
+
* Exchange the logged-in session (the `wac_session` cookie) for a short-lived
|
|
10761
|
+
* bearer access token and cache it as the client's `accessToken`.
|
|
10762
|
+
*
|
|
10763
|
+
* This is the SDK side of the session→token model (accounts'
|
|
10764
|
+
* `POST /v1/auth/access-token`): the dashboard authenticates with the
|
|
10765
|
+
* cookie, but the operational API (delegated keys, witness writes, chain
|
|
10766
|
+
* reads) is bearer-only — no endpoint accepts the ambient cookie, so there's
|
|
10767
|
+
* no CSRF surface even with `SameSite=None`. The minted token is
|
|
10768
|
+
* byte-compatible with an OAuth user token (same signing key, `iss`, `aud`,
|
|
10769
|
+
* scope + org/role/contracts claims), so every `BearerJWT` route accepts it.
|
|
10770
|
+
*
|
|
10771
|
+
* Callers rarely invoke this directly — {@link applyAuth} mints transparently
|
|
10772
|
+
* on the first `BearerJWT` call when no token is set and re-mints on a 401.
|
|
10773
|
+
* It's exposed so a dashboard can pre-warm the token right after login.
|
|
10774
|
+
*
|
|
10775
|
+
* Requires a session: either `sessionCookie` on the constructor (Node) or an
|
|
10776
|
+
* ambient `wac_session` cookie carried by the browser (`credentials:
|
|
10777
|
+
* 'include'`). Throws {@link WitniumchainApiError} (401) when there's no
|
|
10778
|
+
* valid session.
|
|
10779
|
+
*/
|
|
10780
|
+
exchangeSessionForToken(): Promise<OAuthTokenSnapshot>;
|
|
10781
|
+
/**
|
|
10782
|
+
* Best-effort transparent mint for the `BearerJWT` path: if no access token
|
|
10783
|
+
* is cached, try the session exchange (single-flight). Swallows failure so
|
|
10784
|
+
* the caller in {@link applyAuth} surfaces the actionable "no token / no
|
|
10785
|
+
* session" guidance rather than a raw 401 from the exchange endpoint.
|
|
10786
|
+
*/
|
|
10787
|
+
private ensureSessionAccessToken;
|
|
10788
|
+
/**
|
|
10789
|
+
* Obtain a fresh access token for a 401-retry, picking the right mechanism:
|
|
10790
|
+
* an OAuth refresh when a refresh credential exists, else a session re-mint
|
|
10791
|
+
* for a session-exchanged token. Returns true when a fresh token is in hand.
|
|
10792
|
+
*/
|
|
10793
|
+
private reauthenticateForRetry;
|
|
10757
10794
|
/**
|
|
10758
10795
|
* End the OAuth session.
|
|
10759
10796
|
*
|
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. */
|
|
@@ -787,6 +797,84 @@ var WitniumchainClient = class {
|
|
|
787
797
|
})();
|
|
788
798
|
return this.refreshInFlight;
|
|
789
799
|
}
|
|
800
|
+
/**
|
|
801
|
+
* Exchange the logged-in session (the `wac_session` cookie) for a short-lived
|
|
802
|
+
* bearer access token and cache it as the client's `accessToken`.
|
|
803
|
+
*
|
|
804
|
+
* This is the SDK side of the session→token model (accounts'
|
|
805
|
+
* `POST /v1/auth/access-token`): the dashboard authenticates with the
|
|
806
|
+
* cookie, but the operational API (delegated keys, witness writes, chain
|
|
807
|
+
* reads) is bearer-only — no endpoint accepts the ambient cookie, so there's
|
|
808
|
+
* no CSRF surface even with `SameSite=None`. The minted token is
|
|
809
|
+
* byte-compatible with an OAuth user token (same signing key, `iss`, `aud`,
|
|
810
|
+
* scope + org/role/contracts claims), so every `BearerJWT` route accepts it.
|
|
811
|
+
*
|
|
812
|
+
* Callers rarely invoke this directly — {@link applyAuth} mints transparently
|
|
813
|
+
* on the first `BearerJWT` call when no token is set and re-mints on a 401.
|
|
814
|
+
* It's exposed so a dashboard can pre-warm the token right after login.
|
|
815
|
+
*
|
|
816
|
+
* Requires a session: either `sessionCookie` on the constructor (Node) or an
|
|
817
|
+
* ambient `wac_session` cookie carried by the browser (`credentials:
|
|
818
|
+
* 'include'`). Throws {@link WitniumchainApiError} (401) when there's no
|
|
819
|
+
* valid session.
|
|
820
|
+
*/
|
|
821
|
+
async exchangeSessionForToken() {
|
|
822
|
+
if (this.sessionExchangeInFlight) return this.sessionExchangeInFlight;
|
|
823
|
+
this.sessionExchangeInFlight = (async () => {
|
|
824
|
+
try {
|
|
825
|
+
const res = await this.req("POST", "/v1/auth/access-token", { auth: "SessionCookie" });
|
|
826
|
+
this.accessToken = res.accessToken;
|
|
827
|
+
this.accessTokenFromSession = true;
|
|
828
|
+
this.refreshToken = void 0;
|
|
829
|
+
this.hasRefreshCookie = false;
|
|
830
|
+
return {
|
|
831
|
+
accessToken: res.accessToken,
|
|
832
|
+
expiresAt: Math.floor(new Date(res.expiresAt).getTime() / 1e3)
|
|
833
|
+
};
|
|
834
|
+
} finally {
|
|
835
|
+
this.sessionExchangeInFlight = void 0;
|
|
836
|
+
}
|
|
837
|
+
})();
|
|
838
|
+
return this.sessionExchangeInFlight;
|
|
839
|
+
}
|
|
840
|
+
/**
|
|
841
|
+
* Best-effort transparent mint for the `BearerJWT` path: if no access token
|
|
842
|
+
* is cached, try the session exchange (single-flight). Swallows failure so
|
|
843
|
+
* the caller in {@link applyAuth} surfaces the actionable "no token / no
|
|
844
|
+
* session" guidance rather than a raw 401 from the exchange endpoint.
|
|
845
|
+
*/
|
|
846
|
+
async ensureSessionAccessToken() {
|
|
847
|
+
if (this.accessToken) return;
|
|
848
|
+
try {
|
|
849
|
+
await this.exchangeSessionForToken();
|
|
850
|
+
} catch {
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
/**
|
|
854
|
+
* Obtain a fresh access token for a 401-retry, picking the right mechanism:
|
|
855
|
+
* an OAuth refresh when a refresh credential exists, else a session re-mint
|
|
856
|
+
* for a session-exchanged token. Returns true when a fresh token is in hand.
|
|
857
|
+
*/
|
|
858
|
+
async reauthenticateForRetry() {
|
|
859
|
+
if (this.refreshToken || this.hasRefreshCookie) {
|
|
860
|
+
try {
|
|
861
|
+
await this.refreshAccessToken();
|
|
862
|
+
return true;
|
|
863
|
+
} catch {
|
|
864
|
+
return false;
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
if (this.accessTokenFromSession) {
|
|
868
|
+
this.accessToken = void 0;
|
|
869
|
+
try {
|
|
870
|
+
await this.exchangeSessionForToken();
|
|
871
|
+
return true;
|
|
872
|
+
} catch {
|
|
873
|
+
return false;
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
return false;
|
|
877
|
+
}
|
|
790
878
|
/**
|
|
791
879
|
* End the OAuth session.
|
|
792
880
|
*
|
|
@@ -801,6 +889,7 @@ var WitniumchainClient = class {
|
|
|
801
889
|
*/
|
|
802
890
|
signOut() {
|
|
803
891
|
this.accessToken = void 0;
|
|
892
|
+
this.accessTokenFromSession = false;
|
|
804
893
|
this.refreshToken = void 0;
|
|
805
894
|
this.hasRefreshCookie = false;
|
|
806
895
|
this.pendingLogins.clear();
|
|
@@ -898,6 +987,7 @@ var WitniumchainClient = class {
|
|
|
898
987
|
});
|
|
899
988
|
}
|
|
900
989
|
this.accessToken = tokens.access_token;
|
|
990
|
+
this.accessTokenFromSession = false;
|
|
901
991
|
if (tokens.refresh_token) {
|
|
902
992
|
this.refreshToken = tokens.refresh_token;
|
|
903
993
|
} else {
|
|
@@ -1028,13 +1118,9 @@ var WitniumchainClient = class {
|
|
|
1028
1118
|
if (opts._isRetry) return false;
|
|
1029
1119
|
if (opts.auth !== "BearerJWT") return false;
|
|
1030
1120
|
if (res.status !== 401) return false;
|
|
1031
|
-
if (!this.refreshToken && !this.hasRefreshCookie
|
|
1032
|
-
try {
|
|
1033
|
-
await this.refreshAccessToken();
|
|
1034
|
-
return true;
|
|
1035
|
-
} catch {
|
|
1121
|
+
if (!this.refreshToken && !this.hasRefreshCookie && !this.accessTokenFromSession)
|
|
1036
1122
|
return false;
|
|
1037
|
-
|
|
1123
|
+
return this.reauthenticateForRetry();
|
|
1038
1124
|
}
|
|
1039
1125
|
/**
|
|
1040
1126
|
* Same decision as `shouldRefreshAndRetry` but for the JSON-parsed path,
|
|
@@ -1048,18 +1134,14 @@ var WitniumchainClient = class {
|
|
|
1048
1134
|
if (opts._isRetry) return false;
|
|
1049
1135
|
if (opts.auth !== "BearerJWT") return false;
|
|
1050
1136
|
if (status !== 401) return false;
|
|
1051
|
-
if (!this.refreshToken && !this.hasRefreshCookie
|
|
1137
|
+
if (!this.refreshToken && !this.hasRefreshCookie && !this.accessTokenFromSession)
|
|
1138
|
+
return false;
|
|
1052
1139
|
const body = parsed;
|
|
1053
1140
|
const label = typeof body?.error === "string" ? body.error : void 0;
|
|
1054
1141
|
if (label !== void 0 && label !== "token_expired" && label !== "invalid_token") {
|
|
1055
1142
|
return false;
|
|
1056
1143
|
}
|
|
1057
|
-
|
|
1058
|
-
await this.refreshAccessToken();
|
|
1059
|
-
return true;
|
|
1060
|
-
} catch {
|
|
1061
|
-
return false;
|
|
1062
|
-
}
|
|
1144
|
+
return this.reauthenticateForRetry();
|
|
1063
1145
|
}
|
|
1064
1146
|
/**
|
|
1065
1147
|
* Resolve which base URL to call. Default is accounts (`baseUrl`). When a
|
|
@@ -1099,9 +1181,12 @@ var WitniumchainClient = class {
|
|
|
1099
1181
|
return;
|
|
1100
1182
|
}
|
|
1101
1183
|
case "BearerJWT": {
|
|
1184
|
+
if (!this.accessToken) {
|
|
1185
|
+
await this.ensureSessionAccessToken();
|
|
1186
|
+
}
|
|
1102
1187
|
if (!this.accessToken) {
|
|
1103
1188
|
throw new Error(
|
|
1104
|
-
`WitniumchainClient: ${method} ${path} requires an OAuth access token. Pass \`accessToken\` to the constructor,
|
|
1189
|
+
`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
1190
|
);
|
|
1106
1191
|
}
|
|
1107
1192
|
headers["authorization"] = `Bearer ${this.accessToken}`;
|