directus 9.0.0-rc.98 → 9.1.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 (147) hide show
  1. package/dist/app.js +25 -19
  2. package/dist/auth/auth.d.ts +4 -3
  3. package/dist/auth/auth.js +5 -3
  4. package/dist/auth/drivers/index.d.ts +3 -0
  5. package/dist/auth/drivers/index.js +3 -0
  6. package/dist/auth/drivers/ldap.d.ts +21 -0
  7. package/dist/auth/drivers/ldap.js +322 -0
  8. package/dist/auth/drivers/local.d.ts +3 -9
  9. package/dist/auth/drivers/local.js +4 -12
  10. package/dist/auth/drivers/oauth2.d.ts +19 -0
  11. package/dist/auth/drivers/oauth2.js +247 -0
  12. package/dist/auth/drivers/openid.d.ts +19 -0
  13. package/dist/auth/drivers/openid.js +256 -0
  14. package/dist/auth.d.ts +1 -0
  15. package/dist/auth.js +21 -15
  16. package/dist/cache.d.ts +1 -1
  17. package/dist/cache.js +7 -7
  18. package/dist/cli/commands/init/index.js +1 -1
  19. package/dist/cli/index.js +3 -3
  20. package/dist/cli/index.test.js +10 -5
  21. package/dist/cli/utils/create-env/env-stub.liquid +2 -2
  22. package/dist/constants.js +1 -1
  23. package/dist/controllers/auth.js +10 -140
  24. package/dist/controllers/extensions.js +2 -0
  25. package/dist/controllers/files.js +19 -8
  26. package/dist/controllers/not-found.js +8 -2
  27. package/dist/controllers/notifications.d.ts +2 -0
  28. package/dist/controllers/notifications.js +147 -0
  29. package/dist/controllers/utils.js +11 -1
  30. package/dist/database/helpers/date.d.ts +8 -0
  31. package/dist/database/helpers/date.js +44 -0
  32. package/dist/database/helpers/geometry.d.ts +4 -2
  33. package/dist/database/helpers/geometry.js +48 -27
  34. package/dist/database/index.d.ts +1 -1
  35. package/dist/database/index.js +15 -21
  36. package/dist/database/migrations/20211009A-add-auth-data.d.ts +3 -0
  37. package/dist/database/migrations/20211009A-add-auth-data.js +15 -0
  38. package/dist/database/migrations/20211016A-add-webhook-headers.d.ts +3 -0
  39. package/dist/database/migrations/20211016A-add-webhook-headers.js +15 -0
  40. package/dist/database/migrations/20211103A-set-unique-to-user-token.d.ts +3 -0
  41. package/dist/database/migrations/20211103A-set-unique-to-user-token.js +15 -0
  42. package/dist/database/migrations/20211103B-update-special-geometry.d.ts +3 -0
  43. package/dist/database/migrations/20211103B-update-special-geometry.js +25 -0
  44. package/dist/database/migrations/20211104A-remove-collections-listing.d.ts +3 -0
  45. package/dist/database/migrations/20211104A-remove-collections-listing.js +15 -0
  46. package/dist/database/migrations/20211118A-add-notifications.d.ts +3 -0
  47. package/dist/database/migrations/20211118A-add-notifications.js +28 -0
  48. package/dist/database/migrations/run.d.ts +1 -1
  49. package/dist/database/migrations/run.js +10 -4
  50. package/dist/database/run-ast.js +39 -55
  51. package/dist/database/seeds/run.js +4 -2
  52. package/dist/database/system-data/app-access-permissions/app-access-permissions.yaml +14 -0
  53. package/dist/database/system-data/collections/collections.yaml +2 -0
  54. package/dist/database/system-data/fields/activity.yaml +0 -2
  55. package/dist/database/system-data/fields/files.yaml +0 -1
  56. package/dist/database/system-data/fields/notifications.yaml +12 -0
  57. package/dist/database/system-data/fields/roles.yaml +0 -53
  58. package/dist/database/system-data/fields/settings.yaml +65 -69
  59. package/dist/database/system-data/fields/users.yaml +8 -3
  60. package/dist/database/system-data/fields/webhooks.yaml +32 -13
  61. package/dist/database/system-data/relations/relations.yaml +6 -0
  62. package/dist/emitter.d.ts +18 -8
  63. package/dist/emitter.js +68 -23
  64. package/dist/env.js +2 -2
  65. package/dist/exceptions/database/translate.js +26 -5
  66. package/dist/extensions.js +50 -27
  67. package/dist/index.d.ts +3 -0
  68. package/dist/index.js +20 -0
  69. package/dist/mailer.js +12 -3
  70. package/dist/middleware/authenticate.js +48 -48
  71. package/dist/middleware/error-handler.js +10 -3
  72. package/dist/middleware/get-permissions.d.ts +3 -0
  73. package/dist/middleware/get-permissions.js +15 -0
  74. package/dist/server.js +17 -7
  75. package/dist/services/activity.d.ts +7 -5
  76. package/dist/services/activity.js +82 -3
  77. package/dist/services/authentication.js +67 -58
  78. package/dist/services/authorization.d.ts +1 -1
  79. package/dist/services/authorization.js +13 -13
  80. package/dist/services/collections.d.ts +1 -1
  81. package/dist/services/collections.js +25 -23
  82. package/dist/services/fields.d.ts +1 -1
  83. package/dist/services/fields.js +29 -37
  84. package/dist/services/files.js +10 -11
  85. package/dist/services/graphql.js +13 -12
  86. package/dist/services/import.js +4 -4
  87. package/dist/services/index.d.ts +1 -0
  88. package/dist/services/index.js +1 -0
  89. package/dist/services/items.js +43 -83
  90. package/dist/services/mail/index.js +2 -2
  91. package/dist/services/mail/templates/base.liquid +153 -85
  92. package/dist/services/mail/templates/password-reset.liquid +3 -2
  93. package/dist/services/mail/templates/user-invitation.liquid +4 -4
  94. package/dist/services/meta.js +7 -8
  95. package/dist/services/notifications.d.ts +12 -0
  96. package/dist/services/notifications.js +41 -0
  97. package/dist/services/payload.d.ts +1 -0
  98. package/dist/services/payload.js +26 -11
  99. package/dist/services/permissions.d.ts +13 -1
  100. package/dist/services/permissions.js +56 -2
  101. package/dist/services/relations.d.ts +1 -1
  102. package/dist/services/relations.js +12 -18
  103. package/dist/services/specifications.js +21 -2
  104. package/dist/services/users.js +24 -23
  105. package/dist/services/utils.js +3 -3
  106. package/dist/services/webhooks.d.ts +2 -2
  107. package/dist/types/auth.d.ts +10 -2
  108. package/dist/types/collection.d.ts +1 -0
  109. package/dist/types/extensions.d.ts +18 -2
  110. package/dist/types/schema.d.ts +2 -2
  111. package/dist/types/webhooks.d.ts +7 -2
  112. package/dist/utils/apply-query.d.ts +3 -3
  113. package/dist/utils/apply-query.js +54 -16
  114. package/dist/utils/apply-snapshot.js +2 -2
  115. package/dist/utils/get-ast-from-query.js +6 -6
  116. package/dist/utils/get-auth-providers.d.ts +1 -0
  117. package/dist/utils/get-auth-providers.js +1 -0
  118. package/dist/utils/get-default-index-name.js +5 -6
  119. package/dist/utils/get-default-value.js +1 -1
  120. package/dist/utils/get-local-type.d.ts +2 -7
  121. package/dist/utils/get-local-type.js +106 -112
  122. package/dist/utils/get-permissions.d.ts +3 -0
  123. package/dist/utils/get-permissions.js +106 -0
  124. package/dist/utils/get-schema.js +11 -51
  125. package/dist/utils/get-simple-hash.d.ts +5 -0
  126. package/dist/utils/get-simple-hash.js +15 -0
  127. package/dist/utils/md.d.ts +4 -0
  128. package/dist/utils/md.js +15 -0
  129. package/dist/utils/reduce-schema.d.ts +2 -2
  130. package/dist/utils/reduce-schema.js +7 -10
  131. package/dist/utils/sanitize-query.js +3 -3
  132. package/dist/utils/track.js +2 -2
  133. package/dist/utils/user-name.d.ts +2 -0
  134. package/dist/utils/user-name.js +16 -0
  135. package/dist/utils/validate-query.js +4 -0
  136. package/dist/webhooks.js +18 -9
  137. package/example.env +15 -9
  138. package/package.json +41 -31
  139. package/dist/grant.d.ts +0 -5
  140. package/dist/grant.js +0 -24
  141. package/dist/middleware/session.d.ts +0 -3
  142. package/dist/middleware/session.js +0 -29
  143. package/dist/utils/geometry.d.ts +0 -2
  144. package/dist/utils/geometry.js +0 -20
  145. package/dist/utils/get-email-from-profile.d.ts +0 -9
  146. package/dist/utils/get-email-from-profile.js +0 -34
  147. package/index.js +0 -5
package/dist/app.js CHANGED
@@ -40,6 +40,7 @@ const graphql_1 = __importDefault(require("./controllers/graphql"));
40
40
  const items_1 = __importDefault(require("./controllers/items"));
41
41
  const not_found_1 = __importDefault(require("./controllers/not-found"));
42
42
  const panels_1 = __importDefault(require("./controllers/panels"));
43
+ const notifications_1 = __importDefault(require("./controllers/notifications"));
43
44
  const permissions_1 = __importDefault(require("./controllers/permissions"));
44
45
  const presets_1 = __importDefault(require("./controllers/presets"));
45
46
  const relations_1 = __importDefault(require("./controllers/relations"));
@@ -51,12 +52,13 @@ const users_1 = __importDefault(require("./controllers/users"));
51
52
  const utils_1 = __importDefault(require("./controllers/utils"));
52
53
  const webhooks_1 = __importDefault(require("./controllers/webhooks"));
53
54
  const database_1 = require("./database");
54
- const emitter_1 = require("./emitter");
55
+ const emitter_1 = __importDefault(require("./emitter"));
55
56
  const env_1 = __importDefault(require("./env"));
56
57
  const exceptions_1 = require("./exceptions");
57
58
  const extensions_2 = require("./extensions");
58
59
  const logger_1 = __importStar(require("./logger"));
59
60
  const authenticate_1 = __importDefault(require("./middleware/authenticate"));
61
+ const get_permissions_1 = __importDefault(require("./middleware/get-permissions"));
60
62
  const cache_1 = __importDefault(require("./middleware/cache"));
61
63
  const check_ip_1 = require("./middleware/check-ip");
62
64
  const cors_1 = __importDefault(require("./middleware/cors"));
@@ -69,8 +71,8 @@ const track_1 = require("./utils/track");
69
71
  const validate_env_1 = require("./utils/validate-env");
70
72
  const validate_storage_1 = require("./utils/validate-storage");
71
73
  const webhooks_2 = require("./webhooks");
72
- const session_1 = require("./middleware/session");
73
74
  const cache_2 = require("./cache");
75
+ const auth_2 = require("./auth");
74
76
  const url_1 = require("./utils/url");
75
77
  async function createApp() {
76
78
  (0, validate_env_1.validateEnv)(['KEY', 'SECRET']);
@@ -88,14 +90,15 @@ async function createApp() {
88
90
  logger_1.default.warn(`Database migrations have not all been run`);
89
91
  }
90
92
  await (0, cache_2.flushCaches)();
93
+ await (0, auth_2.registerAuthProviders)();
91
94
  const extensionManager = (0, extensions_2.getExtensionManager)();
92
95
  await extensionManager.initialize();
93
96
  const app = (0, express_1.default)();
94
97
  app.disable('x-powered-by');
95
98
  app.set('trust proxy', true);
96
99
  app.set('query parser', (str) => qs_1.default.parse(str, { depth: 10 }));
97
- await (0, emitter_1.emitAsyncSafe)('init.before', { app });
98
- await (0, emitter_1.emitAsyncSafe)('middlewares.init.before', { app });
100
+ await emitter_1.default.emitInit('app.before', { app });
101
+ await emitter_1.default.emitInit('middlewares.before', { app });
99
102
  app.use(logger_1.expressLogger);
100
103
  app.use((req, res, next) => {
101
104
  express_1.default.json({
@@ -125,30 +128,31 @@ async function createApp() {
125
128
  }
126
129
  });
127
130
  if (env_1.default.SERVE_APP) {
128
- const adminPath = require.resolve('@directus/app/dist/index.html');
131
+ const adminPath = require.resolve('@directus/app');
129
132
  const adminUrl = new url_1.Url(env_1.default.PUBLIC_URL).addPath('admin');
130
133
  // Set the App's base path according to the APIs public URL
131
- let html = fs_extra_1.default.readFileSync(adminPath, 'utf-8');
132
- html = html.replace(/<meta charset="utf-8" \/>/, `<meta charset="utf-8" />\n\t\t<base href="${adminUrl.toString({ rootRelative: true })}/">`);
133
- app.get('/admin', (req, res) => res.send(html));
134
+ const html = await fs_extra_1.default.readFile(adminPath, 'utf8');
135
+ const htmlWithBase = html.replace(/<base \/>/, `<base href="${adminUrl.toString({ rootRelative: true })}/" />`);
136
+ const noCacheIndexHtmlHandler = (req, res) => {
137
+ res.setHeader('Cache-Control', 'no-cache');
138
+ res.send(htmlWithBase);
139
+ };
140
+ app.get('/admin', noCacheIndexHtmlHandler);
134
141
  app.use('/admin', express_1.default.static(path_1.default.join(adminPath, '..')));
135
- app.use('/admin/*', (req, res) => {
136
- res.send(html);
137
- });
142
+ app.use('/admin/*', noCacheIndexHtmlHandler);
138
143
  }
139
144
  // use the rate limiter - all routes for now
140
145
  if (env_1.default.RATE_LIMITER_ENABLED === true) {
141
146
  app.use(rate_limiter_1.default);
142
147
  }
143
- // We only rely on cookie-sessions in the oAuth flow where it's required
144
- app.use(session_1.session);
145
148
  app.use(authenticate_1.default);
146
149
  app.use(check_ip_1.checkIP);
147
150
  app.use(sanitize_query_1.default);
148
- await (0, emitter_1.emitAsyncSafe)('middlewares.init.after', { app });
149
- await (0, emitter_1.emitAsyncSafe)('routes.init.before', { app });
150
151
  app.use(cache_1.default);
151
152
  app.use(schema_1.default);
153
+ app.use(get_permissions_1.default);
154
+ await emitter_1.default.emitInit('middlewares.after', { app });
155
+ await emitter_1.default.emitInit('routes.before', { app });
152
156
  app.use('/auth', auth_1.default);
153
157
  app.use('/graphql', graphql_1.default);
154
158
  app.use('/activity', activity_1.default);
@@ -160,6 +164,7 @@ async function createApp() {
160
164
  app.use('/files', files_1.default);
161
165
  app.use('/folders', folders_1.default);
162
166
  app.use('/items', items_1.default);
167
+ app.use('/notifications', notifications_1.default);
163
168
  app.use('/panels', panels_1.default);
164
169
  app.use('/permissions', permissions_1.default);
165
170
  app.use('/presets', presets_1.default);
@@ -171,16 +176,17 @@ async function createApp() {
171
176
  app.use('/users', users_1.default);
172
177
  app.use('/utils', utils_1.default);
173
178
  app.use('/webhooks', webhooks_1.default);
174
- await (0, emitter_1.emitAsyncSafe)('routes.custom.init.before', { app });
179
+ // Register custom endpoints
180
+ await emitter_1.default.emitInit('routes.custom.before', { app });
175
181
  app.use(extensionManager.getEndpointRouter());
176
- await (0, emitter_1.emitAsyncSafe)('routes.custom.init.after', { app });
182
+ await emitter_1.default.emitInit('routes.custom.after', { app });
177
183
  app.use(not_found_1.default);
178
184
  app.use(error_handler_1.default);
179
- await (0, emitter_1.emitAsyncSafe)('routes.init.after', { app });
185
+ await emitter_1.default.emitInit('routes.after', { app });
180
186
  // Register all webhooks
181
187
  await (0, webhooks_2.register)();
182
188
  (0, track_1.track)('serverStarted');
183
- (0, emitter_1.emitAsyncSafe)('init');
189
+ await emitter_1.default.emitInit('app.after', { app });
184
190
  return app;
185
191
  }
186
192
  exports.default = createApp;
@@ -1,8 +1,9 @@
1
1
  import { Knex } from 'knex';
2
- import { User, SessionData } from '../types';
2
+ import { AuthDriverOptions, SchemaOverview, User, SessionData } from '../types';
3
3
  export declare abstract class AuthDriver {
4
4
  knex: Knex;
5
- constructor(knex: Knex, _config: Record<string, any>);
5
+ schema: SchemaOverview;
6
+ constructor(options: AuthDriverOptions, _config: Record<string, any>);
6
7
  /**
7
8
  * Get user id for a given provider payload
8
9
  *
@@ -35,7 +36,7 @@ export declare abstract class AuthDriver {
35
36
  * @param _sessionData Session data
36
37
  * @throws InvalidCredentialsException
37
38
  */
38
- refresh(_user: User, _sessionData: SessionData): Promise<void>;
39
+ refresh(_user: User, sessionData: SessionData): Promise<SessionData>;
39
40
  /**
40
41
  * Handle user session termination
41
42
  *
package/dist/auth/auth.js CHANGED
@@ -2,8 +2,9 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.AuthDriver = void 0;
4
4
  class AuthDriver {
5
- constructor(knex, _config) {
6
- this.knex = knex;
5
+ constructor(options, _config) {
6
+ this.knex = options.knex;
7
+ this.schema = options.schema;
7
8
  }
8
9
  /**
9
10
  * Check with the (external) provider if the user is allowed entry to Directus
@@ -24,8 +25,9 @@ class AuthDriver {
24
25
  * @param _sessionData Session data
25
26
  * @throws InvalidCredentialsException
26
27
  */
27
- async refresh(_user, _sessionData) {
28
+ async refresh(_user, sessionData) {
28
29
  /* Optional */
30
+ return sessionData;
29
31
  }
30
32
  /**
31
33
  * Handle user session termination
@@ -1 +1,4 @@
1
1
  export * from './local';
2
+ export * from './oauth2';
3
+ export * from './openid';
4
+ export * from './ldap';
@@ -11,3 +11,6 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
11
11
  };
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
13
  __exportStar(require("./local"), exports);
14
+ __exportStar(require("./oauth2"), exports);
15
+ __exportStar(require("./openid"), exports);
16
+ __exportStar(require("./ldap"), exports);
@@ -0,0 +1,21 @@
1
+ import { Router } from 'express';
2
+ import { Client } from 'ldapjs';
3
+ import { AuthDriver } from '../auth';
4
+ import { AuthDriverOptions, User, SessionData } from '../../types';
5
+ import { UsersService } from '../../services';
6
+ export declare class LDAPAuthDriver extends AuthDriver {
7
+ bindClient: Client;
8
+ usersService: UsersService;
9
+ config: Record<string, any>;
10
+ constructor(options: AuthDriverOptions, config: Record<string, any>);
11
+ private validateBindClient;
12
+ private fetchUserDn;
13
+ private fetchUserInfo;
14
+ private fetchUserGroups;
15
+ private fetchUserId;
16
+ getUserID(payload: Record<string, any>): Promise<string>;
17
+ verify(user: User, password?: string): Promise<void>;
18
+ login(user: User, payload: Record<string, any>): Promise<SessionData>;
19
+ refresh(user: User): Promise<SessionData>;
20
+ }
21
+ export declare function createLDAPAuthRouter(provider: string): Router;
@@ -0,0 +1,322 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5
+ }) : (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ o[k2] = m[k];
8
+ }));
9
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
11
+ }) : function(o, v) {
12
+ o["default"] = v;
13
+ });
14
+ var __importStar = (this && this.__importStar) || function (mod) {
15
+ if (mod && mod.__esModule) return mod;
16
+ var result = {};
17
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18
+ __setModuleDefault(result, mod);
19
+ return result;
20
+ };
21
+ var __importDefault = (this && this.__importDefault) || function (mod) {
22
+ return (mod && mod.__esModule) ? mod : { "default": mod };
23
+ };
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ exports.createLDAPAuthRouter = exports.LDAPAuthDriver = void 0;
26
+ const express_1 = require("express");
27
+ const ldapjs_1 = __importStar(require("ldapjs"));
28
+ const ms_1 = __importDefault(require("ms"));
29
+ const joi_1 = __importDefault(require("joi"));
30
+ const auth_1 = require("../auth");
31
+ const exceptions_1 = require("../../exceptions");
32
+ const services_1 = require("../../services");
33
+ const async_handler_1 = __importDefault(require("../../utils/async-handler"));
34
+ const env_1 = __importDefault(require("../../env"));
35
+ const respond_1 = require("../../middleware/respond");
36
+ const logger_1 = __importDefault(require("../../logger"));
37
+ // 0x2: ACCOUNTDISABLE
38
+ // 0x10: LOCKOUT
39
+ // 0x800000: PASSWORD_EXPIRED
40
+ const INVALID_ACCOUNT_FLAGS = 0x800012;
41
+ class LDAPAuthDriver extends auth_1.AuthDriver {
42
+ constructor(options, config) {
43
+ var _a;
44
+ super(options, config);
45
+ const { bindDn, bindPassword, userDn, provider, clientUrl } = config;
46
+ if (!bindDn || !bindPassword || !userDn || !provider || (!clientUrl && !((_a = config.client) === null || _a === void 0 ? void 0 : _a.socketPath))) {
47
+ throw new exceptions_1.InvalidConfigException('Invalid provider config', { provider });
48
+ }
49
+ const clientConfig = typeof config.client === 'object' ? config.client : {};
50
+ this.bindClient = ldapjs_1.default.createClient({ url: clientUrl, reconnect: true, ...clientConfig });
51
+ this.bindClient.on('error', (err) => {
52
+ logger_1.default.warn(err);
53
+ });
54
+ this.usersService = new services_1.UsersService({ knex: this.knex, schema: this.schema });
55
+ this.config = config;
56
+ }
57
+ async validateBindClient() {
58
+ const { bindDn, bindPassword, provider } = this.config;
59
+ return new Promise((resolve, reject) => {
60
+ // Healthcheck bind user
61
+ this.bindClient.search(bindDn, {}, (err, res) => {
62
+ if (err) {
63
+ reject(handleError(err));
64
+ return;
65
+ }
66
+ res.on('searchEntry', () => {
67
+ resolve();
68
+ });
69
+ res.on('error', (err) => {
70
+ if (!(err instanceof ldapjs_1.OperationsError)) {
71
+ reject(handleError(err));
72
+ return;
73
+ }
74
+ // Rebind on OperationsError
75
+ this.bindClient.bind(bindDn, bindPassword, (err) => {
76
+ if (err) {
77
+ const error = handleError(err);
78
+ if (error instanceof exceptions_1.InvalidCredentialsException) {
79
+ reject(new exceptions_1.InvalidConfigException('Invalid bind user', { provider }));
80
+ }
81
+ else {
82
+ reject(error);
83
+ }
84
+ }
85
+ else {
86
+ resolve();
87
+ }
88
+ });
89
+ });
90
+ });
91
+ });
92
+ }
93
+ async fetchUserDn(identifier) {
94
+ const { userDn, userAttribute, userScope } = this.config;
95
+ return new Promise((resolve, reject) => {
96
+ // Search for the user in LDAP by attribute
97
+ this.bindClient.search(userDn, { filter: `(${userAttribute !== null && userAttribute !== void 0 ? userAttribute : 'cn'}=${identifier})`, scope: userScope !== null && userScope !== void 0 ? userScope : 'one' }, (err, res) => {
98
+ if (err) {
99
+ reject(handleError(err));
100
+ return;
101
+ }
102
+ res.on('searchEntry', ({ object }) => {
103
+ resolve(object.dn.toLowerCase());
104
+ });
105
+ res.on('error', (err) => {
106
+ reject(handleError(err));
107
+ });
108
+ res.on('end', () => {
109
+ resolve(undefined);
110
+ });
111
+ });
112
+ });
113
+ }
114
+ async fetchUserInfo(userDn) {
115
+ const { mailAttribute } = this.config;
116
+ return new Promise((resolve, reject) => {
117
+ // Fetch user info in LDAP by domain component
118
+ this.bindClient.search(userDn, { attributes: ['givenName', 'sn', mailAttribute !== null && mailAttribute !== void 0 ? mailAttribute : 'mail', 'userAccountControl'] }, (err, res) => {
119
+ if (err) {
120
+ reject(handleError(err));
121
+ return;
122
+ }
123
+ res.on('searchEntry', ({ object }) => {
124
+ const email = object[mailAttribute !== null && mailAttribute !== void 0 ? mailAttribute : 'mail'];
125
+ const user = {
126
+ firstName: typeof object.givenName === 'object' ? object.givenName[0] : object.givenName,
127
+ lastName: typeof object.sn === 'object' ? object.sn[0] : object.sn,
128
+ email: typeof email === 'object' ? email[0] : email,
129
+ userAccountControl: typeof object.userAccountControl === 'object'
130
+ ? Number(object.userAccountControl[0])
131
+ : Number(object.userAccountControl),
132
+ };
133
+ resolve(user);
134
+ });
135
+ res.on('error', (err) => {
136
+ reject(handleError(err));
137
+ });
138
+ res.on('end', () => {
139
+ resolve(undefined);
140
+ });
141
+ });
142
+ });
143
+ }
144
+ async fetchUserGroups(userDn) {
145
+ const { groupDn, groupAttribute, groupScope } = this.config;
146
+ if (!groupDn) {
147
+ return Promise.resolve([]);
148
+ }
149
+ return new Promise((resolve, reject) => {
150
+ let userGroups = [];
151
+ // Search for the user info in LDAP by group attribute
152
+ this.bindClient.search(groupDn, {
153
+ attributes: ['cn'],
154
+ filter: `(${groupAttribute !== null && groupAttribute !== void 0 ? groupAttribute : 'member'}=${userDn})`,
155
+ scope: groupScope !== null && groupScope !== void 0 ? groupScope : 'one',
156
+ }, (err, res) => {
157
+ if (err) {
158
+ reject(handleError(err));
159
+ return;
160
+ }
161
+ res.on('searchEntry', ({ object }) => {
162
+ if (typeof object.cn === 'object') {
163
+ userGroups = [...userGroups, ...object.cn];
164
+ }
165
+ else if (object.cn) {
166
+ userGroups.push(object.cn);
167
+ }
168
+ });
169
+ res.on('error', (err) => {
170
+ reject(handleError(err));
171
+ });
172
+ res.on('end', () => {
173
+ resolve(userGroups);
174
+ });
175
+ });
176
+ });
177
+ }
178
+ async fetchUserId(userDn) {
179
+ const user = await this.knex
180
+ .select('id')
181
+ .from('directus_users')
182
+ .orWhereRaw('LOWER(??) = ?', ['external_identifier', userDn.toLowerCase()])
183
+ .first();
184
+ return user === null || user === void 0 ? void 0 : user.id;
185
+ }
186
+ async getUserID(payload) {
187
+ var _a;
188
+ if (!payload.identifier) {
189
+ throw new exceptions_1.InvalidCredentialsException();
190
+ }
191
+ await this.validateBindClient();
192
+ const userDn = await this.fetchUserDn(payload.identifier);
193
+ if (!userDn) {
194
+ throw new exceptions_1.InvalidCredentialsException();
195
+ }
196
+ const userId = await this.fetchUserId(userDn);
197
+ const userGroups = await this.fetchUserGroups(userDn);
198
+ let userRole;
199
+ if (userGroups.length) {
200
+ userRole = await this.knex
201
+ .select('id')
202
+ .from('directus_roles')
203
+ .whereRaw(`LOWER(??) IN (${userGroups.map(() => '?')})`, [
204
+ 'name',
205
+ ...userGroups.map((group) => group.toLowerCase()),
206
+ ])
207
+ .first();
208
+ }
209
+ if (userId) {
210
+ await this.usersService.updateOne(userId, { role: (_a = userRole === null || userRole === void 0 ? void 0 : userRole.id) !== null && _a !== void 0 ? _a : null });
211
+ return userId;
212
+ }
213
+ const userInfo = await this.fetchUserInfo(userDn);
214
+ if (!userInfo) {
215
+ throw new exceptions_1.InvalidCredentialsException();
216
+ }
217
+ await this.usersService.createOne({
218
+ provider: this.config.provider,
219
+ first_name: userInfo.firstName,
220
+ last_name: userInfo.lastName,
221
+ email: userInfo.email,
222
+ external_identifier: userDn,
223
+ role: userRole === null || userRole === void 0 ? void 0 : userRole.id,
224
+ });
225
+ return (await this.fetchUserId(userDn));
226
+ }
227
+ async verify(user, password) {
228
+ if (!user.external_identifier || !password) {
229
+ throw new exceptions_1.InvalidCredentialsException();
230
+ }
231
+ return new Promise((resolve, reject) => {
232
+ const clientConfig = typeof this.config.client === 'object' ? this.config.client : {};
233
+ const client = ldapjs_1.default.createClient({
234
+ url: this.config.clientUrl,
235
+ ...clientConfig,
236
+ reconnect: false,
237
+ });
238
+ client.on('error', (err) => {
239
+ reject(handleError(err));
240
+ });
241
+ client.bind(user.external_identifier, password, (err) => {
242
+ if (err) {
243
+ reject(handleError(err));
244
+ }
245
+ else {
246
+ resolve();
247
+ }
248
+ client.destroy();
249
+ });
250
+ });
251
+ }
252
+ async login(user, payload) {
253
+ await this.verify(user, payload.password);
254
+ return null;
255
+ }
256
+ async refresh(user) {
257
+ await this.validateBindClient();
258
+ const userInfo = await this.fetchUserInfo(user.external_identifier);
259
+ if ((userInfo === null || userInfo === void 0 ? void 0 : userInfo.userAccountControl) && userInfo.userAccountControl & INVALID_ACCOUNT_FLAGS) {
260
+ throw new exceptions_1.InvalidCredentialsException();
261
+ }
262
+ return null;
263
+ }
264
+ }
265
+ exports.LDAPAuthDriver = LDAPAuthDriver;
266
+ const handleError = (e) => {
267
+ if (e instanceof ldapjs_1.InappropriateAuthenticationError ||
268
+ e instanceof ldapjs_1.InvalidCredentialsError ||
269
+ e instanceof ldapjs_1.InsufficientAccessRightsError) {
270
+ return new exceptions_1.InvalidCredentialsException();
271
+ }
272
+ return new exceptions_1.ServiceUnavailableException('Service returned unexpected error', {
273
+ service: 'ldap',
274
+ message: e.message,
275
+ });
276
+ };
277
+ function createLDAPAuthRouter(provider) {
278
+ const router = (0, express_1.Router)();
279
+ const loginSchema = joi_1.default.object({
280
+ identifier: joi_1.default.string().required(),
281
+ password: joi_1.default.string().required(),
282
+ mode: joi_1.default.string().valid('cookie', 'json'),
283
+ otp: joi_1.default.string(),
284
+ }).unknown();
285
+ router.post('/', (0, async_handler_1.default)(async (req, res, next) => {
286
+ var _a, _b;
287
+ const accountability = {
288
+ ip: req.ip,
289
+ userAgent: req.get('user-agent'),
290
+ role: null,
291
+ };
292
+ const authenticationService = new services_1.AuthenticationService({
293
+ accountability: accountability,
294
+ schema: req.schema,
295
+ });
296
+ const { error } = loginSchema.validate(req.body);
297
+ if (error) {
298
+ throw new exceptions_1.InvalidPayloadException(error.message);
299
+ }
300
+ const mode = req.body.mode || 'json';
301
+ const { accessToken, refreshToken, expires } = await authenticationService.login(provider, req.body, (_a = req.body) === null || _a === void 0 ? void 0 : _a.otp);
302
+ const payload = {
303
+ data: { access_token: accessToken, expires },
304
+ };
305
+ if (mode === 'json') {
306
+ payload.data.refresh_token = refreshToken;
307
+ }
308
+ if (mode === 'cookie') {
309
+ res.cookie(env_1.default.REFRESH_TOKEN_COOKIE_NAME, refreshToken, {
310
+ httpOnly: true,
311
+ domain: env_1.default.REFRESH_TOKEN_COOKIE_DOMAIN,
312
+ maxAge: (0, ms_1.default)(env_1.default.REFRESH_TOKEN_TTL),
313
+ secure: (_b = env_1.default.REFRESH_TOKEN_COOKIE_SECURE) !== null && _b !== void 0 ? _b : false,
314
+ sameSite: env_1.default.REFRESH_TOKEN_COOKIE_SAME_SITE || 'strict',
315
+ });
316
+ }
317
+ res.locals.payload = payload;
318
+ return next();
319
+ }), respond_1.respond);
320
+ return router;
321
+ }
322
+ exports.createLDAPAuthRouter = createLDAPAuthRouter;
@@ -1,15 +1,9 @@
1
- import { AuthDriver } from '../auth';
2
- import { User } from '../../types';
3
1
  import { Router } from 'express';
2
+ import { AuthDriver } from '../auth';
3
+ import { User, SessionData } from '../../types';
4
4
  export declare class LocalAuthDriver extends AuthDriver {
5
- /**
6
- * Get user id by email
7
- */
8
5
  getUserID(payload: Record<string, any>): Promise<string>;
9
- /**
10
- * Verify user password
11
- */
12
6
  verify(user: User, password?: string): Promise<void>;
13
- login(user: User, payload: Record<string, any>): Promise<null>;
7
+ login(user: User, payload: Record<string, any>): Promise<SessionData>;
14
8
  }
15
9
  export declare function createLocalAuthRouter(provider: string): Router;
@@ -4,20 +4,17 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.createLocalAuthRouter = exports.LocalAuthDriver = void 0;
7
+ const express_1 = require("express");
7
8
  const argon2_1 = __importDefault(require("argon2"));
9
+ const ms_1 = __importDefault(require("ms"));
10
+ const joi_1 = __importDefault(require("joi"));
8
11
  const auth_1 = require("../auth");
9
12
  const exceptions_1 = require("../../exceptions");
10
13
  const services_1 = require("../../services");
11
- const express_1 = require("express");
12
- const joi_1 = __importDefault(require("joi"));
13
14
  const async_handler_1 = __importDefault(require("../../utils/async-handler"));
14
15
  const env_1 = __importDefault(require("../../env"));
15
- const ms_1 = __importDefault(require("ms"));
16
16
  const respond_1 = require("../../middleware/respond");
17
17
  class LocalAuthDriver extends auth_1.AuthDriver {
18
- /**
19
- * Get user id by email
20
- */
21
18
  async getUserID(payload) {
22
19
  if (!payload.email) {
23
20
  throw new exceptions_1.InvalidCredentialsException();
@@ -32,18 +29,13 @@ class LocalAuthDriver extends auth_1.AuthDriver {
32
29
  }
33
30
  return user.id;
34
31
  }
35
- /**
36
- * Verify user password
37
- */
38
32
  async verify(user, password) {
39
33
  if (!user.password || !(await argon2_1.default.verify(user.password, password))) {
40
34
  throw new exceptions_1.InvalidCredentialsException();
41
35
  }
42
36
  }
43
37
  async login(user, payload) {
44
- if (payload.password) {
45
- await this.verify(user, payload.password);
46
- }
38
+ await this.verify(user, payload.password);
47
39
  return null;
48
40
  }
49
41
  }
@@ -0,0 +1,19 @@
1
+ import { Router } from 'express';
2
+ import { Client } from 'openid-client';
3
+ import { LocalAuthDriver } from './local';
4
+ import { UsersService } from '../../services';
5
+ import { AuthDriverOptions, User, SessionData } from '../../types';
6
+ export declare class OAuth2AuthDriver extends LocalAuthDriver {
7
+ client: Client;
8
+ redirectUrl: string;
9
+ usersService: UsersService;
10
+ config: Record<string, any>;
11
+ constructor(options: AuthDriverOptions, config: Record<string, any>);
12
+ generateCodeVerifier(): string;
13
+ generateAuthUrl(codeVerifier: string): string;
14
+ private fetchUserId;
15
+ getUserID(payload: Record<string, any>): Promise<string>;
16
+ login(user: User): Promise<SessionData>;
17
+ refresh(user: User, sessionData: SessionData): Promise<SessionData>;
18
+ }
19
+ export declare function createOAuth2AuthRouter(providerName: string): Router;