@semapps/auth 0.4.0-alpha.19 → 0.4.0-alpha.21

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/index.js CHANGED
@@ -4,5 +4,6 @@ module.exports = {
4
4
  AuthOIDCService: require('./services/auth.oidc'),
5
5
  AuthAccountService: require('./services/account'),
6
6
  AuthJWTService: require('./services/jwt'),
7
- AuthMigrationService: require('./services/migration')
7
+ AuthMigrationService: require('./services/migration'),
8
+ AuthMailService: require('./services/mail')
8
9
  };
package/package.json CHANGED
@@ -1,26 +1,28 @@
1
1
  {
2
2
  "name": "@semapps/auth",
3
- "version": "0.4.0-alpha.19",
3
+ "version": "0.4.0-alpha.21",
4
4
  "description": "Authentification module for SemApps",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Virtual Assembly",
7
7
  "dependencies": {
8
- "@semapps/mime-types": "0.4.0-alpha.19",
9
- "@semapps/triplestore": "0.4.0-alpha.19",
8
+ "@semapps/mime-types": "0.4.0-alpha.21",
9
+ "@semapps/triplestore": "0.4.0-alpha.21",
10
10
  "bcrypt": "^5.0.1",
11
11
  "express-session": "^1.17.0",
12
12
  "jsonwebtoken": "^8.5.1",
13
13
  "moleculer": "^0.14.17",
14
14
  "moleculer-db": "^0.8.16",
15
15
  "moleculer-web": "^0.10.0-beta1",
16
+ "node-sass": "^7.0.1",
16
17
  "openid-client": "^4.7.4",
17
18
  "passport": "^0.4.1",
18
19
  "passport-cas2": "0.0.12",
19
20
  "passport-local": "^1.0.0",
21
+ "pug": "^3.0.2",
20
22
  "url-join": "^4.0.1"
21
23
  },
22
24
  "publishConfig": {
23
25
  "access": "public"
24
26
  },
25
- "gitHead": "594627f715304cb9ebad832ea7019390c7b56711"
27
+ "gitHead": "03a76d610ac2abb85d8889afcdaa15fceab5039f"
26
28
  }
@@ -1,6 +1,7 @@
1
1
  const bcrypt = require('bcrypt');
2
2
  const DbService = require('moleculer-db');
3
3
  const { TripleStoreAdapter } = require('@semapps/triplestore');
4
+ const crypto = require('crypto');
4
5
 
5
6
  module.exports = {
6
7
  name: 'auth.account',
@@ -88,6 +89,11 @@ module.exports = {
88
89
  const accounts = await this._find(ctx, { query: { webId } });
89
90
  return accounts.length > 0 ? accounts[0] : null;
90
91
  },
92
+ async findByEmail(ctx) {
93
+ const { email } = ctx.params;
94
+ const accounts = await this._find(ctx, { query: { email } });
95
+ return accounts.length > 0 ? accounts[0] : null;
96
+ },
91
97
  async setPassword(ctx) {
92
98
  const { webId, password } = ctx.params;
93
99
  const hashedPassword = await this.hashPassword(password);
@@ -97,6 +103,33 @@ module.exports = {
97
103
  '@id': account['@id'],
98
104
  hashedPassword
99
105
  });
106
+ },
107
+ async setNewPassword(ctx) {
108
+ const { webId, token, password } = ctx.params;
109
+ const hashedPassword = await this.hashPassword(password);
110
+ const account = await ctx.call('auth.account.findByWebId', { webId });
111
+
112
+ if (account.resetPasswordToken !== token) {
113
+ throw new Error('auth.password.invalid_reset_token');
114
+ }
115
+
116
+ return await this._update(ctx, {
117
+ '@id': account['@id'],
118
+ hashedPassword,
119
+ resetPasswordToken: undefined
120
+ });
121
+ },
122
+ async generateResetPasswordToken(ctx) {
123
+ const { webId } = ctx.params;
124
+ const resetPasswordToken = await this.generateResetPasswordToken();
125
+ const account = await ctx.call('auth.account.findByWebId', { webId });
126
+
127
+ await this._update(ctx, {
128
+ '@id': account['@id'],
129
+ resetPasswordToken
130
+ });
131
+
132
+ return resetPasswordToken;
100
133
  }
101
134
  },
102
135
  methods: {
@@ -138,6 +171,16 @@ module.exports = {
138
171
  }
139
172
  });
140
173
  });
174
+ },
175
+ async generateResetPasswordToken() {
176
+ return new Promise(resolve => {
177
+ crypto.randomBytes(32, function(ex, buf) {
178
+ if (ex) {
179
+ reject(ex);
180
+ }
181
+ resolve(buf.toString('hex'));
182
+ });
183
+ });
141
184
  }
142
185
  }
143
186
  };
@@ -2,6 +2,7 @@ const { Strategy } = require('passport-local');
2
2
  const AuthMixin = require('../mixins/auth');
3
3
  const sendToken = require('../middlewares/sendToken');
4
4
  const { MoleculerError } = require('moleculer').Errors;
5
+ const AuthMailService = require('../services/mail');
5
6
 
6
7
  const AuthLocalService = {
7
8
  name: 'auth',
@@ -12,10 +13,29 @@ const AuthLocalService = {
12
13
  registrationAllowed: true,
13
14
  reservedUsernames: [],
14
15
  webIdSelection: [],
15
- accountSelection: []
16
+ accountSelection: [],
17
+ mail: {
18
+ from: null,
19
+ transport: {
20
+ host: null,
21
+ port: null
22
+ },
23
+ defaults: {
24
+ locale: null,
25
+ frontUrl: null
26
+ }
27
+ }
16
28
  },
17
- created() {
29
+ async created() {
30
+ const { mail } = this.settings;
31
+
18
32
  this.passportId = 'local';
33
+
34
+ await this.broker.createService(AuthMailService, {
35
+ settings: {
36
+ ...mail
37
+ }
38
+ });
19
39
  },
20
40
  actions: {
21
41
  async signup(ctx) {
@@ -50,6 +70,33 @@ const AuthLocalService = {
50
70
  const token = await ctx.call('auth.jwt.generateToken', { payload: { webId: accountData.webId } });
51
71
 
52
72
  return { token, webId: accountData.webId, newUser: true };
73
+ },
74
+ async resetPassword(ctx) {
75
+ const { email } = ctx.params;
76
+
77
+ const account = await ctx.call('auth.account.findByEmail', { email });
78
+
79
+ if (!account) {
80
+ throw new Error('email.not.exists');
81
+ }
82
+
83
+ const token = await ctx.call('auth.account.generateResetPasswordToken', { webId: account.webId });
84
+
85
+ await ctx.call('auth.mail.sendResetPasswordEmail', {
86
+ account,
87
+ token
88
+ });
89
+ },
90
+ async setNewPassword(ctx) {
91
+ const { email, token, password } = ctx.params;
92
+
93
+ const account = await ctx.call('auth.account.findByEmail', { email });
94
+
95
+ if (!account) {
96
+ throw new Error('email.not.exists');
97
+ }
98
+
99
+ await ctx.call('auth.account.setNewPassword', { webId: account.webId, token, password });
53
100
  }
54
101
  },
55
102
  methods: {
@@ -89,10 +136,23 @@ const AuthLocalService = {
89
136
  }
90
137
  };
91
138
 
139
+ const resetPasswordRoute = {
140
+ path: '/auth/reset_password',
141
+ aliases: {
142
+ 'POST /': 'auth.resetPassword'
143
+ }
144
+ };
145
+ const setNewPasswordRoute = {
146
+ path: '/auth/new_password',
147
+ aliases: {
148
+ 'POST /': 'auth.setNewPassword'
149
+ }
150
+ };
151
+
92
152
  if (this.settings.registrationAllowed) {
93
- return [loginRoute, signupRoute];
153
+ return [loginRoute, signupRoute, resetPasswordRoute, setNewPasswordRoute];
94
154
  } else {
95
- return [loginRoute];
155
+ return [loginRoute, resetPasswordRoute, setNewPasswordRoute];
96
156
  }
97
157
  }
98
158
  }
@@ -0,0 +1,49 @@
1
+ const MailService = require('moleculer-mail');
2
+ const path = require('path');
3
+
4
+ module.exports = {
5
+ name: 'auth.mail',
6
+ mixins: [MailService],
7
+ settings: {
8
+ defaults: {
9
+ locale: 'en',
10
+ frontUrl: null
11
+ },
12
+ templateFolder: path.join(__dirname, '../templates'),
13
+ from: null,
14
+ transport: null
15
+ },
16
+ actions: {
17
+ async sendResetPasswordEmail(ctx) {
18
+ const { account, token } = ctx.params;
19
+
20
+ await this.actions.send(
21
+ {
22
+ to: account.email,
23
+ template: 'reset-password',
24
+ locale: this.getTemplateLocale(account.preferredLocale || this.settings.defaults.locale),
25
+ data: {
26
+ account,
27
+ token,
28
+ frontUrl: account.preferredFrontUrl || this.settings.defaults.frontUrl
29
+ }
30
+ },
31
+ {
32
+ parentCtx: ctx
33
+ }
34
+ );
35
+ }
36
+ },
37
+ methods: {
38
+ getTemplateLocale(userLocale) {
39
+ switch (userLocale) {
40
+ case 'fr':
41
+ return 'fr-FR';
42
+ case 'en':
43
+ return 'en-EN';
44
+ default:
45
+ return 'en-EN';
46
+ }
47
+ }
48
+ }
49
+ };
@@ -0,0 +1 @@
1
+ Password reset for {{account.username}} account
@@ -0,0 +1,7 @@
1
+ Hello {{account.username}},
2
+
3
+ In order to reset your password, please click on the link below:
4
+
5
+ {{frontUrl}}/login?new_password=true&token={{token}}
6
+
7
+ If you did not request a password reset, please ignore this message.
@@ -0,0 +1 @@
1
+ Réinitialisation du mot de passe du compte {{account.username}}
@@ -0,0 +1,7 @@
1
+ Bonjour {{account.username}},
2
+
3
+ Pour réinitialiser votre mot de passe, veuillez cliquer sur le lien ci-dessous:
4
+
5
+ {{frontUrl}}/login?new_password=true&token={{token}}
6
+
7
+ Si vous n'avez pas fait une telle demande, vous pouvez ignorer ce message.