@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
@@ -0,0 +1,276 @@
|
|
1
|
+
import path from 'path';
|
2
|
+
// @ts-expect-error TS(7016): Could not find a declaration file for module 'pass... Remove this comment to see the full error message
|
3
|
+
import { Strategy } from 'passport-local';
|
4
|
+
import { ServiceSchema } from 'moleculer';
|
5
|
+
import AuthMixin from '../mixins/auth.ts';
|
6
|
+
import sendToken from '../middlewares/sendToken.ts';
|
7
|
+
import AuthMailService from './mail.ts';
|
8
|
+
|
9
|
+
import { Errors } from 'moleculer';
|
10
|
+
|
11
|
+
const { MoleculerError } = Errors;
|
12
|
+
|
13
|
+
/** @type {import('moleculer').ServiceSchema} */
|
14
|
+
const AuthLocalService = {
|
15
|
+
name: 'auth' as const,
|
16
|
+
mixins: [AuthMixin],
|
17
|
+
settings: {
|
18
|
+
baseUrl: null,
|
19
|
+
jwtPath: null,
|
20
|
+
registrationAllowed: true,
|
21
|
+
reservedUsernames: [],
|
22
|
+
minPasswordLength: 1,
|
23
|
+
minUsernameLength: 1,
|
24
|
+
webIdSelection: [],
|
25
|
+
accountSelection: [],
|
26
|
+
formUrl: null,
|
27
|
+
mail: {
|
28
|
+
from: null,
|
29
|
+
transport: {
|
30
|
+
host: null,
|
31
|
+
port: null
|
32
|
+
},
|
33
|
+
defaults: {
|
34
|
+
locale: null,
|
35
|
+
frontUrl: null
|
36
|
+
}
|
37
|
+
}
|
38
|
+
},
|
39
|
+
dependencies: ['webid'],
|
40
|
+
async created() {
|
41
|
+
const { mail } = this.settings;
|
42
|
+
|
43
|
+
this.passportId = 'local';
|
44
|
+
|
45
|
+
if (mail !== false) {
|
46
|
+
// @ts-expect-error TS(2345): Argument of type '{ mixins: { name: "auth.mail"; m... Remove this comment to see the full error message
|
47
|
+
this.broker.createService({
|
48
|
+
mixins: [AuthMailService],
|
49
|
+
settings: {
|
50
|
+
...mail
|
51
|
+
}
|
52
|
+
});
|
53
|
+
}
|
54
|
+
},
|
55
|
+
actions: {
|
56
|
+
signup: {
|
57
|
+
async handler(ctx) {
|
58
|
+
const { username, email, password, ...rest } = ctx.params;
|
59
|
+
|
60
|
+
// This is going to get in our way otherwise when waiting for completions.
|
61
|
+
// @ts-expect-error TS(2339): Property 'skipObjectsWatcher' does not exist on ty... Remove this comment to see the full error message
|
62
|
+
ctx.meta.skipObjectsWatcher = true;
|
63
|
+
|
64
|
+
let accountData = await ctx.call('auth.account.create', {
|
65
|
+
username,
|
66
|
+
email,
|
67
|
+
password,
|
68
|
+
...this.pickAccountData(rest)
|
69
|
+
});
|
70
|
+
|
71
|
+
try {
|
72
|
+
const profileData = { nick: accountData.username, email: accountData.email, ...rest };
|
73
|
+
const webId = await ctx.call('webid.createWebId', this.pickWebIdData(profileData), {
|
74
|
+
meta: {
|
75
|
+
isSignup: true // Allow services to handle directly the webId creation if it is generated by the AuthService
|
76
|
+
}
|
77
|
+
});
|
78
|
+
|
79
|
+
// Link the webId with the account
|
80
|
+
accountData = await ctx.call('auth.account.attachWebId', { accountUri: accountData['@id'], webId });
|
81
|
+
|
82
|
+
ctx.emit('auth.registered', { webId, profileData, accountData });
|
83
|
+
|
84
|
+
const token = await ctx.call('auth.jwt.generateServerSignedToken', { payload: { webId } });
|
85
|
+
|
86
|
+
return { token, webId, newUser: true };
|
87
|
+
} catch (e) {
|
88
|
+
// Delete account if resource creation failed, or it may cause problems when retrying
|
89
|
+
await ctx.call('auth.account.remove', { id: accountData['@id'] });
|
90
|
+
throw e;
|
91
|
+
}
|
92
|
+
}
|
93
|
+
},
|
94
|
+
|
95
|
+
login: {
|
96
|
+
async handler(ctx) {
|
97
|
+
const { username, password } = ctx.params;
|
98
|
+
|
99
|
+
const accountData = await ctx.call('auth.account.verify', { username, password });
|
100
|
+
|
101
|
+
ctx.emit('auth.connected', { webId: accountData.webId, accountData }, { meta: { webId: null, dataset: null } });
|
102
|
+
|
103
|
+
const token = await ctx.call('auth.jwt.generateServerSignedToken', { payload: { webId: accountData.webId } });
|
104
|
+
|
105
|
+
return { token, webId: accountData.webId, newUser: false };
|
106
|
+
}
|
107
|
+
},
|
108
|
+
|
109
|
+
logout: {
|
110
|
+
async handler(ctx) {
|
111
|
+
// @ts-expect-error TS(2339): Property '$statusCode' does not exist on type '{}'... Remove this comment to see the full error message
|
112
|
+
ctx.meta.$statusCode = 302;
|
113
|
+
// @ts-expect-error TS(2339): Property '$location' does not exist on type '{}'.
|
114
|
+
ctx.meta.$location = ctx.params.redirectUrl || this.settings.formUrl;
|
115
|
+
// @ts-expect-error TS(2339): Property 'webId' does not exist on type '{}'.
|
116
|
+
ctx.emit('auth.disconnected', { webId: ctx.meta.webId });
|
117
|
+
}
|
118
|
+
},
|
119
|
+
|
120
|
+
redirectToForm: {
|
121
|
+
async handler(ctx) {
|
122
|
+
if (this.settings.formUrl) {
|
123
|
+
const formUrl = new URL(this.settings.formUrl);
|
124
|
+
if (ctx.params) {
|
125
|
+
for (const [key, value] of Object.entries(ctx.params)) {
|
126
|
+
formUrl.searchParams.set(key, value);
|
127
|
+
}
|
128
|
+
}
|
129
|
+
// @ts-expect-error TS(2339): Property '$statusCode' does not exist on type '{}'... Remove this comment to see the full error message
|
130
|
+
ctx.meta.$statusCode = 302;
|
131
|
+
// @ts-expect-error TS(2339): Property '$location' does not exist on type '{}'.
|
132
|
+
ctx.meta.$location = formUrl.toString();
|
133
|
+
} else {
|
134
|
+
throw new Error('No formUrl defined in auth.local settings');
|
135
|
+
}
|
136
|
+
}
|
137
|
+
},
|
138
|
+
|
139
|
+
resetPassword: {
|
140
|
+
async handler(ctx) {
|
141
|
+
const { email } = ctx.params;
|
142
|
+
|
143
|
+
const account = await ctx.call('auth.account.findByEmail', { email });
|
144
|
+
|
145
|
+
if (!account) {
|
146
|
+
throw new MoleculerError('email.not.exists', 400, 'BAD_REQUEST');
|
147
|
+
}
|
148
|
+
|
149
|
+
const token = await ctx.call('auth.account.generateResetPasswordToken', { webId: account.webId });
|
150
|
+
|
151
|
+
await ctx.call('auth.mail.sendResetPasswordEmail', {
|
152
|
+
account,
|
153
|
+
token
|
154
|
+
});
|
155
|
+
}
|
156
|
+
},
|
157
|
+
|
158
|
+
setNewPassword: {
|
159
|
+
async handler(ctx) {
|
160
|
+
const { email, token, password } = ctx.params;
|
161
|
+
|
162
|
+
const account = await ctx.call('auth.account.findByEmail', { email });
|
163
|
+
|
164
|
+
if (!account) {
|
165
|
+
throw new MoleculerError('email.not.exists', 400, 'BAD_REQUEST');
|
166
|
+
}
|
167
|
+
|
168
|
+
await ctx.call('auth.account.setNewPassword', { webId: account.webId, token, password });
|
169
|
+
}
|
170
|
+
}
|
171
|
+
},
|
172
|
+
methods: {
|
173
|
+
getStrategy() {
|
174
|
+
return new Strategy(
|
175
|
+
{
|
176
|
+
passReqToCallback: true // We want to have access to req below
|
177
|
+
},
|
178
|
+
(req: any, username: any, password: any, done: any) => {
|
179
|
+
req.$ctx
|
180
|
+
.call('auth.login', req.$params)
|
181
|
+
.then((returnedData: any) => {
|
182
|
+
done(null, returnedData);
|
183
|
+
})
|
184
|
+
.catch((e: any) => {
|
185
|
+
done(new MoleculerError(e.message, 401), false);
|
186
|
+
});
|
187
|
+
}
|
188
|
+
);
|
189
|
+
},
|
190
|
+
getApiRoutes(basePath) {
|
191
|
+
const loginRoute = {
|
192
|
+
path: path.join(basePath, '/auth/login'),
|
193
|
+
name: 'auth-login',
|
194
|
+
use: [this.passport.initialize()],
|
195
|
+
aliases: {
|
196
|
+
'POST /': [this.passport.authenticate(this.passportId, { session: false }), sendToken]
|
197
|
+
}
|
198
|
+
};
|
199
|
+
|
200
|
+
const logoutRoute = {
|
201
|
+
path: path.join(basePath, '/auth/logout'),
|
202
|
+
name: 'auth-logout',
|
203
|
+
aliases: {
|
204
|
+
'GET /': 'auth.logout'
|
205
|
+
}
|
206
|
+
};
|
207
|
+
|
208
|
+
const signupRoute = {
|
209
|
+
path: path.join(basePath, '/auth/signup'),
|
210
|
+
name: 'auth-signup',
|
211
|
+
aliases: {
|
212
|
+
'POST /': 'auth.signup'
|
213
|
+
}
|
214
|
+
};
|
215
|
+
|
216
|
+
const formRoute = {
|
217
|
+
path: path.join(basePath, '/auth'),
|
218
|
+
name: 'auth',
|
219
|
+
aliases: {
|
220
|
+
'GET /': 'auth.redirectToForm'
|
221
|
+
}
|
222
|
+
};
|
223
|
+
|
224
|
+
const resetPasswordRoute = {
|
225
|
+
path: path.join(basePath, '/auth/reset_password'),
|
226
|
+
name: 'auth-reset-password',
|
227
|
+
aliases: {
|
228
|
+
'POST /': 'auth.resetPassword'
|
229
|
+
}
|
230
|
+
};
|
231
|
+
const setNewPasswordRoute = {
|
232
|
+
path: path.join(basePath, '/auth/new_password'),
|
233
|
+
name: 'auth-new-password',
|
234
|
+
aliases: {
|
235
|
+
'POST /': 'auth.setNewPassword'
|
236
|
+
}
|
237
|
+
};
|
238
|
+
|
239
|
+
const accountSettingsRoute = {
|
240
|
+
path: path.join(basePath, '/auth/account'),
|
241
|
+
name: 'auth-account',
|
242
|
+
aliases: {
|
243
|
+
'GET /': 'auth.account.findSettingsByWebId',
|
244
|
+
'POST /': 'auth.account.updateAccountSettings'
|
245
|
+
},
|
246
|
+
authorization: true
|
247
|
+
};
|
248
|
+
|
249
|
+
const routes = [
|
250
|
+
loginRoute,
|
251
|
+
logoutRoute,
|
252
|
+
formRoute,
|
253
|
+
resetPasswordRoute,
|
254
|
+
setNewPasswordRoute,
|
255
|
+
accountSettingsRoute
|
256
|
+
];
|
257
|
+
|
258
|
+
if (this.settings.registrationAllowed) {
|
259
|
+
return [...routes, signupRoute];
|
260
|
+
}
|
261
|
+
|
262
|
+
return routes;
|
263
|
+
}
|
264
|
+
}
|
265
|
+
} satisfies ServiceSchema;
|
266
|
+
|
267
|
+
export default AuthLocalService;
|
268
|
+
|
269
|
+
declare global {
|
270
|
+
export namespace Moleculer {
|
271
|
+
export interface AllServices {
|
272
|
+
// @ts-expect-error TS(2717): Subsequent property declarations must have the sam... Remove this comment to see the full error message
|
273
|
+
[AuthLocalService.name]: typeof AuthLocalService;
|
274
|
+
}
|
275
|
+
}
|
276
|
+
}
|
@@ -1,13 +1,16 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
import urlJoin from 'url-join';
|
2
|
+
// @ts-expect-error TS(7016): Could not find a declaration file for module 'open... Remove this comment to see the full error message
|
3
|
+
import { Issuer, Strategy, custom } from 'openid-client';
|
4
|
+
|
5
|
+
import { ServiceSchema } from 'moleculer';
|
6
|
+
import AuthSSOMixin from '../mixins/auth.sso.ts';
|
3
7
|
|
4
8
|
custom.setHttpOptionsDefaults({
|
5
9
|
timeout: 10000
|
6
10
|
});
|
7
|
-
const AuthSSOMixin = require('../mixins/auth.sso');
|
8
11
|
|
9
12
|
const AuthOIDCService = {
|
10
|
-
name: 'auth',
|
13
|
+
name: 'auth' as const,
|
11
14
|
mixins: [AuthSSOMixin],
|
12
15
|
settings: {
|
13
16
|
baseUrl: null,
|
@@ -51,13 +54,13 @@ const AuthOIDCService = {
|
|
51
54
|
params,
|
52
55
|
passReqToCallback: true
|
53
56
|
},
|
54
|
-
(req, tokenset, userinfo, done) => {
|
57
|
+
(req: any, tokenset: any, userinfo: any, done: any) => {
|
55
58
|
req.$ctx
|
56
59
|
.call('auth.loginOrSignup', { ssoData: userinfo })
|
57
|
-
.then(loginData => {
|
60
|
+
.then((loginData: any) => {
|
58
61
|
done(null, loginData);
|
59
62
|
})
|
60
|
-
.catch(e => {
|
63
|
+
.catch((e: any) => {
|
61
64
|
console.error(e);
|
62
65
|
done(null, false);
|
63
66
|
});
|
@@ -65,6 +68,15 @@ const AuthOIDCService = {
|
|
65
68
|
);
|
66
69
|
}
|
67
70
|
}
|
68
|
-
};
|
71
|
+
} satisfies ServiceSchema;
|
72
|
+
|
73
|
+
export default AuthOIDCService;
|
69
74
|
|
70
|
-
|
75
|
+
declare global {
|
76
|
+
export namespace Moleculer {
|
77
|
+
export interface AllServices {
|
78
|
+
// @ts-expect-error TS(2717): Subsequent property declarations must have the sam... Remove this comment to see the full error message
|
79
|
+
[AuthOIDCService.name]: typeof AuthOIDCService;
|
80
|
+
}
|
81
|
+
}
|
82
|
+
}
|
package/services/jwt.ts
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
import fs from 'fs';
|
2
|
+
import path from 'path';
|
3
|
+
// @ts-expect-error TS(7016): Could not find a declaration file for module 'json... Remove this comment to see the full error message
|
4
|
+
import jwt from 'jsonwebtoken';
|
5
|
+
import crypto from 'crypto';
|
6
|
+
import { ServiceSchema } from 'moleculer';
|
7
|
+
|
8
|
+
/**
|
9
|
+
* Service that creates and validates JSON web tokens(JWT).
|
10
|
+
* Tokens are signed against this server's keys.
|
11
|
+
* This is useful for generating/validating authentication tokens.
|
12
|
+
*
|
13
|
+
* TODO: Tokens do not expire.
|
14
|
+
*/
|
15
|
+
const AuthJwtSchema = {
|
16
|
+
name: 'auth.jwt' as const,
|
17
|
+
settings: {
|
18
|
+
jwtPath: null
|
19
|
+
},
|
20
|
+
async created() {
|
21
|
+
const privateKeyPath = path.resolve(this.settings.jwtPath, 'jwtRS256.key');
|
22
|
+
const publicKeyPath = path.resolve(this.settings.jwtPath, 'jwtRS256.key.pub');
|
23
|
+
|
24
|
+
if (!fs.existsSync(privateKeyPath) && !fs.existsSync(publicKeyPath)) {
|
25
|
+
this.logger.info('JWT keypair not found, generating...');
|
26
|
+
if (!fs.existsSync(this.settings.jwtPath)) {
|
27
|
+
fs.mkdirSync(this.settings.jwtPath);
|
28
|
+
}
|
29
|
+
await this.actions.generateKeyPair({ privateKeyPath, publicKeyPath });
|
30
|
+
}
|
31
|
+
|
32
|
+
this.privateKey = fs.readFileSync(privateKeyPath);
|
33
|
+
this.publicKey = fs.readFileSync(publicKeyPath);
|
34
|
+
},
|
35
|
+
actions: {
|
36
|
+
generateKeyPair: {
|
37
|
+
handler(ctx) {
|
38
|
+
const { privateKeyPath, publicKeyPath } = ctx.params;
|
39
|
+
|
40
|
+
return new Promise((resolve, reject) => {
|
41
|
+
crypto.generateKeyPair(
|
42
|
+
'rsa',
|
43
|
+
{
|
44
|
+
modulusLength: 4096,
|
45
|
+
publicKeyEncoding: {
|
46
|
+
type: 'spki',
|
47
|
+
format: 'pem'
|
48
|
+
},
|
49
|
+
privateKeyEncoding: {
|
50
|
+
type: 'pkcs8',
|
51
|
+
format: 'pem'
|
52
|
+
}
|
53
|
+
},
|
54
|
+
(err, publicKey, privateKey) => {
|
55
|
+
if (err) {
|
56
|
+
reject(err);
|
57
|
+
} else {
|
58
|
+
fs.writeFile(privateKeyPath, privateKey, err => {
|
59
|
+
if (err) {
|
60
|
+
reject(err);
|
61
|
+
} else {
|
62
|
+
fs.writeFile(publicKeyPath, publicKey, err => {
|
63
|
+
if (err) {
|
64
|
+
reject(err);
|
65
|
+
} else {
|
66
|
+
resolve({ privateKey, publicKey });
|
67
|
+
}
|
68
|
+
});
|
69
|
+
}
|
70
|
+
});
|
71
|
+
}
|
72
|
+
}
|
73
|
+
);
|
74
|
+
});
|
75
|
+
}
|
76
|
+
},
|
77
|
+
|
78
|
+
generateServerSignedToken: {
|
79
|
+
async handler(ctx) {
|
80
|
+
const { payload } = ctx.params;
|
81
|
+
return jwt.sign(payload, this.privateKey, { algorithm: 'RS256' });
|
82
|
+
}
|
83
|
+
},
|
84
|
+
|
85
|
+
verifyServerSignedToken: {
|
86
|
+
/** Verifies that the token was signed by this server. */
|
87
|
+
async handler(ctx) {
|
88
|
+
const { token } = ctx.params;
|
89
|
+
try {
|
90
|
+
return jwt.verify(token, this.publicKey, { algorithms: ['RS256'] });
|
91
|
+
} catch (err) {
|
92
|
+
return false;
|
93
|
+
}
|
94
|
+
}
|
95
|
+
},
|
96
|
+
|
97
|
+
generateUnsignedToken: {
|
98
|
+
async handler(ctx) {
|
99
|
+
const { payload } = ctx.params;
|
100
|
+
const token = jwt.sign(payload, null, { algorithm: 'none' });
|
101
|
+
return token;
|
102
|
+
}
|
103
|
+
},
|
104
|
+
|
105
|
+
decodeToken: {
|
106
|
+
// Warning, this does NOT verify if signature is valid
|
107
|
+
async handler(ctx) {
|
108
|
+
const { token } = ctx.params;
|
109
|
+
try {
|
110
|
+
return jwt.decode(token);
|
111
|
+
} catch (err) {
|
112
|
+
return false;
|
113
|
+
}
|
114
|
+
}
|
115
|
+
}
|
116
|
+
}
|
117
|
+
} satisfies ServiceSchema;
|
118
|
+
|
119
|
+
export default AuthJwtSchema;
|
120
|
+
|
121
|
+
declare global {
|
122
|
+
export namespace Moleculer {
|
123
|
+
export interface AllServices {
|
124
|
+
[AuthJwtSchema.name]: typeof AuthJwtSchema;
|
125
|
+
}
|
126
|
+
}
|
127
|
+
}
|
package/services/mail.ts
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
import path from 'path';
|
2
|
+
import urlJoin from 'url-join';
|
3
|
+
// @ts-expect-error TS(7016): Could not find a declaration file for module 'mole... Remove this comment to see the full error message
|
4
|
+
import MailService from 'moleculer-mail';
|
5
|
+
import { ServiceSchema } from 'moleculer';
|
6
|
+
import { fileURLToPath } from 'url';
|
7
|
+
|
8
|
+
// @ts-expect-error TS(1470): The 'import.meta' meta-property is not allowed in ... Remove this comment to see the full error message
|
9
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
10
|
+
|
11
|
+
const AuthMailSchema = {
|
12
|
+
name: 'auth.mail' as const,
|
13
|
+
mixins: [MailService],
|
14
|
+
settings: {
|
15
|
+
defaults: {
|
16
|
+
locale: 'en',
|
17
|
+
frontUrl: null
|
18
|
+
},
|
19
|
+
templateFolder: path.join(__dirname, '../templates'),
|
20
|
+
from: null,
|
21
|
+
transport: null
|
22
|
+
},
|
23
|
+
actions: {
|
24
|
+
sendResetPasswordEmail: {
|
25
|
+
async handler(ctx) {
|
26
|
+
const { account, token } = ctx.params;
|
27
|
+
|
28
|
+
await this.actions.send(
|
29
|
+
{
|
30
|
+
to: account.email,
|
31
|
+
template: 'reset-password',
|
32
|
+
locale: this.getTemplateLocale(account.preferredLocale || this.settings.defaults.locale),
|
33
|
+
data: {
|
34
|
+
account,
|
35
|
+
resetUrl: `${urlJoin(this.settings.defaults.frontUrl, 'login')}?new_password=true&token=${token}`
|
36
|
+
}
|
37
|
+
},
|
38
|
+
{
|
39
|
+
parentCtx: ctx
|
40
|
+
}
|
41
|
+
);
|
42
|
+
}
|
43
|
+
}
|
44
|
+
},
|
45
|
+
methods: {
|
46
|
+
getTemplateLocale(userLocale) {
|
47
|
+
switch (userLocale) {
|
48
|
+
case 'fr':
|
49
|
+
return 'fr-FR';
|
50
|
+
case 'en':
|
51
|
+
return 'en-EN';
|
52
|
+
default:
|
53
|
+
return 'en-EN';
|
54
|
+
}
|
55
|
+
}
|
56
|
+
}
|
57
|
+
} satisfies ServiceSchema;
|
58
|
+
|
59
|
+
export default AuthMailSchema;
|
60
|
+
|
61
|
+
declare global {
|
62
|
+
export namespace Moleculer {
|
63
|
+
export interface AllServices {
|
64
|
+
[AuthMailSchema.name]: typeof AuthMailSchema;
|
65
|
+
}
|
66
|
+
}
|
67
|
+
}
|
@@ -0,0 +1,43 @@
|
|
1
|
+
import { MIME_TYPES } from '@semapps/mime-types';
|
2
|
+
import { getSlugFromUri } from '@semapps/ldp';
|
3
|
+
import { ServiceSchema } from 'moleculer';
|
4
|
+
|
5
|
+
const AuthMigrationSchema = {
|
6
|
+
name: 'auth.migration' as const,
|
7
|
+
actions: {
|
8
|
+
migrateUsersToAccounts: {
|
9
|
+
async handler(ctx) {
|
10
|
+
const { usersContainer, emailPredicate, usernamePredicate } = ctx.params;
|
11
|
+
|
12
|
+
const results = await ctx.call('ldp.container.get', { containerUri: usersContainer, accept: MIME_TYPES.JSON });
|
13
|
+
|
14
|
+
for (const user of results['ldp:contains']) {
|
15
|
+
if (user[emailPredicate]) {
|
16
|
+
try {
|
17
|
+
await ctx.call('auth.account.create', {
|
18
|
+
email: user[emailPredicate],
|
19
|
+
username: usernamePredicate ? user[usernamePredicate] : getSlugFromUri(user.id),
|
20
|
+
webId: user.id
|
21
|
+
});
|
22
|
+
} catch (e) {
|
23
|
+
// @ts-expect-error TS(18046): 'e' is of type 'unknown'.
|
24
|
+
console.log(`Unable to create account for user ${user.id}. Error message: ${e.message}`);
|
25
|
+
}
|
26
|
+
} else {
|
27
|
+
console.log(`No email found for user ${user.id}`);
|
28
|
+
}
|
29
|
+
}
|
30
|
+
}
|
31
|
+
}
|
32
|
+
}
|
33
|
+
} satisfies ServiceSchema;
|
34
|
+
|
35
|
+
export default AuthMigrationSchema;
|
36
|
+
|
37
|
+
declare global {
|
38
|
+
export namespace Moleculer {
|
39
|
+
export interface AllServices {
|
40
|
+
[AuthMigrationSchema.name]: typeof AuthMigrationSchema;
|
41
|
+
}
|
42
|
+
}
|
43
|
+
}
|
package/tsconfig.json
ADDED
package/index.js
DELETED
@@ -1,9 +0,0 @@
|
|
1
|
-
module.exports = {
|
2
|
-
AuthCASService: require('./services/auth.cas'),
|
3
|
-
AuthLocalService: require('./services/auth.local'),
|
4
|
-
AuthOIDCService: require('./services/auth.oidc'),
|
5
|
-
AuthAccountService: require('./services/account'),
|
6
|
-
AuthJWTService: require('./services/jwt'),
|
7
|
-
AuthMigrationService: require('./services/migration'),
|
8
|
-
AuthMailService: require('./services/mail')
|
9
|
-
};
|
package/mixins/auth.sso.js
DELETED
@@ -1,93 +0,0 @@
|
|
1
|
-
const path = require('path');
|
2
|
-
const session = require('express-session');
|
3
|
-
const AuthMixin = require('./auth');
|
4
|
-
const saveRedirectUrl = require('../middlewares/saveRedirectUrl');
|
5
|
-
const redirectToFront = require('../middlewares/redirectToFront');
|
6
|
-
const localLogout = require('../middlewares/localLogout');
|
7
|
-
|
8
|
-
const AuthSSOMixin = {
|
9
|
-
mixins: [AuthMixin],
|
10
|
-
settings: {
|
11
|
-
baseUrl: null,
|
12
|
-
jwtPath: null,
|
13
|
-
registrationAllowed: true,
|
14
|
-
reservedUsernames: [],
|
15
|
-
webIdSelection: [],
|
16
|
-
// SSO-specific settings
|
17
|
-
sessionSecret: 's€m@pps',
|
18
|
-
selectSsoData: null
|
19
|
-
},
|
20
|
-
actions: {
|
21
|
-
async loginOrSignup(ctx) {
|
22
|
-
const { ssoData } = ctx.params;
|
23
|
-
|
24
|
-
const profileData = this.settings.selectSsoData ? await this.settings.selectSsoData(ssoData) : ssoData;
|
25
|
-
|
26
|
-
// TODO use UUID to identify unique accounts with SSO
|
27
|
-
const existingAccounts = await ctx.call('auth.account.find', { query: { email: profileData.email } });
|
28
|
-
|
29
|
-
let accountData;
|
30
|
-
let webId;
|
31
|
-
let newUser;
|
32
|
-
if (existingAccounts.length > 0) {
|
33
|
-
accountData = existingAccounts[0];
|
34
|
-
webId = accountData.webId;
|
35
|
-
newUser = false;
|
36
|
-
|
37
|
-
// TODO update account with recent profileData information
|
38
|
-
|
39
|
-
ctx.emit('auth.connected', { webId, accountData, ssoData }, { meta: { webId: null, dataset: null } });
|
40
|
-
} else {
|
41
|
-
if (!this.settings.registrationAllowed) {
|
42
|
-
throw new Error('registration.not-allowed');
|
43
|
-
}
|
44
|
-
|
45
|
-
accountData = await ctx.call('auth.account.create', {
|
46
|
-
uuid: profileData.uuid,
|
47
|
-
email: profileData.email,
|
48
|
-
username: profileData.username
|
49
|
-
});
|
50
|
-
webId = await ctx.call('webid.createWebId', this.pickWebIdData({ nick: accountData.username, ...profileData }));
|
51
|
-
newUser = true;
|
52
|
-
|
53
|
-
// Link the webId with the account
|
54
|
-
await ctx.call('auth.account.attachWebId', { accountUri: accountData['@id'], webId });
|
55
|
-
|
56
|
-
ctx.emit(
|
57
|
-
'auth.registered',
|
58
|
-
{ webId, profileData, accountData, ssoData },
|
59
|
-
{ meta: { webId: null, dataset: null } }
|
60
|
-
);
|
61
|
-
}
|
62
|
-
|
63
|
-
const token = await ctx.call('auth.jwt.generateServerSignedToken', { payload: { webId } });
|
64
|
-
|
65
|
-
return { token, newUser };
|
66
|
-
}
|
67
|
-
},
|
68
|
-
methods: {
|
69
|
-
getApiRoutes(basePath) {
|
70
|
-
const sessionMiddleware = session({ secret: this.settings.sessionSecret, maxAge: null });
|
71
|
-
return [
|
72
|
-
{
|
73
|
-
path: path.join(basePath, '/auth'),
|
74
|
-
name: 'auth',
|
75
|
-
use: [sessionMiddleware, this.passport.initialize(), this.passport.session()],
|
76
|
-
aliases: {
|
77
|
-
'GET /': [saveRedirectUrl, this.passport.authenticate(this.passportId, { session: false }), redirectToFront]
|
78
|
-
}
|
79
|
-
},
|
80
|
-
{
|
81
|
-
path: path.join(basePath, '/auth/logout'),
|
82
|
-
name: 'auth-logout',
|
83
|
-
use: [sessionMiddleware, this.passport.initialize(), this.passport.session()],
|
84
|
-
aliases: {
|
85
|
-
'GET /': [saveRedirectUrl, localLogout, redirectToFront]
|
86
|
-
}
|
87
|
-
}
|
88
|
-
];
|
89
|
-
}
|
90
|
-
}
|
91
|
-
};
|
92
|
-
|
93
|
-
module.exports = AuthSSOMixin;
|