@nymphjs/tilmeld 1.0.0-beta.138 → 1.0.0-beta.139
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/CHANGELOG.md +17 -0
- package/dist/Group.js +4 -4
- package/dist/Group.js.map +1 -1
- package/dist/Tilmeld.d.ts +16 -2
- package/dist/Tilmeld.js +22 -9
- package/dist/Tilmeld.js.map +1 -1
- package/dist/User.d.ts +2 -2
- package/dist/User.js +106 -31
- package/dist/User.js.map +1 -1
- package/dist/User.testArtifacts.js +9 -9
- package/dist/User.testArtifacts.js.map +1 -1
- package/dist/conf/defaults.js +11 -3
- package/dist/conf/defaults.js.map +1 -1
- package/dist/conf/index.d.ts +14 -6
- package/dist/conf/index.js.map +1 -1
- package/dist/testArtifacts.d.ts +1 -0
- package/dist/testArtifacts.js +32 -3
- package/dist/testArtifacts.js.map +1 -1
- package/package.json +5 -5
- package/src/Group.ts +4 -4
- package/src/Tilmeld.ts +41 -11
- package/src/User.testArtifacts.ts +9 -9
- package/src/User.ts +137 -39
- package/src/conf/defaults.ts +21 -3
- package/src/conf/index.ts +24 -7
- package/src/testArtifacts.ts +51 -3
package/src/User.ts
CHANGED
|
@@ -518,7 +518,7 @@ export class User extends AbleObject<UserData> {
|
|
|
518
518
|
`User account recovery for "${user.username}".`,
|
|
519
519
|
);
|
|
520
520
|
|
|
521
|
-
user.$password(data.password);
|
|
521
|
+
await user.$password(data.password);
|
|
522
522
|
delete user.recoverSecret;
|
|
523
523
|
delete user.recoverSecretDate;
|
|
524
524
|
try {
|
|
@@ -637,6 +637,10 @@ export class User extends AbleObject<UserData> {
|
|
|
637
637
|
async $getUniques(): Promise<string[]> {
|
|
638
638
|
const tilmeld = enforceTilmeld(this);
|
|
639
639
|
const uniques = [`u:${this.username}`];
|
|
640
|
+
if (this.abilities?.includes('system/autogranted')) {
|
|
641
|
+
// Prevent multiple users from ever being granted admin automatically.
|
|
642
|
+
uniques.push('a:autogranted');
|
|
643
|
+
}
|
|
640
644
|
if (
|
|
641
645
|
!tilmeld.config.emailUsernames &&
|
|
642
646
|
tilmeld.config.userFields.includes('email')
|
|
@@ -661,7 +665,7 @@ export class User extends AbleObject<UserData> {
|
|
|
661
665
|
await tilmeld.config.failedLoginAttempt(null, data);
|
|
662
666
|
return { result: false, message: 'Incorrect login/password.' };
|
|
663
667
|
}
|
|
664
|
-
if (!this.$checkPassword(data.password)) {
|
|
668
|
+
if (!(await this.$checkPassword(data.password))) {
|
|
665
669
|
await tilmeld.config.failedLoginAttempt(this, data);
|
|
666
670
|
return { result: false, message: 'Incorrect login/password.' };
|
|
667
671
|
}
|
|
@@ -719,6 +723,17 @@ export class User extends AbleObject<UserData> {
|
|
|
719
723
|
}
|
|
720
724
|
}
|
|
721
725
|
|
|
726
|
+
// Check if the password uses the old hash method.
|
|
727
|
+
if (
|
|
728
|
+
tilmeld.config.pwMethod === 'salt' &&
|
|
729
|
+
!this.password?.startsWith('scrypt$')
|
|
730
|
+
) {
|
|
731
|
+
// Update the password hash with one using the new method.
|
|
732
|
+
await this.$refresh();
|
|
733
|
+
await this.$password(data.password);
|
|
734
|
+
await this.$save();
|
|
735
|
+
}
|
|
736
|
+
|
|
722
737
|
// Login was successful.
|
|
723
738
|
return { result: true, message: 'You are logged in.' };
|
|
724
739
|
}
|
|
@@ -784,7 +799,7 @@ export class User extends AbleObject<UserData> {
|
|
|
784
799
|
);
|
|
785
800
|
|
|
786
801
|
// Authentication was successful, attempt to login.
|
|
787
|
-
if (!(await tilmeld.loginSwitch(this, true))) {
|
|
802
|
+
if (!(await tilmeld.loginSwitch(user, this, true))) {
|
|
788
803
|
return { result: false, message: 'Incorrect login/password.' };
|
|
789
804
|
}
|
|
790
805
|
|
|
@@ -894,6 +909,12 @@ export class User extends AbleObject<UserData> {
|
|
|
894
909
|
|
|
895
910
|
if ('abilities' in input.data) {
|
|
896
911
|
this.$checkIncomingAbilities(input.data.abilities);
|
|
912
|
+
} else if (
|
|
913
|
+
(this.$allowlistData == null ||
|
|
914
|
+
this.$allowlistData.includes('abilities')) &&
|
|
915
|
+
(this.$privateData == null || !this.$privateData.includes('abilities'))
|
|
916
|
+
) {
|
|
917
|
+
this.$checkIncomingAbilities(undefined);
|
|
897
918
|
}
|
|
898
919
|
|
|
899
920
|
// If the user is a new domain user, the data protection needs to be updated
|
|
@@ -939,6 +960,10 @@ export class User extends AbleObject<UserData> {
|
|
|
939
960
|
this.$checkIncomingAbilities(patch.set.abilities);
|
|
940
961
|
}
|
|
941
962
|
|
|
963
|
+
if (patch.unset.includes('abilities')) {
|
|
964
|
+
this.$checkIncomingAbilities(undefined);
|
|
965
|
+
}
|
|
966
|
+
|
|
942
967
|
super.$jsonAcceptPatch(patch, allowConflict);
|
|
943
968
|
}
|
|
944
969
|
|
|
@@ -963,6 +988,20 @@ export class User extends AbleObject<UserData> {
|
|
|
963
988
|
);
|
|
964
989
|
}
|
|
965
990
|
|
|
991
|
+
if (
|
|
992
|
+
xor(
|
|
993
|
+
incoming.filter((ability) => ability.startsWith('system/')),
|
|
994
|
+
this.abilities?.filter((ability) => ability.startsWith('system/')) ??
|
|
995
|
+
[],
|
|
996
|
+
).length &&
|
|
997
|
+
(!tilmeld.currentUser ||
|
|
998
|
+
!tilmeld.currentUser.abilities?.includes('system/admin'))
|
|
999
|
+
) {
|
|
1000
|
+
throw new BadDataError(
|
|
1001
|
+
"You don't have the authority to grant or revoke system abilities.",
|
|
1002
|
+
);
|
|
1003
|
+
}
|
|
1004
|
+
|
|
966
1005
|
if (
|
|
967
1006
|
incoming.includes('tilmeld/admin') !==
|
|
968
1007
|
this.abilities?.includes('tilmeld/admin') &&
|
|
@@ -987,10 +1026,9 @@ export class User extends AbleObject<UserData> {
|
|
|
987
1026
|
|
|
988
1027
|
if (
|
|
989
1028
|
xor(
|
|
990
|
-
incoming.filter((ability
|
|
991
|
-
this.abilities?.filter((ability
|
|
992
|
-
|
|
993
|
-
) ?? [],
|
|
1029
|
+
incoming.filter((ability) => ability.startsWith('tilmeld/')),
|
|
1030
|
+
this.abilities?.filter((ability) => ability.startsWith('tilmeld/')) ??
|
|
1031
|
+
[],
|
|
994
1032
|
).length &&
|
|
995
1033
|
(!tilmeld.currentUser ||
|
|
996
1034
|
(!tilmeld.currentUser.abilities?.includes('system/admin') &&
|
|
@@ -1392,25 +1430,55 @@ export class User extends AbleObject<UserData> {
|
|
|
1392
1430
|
* @param password The password in question.
|
|
1393
1431
|
* @returns True if the passwords match, otherwise false.
|
|
1394
1432
|
*/
|
|
1395
|
-
public $checkPassword(password: string) {
|
|
1433
|
+
public async $checkPassword(password: string) {
|
|
1396
1434
|
const tilmeld = enforceTilmeld(this);
|
|
1397
1435
|
switch (tilmeld.config.pwMethod) {
|
|
1398
1436
|
case 'plain':
|
|
1399
|
-
return
|
|
1437
|
+
return crypto.timingSafeEqual(
|
|
1438
|
+
Buffer.from(this.password ?? ''),
|
|
1439
|
+
Buffer.from(password),
|
|
1440
|
+
);
|
|
1400
1441
|
case 'digest':
|
|
1401
|
-
return (
|
|
1402
|
-
this.password
|
|
1403
|
-
crypto.createHash('sha256').update(password).digest(
|
|
1442
|
+
return crypto.timingSafeEqual(
|
|
1443
|
+
Buffer.from(this.password ?? '', 'base64'),
|
|
1444
|
+
crypto.createHash('sha256').update(password).digest(),
|
|
1404
1445
|
);
|
|
1405
1446
|
case 'salt':
|
|
1406
1447
|
default:
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
crypto
|
|
1410
|
-
.
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1448
|
+
if (!this.password?.startsWith('scrypt$')) {
|
|
1449
|
+
// Check using the old hash method.
|
|
1450
|
+
return crypto.timingSafeEqual(
|
|
1451
|
+
Buffer.from(this.password ?? '', 'base64'),
|
|
1452
|
+
crypto
|
|
1453
|
+
.createHash('sha256')
|
|
1454
|
+
.update(password + this.salt)
|
|
1455
|
+
.digest(),
|
|
1456
|
+
);
|
|
1457
|
+
}
|
|
1458
|
+
// Check using the new hash method.
|
|
1459
|
+
const [alg, n, rr, pp, salt, keyB64] = (this.password ?? '').split('$');
|
|
1460
|
+
if (alg !== 'scrypt') {
|
|
1461
|
+
return false;
|
|
1462
|
+
}
|
|
1463
|
+
const expected = Buffer.from(keyB64, 'base64');
|
|
1464
|
+
const N = Number(n),
|
|
1465
|
+
r = Number(rr),
|
|
1466
|
+
p = Number(pp);
|
|
1467
|
+
const key = await new Promise<Buffer>((resolve, reject) => {
|
|
1468
|
+
crypto.scrypt(
|
|
1469
|
+
password,
|
|
1470
|
+
salt,
|
|
1471
|
+
expected.length,
|
|
1472
|
+
{ N, r, p, maxmem: 256 * N * r },
|
|
1473
|
+
(err, key) => {
|
|
1474
|
+
if (err) {
|
|
1475
|
+
return reject(err);
|
|
1476
|
+
}
|
|
1477
|
+
resolve(key);
|
|
1478
|
+
},
|
|
1479
|
+
);
|
|
1480
|
+
});
|
|
1481
|
+
return crypto.timingSafeEqual(key, expected);
|
|
1414
1482
|
}
|
|
1415
1483
|
}
|
|
1416
1484
|
|
|
@@ -1493,6 +1561,10 @@ export class User extends AbleObject<UserData> {
|
|
|
1493
1561
|
currentPassword: string;
|
|
1494
1562
|
revokeCurrentTokens?: boolean;
|
|
1495
1563
|
}): Promise<{ result: boolean; message: string }> {
|
|
1564
|
+
const tilmeld = enforceTilmeld(this);
|
|
1565
|
+
if (!tilmeld.gatekeeper()) {
|
|
1566
|
+
return { result: false, message: 'You must be logged in.' };
|
|
1567
|
+
}
|
|
1496
1568
|
this.$nymph.config.debugLog(
|
|
1497
1569
|
'tilmeld',
|
|
1498
1570
|
`Password change request for "${this.username}".`,
|
|
@@ -1500,7 +1572,7 @@ export class User extends AbleObject<UserData> {
|
|
|
1500
1572
|
if (!('newPassword' in data) || !data.newPassword.length) {
|
|
1501
1573
|
return { result: false, message: 'Please specify a password.' };
|
|
1502
1574
|
}
|
|
1503
|
-
if (!this.$checkPassword(data.currentPassword ?? '')) {
|
|
1575
|
+
if (!(await this.$checkPassword(data.currentPassword ?? ''))) {
|
|
1504
1576
|
this.$nymph.config.debugLog(
|
|
1505
1577
|
'tilmeld',
|
|
1506
1578
|
`Incorrect password for change request for "${this.username}".`,
|
|
@@ -1513,11 +1585,15 @@ export class User extends AbleObject<UserData> {
|
|
|
1513
1585
|
}
|
|
1514
1586
|
try {
|
|
1515
1587
|
await this.$save();
|
|
1516
|
-
if (data.revokeCurrentTokens) {
|
|
1517
|
-
const tilmeld = enforceTilmeld(this);
|
|
1588
|
+
if (data.revokeCurrentTokens && this.$is(tilmeld.currentUser)) {
|
|
1518
1589
|
await tilmeld.login(this, true);
|
|
1519
1590
|
}
|
|
1520
|
-
return {
|
|
1591
|
+
return {
|
|
1592
|
+
result: true,
|
|
1593
|
+
message: this.$is(tilmeld.currentUser)
|
|
1594
|
+
? 'Your password has been changed.'
|
|
1595
|
+
: "User's password has been changed.",
|
|
1596
|
+
};
|
|
1521
1597
|
} catch (e: any) {
|
|
1522
1598
|
return { result: false, message: "Couldn't save new password." };
|
|
1523
1599
|
}
|
|
@@ -1529,7 +1605,7 @@ export class User extends AbleObject<UserData> {
|
|
|
1529
1605
|
* @param password The new password.
|
|
1530
1606
|
* @returns The resulting password or hash which is stored in the entity.
|
|
1531
1607
|
*/
|
|
1532
|
-
public $password(password: string) {
|
|
1608
|
+
public async $password(password: string) {
|
|
1533
1609
|
const tilmeld = enforceTilmeld(this);
|
|
1534
1610
|
switch (tilmeld.config.pwMethod) {
|
|
1535
1611
|
case 'plain':
|
|
@@ -1546,10 +1622,20 @@ export class User extends AbleObject<UserData> {
|
|
|
1546
1622
|
case 'salt':
|
|
1547
1623
|
default:
|
|
1548
1624
|
this.salt = nanoid();
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1625
|
+
const N = 131072;
|
|
1626
|
+
const r = 8;
|
|
1627
|
+
const p = 1;
|
|
1628
|
+
const KEYLEN = 64;
|
|
1629
|
+
const opts = { N, r, p, maxmem: 256 * N * r };
|
|
1630
|
+
const key = await new Promise<Buffer>((resolve, reject) => {
|
|
1631
|
+
crypto.scrypt(password, this.salt ?? '', KEYLEN, opts, (err, key) => {
|
|
1632
|
+
if (err) {
|
|
1633
|
+
return reject(err);
|
|
1634
|
+
}
|
|
1635
|
+
resolve(key);
|
|
1636
|
+
});
|
|
1637
|
+
});
|
|
1638
|
+
this.password = `scrypt$${N}$${r}$${p}$${this.salt}$${key.toString('base64')}`;
|
|
1553
1639
|
break;
|
|
1554
1640
|
}
|
|
1555
1641
|
return this.password;
|
|
@@ -1564,7 +1650,11 @@ export class User extends AbleObject<UserData> {
|
|
|
1564
1650
|
public async $revokeCurrentTokens(data: {
|
|
1565
1651
|
password: string;
|
|
1566
1652
|
}): Promise<{ result: boolean; message: string }> {
|
|
1567
|
-
|
|
1653
|
+
const tilmeld = enforceTilmeld(this);
|
|
1654
|
+
if (!tilmeld.gatekeeper()) {
|
|
1655
|
+
return { result: false, message: 'You must be logged in.' };
|
|
1656
|
+
}
|
|
1657
|
+
if (!(await this.$checkPassword(data.password ?? ''))) {
|
|
1568
1658
|
return { result: false, message: 'Incorrect password.' };
|
|
1569
1659
|
}
|
|
1570
1660
|
|
|
@@ -1572,12 +1662,18 @@ export class User extends AbleObject<UserData> {
|
|
|
1572
1662
|
|
|
1573
1663
|
try {
|
|
1574
1664
|
await this.$save();
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1665
|
+
if (this.$is(tilmeld.currentUser)) {
|
|
1666
|
+
await tilmeld.login(this, true);
|
|
1667
|
+
return {
|
|
1668
|
+
result: true,
|
|
1669
|
+
message: 'You have logged out of all other sessions.',
|
|
1670
|
+
};
|
|
1671
|
+
} else {
|
|
1672
|
+
return {
|
|
1673
|
+
result: true,
|
|
1674
|
+
message: 'The user has been logged out of all sessions.',
|
|
1675
|
+
};
|
|
1676
|
+
}
|
|
1581
1677
|
} catch (e: any) {
|
|
1582
1678
|
return { result: false, message: "Couldn't save revocation date." };
|
|
1583
1679
|
}
|
|
@@ -1657,7 +1753,7 @@ export class User extends AbleObject<UserData> {
|
|
|
1657
1753
|
secret: string;
|
|
1658
1754
|
code: string;
|
|
1659
1755
|
}): Promise<{ result: boolean; message: string }> {
|
|
1660
|
-
if (!this.$checkPassword(data.password ?? '')) {
|
|
1756
|
+
if (!(await this.$checkPassword(data.password ?? ''))) {
|
|
1661
1757
|
return { result: false, message: 'Incorrect password.' };
|
|
1662
1758
|
}
|
|
1663
1759
|
|
|
@@ -1720,7 +1816,7 @@ export class User extends AbleObject<UserData> {
|
|
|
1720
1816
|
const tilmeld = enforceTilmeld(this);
|
|
1721
1817
|
|
|
1722
1818
|
if (data) {
|
|
1723
|
-
if (!this.$checkPassword(data.password ?? '')) {
|
|
1819
|
+
if (!(await this.$checkPassword(data.password ?? ''))) {
|
|
1724
1820
|
return { result: false, message: 'Incorrect password.' };
|
|
1725
1821
|
}
|
|
1726
1822
|
|
|
@@ -2029,7 +2125,7 @@ export class User extends AbleObject<UserData> {
|
|
|
2029
2125
|
return { ...unCheck, loggedin: false };
|
|
2030
2126
|
}
|
|
2031
2127
|
|
|
2032
|
-
this.$password(data.password);
|
|
2128
|
+
await this.$password(data.password);
|
|
2033
2129
|
if (tilmeld.config.regFields.includes('name')) {
|
|
2034
2130
|
this.name =
|
|
2035
2131
|
this.nameFirst +
|
|
@@ -2089,6 +2185,7 @@ export class User extends AbleObject<UserData> {
|
|
|
2089
2185
|
}
|
|
2090
2186
|
if (!otherUsers.length) {
|
|
2091
2187
|
this.$grant('system/admin');
|
|
2188
|
+
this.$grant('system/autogranted');
|
|
2092
2189
|
this.enabled = true;
|
|
2093
2190
|
madeAdmin = true;
|
|
2094
2191
|
}
|
|
@@ -2452,12 +2549,13 @@ export class User extends AbleObject<UserData> {
|
|
|
2452
2549
|
// be able to cancel the email change from their old address.
|
|
2453
2550
|
this.cancelEmailAddress = this.$originalEmail;
|
|
2454
2551
|
this.cancelEmailSecret = nanoid();
|
|
2552
|
+
this.emailChangeDate = Date.now();
|
|
2455
2553
|
sendVerification = true;
|
|
2456
2554
|
}
|
|
2457
2555
|
}
|
|
2458
2556
|
|
|
2459
2557
|
if (this.passwordTemp != null) {
|
|
2460
|
-
this.$password(this.passwordTemp);
|
|
2558
|
+
await this.$password(this.passwordTemp);
|
|
2461
2559
|
}
|
|
2462
2560
|
delete this.passwordTemp;
|
|
2463
2561
|
|
package/src/conf/defaults.ts
CHANGED
|
@@ -61,21 +61,32 @@ export default {
|
|
|
61
61
|
jwtExpire: 60 * 60 * 24 * 7 * 24, // 24 weeks(ish) (6 months)
|
|
62
62
|
jwtSwitchExpire: 60 * 60 * 6, // 6 hours
|
|
63
63
|
jwtRenew: 60 * 60 * 24 * 7 * 12, // 12 weeks(ish) (3 months)
|
|
64
|
-
jwtBuilder: (config, user, switchToken) => {
|
|
64
|
+
jwtBuilder: (config, user, switchToken, switchForUser) => {
|
|
65
65
|
const secret = config.jwtSecret;
|
|
66
66
|
if (secret === '') {
|
|
67
67
|
throw new Error('JWT secret is not configured.');
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
if (switchToken && (!switchForUser || switchForUser.guid == null)) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
"Issuing a switch token requires providing the user it's for.",
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
70
76
|
return jwt.sign(
|
|
71
77
|
{
|
|
72
78
|
iss: config.appUrl,
|
|
79
|
+
sub: switchForUser ? switchForUser.guid : user.guid,
|
|
73
80
|
nbf: Math.floor(Date.now() / 1000),
|
|
74
81
|
exp:
|
|
75
82
|
Math.floor(Date.now() / 1000) +
|
|
76
83
|
(switchToken ? config.jwtSwitchExpire : config.jwtExpire),
|
|
84
|
+
// TODO: remove this guid claim in favor of sub claim
|
|
85
|
+
// this needs to be done long enough after Nov 1 2026, when sub added
|
|
86
|
+
// also don't forget to change how switchForUser guid is stored.
|
|
77
87
|
guid: user.guid,
|
|
78
88
|
xsrfToken: 'TILMELDXSRF-' + nanoid(),
|
|
89
|
+
...(switchToken ? { switch: true } : {}),
|
|
79
90
|
},
|
|
80
91
|
secret,
|
|
81
92
|
);
|
|
@@ -96,7 +107,14 @@ export default {
|
|
|
96
107
|
return null;
|
|
97
108
|
}
|
|
98
109
|
|
|
99
|
-
const {
|
|
110
|
+
const {
|
|
111
|
+
sub,
|
|
112
|
+
guid,
|
|
113
|
+
xsrfToken: jwtXsrfToken,
|
|
114
|
+
switch: switchBool,
|
|
115
|
+
exp,
|
|
116
|
+
nbf,
|
|
117
|
+
} = payload;
|
|
100
118
|
|
|
101
119
|
if (xsrfToken != null && xsrfToken !== jwtXsrfToken) {
|
|
102
120
|
return null;
|
|
@@ -118,7 +136,7 @@ export default {
|
|
|
118
136
|
|
|
119
137
|
const expire = new Date(exp * 1000);
|
|
120
138
|
|
|
121
|
-
return { guid, issued, expire };
|
|
139
|
+
return { sub, guid, issued, expire, switch: switchBool };
|
|
122
140
|
} catch (e: any) {
|
|
123
141
|
return null;
|
|
124
142
|
}
|
package/src/conf/index.ts
CHANGED
|
@@ -164,12 +164,18 @@ export interface Config {
|
|
|
164
164
|
*/
|
|
165
165
|
pwRecoveryTimeLimit: string;
|
|
166
166
|
/**
|
|
167
|
-
* Method used to store passwords. Salt is
|
|
168
|
-
* compromised.
|
|
169
|
-
* password's digest (hash). Salt: store the password's digest using a
|
|
170
|
-
* complex, unique salt.
|
|
167
|
+
* Method used to store passwords. Salt is the most secure if the database is
|
|
168
|
+
* compromised.
|
|
171
169
|
*
|
|
172
|
-
*
|
|
170
|
+
* - Plain: Store the password in plain text. A very bad idea.
|
|
171
|
+
* - Digest: Store the password's digest (hash) using SHA-256. The minimum
|
|
172
|
+
* level of security, but not enough for modern apps.
|
|
173
|
+
* - Salt: Store the password's digest with a complex, unique salt using
|
|
174
|
+
* the scrypt algorithm. Modern, secure password storage.
|
|
175
|
+
*
|
|
176
|
+
* Really the only reason to use anything other than "salt" is if you need
|
|
177
|
+
* logins to be so incredibly fast you can't even perceive a delay. So, not in
|
|
178
|
+
* a web app.
|
|
173
179
|
*
|
|
174
180
|
* Options are: "plain", "digest", "salt"
|
|
175
181
|
*/
|
|
@@ -265,7 +271,12 @@ export interface Config {
|
|
|
265
271
|
/**
|
|
266
272
|
* Function to build the JWT for user sessions.
|
|
267
273
|
*/
|
|
268
|
-
jwtBuilder: (
|
|
274
|
+
jwtBuilder: (
|
|
275
|
+
config: Config,
|
|
276
|
+
user: User,
|
|
277
|
+
switchToken?: boolean,
|
|
278
|
+
switchForUser?: User,
|
|
279
|
+
) => string;
|
|
269
280
|
/**
|
|
270
281
|
* Function to verify that a JWT was signed with the secret key, validate its
|
|
271
282
|
* data, validate the XSRF token, and extract the GUID and dates.
|
|
@@ -279,7 +290,13 @@ export interface Config {
|
|
|
279
290
|
config: Config,
|
|
280
291
|
token: string,
|
|
281
292
|
xsrfToken?: string,
|
|
282
|
-
) => {
|
|
293
|
+
) => {
|
|
294
|
+
sub: string;
|
|
295
|
+
guid: string;
|
|
296
|
+
issued: Date;
|
|
297
|
+
expire: Date;
|
|
298
|
+
switch?: true;
|
|
299
|
+
} | null;
|
|
283
300
|
/**
|
|
284
301
|
* The absolute path to the email template directory. Used by the default
|
|
285
302
|
* email sender.
|
package/src/testArtifacts.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { GroupTest } from './Group.testArtifacts.js';
|
|
|
17
17
|
|
|
18
18
|
export interface TestModelData extends AccessControlData {
|
|
19
19
|
name?: string;
|
|
20
|
+
another?: TestModel;
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
export interface TestModel extends TestModelData {}
|
|
@@ -147,7 +148,7 @@ export function TilmeldTest(
|
|
|
147
148
|
admin.nameFirst = 'Admin';
|
|
148
149
|
admin.nameLast = 'User';
|
|
149
150
|
admin.name = 'Admin User';
|
|
150
|
-
admin.$password('password');
|
|
151
|
+
await admin.$password('password');
|
|
151
152
|
admin.$grant('system/admin');
|
|
152
153
|
await admin.$saveSkipAC();
|
|
153
154
|
}
|
|
@@ -165,7 +166,7 @@ export function TilmeldTest(
|
|
|
165
166
|
bob.nameFirst = 'Bob';
|
|
166
167
|
bob.nameLast = 'User';
|
|
167
168
|
bob.name = 'Bob User';
|
|
168
|
-
bob.$password('password');
|
|
169
|
+
await bob.$password('password');
|
|
169
170
|
bob.$addGroup(abgroup);
|
|
170
171
|
await bob.$saveSkipAC();
|
|
171
172
|
}
|
|
@@ -176,7 +177,7 @@ export function TilmeldTest(
|
|
|
176
177
|
alice.nameFirst = 'Alice';
|
|
177
178
|
alice.nameLast = 'User';
|
|
178
179
|
alice.name = 'Alice User';
|
|
179
|
-
alice.$password('password');
|
|
180
|
+
await alice.$password('password');
|
|
180
181
|
alice.$addGroup(abgroup);
|
|
181
182
|
await alice.$saveSkipAC();
|
|
182
183
|
}
|
|
@@ -1223,6 +1224,53 @@ export function TilmeldTest(
|
|
|
1223
1224
|
expect(verifyEntityAlice).toBeNull();
|
|
1224
1225
|
});
|
|
1225
1226
|
|
|
1227
|
+
it("alice can't find an entity by qref using an entity she doesn't have permission for", async () => {
|
|
1228
|
+
const { bob, alice } = await makeUsers();
|
|
1229
|
+
|
|
1230
|
+
await tilmeld.fillSession(bob);
|
|
1231
|
+
|
|
1232
|
+
const bobsEntity = await TestModel.factory();
|
|
1233
|
+
bobsEntity.name = "Bob's Entity";
|
|
1234
|
+
await bobsEntity.$save();
|
|
1235
|
+
|
|
1236
|
+
expect(bobsEntity.guid).not.toBeNull();
|
|
1237
|
+
expect(bob.$is(bobsEntity.user)).toEqual(true);
|
|
1238
|
+
|
|
1239
|
+
const testEntityBob = await nymph.getEntity(
|
|
1240
|
+
{ class: TestModel },
|
|
1241
|
+
{ type: '&', guid: bobsEntity.guid || '' },
|
|
1242
|
+
);
|
|
1243
|
+
|
|
1244
|
+
expect(testEntityBob).not.toBeNull();
|
|
1245
|
+
expect(testEntityBob?.guid).toEqual(bobsEntity.guid);
|
|
1246
|
+
|
|
1247
|
+
tilmeld.clearSession();
|
|
1248
|
+
await tilmeld.fillSession(alice);
|
|
1249
|
+
|
|
1250
|
+
const alicesEntity = await TestModel.factory();
|
|
1251
|
+
alicesEntity.name = "Alice's Entity";
|
|
1252
|
+
await alicesEntity.$save();
|
|
1253
|
+
|
|
1254
|
+
expect(alicesEntity.guid).not.toBeNull();
|
|
1255
|
+
expect(alice.$is(alicesEntity.user)).toEqual(true);
|
|
1256
|
+
|
|
1257
|
+
alicesEntity.another = bobsEntity;
|
|
1258
|
+
await alicesEntity.$save();
|
|
1259
|
+
|
|
1260
|
+
const testEntityAlice = await nymph.getEntity(
|
|
1261
|
+
{ class: TestModel },
|
|
1262
|
+
{
|
|
1263
|
+
type: '&',
|
|
1264
|
+
qref: [
|
|
1265
|
+
'another',
|
|
1266
|
+
[{ class: TestModel }, { type: '&', guid: bobsEntity.guid || '' }],
|
|
1267
|
+
],
|
|
1268
|
+
},
|
|
1269
|
+
);
|
|
1270
|
+
|
|
1271
|
+
expect(testEntityAlice).toBeNull();
|
|
1272
|
+
});
|
|
1273
|
+
|
|
1226
1274
|
it('a clear session can delete an entity everyone has full permission for', async () => {
|
|
1227
1275
|
const { bob } = await makeUsers();
|
|
1228
1276
|
|