@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.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. */
|
|
@@ -175,9 +180,10 @@ var WitniumchainClient = class {
|
|
|
175
180
|
* the contract with the caller's client-generated `ownerPublicKey` +
|
|
176
181
|
* `initialSigningPublicKey`. Witnium never sees the private bytes.
|
|
177
182
|
*
|
|
178
|
-
* Returns `{ orgId, userId, contractAddress, contractVersion
|
|
179
|
-
*
|
|
180
|
-
*
|
|
183
|
+
* Returns `{ orgId, userId, contractAddress, contractVersion }`. The
|
|
184
|
+
* email-verify link is mailed to `adminEmail` and never returned, so there
|
|
185
|
+
* is nothing to render. If the mail cannot be sent, nothing is created and
|
|
186
|
+
* the call fails with a 502; retry it.
|
|
181
187
|
*/
|
|
182
188
|
createOrg(body) {
|
|
183
189
|
return this.req("POST", "/v1/orgs", { auth: "Public", body });
|
|
@@ -235,11 +241,36 @@ var WitniumchainClient = class {
|
|
|
235
241
|
query: { token }
|
|
236
242
|
});
|
|
237
243
|
}
|
|
244
|
+
/**
|
|
245
|
+
* Password login. Check `status`: `'authenticated'` means the session
|
|
246
|
+
* cookie is set. `'mfa_required'` means NO session exists yet: the server
|
|
247
|
+
* set a short-lived challenge cookie, and {@link loginMfa} must follow with
|
|
248
|
+
* the user's TOTP or recovery code.
|
|
249
|
+
*/
|
|
238
250
|
login(body) {
|
|
239
251
|
return this.req("POST", "/v1/auth/login", { auth: "Public", body });
|
|
240
252
|
}
|
|
241
|
-
|
|
242
|
-
|
|
253
|
+
/**
|
|
254
|
+
* Second step of an MFA login, after `login()` returned `'mfa_required'`.
|
|
255
|
+
* Relies on the challenge cookie from that call (`credentials: 'include'`).
|
|
256
|
+
* 401 `mfa_code_invalid`: wrong or reused code, the user may retry.
|
|
257
|
+
* 401 `mfa_challenge_invalid`: expired or used up, start again at `login()`.
|
|
258
|
+
* 429 `mfa_rate_limited`: the body carries `retryAfterSeconds`.
|
|
259
|
+
*/
|
|
260
|
+
loginMfa(body) {
|
|
261
|
+
return this.req("POST", "/v1/auth/login/mfa", { auth: "Public", body });
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* End the first-party session. The cached bearer token is dropped even when
|
|
265
|
+
* the request fails, so a logged-out tab never keeps the previous user's
|
|
266
|
+
* token.
|
|
267
|
+
*/
|
|
268
|
+
async logout() {
|
|
269
|
+
try {
|
|
270
|
+
return await this.req("POST", "/v1/auth/logout", { auth: "Public" });
|
|
271
|
+
} finally {
|
|
272
|
+
this.signOut();
|
|
273
|
+
}
|
|
243
274
|
}
|
|
244
275
|
forgotPassword(body) {
|
|
245
276
|
return this.req("POST", "/v1/auth/forgot-password", { auth: "Public", body });
|
|
@@ -472,20 +503,32 @@ var WitniumchainClient = class {
|
|
|
472
503
|
auth: "SessionCookie"
|
|
473
504
|
});
|
|
474
505
|
}
|
|
475
|
-
|
|
476
|
-
|
|
506
|
+
/**
|
|
507
|
+
* Confirming bumps the user's security epoch server-side, which ends every
|
|
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.
|
|
511
|
+
*/
|
|
512
|
+
async confirmTotp(body) {
|
|
513
|
+
const res = await this.req("POST", "/v1/account/mfa/totp/confirm", {
|
|
477
514
|
auth: "SessionCookie",
|
|
478
515
|
body
|
|
479
516
|
});
|
|
517
|
+
this.invalidateCredentials();
|
|
518
|
+
return res;
|
|
480
519
|
}
|
|
481
|
-
|
|
520
|
+
/** Step-up: 403 `mfa_step_up_failed` on a wrong proof, 429 `mfa_rate_limited`. */
|
|
521
|
+
disableTotp(body) {
|
|
482
522
|
return this.req("DELETE", "/v1/account/mfa/totp", {
|
|
483
|
-
auth: "SessionCookie"
|
|
523
|
+
auth: "SessionCookie",
|
|
524
|
+
body
|
|
484
525
|
});
|
|
485
526
|
}
|
|
486
|
-
|
|
527
|
+
/** Step-up: 403 `mfa_step_up_failed` on a wrong proof, 429 `mfa_rate_limited`. */
|
|
528
|
+
regenerateRecoveryCodes(body) {
|
|
487
529
|
return this.req("POST", "/v1/account/mfa/recovery-codes/regenerate", {
|
|
488
|
-
auth: "SessionCookie"
|
|
530
|
+
auth: "SessionCookie",
|
|
531
|
+
body
|
|
489
532
|
});
|
|
490
533
|
}
|
|
491
534
|
// ────────────────────────────────────────────────────────────────────────
|
|
@@ -749,14 +792,16 @@ var WitniumchainClient = class {
|
|
|
749
792
|
*/
|
|
750
793
|
async refreshAccessToken() {
|
|
751
794
|
if (this.refreshInFlight) return this.refreshInFlight;
|
|
752
|
-
|
|
795
|
+
let flight = void 0;
|
|
796
|
+
flight = (async () => {
|
|
753
797
|
try {
|
|
754
798
|
return await this.refreshAccessTokenInternal();
|
|
755
799
|
} finally {
|
|
756
|
-
this.refreshInFlight = void 0;
|
|
800
|
+
if (this.refreshInFlight === flight) this.refreshInFlight = void 0;
|
|
757
801
|
}
|
|
758
802
|
})();
|
|
759
|
-
|
|
803
|
+
this.refreshInFlight = flight;
|
|
804
|
+
return flight;
|
|
760
805
|
}
|
|
761
806
|
/**
|
|
762
807
|
* Exchange the logged-in session (the `wac_session` cookie) for a short-lived
|
|
@@ -781,9 +826,12 @@ var WitniumchainClient = class {
|
|
|
781
826
|
*/
|
|
782
827
|
async exchangeSessionForToken() {
|
|
783
828
|
if (this.sessionExchangeInFlight) return this.sessionExchangeInFlight;
|
|
784
|
-
|
|
829
|
+
const generation = this.credentialGeneration;
|
|
830
|
+
let flight = void 0;
|
|
831
|
+
flight = (async () => {
|
|
785
832
|
try {
|
|
786
833
|
const res = await this.req("POST", "/v1/auth/access-token", { auth: "SessionCookie" });
|
|
834
|
+
this.assertCredentialGeneration(generation);
|
|
787
835
|
this.accessToken = res.accessToken;
|
|
788
836
|
this.accessTokenFromSession = true;
|
|
789
837
|
this.refreshToken = void 0;
|
|
@@ -793,10 +841,32 @@ var WitniumchainClient = class {
|
|
|
793
841
|
expiresAt: Math.floor(new Date(res.expiresAt).getTime() / 1e3)
|
|
794
842
|
};
|
|
795
843
|
} finally {
|
|
796
|
-
this.sessionExchangeInFlight = void 0;
|
|
844
|
+
if (this.sessionExchangeInFlight === flight) this.sessionExchangeInFlight = void 0;
|
|
797
845
|
}
|
|
798
846
|
})();
|
|
799
|
-
|
|
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;
|
|
800
870
|
}
|
|
801
871
|
/**
|
|
802
872
|
* Best-effort transparent mint for the `BearerJWT` path: if no access token
|
|
@@ -808,7 +878,8 @@ var WitniumchainClient = class {
|
|
|
808
878
|
if (this.accessToken) return;
|
|
809
879
|
try {
|
|
810
880
|
await this.exchangeSessionForToken();
|
|
811
|
-
} catch {
|
|
881
|
+
} catch (err) {
|
|
882
|
+
if (!(err instanceof WitniumchainApiError && err.status === 401)) throw err;
|
|
812
883
|
}
|
|
813
884
|
}
|
|
814
885
|
/**
|
|
@@ -849,10 +920,7 @@ var WitniumchainClient = class {
|
|
|
849
920
|
* dropped — which it is, here.
|
|
850
921
|
*/
|
|
851
922
|
signOut() {
|
|
852
|
-
this.
|
|
853
|
-
this.accessTokenFromSession = false;
|
|
854
|
-
this.refreshToken = void 0;
|
|
855
|
-
this.hasRefreshCookie = false;
|
|
923
|
+
this.invalidateCredentials();
|
|
856
924
|
this.pendingLogins.clear();
|
|
857
925
|
}
|
|
858
926
|
// ────────────────────────────────────────────────────────────────────────
|
|
@@ -972,6 +1040,7 @@ var WitniumchainClient = class {
|
|
|
972
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)."
|
|
973
1041
|
});
|
|
974
1042
|
}
|
|
1043
|
+
const generation = this.credentialGeneration;
|
|
975
1044
|
const discovery = await this.fetchDiscovery();
|
|
976
1045
|
const form = new URLSearchParams();
|
|
977
1046
|
form.set("grant_type", "refresh_token");
|
|
@@ -979,15 +1048,18 @@ var WitniumchainClient = class {
|
|
|
979
1048
|
if (this.refreshToken) {
|
|
980
1049
|
form.set("refresh_token", this.refreshToken);
|
|
981
1050
|
}
|
|
1051
|
+
let tokens;
|
|
982
1052
|
try {
|
|
983
|
-
|
|
984
|
-
return this.persistTokenResponse(tokens);
|
|
1053
|
+
tokens = await this.postTokenEndpoint(discovery.token_endpoint, form);
|
|
985
1054
|
} catch (err) {
|
|
1055
|
+
this.assertCredentialGeneration(generation);
|
|
986
1056
|
this.accessToken = void 0;
|
|
987
1057
|
this.refreshToken = void 0;
|
|
988
1058
|
this.hasRefreshCookie = false;
|
|
989
1059
|
throw err;
|
|
990
1060
|
}
|
|
1061
|
+
this.assertCredentialGeneration(generation);
|
|
1062
|
+
return this.persistTokenResponse(tokens);
|
|
991
1063
|
}
|
|
992
1064
|
// ────────────────────────────────────────────────────────────────────────
|
|
993
1065
|
// Internal: fetch wrapper that maps non-2xx → WitniumchainApiError
|
|
@@ -1350,7 +1422,12 @@ var OrgMembers = class {
|
|
|
1350
1422
|
list() {
|
|
1351
1423
|
return this.client.listMembers();
|
|
1352
1424
|
}
|
|
1353
|
-
/**
|
|
1425
|
+
/**
|
|
1426
|
+
* Invite a member by email (default role `member`). `inviteEmailSent` says
|
|
1427
|
+
* whether the invitation (with the verify link) was mailed; the token is
|
|
1428
|
+
* never returned. A 502 means the membership was recorded but the mail
|
|
1429
|
+
* failed; retry.
|
|
1430
|
+
*/
|
|
1354
1431
|
invite(body) {
|
|
1355
1432
|
return this.client.inviteMember(body);
|
|
1356
1433
|
}
|
|
@@ -1370,7 +1447,10 @@ var OrgMembers = class {
|
|
|
1370
1447
|
remove(userId) {
|
|
1371
1448
|
return this.client.removeMember(userId);
|
|
1372
1449
|
}
|
|
1373
|
-
/**
|
|
1450
|
+
/**
|
|
1451
|
+
* Re-send the invitation email to a pending member. Returns `{ email }`;
|
|
1452
|
+
* the fresh verify token is mailed, never returned.
|
|
1453
|
+
*/
|
|
1374
1454
|
resendInvite(userId) {
|
|
1375
1455
|
return this.client.resendMemberInvite(userId);
|
|
1376
1456
|
}
|
|
@@ -1394,14 +1474,18 @@ var MfaTotp = class {
|
|
|
1394
1474
|
* Confirm enrolment with the first user-supplied code. Returns the 10
|
|
1395
1475
|
* single-use recovery codes — show them to the user ONCE; the server never
|
|
1396
1476
|
* returns them again. Throws `WitniumchainApiError` with status 400 when
|
|
1397
|
-
* the code is invalid or the enrolment is already confirmed.
|
|
1477
|
+
* the code is invalid or the enrolment is already confirmed. Signs the
|
|
1478
|
+
* user out of every other device and connected app.
|
|
1398
1479
|
*/
|
|
1399
1480
|
confirm(code) {
|
|
1400
1481
|
return this.client.confirmTotp({ code });
|
|
1401
1482
|
}
|
|
1402
|
-
/**
|
|
1403
|
-
|
|
1404
|
-
|
|
1483
|
+
/**
|
|
1484
|
+
* Disable TOTP, wiping both the secret and all recovery codes. Requires the
|
|
1485
|
+
* user's current code or a recovery code.
|
|
1486
|
+
*/
|
|
1487
|
+
disable(proof) {
|
|
1488
|
+
return this.client.disableTotp(proof);
|
|
1405
1489
|
}
|
|
1406
1490
|
};
|
|
1407
1491
|
var MfaRecoveryCodes = class {
|
|
@@ -1412,9 +1496,10 @@ var MfaRecoveryCodes = class {
|
|
|
1412
1496
|
/**
|
|
1413
1497
|
* Issue a fresh set of 10 recovery codes, invalidating the prior ones.
|
|
1414
1498
|
* Same as `confirm` — codes are returned ONCE and never readable again.
|
|
1499
|
+
* Requires the user's current code or a recovery code.
|
|
1415
1500
|
*/
|
|
1416
|
-
regenerate() {
|
|
1417
|
-
return this.client.regenerateRecoveryCodes();
|
|
1501
|
+
regenerate(proof) {
|
|
1502
|
+
return this.client.regenerateRecoveryCodes(proof);
|
|
1418
1503
|
}
|
|
1419
1504
|
};
|
|
1420
1505
|
function sleep(ms) {
|