@semapps/auth 1.0.10 → 1.1.1
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/mixins/auth.js +5 -2
- package/package.json +7 -6
- package/services/account.js +66 -25
- package/services/auth.local.js +4 -5
- package/services/capabilities.js +2 -6
package/mixins/auth.js
CHANGED
@@ -14,6 +14,8 @@ const AuthMixin = {
|
|
14
14
|
capabilitiesPath: undefined,
|
15
15
|
registrationAllowed: true,
|
16
16
|
reservedUsernames: [],
|
17
|
+
minPasswordLength: 1,
|
18
|
+
minUsernameLength: 1,
|
17
19
|
webIdSelection: [],
|
18
20
|
accountSelection: [],
|
19
21
|
accountsDataset: 'settings',
|
@@ -21,7 +23,8 @@ const AuthMixin = {
|
|
21
23
|
},
|
22
24
|
dependencies: ['api'],
|
23
25
|
async created() {
|
24
|
-
const { jwtPath, reservedUsernames, accountsDataset, podProvider } =
|
26
|
+
const { jwtPath, reservedUsernames, minPasswordLength, minUsernameLength, accountsDataset, podProvider } =
|
27
|
+
this.settings;
|
25
28
|
|
26
29
|
this.broker.createService({
|
27
30
|
mixins: [AuthJWTService],
|
@@ -30,7 +33,7 @@ const AuthMixin = {
|
|
30
33
|
|
31
34
|
this.broker.createService({
|
32
35
|
mixins: [AuthAccountService],
|
33
|
-
settings: { reservedUsernames },
|
36
|
+
settings: { reservedUsernames, minPasswordLength, minUsernameLength },
|
34
37
|
adapter: new TripleStoreAdapter({ type: 'AuthAccount', dataset: accountsDataset })
|
35
38
|
});
|
36
39
|
|
package/package.json
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
{
|
2
2
|
"name": "@semapps/auth",
|
3
|
-
"version": "1.
|
3
|
+
"version": "1.1.1",
|
4
4
|
"description": "Authentification module for SemApps",
|
5
5
|
"license": "Apache-2.0",
|
6
6
|
"author": "Virtual Assembly",
|
7
7
|
"dependencies": {
|
8
|
-
"@semapps/ldp": "1.
|
9
|
-
"@semapps/middlewares": "1.
|
10
|
-
"@semapps/mime-types": "1.
|
11
|
-
"@semapps/triplestore": "1.
|
8
|
+
"@semapps/ldp": "1.1.1",
|
9
|
+
"@semapps/middlewares": "1.1.1",
|
10
|
+
"@semapps/mime-types": "1.1.1",
|
11
|
+
"@semapps/triplestore": "1.1.1",
|
12
12
|
"bcrypt": "^5.0.1",
|
13
13
|
"express-session": "^1.17.0",
|
14
14
|
"jsonwebtoken": "^8.5.1",
|
@@ -21,6 +21,7 @@
|
|
21
21
|
"passport-cas2": "0.0.12",
|
22
22
|
"passport-local": "^1.0.0",
|
23
23
|
"pug": "^3.0.2",
|
24
|
+
"speakingurl": "^14.0.1",
|
24
25
|
"url-join": "^4.0.1"
|
25
26
|
},
|
26
27
|
"publishConfig": {
|
@@ -29,5 +30,5 @@
|
|
29
30
|
"engines": {
|
30
31
|
"node": ">=14"
|
31
32
|
},
|
32
|
-
"gitHead": "
|
33
|
+
"gitHead": "5782cd74389aea502368e292be5b827ddf3d123d"
|
33
34
|
}
|
package/services/account.js
CHANGED
@@ -1,53 +1,88 @@
|
|
1
1
|
const bcrypt = require('bcrypt');
|
2
|
+
const createSlug = require('speakingurl');
|
2
3
|
const DbService = require('moleculer-db');
|
3
4
|
const { TripleStoreAdapter } = require('@semapps/triplestore');
|
4
5
|
const crypto = require('crypto');
|
5
6
|
|
7
|
+
// Taken from https://stackoverflow.com/a/9204568/7900695
|
8
|
+
const emailRegexp = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
9
|
+
|
6
10
|
module.exports = {
|
7
11
|
name: 'auth.account',
|
8
12
|
mixins: [DbService],
|
9
13
|
adapter: new TripleStoreAdapter({ type: 'AuthAccount', dataset: 'settings' }),
|
10
14
|
settings: {
|
11
15
|
idField: '@id',
|
12
|
-
reservedUsernames: ['relay']
|
16
|
+
reservedUsernames: ['relay'],
|
17
|
+
minPasswordLength: 1,
|
18
|
+
minUsernameLength: 1
|
13
19
|
},
|
14
20
|
dependencies: ['triplestore'],
|
15
21
|
actions: {
|
16
22
|
async create(ctx) {
|
17
23
|
let { uuid, username, password, email, webId, ...rest } = ctx.params;
|
18
|
-
const hashedPassword = password ? await this.hashPassword(password) : undefined;
|
19
24
|
|
20
|
-
|
25
|
+
// FORMAT AND VERIFY PASSWORD
|
26
|
+
|
27
|
+
if (password) {
|
28
|
+
if (password.length < this.settings.minPasswordLength) {
|
29
|
+
throw new Error('password.too-short');
|
30
|
+
}
|
31
|
+
|
32
|
+
password = await this.hashPassword(password);
|
33
|
+
}
|
34
|
+
|
35
|
+
// FORMAT AND VERIFY EMAIL
|
21
36
|
|
22
|
-
|
23
|
-
|
24
|
-
|
37
|
+
if (email) {
|
38
|
+
email = email.toLowerCase();
|
39
|
+
|
40
|
+
const emailExists = await ctx.call('auth.account.emailExists', { email });
|
41
|
+
if (emailExists) {
|
42
|
+
throw new Error('email.already.exists');
|
43
|
+
}
|
44
|
+
|
45
|
+
if (!emailRegexp.test(email)) {
|
46
|
+
throw new Error('email.invalid');
|
47
|
+
}
|
25
48
|
}
|
26
49
|
|
50
|
+
// FORMAT AND VERIFY USERNAME
|
51
|
+
|
27
52
|
if (username) {
|
28
|
-
if (!ctx.meta.isSystemCall)
|
53
|
+
if (!ctx.meta.isSystemCall) {
|
54
|
+
const { isValid, error } = await this.isValidUsername(ctx, username);
|
55
|
+
if (!isValid) throw new Error(error);
|
56
|
+
}
|
29
57
|
} else if (email) {
|
30
|
-
// If username is not provided, find
|
31
|
-
|
32
|
-
|
33
|
-
let
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
// Do nothing, the loop will continue
|
58
|
+
// If username is not provided, find one automatically from the email (without errors)
|
59
|
+
username = createSlug(email.split('@')[0].toLowerCase());
|
60
|
+
|
61
|
+
let { isValid, error } = await this.isValidUsername(ctx, username);
|
62
|
+
|
63
|
+
if (!isValid) {
|
64
|
+
if (error === 'username.invalid' || error === 'username.too-short') {
|
65
|
+
// If username generated from email is invalid, use a generic name
|
66
|
+
username = 'user';
|
40
67
|
}
|
41
|
-
|
42
|
-
|
43
|
-
|
68
|
+
|
69
|
+
// If necessary, add a number after the username
|
70
|
+
let i = 0;
|
71
|
+
do {
|
72
|
+
username = i === 0 ? username : username + i;
|
73
|
+
({ isValid } = await this.isValidUsername(ctx, username));
|
74
|
+
} while (!isValid);
|
75
|
+
}
|
76
|
+
} else {
|
77
|
+
throw new Error('You must provide at least a username or an email address');
|
78
|
+
}
|
44
79
|
|
45
80
|
return await this._create(ctx, {
|
46
81
|
...rest,
|
47
82
|
uuid,
|
48
83
|
username,
|
49
84
|
email,
|
50
|
-
hashedPassword,
|
85
|
+
hashedPassword: password,
|
51
86
|
webId
|
52
87
|
});
|
53
88
|
},
|
@@ -219,23 +254,29 @@ module.exports = {
|
|
219
254
|
},
|
220
255
|
methods: {
|
221
256
|
async isValidUsername(ctx, username) {
|
257
|
+
let error;
|
258
|
+
|
222
259
|
// Ensure the username has no space or special characters
|
223
260
|
if (!/^[a-z0-9\-+_.]+$/.exec(username)) {
|
224
|
-
|
261
|
+
error = 'username.invalid';
|
262
|
+
}
|
263
|
+
|
264
|
+
if (username.length < this.settings.minUsernameLength) {
|
265
|
+
error = 'username.too-short';
|
225
266
|
}
|
226
267
|
|
227
268
|
// Ensure we don't use reservedUsernames
|
228
269
|
if (this.settings.reservedUsernames.includes(username)) {
|
229
|
-
|
270
|
+
error = 'username.reserved';
|
230
271
|
}
|
231
272
|
|
232
273
|
// Ensure username doesn't already exist
|
233
274
|
const usernameExists = await ctx.call('auth.account.usernameExists', { username });
|
234
275
|
if (usernameExists) {
|
235
|
-
|
276
|
+
error = 'username.already.exists';
|
236
277
|
}
|
237
278
|
|
238
|
-
return
|
279
|
+
return { isValid: !error, error };
|
239
280
|
},
|
240
281
|
async hashPassword(password) {
|
241
282
|
return new Promise((resolve, reject) => {
|
package/services/auth.local.js
CHANGED
@@ -14,6 +14,8 @@ const AuthLocalService = {
|
|
14
14
|
jwtPath: null,
|
15
15
|
registrationAllowed: true,
|
16
16
|
reservedUsernames: [],
|
17
|
+
minPasswordLength: 1,
|
18
|
+
minUsernameLength: 1,
|
17
19
|
webIdSelection: [],
|
18
20
|
accountSelection: [],
|
19
21
|
formUrl: null,
|
@@ -47,13 +49,10 @@ const AuthLocalService = {
|
|
47
49
|
actions: {
|
48
50
|
async signup(ctx) {
|
49
51
|
const { username, email, password, ...rest } = ctx.params;
|
52
|
+
|
50
53
|
// This is going to get in our way otherwise when waiting for completions.
|
51
54
|
ctx.meta.skipObjectsWatcher = true;
|
52
55
|
|
53
|
-
if (username && username.length < 2) {
|
54
|
-
throw new MoleculerError('The username must be at least 2 characters long', 400, 'BAD_REQUEST');
|
55
|
-
}
|
56
|
-
|
57
56
|
let accountData = await ctx.call('auth.account.create', {
|
58
57
|
username,
|
59
58
|
email,
|
@@ -62,7 +61,7 @@ const AuthLocalService = {
|
|
62
61
|
});
|
63
62
|
|
64
63
|
try {
|
65
|
-
const profileData = { nick: username, email, ...rest };
|
64
|
+
const profileData = { nick: accountData.username, email: accountData.email, ...rest };
|
66
65
|
const webId = await ctx.call('webid.createWebId', this.pickWebIdData(profileData), {
|
67
66
|
meta: {
|
68
67
|
isSignup: true // Allow services to handle directly the webId creation if it is generated by the AuthService
|
package/services/capabilities.js
CHANGED
@@ -12,16 +12,12 @@ const CapabilitiesService = {
|
|
12
12
|
mixins: [ControlledContainerMixin],
|
13
13
|
settings: {
|
14
14
|
path: CAPABILITIES_ROUTE,
|
15
|
+
acceptedTypes: ['acl:Authorization'],
|
15
16
|
excludeFromMirror: true,
|
16
17
|
activateTombstones: false,
|
17
18
|
permissions: {},
|
18
19
|
newResourcesPermissions: {},
|
19
|
-
|
20
|
-
labelMap: {
|
21
|
-
en: 'Capabilities'
|
22
|
-
},
|
23
|
-
internal: true
|
24
|
-
}
|
20
|
+
typeIndex: 'private'
|
25
21
|
},
|
26
22
|
hooks: {
|
27
23
|
before: {
|