@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
@@ -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
+ };
@@ -0,0 +1,6 @@
1
+ const localLogout = (req: any, res: any, next: any) => {
2
+ req.logout(); // Passport logout
3
+ next();
4
+ };
5
+
6
+ export default localLogout;
@@ -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
- module.exports = redirectToFront;
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
- module.exports = saveRedirectUrl;
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
- module.exports = sendToken;
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
- const passport = require('passport');
2
- const { Errors: E } = require('moleculer-web');
3
- const { TripleStoreAdapter } = require('@semapps/triplestore');
4
- const AuthAccountService = require('../services/account');
5
- const AuthJWTService = require('../services/jwt');
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
- // See https://moleculer.services/docs/0.13/moleculer-web.html#Authentication
113
- async authenticate(ctx) {
114
- const { route, req, res } = ctx.params;
115
- // Extract method and token from authorization header.
116
- const [method, token] = req.headers.authorization?.split(' ') || [];
117
-
118
- if (!token) {
119
- // No token
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
- if (method === 'Bearer') {
125
- const payload = await ctx.call('auth.jwt.verifyServerSignedToken', { token });
126
- if (payload) {
127
- ctx.meta.tokenPayload = payload;
128
- ctx.meta.webId = payload.webId;
129
- return Promise.resolve(payload);
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
- // See https://moleculer.services/docs/0.13/moleculer-web.html#Authorization
148
- async authorize(ctx) {
149
- const { route, req, res } = ctx.params;
150
- // Extract token from authorization header (do not take the Bearer part)
151
- /** @type {[string, string]} */
152
- const [method, token] = req.headers.authorization && req.headers.authorization.split(' ');
153
-
154
- if (!token) {
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(this.settings.webIdSelection.filter(key => key in data).map(key => [key, data[key]]));
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
- module.exports = AuthMixin;
265
+ export default AuthMixin;