@nymphjs/tilmeld 1.0.0-beta.61 → 1.0.0-beta.63

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
@@ -1,4 +1,5 @@
1
- import {
1
+ import type {
2
+ Nymph,
2
3
  EntityData,
3
4
  EntityJson,
4
5
  EntityPatch,
@@ -6,7 +7,7 @@ import {
6
7
  SerializedEntityData,
7
8
  } from '@nymphjs/nymph';
8
9
  import { humanSecret, nanoid } from '@nymphjs/guid';
9
- import { EmailOptions } from 'email-templates';
10
+ import type { EmailOptions } from 'email-templates';
10
11
  import strtotime from 'locutus/php/datetime/strtotime';
11
12
  import Base64 from 'crypto-js/enc-base64';
12
13
  import sha256 from 'crypto-js/sha256';
@@ -17,7 +18,8 @@ import { toDataURL } from 'qrcode';
17
18
 
18
19
  import { enforceTilmeld } from './enforceTilmeld';
19
20
  import AbleObject from './AbleObject';
20
- import Group, { GroupData } from './Group';
21
+ import type Group from './Group';
22
+ import type { GroupData } from './Group';
21
23
  import {
22
24
  BadDataError,
23
25
  BadEmailError,
@@ -273,9 +275,13 @@ export default class User extends AbleObject<UserData> {
273
275
  */
274
276
  private $gatekeeperCache?: { [k: string]: true };
275
277
  /**
276
- * This is explicitly used only during the registration proccess.
278
+ * This should only be used by the backend.
277
279
  */
278
280
  private $skipAcWhenSaving = false;
281
+ /**
282
+ * This should only be used by the backend.
283
+ */
284
+ private $skipAcWhenDeleting = false;
279
285
  /**
280
286
  * The user's group descendants.
281
287
  */
@@ -308,17 +314,6 @@ export default class User extends AbleObject<UserData> {
308
314
  return entity;
309
315
  }
310
316
 
311
- constructor() {
312
- super();
313
-
314
- // Defaults.
315
- this.$data.enabled = true;
316
- this.$data.abilities = [];
317
- this.$data.groups = [];
318
- this.$data.inheritAbilities = true;
319
- this.$updateDataProtection();
320
- }
321
-
322
317
  public static current(returnObjectIfNotExist: true): User & UserData;
323
318
  public static current(
324
319
  returnObjectIfNotExist?: false,
@@ -531,6 +526,47 @@ export default class User extends AbleObject<UserData> {
531
526
  return result;
532
527
  }
533
528
 
529
+ constructor() {
530
+ super();
531
+
532
+ // Defaults.
533
+ this.$data.enabled = true;
534
+ this.$data.abilities = [];
535
+ this.$data.groups = [];
536
+ this.$data.inheritAbilities = true;
537
+ this.$updateDataProtection();
538
+ }
539
+
540
+ $setNymph(nymph: Nymph) {
541
+ this.$nymph = nymph;
542
+ if (!this.$asleep()) {
543
+ if (this.$data.group && this.$data.group.$nymph !== nymph) {
544
+ this.$data.group.$setNymph(nymph);
545
+ }
546
+ for (const group of this.$data.groups ?? []) {
547
+ if (group && group.$nymph !== nymph) {
548
+ group.$setNymph(nymph);
549
+ }
550
+ }
551
+ }
552
+ }
553
+
554
+ async $getUniques(): Promise<string[]> {
555
+ const tilmeld = enforceTilmeld(this);
556
+ const uniques = [`u:${this.$data.username}`];
557
+ if (
558
+ !tilmeld.config.emailUsernames &&
559
+ tilmeld.config.userFields.includes('email')
560
+ ) {
561
+ uniques.push(`e:${this.$data.email}`);
562
+
563
+ if (this.$data.newEmailAddress != null) {
564
+ uniques.push(`e:${this.$data.newEmailAddress}`);
565
+ }
566
+ }
567
+ return uniques;
568
+ }
569
+
534
570
  public async $login(data: {
535
571
  username: string;
536
572
  password: string;
@@ -1793,7 +1829,7 @@ export default class User extends AbleObject<UserData> {
1793
1829
  const transaction = 'tilmeld-register-' + nanoid();
1794
1830
  const nymph = this.$nymph;
1795
1831
  const tnymph = await this.$nymph.startTransaction(transaction);
1796
- this.$nymph = tnymph;
1832
+ this.$setNymph(tnymph);
1797
1833
  tilmeld = enforceTilmeld(this);
1798
1834
 
1799
1835
  let message = '';
@@ -1809,7 +1845,7 @@ export default class User extends AbleObject<UserData> {
1809
1845
  }
1810
1846
  } catch (e: any) {
1811
1847
  await tnymph.rollback(transaction);
1812
- this.$nymph = nymph;
1848
+ this.$setNymph(nymph);
1813
1849
  return {
1814
1850
  result: false,
1815
1851
  loggedin: false,
@@ -1817,78 +1853,6 @@ export default class User extends AbleObject<UserData> {
1817
1853
  };
1818
1854
  }
1819
1855
 
1820
- // Add primary group.
1821
- let generatedPrimaryGroup: (Group & GroupData) | null = null;
1822
- if (tilmeld.config.generatePrimary) {
1823
- // Generate a new primary group for the user.
1824
- generatedPrimaryGroup = await tilmeld.Group.factory();
1825
- generatedPrimaryGroup.groupname = this.$data.username;
1826
- generatedPrimaryGroup.avatar = this.$data.avatar;
1827
- if (tilmeld.config.userFields.includes('name')) {
1828
- generatedPrimaryGroup.name = this.$data.name;
1829
- }
1830
- if (tilmeld.config.userFields.includes('email')) {
1831
- generatedPrimaryGroup.email = this.$data.email;
1832
- }
1833
- const parent = await tnymph.getEntity(
1834
- { class: tilmeld.Group },
1835
- {
1836
- type: '&',
1837
- equal: ['defaultPrimary', true],
1838
- },
1839
- );
1840
- if (parent != null) {
1841
- generatedPrimaryGroup.parent = parent;
1842
- }
1843
- if (!(await generatedPrimaryGroup.$saveSkipAC())) {
1844
- await tnymph.rollback(transaction);
1845
- this.$nymph = nymph;
1846
- return {
1847
- result: false,
1848
- loggedin: false,
1849
- message: 'Error creating primary group for user.',
1850
- };
1851
- }
1852
- this.$data.group = generatedPrimaryGroup;
1853
- } else {
1854
- // Add the default primary.
1855
- const group = await tnymph.getEntity(
1856
- { class: tilmeld.Group },
1857
- {
1858
- type: '&',
1859
- equal: ['defaultPrimary', true],
1860
- },
1861
- );
1862
- if (group != null) {
1863
- this.$data.group = group;
1864
- }
1865
- }
1866
-
1867
- // Add secondary groups.
1868
- if (
1869
- tilmeld.config.userFields.includes('email') &&
1870
- tilmeld.config.verifyEmail &&
1871
- tilmeld.config.unverifiedAccess
1872
- ) {
1873
- // Add the default secondaries for unverified users.
1874
- this.$data.groups = await tnymph.getEntities(
1875
- { class: tilmeld.Group },
1876
- {
1877
- type: '&',
1878
- equal: ['unverifiedSecondary', true],
1879
- },
1880
- );
1881
- } else {
1882
- // Add the default secondaries.
1883
- this.$data.groups = await tnymph.getEntities(
1884
- { class: tilmeld.Group },
1885
- {
1886
- type: '&',
1887
- equal: ['defaultSecondary', true],
1888
- },
1889
- );
1890
- }
1891
-
1892
1856
  if (
1893
1857
  tilmeld.config.userFields.includes('email') &&
1894
1858
  tilmeld.config.verifyEmail
@@ -1901,7 +1865,7 @@ export default class User extends AbleObject<UserData> {
1901
1865
  this.$data.enabled = true;
1902
1866
  }
1903
1867
 
1904
- // If create_admin is true and there are no other users, grant
1868
+ // If createAdmin is true and there are no other users, grant
1905
1869
  // "system/admin".
1906
1870
  let madeAdmin = false;
1907
1871
  if (tilmeld.config.createAdmin) {
@@ -1950,21 +1914,6 @@ export default class User extends AbleObject<UserData> {
1950
1914
  );
1951
1915
  }
1952
1916
 
1953
- // Save the primary group.
1954
- if (generatedPrimaryGroup != null) {
1955
- generatedPrimaryGroup.user = this;
1956
- if (!(await generatedPrimaryGroup.$saveSkipAC())) {
1957
- await tnymph.rollback(transaction);
1958
- this.$nymph = nymph;
1959
- return {
1960
- result: false,
1961
- loggedin: false,
1962
- message:
1963
- 'Your primary group could not be assigned. Please try again later.',
1964
- };
1965
- }
1966
- }
1967
-
1968
1917
  // Finish up.
1969
1918
  if (
1970
1919
  tilmeld.config.verifyEmail &&
@@ -2012,7 +1961,7 @@ export default class User extends AbleObject<UserData> {
2012
1961
  `Error registering new user "${this.$data.username}".`,
2013
1962
  );
2014
1963
  await tnymph.rollback(transaction);
2015
- this.$nymph = nymph;
1964
+ this.$setNymph(nymph);
2016
1965
  return {
2017
1966
  result: false,
2018
1967
  loggedin: false,
@@ -2025,13 +1974,13 @@ export default class User extends AbleObject<UserData> {
2025
1974
  `Error registering new user "${this.$data.username}": ${e}`,
2026
1975
  );
2027
1976
  await tnymph.rollback(transaction);
2028
- this.$nymph = nymph;
1977
+ this.$setNymph(nymph);
2029
1978
  throw e;
2030
1979
  }
2031
1980
 
2032
1981
  try {
2033
1982
  await tnymph.commit(transaction);
2034
- this.$nymph = nymph;
1983
+ this.$setNymph(nymph);
2035
1984
  await this.$nymph.tilmeld?.fillSession(this);
2036
1985
  } catch (e: any) {
2037
1986
  throw e;
@@ -2051,6 +2000,7 @@ export default class User extends AbleObject<UserData> {
2051
2000
  }
2052
2001
 
2053
2002
  if (
2003
+ !this.$skipAcWhenSaving &&
2054
2004
  tilmeld.gatekeeper('tilmeld/admin') &&
2055
2005
  !tilmeld.gatekeeper('system/admin') &&
2056
2006
  this.$data.abilities?.includes('system/admin')
@@ -2277,42 +2227,129 @@ export default class User extends AbleObject<UserData> {
2277
2227
  throw new BadDataError('A password is required.');
2278
2228
  }
2279
2229
 
2280
- try {
2281
- tilmeld.config.validatorUser(tilmeld, this);
2282
- } catch (e: any) {
2283
- throw new BadDataError(e?.message);
2284
- }
2285
-
2286
2230
  // Start transaction.
2287
2231
  const transaction = 'tilmeld-save-' + nanoid();
2288
2232
  const nymph = this.$nymph;
2289
2233
  const tnymph = await this.$nymph.startTransaction(transaction);
2290
- this.$nymph = tnymph;
2234
+ this.$setNymph(tnymph);
2291
2235
  tilmeld = enforceTilmeld(this);
2292
2236
 
2293
- const group = this.$data.group;
2294
- await group?.$wake();
2295
- if (group != null && group.user != null && this.$is(group.user)) {
2296
- try {
2237
+ // If the primary group is generated now, the user assigned to it won't have
2238
+ // a guid yet, so it needs to be assigned after the user is saved.
2239
+ let saveGeneratedPrimaryGroupAgain = false;
2240
+
2241
+ try {
2242
+ let group = this.$data.group;
2243
+ await group?.$wake();
2244
+ if (group == null && this.guid == null) {
2245
+ if (tilmeld.config.generatePrimary) {
2246
+ // Generate a new primary group for the user.
2247
+ group = await tilmeld.Group.factory();
2248
+ // This user doesn't have a guid yet, so that's why we set
2249
+ // saveGeneratedPrimaryGroupAgain.
2250
+ group.user = this;
2251
+ saveGeneratedPrimaryGroupAgain = true;
2252
+ const parent = await tnymph.getEntity(
2253
+ { class: tilmeld.Group },
2254
+ {
2255
+ type: '&',
2256
+ equal: ['defaultPrimary', true],
2257
+ },
2258
+ );
2259
+ if (parent != null) {
2260
+ group.parent = parent;
2261
+ }
2262
+ } else {
2263
+ // Add the default primary.
2264
+ const group = await tnymph.getEntity(
2265
+ { class: tilmeld.Group },
2266
+ {
2267
+ type: '&',
2268
+ equal: ['defaultPrimary', true],
2269
+ },
2270
+ );
2271
+ if (group != null) {
2272
+ this.$data.group = group;
2273
+ }
2274
+ }
2275
+ }
2276
+ if (group != null && group.user != null && this.$is(group.user)) {
2297
2277
  // Update the user's generated primary group.
2298
- group.groupname = this.$data.username;
2299
- group.avatar = this.$data.avatar;
2300
- if (tilmeld.config.userFields.includes('email')) {
2278
+ let changed = false;
2279
+ if (group.groupname !== this.$data.username) {
2280
+ group.groupname = this.$data.username;
2281
+ changed = true;
2282
+ }
2283
+ if (group.avatar !== this.$data.avatar) {
2284
+ group.avatar = this.$data.avatar;
2285
+ changed = true;
2286
+ }
2287
+ if (
2288
+ tilmeld.config.userFields.includes('email') &&
2289
+ group.email !== this.$data.email
2290
+ ) {
2301
2291
  group.email = this.$data.email;
2292
+ changed = true;
2302
2293
  }
2303
- if (tilmeld.config.userFields.includes('name')) {
2294
+ if (
2295
+ tilmeld.config.userFields.includes('name') &&
2296
+ group.name !== this.$data.name
2297
+ ) {
2304
2298
  group.name = this.$data.name;
2299
+ changed = true;
2305
2300
  }
2306
- if (tilmeld.config.userFields.includes('phone')) {
2301
+ if (
2302
+ tilmeld.config.userFields.includes('phone') &&
2303
+ group.phone !== this.$data.phone
2304
+ ) {
2307
2305
  group.phone = this.$data.phone;
2306
+ changed = true;
2307
+ }
2308
+ if (changed) {
2309
+ if (!(await group.$saveSkipAC())) {
2310
+ throw Error('Error updating primary group for user.');
2311
+ }
2308
2312
  }
2309
- await group.$saveSkipAC();
2310
2313
  this.$data.group = group;
2314
+ }
2315
+
2316
+ if (this.$data.groups == null || this.$data.groups.length === 0) {
2317
+ // Add secondary groups.
2318
+ if (
2319
+ tilmeld.config.userFields.includes('email') &&
2320
+ tilmeld.config.verifyEmail &&
2321
+ tilmeld.config.unverifiedAccess &&
2322
+ this.$data.secret != null
2323
+ ) {
2324
+ // Add the default secondaries for unverified users.
2325
+ this.$data.groups = await tnymph.getEntities(
2326
+ { class: tilmeld.Group },
2327
+ {
2328
+ type: '&',
2329
+ equal: ['unverifiedSecondary', true],
2330
+ },
2331
+ );
2332
+ } else {
2333
+ // Add the default secondaries.
2334
+ this.$data.groups = await tnymph.getEntities(
2335
+ { class: tilmeld.Group },
2336
+ {
2337
+ type: '&',
2338
+ equal: ['defaultSecondary', true],
2339
+ },
2340
+ );
2341
+ }
2342
+ }
2343
+
2344
+ try {
2345
+ tilmeld.config.validatorUser(tilmeld, this);
2311
2346
  } catch (e: any) {
2312
- await tnymph.rollback(transaction);
2313
- this.$nymph = nymph;
2314
- throw e;
2347
+ throw new BadDataError(e?.message);
2315
2348
  }
2349
+ } catch (e: any) {
2350
+ await tnymph.rollback(transaction);
2351
+ this.$setNymph(nymph);
2352
+ throw e;
2316
2353
  }
2317
2354
 
2318
2355
  let ret: boolean;
@@ -2322,11 +2359,26 @@ export default class User extends AbleObject<UserData> {
2322
2359
 
2323
2360
  try {
2324
2361
  ret = await super.$save();
2362
+
2363
+ if (ret && saveGeneratedPrimaryGroupAgain) {
2364
+ const group = this.$data.group;
2365
+ if (group == null) {
2366
+ throw Error('Generated primary group not found.');
2367
+ }
2368
+ await group.$wake();
2369
+ // Now this user has a GUID, so saving it on the group will succeed.
2370
+ group.user = this;
2371
+ if (!(await group.$saveSkipAC())) {
2372
+ throw Error("Generated primary group couldn't be saved.");
2373
+ }
2374
+ this.$data.group = group;
2375
+ }
2325
2376
  } catch (e: any) {
2326
2377
  await tnymph.rollback(transaction);
2327
- this.$nymph = nymph;
2378
+ this.$setNymph(nymph);
2328
2379
  throw e;
2329
2380
  }
2381
+
2330
2382
  if (ret) {
2331
2383
  if (sendVerification) {
2332
2384
  // The email has changed, so send a new verification email.
@@ -2335,7 +2387,7 @@ export default class User extends AbleObject<UserData> {
2335
2387
  this.guid = preGuid;
2336
2388
  this.cdate = preCdate;
2337
2389
  this.mdate = preMdate;
2338
- this.$nymph = nymph;
2390
+ this.$setNymph(nymph);
2339
2391
  throw new Error("Couldn't send verification email.");
2340
2392
  }
2341
2393
  }
@@ -2353,7 +2405,7 @@ export default class User extends AbleObject<UserData> {
2353
2405
  } else {
2354
2406
  await tnymph.rollback(transaction);
2355
2407
  }
2356
- this.$nymph = nymph;
2408
+ this.$setNymph(nymph);
2357
2409
  return ret;
2358
2410
  }
2359
2411
 
@@ -2375,10 +2427,11 @@ export default class User extends AbleObject<UserData> {
2375
2427
 
2376
2428
  public async $delete() {
2377
2429
  const tilmeld = enforceTilmeld(this);
2378
- if (!tilmeld.gatekeeper('tilmeld/admin')) {
2430
+ if (!this.$skipAcWhenDeleting && !tilmeld.gatekeeper('tilmeld/admin')) {
2379
2431
  throw new BadDataError("You don't have the authority to delete users.");
2380
2432
  }
2381
2433
  if (
2434
+ !this.$skipAcWhenDeleting &&
2382
2435
  !tilmeld.gatekeeper('system/admin') &&
2383
2436
  this.$data.abilities?.includes('system/admin')
2384
2437
  ) {
@@ -2392,6 +2445,22 @@ export default class User extends AbleObject<UserData> {
2392
2445
  return await super.$delete();
2393
2446
  }
2394
2447
 
2448
+ /*
2449
+ * This should *never* be accessible on the client.
2450
+ */
2451
+ async $deleteSkipAC() {
2452
+ this.$skipAcWhenDeleting = true;
2453
+ return await this.$delete();
2454
+ }
2455
+
2456
+ $tilmeldDeleteSkipAC() {
2457
+ if (this.$skipAcWhenDeleting) {
2458
+ this.$skipAcWhenDeleting = false;
2459
+ return true;
2460
+ }
2461
+ return false;
2462
+ }
2463
+
2395
2464
  public static on<T extends EventType>(
2396
2465
  event: T,
2397
2466
  callback: T extends 'checkUsername'
package/tsconfig.json CHANGED
@@ -2,8 +2,8 @@
2
2
  "extends": "@tsconfig/recommended/tsconfig.json",
3
3
 
4
4
  "compilerOptions": {
5
- "lib": ["ES2021"],
6
- "target": "ES2021",
5
+ "lib": ["ES2023"],
6
+ "target": "ES2022",
7
7
  "noImplicitAny": true,
8
8
  "removeComments": false,
9
9
  "sourceMap": true,