@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,111 @@
|
|
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
|
+
/**
|
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
|
+
const AuthJwtSchema = {
|
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
|
+
if (!fs.existsSync(privateKeyPath) && !fs.existsSync(publicKeyPath)) {
|
22
|
+
this.logger.info('JWT keypair not found, generating...');
|
23
|
+
if (!fs.existsSync(this.settings.jwtPath)) {
|
24
|
+
fs.mkdirSync(this.settings.jwtPath);
|
25
|
+
}
|
26
|
+
await this.actions.generateKeyPair({ privateKeyPath, publicKeyPath });
|
27
|
+
}
|
28
|
+
this.privateKey = fs.readFileSync(privateKeyPath);
|
29
|
+
this.publicKey = fs.readFileSync(publicKeyPath);
|
30
|
+
},
|
31
|
+
actions: {
|
32
|
+
generateKeyPair: {
|
33
|
+
handler(ctx) {
|
34
|
+
const { privateKeyPath, publicKeyPath } = ctx.params;
|
35
|
+
return new Promise((resolve, reject) => {
|
36
|
+
crypto.generateKeyPair('rsa', {
|
37
|
+
modulusLength: 4096,
|
38
|
+
publicKeyEncoding: {
|
39
|
+
type: 'spki',
|
40
|
+
format: 'pem'
|
41
|
+
},
|
42
|
+
privateKeyEncoding: {
|
43
|
+
type: 'pkcs8',
|
44
|
+
format: 'pem'
|
45
|
+
}
|
46
|
+
}, (err, publicKey, privateKey) => {
|
47
|
+
if (err) {
|
48
|
+
reject(err);
|
49
|
+
}
|
50
|
+
else {
|
51
|
+
fs.writeFile(privateKeyPath, privateKey, err => {
|
52
|
+
if (err) {
|
53
|
+
reject(err);
|
54
|
+
}
|
55
|
+
else {
|
56
|
+
fs.writeFile(publicKeyPath, publicKey, err => {
|
57
|
+
if (err) {
|
58
|
+
reject(err);
|
59
|
+
}
|
60
|
+
else {
|
61
|
+
resolve({ privateKey, publicKey });
|
62
|
+
}
|
63
|
+
});
|
64
|
+
}
|
65
|
+
});
|
66
|
+
}
|
67
|
+
});
|
68
|
+
});
|
69
|
+
}
|
70
|
+
},
|
71
|
+
generateServerSignedToken: {
|
72
|
+
async handler(ctx) {
|
73
|
+
const { payload } = ctx.params;
|
74
|
+
return jwt.sign(payload, this.privateKey, { algorithm: 'RS256' });
|
75
|
+
}
|
76
|
+
},
|
77
|
+
verifyServerSignedToken: {
|
78
|
+
/** Verifies that the token was signed by this server. */
|
79
|
+
async handler(ctx) {
|
80
|
+
const { token } = ctx.params;
|
81
|
+
try {
|
82
|
+
return jwt.verify(token, this.publicKey, { algorithms: ['RS256'] });
|
83
|
+
}
|
84
|
+
catch (err) {
|
85
|
+
return false;
|
86
|
+
}
|
87
|
+
}
|
88
|
+
},
|
89
|
+
generateUnsignedToken: {
|
90
|
+
async handler(ctx) {
|
91
|
+
const { payload } = ctx.params;
|
92
|
+
const token = jwt.sign(payload, null, { algorithm: 'none' });
|
93
|
+
return token;
|
94
|
+
}
|
95
|
+
},
|
96
|
+
decodeToken: {
|
97
|
+
// Warning, this does NOT verify if signature is valid
|
98
|
+
async handler(ctx) {
|
99
|
+
const { token } = ctx.params;
|
100
|
+
try {
|
101
|
+
return jwt.decode(token);
|
102
|
+
}
|
103
|
+
catch (err) {
|
104
|
+
return false;
|
105
|
+
}
|
106
|
+
}
|
107
|
+
}
|
108
|
+
}
|
109
|
+
};
|
110
|
+
export default AuthJwtSchema;
|
111
|
+
//# sourceMappingURL=jwt.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"jwt.js","sourceRoot":"","sources":["../../services/jwt.ts"],"names":[],"mappings":"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"}
|
@@ -0,0 +1,31 @@
|
|
1
|
+
declare const AuthMailSchema: {
|
2
|
+
name: "auth.mail";
|
3
|
+
mixins: any[];
|
4
|
+
settings: {
|
5
|
+
defaults: {
|
6
|
+
locale: string;
|
7
|
+
frontUrl: null;
|
8
|
+
};
|
9
|
+
templateFolder: string;
|
10
|
+
from: null;
|
11
|
+
transport: null;
|
12
|
+
};
|
13
|
+
actions: {
|
14
|
+
sendResetPasswordEmail: {
|
15
|
+
handler(ctx: Moleculer.Context<Optionalize<{
|
16
|
+
[x: string]: any;
|
17
|
+
}>, {}, Moleculer.GenericObject>): Promise<void>;
|
18
|
+
};
|
19
|
+
};
|
20
|
+
methods: {
|
21
|
+
getTemplateLocale(userLocale: any): "fr-FR" | "en-EN";
|
22
|
+
};
|
23
|
+
};
|
24
|
+
export default AuthMailSchema;
|
25
|
+
declare global {
|
26
|
+
export namespace Moleculer {
|
27
|
+
interface AllServices {
|
28
|
+
[AuthMailSchema.name]: typeof AuthMailSchema;
|
29
|
+
}
|
30
|
+
}
|
31
|
+
}
|
@@ -0,0 +1,52 @@
|
|
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 { fileURLToPath } from 'url';
|
6
|
+
// @ts-expect-error TS(1470): The 'import.meta' meta-property is not allowed in ... Remove this comment to see the full error message
|
7
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
8
|
+
const AuthMailSchema = {
|
9
|
+
name: 'auth.mail',
|
10
|
+
mixins: [MailService],
|
11
|
+
settings: {
|
12
|
+
defaults: {
|
13
|
+
locale: 'en',
|
14
|
+
frontUrl: null
|
15
|
+
},
|
16
|
+
templateFolder: path.join(__dirname, '../templates'),
|
17
|
+
from: null,
|
18
|
+
transport: null
|
19
|
+
},
|
20
|
+
actions: {
|
21
|
+
sendResetPasswordEmail: {
|
22
|
+
async handler(ctx) {
|
23
|
+
const { account, token } = ctx.params;
|
24
|
+
await this.actions.send({
|
25
|
+
to: account.email,
|
26
|
+
template: 'reset-password',
|
27
|
+
locale: this.getTemplateLocale(account.preferredLocale || this.settings.defaults.locale),
|
28
|
+
data: {
|
29
|
+
account,
|
30
|
+
resetUrl: `${urlJoin(this.settings.defaults.frontUrl, 'login')}?new_password=true&token=${token}`
|
31
|
+
}
|
32
|
+
}, {
|
33
|
+
parentCtx: ctx
|
34
|
+
});
|
35
|
+
}
|
36
|
+
}
|
37
|
+
},
|
38
|
+
methods: {
|
39
|
+
getTemplateLocale(userLocale) {
|
40
|
+
switch (userLocale) {
|
41
|
+
case 'fr':
|
42
|
+
return 'fr-FR';
|
43
|
+
case 'en':
|
44
|
+
return 'en-EN';
|
45
|
+
default:
|
46
|
+
return 'en-EN';
|
47
|
+
}
|
48
|
+
}
|
49
|
+
}
|
50
|
+
};
|
51
|
+
export default AuthMailSchema;
|
52
|
+
//# sourceMappingURL=mail.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"mail.js","sourceRoot":"","sources":["../../services/mail.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,OAAO,MAAM,UAAU,CAAC;AAC/B,qIAAqI;AACrI,OAAO,WAAW,MAAM,gBAAgB,CAAC;AAEzC,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AAEpC,qIAAqI;AACrI,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAE/D,MAAM,cAAc,GAAG;IACrB,IAAI,EAAE,WAAoB;IAC1B,MAAM,EAAE,CAAC,WAAW,CAAC;IACrB,QAAQ,EAAE;QACR,QAAQ,EAAE;YACR,MAAM,EAAE,IAAI;YACZ,QAAQ,EAAE,IAAI;SACf;QACD,cAAc,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,cAAc,CAAC;QACpD,IAAI,EAAE,IAAI;QACV,SAAS,EAAE,IAAI;KAChB;IACD,OAAO,EAAE;QACP,sBAAsB,EAAE;YACtB,KAAK,CAAC,OAAO,CAAC,GAAG;gBACf,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC;gBAEtC,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CACrB;oBACE,EAAE,EAAE,OAAO,CAAC,KAAK;oBACjB,QAAQ,EAAE,gBAAgB;oBAC1B,MAAM,EAAE,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC,eAAe,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC;oBACxF,IAAI,EAAE;wBACJ,OAAO;wBACP,QAAQ,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,4BAA4B,KAAK,EAAE;qBAClG;iBACF,EACD;oBACE,SAAS,EAAE,GAAG;iBACf,CACF,CAAC;YACJ,CAAC;SACF;KACF;IACD,OAAO,EAAE;QACP,iBAAiB,CAAC,UAAU;YAC1B,QAAQ,UAAU,EAAE,CAAC;gBACnB,KAAK,IAAI;oBACP,OAAO,OAAO,CAAC;gBACjB,KAAK,IAAI;oBACP,OAAO,OAAO,CAAC;gBACjB;oBACE,OAAO,OAAO,CAAC;YACnB,CAAC;QACH,CAAC;KACF;CACsB,CAAC;AAE1B,eAAe,cAAc,CAAC"}
|
@@ -0,0 +1,18 @@
|
|
1
|
+
declare const AuthMigrationSchema: {
|
2
|
+
name: "auth.migration";
|
3
|
+
actions: {
|
4
|
+
migrateUsersToAccounts: {
|
5
|
+
handler(ctx: Moleculer.Context<Optionalize<{
|
6
|
+
[x: string]: any;
|
7
|
+
}>, {}, Moleculer.GenericObject>): Promise<void>;
|
8
|
+
};
|
9
|
+
};
|
10
|
+
};
|
11
|
+
export default AuthMigrationSchema;
|
12
|
+
declare global {
|
13
|
+
export namespace Moleculer {
|
14
|
+
interface AllServices {
|
15
|
+
[AuthMigrationSchema.name]: typeof AuthMigrationSchema;
|
16
|
+
}
|
17
|
+
}
|
18
|
+
}
|
@@ -0,0 +1,33 @@
|
|
1
|
+
import { MIME_TYPES } from '@semapps/mime-types';
|
2
|
+
import { getSlugFromUri } from '@semapps/ldp';
|
3
|
+
const AuthMigrationSchema = {
|
4
|
+
name: 'auth.migration',
|
5
|
+
actions: {
|
6
|
+
migrateUsersToAccounts: {
|
7
|
+
async handler(ctx) {
|
8
|
+
const { usersContainer, emailPredicate, usernamePredicate } = ctx.params;
|
9
|
+
const results = await ctx.call('ldp.container.get', { containerUri: usersContainer, accept: MIME_TYPES.JSON });
|
10
|
+
for (const user of results['ldp:contains']) {
|
11
|
+
if (user[emailPredicate]) {
|
12
|
+
try {
|
13
|
+
await ctx.call('auth.account.create', {
|
14
|
+
email: user[emailPredicate],
|
15
|
+
username: usernamePredicate ? user[usernamePredicate] : getSlugFromUri(user.id),
|
16
|
+
webId: user.id
|
17
|
+
});
|
18
|
+
}
|
19
|
+
catch (e) {
|
20
|
+
// @ts-expect-error TS(18046): 'e' is of type 'unknown'.
|
21
|
+
console.log(`Unable to create account for user ${user.id}. Error message: ${e.message}`);
|
22
|
+
}
|
23
|
+
}
|
24
|
+
else {
|
25
|
+
console.log(`No email found for user ${user.id}`);
|
26
|
+
}
|
27
|
+
}
|
28
|
+
}
|
29
|
+
}
|
30
|
+
}
|
31
|
+
};
|
32
|
+
export default AuthMigrationSchema;
|
33
|
+
//# sourceMappingURL=migration.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"migration.js","sourceRoot":"","sources":["../../services/migration.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAG9C,MAAM,mBAAmB,GAAG;IAC1B,IAAI,EAAE,gBAAyB;IAC/B,OAAO,EAAE;QACP,sBAAsB,EAAE;YACtB,KAAK,CAAC,OAAO,CAAC,GAAG;gBACf,MAAM,EAAE,cAAc,EAAE,cAAc,EAAE,iBAAiB,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC;gBAEzE,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,mBAAmB,EAAE,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,EAAE,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC;gBAE/G,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,cAAc,CAAC,EAAE,CAAC;oBAC3C,IAAI,IAAI,CAAC,cAAc,CAAC,EAAE,CAAC;wBACzB,IAAI,CAAC;4BACH,MAAM,GAAG,CAAC,IAAI,CAAC,qBAAqB,EAAE;gCACpC,KAAK,EAAE,IAAI,CAAC,cAAc,CAAC;gCAC3B,QAAQ,EAAE,iBAAiB,CAAC,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,CAAC;gCAC/E,KAAK,EAAE,IAAI,CAAC,EAAE;6BACf,CAAC,CAAC;wBACL,CAAC;wBAAC,OAAO,CAAC,EAAE,CAAC;4BACX,wDAAwD;4BACxD,OAAO,CAAC,GAAG,CAAC,qCAAqC,IAAI,CAAC,EAAE,oBAAoB,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;wBAC3F,CAAC;oBACH,CAAC;yBAAM,CAAC;wBACN,OAAO,CAAC,GAAG,CAAC,2BAA2B,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;oBACpD,CAAC;gBACH,CAAC;YACH,CAAC;SACF;KACF;CACsB,CAAC;AAE1B,eAAe,mBAAmB,CAAC"}
|
@@ -0,0 +1 @@
|
|
1
|
+
{"root":["../index.ts"],"errors":true,"version":"5.9.2"}
|
package/index.ts
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
import AuthCASService from './services/auth.cas.ts';
|
2
|
+
import AuthLocalService from './services/auth.local.ts';
|
3
|
+
import AuthOIDCService from './services/auth.oidc.ts';
|
4
|
+
import AuthAccountService from './services/account.ts';
|
5
|
+
import AuthJWTService from './services/jwt.ts';
|
6
|
+
import AuthMigrationService from './services/migration.ts';
|
7
|
+
import AuthMailService from './services/mail.ts';
|
8
|
+
|
9
|
+
export {
|
10
|
+
AuthCASService,
|
11
|
+
AuthLocalService,
|
12
|
+
AuthOIDCService,
|
13
|
+
AuthAccountService,
|
14
|
+
AuthJWTService,
|
15
|
+
AuthMigrationService,
|
16
|
+
AuthMailService
|
17
|
+
};
|
@@ -1,4 +1,4 @@
|
|
1
|
-
const redirectToFront = (req, res) => {
|
1
|
+
const redirectToFront = (req: any, res: any) => {
|
2
2
|
// Redirect browser to the redirect URL pushed in session
|
3
3
|
const redirectUrl = new URL(req.session.redirectUrl);
|
4
4
|
if (req.user) {
|
@@ -11,4 +11,4 @@ const redirectToFront = (req, res) => {
|
|
11
11
|
res.end();
|
12
12
|
};
|
13
13
|
|
14
|
-
|
14
|
+
export default redirectToFront;
|
@@ -1,4 +1,4 @@
|
|
1
|
-
const saveRedirectUrl = (req, res, next) => {
|
1
|
+
const saveRedirectUrl = (req: any, res: any, next: any) => {
|
2
2
|
// Persist referer on the session to get it back after redirection
|
3
3
|
// If the redirectUrl is already in the session, use it as default value
|
4
4
|
req.session.redirectUrl =
|
@@ -6,4 +6,4 @@ const saveRedirectUrl = (req, res, next) => {
|
|
6
6
|
next();
|
7
7
|
};
|
8
8
|
|
9
|
-
|
9
|
+
export default saveRedirectUrl;
|
@@ -1,6 +1,6 @@
|
|
1
|
-
const sendToken = (req, res) => {
|
1
|
+
const sendToken = (req: any, res: any) => {
|
2
2
|
res.setHeader('Content-Type', 'application/json');
|
3
3
|
res.end(JSON.stringify({ token: req.user.token, webId: req.user.webId, newUser: req.user.newUser }));
|
4
4
|
};
|
5
5
|
|
6
|
-
|
6
|
+
export default sendToken;
|
@@ -0,0 +1,100 @@
|
|
1
|
+
import path from 'path';
|
2
|
+
// @ts-expect-error TS(7016): Could not find a declaration file for module 'expr... Remove this comment to see the full error message
|
3
|
+
import session from 'express-session';
|
4
|
+
import { ServiceSchema } from 'moleculer';
|
5
|
+
import AuthMixin from './auth.ts';
|
6
|
+
import saveRedirectUrl from '../middlewares/saveRedirectUrl.ts';
|
7
|
+
import redirectToFront from '../middlewares/redirectToFront.ts';
|
8
|
+
import localLogout from '../middlewares/localLogout.ts';
|
9
|
+
|
10
|
+
const AuthSSOMixin = {
|
11
|
+
mixins: [AuthMixin],
|
12
|
+
settings: {
|
13
|
+
baseUrl: null,
|
14
|
+
jwtPath: null,
|
15
|
+
registrationAllowed: true,
|
16
|
+
reservedUsernames: [],
|
17
|
+
webIdSelection: [],
|
18
|
+
// SSO-specific settings
|
19
|
+
sessionSecret: 's€m@pps',
|
20
|
+
selectSsoData: null
|
21
|
+
},
|
22
|
+
actions: {
|
23
|
+
loginOrSignup: {
|
24
|
+
async handler(ctx) {
|
25
|
+
const { ssoData } = ctx.params;
|
26
|
+
|
27
|
+
const profileData = this.settings.selectSsoData ? await this.settings.selectSsoData(ssoData) : ssoData;
|
28
|
+
|
29
|
+
// TODO use UUID to identify unique accounts with SSO
|
30
|
+
const existingAccounts = await ctx.call('auth.account.find', { query: { email: profileData.email } });
|
31
|
+
|
32
|
+
let accountData;
|
33
|
+
let webId;
|
34
|
+
let newUser;
|
35
|
+
if (existingAccounts.length > 0) {
|
36
|
+
accountData = existingAccounts[0];
|
37
|
+
webId = accountData.webId;
|
38
|
+
newUser = false;
|
39
|
+
|
40
|
+
// TODO update account with recent profileData information
|
41
|
+
|
42
|
+
ctx.emit('auth.connected', { webId, accountData, ssoData }, { meta: { webId: null, dataset: null } });
|
43
|
+
} else {
|
44
|
+
if (!this.settings.registrationAllowed) {
|
45
|
+
throw new Error('registration.not-allowed');
|
46
|
+
}
|
47
|
+
|
48
|
+
accountData = await ctx.call('auth.account.create', {
|
49
|
+
uuid: profileData.uuid,
|
50
|
+
email: profileData.email,
|
51
|
+
username: profileData.username
|
52
|
+
});
|
53
|
+
webId = await ctx.call(
|
54
|
+
'webid.createWebId',
|
55
|
+
this.pickWebIdData({ nick: accountData.username, ...profileData })
|
56
|
+
);
|
57
|
+
newUser = true;
|
58
|
+
|
59
|
+
// Link the webId with the account
|
60
|
+
await ctx.call('auth.account.attachWebId', { accountUri: accountData['@id'], webId });
|
61
|
+
|
62
|
+
ctx.emit(
|
63
|
+
'auth.registered',
|
64
|
+
{ webId, profileData, accountData, ssoData },
|
65
|
+
{ meta: { webId: null, dataset: null } }
|
66
|
+
);
|
67
|
+
}
|
68
|
+
|
69
|
+
const token = await ctx.call('auth.jwt.generateServerSignedToken', { payload: { webId } });
|
70
|
+
|
71
|
+
return { token, newUser };
|
72
|
+
}
|
73
|
+
}
|
74
|
+
},
|
75
|
+
methods: {
|
76
|
+
getApiRoutes(basePath) {
|
77
|
+
const sessionMiddleware = session({ secret: this.settings.sessionSecret, maxAge: null });
|
78
|
+
return [
|
79
|
+
{
|
80
|
+
path: path.join(basePath, '/auth'),
|
81
|
+
name: 'auth',
|
82
|
+
use: [sessionMiddleware, this.passport.initialize(), this.passport.session()],
|
83
|
+
aliases: {
|
84
|
+
'GET /': [saveRedirectUrl, this.passport.authenticate(this.passportId, { session: false }), redirectToFront]
|
85
|
+
}
|
86
|
+
},
|
87
|
+
{
|
88
|
+
path: path.join(basePath, '/auth/logout'),
|
89
|
+
name: 'auth-logout',
|
90
|
+
use: [sessionMiddleware, this.passport.initialize(), this.passport.session()],
|
91
|
+
aliases: {
|
92
|
+
'GET /': [saveRedirectUrl, localLogout, redirectToFront]
|
93
|
+
}
|
94
|
+
}
|
95
|
+
];
|
96
|
+
}
|
97
|
+
}
|
98
|
+
} satisfies Partial<ServiceSchema>;
|
99
|
+
|
100
|
+
export default AuthSSOMixin;
|
@@ -1,8 +1,11 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
1
|
+
// @ts-expect-error TS(7016): Could not find a declaration file for module 'pass... Remove this comment to see the full error message
|
2
|
+
import passport from 'passport';
|
3
|
+
// @ts-expect-error TS(2614): Module '"moleculer-web"' has no exported member 'E... Remove this comment to see the full error message
|
4
|
+
import { Errors as E } from 'moleculer-web';
|
5
|
+
import { TripleStoreAdapter } from '@semapps/triplestore';
|
6
|
+
import { ServiceSchema } from 'moleculer';
|
7
|
+
import AuthAccountService from '../services/account.ts';
|
8
|
+
import AuthJWTService from '../services/jwt.ts';
|
6
9
|
|
7
10
|
/**
|
8
11
|
* Auth Mixin that handles authentication and authorization for routes
|
@@ -76,11 +79,13 @@ const AuthMixin = {
|
|
76
79
|
const { jwtPath, reservedUsernames, minPasswordLength, minUsernameLength, accountsDataset, podProvider } =
|
77
80
|
this.settings;
|
78
81
|
|
82
|
+
// @ts-expect-error TS(2345): Argument of type '{ mixins: { name: "auth.jwt"; se... Remove this comment to see the full error message
|
79
83
|
this.broker.createService({
|
80
84
|
mixins: [AuthJWTService],
|
81
85
|
settings: { jwtPath }
|
82
86
|
});
|
83
87
|
|
88
|
+
// @ts-expect-error TS(2345): Argument of type '{ mixins: { name: "auth.account"... Remove this comment to see the full error message
|
84
89
|
this.broker.createService({
|
85
90
|
mixins: [AuthAccountService],
|
86
91
|
settings: { reservedUsernames, minPasswordLength, minUsernameLength },
|
@@ -91,10 +96,10 @@ const AuthMixin = {
|
|
91
96
|
if (!this.passportId) throw new Error('this.passportId must be set in the service creation.');
|
92
97
|
|
93
98
|
this.passport = passport;
|
94
|
-
this.passport.serializeUser((user, done) => {
|
99
|
+
this.passport.serializeUser((user: any, done: any) => {
|
95
100
|
done(null, user);
|
96
101
|
});
|
97
|
-
this.passport.deserializeUser((user, done) => {
|
102
|
+
this.passport.deserializeUser((user: any, done: any) => {
|
98
103
|
done(null, user);
|
99
104
|
});
|
100
105
|
|
@@ -109,24 +114,70 @@ const AuthMixin = {
|
|
109
114
|
}
|
110
115
|
},
|
111
116
|
actions: {
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
117
|
+
authenticate: {
|
118
|
+
// See https://moleculer.services/docs/0.13/moleculer-web.html#Authentication
|
119
|
+
async handler(ctx) {
|
120
|
+
const { route, req, res } = ctx.params;
|
121
|
+
// Extract method and token from authorization header.
|
122
|
+
const [method, token] = req.headers.authorization?.split(' ') || [];
|
123
|
+
|
124
|
+
if (!token) {
|
125
|
+
// No token
|
126
|
+
// @ts-expect-error TS(2339): Property 'webId' does not exist on type '{}'.
|
127
|
+
ctx.meta.webId = 'anon';
|
128
|
+
return Promise.resolve(null);
|
129
|
+
}
|
130
|
+
|
131
|
+
if (method === 'Bearer') {
|
132
|
+
const payload = await ctx.call('auth.jwt.verifyServerSignedToken', { token });
|
133
|
+
if (payload) {
|
134
|
+
// @ts-expect-error TS(2339): Property 'tokenPayload' does not exist on type '{}... Remove this comment to see the full error message
|
135
|
+
ctx.meta.tokenPayload = payload;
|
136
|
+
// @ts-expect-error TS(2339): Property 'webId' does not exist on type '{}'.
|
137
|
+
ctx.meta.webId = payload.webId;
|
138
|
+
return Promise.resolve(payload);
|
139
|
+
}
|
140
|
+
|
141
|
+
// Check if token is a capability.
|
142
|
+
if (route.opts.authorizeWithCapability) {
|
143
|
+
return this.validateCapability(ctx, token);
|
144
|
+
}
|
145
|
+
|
146
|
+
// Invalid token
|
147
|
+
// TODO make sure token is deleted client-side
|
148
|
+
return Promise.reject(new E.UnAuthorizedError(E.ERR_INVALID_TOKEN));
|
149
|
+
}
|
150
|
+
|
151
|
+
// No valid auth method given.
|
152
|
+
// @ts-expect-error TS(2339): Property 'webId' does not exist on type '{}'.
|
120
153
|
ctx.meta.webId = 'anon';
|
121
154
|
return Promise.resolve(null);
|
122
155
|
}
|
156
|
+
},
|
157
|
+
|
158
|
+
authorize: {
|
159
|
+
// See https://moleculer.services/docs/0.13/moleculer-web.html#Authorization
|
160
|
+
async handler(ctx) {
|
161
|
+
const { route, req, res } = ctx.params;
|
162
|
+
// Extract token from authorization header (do not take the Bearer part)
|
163
|
+
/** @type {[string, string]} */
|
164
|
+
const [method, token] = req.headers.authorization && req.headers.authorization.split(' ');
|
123
165
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
166
|
+
if (!token) {
|
167
|
+
return Promise.reject(new E.UnAuthorizedError(E.ERR_NO_TOKEN));
|
168
|
+
}
|
169
|
+
if (method !== 'Bearer') {
|
170
|
+
return Promise.reject(new E.UnAuthorizedError(E.ERR_INVALID_TOKEN));
|
171
|
+
}
|
172
|
+
|
173
|
+
// Validate if the token was signed by server (registered user).
|
174
|
+
const serverSignedPayload = await ctx.call('auth.jwt.verifyServerSignedToken', { token });
|
175
|
+
if (serverSignedPayload) {
|
176
|
+
// @ts-expect-error TS(2339): Property 'tokenPayload' does not exist on type '{}... Remove this comment to see the full error message
|
177
|
+
ctx.meta.tokenPayload = serverSignedPayload;
|
178
|
+
// @ts-expect-error TS(2339): Property 'webId' does not exist on type '{}'.
|
179
|
+
ctx.meta.webId = serverSignedPayload.webId;
|
180
|
+
return Promise.resolve(serverSignedPayload);
|
130
181
|
}
|
131
182
|
|
132
183
|
// Check if token is a capability.
|
@@ -134,53 +185,19 @@ const AuthMixin = {
|
|
134
185
|
return this.validateCapability(ctx, token);
|
135
186
|
}
|
136
187
|
|
137
|
-
// Invalid token
|
138
|
-
// TODO make sure token is deleted client-side
|
139
188
|
return Promise.reject(new E.UnAuthorizedError(E.ERR_INVALID_TOKEN));
|
140
189
|
}
|
141
|
-
|
142
|
-
// No valid auth method given.
|
143
|
-
ctx.meta.webId = 'anon';
|
144
|
-
return Promise.resolve(null);
|
145
190
|
},
|
146
191
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
return Promise.reject(new E.UnAuthorizedError(E.ERR_NO_TOKEN));
|
192
|
+
impersonate: {
|
193
|
+
async handler(ctx) {
|
194
|
+
const { webId } = ctx.params;
|
195
|
+
return await ctx.call('auth.jwt.generateServerSignedToken', {
|
196
|
+
payload: {
|
197
|
+
webId
|
198
|
+
}
|
199
|
+
});
|
156
200
|
}
|
157
|
-
if (method !== 'Bearer') {
|
158
|
-
return Promise.reject(new E.UnAuthorizedError(E.ERR_INVALID_TOKEN));
|
159
|
-
}
|
160
|
-
|
161
|
-
// Validate if the token was signed by server (registered user).
|
162
|
-
const serverSignedPayload = await ctx.call('auth.jwt.verifyServerSignedToken', { token });
|
163
|
-
if (serverSignedPayload) {
|
164
|
-
ctx.meta.tokenPayload = serverSignedPayload;
|
165
|
-
ctx.meta.webId = serverSignedPayload.webId;
|
166
|
-
return Promise.resolve(serverSignedPayload);
|
167
|
-
}
|
168
|
-
|
169
|
-
// Check if token is a capability.
|
170
|
-
if (route.opts.authorizeWithCapability) {
|
171
|
-
return this.validateCapability(ctx, token);
|
172
|
-
}
|
173
|
-
|
174
|
-
return Promise.reject(new E.UnAuthorizedError(E.ERR_INVALID_TOKEN));
|
175
|
-
},
|
176
|
-
|
177
|
-
async impersonate(ctx) {
|
178
|
-
const { webId } = ctx.params;
|
179
|
-
return await ctx.call('auth.jwt.generateServerSignedToken', {
|
180
|
-
payload: {
|
181
|
-
webId
|
182
|
-
}
|
183
|
-
});
|
184
201
|
}
|
185
202
|
},
|
186
203
|
methods: {
|
@@ -189,6 +206,11 @@ const AuthMixin = {
|
|
189
206
|
// We do not use the VC-JOSE spec to sign and envelop presentations. Instead we go
|
190
207
|
// with embedded signatures. This way, the signature persists within the resource.
|
191
208
|
|
209
|
+
const hasCapabilityService = ctx.broker.registry.actions.isAvailable(
|
210
|
+
'crypto.vc.verifier.verifyCapabilityPresentation'
|
211
|
+
);
|
212
|
+
if (!hasCapabilityService) return Promise.reject(new E.UnAuthorizedError(E.ERR_INVALID_TOKEN));
|
213
|
+
|
192
214
|
// Decode JTW to JSON.
|
193
215
|
const decodedToken = await ctx.call('auth.jwt.decodeToken', { token });
|
194
216
|
if (!decodedToken) return Promise.reject(new E.UnAuthorizedError(E.ERR_INVALID_TOKEN));
|
@@ -220,7 +242,9 @@ const AuthMixin = {
|
|
220
242
|
},
|
221
243
|
pickWebIdData(data) {
|
222
244
|
if (this.settings.webIdSelection.length > 0) {
|
223
|
-
return Object.fromEntries(
|
245
|
+
return Object.fromEntries(
|
246
|
+
this.settings.webIdSelection.filter((key: any) => key in data).map((key: any) => [key, data[key]])
|
247
|
+
);
|
224
248
|
} else {
|
225
249
|
// TODO do not return anything if webIdSelection is empty, to conform with pickAccountData
|
226
250
|
return data || {};
|
@@ -229,13 +253,13 @@ const AuthMixin = {
|
|
229
253
|
pickAccountData(data) {
|
230
254
|
if (this.settings.accountSelection.length > 0) {
|
231
255
|
return Object.fromEntries(
|
232
|
-
this.settings.accountSelection.filter(key => key in data).map(key => [key, data[key]])
|
256
|
+
this.settings.accountSelection.filter((key: any) => key in data).map((key: any) => [key, data[key]])
|
233
257
|
);
|
234
258
|
} else {
|
235
259
|
return {};
|
236
260
|
}
|
237
261
|
}
|
238
262
|
}
|
239
|
-
}
|
263
|
+
} satisfies Partial<ServiceSchema>;
|
240
264
|
|
241
|
-
|
265
|
+
export default AuthMixin;
|