@nymphjs/tilmeld 1.0.0-beta.17 → 1.0.0-beta.19

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/Tilmeld.ts CHANGED
@@ -15,6 +15,7 @@ import { AccessControlError } from './errors';
15
15
  import Group from './Group';
16
16
  import User, { UserData } from './User';
17
17
  import { AccessControlData } from './Tilmeld.types';
18
+ import { enforceTilmeld } from './enforceTilmeld';
18
19
 
19
20
  /**
20
21
  * A user and group system for Nymph.js.
@@ -73,6 +74,11 @@ export default class Tilmeld implements TilmeldInterface {
73
74
  */
74
75
  private skipAuthenticate = false;
75
76
 
77
+ /**
78
+ * Used to avoid infinite loop.
79
+ */
80
+ private alreadyLoggedOutSwitch = false;
81
+
76
82
  /**
77
83
  * Create a new instance of Tilmeld.
78
84
  *
@@ -157,7 +163,7 @@ export default class Tilmeld implements TilmeldInterface {
157
163
  options: Options,
158
164
  selectors: FormattedSelector[]
159
165
  ) {
160
- const tilmeld = nymph.tilmeld as Tilmeld;
166
+ const tilmeld = enforceTilmeld(nymph);
161
167
  if (
162
168
  !options ||
163
169
  !('skipAc' in options) ||
@@ -197,7 +203,7 @@ export default class Tilmeld implements TilmeldInterface {
197
203
  nymph: Nymph,
198
204
  entity: EntityInterface
199
205
  ) {
200
- const tilmeld = nymph.tilmeld as Tilmeld;
206
+ const tilmeld = enforceTilmeld(nymph);
201
207
  if (
202
208
  typeof entity.$tilmeldDeleteSkipAC === 'function' &&
203
209
  entity.$tilmeldDeleteSkipAC()
@@ -233,7 +239,7 @@ export default class Tilmeld implements TilmeldInterface {
233
239
  nymph: Nymph,
234
240
  entity: EntityInterface & AccessControlData
235
241
  ) {
236
- const tilmeld = nymph.tilmeld as Tilmeld;
242
+ const tilmeld = enforceTilmeld(nymph);
237
243
  if (!entity) {
238
244
  return;
239
245
  }
@@ -344,11 +350,10 @@ export default class Tilmeld implements TilmeldInterface {
344
350
  }
345
351
 
346
352
  if (
347
- ((originalAc.user && !originalAc.user.$is(newAc.user)) ||
348
- (!originalAc.user &&
349
- newAc.user &&
350
- !newAc.user.$is(tilmeld.currentUser))) &&
351
- !tilmeld.gatekeeper('tilmeld/admin')
353
+ (originalAc.user && !originalAc.user.$is(newAc.user)) ||
354
+ (!originalAc.user &&
355
+ newAc.user &&
356
+ !newAc.user.$is(tilmeld.currentUser))
352
357
  ) {
353
358
  throw new AccessControlError(
354
359
  'No permission to assign to another user.'
@@ -361,8 +366,7 @@ export default class Tilmeld implements TilmeldInterface {
361
366
  !(
362
367
  newAc.group.$is(tilmeld.currentUser?.group) ||
363
368
  newAc.group.$inArray(tilmeld.currentUser?.groups ?? [])
364
- ) &&
365
- !tilmeld.gatekeeper('tilmeld/admin')
369
+ )
366
370
  ) {
367
371
  throw new AccessControlError(
368
372
  'No permission to assign to another group.'
@@ -400,7 +404,7 @@ export default class Tilmeld implements TilmeldInterface {
400
404
  nymph: Nymph,
401
405
  entity: EntityInterface & AccessControlData
402
406
  ) {
403
- const tilmeld = nymph.tilmeld as Tilmeld;
407
+ const tilmeld = enforceTilmeld(nymph);
404
408
  const user = tilmeld.currentUser;
405
409
  if (
406
410
  user != null &&
@@ -532,14 +536,6 @@ export default class Tilmeld implements TilmeldInterface {
532
536
  ) {
533
537
  const user = this.currentUser;
534
538
 
535
- if (
536
- user != null &&
537
- (user.abilities?.indexOf('system/admin') ?? -1) !== -1
538
- ) {
539
- // The user is a system admin, so they can see everything.
540
- return;
541
- }
542
-
543
539
  if (options == null) {
544
540
  throw new Error('No options in argument.');
545
541
  } else if (
@@ -671,7 +667,7 @@ export default class Tilmeld implements TilmeldInterface {
671
667
  * - FULL_ACCESS, the user has read, write, and delete access to the entity,
672
668
  * as well as being able to manage its access controls and ownership.
673
669
  *
674
- * These properties defaults to:
670
+ * These properties default to:
675
671
  *
676
672
  * - acUser = TilmeldAccessLevels.FULL_ACCESS
677
673
  * - acGroup = TilmeldAccessLevels.READ_ACCESS
@@ -686,9 +682,7 @@ export default class Tilmeld implements TilmeldInterface {
686
682
  * The following conditions will result in different checks, which determine
687
683
  * whether the check passes:
688
684
  *
689
- * - The user has the "system/admin" ability. (Always true.)
690
685
  * - It is a user or group. (True for READ_ACCESS or Tilmeld admins.)
691
- * - The entity has no "user" and no "group". (Always true.)
692
686
  * - No user is logged in. (Check other AC.)
693
687
  * - The entity is the user. (Always true.)
694
688
  * - It is the user's primary group. (True for READ_ACCESS.)
@@ -729,30 +723,22 @@ export default class Tilmeld implements TilmeldInterface {
729
723
  userOrEmpty = user;
730
724
  }
731
725
 
732
- if (userOrEmpty.$gatekeeper('system/admin')) {
733
- return true;
734
- }
735
-
736
726
  // Users and groups are always readable. Editable by Tilmeld admins.
737
727
  if (
738
728
  (entity instanceof User || entity instanceof Group) &&
739
729
  (type === TilmeldAccessLevels.READ_ACCESS ||
740
- userOrEmpty.$gatekeeper('tilmeld/admin'))
730
+ userOrEmpty.abilities?.includes('tilmeld/admin') ||
731
+ userOrEmpty.abilities?.includes('system/admin'))
741
732
  ) {
742
733
  return true;
743
734
  }
744
735
 
745
- // Entities with no owners are always editable.
746
- if (entity.user == null && entity.group == null) {
747
- return true;
748
- }
749
-
750
736
  // Load access control, since we need it now...
751
737
  const acUser = entity.acUser ?? TilmeldAccessLevels.FULL_ACCESS;
752
738
  const acGroup = entity.acGroup ?? TilmeldAccessLevels.READ_ACCESS;
753
739
  const acOther = entity.acOther ?? TilmeldAccessLevels.NO_ACCESS;
754
740
 
755
- if (userOrNull === null) {
741
+ if (userOrNull == null) {
756
742
  return acOther >= type;
757
743
  }
758
744
 
@@ -862,7 +848,7 @@ export default class Tilmeld implements TilmeldInterface {
862
848
  userOrEmpty = user;
863
849
  }
864
850
 
865
- if (userOrEmpty.$gatekeeper('system/admin')) {
851
+ if (userOrEmpty.abilities?.includes('system/admin')) {
866
852
  return true;
867
853
  }
868
854
 
@@ -950,7 +936,7 @@ export default class Tilmeld implements TilmeldInterface {
950
936
  guid: guid,
951
937
  }
952
938
  );
953
- if (!user || !user.guid || !user.enabled) {
939
+ if (!user || !user.guid) {
954
940
  return null;
955
941
  }
956
942
 
@@ -958,7 +944,8 @@ export default class Tilmeld implements TilmeldInterface {
958
944
  }
959
945
 
960
946
  /**
961
- * Check for a TILMELDAUTH token, and, if set, authenticate from it.
947
+ * Check for TILMELDAUTH and TILMELDSWITCH tokens, and, if set, authenticate
948
+ * from it/them.
962
949
  *
963
950
  * You can also call this function after setting `response.locals.user` to the
964
951
  * user you want to authenticate. You *should* check for `user.enabled` before
@@ -994,24 +981,35 @@ export default class Tilmeld implements TilmeldInterface {
994
981
  // to provide the auth token.
995
982
  let fromAuthHeader = false;
996
983
  let authToken: string;
984
+ let switchToken: string | null;
997
985
  if (this.request.header('x-tilmeldauth') != null) {
998
986
  fromAuthHeader = true;
999
987
  authToken = this.request.header('x-tilmeldauth') as string;
988
+ switchToken =
989
+ (this.request.header('x-tilmeldswitch') as string) ||
990
+ 'TILMELDSWITCH' in cookies
991
+ ? cookies.TILMELDSWITCH
992
+ : null;
1000
993
  } else if ('TILMELDAUTH' in cookies) {
1001
994
  fromAuthHeader = false;
1002
995
  authToken = cookies.TILMELDAUTH;
996
+ switchToken = 'TILMELDSWITCH' in cookies ? cookies.TILMELDSWITCH : null;
1003
997
  } else {
1004
998
  return false;
1005
999
  }
1006
1000
 
1007
- let extract: { guid: string; expire: Date } | null;
1001
+ let authExtract: { guid: string; expire: Date } | null;
1002
+ let switchExtract: { guid: string; expire: Date } | null;
1008
1003
  if (
1009
1004
  skipXsrfToken ||
1010
1005
  this.request.originalUrl.startsWith(this.config.setupPath)
1011
1006
  ) {
1012
1007
  // The request is for the setup app, or we were told to skip the XSRF
1013
1008
  // check, so don't check for the XSRF token.
1014
- extract = this.config.jwtExtract(this.config, authToken);
1009
+ authExtract = this.config.jwtExtract(this.config, authToken);
1010
+ switchExtract = switchToken
1011
+ ? this.config.jwtExtract(this.config, switchToken)
1012
+ : null;
1015
1013
  } else {
1016
1014
  // The request is for something else, so check for a valid XSRF token,
1017
1015
  // unless the auth token is provided by a header (instead of a cookie).
@@ -1020,21 +1018,44 @@ export default class Tilmeld implements TilmeldInterface {
1020
1018
  return false;
1021
1019
  }
1022
1020
 
1023
- extract = this.config.jwtExtract(this.config, authToken, xsrfToken);
1021
+ authExtract = this.config.jwtExtract(this.config, authToken, xsrfToken);
1022
+ switchExtract = switchToken
1023
+ ? this.config.jwtExtract(this.config, switchToken)
1024
+ : null;
1024
1025
  }
1025
1026
 
1026
- if (extract == null) {
1027
+ if (authExtract == null) {
1027
1028
  this.logout();
1028
1029
  return false;
1029
1030
  }
1030
- const { guid, expire } = extract;
1031
+ const { guid, expire } = authExtract;
1032
+ const { guid: switchGuid, expire: switchExpire } = switchExtract || {
1033
+ guid: null,
1034
+ expire: null,
1035
+ };
1031
1036
 
1032
1037
  const user = this.User.factorySync(guid);
1033
- if (user.guid == null || !user.enabled) {
1038
+ if (user.guid == null || !user.enabled || expire.valueOf() <= Date.now()) {
1034
1039
  this.logout();
1035
1040
  return false;
1036
1041
  }
1037
1042
 
1043
+ if (switchGuid && switchExpire && switchExpire.valueOf() > Date.now()) {
1044
+ // Load the switch user.
1045
+ const switchUser = this.User.factorySync(switchGuid);
1046
+ if (switchUser.guid == null) {
1047
+ this.logoutSwitch();
1048
+ } else {
1049
+ this.fillSession(switchUser);
1050
+
1051
+ if (this.response) {
1052
+ this.response.locals.user = switchUser;
1053
+ }
1054
+
1055
+ return true;
1056
+ }
1057
+ }
1058
+
1038
1059
  if (expire.valueOf() < Date.now() + this.config.jwtRenew * 1000) {
1039
1060
  // If the user is less than renew time from needing a new token, give them
1040
1061
  // a new one.
@@ -1094,12 +1115,67 @@ export default class Tilmeld implements TilmeldInterface {
1094
1115
  return false;
1095
1116
  }
1096
1117
 
1118
+ /**
1119
+ * Adds a switch auth token for the given user.
1120
+ *
1121
+ * This effectively logs the current user in to the system as the given user.
1122
+ *
1123
+ * @param user The user.
1124
+ * @param sendAuthHeader Send the auth token as a custom header.
1125
+ * @param sendCookie Send the auth token as a cookie.
1126
+ * @returns True on success, false on failure.
1127
+ */
1128
+ public loginSwitch(
1129
+ user: User & UserData,
1130
+ sendAuthHeader: boolean,
1131
+ sendCookie = true
1132
+ ) {
1133
+ if (user.guid != null) {
1134
+ if (this.response) {
1135
+ if (!this.response.headersSent) {
1136
+ const token = this.config.jwtBuilder(this.config, user, true);
1137
+
1138
+ if (sendCookie) {
1139
+ const appUrl = new URL(this.config.appUrl);
1140
+ this.response.cookie('TILMELDSWITCH', token, {
1141
+ domain: this.config.cookieDomain,
1142
+ path: this.config.cookiePath,
1143
+ maxAge: this.config.jwtSwitchExpire * 1000,
1144
+ secure: appUrl.protocol === 'https',
1145
+ httpOnly: false, // Allow JS access (for PubSub auth token).
1146
+ sameSite: appUrl.protocol === 'https' ? 'lax' : 'strict',
1147
+ });
1148
+ }
1149
+
1150
+ if (sendAuthHeader) {
1151
+ this.response.set('X-TILMELDSWITCH', token);
1152
+ }
1153
+ }
1154
+
1155
+ this.response.locals.user = user;
1156
+ }
1157
+
1158
+ this.fillSession(user);
1159
+ return true;
1160
+ }
1161
+ return false;
1162
+ }
1163
+
1097
1164
  /**
1098
1165
  * Logs the current user out of the system.
1099
1166
  *
1100
1167
  * @param clearCookie Clear the auth cookie. (Also send a header.)
1101
1168
  */
1102
1169
  public logout(clearCookie = true) {
1170
+ if (this.request && !this.alreadyLoggedOutSwitch) {
1171
+ const cookies = this.request.cookies ?? {};
1172
+
1173
+ if ('TILMELDSWITCH' in cookies) {
1174
+ this.alreadyLoggedOutSwitch = true;
1175
+ return this.logoutSwitch(clearCookie);
1176
+ }
1177
+ }
1178
+
1103
1179
  this.clearSession();
1104
1180
  if (this.response) {
1105
1181
  if (clearCookie && !this.response.headersSent) {
@@ -1113,6 +1189,27 @@ export default class Tilmeld implements TilmeldInterface {
1113
1189
  }
1114
1190
  }
1115
1191
 
1192
+ /**
1193
+ * Clears the switch user out of the system.
1194
+ *
1195
+ * @param clearCookie Clear the auth cookie. (Also send a header.)
1196
+ */
1197
+ public logoutSwitch(clearCookie = true) {
1198
+ this.clearSession();
1199
+ if (this.response) {
1200
+ if (clearCookie && !this.response.headersSent) {
1201
+ this.response.clearCookie('TILMELDSWITCH', {
1202
+ domain: this.config.cookieDomain,
1203
+ path: this.config.cookiePath,
1204
+ });
1205
+ this.response.set('X-TILMELDSWITCH', '');
1206
+ }
1207
+ this.response.locals.user = null;
1208
+
1209
+ this.authenticate();
1210
+ }
1211
+ }
1212
+
1116
1213
  // /**
1117
1214
  // * Sort an array of groups hierarchically.
1118
1215
  // *