@nymphjs/tilmeld 1.0.0-beta.17 → 1.0.0-beta.18
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 +13 -0
- package/README.md +13 -0
- package/dist/Group.js +48 -22
- package/dist/Group.js.map +1 -1
- package/dist/Tilmeld.d.ts +3 -0
- package/dist/Tilmeld.js +99 -29
- package/dist/Tilmeld.js.map +1 -1
- package/dist/User.d.ts +8 -0
- package/dist/User.js +128 -42
- package/dist/User.js.map +1 -1
- package/dist/conf/d.d.ts +2 -1
- package/dist/conf/defaults.js +3 -2
- package/dist/conf/defaults.js.map +1 -1
- package/dist/enforceTilmeld.d.ts +3 -0
- package/dist/enforceTilmeld.js +22 -0
- package/dist/enforceTilmeld.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/src/Group.ts +70 -23
- package/src/Tilmeld.ts +139 -42
- package/src/User.ts +191 -42
- package/src/conf/d.ts +10 -1
- package/src/conf/defaults.ts +3 -2
- package/src/enforceTilmeld.ts +20 -0
- package/src/index.ts +2 -0
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
|
|
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
|
|
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
|
|
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
|
-
(
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
1027
|
+
if (authExtract == null) {
|
|
1027
1028
|
this.logout();
|
|
1028
1029
|
return false;
|
|
1029
1030
|
}
|
|
1030
|
-
const { guid, expire } =
|
|
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
|
// *
|