@nymphjs/tilmeld 1.0.0-beta.2 → 1.0.0-beta.20

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/src/User.ts CHANGED
@@ -14,6 +14,7 @@ import { difference } from 'lodash';
14
14
  import { humanSecret, nanoid } from '@nymphjs/guid';
15
15
 
16
16
  import type Tilmeld from './Tilmeld';
17
+ import { enforceTilmeld } from './enforceTilmeld';
17
18
  import AbleObject from './AbleObject';
18
19
  import Group, { GroupData } from './Group';
19
20
  import {
@@ -24,12 +25,22 @@ import {
24
25
  } from './errors';
25
26
 
26
27
  export type EventType =
28
+ | 'checkUsername'
27
29
  | 'beforeRegister'
28
30
  | 'afterRegister'
29
31
  | 'beforeLogin'
30
32
  | 'afterLogin'
31
33
  | 'beforeLogout'
32
34
  | 'afterLogout';
35
+ /**
36
+ * This is run when the user has entered an otherwise valid username into the
37
+ * signup form. It should return a result, which when false will stop the
38
+ * process and return the included message, disallowing the username.
39
+ */
40
+ export type TilmeldCheckUsernameCallback = (
41
+ user: User & UserData,
42
+ data: { username: string }
43
+ ) => Promise<{ result: boolean; message?: string }>;
33
44
  /**
34
45
  * Theses are run before the user data checks, so the only checks before are
35
46
  * whether registration is allowed and whether the user is already registered.
@@ -135,7 +146,8 @@ export type UserData = {
135
146
  */
136
147
  newEmailSecret?: string;
137
148
  /**
138
- * The new email address.
149
+ * If the user has changed their email address, this is the new one, awaiting
150
+ * verification.
139
151
  */
140
152
  newEmailAddress?: string;
141
153
  /**
@@ -167,6 +179,11 @@ export type UserData = {
167
179
  * database.
168
180
  */
169
181
  passwordTemp?: string;
182
+ /**
183
+ * If set, this timestamp is the cutoff point for JWT issue dates. Any token
184
+ * issued before this date will not authenticate the user.
185
+ */
186
+ revokeTokenDate?: number;
170
187
  };
171
188
 
172
189
  /**
@@ -185,6 +202,7 @@ export default class User extends AbleObject<UserData> {
185
202
  static tilmeld: Tilmeld;
186
203
  static ETYPE = 'tilmeld_user';
187
204
  static class = 'User';
205
+ private static checkUsernameCallbacks: TilmeldCheckUsernameCallback[] = [];
188
206
  private static beforeRegisterCallbacks: TilmeldBeforeRegisterCallback[] = [];
189
207
  private static afterRegisterCallbacks: TilmeldAfterRegisterCallback[] = [];
190
208
  private static beforeLoginCallbacks: TilmeldBeforeLoginCallback[] = [];
@@ -200,23 +218,31 @@ export default class User extends AbleObject<UserData> {
200
218
  '$register',
201
219
  ];
202
220
  private static DEFAULT_PRIVATE_DATA = [
221
+ // The things that are commented are there to show what is _not_ private.
222
+ 'abilities',
223
+ // 'username',
224
+ // 'nameFirst',
225
+ // 'nameMiddle',
226
+ // 'nameLast',
227
+ // 'name',
203
228
  'email',
204
- 'originalEmail',
229
+ // 'avatar',
205
230
  'phone',
206
231
  'group',
207
232
  'groups',
208
- 'abilities',
209
233
  'inheritAbilities',
210
- 'recoverSecret',
211
- 'recoverSecretDate',
212
- 'password',
213
- 'salt',
234
+ 'enabled',
214
235
  'secret',
215
- 'newEmailAddress',
236
+ 'emailChangeDate',
216
237
  'newEmailSecret',
217
- 'cancelEmailAddress',
238
+ 'newEmailAddress',
218
239
  'cancelEmailSecret',
219
- 'emailChangeDate',
240
+ 'cancelEmailAddress',
241
+ 'recoverSecret',
242
+ 'recoverSecretDate',
243
+ 'salt',
244
+ 'password',
245
+ 'passwordTemp',
220
246
  ];
221
247
  private static DEFAULT_ALLOWLIST_DATA: string[] = [];
222
248
 
@@ -501,7 +527,7 @@ export default class User extends AbleObject<UserData> {
501
527
  password: string;
502
528
  additionalData?: { [k: string]: any };
503
529
  }) {
504
- const tilmeld = this.$nymph.tilmeld as Tilmeld;
530
+ const tilmeld = enforceTilmeld(this);
505
531
  if (this.guid == null) {
506
532
  return { result: false, message: 'Incorrect login/password.' };
507
533
  }
@@ -545,12 +571,84 @@ export default class User extends AbleObject<UserData> {
545
571
  return { result: true, message: 'You are logged in.' };
546
572
  }
547
573
 
574
+ public async $switchUser(data?: { additionalData?: { [k: string]: any } }) {
575
+ const tilmeld = enforceTilmeld(this);
576
+ if (this.guid == null) {
577
+ return { result: false, message: 'Incorrect login/password.' };
578
+ }
579
+
580
+ let user = tilmeld.User.current();
581
+
582
+ if (!user || user.guid == null) {
583
+ return { result: false, message: "You're not logged in." };
584
+ }
585
+
586
+ const isCurrentUser = user != null && user.$is(this);
587
+
588
+ if (isCurrentUser) {
589
+ return { result: false, message: "You can't switch to yourself." };
590
+ }
591
+
592
+ const toAbilities = this.$data.abilities || [];
593
+ const fromAbilities = user.abilities || [];
594
+
595
+ if (
596
+ fromAbilities.indexOf('tilmeld/switch') === -1 &&
597
+ fromAbilities.indexOf('system/admin') === -1
598
+ ) {
599
+ return {
600
+ result: false,
601
+ message: "You don't have permission to switch users.",
602
+ };
603
+ }
604
+
605
+ if (
606
+ toAbilities.indexOf('system/admin') !== -1 ||
607
+ toAbilities.indexOf('tilmeld/admin') !== -1
608
+ ) {
609
+ return { result: false, message: "You can't switch to an admin." };
610
+ }
611
+
612
+ try {
613
+ for (let callback of (this.constructor as typeof User)
614
+ .beforeLoginCallbacks) {
615
+ if (callback) {
616
+ await callback(this, {
617
+ username: this.$data.username || '',
618
+ password: '',
619
+ ...(data || {}),
620
+ });
621
+ }
622
+ }
623
+ } catch (e: any) {
624
+ return {
625
+ result: false,
626
+ message: e.message,
627
+ };
628
+ }
629
+
630
+ // Authentication was successful, attempt to login.
631
+ if (!tilmeld.loginSwitch(this, true)) {
632
+ return { result: false, message: 'Incorrect login/password.' };
633
+ }
634
+
635
+ for (let callback of (this.constructor as typeof User)
636
+ .afterLoginCallbacks) {
637
+ if (callback) {
638
+ await callback(this);
639
+ }
640
+ }
641
+
642
+ // Login was successful.
643
+ return { result: true, message: 'You are logged in.' };
644
+ }
645
+
548
646
  /**
549
647
  * Log a user out of the system.
550
648
  * @returns An object with a boolean 'result' entry and a 'message' entry.
551
649
  */
552
650
  public async $logout() {
553
- const tilmeld = this.$nymph.tilmeld as Tilmeld;
651
+ const tilmeld = enforceTilmeld(this);
554
652
 
555
653
  try {
556
654
  for (let callback of (this.constructor as typeof User)
@@ -629,17 +727,39 @@ export default class User extends AbleObject<UserData> {
629
727
  }
630
728
 
631
729
  public $jsonAcceptData(input: EntityJson, allowConflict = false) {
632
- const tilmeld = this.$nymph.tilmeld as Tilmeld;
730
+ const tilmeld = enforceTilmeld(this);
633
731
  this.$referenceWake();
634
732
 
635
733
  if (
636
- input.data.abilities?.indexOf('system/admin') !== -1 &&
637
- this.$data.abilities?.indexOf('system/admin') === -1 &&
638
- tilmeld.gatekeeper('tilmeld/admin') &&
734
+ 'abilities' in input.data &&
735
+ input.data.abilities?.includes('system/admin') !==
736
+ this.$data.abilities?.includes('system/admin') &&
737
+ !tilmeld.gatekeeper('system/admin')
738
+ ) {
739
+ throw new BadDataError(
740
+ "You don't have the authority to grant or revoke system/admin."
741
+ );
742
+ }
743
+
744
+ if (
745
+ 'abilities' in input.data &&
746
+ input.data.abilities?.includes('tilmeld/admin') !==
747
+ this.$data.abilities?.includes('tilmeld/admin') &&
748
+ !tilmeld.gatekeeper('system/admin')
749
+ ) {
750
+ throw new BadDataError(
751
+ "You don't have the authority to grant or revoke tilmeld/admin."
752
+ );
753
+ }
754
+
755
+ if (
756
+ 'abilities' in input.data &&
757
+ input.data.abilities?.includes('tilmeld/switch') !==
758
+ this.$data.abilities?.includes('tilmeld/switch') &&
639
759
  !tilmeld.gatekeeper('system/admin')
640
760
  ) {
641
761
  throw new BadDataError(
642
- "You don't have the authority to make this user a system admin."
762
+ "You don't have the authority to grant or revoke tilmeld/switch."
643
763
  );
644
764
  }
645
765
 
@@ -648,17 +768,39 @@ export default class User extends AbleObject<UserData> {
648
768
  }
649
769
 
650
770
  public $jsonAcceptPatch(patch: EntityPatch, allowConflict = false) {
651
- const tilmeld = this.$nymph.tilmeld as Tilmeld;
771
+ const tilmeld = enforceTilmeld(this);
652
772
  this.$referenceWake();
653
773
 
654
774
  if (
655
- patch.set.abilities?.indexOf('system/admin') !== -1 &&
656
- this.$data.abilities?.indexOf('system/admin') === -1 &&
657
- tilmeld.gatekeeper('tilmeld/admin') &&
775
+ 'abilities' in patch.set &&
776
+ patch.set.abilities?.includes('system/admin') !==
777
+ this.$data.abilities?.includes('system/admin') &&
658
778
  !tilmeld.gatekeeper('system/admin')
659
779
  ) {
660
780
  throw new BadDataError(
661
- "You don't have the authority to make this user a system admin."
781
+ "You don't have the authority to grant or revoke system/admin."
782
+ );
783
+ }
784
+
785
+ if (
786
+ 'abilities' in patch.set &&
787
+ patch.set.abilities?.includes('tilmeld/admin') !==
788
+ this.$data.abilities?.includes('tilmeld/admin') &&
789
+ !tilmeld.gatekeeper('system/admin')
790
+ ) {
791
+ throw new BadDataError(
792
+ "You don't have the authority to grant or revoke tilmeld/admin."
793
+ );
794
+ }
795
+
796
+ if (
797
+ 'abilities' in patch.set &&
798
+ patch.set.abilities?.includes('tilmeld/switch') !==
799
+ this.$data.abilities?.includes('tilmeld/switch') &&
800
+ !tilmeld.gatekeeper('system/admin')
801
+ ) {
802
+ throw new BadDataError(
803
+ "You don't have the authority to grant or revoke tilmeld/switch."
662
804
  );
663
805
  }
664
806
 
@@ -677,7 +819,7 @@ export default class User extends AbleObject<UserData> {
677
819
  * @param givenUser User to update protection for. If undefined, will use the currently logged in user.
678
820
  */
679
821
  public $updateDataProtection(givenUser?: User & UserData) {
680
- const tilmeld = this.$nymph.tilmeld as Tilmeld;
822
+ const tilmeld = enforceTilmeld(this);
681
823
  let user = givenUser ?? tilmeld.User.current();
682
824
 
683
825
  this.$clientEnabledMethods = [...User.DEFAULT_CLIENT_ENABLED_METHODS];
@@ -701,6 +843,7 @@ export default class User extends AbleObject<UserData> {
701
843
  // Users can check to see what abilities they have.
702
844
  this.$clientEnabledMethods.push('$gatekeeper');
703
845
  this.$clientEnabledMethods.push('$changePassword');
846
+ this.$clientEnabledMethods.push('$revokeCurrentTokens');
704
847
  this.$clientEnabledMethods.push('$logout');
705
848
  this.$clientEnabledMethods.push('$sendEmailVerification');
706
849
  }
@@ -732,17 +875,43 @@ export default class User extends AbleObject<UserData> {
732
875
  this.$allowlistData.push('phone');
733
876
  }
734
877
  this.$privateData = [
878
+ // The things that are commented are there to show what is _not_ private.
879
+ // 'abilities',
880
+ // 'username',
881
+ // 'nameFirst',
882
+ // 'nameMiddle',
883
+ // 'nameLast',
884
+ // 'name',
885
+ // 'email',
886
+ // 'avatar',
887
+ // 'phone',
888
+ // 'group',
889
+ // 'groups',
890
+ // 'inheritAbilities',
891
+ 'enabled',
735
892
  'secret',
893
+ 'emailChangeDate',
736
894
  'newEmailSecret',
737
- 'cancelEmailAddress',
895
+ // 'newEmailAddress',
738
896
  'cancelEmailSecret',
739
- 'emailChangeDate',
897
+ 'cancelEmailAddress',
740
898
  'recoverSecret',
741
899
  'recoverSecretDate',
742
- 'password',
743
900
  'salt',
901
+ 'password',
902
+ 'passwordTemp',
744
903
  ];
745
904
  }
905
+
906
+ if (
907
+ user != null &&
908
+ !isCurrentUser &&
909
+ (abilities.indexOf('tilmeld/switch') !== -1 ||
910
+ abilities.indexOf('system/admin') !== -1)
911
+ ) {
912
+ // Users with this ability can switch to other users.
913
+ this.$clientEnabledMethods.push('$switchUser');
914
+ }
746
915
  }
747
916
 
748
917
  /**
@@ -760,7 +929,7 @@ export default class User extends AbleObject<UserData> {
760
929
  * @returns True or false.
761
930
  */
762
931
  public $gatekeeper(ability?: string): boolean {
763
- const tilmeld = this.$nymph.tilmeld as Tilmeld;
932
+ const tilmeld = enforceTilmeld(this);
764
933
  if (ability == null) {
765
934
  return tilmeld.User.current()?.$is(this) ?? false;
766
935
  }
@@ -787,7 +956,7 @@ export default class User extends AbleObject<UserData> {
787
956
  }
788
957
  return (
789
958
  (ability in this.$gatekeeperCache && !!this.$gatekeeperCache[ability]) ||
790
- !!this.$gatekeeperCache['system/admin']
959
+ !!this.$data.abilities?.includes('system/admin')
791
960
  );
792
961
  }
793
962
 
@@ -804,7 +973,7 @@ export default class User extends AbleObject<UserData> {
804
973
  * @returns True on success, false on failure.
805
974
  */
806
975
  public async $sendEmailVerification() {
807
- const tilmeld = this.$nymph.tilmeld as Tilmeld;
976
+ const tilmeld = enforceTilmeld(this);
808
977
  if (this.guid == null) {
809
978
  return false;
810
979
  }
@@ -917,7 +1086,7 @@ export default class User extends AbleObject<UserData> {
917
1086
  * @returns True if the passwords match, otherwise false.
918
1087
  */
919
1088
  public $checkPassword(password: string) {
920
- const tilmeld = this.$nymph.tilmeld as Tilmeld;
1089
+ const tilmeld = enforceTilmeld(this);
921
1090
  switch (tilmeld.config.pwMethod) {
922
1091
  case 'plain':
923
1092
  return this.$data.password === password;
@@ -1006,6 +1175,7 @@ export default class User extends AbleObject<UserData> {
1006
1175
  public async $changePassword(data: {
1007
1176
  newPassword: string;
1008
1177
  currentPassword: string;
1178
+ revokeCurrentTokens?: boolean;
1009
1179
  }): Promise<{ result: boolean; message: string }> {
1010
1180
  if (!('newPassword' in data) || !data.newPassword.length) {
1011
1181
  return { result: false, message: 'Please specify a password.' };
@@ -1014,7 +1184,14 @@ export default class User extends AbleObject<UserData> {
1014
1184
  return { result: false, message: 'Incorrect password.' };
1015
1185
  }
1016
1186
  this.$data.passwordTemp = data.newPassword;
1187
+ if (data.revokeCurrentTokens) {
1188
+ this.$data.revokeTokenDate = Date.now();
1189
+ }
1017
1190
  if (await this.$save()) {
1191
+ if (data.revokeCurrentTokens) {
1192
+ const tilmeld = enforceTilmeld(this);
1193
+ tilmeld.login(this, true);
1194
+ }
1018
1195
  return { result: true, message: 'Your password has been changed.' };
1019
1196
  } else {
1020
1197
  return { result: false, message: "Couldn't save new password." };
@@ -1028,7 +1205,7 @@ export default class User extends AbleObject<UserData> {
1028
1205
  * @returns The resulting password or hash which is stored in the entity.
1029
1206
  */
1030
1207
  public $password(password: string) {
1031
- const tilmeld = this.$nymph.tilmeld as Tilmeld;
1208
+ const tilmeld = enforceTilmeld(this);
1032
1209
  switch (tilmeld.config.pwMethod) {
1033
1210
  case 'plain':
1034
1211
  delete this.$data.salt;
@@ -1049,13 +1226,38 @@ export default class User extends AbleObject<UserData> {
1049
1226
  return this.$data.password;
1050
1227
  }
1051
1228
 
1229
+ /**
1230
+ * A frontend accessible method to revoke all currently issued tokens.
1231
+ *
1232
+ * @param data The input data from the client.
1233
+ * @returns An object with a boolean 'result' entry and a 'message' entry.
1234
+ */
1235
+ public async $revokeCurrentTokens(data: {
1236
+ password: string;
1237
+ }): Promise<{ result: boolean; message: string }> {
1238
+ if (!this.$checkPassword(data.password ?? '')) {
1239
+ return { result: false, message: 'Incorrect password.' };
1240
+ }
1241
+ this.$data.revokeTokenDate = Date.now();
1242
+ if (await this.$save()) {
1243
+ const tilmeld = enforceTilmeld(this);
1244
+ tilmeld.login(this, true);
1245
+ return {
1246
+ result: true,
1247
+ message: 'You have logged out of all other sessions.',
1248
+ };
1249
+ } else {
1250
+ return { result: false, message: "Couldn't save revocation date." };
1251
+ }
1252
+ }
1253
+
1052
1254
  /**
1053
1255
  * Check that a username is valid.
1054
1256
  *
1055
1257
  * @returns An object with a boolean 'result' entry and a 'message' entry.
1056
1258
  */
1057
1259
  public async $checkUsername(): Promise<{ result: boolean; message: string }> {
1058
- const tilmeld = this.$nymph.tilmeld as Tilmeld;
1260
+ const tilmeld = enforceTilmeld(this);
1059
1261
  if (!tilmeld.config.emailUsernames) {
1060
1262
  if (this.$data.username == null || !this.$data.username.length) {
1061
1263
  return { result: false, message: 'Please specify a username.' };
@@ -1109,6 +1311,18 @@ export default class User extends AbleObject<UserData> {
1109
1311
  return { result: false, message: 'That username is taken.' };
1110
1312
  }
1111
1313
 
1314
+ for (let callback of (this.constructor as typeof User)
1315
+ .checkUsernameCallbacks) {
1316
+ if (callback) {
1317
+ const result = await callback(this, {
1318
+ username: this.$data.username,
1319
+ });
1320
+ if (!result.result) {
1321
+ return { message: 'Username is not available.', ...result };
1322
+ }
1323
+ }
1324
+ }
1325
+
1112
1326
  return {
1113
1327
  result: true,
1114
1328
  message:
@@ -1142,7 +1356,7 @@ export default class User extends AbleObject<UserData> {
1142
1356
  * @returns An object with a boolean 'result' entry and a 'message' entry.
1143
1357
  */
1144
1358
  public async $checkEmail(): Promise<{ result: boolean; message: string }> {
1145
- const tilmeld = this.$nymph.tilmeld as Tilmeld;
1359
+ const tilmeld = enforceTilmeld(this);
1146
1360
  if (this.$data.email == null || !this.$data.email.length) {
1147
1361
  if (tilmeld.config.verifyEmail) {
1148
1362
  return { result: false, message: 'Please specify an email.' };
@@ -1187,7 +1401,7 @@ export default class User extends AbleObject<UserData> {
1187
1401
  * @returns An object with a boolean 'result' entry and a 'message' entry.
1188
1402
  */
1189
1403
  public async $checkPhone(): Promise<{ result: boolean; message: string }> {
1190
- const tilmeld = this.$nymph.tilmeld as Tilmeld;
1404
+ const tilmeld = enforceTilmeld(this);
1191
1405
  if (this.$data.phone == null || !this.$data.phone.length) {
1192
1406
  return { result: false, message: 'Please specify a phone number.' };
1193
1407
  }
@@ -1226,7 +1440,7 @@ export default class User extends AbleObject<UserData> {
1226
1440
  password: string;
1227
1441
  additionalData?: { [k: string]: any };
1228
1442
  }): Promise<{ result: boolean; loggedin: boolean; message: string }> {
1229
- let tilmeld = this.$nymph.tilmeld as Tilmeld;
1443
+ let tilmeld = enforceTilmeld(this);
1230
1444
  if (!tilmeld.config.allowRegistration) {
1231
1445
  return {
1232
1446
  result: false,
@@ -1288,7 +1502,7 @@ export default class User extends AbleObject<UserData> {
1288
1502
  const nymph = this.$nymph;
1289
1503
  const tnymph = await this.$nymph.startTransaction(transaction);
1290
1504
  this.$nymph = tnymph;
1291
- tilmeld = this.$nymph.tilmeld as Tilmeld;
1505
+ tilmeld = enforceTilmeld(this);
1292
1506
 
1293
1507
  try {
1294
1508
  // Add primary group.
@@ -1493,7 +1707,7 @@ export default class User extends AbleObject<UserData> {
1493
1707
  }
1494
1708
 
1495
1709
  public async $save() {
1496
- let tilmeld = this.$nymph.tilmeld as Tilmeld;
1710
+ let tilmeld = enforceTilmeld(this);
1497
1711
  if (this.$data.username == null || !this.$data.username.trim().length) {
1498
1712
  return false;
1499
1713
  }
@@ -1501,7 +1715,7 @@ export default class User extends AbleObject<UserData> {
1501
1715
  if (
1502
1716
  tilmeld.gatekeeper('tilmeld/admin') &&
1503
1717
  !tilmeld.gatekeeper('system/admin') &&
1504
- this.$gatekeeper('system/admin')
1718
+ this.$data.abilities?.includes('system/admin')
1505
1719
  ) {
1506
1720
  throw new BadDataError(
1507
1721
  "You don't have the authority to modify system admins."
@@ -1691,7 +1905,7 @@ export default class User extends AbleObject<UserData> {
1691
1905
  const nymph = this.$nymph;
1692
1906
  const tnymph = await this.$nymph.startTransaction(transaction);
1693
1907
  this.$nymph = tnymph;
1694
- tilmeld = this.$nymph.tilmeld as Tilmeld;
1908
+ tilmeld = enforceTilmeld(this);
1695
1909
 
1696
1910
  if (
1697
1911
  this.$data.group != null &&
@@ -1742,9 +1956,9 @@ export default class User extends AbleObject<UserData> {
1742
1956
  this.$gatekeeperCache = undefined;
1743
1957
  await tnymph.commit(transaction);
1744
1958
 
1745
- if ((nymph.tilmeld as Tilmeld).User.current(true).$is(this)) {
1959
+ if (enforceTilmeld(nymph).User.current(true).$is(this)) {
1746
1960
  // Update the user in the session cache.
1747
- (nymph.tilmeld as Tilmeld).fillSession(this);
1961
+ enforceTilmeld(nymph).fillSession(this);
1748
1962
  }
1749
1963
  } else {
1750
1964
  await tnymph.rollback(transaction);
@@ -1770,13 +1984,13 @@ export default class User extends AbleObject<UserData> {
1770
1984
  }
1771
1985
 
1772
1986
  public async $delete() {
1773
- const tilmeld = this.$nymph.tilmeld as Tilmeld;
1987
+ const tilmeld = enforceTilmeld(this);
1774
1988
  if (!tilmeld.gatekeeper('tilmeld/admin')) {
1775
1989
  throw new BadDataError("You don't have the authority to delete users.");
1776
1990
  }
1777
1991
  if (
1778
1992
  !tilmeld.gatekeeper('system/admin') &&
1779
- this.$gatekeeper('system/admin')
1993
+ this.$data.abilities?.includes('system/admin')
1780
1994
  ) {
1781
1995
  throw new BadDataError(
1782
1996
  "You don't have the authority to delete system admins."
@@ -1790,7 +2004,9 @@ export default class User extends AbleObject<UserData> {
1790
2004
 
1791
2005
  public static on<T extends EventType>(
1792
2006
  event: T,
1793
- callback: T extends 'beforeRegister'
2007
+ callback: T extends 'checkUsername'
2008
+ ? TilmeldCheckUsernameCallback
2009
+ : T extends 'beforeRegister'
1794
2010
  ? TilmeldBeforeRegisterCallback
1795
2011
  : T extends 'afterRegister'
1796
2012
  ? TilmeldAfterRegisterCallback
@@ -1804,7 +2020,9 @@ export default class User extends AbleObject<UserData> {
1804
2020
  ? TilmeldAfterLogoutCallback
1805
2021
  : never
1806
2022
  ) {
1807
- const prop = (event + 'Callbacks') as T extends 'beforeRegister'
2023
+ const prop = (event + 'Callbacks') as T extends 'checkUsername'
2024
+ ? 'checkUsernameCallbacks'
2025
+ : T extends 'beforeRegister'
1808
2026
  ? 'beforeRegisterCallbacks'
1809
2027
  : T extends 'afterRegister'
1810
2028
  ? 'afterRegisterCallbacks'
@@ -1827,7 +2045,9 @@ export default class User extends AbleObject<UserData> {
1827
2045
 
1828
2046
  public static off<T extends EventType>(
1829
2047
  event: T,
1830
- callback: T extends 'beforeRegister'
2048
+ callback: T extends 'checkUsername'
2049
+ ? TilmeldCheckUsernameCallback
2050
+ : T extends 'beforeRegister'
1831
2051
  ? TilmeldBeforeRegisterCallback
1832
2052
  : T extends 'afterRegister'
1833
2053
  ? TilmeldAfterRegisterCallback
@@ -1841,7 +2061,9 @@ export default class User extends AbleObject<UserData> {
1841
2061
  ? TilmeldAfterLogoutCallback
1842
2062
  : never
1843
2063
  ) {
1844
- const prop = (event + 'Callbacks') as T extends 'beforeRegister'
2064
+ const prop = (event + 'Callbacks') as T extends 'checkUsername'
2065
+ ? 'checkUsernameCallbacks'
2066
+ : T extends 'beforeRegister'
1845
2067
  ? 'beforeRegisterCallbacks'
1846
2068
  : T extends 'afterRegister'
1847
2069
  ? 'afterRegisterCallbacks'
package/src/conf/d.ts CHANGED
@@ -217,6 +217,11 @@ export interface Config {
217
217
  * How long from current time, in seconds, the JWT token expires.
218
218
  */
219
219
  jwtExpire: number;
220
+ /**
221
+ * How long from current time, in seconds, the JWT token for a user switch
222
+ * expires.
223
+ */
224
+ jwtSwitchExpire: number;
220
225
  /**
221
226
  * How long, in seconds, before the JWT token expires to give the user a new
222
227
  * one.
@@ -225,21 +230,25 @@ export interface Config {
225
230
  /**
226
231
  * Function to build the JWT for user sessions.
227
232
  */
228
- jwtBuilder: (config: Config, user: User) => string;
233
+ jwtBuilder: (
234
+ config: Config,
235
+ user: User & UserData,
236
+ switchToken?: boolean
237
+ ) => string;
229
238
  /**
230
- * Function to verify that a JWT was signed with the secret key, vaildate its
231
- * data, validate the XSRF token, and extract the GUID.
239
+ * Function to verify that a JWT was signed with the secret key, validate its
240
+ * data, validate the XSRF token, and extract the GUID and dates.
232
241
  *
233
242
  * If no XSRF token is supplied, ignore it.
234
243
  *
235
- * Return false if the JWT is not valid, or an array of GUID and expire
236
- * timestamp otherwise.
244
+ * Return false if the JWT is not valid, or an object with GUID, issued date,
245
+ * and expire date otherwise.
237
246
  */
238
247
  jwtExtract: (
239
248
  config: Config,
240
249
  token: string,
241
250
  xsrfToken?: string
242
- ) => { guid: string; expire: Date } | null;
251
+ ) => { guid: string; issued: Date; expire: Date } | null;
243
252
  /**
244
253
  * The absolute path to the email template directory. Used by the default
245
254
  * email sender.