@nymphjs/tilmeld 1.0.0-beta.9 → 1.0.0-beta.91
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 +414 -0
- package/README.md +15 -2
- package/dist/AbleObject.d.ts +15 -0
- package/dist/AbleObject.js +18 -6
- package/dist/AbleObject.js.map +1 -1
- package/dist/Group.d.ts +181 -15
- package/dist/Group.js +551 -192
- package/dist/Group.js.map +1 -1
- package/dist/Group.test.js +83 -13
- package/dist/Group.test.js.map +1 -1
- package/dist/Tilmeld.d.ts +241 -12
- package/dist/Tilmeld.js +636 -254
- package/dist/Tilmeld.js.map +1 -1
- package/dist/Tilmeld.types.d.ts +2 -2
- package/dist/Tilmeld.types.js +1 -2
- package/dist/User.d.ts +395 -22
- package/dist/User.js +1316 -365
- package/dist/User.js.map +1 -1
- package/dist/User.test.js +132 -13
- package/dist/User.test.js.map +1 -1
- package/dist/conf/d.d.ts +287 -7
- package/dist/conf/d.js +1 -2
- package/dist/conf/defaults.d.ts +1 -1
- package/dist/conf/defaults.js +188 -141
- package/dist/conf/defaults.js.map +1 -1
- package/dist/conf/index.d.ts +2 -2
- package/dist/conf/index.js +2 -8
- package/dist/conf/index.js.map +1 -1
- package/dist/enforceTilmeld.d.ts +3 -0
- package/dist/enforceTilmeld.js +18 -0
- package/dist/enforceTilmeld.js.map +1 -0
- package/dist/errors/AccessControlError.d.ts +2 -1
- package/dist/errors/AccessControlError.js +3 -6
- package/dist/errors/AccessControlError.js.map +1 -1
- package/dist/errors/BadDataError.d.ts +2 -1
- package/dist/errors/BadDataError.js +3 -6
- package/dist/errors/BadDataError.js.map +1 -1
- package/dist/errors/BadEmailError.d.ts +2 -1
- package/dist/errors/BadEmailError.js +3 -6
- package/dist/errors/BadEmailError.js.map +1 -1
- package/dist/errors/BadUsernameError.d.ts +2 -1
- package/dist/errors/BadUsernameError.js +3 -6
- package/dist/errors/BadUsernameError.js.map +1 -1
- package/dist/errors/CouldNotChangeDefaultPrimaryGroupError.js +1 -5
- package/dist/errors/CouldNotChangeDefaultPrimaryGroupError.js.map +1 -1
- package/dist/errors/EmailChangeRateLimitExceededError.js +1 -5
- package/dist/errors/EmailChangeRateLimitExceededError.js.map +1 -1
- package/dist/errors/index.d.ts +6 -6
- package/dist/errors/index.js +6 -22
- package/dist/errors/index.js.map +1 -1
- package/dist/index.d.ts +11 -10
- package/dist/index.js +16 -35
- package/dist/index.js.map +1 -1
- package/dist/tilmeldJoiProps.js +13 -18
- package/dist/tilmeldJoiProps.js.map +1 -1
- package/jest.config.js +11 -2
- package/package.json +34 -31
- package/src/AbleObject.ts +2 -2
- package/src/Group.test.ts +104 -6
- package/src/Group.ts +661 -226
- package/src/Tilmeld.ts +420 -254
- package/src/Tilmeld.types.ts +2 -2
- package/src/User.test.ts +163 -6
- package/src/User.ts +1557 -454
- package/src/conf/d.ts +63 -17
- package/src/conf/defaults.ts +162 -109
- package/src/conf/index.ts +2 -2
- package/src/enforceTilmeld.ts +20 -0
- package/src/errors/AccessControlError.ts +4 -2
- package/src/errors/BadDataError.ts +4 -2
- package/src/errors/BadEmailError.ts +4 -2
- package/src/errors/BadUsernameError.ts +4 -2
- package/src/errors/index.ts +6 -6
- package/src/index.ts +12 -10
- package/src/tilmeldJoiProps.ts +11 -11
- package/src/type/locutus.d.ts +3 -0
- package/tsconfig.json +5 -3
- 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
|
|
5
|
-
import
|
|
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
|
-
*
|
|
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: (
|
|
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,
|
|
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
|
|
236
|
-
*
|
|
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
|
}
|
package/src/conf/defaults.ts
CHANGED
|
@@ -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 *
|
|
55
|
-
|
|
56
|
-
|
|
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:
|
|
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
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
}
|
package/src/errors/index.ts
CHANGED
|
@@ -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 './
|
|
3
|
+
export * from './enforceTilmeld.js';
|
|
4
4
|
|
|
5
|
-
|
|
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 };
|