@nxgt/janus 0.23.1 → 0.24.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/README.md +96 -12
- package/dist/auth/email-flows/prove-first.d.ts +23 -1
- package/dist/auth/email-flows/prove-first.d.ts.map +1 -1
- package/dist/auth/index.d.ts +1 -1
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/outage.d.ts +15 -4
- package/dist/auth/outage.d.ts.map +1 -1
- package/dist/auth/types/email-flows.d.ts +13 -2
- package/dist/auth/types/email-flows.d.ts.map +1 -1
- package/dist/auth/types/find-or-create.d.ts +59 -0
- package/dist/auth/types/find-or-create.d.ts.map +1 -0
- package/dist/auth/types/index.d.ts +1 -0
- package/dist/auth/types/index.d.ts.map +1 -1
- package/dist/auth/types/janus.d.ts +2 -1
- package/dist/auth/types/janus.d.ts.map +1 -1
- package/dist/auth/users/any-type-api.d.ts +2 -2
- package/dist/auth/users/any-type-api.d.ts.map +1 -1
- package/dist/auth/users/find-or-create.d.ts +12 -0
- package/dist/auth/users/find-or-create.d.ts.map +1 -0
- package/dist/auth/users/insert.d.ts +8 -3
- package/dist/auth/users/insert.d.ts.map +1 -1
- package/dist/auth/users/type-api.d.ts +1 -1
- package/dist/auth/users/type-api.d.ts.map +1 -1
- package/dist/index.js +149 -36
- package/dist/index.js.map +8 -7
- package/dist/permissions/engine.d.ts.map +1 -1
- package/dist/permissions/index.d.ts +1 -1
- package/dist/permissions/index.d.ts.map +1 -1
- package/dist/permissions/index.js +49 -16
- package/dist/permissions/index.js.map +6 -5
- package/dist/permissions/input.d.ts +13 -1
- package/dist/permissions/input.d.ts.map +1 -1
- package/dist/permissions/model/grant.d.ts +14 -1
- package/dist/permissions/model/grant.d.ts.map +1 -1
- package/dist/permissions/model/permissions.d.ts +13 -1
- package/dist/permissions/model/permissions.d.ts.map +1 -1
- package/dist/permissions/port/types.d.ts +8 -2
- package/dist/permissions/port/types.d.ts.map +1 -1
- package/dist/permissions/replace.d.ts +25 -0
- package/dist/permissions/replace.d.ts.map +1 -0
- package/docs/README.md +2 -2
- package/docs/guide/email-flows.md +33 -3
- package/docs/guide/errors.md +1 -1
- package/docs/guide/permissions.md +42 -1
- package/docs/guide/sessions.md +6 -0
- package/docs/guide/users.md +44 -0
- package/docs/guide/vocabulary.md +1 -1
- package/docs/roadmap.md +25 -30
- package/docs/troubleshooting.md +44 -15
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -550,6 +550,15 @@ async function unlessVersionConflict(write) {
|
|
|
550
550
|
throw error;
|
|
551
551
|
}
|
|
552
552
|
}
|
|
553
|
+
async function unlessLoginTaken(write) {
|
|
554
|
+
try {
|
|
555
|
+
return await write;
|
|
556
|
+
} catch (error) {
|
|
557
|
+
if (error instanceof StoreConflict2 && error.on === "login")
|
|
558
|
+
return error;
|
|
559
|
+
throw error;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
553
562
|
|
|
554
563
|
// src/auth/context/password.ts
|
|
555
564
|
function passwordRule(type, where) {
|
|
@@ -1648,19 +1657,69 @@ async function endWhatTheSquatterOpened(context, userId) {
|
|
|
1648
1657
|
function proveFirstFlow(context, type, at) {
|
|
1649
1658
|
return async (user) => {
|
|
1650
1659
|
const where = at("verifyEmail.proveFirst");
|
|
1651
|
-
const
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1660
|
+
const id = refId(user);
|
|
1661
|
+
const claimed = claimedEmail(type, user);
|
|
1662
|
+
const first = await readActive(context, type, id, where);
|
|
1663
|
+
assertClaimed(type, claimed, first, user, where);
|
|
1664
|
+
const answer = await unlessVersionConflict(proveOrAnswer(context, type, first, where));
|
|
1665
|
+
if (answer !== null)
|
|
1666
|
+
return answer;
|
|
1667
|
+
const again = await readActive(context, type, id, where);
|
|
1668
|
+
assertClaimed(type, claimed, again, user, where);
|
|
1669
|
+
if (!sameEmail(type, first, again)) {
|
|
1670
|
+
throw new StoreConflict2("version", `${where}: the e-mail changed since it was read`, {
|
|
1671
|
+
userId: first.id,
|
|
1672
|
+
expectedVersion: first.version,
|
|
1673
|
+
actualVersion: again.version,
|
|
1674
|
+
operation: where
|
|
1656
1675
|
});
|
|
1657
1676
|
}
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1677
|
+
return proveOrAnswer(context, type, again, where);
|
|
1678
|
+
};
|
|
1679
|
+
}
|
|
1680
|
+
async function readActive(context, type, id, where) {
|
|
1681
|
+
const record = await getRecord(context, id, type.name, where);
|
|
1682
|
+
if (!record.active) {
|
|
1683
|
+
throw new UserInactiveError2(`${where}: the user is inactive`, {
|
|
1684
|
+
userId: record.id,
|
|
1685
|
+
userType: type.name
|
|
1686
|
+
});
|
|
1687
|
+
}
|
|
1688
|
+
return record;
|
|
1689
|
+
}
|
|
1690
|
+
async function proveOrAnswer(context, type, record, where) {
|
|
1691
|
+
if (record.emailVerifiedAt !== null) {
|
|
1692
|
+
return { user: toUser(record), proved: false };
|
|
1693
|
+
}
|
|
1694
|
+
const proved = await proveFirstEmail(context, type, record, where);
|
|
1695
|
+
return { user: toUser(proved), proved: true };
|
|
1696
|
+
}
|
|
1697
|
+
function sameEmail(type, a, b) {
|
|
1698
|
+
const held = (record) => {
|
|
1699
|
+
const email = emailOf(type, record.fields);
|
|
1700
|
+
return email === null ? null : normalizeEmail(email);
|
|
1663
1701
|
};
|
|
1702
|
+
return held(a) === held(b);
|
|
1703
|
+
}
|
|
1704
|
+
function claimedEmail(type, user) {
|
|
1705
|
+
if (typeof user !== "object" || user === null)
|
|
1706
|
+
return null;
|
|
1707
|
+
const value = user[type.email];
|
|
1708
|
+
return typeof value === "string" ? normalizeEmail(value) : null;
|
|
1709
|
+
}
|
|
1710
|
+
function assertClaimed(type, claimed, record, user, where) {
|
|
1711
|
+
if (claimed === null)
|
|
1712
|
+
return;
|
|
1713
|
+
const email = emailOf(type, record.fields);
|
|
1714
|
+
if (email !== null && normalizeEmail(email) === claimed)
|
|
1715
|
+
return;
|
|
1716
|
+
const held = user.version;
|
|
1717
|
+
throw new StoreConflict2("version", `${where}: the e-mail changed since the user was read`, {
|
|
1718
|
+
userId: record.id,
|
|
1719
|
+
...typeof held === "number" ? { expectedVersion: held } : {},
|
|
1720
|
+
actualVersion: record.version,
|
|
1721
|
+
operation: where
|
|
1722
|
+
});
|
|
1664
1723
|
}
|
|
1665
1724
|
|
|
1666
1725
|
// src/auth/email-flows/verify-email.ts
|
|
@@ -2387,36 +2446,13 @@ function findByEmailFlows(context, type) {
|
|
|
2387
2446
|
// src/auth/users/by-login.ts
|
|
2388
2447
|
var byLogin = async (context, type, login) => isStorable(login) ? context.store.users.findUserByLogin(type.name, login) : null;
|
|
2389
2448
|
|
|
2390
|
-
// src/auth/users/change-password.ts
|
|
2391
|
-
async function changePassword(context, type, user, change, options, where) {
|
|
2392
|
-
passwordRule(type, where);
|
|
2393
|
-
checkPassword(type, change?.next, where);
|
|
2394
|
-
const hasher = requireHasher(context, where);
|
|
2395
|
-
const written = await writeUser(context, user, type, options, where, async (record, now) => {
|
|
2396
|
-
if (record.password === null || !await passwordMatches(context, record, change.current, where)) {
|
|
2397
|
-
throw new CredentialError2("CREDENTIALS_INVALID", `${where}: the current password does not match`, {
|
|
2398
|
-
reason: record.password === null ? "noPassword" : "wrongPassword",
|
|
2399
|
-
userId: record.id,
|
|
2400
|
-
userType: type.name
|
|
2401
|
-
});
|
|
2402
|
-
}
|
|
2403
|
-
return {
|
|
2404
|
-
password: {
|
|
2405
|
-
hash: await hasher.hash(change.next),
|
|
2406
|
-
updatedAt: now
|
|
2407
|
-
}
|
|
2408
|
-
};
|
|
2409
|
-
});
|
|
2410
|
-
await passwordChanged(context, written);
|
|
2411
|
-
return written;
|
|
2412
|
-
}
|
|
2413
|
-
|
|
2414
2449
|
// src/auth/users/insert.ts
|
|
2415
|
-
async function insert(context, type, input, where) {
|
|
2450
|
+
async function insert(context, type, input, where, accept) {
|
|
2416
2451
|
const { store, clock } = context;
|
|
2417
2452
|
const { password, active, ...rest } = input ?? {};
|
|
2418
2453
|
const now = clock.now();
|
|
2419
2454
|
const fields = await validateFields(type, rest, where);
|
|
2455
|
+
accept?.(fields);
|
|
2420
2456
|
let hash = null;
|
|
2421
2457
|
if (password !== undefined) {
|
|
2422
2458
|
checkPassword(type, password, where);
|
|
@@ -2443,6 +2479,82 @@ async function insert(context, type, input, where) {
|
|
|
2443
2479
|
return inserted;
|
|
2444
2480
|
}
|
|
2445
2481
|
|
|
2482
|
+
// src/auth/users/find-or-create.ts
|
|
2483
|
+
async function holder(context, type, key) {
|
|
2484
|
+
if (type.password !== null) {
|
|
2485
|
+
return byLogin(context, type, type.password.normalize(key));
|
|
2486
|
+
}
|
|
2487
|
+
return key.trim() === "" ? null : holderOfEmail(context, type, key);
|
|
2488
|
+
}
|
|
2489
|
+
function refusal2(type, where, field) {
|
|
2490
|
+
return new UserInvalidError2(`${where}: the fields do not match the ${type.name} schema (1 issue, at ${field})`, {
|
|
2491
|
+
issues: [
|
|
2492
|
+
{ path: [field], message: "is not the key findOrCreate was given" }
|
|
2493
|
+
],
|
|
2494
|
+
userType: type.name
|
|
2495
|
+
});
|
|
2496
|
+
}
|
|
2497
|
+
function mustNameKey(type, key, where) {
|
|
2498
|
+
return (fields) => {
|
|
2499
|
+
const field = type.password === null ? type.email : type.password.login;
|
|
2500
|
+
const value = fields[field];
|
|
2501
|
+
const named = typeof value !== "string" ? null : type.password === null ? normalizeEmail(value) : type.password.normalize(value);
|
|
2502
|
+
const wanted = type.password === null ? normalizeEmail(key) : type.password.normalize(key);
|
|
2503
|
+
if (named === null || named !== wanted) {
|
|
2504
|
+
throw refusal2(type, where, field);
|
|
2505
|
+
}
|
|
2506
|
+
};
|
|
2507
|
+
}
|
|
2508
|
+
function findOrCreateFlows(context, type, at) {
|
|
2509
|
+
return {
|
|
2510
|
+
async findOrCreate(key, input) {
|
|
2511
|
+
const where = at("findOrCreate");
|
|
2512
|
+
if (typeof key !== "string") {
|
|
2513
|
+
throw new UserInvalidError2(`${where}: the key must be a string`, {
|
|
2514
|
+
issues: [{ path: [], message: "is not a string" }],
|
|
2515
|
+
userType: type.name
|
|
2516
|
+
});
|
|
2517
|
+
}
|
|
2518
|
+
const found = await holder(context, type, key);
|
|
2519
|
+
if (found !== null)
|
|
2520
|
+
return { user: toUser(found), created: false };
|
|
2521
|
+
const record = await input();
|
|
2522
|
+
const written = await unlessLoginTaken(insert(context, type, record, where, mustNameKey(type, key, where)));
|
|
2523
|
+
if (!(written instanceof StoreConflict2)) {
|
|
2524
|
+
return { user: toUser(written), created: true };
|
|
2525
|
+
}
|
|
2526
|
+
const winner = await holder(context, type, key);
|
|
2527
|
+
if (winner === null)
|
|
2528
|
+
throw written;
|
|
2529
|
+
return { user: toUser(winner), created: false };
|
|
2530
|
+
}
|
|
2531
|
+
};
|
|
2532
|
+
}
|
|
2533
|
+
|
|
2534
|
+
// src/auth/users/change-password.ts
|
|
2535
|
+
async function changePassword(context, type, user, change, options, where) {
|
|
2536
|
+
passwordRule(type, where);
|
|
2537
|
+
checkPassword(type, change?.next, where);
|
|
2538
|
+
const hasher = requireHasher(context, where);
|
|
2539
|
+
const written = await writeUser(context, user, type, options, where, async (record, now) => {
|
|
2540
|
+
if (record.password === null || !await passwordMatches(context, record, change.current, where)) {
|
|
2541
|
+
throw new CredentialError2("CREDENTIALS_INVALID", `${where}: the current password does not match`, {
|
|
2542
|
+
reason: record.password === null ? "noPassword" : "wrongPassword",
|
|
2543
|
+
userId: record.id,
|
|
2544
|
+
userType: type.name
|
|
2545
|
+
});
|
|
2546
|
+
}
|
|
2547
|
+
return {
|
|
2548
|
+
password: {
|
|
2549
|
+
hash: await hasher.hash(change.next),
|
|
2550
|
+
updatedAt: now
|
|
2551
|
+
}
|
|
2552
|
+
};
|
|
2553
|
+
});
|
|
2554
|
+
await passwordChanged(context, written);
|
|
2555
|
+
return written;
|
|
2556
|
+
}
|
|
2557
|
+
|
|
2446
2558
|
// src/auth/users/sign-in.ts
|
|
2447
2559
|
async function signIn(context, type, finish, input, call) {
|
|
2448
2560
|
const { where, device } = call;
|
|
@@ -2663,6 +2775,7 @@ function typeApi(context, type) {
|
|
|
2663
2775
|
magicLink: magicLinkFlows(context, type, at, secondFactor.finish),
|
|
2664
2776
|
stepUp: stepUpFlows(context, type, at),
|
|
2665
2777
|
...findByEmailFlows(context, type),
|
|
2778
|
+
...findOrCreateFlows(context, type, at),
|
|
2666
2779
|
...emailFlows(context, type, at)
|
|
2667
2780
|
};
|
|
2668
2781
|
}
|
|
@@ -2751,5 +2864,5 @@ export {
|
|
|
2751
2864
|
systemClock
|
|
2752
2865
|
};
|
|
2753
2866
|
|
|
2754
|
-
//# debugId=
|
|
2867
|
+
//# debugId=DBC7C1267CB6B80364756E2164756E21
|
|
2755
2868
|
//# sourceMappingURL=index.js.map
|