@nymphjs/tilmeld 1.0.0-beta.5 → 1.0.0-beta.51

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/conf/d.ts CHANGED
@@ -9,7 +9,7 @@ import User, { UserData } from '../User';
9
9
  */
10
10
  export interface Config {
11
11
  /**
12
- * The name of the app. Used in emails sent to users.
12
+ * The name of the app. Used in emails sent to users and as the TOTP issuer.
13
13
  */
14
14
  appName: string;
15
15
  /**
@@ -206,7 +206,11 @@ export interface Config {
206
206
  */
207
207
  validEmailRegexNotice: string;
208
208
  /**
209
- * The maximum length for usernames. Infinity for unlimited.
209
+ * The minimum length for usernames.
210
+ */
211
+ minUsernameLength: number;
212
+ /**
213
+ * The maximum length for usernames. `Infinity` for unlimited.
210
214
  */
211
215
  maxUsernameLength: number;
212
216
  /**
@@ -217,6 +221,11 @@ export interface Config {
217
221
  * How long from current time, in seconds, the JWT token expires.
218
222
  */
219
223
  jwtExpire: number;
224
+ /**
225
+ * How long from current time, in seconds, the JWT token for a user switch
226
+ * expires.
227
+ */
228
+ jwtSwitchExpire: number;
220
229
  /**
221
230
  * How long, in seconds, before the JWT token expires to give the user a new
222
231
  * one.
@@ -225,21 +234,25 @@ export interface Config {
225
234
  /**
226
235
  * Function to build the JWT for user sessions.
227
236
  */
228
- jwtBuilder: (config: Config, user: User) => string;
237
+ jwtBuilder: (
238
+ config: Config,
239
+ user: User & UserData,
240
+ switchToken?: boolean,
241
+ ) => string;
229
242
  /**
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.
243
+ * Function to verify that a JWT was signed with the secret key, validate its
244
+ * data, validate the XSRF token, and extract the GUID and dates.
232
245
  *
233
246
  * If no XSRF token is supplied, ignore it.
234
247
  *
235
- * Return false if the JWT is not valid, or an array of GUID and expire
236
- * timestamp otherwise.
248
+ * Return false if the JWT is not valid, or an object with GUID, issued date,
249
+ * and expire date otherwise.
237
250
  */
238
251
  jwtExtract: (
239
252
  config: Config,
240
253
  token: string,
241
- xsrfToken?: string
242
- ) => { guid: string; expire: Date } | null;
254
+ xsrfToken?: string,
255
+ ) => { guid: string; issued: Date; expire: Date } | null;
243
256
  /**
244
257
  * The absolute path to the email template directory. Used by the default
245
258
  * email sender.
@@ -255,7 +268,7 @@ export interface Config {
255
268
  configEmail: (
256
269
  tilmeld: Tilmeld,
257
270
  options: EmailOptions,
258
- user: User & UserData
271
+ user: User & UserData,
259
272
  ) => Promise<EmailConfig>;
260
273
  /**
261
274
  * Send an email to a user. Uses `email-templates` by default.
@@ -285,7 +298,7 @@ export interface Config {
285
298
  sendEmail: (
286
299
  tilmeld: Tilmeld,
287
300
  options: EmailOptions,
288
- user: User & UserData
301
+ user: User & UserData,
289
302
  ) => Promise<boolean>;
290
303
  /**
291
304
  * The address you'd like to receive a notification of registered users, if
@@ -49,11 +49,13 @@ export default {
49
49
  validEmailRegex:
50
50
  /^[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
51
  validEmailRegexNotice: 'Email must be a correctly formatted address.',
52
+ minUsernameLength: 1,
52
53
  maxUsernameLength: 128,
53
54
  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) => {
55
+ jwtExpire: 60 * 60 * 24 * 7 * 24, // 24 weeks(ish) (6 months)
56
+ jwtSwitchExpire: 60 * 60 * 6, // 6 hours
57
+ jwtRenew: 60 * 60 * 24 * 7 * 12, // 12 weeks(ish) (3 months)
58
+ jwtBuilder: (config, user, switchToken) => {
57
59
  const secret = config.jwtSecret;
58
60
  if (secret === '') {
59
61
  throw new Error('JWT secret is not configured.');
@@ -62,12 +64,14 @@ export default {
62
64
  return jwt.sign(
63
65
  {
64
66
  iss: config.appUrl,
65
- nbf: Date.now() / 1000,
67
+ nbf: Math.floor(Date.now() / 1000),
68
+ exp:
69
+ Math.floor(Date.now() / 1000) +
70
+ (switchToken ? config.jwtSwitchExpire : config.jwtExpire),
66
71
  guid: user.guid,
67
72
  xsrfToken: 'TILMELDXSRF-' + nanoid(),
68
73
  },
69
74
  secret,
70
- { expiresIn: config.jwtExpire }
71
75
  );
72
76
  },
73
77
  jwtExtract: (config, token, xsrfToken?) => {
@@ -79,14 +83,14 @@ export default {
79
83
  try {
80
84
  const payload = jwt.verify(token, secret, {
81
85
  issuer: config.appUrl,
82
- clockTolerance: 10,
86
+ clockTolerance: 60,
83
87
  });
84
88
 
85
89
  if (typeof payload === 'string') {
86
90
  return null;
87
91
  }
88
92
 
89
- const { guid, xsrfToken: jwtXsrfToken, exp } = payload;
93
+ const { guid, xsrfToken: jwtXsrfToken, exp, nbf } = payload;
90
94
 
91
95
  if (xsrfToken != null && xsrfToken !== jwtXsrfToken) {
92
96
  return null;
@@ -96,13 +100,19 @@ export default {
96
100
  return null;
97
101
  }
98
102
 
103
+ if (nbf == null) {
104
+ return null;
105
+ }
106
+
107
+ const issued = new Date(nbf * 1000);
108
+
99
109
  if (exp == null) {
100
110
  return null;
101
111
  }
102
112
 
103
113
  const expire = new Date(exp * 1000);
104
114
 
105
- return { guid, expire };
115
+ return { guid, issued, expire };
106
116
  } catch (e: any) {
107
117
  return null;
108
118
  }
@@ -121,14 +131,14 @@ export default {
121
131
  },
122
132
  sendEmail: async (tilmeld, options, user) => {
123
133
  const email = new Email(
124
- await tilmeld.config.configEmail(tilmeld, options, user)
134
+ await tilmeld.config.configEmail(tilmeld, options, user),
125
135
  );
126
136
  try {
127
137
  const result = await email.send({
128
138
  ...options,
129
139
  template: path.join(
130
140
  tilmeld.config.emailTemplateDir,
131
- options.template ?? '.'
141
+ options.template ?? '.',
132
142
  ),
133
143
  locals: {
134
144
  // System
@@ -198,13 +208,13 @@ export default {
198
208
  name: 'control characters',
199
209
  invert: true,
200
210
  })
201
- .max(256)
211
+ .max(256),
202
212
  ),
203
213
  defaultPrimary: Joi.boolean(),
204
214
  defaultSecondary: Joi.boolean(),
205
215
  unverifiedSecondary: Joi.boolean(),
206
216
  }),
207
- 'Invalid Group: '
217
+ 'Invalid Group: ',
208
218
  );
209
219
  },
210
220
  validatorUser: (user) => {
@@ -267,7 +277,7 @@ export default {
267
277
  name: 'control characters',
268
278
  invert: true,
269
279
  })
270
- .max(256)
280
+ .max(256),
271
281
  ),
272
282
  inheritAbilities: Joi.boolean(),
273
283
  secret: Joi.string()
@@ -308,8 +318,14 @@ export default {
308
318
  recoverSecretDate: Joi.number(),
309
319
  salt: Joi.string().trim(false),
310
320
  password: Joi.string().trim(false).required(),
321
+ revokeTokenDate: Joi.number(),
322
+ totpSecret: Joi.string()
323
+ .trim(false)
324
+ .min(24)
325
+ .max(40)
326
+ .pattern(/^[A-Z234567]+$/, 'base32 encoded unpadded'),
311
327
  }),
312
- 'Invalid User: '
328
+ 'Invalid User: ',
313
329
  );
314
330
  },
315
331
  } as Config;
@@ -0,0 +1,20 @@
1
+ import { Entity, EntityConstructor, Nymph } from '@nymphjs/nymph';
2
+
3
+ import Tilmeld from './Tilmeld';
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
+ }
package/src/index.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  export * from './conf';
2
2
 
3
+ export * from './enforceTilmeld';
4
+
3
5
  export * from './errors';
4
6
 
5
7
  import AbleObject from './AbleObject';
@@ -11,36 +11,36 @@ export default {
11
11
  TilmeldAccessLevels.NO_ACCESS,
12
12
  TilmeldAccessLevels.READ_ACCESS,
13
13
  TilmeldAccessLevels.WRITE_ACCESS,
14
- TilmeldAccessLevels.FULL_ACCESS
14
+ TilmeldAccessLevels.FULL_ACCESS,
15
15
  ),
16
16
  acGroup: Joi.number().valid(
17
17
  TilmeldAccessLevels.NO_ACCESS,
18
18
  TilmeldAccessLevels.READ_ACCESS,
19
19
  TilmeldAccessLevels.WRITE_ACCESS,
20
- TilmeldAccessLevels.FULL_ACCESS
20
+ TilmeldAccessLevels.FULL_ACCESS,
21
21
  ),
22
22
  acOther: Joi.number().valid(
23
23
  TilmeldAccessLevels.NO_ACCESS,
24
24
  TilmeldAccessLevels.READ_ACCESS,
25
25
  TilmeldAccessLevels.WRITE_ACCESS,
26
- TilmeldAccessLevels.FULL_ACCESS
26
+ TilmeldAccessLevels.FULL_ACCESS,
27
27
  ),
28
28
  acRead: Joi.array().items(
29
29
  Joi.alternatives().try(
30
30
  Joi.object().instance(User),
31
- Joi.object().instance(Group)
32
- )
31
+ Joi.object().instance(Group),
32
+ ),
33
33
  ),
34
34
  acWrite: Joi.array().items(
35
35
  Joi.alternatives().try(
36
36
  Joi.object().instance(User),
37
- Joi.object().instance(Group)
38
- )
37
+ Joi.object().instance(Group),
38
+ ),
39
39
  ),
40
40
  acFull: Joi.array().items(
41
41
  Joi.alternatives().try(
42
42
  Joi.object().instance(User),
43
- Joi.object().instance(Group)
44
- )
43
+ Joi.object().instance(Group),
44
+ ),
45
45
  ),
46
46
  };
package/typedoc.json ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "extends": ["../../typedoc.base.json"],
3
+ "entryPoints": ["src/index.ts"]
4
+ }