@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 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 } = this.settings;
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.0.10",
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.0.10",
9
- "@semapps/middlewares": "1.0.10",
10
- "@semapps/mime-types": "1.0.10",
11
- "@semapps/triplestore": "1.0.10",
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": "5da36242c69e3969d9af815b790734cbce40f1f1"
33
+ "gitHead": "5782cd74389aea502368e292be5b827ddf3d123d"
33
34
  }
@@ -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
- email = email && email.toLowerCase();
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
- const emailExists = !email ? false : await ctx.call('auth.account.emailExists', { email });
23
- if (emailExists) {
24
- throw new Error('email.already.exists');
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) await this.isValidUsername(ctx, username);
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 an username based on the email
31
- const usernameFromEmail = email.split('@')[0].toLowerCase();
32
- let usernameValid = false;
33
- let i = 0;
34
- do {
35
- username = i === 0 ? usernameFromEmail : usernameFromEmail + i;
36
- try {
37
- usernameValid = await this.isValidUsername(ctx, username);
38
- } catch (e) {
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
- i++;
42
- } while (!usernameValid);
43
- } else throw new Error('you must provide at least a username or an email address');
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
- throw new Error('username.invalid');
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
- throw new Error('username.already.exists');
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
- throw new Error('username.already.exists');
276
+ error = 'username.already.exists';
236
277
  }
237
278
 
238
- return true;
279
+ return { isValid: !error, error };
239
280
  },
240
281
  async hashPassword(password) {
241
282
  return new Promise((resolve, reject) => {
@@ -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
@@ -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
- description: {
20
- labelMap: {
21
- en: 'Capabilities'
22
- },
23
- internal: true
24
- }
20
+ typeIndex: 'private'
25
21
  },
26
22
  hooks: {
27
23
  before: {