directus 9.0.0-rc.99 → 9.1.1
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/app.js +23 -16
- package/dist/auth/drivers/index.d.ts +1 -0
- package/dist/auth/drivers/index.js +1 -0
- package/dist/auth/drivers/ldap.d.ts +21 -0
- package/dist/auth/drivers/ldap.js +322 -0
- package/dist/auth/drivers/local.d.ts +1 -1
- package/dist/auth/drivers/local.js +3 -3
- package/dist/auth/drivers/oauth2.d.ts +1 -1
- package/dist/auth/drivers/oauth2.js +29 -22
- package/dist/auth/drivers/openid.d.ts +1 -1
- package/dist/auth/drivers/openid.js +34 -12
- package/dist/auth.js +2 -0
- package/dist/cache.d.ts +1 -1
- package/dist/cache.js +7 -7
- package/dist/cli/commands/init/index.js +1 -1
- package/dist/cli/index.js +3 -3
- package/dist/cli/index.test.js +10 -5
- package/dist/constants.js +1 -1
- package/dist/controllers/auth.js +3 -0
- package/dist/controllers/extensions.js +2 -0
- package/dist/controllers/files.js +19 -8
- package/dist/controllers/not-found.js +8 -2
- package/dist/controllers/notifications.d.ts +2 -0
- package/dist/controllers/notifications.js +147 -0
- package/dist/controllers/utils.js +11 -1
- package/dist/database/helpers/date.d.ts +8 -0
- package/dist/database/helpers/date.js +44 -0
- package/dist/database/helpers/geometry.d.ts +4 -2
- package/dist/database/helpers/geometry.js +48 -27
- package/dist/database/index.d.ts +1 -1
- package/dist/database/index.js +15 -21
- package/dist/database/migrations/20211016A-add-webhook-headers.d.ts +3 -0
- package/dist/database/migrations/20211016A-add-webhook-headers.js +15 -0
- package/dist/database/migrations/20211103A-set-unique-to-user-token.d.ts +3 -0
- package/dist/database/migrations/20211103A-set-unique-to-user-token.js +15 -0
- package/dist/database/migrations/20211103B-update-special-geometry.d.ts +3 -0
- package/dist/database/migrations/20211103B-update-special-geometry.js +25 -0
- package/dist/database/migrations/20211104A-remove-collections-listing.d.ts +3 -0
- package/dist/database/migrations/20211104A-remove-collections-listing.js +15 -0
- package/dist/database/migrations/20211118A-add-notifications.d.ts +3 -0
- package/dist/database/migrations/20211118A-add-notifications.js +28 -0
- package/dist/database/migrations/run.d.ts +1 -1
- package/dist/database/migrations/run.js +10 -4
- package/dist/database/run-ast.js +5 -19
- package/dist/database/seeds/run.js +4 -2
- package/dist/database/system-data/app-access-permissions/app-access-permissions.yaml +14 -0
- package/dist/database/system-data/collections/collections.yaml +2 -0
- package/dist/database/system-data/fields/activity.yaml +0 -2
- package/dist/database/system-data/fields/files.yaml +0 -1
- package/dist/database/system-data/fields/notifications.yaml +12 -0
- package/dist/database/system-data/fields/roles.yaml +0 -53
- package/dist/database/system-data/fields/settings.yaml +65 -69
- package/dist/database/system-data/fields/users.yaml +5 -1
- package/dist/database/system-data/fields/webhooks.yaml +25 -12
- package/dist/database/system-data/relations/relations.yaml +6 -0
- package/dist/emitter.d.ts +18 -8
- package/dist/emitter.js +68 -23
- package/dist/env.js +1 -0
- package/dist/exceptions/database/translate.js +26 -5
- package/dist/extensions.js +50 -27
- package/dist/index.d.ts +3 -0
- package/dist/index.js +20 -0
- package/dist/mailer.js +12 -3
- package/dist/middleware/authenticate.js +48 -48
- package/dist/middleware/error-handler.js +10 -3
- package/dist/middleware/get-permissions.d.ts +3 -0
- package/dist/middleware/get-permissions.js +15 -0
- package/dist/server.js +17 -7
- package/dist/services/activity.d.ts +7 -5
- package/dist/services/activity.js +82 -3
- package/dist/services/authentication.js +35 -49
- package/dist/services/authorization.d.ts +1 -1
- package/dist/services/authorization.js +13 -13
- package/dist/services/collections.d.ts +1 -1
- package/dist/services/collections.js +22 -23
- package/dist/services/fields.d.ts +1 -1
- package/dist/services/fields.js +29 -37
- package/dist/services/files.js +10 -11
- package/dist/services/graphql.js +11 -8
- package/dist/services/import.js +4 -4
- package/dist/services/index.d.ts +1 -0
- package/dist/services/index.js +1 -0
- package/dist/services/items.js +43 -83
- package/dist/services/mail/index.js +2 -2
- package/dist/services/mail/templates/base.liquid +153 -85
- package/dist/services/mail/templates/password-reset.liquid +3 -2
- package/dist/services/mail/templates/user-invitation.liquid +4 -4
- package/dist/services/meta.js +7 -8
- package/dist/services/notifications.d.ts +12 -0
- package/dist/services/notifications.js +41 -0
- package/dist/services/payload.js +13 -9
- package/dist/services/permissions.d.ts +13 -1
- package/dist/services/permissions.js +56 -2
- package/dist/services/relations.d.ts +1 -1
- package/dist/services/relations.js +12 -18
- package/dist/services/specifications.js +21 -2
- package/dist/services/users.js +1 -0
- package/dist/services/utils.js +3 -3
- package/dist/services/webhooks.d.ts +2 -2
- package/dist/types/auth.d.ts +1 -1
- package/dist/types/collection.d.ts +1 -0
- package/dist/types/extensions.d.ts +18 -2
- package/dist/types/schema.d.ts +2 -2
- package/dist/types/webhooks.d.ts +7 -2
- package/dist/utils/apply-query.d.ts +3 -3
- package/dist/utils/apply-query.js +52 -14
- package/dist/utils/apply-snapshot.js +2 -2
- package/dist/utils/get-ast-from-query.js +6 -6
- package/dist/utils/get-default-index-name.js +5 -6
- package/dist/utils/get-default-value.js +1 -1
- package/dist/utils/get-local-type.d.ts +2 -7
- package/dist/utils/get-local-type.js +106 -112
- package/dist/utils/get-permissions.d.ts +3 -0
- package/dist/utils/get-permissions.js +106 -0
- package/dist/utils/get-schema.js +11 -51
- package/dist/utils/get-simple-hash.d.ts +5 -0
- package/dist/utils/get-simple-hash.js +15 -0
- package/dist/utils/md.d.ts +4 -0
- package/dist/utils/md.js +15 -0
- package/dist/utils/reduce-schema.d.ts +2 -2
- package/dist/utils/reduce-schema.js +7 -10
- package/dist/utils/sanitize-query.js +3 -3
- package/dist/utils/user-name.d.ts +2 -0
- package/dist/utils/user-name.js +16 -0
- package/dist/utils/validate-query.js +4 -0
- package/dist/webhooks.js +18 -9
- package/package.json +39 -28
- package/dist/utils/geometry.d.ts +0 -2
- package/dist/utils/geometry.js +0 -20
- 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"));
|
|
@@ -95,8 +97,8 @@ async function createApp() {
|
|
|
95
97
|
app.disable('x-powered-by');
|
|
96
98
|
app.set('trust proxy', true);
|
|
97
99
|
app.set('query parser', (str) => qs_1.default.parse(str, { depth: 10 }));
|
|
98
|
-
await
|
|
99
|
-
await
|
|
100
|
+
await emitter_1.default.emitInit('app.before', { app });
|
|
101
|
+
await emitter_1.default.emitInit('middlewares.before', { app });
|
|
100
102
|
app.use(logger_1.expressLogger);
|
|
101
103
|
app.use((req, res, next) => {
|
|
102
104
|
express_1.default.json({
|
|
@@ -126,16 +128,18 @@ async function createApp() {
|
|
|
126
128
|
}
|
|
127
129
|
});
|
|
128
130
|
if (env_1.default.SERVE_APP) {
|
|
129
|
-
const adminPath = require.resolve('@directus/app
|
|
131
|
+
const adminPath = require.resolve('@directus/app');
|
|
130
132
|
const adminUrl = new url_1.Url(env_1.default.PUBLIC_URL).addPath('admin');
|
|
131
133
|
// Set the App's base path according to the APIs public URL
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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);
|
|
135
141
|
app.use('/admin', express_1.default.static(path_1.default.join(adminPath, '..')));
|
|
136
|
-
app.use('/admin/*',
|
|
137
|
-
res.send(html);
|
|
138
|
-
});
|
|
142
|
+
app.use('/admin/*', noCacheIndexHtmlHandler);
|
|
139
143
|
}
|
|
140
144
|
// use the rate limiter - all routes for now
|
|
141
145
|
if (env_1.default.RATE_LIMITER_ENABLED === true) {
|
|
@@ -144,10 +148,11 @@ async function createApp() {
|
|
|
144
148
|
app.use(authenticate_1.default);
|
|
145
149
|
app.use(check_ip_1.checkIP);
|
|
146
150
|
app.use(sanitize_query_1.default);
|
|
147
|
-
await (0, emitter_1.emitAsyncSafe)('middlewares.init.after', { app });
|
|
148
|
-
await (0, emitter_1.emitAsyncSafe)('routes.init.before', { app });
|
|
149
151
|
app.use(cache_1.default);
|
|
150
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 });
|
|
151
156
|
app.use('/auth', auth_1.default);
|
|
152
157
|
app.use('/graphql', graphql_1.default);
|
|
153
158
|
app.use('/activity', activity_1.default);
|
|
@@ -159,6 +164,7 @@ async function createApp() {
|
|
|
159
164
|
app.use('/files', files_1.default);
|
|
160
165
|
app.use('/folders', folders_1.default);
|
|
161
166
|
app.use('/items', items_1.default);
|
|
167
|
+
app.use('/notifications', notifications_1.default);
|
|
162
168
|
app.use('/panels', panels_1.default);
|
|
163
169
|
app.use('/permissions', permissions_1.default);
|
|
164
170
|
app.use('/presets', presets_1.default);
|
|
@@ -170,16 +176,17 @@ async function createApp() {
|
|
|
170
176
|
app.use('/users', users_1.default);
|
|
171
177
|
app.use('/utils', utils_1.default);
|
|
172
178
|
app.use('/webhooks', webhooks_1.default);
|
|
173
|
-
|
|
179
|
+
// Register custom endpoints
|
|
180
|
+
await emitter_1.default.emitInit('routes.custom.before', { app });
|
|
174
181
|
app.use(extensionManager.getEndpointRouter());
|
|
175
|
-
await
|
|
182
|
+
await emitter_1.default.emitInit('routes.custom.after', { app });
|
|
176
183
|
app.use(not_found_1.default);
|
|
177
184
|
app.use(error_handler_1.default);
|
|
178
|
-
await
|
|
185
|
+
await emitter_1.default.emitInit('routes.after', { app });
|
|
179
186
|
// Register all webhooks
|
|
180
187
|
await (0, webhooks_2.register)();
|
|
181
188
|
(0, track_1.track)('serverStarted');
|
|
182
|
-
|
|
189
|
+
await emitter_1.default.emitInit('app.after', { app });
|
|
183
190
|
return app;
|
|
184
191
|
}
|
|
185
192
|
exports.default = createApp;
|
|
@@ -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,6 +1,6 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
1
2
|
import { AuthDriver } from '../auth';
|
|
2
3
|
import { User, SessionData } from '../../types';
|
|
3
|
-
import { Router } from 'express';
|
|
4
4
|
export declare class LocalAuthDriver extends AuthDriver {
|
|
5
5
|
getUserID(payload: Record<string, any>): Promise<string>;
|
|
6
6
|
verify(user: User, password?: string): Promise<void>;
|
|
@@ -4,15 +4,15 @@ 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
18
|
async getUserID(payload) {
|
|
@@ -13,7 +13,7 @@ export declare class OAuth2AuthDriver extends LocalAuthDriver {
|
|
|
13
13
|
generateAuthUrl(codeVerifier: string): string;
|
|
14
14
|
private fetchUserId;
|
|
15
15
|
getUserID(payload: Record<string, any>): Promise<string>;
|
|
16
|
-
login(user: User
|
|
16
|
+
login(user: User): Promise<SessionData>;
|
|
17
17
|
refresh(user: User, sessionData: SessionData): Promise<SessionData>;
|
|
18
18
|
}
|
|
19
19
|
export declare function createOAuth2AuthRouter(providerName: string): Router;
|
|
@@ -21,7 +21,7 @@ class OAuth2AuthDriver extends local_1.LocalAuthDriver {
|
|
|
21
21
|
constructor(options, config) {
|
|
22
22
|
super(options, config);
|
|
23
23
|
const { authorizeUrl, accessUrl, profileUrl, clientId, clientSecret, ...additionalConfig } = config;
|
|
24
|
-
if (!authorizeUrl || !accessUrl || !
|
|
24
|
+
if (!authorizeUrl || !accessUrl || !clientId || !clientSecret || !additionalConfig.provider) {
|
|
25
25
|
throw new exceptions_1.InvalidConfigException('Invalid provider config', { provider: additionalConfig.provider });
|
|
26
26
|
}
|
|
27
27
|
const redirectUrl = new url_1.Url(env_1.default.PUBLIC_URL).addPath('auth', 'login', additionalConfig.provider, 'callback');
|
|
@@ -47,10 +47,13 @@ class OAuth2AuthDriver extends local_1.LocalAuthDriver {
|
|
|
47
47
|
generateAuthUrl(codeVerifier) {
|
|
48
48
|
var _a;
|
|
49
49
|
try {
|
|
50
|
+
const codeChallenge = openid_client_1.generators.codeChallenge(codeVerifier);
|
|
50
51
|
return this.client.authorizationUrl({
|
|
51
52
|
scope: (_a = this.config.scope) !== null && _a !== void 0 ? _a : 'email',
|
|
52
|
-
code_challenge:
|
|
53
|
+
code_challenge: codeChallenge,
|
|
53
54
|
code_challenge_method: 'S256',
|
|
55
|
+
// Some providers require state even with PKCE
|
|
56
|
+
state: codeChallenge,
|
|
54
57
|
access_type: 'offline',
|
|
55
58
|
});
|
|
56
59
|
}
|
|
@@ -62,8 +65,7 @@ class OAuth2AuthDriver extends local_1.LocalAuthDriver {
|
|
|
62
65
|
const user = await this.knex
|
|
63
66
|
.select('id')
|
|
64
67
|
.from('directus_users')
|
|
65
|
-
.whereRaw('LOWER(??) = ?', ['
|
|
66
|
-
.orWhereRaw('LOWER(??) = ?', ['external_identifier', identifier.toLowerCase()])
|
|
68
|
+
.whereRaw('LOWER(??) = ?', ['external_identifier', identifier.toLowerCase()])
|
|
67
69
|
.first();
|
|
68
70
|
return user === null || user === void 0 ? void 0 : user.id;
|
|
69
71
|
}
|
|
@@ -75,13 +77,17 @@ class OAuth2AuthDriver extends local_1.LocalAuthDriver {
|
|
|
75
77
|
let tokenSet;
|
|
76
78
|
let userInfo;
|
|
77
79
|
try {
|
|
78
|
-
tokenSet = await this.client.
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
80
|
+
tokenSet = await this.client.oauthCallback(this.redirectUrl, { code: payload.code, state: payload.state }, { code_verifier: payload.codeVerifier, state: openid_client_1.generators.codeChallenge(payload.codeVerifier) });
|
|
81
|
+
const issuer = this.client.issuer;
|
|
82
|
+
if (issuer.metadata.userinfo_endpoint) {
|
|
83
|
+
userInfo = await this.client.userinfo(tokenSet.access_token);
|
|
84
|
+
}
|
|
85
|
+
else if (tokenSet.id_token) {
|
|
86
|
+
userInfo = tokenSet.claims();
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
throw new exceptions_1.InvalidConfigException('OAuth profile URL not defined', { provider: this.config.provider });
|
|
90
|
+
}
|
|
85
91
|
}
|
|
86
92
|
catch (e) {
|
|
87
93
|
throw handleError(e);
|
|
@@ -108,19 +114,17 @@ class OAuth2AuthDriver extends local_1.LocalAuthDriver {
|
|
|
108
114
|
if (!allowPublicRegistration) {
|
|
109
115
|
throw new exceptions_1.InvalidCredentialsException();
|
|
110
116
|
}
|
|
111
|
-
// If email matches identifier, don't set "external_identifier"
|
|
112
|
-
const emailIsIdentifier = (email === null || email === void 0 ? void 0 : email.toLowerCase()) === identifier.toLowerCase();
|
|
113
117
|
await this.usersService.createOne({
|
|
114
118
|
provider: this.config.provider,
|
|
115
119
|
email: email,
|
|
116
|
-
external_identifier:
|
|
120
|
+
external_identifier: identifier,
|
|
117
121
|
role: this.config.defaultRoleId,
|
|
118
122
|
auth_data: tokenSet.refresh_token && JSON.stringify({ refreshToken: tokenSet.refresh_token }),
|
|
119
123
|
});
|
|
120
124
|
return (await this.fetchUserId(identifier));
|
|
121
125
|
}
|
|
122
|
-
async login(user
|
|
123
|
-
return this.refresh(user,
|
|
126
|
+
async login(user) {
|
|
127
|
+
return this.refresh(user, null);
|
|
124
128
|
}
|
|
125
129
|
async refresh(user, sessionData) {
|
|
126
130
|
let authData = user.auth_data;
|
|
@@ -180,11 +184,14 @@ function createOAuth2AuthRouter(providerName) {
|
|
|
180
184
|
}, respond_1.respond);
|
|
181
185
|
router.get('/callback', (0, async_handler_1.default)(async (req, res, next) => {
|
|
182
186
|
var _a;
|
|
183
|
-
|
|
184
|
-
|
|
187
|
+
let tokenData;
|
|
188
|
+
try {
|
|
189
|
+
tokenData = jsonwebtoken_1.default.verify(req.cookies[`oauth2.${providerName}`], env_1.default.SECRET, { issuer: 'directus' });
|
|
190
|
+
}
|
|
191
|
+
catch (e) {
|
|
185
192
|
throw new exceptions_1.InvalidCredentialsException();
|
|
186
193
|
}
|
|
187
|
-
const { verifier, redirect } =
|
|
194
|
+
const { verifier, redirect } = tokenData;
|
|
188
195
|
const authenticationService = new services_1.AuthenticationService({
|
|
189
196
|
accountability: {
|
|
190
197
|
ip: req.ip,
|
|
@@ -196,13 +203,13 @@ function createOAuth2AuthRouter(providerName) {
|
|
|
196
203
|
let authResponse;
|
|
197
204
|
try {
|
|
198
205
|
res.clearCookie(`oauth2.${providerName}`);
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
logger_1.default.warn(`Couldn't extract oAuth2 code from query: ${JSON.stringify(req.query)}`);
|
|
206
|
+
if (!req.query.code || !req.query.state) {
|
|
207
|
+
logger_1.default.warn(`Couldn't extract OAuth2 code or state from query: ${JSON.stringify(req.query)}`);
|
|
202
208
|
}
|
|
203
209
|
authResponse = await authenticationService.login(providerName, {
|
|
204
210
|
code: req.query.code,
|
|
205
211
|
codeVerifier: verifier,
|
|
212
|
+
state: req.query.state,
|
|
206
213
|
});
|
|
207
214
|
}
|
|
208
215
|
catch (error) {
|
|
@@ -13,7 +13,7 @@ export declare class OpenIDAuthDriver extends LocalAuthDriver {
|
|
|
13
13
|
generateAuthUrl(codeVerifier: string): Promise<string>;
|
|
14
14
|
private fetchUserId;
|
|
15
15
|
getUserID(payload: Record<string, any>): Promise<string>;
|
|
16
|
-
login(user: User
|
|
16
|
+
login(user: User): Promise<SessionData>;
|
|
17
17
|
refresh(user: User, sessionData: SessionData): Promise<SessionData>;
|
|
18
18
|
}
|
|
19
19
|
export declare function createOpenIDAuthRouter(providerName: string): Router;
|