@nymphjs/tilmeld 1.0.0-beta.9 → 1.0.0-beta.90

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.
Files changed (78) hide show
  1. package/CHANGELOG.md +408 -0
  2. package/README.md +15 -2
  3. package/dist/AbleObject.d.ts +15 -0
  4. package/dist/AbleObject.js +18 -6
  5. package/dist/AbleObject.js.map +1 -1
  6. package/dist/Group.d.ts +140 -15
  7. package/dist/Group.js +466 -191
  8. package/dist/Group.js.map +1 -1
  9. package/dist/Group.test.js +83 -13
  10. package/dist/Group.test.js.map +1 -1
  11. package/dist/Tilmeld.d.ts +239 -12
  12. package/dist/Tilmeld.js +634 -254
  13. package/dist/Tilmeld.js.map +1 -1
  14. package/dist/Tilmeld.types.d.ts +2 -2
  15. package/dist/Tilmeld.types.js +1 -2
  16. package/dist/User.d.ts +365 -22
  17. package/dist/User.js +1265 -364
  18. package/dist/User.js.map +1 -1
  19. package/dist/User.test.js +132 -13
  20. package/dist/User.test.js.map +1 -1
  21. package/dist/conf/d.d.ts +287 -7
  22. package/dist/conf/d.js +1 -2
  23. package/dist/conf/defaults.d.ts +1 -1
  24. package/dist/conf/defaults.js +188 -141
  25. package/dist/conf/defaults.js.map +1 -1
  26. package/dist/conf/index.d.ts +2 -2
  27. package/dist/conf/index.js +2 -8
  28. package/dist/conf/index.js.map +1 -1
  29. package/dist/enforceTilmeld.d.ts +3 -0
  30. package/dist/enforceTilmeld.js +18 -0
  31. package/dist/enforceTilmeld.js.map +1 -0
  32. package/dist/errors/AccessControlError.d.ts +2 -1
  33. package/dist/errors/AccessControlError.js +3 -6
  34. package/dist/errors/AccessControlError.js.map +1 -1
  35. package/dist/errors/BadDataError.d.ts +2 -1
  36. package/dist/errors/BadDataError.js +3 -6
  37. package/dist/errors/BadDataError.js.map +1 -1
  38. package/dist/errors/BadEmailError.d.ts +2 -1
  39. package/dist/errors/BadEmailError.js +3 -6
  40. package/dist/errors/BadEmailError.js.map +1 -1
  41. package/dist/errors/BadUsernameError.d.ts +2 -1
  42. package/dist/errors/BadUsernameError.js +3 -6
  43. package/dist/errors/BadUsernameError.js.map +1 -1
  44. package/dist/errors/CouldNotChangeDefaultPrimaryGroupError.js +1 -5
  45. package/dist/errors/CouldNotChangeDefaultPrimaryGroupError.js.map +1 -1
  46. package/dist/errors/EmailChangeRateLimitExceededError.js +1 -5
  47. package/dist/errors/EmailChangeRateLimitExceededError.js.map +1 -1
  48. package/dist/errors/index.d.ts +6 -6
  49. package/dist/errors/index.js +6 -22
  50. package/dist/errors/index.js.map +1 -1
  51. package/dist/index.d.ts +11 -10
  52. package/dist/index.js +16 -35
  53. package/dist/index.js.map +1 -1
  54. package/dist/tilmeldJoiProps.js +13 -18
  55. package/dist/tilmeldJoiProps.js.map +1 -1
  56. package/jest.config.js +11 -2
  57. package/package.json +34 -31
  58. package/src/AbleObject.ts +2 -2
  59. package/src/Group.test.ts +104 -6
  60. package/src/Group.ts +478 -226
  61. package/src/Tilmeld.ts +418 -254
  62. package/src/Tilmeld.types.ts +2 -2
  63. package/src/User.test.ts +163 -6
  64. package/src/User.ts +1428 -444
  65. package/src/conf/d.ts +63 -17
  66. package/src/conf/defaults.ts +162 -109
  67. package/src/conf/index.ts +2 -2
  68. package/src/enforceTilmeld.ts +20 -0
  69. package/src/errors/AccessControlError.ts +4 -2
  70. package/src/errors/BadDataError.ts +4 -2
  71. package/src/errors/BadEmailError.ts +4 -2
  72. package/src/errors/BadUsernameError.ts +4 -2
  73. package/src/errors/index.ts +6 -6
  74. package/src/index.ts +12 -10
  75. package/src/tilmeldJoiProps.ts +11 -11
  76. package/src/type/locutus.d.ts +3 -0
  77. package/tsconfig.json +5 -3
  78. package/typedoc.json +4 -0
package/src/conf/d.ts CHANGED
@@ -1,15 +1,17 @@
1
- import { EmailOptions, EmailConfig } from 'email-templates';
1
+ import type { EmailOptions, EmailConfig } from 'email-templates';
2
2
 
3
- import type Tilmeld from '../Tilmeld';
4
- import Group, { GroupData } from '../Group';
5
- import User, { UserData } from '../User';
3
+ import type Tilmeld from '../Tilmeld.js';
4
+ import type Group from '../Group.js';
5
+ import type { GroupData } from '../Group.js';
6
+ import type User from '../User.js';
7
+ import type { UserData } from '../User.js';
6
8
 
7
9
  /**
8
10
  * Tilmeld Config
9
11
  */
10
12
  export interface Config {
11
13
  /**
12
- * The name of the app. Used in emails sent to users.
14
+ * The name of the app. Used in emails sent to users and as the TOTP issuer.
13
15
  */
14
16
  appName: string;
15
17
  /**
@@ -45,8 +47,27 @@ export interface Config {
45
47
  * Instead of a "username", a user logs in and is referred to by their email
46
48
  * address. Enabling this after many users have been created can be messy.
47
49
  * Make sure they all have email addresses first.
50
+ *
51
+ * Email usernames cannot be enabled if domain support is.
48
52
  */
49
53
  emailUsernames: boolean;
54
+ /**
55
+ * Enable domain support.
56
+ *
57
+ * If domain support is enabled, usernames can be either bare, meaning they
58
+ * belong to the root domain, or can include a domain part (username@domain),
59
+ * meaning they belong to that domain.
60
+ *
61
+ * It also enables domain admin abilities, meaning a domain admin has the
62
+ * ability to create and modify users on the domain
63
+ * ("tilmeld/domain/example.com/admin").
64
+ *
65
+ * Once a user is created, whether it has a domain part can't be changed.
66
+ * However, it can be moved to a different domain.
67
+ *
68
+ * Domain support cannot be enabled if email usernames is.
69
+ */
70
+ domainSupport: boolean;
50
71
  /**
51
72
  * Allow users to register.
52
73
  */
@@ -206,7 +227,23 @@ export interface Config {
206
227
  */
207
228
  validEmailRegexNotice: string;
208
229
  /**
209
- * The maximum length for usernames. Infinity for unlimited.
230
+ * Domains must match this regular expression. By default, this uses a regex
231
+ * that should match most domains:
232
+ *
233
+ * https://stackoverflow.com/a/26987741
234
+ */
235
+ validDomainRegex: RegExp;
236
+ /**
237
+ * When a user enters a domain that doesn't match the regex, this message will
238
+ * be displayed.
239
+ */
240
+ validDomainRegexNotice: string;
241
+ /**
242
+ * The minimum length for usernames.
243
+ */
244
+ minUsernameLength: number;
245
+ /**
246
+ * The maximum length for usernames. `Infinity` for unlimited.
210
247
  */
211
248
  maxUsernameLength: number;
212
249
  /**
@@ -217,6 +254,11 @@ export interface Config {
217
254
  * How long from current time, in seconds, the JWT token expires.
218
255
  */
219
256
  jwtExpire: number;
257
+ /**
258
+ * How long from current time, in seconds, the JWT token for a user switch
259
+ * expires.
260
+ */
261
+ jwtSwitchExpire: number;
220
262
  /**
221
263
  * How long, in seconds, before the JWT token expires to give the user a new
222
264
  * one.
@@ -225,21 +267,25 @@ export interface Config {
225
267
  /**
226
268
  * Function to build the JWT for user sessions.
227
269
  */
228
- jwtBuilder: (config: Config, user: User) => string;
270
+ jwtBuilder: (
271
+ config: Config,
272
+ user: User & UserData,
273
+ switchToken?: boolean,
274
+ ) => string;
229
275
  /**
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.
276
+ * Function to verify that a JWT was signed with the secret key, validate its
277
+ * data, validate the XSRF token, and extract the GUID and dates.
232
278
  *
233
279
  * If no XSRF token is supplied, ignore it.
234
280
  *
235
- * Return false if the JWT is not valid, or an array of GUID and expire
236
- * timestamp otherwise.
281
+ * Return false if the JWT is not valid, or an object with GUID, issued date,
282
+ * and expire date otherwise.
237
283
  */
238
284
  jwtExtract: (
239
285
  config: Config,
240
286
  token: string,
241
- xsrfToken?: string
242
- ) => { guid: string; expire: Date } | null;
287
+ xsrfToken?: string,
288
+ ) => { guid: string; issued: Date; expire: Date } | null;
243
289
  /**
244
290
  * The absolute path to the email template directory. Used by the default
245
291
  * email sender.
@@ -255,7 +301,7 @@ export interface Config {
255
301
  configEmail: (
256
302
  tilmeld: Tilmeld,
257
303
  options: EmailOptions,
258
- user: User & UserData
304
+ user: User & UserData,
259
305
  ) => Promise<EmailConfig>;
260
306
  /**
261
307
  * Send an email to a user. Uses `email-templates` by default.
@@ -285,7 +331,7 @@ export interface Config {
285
331
  sendEmail: (
286
332
  tilmeld: Tilmeld,
287
333
  options: EmailOptions,
288
- user: User & UserData
334
+ user: User & UserData,
289
335
  ) => Promise<boolean>;
290
336
  /**
291
337
  * The address you'd like to receive a notification of registered users, if
@@ -295,9 +341,9 @@ export interface Config {
295
341
  /**
296
342
  * The validator used to check groups before saving.
297
343
  */
298
- validatorGroup: (group: Group & GroupData) => void;
344
+ validatorGroup: (tilmeld: Tilmeld, group: Group & GroupData) => void;
299
345
  /**
300
346
  * The validator used to check users before saving.
301
347
  */
302
- validatorUser: (user: User & UserData) => void;
348
+ validatorUser: (tilmeld: Tilmeld, user: User & UserData) => void;
303
349
  }
@@ -1,14 +1,16 @@
1
- import path from 'path';
2
- import { URL } from 'url';
1
+ import path from 'node:path';
2
+ import { URL, fileURLToPath } from 'node:url';
3
3
  import jwt from 'jsonwebtoken';
4
4
  import Email from 'email-templates';
5
5
  import Joi from 'joi';
6
6
  import { nanoid } from '@nymphjs/guid';
7
7
  import { nymphJoiProps } from '@nymphjs/nymph';
8
8
 
9
- import { Config } from './d';
10
- import User from '../User';
11
- import Group from '../Group';
9
+ import type { Config } from './d.js';
10
+ import User from '../User.js';
11
+ import Group from '../Group.js';
12
+
13
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
12
14
 
13
15
  export default {
14
16
  appName: 'My Nymph App',
@@ -18,6 +20,7 @@ export default {
18
20
  setupPath: '/tilmeld',
19
21
  createAdmin: true,
20
22
  emailUsernames: true,
23
+ domainSupport: false,
21
24
  allowRegistration: true,
22
25
  allowUsernameChange: true,
23
26
  enableUserSearch: false,
@@ -49,11 +52,16 @@ export default {
49
52
  validEmailRegex:
50
53
  /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/,
51
54
  validEmailRegexNotice: 'Email must be a correctly formatted address.',
55
+ validDomainRegex:
56
+ /^(((?!-))(xn--|_)?[a-z0-9\-]{0,61}[a-z0-9]{1,1}\.)*(xn--)?([a-z0-9][a-z0-9\-]{0,60}|[a-z0-9\-]{1,30}\.[a-z]{2,})$/,
57
+ validDomainRegexNotice: 'Domain must be a correctly formatted domain name.',
58
+ minUsernameLength: 1,
52
59
  maxUsernameLength: 128,
53
60
  jwtSecret: '',
54
- jwtExpire: 60 * 60 * 24 * 7 * 8, // 8 weeks(ish)
55
- jwtRenew: 60 * 60 * 24 * 7 * 2, // 2 weeks(ish)
56
- jwtBuilder: (config, user) => {
61
+ jwtExpire: 60 * 60 * 24 * 7 * 24, // 24 weeks(ish) (6 months)
62
+ jwtSwitchExpire: 60 * 60 * 6, // 6 hours
63
+ jwtRenew: 60 * 60 * 24 * 7 * 12, // 12 weeks(ish) (3 months)
64
+ jwtBuilder: (config, user, switchToken) => {
57
65
  const secret = config.jwtSecret;
58
66
  if (secret === '') {
59
67
  throw new Error('JWT secret is not configured.');
@@ -62,12 +70,14 @@ export default {
62
70
  return jwt.sign(
63
71
  {
64
72
  iss: config.appUrl,
65
- nbf: Date.now() / 1000,
73
+ nbf: Math.floor(Date.now() / 1000),
74
+ exp:
75
+ Math.floor(Date.now() / 1000) +
76
+ (switchToken ? config.jwtSwitchExpire : config.jwtExpire),
66
77
  guid: user.guid,
67
78
  xsrfToken: 'TILMELDXSRF-' + nanoid(),
68
79
  },
69
80
  secret,
70
- { expiresIn: config.jwtExpire }
71
81
  );
72
82
  },
73
83
  jwtExtract: (config, token, xsrfToken?) => {
@@ -79,14 +89,14 @@ export default {
79
89
  try {
80
90
  const payload = jwt.verify(token, secret, {
81
91
  issuer: config.appUrl,
82
- clockTolerance: 10,
92
+ clockTolerance: 60,
83
93
  });
84
94
 
85
95
  if (typeof payload === 'string') {
86
96
  return null;
87
97
  }
88
98
 
89
- const { guid, xsrfToken: jwtXsrfToken, exp } = payload;
99
+ const { guid, xsrfToken: jwtXsrfToken, exp, nbf } = payload;
90
100
 
91
101
  if (xsrfToken != null && xsrfToken !== jwtXsrfToken) {
92
102
  return null;
@@ -96,13 +106,19 @@ export default {
96
106
  return null;
97
107
  }
98
108
 
109
+ if (nbf == null) {
110
+ return null;
111
+ }
112
+
113
+ const issued = new Date(nbf * 1000);
114
+
99
115
  if (exp == null) {
100
116
  return null;
101
117
  }
102
118
 
103
119
  const expire = new Date(exp * 1000);
104
120
 
105
- return { guid, expire };
121
+ return { guid, issued, expire };
106
122
  } catch (e: any) {
107
123
  return null;
108
124
  }
@@ -121,14 +137,14 @@ export default {
121
137
  },
122
138
  sendEmail: async (tilmeld, options, user) => {
123
139
  const email = new Email(
124
- await tilmeld.config.configEmail(tilmeld, options, user)
140
+ await tilmeld.config.configEmail(tilmeld, options, user),
125
141
  );
126
142
  try {
127
143
  const result = await email.send({
128
144
  ...options,
129
145
  template: path.join(
130
146
  tilmeld.config.emailTemplateDir,
131
- options.template ?? '.'
147
+ options.template ?? '.',
132
148
  ),
133
149
  locals: {
134
150
  // System
@@ -156,29 +172,48 @@ export default {
156
172
  });
157
173
  return !!result;
158
174
  } catch (e: any) {
175
+ tilmeld.nymph.config.debugError('tilmeld', `Failed to send email: ${e}`);
159
176
  return false;
160
177
  }
161
178
  },
162
179
  userRegisteredRecipient: null,
163
- validatorGroup: (group) => {
180
+ validatorGroup: (tilmeld, group) => {
181
+ const nameFields = tilmeld.config.userFields.includes('name')
182
+ ? {
183
+ name: Joi.string()
184
+ .trim(false)
185
+ .pattern(/[\x01-\x1F\x7F]/, {
186
+ name: 'control characters',
187
+ invert: true,
188
+ })
189
+ .max(512)
190
+ .required(),
191
+ }
192
+ : {};
193
+ const emailFields = tilmeld.config.userFields.includes('email')
194
+ ? {
195
+ email: Joi.string()
196
+ .trim(false)
197
+ .email({ minDomainSegments: 1, tlds: false })
198
+ .required(),
199
+ }
200
+ : {};
201
+ const phoneFields = tilmeld.config.userFields.includes('phone')
202
+ ? {
203
+ phone: Joi.string()
204
+ .trim(false)
205
+ .pattern(/^[0-9]+$/, 'numbers'),
206
+ }
207
+ : {};
164
208
  Joi.attempt(
165
209
  group.$getValidatable(),
166
210
  Joi.object().keys({
167
211
  ...nymphJoiProps,
212
+ ...nameFields,
213
+ ...emailFields,
214
+ ...phoneFields,
168
215
  groupname: Joi.string().trim(false).required(),
169
216
  enabled: Joi.boolean().required(),
170
- email: Joi.string()
171
- .trim(false)
172
- .email({ minDomainSegments: 1, tlds: false })
173
- .required(),
174
- name: Joi.string()
175
- .trim(false)
176
- .pattern(/[\x01-\x1F\x7F]/, {
177
- name: 'control characters',
178
- invert: true,
179
- })
180
- .max(512)
181
- .required(),
182
217
  avatar: Joi.string()
183
218
  .trim(false)
184
219
  .uri()
@@ -187,9 +222,6 @@ export default {
187
222
  invert: true,
188
223
  })
189
224
  .max(1024),
190
- phone: Joi.string()
191
- .trim(false)
192
- .pattern(/^[0-9]+$/, 'numbers'),
193
225
  parent: Joi.object().instance(Group),
194
226
  user: Joi.object().instance(User),
195
227
  abilities: Joi.array().items(
@@ -198,56 +230,110 @@ export default {
198
230
  name: 'control characters',
199
231
  invert: true,
200
232
  })
201
- .max(256)
233
+ .max(256),
202
234
  ),
203
235
  defaultPrimary: Joi.boolean(),
204
236
  defaultSecondary: Joi.boolean(),
205
237
  unverifiedSecondary: Joi.boolean(),
206
238
  }),
207
- 'Invalid Group: '
239
+ 'Invalid Group: ',
208
240
  );
209
241
  },
210
- validatorUser: (user) => {
242
+ validatorUser: (tilmeld, user) => {
243
+ const nameFields = tilmeld.config.userFields.includes('name')
244
+ ? {
245
+ nameFirst: Joi.string()
246
+ .trim(false)
247
+ .pattern(/[\x01-\x1F\x7F]/, {
248
+ name: 'control characters',
249
+ invert: true,
250
+ })
251
+ .max(512)
252
+ .required(),
253
+ nameMiddle: Joi.string()
254
+ .trim(false)
255
+ .pattern(/[\x01-\x1F\x7F]/, {
256
+ name: 'control characters',
257
+ invert: true,
258
+ })
259
+ .max(512),
260
+ nameLast: Joi.string()
261
+ .trim(false)
262
+ .pattern(/[\x01-\x1F\x7F]/, {
263
+ name: 'control characters',
264
+ invert: true,
265
+ })
266
+ .max(512),
267
+ name: Joi.string()
268
+ .trim(false)
269
+ .pattern(/[\x01-\x1F\x7F]/, {
270
+ name: 'control characters',
271
+ invert: true,
272
+ })
273
+ .max(512)
274
+ .required(),
275
+ }
276
+ : {};
277
+ const emailFields = tilmeld.config.userFields.includes('email')
278
+ ? {
279
+ email: Joi.string()
280
+ .trim(false)
281
+ .email({ minDomainSegments: 1, tlds: false })
282
+ .required(),
283
+ secret: Joi.string()
284
+ .trim(false)
285
+ .pattern(/[\x01-\x1F\x7F]/, {
286
+ name: 'control characters',
287
+ invert: true,
288
+ })
289
+ .length(21),
290
+ emailChangeDate: Joi.number(),
291
+ newEmailSecret: Joi.string()
292
+ .trim(false)
293
+ .pattern(/[\x01-\x1F\x7F]/, {
294
+ name: 'control characters',
295
+ invert: true,
296
+ })
297
+ .length(21),
298
+ newEmailAddress: Joi.string()
299
+ .trim(false)
300
+ .email({ minDomainSegments: 1, tlds: false }),
301
+ cancelEmailSecret: Joi.string()
302
+ .trim(false)
303
+ .pattern(/[\x01-\x1F\x7F]/, {
304
+ name: 'control characters',
305
+ invert: true,
306
+ })
307
+ .length(21),
308
+ cancelEmailAddress: Joi.string()
309
+ .trim(false)
310
+ .email({ minDomainSegments: 1, tlds: false }),
311
+ recoverSecret: Joi.string()
312
+ .trim(false)
313
+ .pattern(/[\x01-\x1F\x7F]/, {
314
+ name: 'control characters',
315
+ invert: true,
316
+ })
317
+ .length(10),
318
+ recoverSecretDate: Joi.number(),
319
+ }
320
+ : {};
321
+ const phoneFields = tilmeld.config.userFields.includes('phone')
322
+ ? {
323
+ phone: Joi.string()
324
+ .trim(false)
325
+ .pattern(/^[0-9]+$/, 'numbers'),
326
+ }
327
+ : {};
211
328
  Joi.attempt(
212
329
  user.$getValidatable(),
213
330
  Joi.object().keys({
214
331
  ...nymphJoiProps,
332
+ ...nameFields,
333
+ ...emailFields,
334
+ ...phoneFields,
215
335
  username: Joi.string().trim(false).required(),
216
336
  enabled: Joi.boolean().required(),
217
- email: Joi.string()
218
- .trim(false)
219
- .email({ minDomainSegments: 1, tlds: false })
220
- .required(),
221
- nameFirst: Joi.string()
222
- .trim(false)
223
- .pattern(/[\x01-\x1F\x7F]/, {
224
- name: 'control characters',
225
- invert: true,
226
- })
227
- .max(512)
228
- .required(),
229
- nameMiddle: Joi.string()
230
- .trim(false)
231
- .pattern(/[\x01-\x1F\x7F]/, {
232
- name: 'control characters',
233
- invert: true,
234
- })
235
- .max(512),
236
- nameLast: Joi.string()
237
- .trim(false)
238
- .pattern(/[\x01-\x1F\x7F]/, {
239
- name: 'control characters',
240
- invert: true,
241
- })
242
- .max(512),
243
- name: Joi.string()
244
- .trim(false)
245
- .pattern(/[\x01-\x1F\x7F]/, {
246
- name: 'control characters',
247
- invert: true,
248
- })
249
- .max(512)
250
- .required(),
251
337
  avatar: Joi.string()
252
338
  .trim(false)
253
339
  .uri()
@@ -256,9 +342,6 @@ export default {
256
342
  invert: true,
257
343
  })
258
344
  .max(1024),
259
- phone: Joi.string()
260
- .trim(false)
261
- .pattern(/^[0-9]+$/, 'numbers'),
262
345
  group: Joi.object().instance(Group),
263
346
  groups: Joi.array().items(Joi.object().instance(Group)).required(),
264
347
  abilities: Joi.array().items(
@@ -267,49 +350,19 @@ export default {
267
350
  name: 'control characters',
268
351
  invert: true,
269
352
  })
270
- .max(256)
353
+ .max(256),
271
354
  ),
272
355
  inheritAbilities: Joi.boolean(),
273
- secret: Joi.string()
274
- .trim(false)
275
- .pattern(/[\x01-\x1F\x7F]/, {
276
- name: 'control characters',
277
- invert: true,
278
- })
279
- .length(21),
280
- emailChangeDate: Joi.number(),
281
- newEmailSecret: Joi.string()
282
- .trim(false)
283
- .pattern(/[\x01-\x1F\x7F]/, {
284
- name: 'control characters',
285
- invert: true,
286
- })
287
- .length(21),
288
- newEmailAddress: Joi.string()
289
- .trim(false)
290
- .email({ minDomainSegments: 1, tlds: false }),
291
- cancelEmailSecret: Joi.string()
292
- .trim(false)
293
- .pattern(/[\x01-\x1F\x7F]/, {
294
- name: 'control characters',
295
- invert: true,
296
- })
297
- .length(21),
298
- cancelEmailAddress: Joi.string()
299
- .trim(false)
300
- .email({ minDomainSegments: 1, tlds: false }),
301
- recoverSecret: Joi.string()
302
- .trim(false)
303
- .pattern(/[\x01-\x1F\x7F]/, {
304
- name: 'control characters',
305
- invert: true,
306
- })
307
- .length(10),
308
- recoverSecretDate: Joi.number(),
309
356
  salt: Joi.string().trim(false),
310
357
  password: Joi.string().trim(false).required(),
358
+ revokeTokenDate: Joi.number(),
359
+ totpSecret: Joi.string()
360
+ .trim(false)
361
+ .min(24)
362
+ .max(40)
363
+ .pattern(/^[A-Z234567]+$/, 'base32 encoded unpadded'),
311
364
  }),
312
- 'Invalid User: '
365
+ 'Invalid User: ',
313
366
  );
314
367
  },
315
368
  } as Config;
package/src/conf/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { Config } from './d';
1
+ export { Config } from './d.js';
2
2
 
3
- import defaults from './defaults';
3
+ import defaults from './defaults.js';
4
4
  export { defaults as ConfigDefaults };
@@ -0,0 +1,20 @@
1
+ import { Entity, EntityConstructor, Nymph } from '@nymphjs/nymph';
2
+
3
+ import Tilmeld from './Tilmeld.js';
4
+
5
+ export function enforceTilmeld(object: Nymph | Entity | EntityConstructor) {
6
+ let tilmeld: Tilmeld | undefined;
7
+ if (object instanceof Entity) {
8
+ tilmeld = object.$nymph.tilmeld as Tilmeld | undefined;
9
+ } else if (object instanceof Nymph) {
10
+ tilmeld = object.tilmeld as Tilmeld | undefined;
11
+ } else {
12
+ tilmeld = object.nymph.tilmeld as Tilmeld | undefined;
13
+ }
14
+
15
+ if (tilmeld == null) {
16
+ throw new Error('Tilmeld was not configured on this Nymph instance!');
17
+ }
18
+
19
+ return tilmeld;
20
+ }
@@ -1,6 +1,8 @@
1
- export class AccessControlError extends Error {
1
+ import { HttpError } from '@nymphjs/server';
2
+
3
+ export class AccessControlError extends HttpError {
2
4
  constructor(message: string) {
3
- super(message);
5
+ super(message, 403);
4
6
  this.name = 'AccessControlError';
5
7
  }
6
8
  }
@@ -1,6 +1,8 @@
1
- export class BadDataError extends Error {
1
+ import { HttpError } from '@nymphjs/server';
2
+
3
+ export class BadDataError extends HttpError {
2
4
  constructor(message: string) {
3
- super(message);
5
+ super(message, 400);
4
6
  this.name = 'BadDataError';
5
7
  }
6
8
  }
@@ -1,6 +1,8 @@
1
- export class BadEmailError extends Error {
1
+ import { HttpError } from '@nymphjs/server';
2
+
3
+ export class BadEmailError extends HttpError {
2
4
  constructor(message: string) {
3
- super(message);
5
+ super(message, 400);
4
6
  this.name = 'BadEmailError';
5
7
  }
6
8
  }
@@ -1,6 +1,8 @@
1
- export class BadUsernameError extends Error {
1
+ import { HttpError } from '@nymphjs/server';
2
+
3
+ export class BadUsernameError extends HttpError {
2
4
  constructor(message: string) {
3
- super(message);
5
+ super(message, 400);
4
6
  this.name = 'BadUsernameError';
5
7
  }
6
8
  }
@@ -1,11 +1,11 @@
1
- export * from './AccessControlError';
1
+ export * from './AccessControlError.js';
2
2
 
3
- export * from './BadDataError';
3
+ export * from './BadDataError.js';
4
4
 
5
- export * from './BadEmailError';
5
+ export * from './BadEmailError.js';
6
6
 
7
- export * from './BadUsernameError';
7
+ export * from './BadUsernameError.js';
8
8
 
9
- export * from './CouldNotChangeDefaultPrimaryGroupError';
9
+ export * from './CouldNotChangeDefaultPrimaryGroupError.js';
10
10
 
11
- export * from './EmailChangeRateLimitExceededError';
11
+ export * from './EmailChangeRateLimitExceededError.js';
package/src/index.ts CHANGED
@@ -1,22 +1,24 @@
1
- export * from './conf';
1
+ export * from './conf/index.js';
2
2
 
3
- export * from './errors';
3
+ export * from './enforceTilmeld.js';
4
4
 
5
- import AbleObject from './AbleObject';
5
+ export * from './errors/index.js';
6
+
7
+ import AbleObject from './AbleObject.js';
6
8
  export { AbleObject };
7
9
 
8
- export * from './Group';
9
- import Group from './Group';
10
+ export * from './Group.js';
11
+ import Group from './Group.js';
10
12
  export { Group };
11
13
 
12
- import Tilmeld from './Tilmeld';
14
+ import Tilmeld from './Tilmeld.js';
13
15
  export { Tilmeld };
14
16
 
15
- export * from './Tilmeld.types';
17
+ export * from './Tilmeld.types.js';
16
18
 
17
- import tilmeldJoiProps from './tilmeldJoiProps';
19
+ import tilmeldJoiProps from './tilmeldJoiProps.js';
18
20
  export { tilmeldJoiProps };
19
21
 
20
- export * from './User';
21
- import User from './User';
22
+ export * from './User.js';
23
+ import User from './User.js';
22
24
  export { User };