@witnium-tech/witniumchain 0.11.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/FRONTEND-INTEGRATION.md +34 -8
- package/README.md +15 -5
- package/dist/index.d.mts +1789 -321
- package/dist/index.d.ts +1789 -321
- package/dist/index.js +117 -32
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +117 -32
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -121,6 +121,11 @@ var WitniumchainClient = class {
|
|
|
121
121
|
// first BearerJWT call (and any concurrent fan-out) awaits one in-flight
|
|
122
122
|
// mint rather than minting N tokens.
|
|
123
123
|
sessionExchangeInFlight;
|
|
124
|
+
// Bumped whenever the cached credentials stop being valid (sign-out, the
|
|
125
|
+
// TOTP-confirm epoch bump). A mint or refresh that started under an older
|
|
126
|
+
// generation must not store its result: it could be a previous user's
|
|
127
|
+
// token, or one the server has already ended.
|
|
128
|
+
credentialGeneration = 0;
|
|
124
129
|
/** Subscriptions / billing helpers. See {@link Subscriptions}. */
|
|
125
130
|
subscriptions;
|
|
126
131
|
/** Delegated-key namespace including the one-call {@link DelegatedKeys.provision} flow. */
|
|
@@ -177,9 +182,10 @@ var WitniumchainClient = class {
|
|
|
177
182
|
* the contract with the caller's client-generated `ownerPublicKey` +
|
|
178
183
|
* `initialSigningPublicKey`. Witnium never sees the private bytes.
|
|
179
184
|
*
|
|
180
|
-
* Returns `{ orgId, userId, contractAddress, contractVersion
|
|
181
|
-
*
|
|
182
|
-
*
|
|
185
|
+
* Returns `{ orgId, userId, contractAddress, contractVersion }`. The
|
|
186
|
+
* email-verify link is mailed to `adminEmail` and never returned, so there
|
|
187
|
+
* is nothing to render. If the mail cannot be sent, nothing is created and
|
|
188
|
+
* the call fails with a 502; retry it.
|
|
183
189
|
*/
|
|
184
190
|
createOrg(body) {
|
|
185
191
|
return this.req("POST", "/v1/orgs", { auth: "Public", body });
|
|
@@ -237,11 +243,36 @@ var WitniumchainClient = class {
|
|
|
237
243
|
query: { token }
|
|
238
244
|
});
|
|
239
245
|
}
|
|
246
|
+
/**
|
|
247
|
+
* Password login. Check `status`: `'authenticated'` means the session
|
|
248
|
+
* cookie is set. `'mfa_required'` means NO session exists yet: the server
|
|
249
|
+
* set a short-lived challenge cookie, and {@link loginMfa} must follow with
|
|
250
|
+
* the user's TOTP or recovery code.
|
|
251
|
+
*/
|
|
240
252
|
login(body) {
|
|
241
253
|
return this.req("POST", "/v1/auth/login", { auth: "Public", body });
|
|
242
254
|
}
|
|
243
|
-
|
|
244
|
-
|
|
255
|
+
/**
|
|
256
|
+
* Second step of an MFA login, after `login()` returned `'mfa_required'`.
|
|
257
|
+
* Relies on the challenge cookie from that call (`credentials: 'include'`).
|
|
258
|
+
* 401 `mfa_code_invalid`: wrong or reused code, the user may retry.
|
|
259
|
+
* 401 `mfa_challenge_invalid`: expired or used up, start again at `login()`.
|
|
260
|
+
* 429 `mfa_rate_limited`: the body carries `retryAfterSeconds`.
|
|
261
|
+
*/
|
|
262
|
+
loginMfa(body) {
|
|
263
|
+
return this.req("POST", "/v1/auth/login/mfa", { auth: "Public", body });
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* End the first-party session. The cached bearer token is dropped even when
|
|
267
|
+
* the request fails, so a logged-out tab never keeps the previous user's
|
|
268
|
+
* token.
|
|
269
|
+
*/
|
|
270
|
+
async logout() {
|
|
271
|
+
try {
|
|
272
|
+
return await this.req("POST", "/v1/auth/logout", { auth: "Public" });
|
|
273
|
+
} finally {
|
|
274
|
+
this.signOut();
|
|
275
|
+
}
|
|
245
276
|
}
|
|
246
277
|
forgotPassword(body) {
|
|
247
278
|
return this.req("POST", "/v1/auth/forgot-password", { auth: "Public", body });
|
|
@@ -474,20 +505,32 @@ var WitniumchainClient = class {
|
|
|
474
505
|
auth: "SessionCookie"
|
|
475
506
|
});
|
|
476
507
|
}
|
|
477
|
-
|
|
478
|
-
|
|
508
|
+
/**
|
|
509
|
+
* Confirming bumps the user's security epoch server-side, which ends every
|
|
510
|
+
* session-minted access token and OAuth artifact of the user. Cached and
|
|
511
|
+
* in-flight tokens are dropped so the next bearer call mints a fresh one
|
|
512
|
+
* from the (surviving) session.
|
|
513
|
+
*/
|
|
514
|
+
async confirmTotp(body) {
|
|
515
|
+
const res = await this.req("POST", "/v1/account/mfa/totp/confirm", {
|
|
479
516
|
auth: "SessionCookie",
|
|
480
517
|
body
|
|
481
518
|
});
|
|
519
|
+
this.invalidateCredentials();
|
|
520
|
+
return res;
|
|
482
521
|
}
|
|
483
|
-
|
|
522
|
+
/** Step-up: 403 `mfa_step_up_failed` on a wrong proof, 429 `mfa_rate_limited`. */
|
|
523
|
+
disableTotp(body) {
|
|
484
524
|
return this.req("DELETE", "/v1/account/mfa/totp", {
|
|
485
|
-
auth: "SessionCookie"
|
|
525
|
+
auth: "SessionCookie",
|
|
526
|
+
body
|
|
486
527
|
});
|
|
487
528
|
}
|
|
488
|
-
|
|
529
|
+
/** Step-up: 403 `mfa_step_up_failed` on a wrong proof, 429 `mfa_rate_limited`. */
|
|
530
|
+
regenerateRecoveryCodes(body) {
|
|
489
531
|
return this.req("POST", "/v1/account/mfa/recovery-codes/regenerate", {
|
|
490
|
-
auth: "SessionCookie"
|
|
532
|
+
auth: "SessionCookie",
|
|
533
|
+
body
|
|
491
534
|
});
|
|
492
535
|
}
|
|
493
536
|
// ────────────────────────────────────────────────────────────────────────
|
|
@@ -751,14 +794,16 @@ var WitniumchainClient = class {
|
|
|
751
794
|
*/
|
|
752
795
|
async refreshAccessToken() {
|
|
753
796
|
if (this.refreshInFlight) return this.refreshInFlight;
|
|
754
|
-
|
|
797
|
+
let flight = void 0;
|
|
798
|
+
flight = (async () => {
|
|
755
799
|
try {
|
|
756
800
|
return await this.refreshAccessTokenInternal();
|
|
757
801
|
} finally {
|
|
758
|
-
this.refreshInFlight = void 0;
|
|
802
|
+
if (this.refreshInFlight === flight) this.refreshInFlight = void 0;
|
|
759
803
|
}
|
|
760
804
|
})();
|
|
761
|
-
|
|
805
|
+
this.refreshInFlight = flight;
|
|
806
|
+
return flight;
|
|
762
807
|
}
|
|
763
808
|
/**
|
|
764
809
|
* Exchange the logged-in session (the `wac_session` cookie) for a short-lived
|
|
@@ -783,9 +828,12 @@ var WitniumchainClient = class {
|
|
|
783
828
|
*/
|
|
784
829
|
async exchangeSessionForToken() {
|
|
785
830
|
if (this.sessionExchangeInFlight) return this.sessionExchangeInFlight;
|
|
786
|
-
|
|
831
|
+
const generation = this.credentialGeneration;
|
|
832
|
+
let flight = void 0;
|
|
833
|
+
flight = (async () => {
|
|
787
834
|
try {
|
|
788
835
|
const res = await this.req("POST", "/v1/auth/access-token", { auth: "SessionCookie" });
|
|
836
|
+
this.assertCredentialGeneration(generation);
|
|
789
837
|
this.accessToken = res.accessToken;
|
|
790
838
|
this.accessTokenFromSession = true;
|
|
791
839
|
this.refreshToken = void 0;
|
|
@@ -795,10 +843,32 @@ var WitniumchainClient = class {
|
|
|
795
843
|
expiresAt: Math.floor(new Date(res.expiresAt).getTime() / 1e3)
|
|
796
844
|
};
|
|
797
845
|
} finally {
|
|
798
|
-
this.sessionExchangeInFlight = void 0;
|
|
846
|
+
if (this.sessionExchangeInFlight === flight) this.sessionExchangeInFlight = void 0;
|
|
799
847
|
}
|
|
800
848
|
})();
|
|
801
|
-
|
|
849
|
+
this.sessionExchangeInFlight = flight;
|
|
850
|
+
return flight;
|
|
851
|
+
}
|
|
852
|
+
assertCredentialGeneration(generation) {
|
|
853
|
+
if (generation !== this.credentialGeneration) {
|
|
854
|
+
throw new WitniumchainApiError({
|
|
855
|
+
status: 0,
|
|
856
|
+
message: "WitniumchainClient: the session changed (sign-out or MFA confirmation) while a token request was in flight; its token was discarded."
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
/**
|
|
861
|
+
* Forget every cached credential and orphan in-flight mints/refreshes, so
|
|
862
|
+
* none of them can store a token afterwards.
|
|
863
|
+
*/
|
|
864
|
+
invalidateCredentials() {
|
|
865
|
+
this.credentialGeneration++;
|
|
866
|
+
this.accessToken = void 0;
|
|
867
|
+
this.accessTokenFromSession = false;
|
|
868
|
+
this.refreshToken = void 0;
|
|
869
|
+
this.hasRefreshCookie = false;
|
|
870
|
+
this.sessionExchangeInFlight = void 0;
|
|
871
|
+
this.refreshInFlight = void 0;
|
|
802
872
|
}
|
|
803
873
|
/**
|
|
804
874
|
* Best-effort transparent mint for the `BearerJWT` path: if no access token
|
|
@@ -810,7 +880,8 @@ var WitniumchainClient = class {
|
|
|
810
880
|
if (this.accessToken) return;
|
|
811
881
|
try {
|
|
812
882
|
await this.exchangeSessionForToken();
|
|
813
|
-
} catch {
|
|
883
|
+
} catch (err) {
|
|
884
|
+
if (!(err instanceof WitniumchainApiError && err.status === 401)) throw err;
|
|
814
885
|
}
|
|
815
886
|
}
|
|
816
887
|
/**
|
|
@@ -851,10 +922,7 @@ var WitniumchainClient = class {
|
|
|
851
922
|
* dropped — which it is, here.
|
|
852
923
|
*/
|
|
853
924
|
signOut() {
|
|
854
|
-
this.
|
|
855
|
-
this.accessTokenFromSession = false;
|
|
856
|
-
this.refreshToken = void 0;
|
|
857
|
-
this.hasRefreshCookie = false;
|
|
925
|
+
this.invalidateCredentials();
|
|
858
926
|
this.pendingLogins.clear();
|
|
859
927
|
}
|
|
860
928
|
// ────────────────────────────────────────────────────────────────────────
|
|
@@ -974,6 +1042,7 @@ var WitniumchainClient = class {
|
|
|
974
1042
|
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)."
|
|
975
1043
|
});
|
|
976
1044
|
}
|
|
1045
|
+
const generation = this.credentialGeneration;
|
|
977
1046
|
const discovery = await this.fetchDiscovery();
|
|
978
1047
|
const form = new URLSearchParams();
|
|
979
1048
|
form.set("grant_type", "refresh_token");
|
|
@@ -981,15 +1050,18 @@ var WitniumchainClient = class {
|
|
|
981
1050
|
if (this.refreshToken) {
|
|
982
1051
|
form.set("refresh_token", this.refreshToken);
|
|
983
1052
|
}
|
|
1053
|
+
let tokens;
|
|
984
1054
|
try {
|
|
985
|
-
|
|
986
|
-
return this.persistTokenResponse(tokens);
|
|
1055
|
+
tokens = await this.postTokenEndpoint(discovery.token_endpoint, form);
|
|
987
1056
|
} catch (err) {
|
|
1057
|
+
this.assertCredentialGeneration(generation);
|
|
988
1058
|
this.accessToken = void 0;
|
|
989
1059
|
this.refreshToken = void 0;
|
|
990
1060
|
this.hasRefreshCookie = false;
|
|
991
1061
|
throw err;
|
|
992
1062
|
}
|
|
1063
|
+
this.assertCredentialGeneration(generation);
|
|
1064
|
+
return this.persistTokenResponse(tokens);
|
|
993
1065
|
}
|
|
994
1066
|
// ────────────────────────────────────────────────────────────────────────
|
|
995
1067
|
// Internal: fetch wrapper that maps non-2xx → WitniumchainApiError
|
|
@@ -1352,7 +1424,12 @@ var OrgMembers = class {
|
|
|
1352
1424
|
list() {
|
|
1353
1425
|
return this.client.listMembers();
|
|
1354
1426
|
}
|
|
1355
|
-
/**
|
|
1427
|
+
/**
|
|
1428
|
+
* Invite a member by email (default role `member`). `inviteEmailSent` says
|
|
1429
|
+
* whether the invitation (with the verify link) was mailed; the token is
|
|
1430
|
+
* never returned. A 502 means the membership was recorded but the mail
|
|
1431
|
+
* failed; retry.
|
|
1432
|
+
*/
|
|
1356
1433
|
invite(body) {
|
|
1357
1434
|
return this.client.inviteMember(body);
|
|
1358
1435
|
}
|
|
@@ -1372,7 +1449,10 @@ var OrgMembers = class {
|
|
|
1372
1449
|
remove(userId) {
|
|
1373
1450
|
return this.client.removeMember(userId);
|
|
1374
1451
|
}
|
|
1375
|
-
/**
|
|
1452
|
+
/**
|
|
1453
|
+
* Re-send the invitation email to a pending member. Returns `{ email }`;
|
|
1454
|
+
* the fresh verify token is mailed, never returned.
|
|
1455
|
+
*/
|
|
1376
1456
|
resendInvite(userId) {
|
|
1377
1457
|
return this.client.resendMemberInvite(userId);
|
|
1378
1458
|
}
|
|
@@ -1396,14 +1476,18 @@ var MfaTotp = class {
|
|
|
1396
1476
|
* Confirm enrolment with the first user-supplied code. Returns the 10
|
|
1397
1477
|
* single-use recovery codes — show them to the user ONCE; the server never
|
|
1398
1478
|
* returns them again. Throws `WitniumchainApiError` with status 400 when
|
|
1399
|
-
* the code is invalid or the enrolment is already confirmed.
|
|
1479
|
+
* the code is invalid or the enrolment is already confirmed. Signs the
|
|
1480
|
+
* user out of every other device and connected app.
|
|
1400
1481
|
*/
|
|
1401
1482
|
confirm(code) {
|
|
1402
1483
|
return this.client.confirmTotp({ code });
|
|
1403
1484
|
}
|
|
1404
|
-
/**
|
|
1405
|
-
|
|
1406
|
-
|
|
1485
|
+
/**
|
|
1486
|
+
* Disable TOTP, wiping both the secret and all recovery codes. Requires the
|
|
1487
|
+
* user's current code or a recovery code.
|
|
1488
|
+
*/
|
|
1489
|
+
disable(proof) {
|
|
1490
|
+
return this.client.disableTotp(proof);
|
|
1407
1491
|
}
|
|
1408
1492
|
};
|
|
1409
1493
|
var MfaRecoveryCodes = class {
|
|
@@ -1414,9 +1498,10 @@ var MfaRecoveryCodes = class {
|
|
|
1414
1498
|
/**
|
|
1415
1499
|
* Issue a fresh set of 10 recovery codes, invalidating the prior ones.
|
|
1416
1500
|
* Same as `confirm` — codes are returned ONCE and never readable again.
|
|
1501
|
+
* Requires the user's current code or a recovery code.
|
|
1417
1502
|
*/
|
|
1418
|
-
regenerate() {
|
|
1419
|
-
return this.client.regenerateRecoveryCodes();
|
|
1503
|
+
regenerate(proof) {
|
|
1504
|
+
return this.client.regenerateRecoveryCodes(proof);
|
|
1420
1505
|
}
|
|
1421
1506
|
};
|
|
1422
1507
|
function sleep(ms) {
|