@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.
Files changed (68) hide show
  1. package/dist/index.d.ts +8 -0
  2. package/dist/index.js +9 -0
  3. package/dist/index.js.map +1 -0
  4. package/dist/middlewares/localLogout.d.ts +2 -0
  5. package/dist/middlewares/localLogout.js +6 -0
  6. package/dist/middlewares/localLogout.js.map +1 -0
  7. package/dist/middlewares/redirectToFront.d.ts +2 -0
  8. package/dist/middlewares/redirectToFront.js +15 -0
  9. package/dist/middlewares/redirectToFront.js.map +1 -0
  10. package/dist/middlewares/saveRedirectUrl.d.ts +2 -0
  11. package/dist/middlewares/saveRedirectUrl.js +9 -0
  12. package/dist/middlewares/saveRedirectUrl.js.map +1 -0
  13. package/dist/middlewares/sendToken.d.ts +2 -0
  14. package/dist/middlewares/sendToken.js +6 -0
  15. package/dist/middlewares/sendToken.js.map +1 -0
  16. package/dist/mixins/auth.d.ts +98 -0
  17. package/dist/mixins/auth.js +235 -0
  18. package/dist/mixins/auth.js.map +1 -0
  19. package/dist/mixins/auth.sso.d.ts +76 -0
  20. package/dist/mixins/auth.sso.js +82 -0
  21. package/dist/mixins/auth.sso.js.map +1 -0
  22. package/dist/services/account.d.ts +122 -0
  23. package/dist/services/account.js +324 -0
  24. package/dist/services/account.js.map +1 -0
  25. package/dist/services/auth.cas.d.ts +100 -0
  26. package/dist/services/auth.cas.js +43 -0
  27. package/dist/services/auth.cas.js.map +1 -0
  28. package/dist/services/auth.local.d.ts +143 -0
  29. package/dist/services/auth.local.js +229 -0
  30. package/dist/services/auth.local.js.map +1 -0
  31. package/dist/services/auth.oidc.d.ts +102 -0
  32. package/dist/services/auth.oidc.js +63 -0
  33. package/dist/services/auth.oidc.js.map +1 -0
  34. package/dist/services/jwt.d.ts +50 -0
  35. package/dist/services/jwt.js +111 -0
  36. package/dist/services/jwt.js.map +1 -0
  37. package/dist/services/mail.d.ts +31 -0
  38. package/dist/services/mail.js +52 -0
  39. package/dist/services/mail.js.map +1 -0
  40. package/dist/services/migration.d.ts +18 -0
  41. package/dist/services/migration.js +33 -0
  42. package/dist/services/migration.js.map +1 -0
  43. package/dist/tsconfig.tsbuildinfo +1 -0
  44. package/index.ts +17 -0
  45. package/middlewares/localLogout.ts +6 -0
  46. package/middlewares/{redirectToFront.js → redirectToFront.ts} +2 -2
  47. package/middlewares/{saveRedirectUrl.js → saveRedirectUrl.ts} +2 -2
  48. package/middlewares/{sendToken.js → sendToken.ts} +2 -2
  49. package/mixins/auth.sso.ts +100 -0
  50. package/mixins/{auth.js → auth.ts} +91 -67
  51. package/package.json +16 -10
  52. package/services/account.ts +382 -0
  53. package/services/auth.cas.ts +56 -0
  54. package/services/auth.local.ts +276 -0
  55. package/services/{auth.oidc.js → auth.oidc.ts} +21 -9
  56. package/services/jwt.ts +127 -0
  57. package/services/mail.ts +67 -0
  58. package/services/migration.ts +43 -0
  59. package/tsconfig.json +10 -0
  60. package/index.js +0 -9
  61. package/middlewares/localLogout.js +0 -6
  62. package/mixins/auth.sso.js +0 -93
  63. package/services/account.js +0 -315
  64. package/services/auth.cas.js +0 -45
  65. package/services/auth.local.js +0 -238
  66. package/services/jwt.js +0 -101
  67. package/services/mail.js +0 -49
  68. package/services/migration.js +0 -29
@@ -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
- };
@@ -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;
@@ -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
- };