@nymphjs/tilmeld 1.0.0-alpha.9 → 1.0.0-beta.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/CHANGELOG.md +168 -0
- package/dist/Group.d.ts +1 -1
- package/dist/Group.js +24 -23
- package/dist/Group.js.map +1 -1
- package/dist/Tilmeld.js +100 -37
- package/dist/Tilmeld.js.map +1 -1
- package/dist/Tilmeld.types.d.ts +1 -1
- package/dist/User.d.ts +41 -3
- package/dist/User.js +135 -34
- package/dist/User.js.map +1 -1
- package/dist/conf/d.d.ts +2 -1
- package/dist/conf/defaults.js +13 -19
- package/dist/conf/defaults.js.map +1 -1
- package/dist/errors/index.js +5 -1
- package/dist/errors/index.js.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/tilmeldJoiProps.d.ts +6 -6
- package/jest.config.js +1 -1
- package/package.json +20 -22
- package/src/Group.ts +2 -1
- package/src/Tilmeld.ts +136 -43
- package/src/User.ts +229 -16
- package/src/conf/d.ts +13 -1
- package/src/conf/defaults.ts +14 -18
package/src/Tilmeld.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
TilmeldAccessLevels,
|
|
8
8
|
} from '@nymphjs/nymph';
|
|
9
9
|
import { Request, Response } from 'express';
|
|
10
|
+
import { xor } from 'lodash';
|
|
10
11
|
|
|
11
12
|
import { Config, ConfigDefaults as defaults } from './conf';
|
|
12
13
|
import { AccessControlError } from './errors';
|
|
@@ -201,7 +202,7 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
201
202
|
return;
|
|
202
203
|
}
|
|
203
204
|
// Test for permissions.
|
|
204
|
-
if (tilmeld.checkPermissions(entity, TilmeldAccessLevels.FULL_ACCESS)) {
|
|
205
|
+
if (!tilmeld.checkPermissions(entity, TilmeldAccessLevels.FULL_ACCESS)) {
|
|
205
206
|
throw new AccessControlError('No permission to delete.');
|
|
206
207
|
}
|
|
207
208
|
};
|
|
@@ -245,15 +246,15 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
245
246
|
// allowing a change to ac properties.
|
|
246
247
|
|
|
247
248
|
const originalAc = entity.$getOriginalAcValues();
|
|
248
|
-
const newAc = {
|
|
249
|
-
user: entity.user ??
|
|
250
|
-
group: entity.group ??
|
|
251
|
-
acUser: entity.acUser ??
|
|
252
|
-
acGroup: entity.acGroup ??
|
|
253
|
-
acOther: entity.acOther ??
|
|
254
|
-
acRead: entity.acRead ??
|
|
255
|
-
acWrite: entity.acWrite ??
|
|
256
|
-
acFull: entity.acFull ??
|
|
249
|
+
const newAc: AccessControlData = {
|
|
250
|
+
user: entity.user ?? undefined,
|
|
251
|
+
group: entity.group ?? undefined,
|
|
252
|
+
acUser: entity.acUser ?? undefined,
|
|
253
|
+
acGroup: entity.acGroup ?? undefined,
|
|
254
|
+
acOther: entity.acOther ?? undefined,
|
|
255
|
+
acRead: entity.acRead ?? undefined,
|
|
256
|
+
acWrite: entity.acWrite ?? undefined,
|
|
257
|
+
acFull: entity.acFull ?? undefined,
|
|
257
258
|
};
|
|
258
259
|
|
|
259
260
|
const setAcProperties = (acValues: { [k: string]: any }) => {
|
|
@@ -269,10 +270,102 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
269
270
|
|
|
270
271
|
// Restore original AC properties and check permissions.
|
|
271
272
|
setAcProperties(originalAc);
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
273
|
+
|
|
274
|
+
function areAcPropertiesChanged(
|
|
275
|
+
a: AccessControlData,
|
|
276
|
+
b: AccessControlData
|
|
277
|
+
) {
|
|
278
|
+
for (const name of ['user', 'group'] as ('user' | 'group')[]) {
|
|
279
|
+
const aVal = a[name];
|
|
280
|
+
const bVal = b[name];
|
|
281
|
+
if (aVal == null && bVal == null) {
|
|
282
|
+
continue;
|
|
283
|
+
} else if (aVal == null || bVal == null) {
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
286
|
+
if (!aVal.$is(bVal)) {
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
for (const name of ['acUser', 'acGroup', 'acOther'] as (
|
|
291
|
+
| 'acUser'
|
|
292
|
+
| 'acGroup'
|
|
293
|
+
| 'acOther'
|
|
294
|
+
)[]) {
|
|
295
|
+
const aVal = a[name];
|
|
296
|
+
const bVal = b[name];
|
|
297
|
+
if (aVal == null && bVal == null) {
|
|
298
|
+
continue;
|
|
299
|
+
} else if (aVal == null || bVal == null) {
|
|
300
|
+
return true;
|
|
301
|
+
}
|
|
302
|
+
if (aVal !== bVal) {
|
|
303
|
+
return true;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
for (const name of ['acRead', 'acWrite', 'acFull'] as (
|
|
307
|
+
| 'acRead'
|
|
308
|
+
| 'acWrite'
|
|
309
|
+
| 'acFull'
|
|
310
|
+
)[]) {
|
|
311
|
+
const aVal = a[name];
|
|
312
|
+
const bVal = b[name];
|
|
313
|
+
if (aVal == null && bVal == null) {
|
|
314
|
+
continue;
|
|
315
|
+
} else if (aVal == null || bVal == null) {
|
|
316
|
+
return true;
|
|
317
|
+
}
|
|
318
|
+
if (aVal.length !== bVal.length) {
|
|
319
|
+
return true;
|
|
320
|
+
}
|
|
321
|
+
if (
|
|
322
|
+
xor(
|
|
323
|
+
aVal.map((entity) => entity.guid),
|
|
324
|
+
bVal.map((entity) => entity.guid)
|
|
325
|
+
).length
|
|
326
|
+
) {
|
|
327
|
+
return true;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return false;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (areAcPropertiesChanged(originalAc, newAc)) {
|
|
334
|
+
if (
|
|
335
|
+
!tilmeld.checkPermissions(entity, TilmeldAccessLevels.FULL_ACCESS)
|
|
336
|
+
) {
|
|
337
|
+
// Only allow changes to AC properties if the user has full access.
|
|
338
|
+
throw new AccessControlError(
|
|
339
|
+
'No permission to change access control properties.'
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (
|
|
344
|
+
((originalAc.user && !originalAc.user.$is(newAc.user)) ||
|
|
345
|
+
(!originalAc.user &&
|
|
346
|
+
newAc.user &&
|
|
347
|
+
!newAc.user.$is(tilmeld.currentUser))) &&
|
|
348
|
+
!tilmeld.gatekeeper('tilmeld/admin')
|
|
349
|
+
) {
|
|
350
|
+
throw new AccessControlError(
|
|
351
|
+
'No permission to assign to another user.'
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (
|
|
356
|
+
newAc.group &&
|
|
357
|
+
!newAc.group.$is(originalAc.group) &&
|
|
358
|
+
!(
|
|
359
|
+
newAc.group.$is(tilmeld.currentUser?.group) ||
|
|
360
|
+
newAc.group.$inArray(tilmeld.currentUser?.groups ?? [])
|
|
361
|
+
) &&
|
|
362
|
+
!tilmeld.gatekeeper('tilmeld/admin')
|
|
363
|
+
) {
|
|
364
|
+
throw new AccessControlError(
|
|
365
|
+
'No permission to assign to another group.'
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
|
|
276
369
|
setAcProperties(newAc);
|
|
277
370
|
}
|
|
278
371
|
}
|
|
@@ -455,7 +548,7 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
455
548
|
return;
|
|
456
549
|
}
|
|
457
550
|
|
|
458
|
-
if (user
|
|
551
|
+
if (user == null || user.guid == null) {
|
|
459
552
|
selectors.push({
|
|
460
553
|
type: '|',
|
|
461
554
|
// Other access control is sufficient.
|
|
@@ -464,7 +557,7 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
464
557
|
selector: [
|
|
465
558
|
{
|
|
466
559
|
type: '&',
|
|
467
|
-
'!defined': ['user', 'group'],
|
|
560
|
+
'!defined': [['user', 'group']],
|
|
468
561
|
},
|
|
469
562
|
],
|
|
470
563
|
});
|
|
@@ -472,56 +565,43 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
472
565
|
const subSelectors: FormattedSelector[] = [
|
|
473
566
|
{
|
|
474
567
|
type: '&',
|
|
475
|
-
'!defined': ['user', 'group'],
|
|
568
|
+
'!defined': [['user', 'group']],
|
|
476
569
|
},
|
|
477
570
|
// It is owned by the user.
|
|
478
571
|
{
|
|
479
572
|
type: '&',
|
|
480
|
-
ref: [['user', user]],
|
|
573
|
+
ref: [['user', user.guid]],
|
|
481
574
|
gte: [['acUser', TilmeldAccessLevels.READ_ACCESS]],
|
|
482
575
|
},
|
|
483
576
|
];
|
|
484
|
-
const selector: FormattedSelector = {
|
|
485
|
-
type: '|',
|
|
486
|
-
// Other access control is sufficient.
|
|
487
|
-
gte: [['acOther', TilmeldAccessLevels.READ_ACCESS]],
|
|
488
|
-
// The user and group are not set.
|
|
489
|
-
selector: subSelectors,
|
|
490
|
-
// The user is listed in acRead, acWrite, or acFull.
|
|
491
|
-
ref: [
|
|
492
|
-
['acRead', user],
|
|
493
|
-
['acWrite', user],
|
|
494
|
-
['acFull', user],
|
|
495
|
-
],
|
|
496
|
-
};
|
|
497
577
|
const groupRefs: FormattedSelector['ref'] = [];
|
|
498
578
|
const acRefs: FormattedSelector['ref'] = [];
|
|
499
579
|
if (user.group != null && user.group.guid != null) {
|
|
500
580
|
// It belongs to the user's primary group.
|
|
501
|
-
groupRefs.push(['group', user.group]);
|
|
581
|
+
groupRefs.push(['group', user.group.guid]);
|
|
502
582
|
// User's primary group is listed in acRead, acWrite, or acFull.
|
|
503
|
-
acRefs.push(['acRead', user.group]);
|
|
504
|
-
acRefs.push(['acWrite', user.group]);
|
|
505
|
-
acRefs.push(['acFull', user.group]);
|
|
583
|
+
acRefs.push(['acRead', user.group.guid]);
|
|
584
|
+
acRefs.push(['acWrite', user.group.guid]);
|
|
585
|
+
acRefs.push(['acFull', user.group.guid]);
|
|
506
586
|
}
|
|
507
587
|
for (let curSecondaryGroup of user.groups ?? []) {
|
|
508
588
|
if (curSecondaryGroup != null && curSecondaryGroup.guid != null) {
|
|
509
589
|
// It belongs to the user's secondary group.
|
|
510
|
-
groupRefs.push(['group', curSecondaryGroup]);
|
|
590
|
+
groupRefs.push(['group', curSecondaryGroup.guid]);
|
|
511
591
|
// User's secondary group is listed in acRead, acWrite, or acFull.
|
|
512
|
-
acRefs.push(['acRead', curSecondaryGroup]);
|
|
513
|
-
acRefs.push(['acWrite', curSecondaryGroup]);
|
|
514
|
-
acRefs.push(['acFull', curSecondaryGroup]);
|
|
592
|
+
acRefs.push(['acRead', curSecondaryGroup.guid]);
|
|
593
|
+
acRefs.push(['acWrite', curSecondaryGroup.guid]);
|
|
594
|
+
acRefs.push(['acFull', curSecondaryGroup.guid]);
|
|
515
595
|
}
|
|
516
596
|
}
|
|
517
597
|
for (let curDescendantGroup of user.$getDescendantGroupsSync()) {
|
|
518
598
|
if (curDescendantGroup != null && curDescendantGroup.guid != null) {
|
|
519
599
|
// It belongs to the user's secondary group.
|
|
520
|
-
groupRefs.push(['group', curDescendantGroup]);
|
|
600
|
+
groupRefs.push(['group', curDescendantGroup.guid]);
|
|
521
601
|
// User's secondary group is listed in acRead, acWrite, or acFull.
|
|
522
|
-
acRefs.push(['acRead', curDescendantGroup]);
|
|
523
|
-
acRefs.push(['acWrite', curDescendantGroup]);
|
|
524
|
-
acRefs.push(['acFull', curDescendantGroup]);
|
|
602
|
+
acRefs.push(['acRead', curDescendantGroup.guid]);
|
|
603
|
+
acRefs.push(['acWrite', curDescendantGroup.guid]);
|
|
604
|
+
acRefs.push(['acFull', curDescendantGroup.guid]);
|
|
525
605
|
}
|
|
526
606
|
}
|
|
527
607
|
// All the group refs.
|
|
@@ -544,6 +624,19 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
544
624
|
ref: acRefs,
|
|
545
625
|
});
|
|
546
626
|
}
|
|
627
|
+
const selector: FormattedSelector = {
|
|
628
|
+
type: '|',
|
|
629
|
+
// Other access control is sufficient.
|
|
630
|
+
gte: [['acOther', TilmeldAccessLevels.READ_ACCESS]],
|
|
631
|
+
// The user and group are not set.
|
|
632
|
+
selector: subSelectors,
|
|
633
|
+
// The user is listed in acRead, acWrite, or acFull.
|
|
634
|
+
ref: [
|
|
635
|
+
['acRead', user.guid],
|
|
636
|
+
['acWrite', user.guid],
|
|
637
|
+
['acFull', user.guid],
|
|
638
|
+
],
|
|
639
|
+
};
|
|
547
640
|
selectors.push(selector);
|
|
548
641
|
}
|
|
549
642
|
}
|
package/src/User.ts
CHANGED
|
@@ -7,12 +7,11 @@ import {
|
|
|
7
7
|
} from '@nymphjs/nymph';
|
|
8
8
|
import { EmailOptions } from 'email-templates';
|
|
9
9
|
import strtotime from 'locutus/php/datetime/strtotime';
|
|
10
|
-
import { nanoid, customAlphabet } from 'nanoid';
|
|
11
|
-
import { nolookalikesSafe } from 'nanoid-dictionary';
|
|
12
10
|
import Base64 from 'crypto-js/enc-base64';
|
|
13
11
|
import sha256 from 'crypto-js/sha256';
|
|
14
12
|
import md5 from 'crypto-js/md5';
|
|
15
13
|
import { difference } from 'lodash';
|
|
14
|
+
import { humanSecret, nanoid } from '@nymphjs/guid';
|
|
16
15
|
|
|
17
16
|
import type Tilmeld from './Tilmeld';
|
|
18
17
|
import AbleObject from './AbleObject';
|
|
@@ -24,6 +23,44 @@ import {
|
|
|
24
23
|
EmailChangeRateLimitExceededError,
|
|
25
24
|
} from './errors';
|
|
26
25
|
|
|
26
|
+
export type EventType =
|
|
27
|
+
| 'beforeRegister'
|
|
28
|
+
| 'afterRegister'
|
|
29
|
+
| 'beforeLogin'
|
|
30
|
+
| 'afterLogin'
|
|
31
|
+
| 'beforeLogout'
|
|
32
|
+
| 'afterLogout';
|
|
33
|
+
/**
|
|
34
|
+
* Theses are run before the user data checks, so the only checks before are
|
|
35
|
+
* whether registration is allowed and whether the user is already registered.
|
|
36
|
+
*/
|
|
37
|
+
export type TilmeldBeforeRegisterCallback = (
|
|
38
|
+
user: User & UserData,
|
|
39
|
+
data: { password: string; additionalData?: { [k: string]: any } }
|
|
40
|
+
) => Promise<void> | void;
|
|
41
|
+
export type TilmeldAfterRegisterCallback = (
|
|
42
|
+
user: User & UserData,
|
|
43
|
+
result: { loggedin: boolean; message: string }
|
|
44
|
+
) => Promise<void> | void;
|
|
45
|
+
/**
|
|
46
|
+
* These are run after the authentication checks, but before the login action.
|
|
47
|
+
*/
|
|
48
|
+
export type TilmeldBeforeLoginCallback = (
|
|
49
|
+
user: User & UserData,
|
|
50
|
+
data: {
|
|
51
|
+
username: string;
|
|
52
|
+
password: string;
|
|
53
|
+
additionalData?: { [k: string]: any };
|
|
54
|
+
}
|
|
55
|
+
) => void;
|
|
56
|
+
/**
|
|
57
|
+
* This is run before the transaction is committed, and you can perform
|
|
58
|
+
* additional functions on the transaction, which is available in `user.$nymph`.
|
|
59
|
+
*/
|
|
60
|
+
export type TilmeldAfterLoginCallback = (user: User & UserData) => void;
|
|
61
|
+
export type TilmeldBeforeLogoutCallback = (user: User & UserData) => void;
|
|
62
|
+
export type TilmeldAfterLogoutCallback = (user: User & UserData) => void;
|
|
63
|
+
|
|
27
64
|
export type UserData = {
|
|
28
65
|
/**
|
|
29
66
|
* The abilities granted to the user.
|
|
@@ -142,6 +179,12 @@ export default class User extends AbleObject<UserData> {
|
|
|
142
179
|
static tilmeld: Tilmeld;
|
|
143
180
|
static ETYPE = 'tilmeld_user';
|
|
144
181
|
static class = 'User';
|
|
182
|
+
private static beforeRegisterCallbacks: TilmeldBeforeRegisterCallback[] = [];
|
|
183
|
+
private static afterRegisterCallbacks: TilmeldAfterRegisterCallback[] = [];
|
|
184
|
+
private static beforeLoginCallbacks: TilmeldBeforeLoginCallback[] = [];
|
|
185
|
+
private static afterLoginCallbacks: TilmeldAfterLoginCallback[] = [];
|
|
186
|
+
private static beforeLogoutCallbacks: TilmeldBeforeLogoutCallback[] = [];
|
|
187
|
+
private static afterLogoutCallbacks: TilmeldAfterLogoutCallback[] = [];
|
|
145
188
|
|
|
146
189
|
private static DEFAULT_CLIENT_ENABLED_METHODS = [
|
|
147
190
|
'$checkUsername',
|
|
@@ -325,7 +368,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
325
368
|
}
|
|
326
369
|
|
|
327
370
|
// Create a unique secret.
|
|
328
|
-
getUser.recoverSecret =
|
|
371
|
+
getUser.recoverSecret = humanSecret();
|
|
329
372
|
getUser.recoverSecretDate = Date.now();
|
|
330
373
|
if (!(await getUser.$saveSkipAC())) {
|
|
331
374
|
return { result: false, message: "Couldn't save recovery secret." };
|
|
@@ -429,7 +472,11 @@ export default class User extends AbleObject<UserData> {
|
|
|
429
472
|
};
|
|
430
473
|
}
|
|
431
474
|
|
|
432
|
-
public static async loginUser(data: {
|
|
475
|
+
public static async loginUser(data: {
|
|
476
|
+
username: string;
|
|
477
|
+
password: string;
|
|
478
|
+
additionalData?: { [k: string]: any };
|
|
479
|
+
}) {
|
|
433
480
|
if (!('username' in data) || !data.username.length) {
|
|
434
481
|
return { result: false, message: 'Incorrect login/password.' };
|
|
435
482
|
}
|
|
@@ -443,7 +490,11 @@ export default class User extends AbleObject<UserData> {
|
|
|
443
490
|
return result;
|
|
444
491
|
}
|
|
445
492
|
|
|
446
|
-
public $login(data: {
|
|
493
|
+
public $login(data: {
|
|
494
|
+
username: string;
|
|
495
|
+
password: string;
|
|
496
|
+
additionalData?: { [k: string]: any };
|
|
497
|
+
}) {
|
|
447
498
|
const tilmeld = this.$nymph.tilmeld as Tilmeld;
|
|
448
499
|
if (this.guid == null) {
|
|
449
500
|
return { result: false, message: 'Incorrect login/password.' };
|
|
@@ -458,11 +509,32 @@ export default class User extends AbleObject<UserData> {
|
|
|
458
509
|
return { result: false, message: 'Incorrect login/password.' };
|
|
459
510
|
}
|
|
460
511
|
|
|
512
|
+
try {
|
|
513
|
+
for (let callback of (this.constructor as typeof User)
|
|
514
|
+
.beforeLoginCallbacks) {
|
|
515
|
+
if (callback) {
|
|
516
|
+
callback(this, data);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
} catch (e: any) {
|
|
520
|
+
return {
|
|
521
|
+
result: false,
|
|
522
|
+
message: e.message,
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
|
|
461
526
|
// Authentication was successful, attempt to login.
|
|
462
527
|
if (!tilmeld.login(this, true)) {
|
|
463
528
|
return { result: false, message: 'Incorrect login/password.' };
|
|
464
529
|
}
|
|
465
530
|
|
|
531
|
+
for (let callback of (this.constructor as typeof User)
|
|
532
|
+
.afterLoginCallbacks) {
|
|
533
|
+
if (callback) {
|
|
534
|
+
callback(this);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
466
538
|
// Login was successful.
|
|
467
539
|
return { result: true, message: 'You are logged in.' };
|
|
468
540
|
}
|
|
@@ -473,7 +545,30 @@ export default class User extends AbleObject<UserData> {
|
|
|
473
545
|
*/
|
|
474
546
|
public $logout() {
|
|
475
547
|
const tilmeld = this.$nymph.tilmeld as Tilmeld;
|
|
548
|
+
|
|
549
|
+
try {
|
|
550
|
+
for (let callback of (this.constructor as typeof User)
|
|
551
|
+
.beforeLogoutCallbacks) {
|
|
552
|
+
if (callback) {
|
|
553
|
+
callback(this);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
} catch (e: any) {
|
|
557
|
+
return {
|
|
558
|
+
result: false,
|
|
559
|
+
message: e.message,
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
|
|
476
563
|
tilmeld.logout();
|
|
564
|
+
|
|
565
|
+
for (let callback of (this.constructor as typeof User)
|
|
566
|
+
.afterLogoutCallbacks) {
|
|
567
|
+
if (callback) {
|
|
568
|
+
callback(this);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
477
572
|
return { result: true, message: 'You have been logged out.' };
|
|
478
573
|
}
|
|
479
574
|
|
|
@@ -1123,8 +1218,9 @@ export default class User extends AbleObject<UserData> {
|
|
|
1123
1218
|
|
|
1124
1219
|
public async $register(data: {
|
|
1125
1220
|
password: string;
|
|
1221
|
+
additionalData?: { [k: string]: any };
|
|
1126
1222
|
}): Promise<{ result: boolean; loggedin: boolean; message: string }> {
|
|
1127
|
-
|
|
1223
|
+
let tilmeld = this.$nymph.tilmeld as Tilmeld;
|
|
1128
1224
|
if (!tilmeld.config.allowRegistration) {
|
|
1129
1225
|
return {
|
|
1130
1226
|
result: false,
|
|
@@ -1139,6 +1235,22 @@ export default class User extends AbleObject<UserData> {
|
|
|
1139
1235
|
message: 'This is already a registered user.',
|
|
1140
1236
|
};
|
|
1141
1237
|
}
|
|
1238
|
+
|
|
1239
|
+
try {
|
|
1240
|
+
for (let callback of (this.constructor as typeof User)
|
|
1241
|
+
.beforeRegisterCallbacks) {
|
|
1242
|
+
if (callback) {
|
|
1243
|
+
await callback(this, data);
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
} catch (e: any) {
|
|
1247
|
+
return {
|
|
1248
|
+
result: false,
|
|
1249
|
+
loggedin: false,
|
|
1250
|
+
message: e.message,
|
|
1251
|
+
};
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1142
1254
|
if (!('password' in data) || !data.password.length) {
|
|
1143
1255
|
return {
|
|
1144
1256
|
result: false,
|
|
@@ -1170,6 +1282,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
1170
1282
|
const nymph = this.$nymph;
|
|
1171
1283
|
const tnymph = await this.$nymph.startTransaction(transaction);
|
|
1172
1284
|
this.$nymph = tnymph;
|
|
1285
|
+
tilmeld = this.$nymph.tilmeld as Tilmeld;
|
|
1173
1286
|
|
|
1174
1287
|
try {
|
|
1175
1288
|
// Add primary group.
|
|
@@ -1318,19 +1431,40 @@ export default class User extends AbleObject<UserData> {
|
|
|
1318
1431
|
tilmeld.config.unverifiedAccess &&
|
|
1319
1432
|
!madeAdmin
|
|
1320
1433
|
) {
|
|
1321
|
-
tilmeld.login(this, true)
|
|
1434
|
+
if (!tilmeld.login(this, true)) {
|
|
1435
|
+
throw new Error('An error occurred trying to log you in.');
|
|
1436
|
+
}
|
|
1322
1437
|
message +=
|
|
1323
1438
|
"You're now logged in! An email has been sent to " +
|
|
1324
1439
|
`${this.$data.email} with a verification link for you to finish ` +
|
|
1325
1440
|
'registration.';
|
|
1326
1441
|
loggedin = true;
|
|
1327
1442
|
} else {
|
|
1328
|
-
tilmeld.login(this, true)
|
|
1443
|
+
if (!tilmeld.login(this, true)) {
|
|
1444
|
+
throw new Error('An error occurred trying to log you in.');
|
|
1445
|
+
}
|
|
1329
1446
|
message += "You're now registered and logged in!";
|
|
1330
1447
|
loggedin = true;
|
|
1331
1448
|
}
|
|
1332
|
-
|
|
1333
|
-
this
|
|
1449
|
+
|
|
1450
|
+
for (let callback of (this.constructor as typeof User)
|
|
1451
|
+
.afterRegisterCallbacks) {
|
|
1452
|
+
if (callback) {
|
|
1453
|
+
await callback(this, {
|
|
1454
|
+
loggedin,
|
|
1455
|
+
message,
|
|
1456
|
+
});
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
try {
|
|
1461
|
+
await tnymph.commit(transaction);
|
|
1462
|
+
this.$nymph = nymph;
|
|
1463
|
+
this.$nymph.tilmeld?.fillSession(this);
|
|
1464
|
+
} catch (e: any) {
|
|
1465
|
+
throw e;
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1334
1468
|
return {
|
|
1335
1469
|
result: true,
|
|
1336
1470
|
loggedin,
|
|
@@ -1353,7 +1487,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
1353
1487
|
}
|
|
1354
1488
|
|
|
1355
1489
|
public async $save() {
|
|
1356
|
-
|
|
1490
|
+
let tilmeld = this.$nymph.tilmeld as Tilmeld;
|
|
1357
1491
|
if (this.$data.username == null || !this.$data.username.trim().length) {
|
|
1358
1492
|
return false;
|
|
1359
1493
|
}
|
|
@@ -1551,6 +1685,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
1551
1685
|
const nymph = this.$nymph;
|
|
1552
1686
|
const tnymph = await this.$nymph.startTransaction(transaction);
|
|
1553
1687
|
this.$nymph = tnymph;
|
|
1688
|
+
tilmeld = this.$nymph.tilmeld as Tilmeld;
|
|
1554
1689
|
|
|
1555
1690
|
if (
|
|
1556
1691
|
this.$data.group != null &&
|
|
@@ -1597,14 +1732,14 @@ export default class User extends AbleObject<UserData> {
|
|
|
1597
1732
|
}
|
|
1598
1733
|
}
|
|
1599
1734
|
|
|
1600
|
-
if (tilmeld.User.current(true).$is(this)) {
|
|
1601
|
-
// Update the user in the session cache.
|
|
1602
|
-
tilmeld.fillSession(this);
|
|
1603
|
-
}
|
|
1604
|
-
|
|
1605
1735
|
this.$descendantGroups = undefined;
|
|
1606
1736
|
this.$gatekeeperCache = undefined;
|
|
1607
1737
|
await tnymph.commit(transaction);
|
|
1738
|
+
|
|
1739
|
+
if ((nymph.tilmeld as Tilmeld).User.current(true).$is(this)) {
|
|
1740
|
+
// Update the user in the session cache.
|
|
1741
|
+
(nymph.tilmeld as Tilmeld).fillSession(this);
|
|
1742
|
+
}
|
|
1608
1743
|
} else {
|
|
1609
1744
|
await tnymph.rollback(transaction);
|
|
1610
1745
|
}
|
|
@@ -1646,4 +1781,82 @@ export default class User extends AbleObject<UserData> {
|
|
|
1646
1781
|
}
|
|
1647
1782
|
return await super.$delete();
|
|
1648
1783
|
}
|
|
1784
|
+
|
|
1785
|
+
public static on<T extends EventType>(
|
|
1786
|
+
event: T,
|
|
1787
|
+
callback: T extends 'beforeRegister'
|
|
1788
|
+
? TilmeldBeforeRegisterCallback
|
|
1789
|
+
: T extends 'afterRegister'
|
|
1790
|
+
? TilmeldAfterRegisterCallback
|
|
1791
|
+
: T extends 'beforeLogin'
|
|
1792
|
+
? TilmeldBeforeLoginCallback
|
|
1793
|
+
: T extends 'afterLogin'
|
|
1794
|
+
? TilmeldAfterLoginCallback
|
|
1795
|
+
: T extends 'beforeLogout'
|
|
1796
|
+
? TilmeldBeforeLogoutCallback
|
|
1797
|
+
: T extends 'afterLogout'
|
|
1798
|
+
? TilmeldAfterLogoutCallback
|
|
1799
|
+
: never
|
|
1800
|
+
) {
|
|
1801
|
+
const prop = (event + 'Callbacks') as T extends 'beforeRegister'
|
|
1802
|
+
? 'beforeRegisterCallbacks'
|
|
1803
|
+
: T extends 'afterRegister'
|
|
1804
|
+
? 'afterRegisterCallbacks'
|
|
1805
|
+
: T extends 'beforeLogin'
|
|
1806
|
+
? 'beforeLoginCallbacks'
|
|
1807
|
+
: T extends 'afterLogin'
|
|
1808
|
+
? 'afterLoginCallbacks'
|
|
1809
|
+
: T extends 'beforeLogout'
|
|
1810
|
+
? 'beforeLogoutCallbacks'
|
|
1811
|
+
: T extends 'afterLogout'
|
|
1812
|
+
? 'afterLogoutCallbacks'
|
|
1813
|
+
: never;
|
|
1814
|
+
if (!(prop in this)) {
|
|
1815
|
+
throw new Error('Invalid event type.');
|
|
1816
|
+
}
|
|
1817
|
+
// @ts-ignore: The callback should always be the right type here.
|
|
1818
|
+
this[prop].push(callback);
|
|
1819
|
+
return () => this.off(event, callback);
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
public static off<T extends EventType>(
|
|
1823
|
+
event: T,
|
|
1824
|
+
callback: T extends 'beforeRegister'
|
|
1825
|
+
? TilmeldBeforeRegisterCallback
|
|
1826
|
+
: T extends 'afterRegister'
|
|
1827
|
+
? TilmeldAfterRegisterCallback
|
|
1828
|
+
: T extends 'beforeLogin'
|
|
1829
|
+
? TilmeldBeforeLoginCallback
|
|
1830
|
+
: T extends 'afterLogin'
|
|
1831
|
+
? TilmeldAfterLoginCallback
|
|
1832
|
+
: T extends 'beforeLogout'
|
|
1833
|
+
? TilmeldBeforeLogoutCallback
|
|
1834
|
+
: T extends 'afterLogout'
|
|
1835
|
+
? TilmeldAfterLogoutCallback
|
|
1836
|
+
: never
|
|
1837
|
+
) {
|
|
1838
|
+
const prop = (event + 'Callbacks') as T extends 'beforeRegister'
|
|
1839
|
+
? 'beforeRegisterCallbacks'
|
|
1840
|
+
: T extends 'afterRegister'
|
|
1841
|
+
? 'afterRegisterCallbacks'
|
|
1842
|
+
: T extends 'beforeLogin'
|
|
1843
|
+
? 'beforeLoginCallbacks'
|
|
1844
|
+
: T extends 'afterLogin'
|
|
1845
|
+
? 'afterLoginCallbacks'
|
|
1846
|
+
: T extends 'beforeLogout'
|
|
1847
|
+
? 'beforeLogoutCallbacks'
|
|
1848
|
+
: T extends 'afterLogout'
|
|
1849
|
+
? 'afterLogoutCallbacks'
|
|
1850
|
+
: never;
|
|
1851
|
+
if (!(prop in this)) {
|
|
1852
|
+
return false;
|
|
1853
|
+
}
|
|
1854
|
+
// @ts-ignore: The callback should always be the right type here.
|
|
1855
|
+
const i = this[prop].indexOf(callback);
|
|
1856
|
+
if (i > -1) {
|
|
1857
|
+
// @ts-ignore: The callback should always be the right type here.
|
|
1858
|
+
this[prop].splice(i, 1);
|
|
1859
|
+
}
|
|
1860
|
+
return true;
|
|
1861
|
+
}
|
|
1649
1862
|
}
|
package/src/conf/d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EmailOptions } from 'email-templates';
|
|
1
|
+
import { EmailOptions, EmailConfig } from 'email-templates';
|
|
2
2
|
|
|
3
3
|
import type Tilmeld from '../Tilmeld';
|
|
4
4
|
import Group, { GroupData } from '../Group';
|
|
@@ -245,6 +245,18 @@ export interface Config {
|
|
|
245
245
|
* email sender.
|
|
246
246
|
*/
|
|
247
247
|
emailTemplateDir: string;
|
|
248
|
+
/**
|
|
249
|
+
* Configure an email object to prepare for sending.
|
|
250
|
+
*
|
|
251
|
+
* You can use this, for example, for DKIM signing.
|
|
252
|
+
*
|
|
253
|
+
* See: https://github.com/forwardemail/email-templates/blob/v8.0.9/README.md#Options
|
|
254
|
+
*/
|
|
255
|
+
configEmail: (
|
|
256
|
+
tilmeld: Tilmeld,
|
|
257
|
+
options: EmailOptions,
|
|
258
|
+
user: User & UserData
|
|
259
|
+
) => Promise<EmailConfig>;
|
|
248
260
|
/**
|
|
249
261
|
* Send an email to a user. Uses `email-templates` by default.
|
|
250
262
|
*
|