@semapps/auth 1.1.3 → 1.2.0
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/dist/index.d.ts +8 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/middlewares/localLogout.d.ts +2 -0
- package/dist/middlewares/localLogout.js +6 -0
- package/dist/middlewares/localLogout.js.map +1 -0
- package/dist/middlewares/redirectToFront.d.ts +2 -0
- package/dist/middlewares/redirectToFront.js +15 -0
- package/dist/middlewares/redirectToFront.js.map +1 -0
- package/dist/middlewares/saveRedirectUrl.d.ts +2 -0
- package/dist/middlewares/saveRedirectUrl.js +9 -0
- package/dist/middlewares/saveRedirectUrl.js.map +1 -0
- package/dist/middlewares/sendToken.d.ts +2 -0
- package/dist/middlewares/sendToken.js +6 -0
- package/dist/middlewares/sendToken.js.map +1 -0
- package/dist/mixins/auth.d.ts +98 -0
- package/dist/mixins/auth.js +235 -0
- package/dist/mixins/auth.js.map +1 -0
- package/dist/mixins/auth.sso.d.ts +76 -0
- package/dist/mixins/auth.sso.js +82 -0
- package/dist/mixins/auth.sso.js.map +1 -0
- package/dist/services/account.d.ts +122 -0
- package/dist/services/account.js +324 -0
- package/dist/services/account.js.map +1 -0
- package/dist/services/auth.cas.d.ts +100 -0
- package/dist/services/auth.cas.js +43 -0
- package/dist/services/auth.cas.js.map +1 -0
- package/dist/services/auth.local.d.ts +143 -0
- package/dist/services/auth.local.js +229 -0
- package/dist/services/auth.local.js.map +1 -0
- package/dist/services/auth.oidc.d.ts +102 -0
- package/dist/services/auth.oidc.js +63 -0
- package/dist/services/auth.oidc.js.map +1 -0
- package/dist/services/jwt.d.ts +50 -0
- package/dist/services/jwt.js +111 -0
- package/dist/services/jwt.js.map +1 -0
- package/dist/services/mail.d.ts +31 -0
- package/dist/services/mail.js +52 -0
- package/dist/services/mail.js.map +1 -0
- package/dist/services/migration.d.ts +18 -0
- package/dist/services/migration.js +33 -0
- package/dist/services/migration.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/index.ts +17 -0
- package/middlewares/localLogout.ts +6 -0
- package/middlewares/{redirectToFront.js → redirectToFront.ts} +2 -2
- package/middlewares/{saveRedirectUrl.js → saveRedirectUrl.ts} +2 -2
- package/middlewares/{sendToken.js → sendToken.ts} +2 -2
- package/mixins/auth.sso.ts +100 -0
- package/mixins/{auth.js → auth.ts} +91 -67
- package/package.json +16 -10
- package/services/account.ts +382 -0
- package/services/auth.cas.ts +56 -0
- package/services/auth.local.ts +276 -0
- package/services/{auth.oidc.js → auth.oidc.ts} +21 -9
- package/services/jwt.ts +127 -0
- package/services/mail.ts +67 -0
- package/services/migration.ts +43 -0
- package/tsconfig.json +10 -0
- package/index.js +0 -9
- package/middlewares/localLogout.js +0 -6
- package/mixins/auth.sso.js +0 -93
- package/services/account.js +0 -315
- package/services/auth.cas.js +0 -45
- package/services/auth.local.js +0 -238
- package/services/jwt.js +0 -101
- package/services/mail.js +0 -49
- package/services/migration.js +0 -29
package/services/account.js
DELETED
@@ -1,315 +0,0 @@
|
|
1
|
-
const bcrypt = require('bcrypt');
|
2
|
-
const createSlug = require('speakingurl');
|
3
|
-
const DbService = require('moleculer-db');
|
4
|
-
const { TripleStoreAdapter } = require('@semapps/triplestore');
|
5
|
-
const crypto = require('crypto');
|
6
|
-
|
7
|
-
// Taken from https://stackoverflow.com/a/9204568/7900695
|
8
|
-
const emailRegexp = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
9
|
-
|
10
|
-
module.exports = {
|
11
|
-
name: 'auth.account',
|
12
|
-
mixins: [DbService],
|
13
|
-
adapter: new TripleStoreAdapter({ type: 'AuthAccount', dataset: 'settings' }),
|
14
|
-
settings: {
|
15
|
-
idField: '@id',
|
16
|
-
reservedUsernames: ['relay'],
|
17
|
-
minPasswordLength: 1,
|
18
|
-
minUsernameLength: 1
|
19
|
-
},
|
20
|
-
dependencies: ['triplestore'],
|
21
|
-
actions: {
|
22
|
-
async create(ctx) {
|
23
|
-
let { uuid, username, password, email, webId, ...rest } = ctx.params;
|
24
|
-
|
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
|
36
|
-
|
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
|
-
}
|
48
|
-
}
|
49
|
-
|
50
|
-
// FORMAT AND VERIFY USERNAME
|
51
|
-
|
52
|
-
if (username) {
|
53
|
-
if (!ctx.meta.isSystemCall) {
|
54
|
-
const { isValid, error } = await this.isValidUsername(ctx, username);
|
55
|
-
if (!isValid) throw new Error(error);
|
56
|
-
}
|
57
|
-
} else if (email) {
|
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';
|
67
|
-
}
|
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
|
-
}
|
79
|
-
|
80
|
-
return await this._create(ctx, {
|
81
|
-
...rest,
|
82
|
-
uuid,
|
83
|
-
username,
|
84
|
-
email,
|
85
|
-
hashedPassword: password,
|
86
|
-
webId
|
87
|
-
});
|
88
|
-
},
|
89
|
-
async attachWebId(ctx) {
|
90
|
-
const { accountUri, webId } = ctx.params;
|
91
|
-
|
92
|
-
return await this._update(ctx, {
|
93
|
-
'@id': accountUri,
|
94
|
-
webId
|
95
|
-
});
|
96
|
-
},
|
97
|
-
async verify(ctx) {
|
98
|
-
const { username, password } = ctx.params;
|
99
|
-
|
100
|
-
// If the username includes a @, assume it is an email
|
101
|
-
const query = username.includes('@') ? { email: username } : { username };
|
102
|
-
|
103
|
-
const accounts = await this._find(ctx, { query });
|
104
|
-
|
105
|
-
if (accounts.length > 0) {
|
106
|
-
const passwordMatch = await this.comparePassword(password, accounts[0].hashedPassword);
|
107
|
-
if (passwordMatch) {
|
108
|
-
return accounts[0];
|
109
|
-
}
|
110
|
-
throw new Error('account.not-found');
|
111
|
-
} else {
|
112
|
-
throw new Error('account.not-found');
|
113
|
-
}
|
114
|
-
},
|
115
|
-
async usernameExists(ctx) {
|
116
|
-
const { username } = ctx.params;
|
117
|
-
const accounts = await this._find(ctx, { query: { username } });
|
118
|
-
return accounts.length > 0;
|
119
|
-
},
|
120
|
-
async emailExists(ctx) {
|
121
|
-
const { email } = ctx.params;
|
122
|
-
const accounts = await this._find(ctx, { query: { email } });
|
123
|
-
return accounts.length > 0;
|
124
|
-
},
|
125
|
-
/** Overwrite find method, to filter accounts with tombstone. */
|
126
|
-
async find(ctx) {
|
127
|
-
/** @type {object[]} */
|
128
|
-
const accounts = await this._find(ctx, ctx.params);
|
129
|
-
return accounts.filter(account => !account.deletedAt);
|
130
|
-
},
|
131
|
-
async findByUsername(ctx) {
|
132
|
-
const { username } = ctx.params;
|
133
|
-
const accounts = await this._find(ctx, { query: { username } });
|
134
|
-
return accounts.length > 0 ? accounts[0] : null;
|
135
|
-
},
|
136
|
-
async findByWebId(ctx) {
|
137
|
-
const { webId } = ctx.params;
|
138
|
-
const accounts = await this._find(ctx, { query: { webId } });
|
139
|
-
return accounts.length > 0 ? accounts[0] : null;
|
140
|
-
},
|
141
|
-
async findByEmail(ctx) {
|
142
|
-
const { email } = ctx.params;
|
143
|
-
const accounts = await this._find(ctx, { query: { email } });
|
144
|
-
return accounts.length > 0 ? accounts[0] : null;
|
145
|
-
},
|
146
|
-
async setPassword(ctx) {
|
147
|
-
const { webId, password } = ctx.params;
|
148
|
-
const hashedPassword = await this.hashPassword(password);
|
149
|
-
const account = await ctx.call('auth.account.findByWebId', { webId });
|
150
|
-
|
151
|
-
return await this._update(ctx, {
|
152
|
-
'@id': account['@id'],
|
153
|
-
hashedPassword
|
154
|
-
});
|
155
|
-
},
|
156
|
-
async setNewPassword(ctx) {
|
157
|
-
const { webId, token, password } = ctx.params;
|
158
|
-
const hashedPassword = await this.hashPassword(password);
|
159
|
-
const account = await ctx.call('auth.account.findByWebId', { webId });
|
160
|
-
|
161
|
-
if (account.resetPasswordToken !== token) {
|
162
|
-
throw new Error('auth.password.invalid_reset_token');
|
163
|
-
}
|
164
|
-
|
165
|
-
return await this._update(ctx, {
|
166
|
-
'@id': account['@id'],
|
167
|
-
hashedPassword,
|
168
|
-
resetPasswordToken: undefined
|
169
|
-
});
|
170
|
-
},
|
171
|
-
async generateResetPasswordToken(ctx) {
|
172
|
-
const { webId } = ctx.params;
|
173
|
-
const resetPasswordToken = await this.generateResetPasswordToken();
|
174
|
-
const account = await ctx.call('auth.account.findByWebId', { webId });
|
175
|
-
|
176
|
-
await this._update(ctx, {
|
177
|
-
'@id': account['@id'],
|
178
|
-
resetPasswordToken
|
179
|
-
});
|
180
|
-
|
181
|
-
return resetPasswordToken;
|
182
|
-
},
|
183
|
-
async findDatasetByWebId(ctx) {
|
184
|
-
const webId = ctx.params.webId || ctx.meta.webId;
|
185
|
-
const account = await ctx.call('auth.account.findByWebId', { webId });
|
186
|
-
return account?.username;
|
187
|
-
},
|
188
|
-
async findSettingsByWebId(ctx) {
|
189
|
-
const webId = ctx.meta.webId;
|
190
|
-
|
191
|
-
const account = await ctx.call('auth.account.findByWebId', { webId });
|
192
|
-
|
193
|
-
return {
|
194
|
-
email: account.email,
|
195
|
-
preferredLocale: account.preferredLocale
|
196
|
-
};
|
197
|
-
},
|
198
|
-
async updateAccountSettings(ctx) {
|
199
|
-
const { currentPassword, email, newPassword } = ctx.params;
|
200
|
-
const { webId } = ctx.meta;
|
201
|
-
const account = await ctx.call('auth.account.findByWebId', { webId });
|
202
|
-
const passwordMatch = await this.comparePassword(currentPassword, account.hashedPassword);
|
203
|
-
let params = {};
|
204
|
-
|
205
|
-
if (!passwordMatch) {
|
206
|
-
throw new Error('auth.account.invalid_password');
|
207
|
-
}
|
208
|
-
|
209
|
-
if (newPassword) {
|
210
|
-
const hashedPassword = await this.hashPassword(newPassword);
|
211
|
-
params = { ...params, hashedPassword };
|
212
|
-
}
|
213
|
-
|
214
|
-
if (email !== account.email) {
|
215
|
-
const existing = await ctx.call('auth.account.findByEmail', { email });
|
216
|
-
if (existing) {
|
217
|
-
throw new Error('email.already.exists');
|
218
|
-
}
|
219
|
-
|
220
|
-
params = { ...params, email };
|
221
|
-
}
|
222
|
-
|
223
|
-
return await this._update(ctx, {
|
224
|
-
'@id': account['@id'],
|
225
|
-
...params
|
226
|
-
});
|
227
|
-
},
|
228
|
-
async deleteByWebId(ctx) {
|
229
|
-
const { webId } = ctx.params;
|
230
|
-
const account = await ctx.call('auth.account.findByWebId', { webId });
|
231
|
-
|
232
|
-
if (account) {
|
233
|
-
await this._remove(ctx, { id: account['@id'] });
|
234
|
-
return true;
|
235
|
-
}
|
236
|
-
|
237
|
-
return false;
|
238
|
-
},
|
239
|
-
// Remove email and password from an account, set deletedAt timestamp.
|
240
|
-
async setTombstone(ctx) {
|
241
|
-
const { webId } = ctx.params;
|
242
|
-
const account = await ctx.call('auth.account.findByWebId', { webId });
|
243
|
-
|
244
|
-
return await this._update(ctx, {
|
245
|
-
// Set all values to undefined...
|
246
|
-
...Object.fromEntries(Object.keys(account).map(key => [key, null])),
|
247
|
-
'@id': account['@id'],
|
248
|
-
// ...except for
|
249
|
-
webId: account.webId,
|
250
|
-
username: account.username,
|
251
|
-
// And add a deletedAt date.
|
252
|
-
deletedAt: new Date().toISOString()
|
253
|
-
});
|
254
|
-
}
|
255
|
-
},
|
256
|
-
methods: {
|
257
|
-
async isValidUsername(ctx, username) {
|
258
|
-
let error;
|
259
|
-
|
260
|
-
// Ensure the username has no space or special characters
|
261
|
-
if (!/^[a-z0-9\-+_.]+$/.exec(username)) {
|
262
|
-
error = 'username.invalid';
|
263
|
-
}
|
264
|
-
|
265
|
-
if (username.length < this.settings.minUsernameLength) {
|
266
|
-
error = 'username.too-short';
|
267
|
-
}
|
268
|
-
|
269
|
-
// Ensure we don't use reservedUsernames
|
270
|
-
if (this.settings.reservedUsernames.includes(username)) {
|
271
|
-
error = 'username.reserved';
|
272
|
-
}
|
273
|
-
|
274
|
-
// Ensure username doesn't already exist
|
275
|
-
const usernameExists = await ctx.call('auth.account.usernameExists', { username });
|
276
|
-
if (usernameExists) {
|
277
|
-
error = 'username.already.exists';
|
278
|
-
}
|
279
|
-
|
280
|
-
return { isValid: !error, error };
|
281
|
-
},
|
282
|
-
async hashPassword(password) {
|
283
|
-
return new Promise((resolve, reject) => {
|
284
|
-
bcrypt.hash(password, 10, (err, hash) => {
|
285
|
-
if (err) {
|
286
|
-
reject(err);
|
287
|
-
} else {
|
288
|
-
resolve(hash);
|
289
|
-
}
|
290
|
-
});
|
291
|
-
});
|
292
|
-
},
|
293
|
-
async comparePassword(password, hash) {
|
294
|
-
return new Promise(resolve => {
|
295
|
-
bcrypt.compare(password, hash, (err, res) => {
|
296
|
-
if (res === true) {
|
297
|
-
resolve(true);
|
298
|
-
} else {
|
299
|
-
resolve(false);
|
300
|
-
}
|
301
|
-
});
|
302
|
-
});
|
303
|
-
},
|
304
|
-
async generateResetPasswordToken() {
|
305
|
-
return new Promise((resolve, reject) => {
|
306
|
-
crypto.randomBytes(32, (ex, buf) => {
|
307
|
-
if (ex) {
|
308
|
-
reject(ex);
|
309
|
-
}
|
310
|
-
resolve(buf.toString('hex'));
|
311
|
-
});
|
312
|
-
});
|
313
|
-
}
|
314
|
-
}
|
315
|
-
};
|
package/services/auth.cas.js
DELETED
@@ -1,45 +0,0 @@
|
|
1
|
-
const { Strategy } = require('passport-cas2');
|
2
|
-
const { Errors: E } = require('moleculer-web');
|
3
|
-
const AuthSSOMixin = require('../mixins/auth.sso');
|
4
|
-
|
5
|
-
const AuthCASService = {
|
6
|
-
name: 'auth',
|
7
|
-
mixins: [AuthSSOMixin],
|
8
|
-
settings: {
|
9
|
-
baseUrl: null,
|
10
|
-
jwtPath: null,
|
11
|
-
registrationAllowed: true,
|
12
|
-
reservedUsernames: [],
|
13
|
-
webIdSelection: [],
|
14
|
-
// SSO-specific settings
|
15
|
-
sessionSecret: 's€m@pps',
|
16
|
-
selectSsoData: null,
|
17
|
-
// Cas-specific settings
|
18
|
-
casUrl: null
|
19
|
-
},
|
20
|
-
async created() {
|
21
|
-
this.passportId = 'cas';
|
22
|
-
},
|
23
|
-
methods: {
|
24
|
-
getStrategy() {
|
25
|
-
return new Strategy(
|
26
|
-
{
|
27
|
-
casURL: this.settings.casUrl,
|
28
|
-
passReqToCallback: true
|
29
|
-
},
|
30
|
-
(req, username, profile, done) => {
|
31
|
-
req.$ctx
|
32
|
-
.call('auth.loginOrSignup', { ssoData: { username, ...profile } })
|
33
|
-
.then(loginData => {
|
34
|
-
done(null, loginData);
|
35
|
-
})
|
36
|
-
.catch(e => {
|
37
|
-
done(new E.UnAuthorizedError(e.message), false);
|
38
|
-
});
|
39
|
-
}
|
40
|
-
);
|
41
|
-
}
|
42
|
-
}
|
43
|
-
};
|
44
|
-
|
45
|
-
module.exports = AuthCASService;
|
package/services/auth.local.js
DELETED
@@ -1,238 +0,0 @@
|
|
1
|
-
const path = require('path');
|
2
|
-
const { Strategy } = require('passport-local');
|
3
|
-
const AuthMixin = require('../mixins/auth');
|
4
|
-
const sendToken = require('../middlewares/sendToken');
|
5
|
-
const { MoleculerError } = require('moleculer').Errors;
|
6
|
-
const AuthMailService = require('./mail');
|
7
|
-
|
8
|
-
/** @type {import('moleculer').ServiceSchema} */
|
9
|
-
const AuthLocalService = {
|
10
|
-
name: 'auth',
|
11
|
-
mixins: [AuthMixin],
|
12
|
-
settings: {
|
13
|
-
baseUrl: null,
|
14
|
-
jwtPath: null,
|
15
|
-
registrationAllowed: true,
|
16
|
-
reservedUsernames: [],
|
17
|
-
minPasswordLength: 1,
|
18
|
-
minUsernameLength: 1,
|
19
|
-
webIdSelection: [],
|
20
|
-
accountSelection: [],
|
21
|
-
formUrl: null,
|
22
|
-
mail: {
|
23
|
-
from: null,
|
24
|
-
transport: {
|
25
|
-
host: null,
|
26
|
-
port: null
|
27
|
-
},
|
28
|
-
defaults: {
|
29
|
-
locale: null,
|
30
|
-
frontUrl: null
|
31
|
-
}
|
32
|
-
}
|
33
|
-
},
|
34
|
-
dependencies: ['webid'],
|
35
|
-
async created() {
|
36
|
-
const { mail } = this.settings;
|
37
|
-
|
38
|
-
this.passportId = 'local';
|
39
|
-
|
40
|
-
if (mail !== false) {
|
41
|
-
this.broker.createService({
|
42
|
-
mixins: [AuthMailService],
|
43
|
-
settings: {
|
44
|
-
...mail
|
45
|
-
}
|
46
|
-
});
|
47
|
-
}
|
48
|
-
},
|
49
|
-
actions: {
|
50
|
-
async signup(ctx) {
|
51
|
-
const { username, email, password, ...rest } = ctx.params;
|
52
|
-
|
53
|
-
// This is going to get in our way otherwise when waiting for completions.
|
54
|
-
ctx.meta.skipObjectsWatcher = true;
|
55
|
-
|
56
|
-
let accountData = await ctx.call('auth.account.create', {
|
57
|
-
username,
|
58
|
-
email,
|
59
|
-
password,
|
60
|
-
...this.pickAccountData(rest)
|
61
|
-
});
|
62
|
-
|
63
|
-
try {
|
64
|
-
const profileData = { nick: accountData.username, email: accountData.email, ...rest };
|
65
|
-
const webId = await ctx.call('webid.createWebId', this.pickWebIdData(profileData), {
|
66
|
-
meta: {
|
67
|
-
isSignup: true // Allow services to handle directly the webId creation if it is generated by the AuthService
|
68
|
-
}
|
69
|
-
});
|
70
|
-
|
71
|
-
// Link the webId with the account
|
72
|
-
accountData = await ctx.call('auth.account.attachWebId', { accountUri: accountData['@id'], webId });
|
73
|
-
|
74
|
-
ctx.emit('auth.registered', { webId, profileData, accountData });
|
75
|
-
|
76
|
-
const token = await ctx.call('auth.jwt.generateServerSignedToken', { payload: { webId } });
|
77
|
-
|
78
|
-
return { token, webId, newUser: true };
|
79
|
-
} catch (e) {
|
80
|
-
// Delete account if resource creation failed, or it may cause problems when retrying
|
81
|
-
await ctx.call('auth.account.remove', { id: accountData['@id'] });
|
82
|
-
throw e;
|
83
|
-
}
|
84
|
-
},
|
85
|
-
async login(ctx) {
|
86
|
-
const { username, password } = ctx.params;
|
87
|
-
|
88
|
-
const accountData = await ctx.call('auth.account.verify', { username, password });
|
89
|
-
|
90
|
-
ctx.emit('auth.connected', { webId: accountData.webId, accountData }, { meta: { webId: null, dataset: null } });
|
91
|
-
|
92
|
-
const token = await ctx.call('auth.jwt.generateServerSignedToken', { payload: { webId: accountData.webId } });
|
93
|
-
|
94
|
-
return { token, webId: accountData.webId, newUser: false };
|
95
|
-
},
|
96
|
-
async logout(ctx) {
|
97
|
-
ctx.meta.$statusCode = 302;
|
98
|
-
ctx.meta.$location = ctx.params.redirectUrl || this.settings.formUrl;
|
99
|
-
ctx.emit('auth.disconnected', { webId: ctx.meta.webId });
|
100
|
-
},
|
101
|
-
async redirectToForm(ctx) {
|
102
|
-
if (this.settings.formUrl) {
|
103
|
-
const formUrl = new URL(this.settings.formUrl);
|
104
|
-
if (ctx.params) {
|
105
|
-
for (const [key, value] of Object.entries(ctx.params)) {
|
106
|
-
formUrl.searchParams.set(key, value);
|
107
|
-
}
|
108
|
-
}
|
109
|
-
ctx.meta.$statusCode = 302;
|
110
|
-
ctx.meta.$location = formUrl.toString();
|
111
|
-
} else {
|
112
|
-
throw new Error('No formUrl defined in auth.local settings');
|
113
|
-
}
|
114
|
-
},
|
115
|
-
async resetPassword(ctx) {
|
116
|
-
const { email } = ctx.params;
|
117
|
-
|
118
|
-
const account = await ctx.call('auth.account.findByEmail', { email });
|
119
|
-
|
120
|
-
if (!account) {
|
121
|
-
throw new MoleculerError('email.not.exists', 400, 'BAD_REQUEST');
|
122
|
-
}
|
123
|
-
|
124
|
-
const token = await ctx.call('auth.account.generateResetPasswordToken', { webId: account.webId });
|
125
|
-
|
126
|
-
await ctx.call('auth.mail.sendResetPasswordEmail', {
|
127
|
-
account,
|
128
|
-
token
|
129
|
-
});
|
130
|
-
},
|
131
|
-
async setNewPassword(ctx) {
|
132
|
-
const { email, token, password } = ctx.params;
|
133
|
-
|
134
|
-
const account = await ctx.call('auth.account.findByEmail', { email });
|
135
|
-
|
136
|
-
if (!account) {
|
137
|
-
throw new MoleculerError('email.not.exists', 400, 'BAD_REQUEST');
|
138
|
-
}
|
139
|
-
|
140
|
-
await ctx.call('auth.account.setNewPassword', { webId: account.webId, token, password });
|
141
|
-
}
|
142
|
-
},
|
143
|
-
methods: {
|
144
|
-
getStrategy() {
|
145
|
-
return new Strategy(
|
146
|
-
{
|
147
|
-
passReqToCallback: true // We want to have access to req below
|
148
|
-
},
|
149
|
-
(req, username, password, done) => {
|
150
|
-
req.$ctx
|
151
|
-
.call('auth.login', req.$params)
|
152
|
-
.then(returnedData => {
|
153
|
-
done(null, returnedData);
|
154
|
-
})
|
155
|
-
.catch(e => {
|
156
|
-
done(new MoleculerError(e.message, 401), false);
|
157
|
-
});
|
158
|
-
}
|
159
|
-
);
|
160
|
-
},
|
161
|
-
getApiRoutes(basePath) {
|
162
|
-
const loginRoute = {
|
163
|
-
path: path.join(basePath, '/auth/login'),
|
164
|
-
name: 'auth-login',
|
165
|
-
use: [this.passport.initialize()],
|
166
|
-
aliases: {
|
167
|
-
'POST /': [this.passport.authenticate(this.passportId, { session: false }), sendToken]
|
168
|
-
}
|
169
|
-
};
|
170
|
-
|
171
|
-
const logoutRoute = {
|
172
|
-
path: path.join(basePath, '/auth/logout'),
|
173
|
-
name: 'auth-logout',
|
174
|
-
aliases: {
|
175
|
-
'GET /': 'auth.logout'
|
176
|
-
}
|
177
|
-
};
|
178
|
-
|
179
|
-
const signupRoute = {
|
180
|
-
path: path.join(basePath, '/auth/signup'),
|
181
|
-
name: 'auth-signup',
|
182
|
-
aliases: {
|
183
|
-
'POST /': 'auth.signup'
|
184
|
-
}
|
185
|
-
};
|
186
|
-
|
187
|
-
const formRoute = {
|
188
|
-
path: path.join(basePath, '/auth'),
|
189
|
-
name: 'auth',
|
190
|
-
aliases: {
|
191
|
-
'GET /': 'auth.redirectToForm'
|
192
|
-
}
|
193
|
-
};
|
194
|
-
|
195
|
-
const resetPasswordRoute = {
|
196
|
-
path: path.join(basePath, '/auth/reset_password'),
|
197
|
-
name: 'auth-reset-password',
|
198
|
-
aliases: {
|
199
|
-
'POST /': 'auth.resetPassword'
|
200
|
-
}
|
201
|
-
};
|
202
|
-
const setNewPasswordRoute = {
|
203
|
-
path: path.join(basePath, '/auth/new_password'),
|
204
|
-
name: 'auth-new-password',
|
205
|
-
aliases: {
|
206
|
-
'POST /': 'auth.setNewPassword'
|
207
|
-
}
|
208
|
-
};
|
209
|
-
|
210
|
-
const accountSettingsRoute = {
|
211
|
-
path: path.join(basePath, '/auth/account'),
|
212
|
-
name: 'auth-account',
|
213
|
-
aliases: {
|
214
|
-
'GET /': 'auth.account.findSettingsByWebId',
|
215
|
-
'POST /': 'auth.account.updateAccountSettings'
|
216
|
-
},
|
217
|
-
authorization: true
|
218
|
-
};
|
219
|
-
|
220
|
-
const routes = [
|
221
|
-
loginRoute,
|
222
|
-
logoutRoute,
|
223
|
-
formRoute,
|
224
|
-
resetPasswordRoute,
|
225
|
-
setNewPasswordRoute,
|
226
|
-
accountSettingsRoute
|
227
|
-
];
|
228
|
-
|
229
|
-
if (this.settings.registrationAllowed) {
|
230
|
-
return [...routes, signupRoute];
|
231
|
-
}
|
232
|
-
|
233
|
-
return routes;
|
234
|
-
}
|
235
|
-
}
|
236
|
-
};
|
237
|
-
|
238
|
-
module.exports = AuthLocalService;
|
package/services/jwt.js
DELETED
@@ -1,101 +0,0 @@
|
|
1
|
-
const fs = require('fs');
|
2
|
-
const path = require('path');
|
3
|
-
const jwt = require('jsonwebtoken');
|
4
|
-
const crypto = require('crypto');
|
5
|
-
|
6
|
-
/**
|
7
|
-
* Service that creates and validates JSON web tokens(JWT).
|
8
|
-
* Tokens are signed against this server's keys.
|
9
|
-
* This is useful for generating/validating authentication tokens.
|
10
|
-
*
|
11
|
-
* TODO: Tokens do not expire.
|
12
|
-
*/
|
13
|
-
module.exports = {
|
14
|
-
name: 'auth.jwt',
|
15
|
-
settings: {
|
16
|
-
jwtPath: null
|
17
|
-
},
|
18
|
-
async created() {
|
19
|
-
const privateKeyPath = path.resolve(this.settings.jwtPath, 'jwtRS256.key');
|
20
|
-
const publicKeyPath = path.resolve(this.settings.jwtPath, 'jwtRS256.key.pub');
|
21
|
-
|
22
|
-
if (!fs.existsSync(privateKeyPath) && !fs.existsSync(publicKeyPath)) {
|
23
|
-
this.logger.info('JWT keypair not found, generating...');
|
24
|
-
if (!fs.existsSync(this.settings.jwtPath)) {
|
25
|
-
fs.mkdirSync(this.settings.jwtPath);
|
26
|
-
}
|
27
|
-
await this.actions.generateKeyPair({ privateKeyPath, publicKeyPath });
|
28
|
-
}
|
29
|
-
|
30
|
-
this.privateKey = fs.readFileSync(privateKeyPath);
|
31
|
-
this.publicKey = fs.readFileSync(publicKeyPath);
|
32
|
-
},
|
33
|
-
actions: {
|
34
|
-
generateKeyPair(ctx) {
|
35
|
-
const { privateKeyPath, publicKeyPath } = ctx.params;
|
36
|
-
|
37
|
-
return new Promise((resolve, reject) => {
|
38
|
-
crypto.generateKeyPair(
|
39
|
-
'rsa',
|
40
|
-
{
|
41
|
-
modulusLength: 4096,
|
42
|
-
publicKeyEncoding: {
|
43
|
-
type: 'spki',
|
44
|
-
format: 'pem'
|
45
|
-
},
|
46
|
-
privateKeyEncoding: {
|
47
|
-
type: 'pkcs8',
|
48
|
-
format: 'pem'
|
49
|
-
}
|
50
|
-
},
|
51
|
-
(err, publicKey, privateKey) => {
|
52
|
-
if (err) {
|
53
|
-
reject(err);
|
54
|
-
} else {
|
55
|
-
fs.writeFile(privateKeyPath, privateKey, err => {
|
56
|
-
if (err) {
|
57
|
-
reject(err);
|
58
|
-
} else {
|
59
|
-
fs.writeFile(publicKeyPath, publicKey, err => {
|
60
|
-
if (err) {
|
61
|
-
reject(err);
|
62
|
-
} else {
|
63
|
-
resolve({ privateKey, publicKey });
|
64
|
-
}
|
65
|
-
});
|
66
|
-
}
|
67
|
-
});
|
68
|
-
}
|
69
|
-
}
|
70
|
-
);
|
71
|
-
});
|
72
|
-
},
|
73
|
-
async generateServerSignedToken(ctx) {
|
74
|
-
const { payload } = ctx.params;
|
75
|
-
return jwt.sign(payload, this.privateKey, { algorithm: 'RS256' });
|
76
|
-
},
|
77
|
-
/** Verifies that the token was signed by this server. */
|
78
|
-
async verifyServerSignedToken(ctx) {
|
79
|
-
const { token } = ctx.params;
|
80
|
-
try {
|
81
|
-
return jwt.verify(token, this.publicKey, { algorithms: ['RS256'] });
|
82
|
-
} catch (err) {
|
83
|
-
return false;
|
84
|
-
}
|
85
|
-
},
|
86
|
-
async generateUnsignedToken(ctx) {
|
87
|
-
const { payload } = ctx.params;
|
88
|
-
const token = jwt.sign(payload, null, { algorithm: 'none' });
|
89
|
-
return token;
|
90
|
-
},
|
91
|
-
// Warning, this does NOT verify if signature is valid
|
92
|
-
async decodeToken(ctx) {
|
93
|
-
const { token } = ctx.params;
|
94
|
-
try {
|
95
|
-
return jwt.decode(token);
|
96
|
-
} catch (err) {
|
97
|
-
return false;
|
98
|
-
}
|
99
|
-
}
|
100
|
-
}
|
101
|
-
};
|