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
@@ -0,0 +1,247 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createOAuth2AuthRouter = exports.OAuth2AuthDriver = void 0;
7
+ const express_1 = require("express");
8
+ const openid_client_1 = require("openid-client");
9
+ const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
10
+ const ms_1 = __importDefault(require("ms"));
11
+ const local_1 = require("./local");
12
+ const auth_1 = require("../../auth");
13
+ const env_1 = __importDefault(require("../../env"));
14
+ const services_1 = require("../../services");
15
+ const exceptions_1 = require("../../exceptions");
16
+ const respond_1 = require("../../middleware/respond");
17
+ const async_handler_1 = __importDefault(require("../../utils/async-handler"));
18
+ const url_1 = require("../../utils/url");
19
+ const logger_1 = __importDefault(require("../../logger"));
20
+ class OAuth2AuthDriver extends local_1.LocalAuthDriver {
21
+ constructor(options, config) {
22
+ super(options, config);
23
+ const { authorizeUrl, accessUrl, profileUrl, clientId, clientSecret, ...additionalConfig } = config;
24
+ if (!authorizeUrl || !accessUrl || !clientId || !clientSecret || !additionalConfig.provider) {
25
+ throw new exceptions_1.InvalidConfigException('Invalid provider config', { provider: additionalConfig.provider });
26
+ }
27
+ const redirectUrl = new url_1.Url(env_1.default.PUBLIC_URL).addPath('auth', 'login', additionalConfig.provider, 'callback');
28
+ this.redirectUrl = redirectUrl.toString();
29
+ this.usersService = new services_1.UsersService({ knex: this.knex, schema: this.schema });
30
+ this.config = additionalConfig;
31
+ const issuer = new openid_client_1.Issuer({
32
+ authorization_endpoint: authorizeUrl,
33
+ token_endpoint: accessUrl,
34
+ userinfo_endpoint: profileUrl,
35
+ issuer: additionalConfig.provider,
36
+ });
37
+ this.client = new issuer.Client({
38
+ client_id: clientId,
39
+ client_secret: clientSecret,
40
+ redirect_uris: [this.redirectUrl],
41
+ response_types: ['code'],
42
+ });
43
+ }
44
+ generateCodeVerifier() {
45
+ return openid_client_1.generators.codeVerifier();
46
+ }
47
+ generateAuthUrl(codeVerifier) {
48
+ var _a;
49
+ try {
50
+ const codeChallenge = openid_client_1.generators.codeChallenge(codeVerifier);
51
+ return this.client.authorizationUrl({
52
+ scope: (_a = this.config.scope) !== null && _a !== void 0 ? _a : 'email',
53
+ code_challenge: codeChallenge,
54
+ code_challenge_method: 'S256',
55
+ // Some providers require state even with PKCE
56
+ state: codeChallenge,
57
+ access_type: 'offline',
58
+ });
59
+ }
60
+ catch (e) {
61
+ throw handleError(e);
62
+ }
63
+ }
64
+ async fetchUserId(identifier) {
65
+ const user = await this.knex
66
+ .select('id')
67
+ .from('directus_users')
68
+ .whereRaw('LOWER(??) = ?', ['external_identifier', identifier.toLowerCase()])
69
+ .first();
70
+ return user === null || user === void 0 ? void 0 : user.id;
71
+ }
72
+ async getUserID(payload) {
73
+ var _a;
74
+ if (!payload.code || !payload.codeVerifier) {
75
+ throw new exceptions_1.InvalidCredentialsException();
76
+ }
77
+ let tokenSet;
78
+ let userInfo;
79
+ try {
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
+ }
91
+ }
92
+ catch (e) {
93
+ throw handleError(e);
94
+ }
95
+ const { emailKey, identifierKey, allowPublicRegistration } = this.config;
96
+ const email = userInfo[emailKey !== null && emailKey !== void 0 ? emailKey : 'email'];
97
+ // Fallback to email if explicit identifier not found
98
+ const identifier = (_a = userInfo[identifierKey]) !== null && _a !== void 0 ? _a : email;
99
+ if (!identifier) {
100
+ logger_1.default.warn(`Failed to find user identifier for provider "${this.config.provider}"`);
101
+ throw new exceptions_1.InvalidCredentialsException();
102
+ }
103
+ const userId = await this.fetchUserId(identifier);
104
+ if (userId) {
105
+ // Update user refreshToken if provided
106
+ if (tokenSet.refresh_token) {
107
+ await this.usersService.updateOne(userId, {
108
+ auth_data: JSON.stringify({ refreshToken: tokenSet.refresh_token }),
109
+ });
110
+ }
111
+ return userId;
112
+ }
113
+ // Is public registration allowed?
114
+ if (!allowPublicRegistration) {
115
+ throw new exceptions_1.InvalidCredentialsException();
116
+ }
117
+ await this.usersService.createOne({
118
+ provider: this.config.provider,
119
+ email: email,
120
+ external_identifier: identifier,
121
+ role: this.config.defaultRoleId,
122
+ auth_data: tokenSet.refresh_token && JSON.stringify({ refreshToken: tokenSet.refresh_token }),
123
+ });
124
+ return (await this.fetchUserId(identifier));
125
+ }
126
+ async login(user) {
127
+ return this.refresh(user, null);
128
+ }
129
+ async refresh(user, sessionData) {
130
+ let authData = user.auth_data;
131
+ if (typeof authData === 'string') {
132
+ try {
133
+ authData = JSON.parse(authData);
134
+ }
135
+ catch {
136
+ logger_1.default.warn(`Session data isn't valid JSON: ${authData}`);
137
+ }
138
+ }
139
+ if (!(authData === null || authData === void 0 ? void 0 : authData.refreshToken)) {
140
+ return sessionData;
141
+ }
142
+ try {
143
+ const tokenSet = await this.client.refresh(authData.refreshToken);
144
+ return { accessToken: tokenSet.access_token };
145
+ }
146
+ catch (e) {
147
+ throw handleError(e);
148
+ }
149
+ }
150
+ }
151
+ exports.OAuth2AuthDriver = OAuth2AuthDriver;
152
+ const handleError = (e) => {
153
+ if (e instanceof openid_client_1.errors.OPError) {
154
+ if (e.error === 'invalid_grant') {
155
+ // Invalid token
156
+ return new exceptions_1.InvalidCredentialsException();
157
+ }
158
+ // Server response error
159
+ return new exceptions_1.ServiceUnavailableException('Service returned unexpected response', {
160
+ service: 'openid',
161
+ message: e.error_description,
162
+ });
163
+ }
164
+ else if (e instanceof openid_client_1.errors.RPError) {
165
+ // Internal client error
166
+ return new exceptions_1.InvalidCredentialsException();
167
+ }
168
+ return e;
169
+ };
170
+ function createOAuth2AuthRouter(providerName) {
171
+ const router = (0, express_1.Router)();
172
+ router.get('/', (req, res) => {
173
+ const provider = (0, auth_1.getAuthProvider)(providerName);
174
+ const codeVerifier = provider.generateCodeVerifier();
175
+ const token = jsonwebtoken_1.default.sign({ verifier: codeVerifier, redirect: req.query.redirect }, env_1.default.SECRET, {
176
+ expiresIn: '5m',
177
+ issuer: 'directus',
178
+ });
179
+ res.cookie(`oauth2.${providerName}`, token, {
180
+ httpOnly: true,
181
+ sameSite: 'lax',
182
+ });
183
+ return res.redirect(provider.generateAuthUrl(codeVerifier));
184
+ }, respond_1.respond);
185
+ router.get('/callback', (0, async_handler_1.default)(async (req, res, next) => {
186
+ var _a;
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) {
192
+ throw new exceptions_1.InvalidCredentialsException();
193
+ }
194
+ const { verifier, redirect } = tokenData;
195
+ const authenticationService = new services_1.AuthenticationService({
196
+ accountability: {
197
+ ip: req.ip,
198
+ userAgent: req.get('user-agent'),
199
+ role: null,
200
+ },
201
+ schema: req.schema,
202
+ });
203
+ let authResponse;
204
+ try {
205
+ res.clearCookie(`oauth2.${providerName}`);
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)}`);
208
+ }
209
+ authResponse = await authenticationService.login(providerName, {
210
+ code: req.query.code,
211
+ codeVerifier: verifier,
212
+ state: req.query.state,
213
+ });
214
+ }
215
+ catch (error) {
216
+ logger_1.default.warn(error);
217
+ if (redirect) {
218
+ let reason = 'UNKNOWN_EXCEPTION';
219
+ if (error instanceof exceptions_1.ServiceUnavailableException) {
220
+ reason = 'SERVICE_UNAVAILABLE';
221
+ }
222
+ else if (error instanceof exceptions_1.InvalidCredentialsException) {
223
+ reason = 'INVALID_USER';
224
+ }
225
+ return res.redirect(`${redirect.split('?')[0]}?reason=${reason}`);
226
+ }
227
+ throw error;
228
+ }
229
+ const { accessToken, refreshToken, expires } = authResponse;
230
+ if (redirect) {
231
+ res.cookie(env_1.default.REFRESH_TOKEN_COOKIE_NAME, refreshToken, {
232
+ httpOnly: true,
233
+ domain: env_1.default.REFRESH_TOKEN_COOKIE_DOMAIN,
234
+ maxAge: (0, ms_1.default)(env_1.default.REFRESH_TOKEN_TTL),
235
+ secure: (_a = env_1.default.REFRESH_TOKEN_COOKIE_SECURE) !== null && _a !== void 0 ? _a : false,
236
+ sameSite: env_1.default.REFRESH_TOKEN_COOKIE_SAME_SITE || 'strict',
237
+ });
238
+ return res.redirect(redirect);
239
+ }
240
+ res.locals.payload = {
241
+ data: { access_token: accessToken, refresh_token: refreshToken, expires },
242
+ };
243
+ next();
244
+ }), respond_1.respond);
245
+ return router;
246
+ }
247
+ exports.createOAuth2AuthRouter = createOAuth2AuthRouter;
@@ -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 OpenIDAuthDriver extends LocalAuthDriver {
7
+ client: Promise<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): Promise<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 createOpenIDAuthRouter(providerName: string): Router;
@@ -0,0 +1,256 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createOpenIDAuthRouter = exports.OpenIDAuthDriver = void 0;
7
+ const express_1 = require("express");
8
+ const openid_client_1 = require("openid-client");
9
+ const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
10
+ const ms_1 = __importDefault(require("ms"));
11
+ const local_1 = require("./local");
12
+ const auth_1 = require("../../auth");
13
+ const env_1 = __importDefault(require("../../env"));
14
+ const services_1 = require("../../services");
15
+ const exceptions_1 = require("../../exceptions");
16
+ const respond_1 = require("../../middleware/respond");
17
+ const async_handler_1 = __importDefault(require("../../utils/async-handler"));
18
+ const url_1 = require("../../utils/url");
19
+ const logger_1 = __importDefault(require("../../logger"));
20
+ class OpenIDAuthDriver extends local_1.LocalAuthDriver {
21
+ constructor(options, config) {
22
+ super(options, config);
23
+ const { issuerUrl, clientId, clientSecret, ...additionalConfig } = config;
24
+ if (!issuerUrl || !clientId || !clientSecret || !additionalConfig.provider) {
25
+ throw new exceptions_1.InvalidConfigException('Invalid provider config', { provider: additionalConfig.provider });
26
+ }
27
+ const redirectUrl = new url_1.Url(env_1.default.PUBLIC_URL).addPath('auth', 'login', additionalConfig.provider, 'callback');
28
+ this.redirectUrl = redirectUrl.toString();
29
+ this.usersService = new services_1.UsersService({ knex: this.knex, schema: this.schema });
30
+ this.config = additionalConfig;
31
+ this.client = new Promise((resolve, reject) => {
32
+ openid_client_1.Issuer.discover(issuerUrl)
33
+ .then((issuer) => {
34
+ const supportedTypes = issuer.metadata.response_types_supported;
35
+ if (!(supportedTypes === null || supportedTypes === void 0 ? void 0 : supportedTypes.includes('code'))) {
36
+ reject(new exceptions_1.InvalidConfigException('OpenID provider does not support required code flow', {
37
+ provider: additionalConfig.provider,
38
+ }));
39
+ }
40
+ resolve(new issuer.Client({
41
+ client_id: clientId,
42
+ client_secret: clientSecret,
43
+ redirect_uris: [this.redirectUrl],
44
+ response_types: ['code'],
45
+ }));
46
+ })
47
+ .catch(reject);
48
+ });
49
+ }
50
+ generateCodeVerifier() {
51
+ return openid_client_1.generators.codeVerifier();
52
+ }
53
+ async generateAuthUrl(codeVerifier) {
54
+ var _a;
55
+ try {
56
+ const client = await this.client;
57
+ const codeChallenge = openid_client_1.generators.codeChallenge(codeVerifier);
58
+ return client.authorizationUrl({
59
+ scope: (_a = this.config.scope) !== null && _a !== void 0 ? _a : 'openid profile email',
60
+ code_challenge: codeChallenge,
61
+ code_challenge_method: 'S256',
62
+ // Some providers require state even with PKCE
63
+ state: codeChallenge,
64
+ access_type: 'offline',
65
+ });
66
+ }
67
+ catch (e) {
68
+ throw handleError(e);
69
+ }
70
+ }
71
+ async fetchUserId(identifier) {
72
+ const user = await this.knex
73
+ .select('id')
74
+ .from('directus_users')
75
+ .whereRaw('LOWER(??) = ?', ['external_identifier', identifier.toLowerCase()])
76
+ .first();
77
+ return user === null || user === void 0 ? void 0 : user.id;
78
+ }
79
+ async getUserID(payload) {
80
+ var _a;
81
+ if (!payload.code || !payload.codeVerifier) {
82
+ throw new exceptions_1.InvalidCredentialsException();
83
+ }
84
+ let tokenSet;
85
+ let userInfo;
86
+ try {
87
+ const client = await this.client;
88
+ tokenSet = await client.callback(this.redirectUrl, { code: payload.code, state: payload.state }, { code_verifier: payload.codeVerifier, state: openid_client_1.generators.codeChallenge(payload.codeVerifier) });
89
+ const issuer = client.issuer;
90
+ if (issuer.metadata.userinfo_endpoint) {
91
+ userInfo = await client.userinfo(tokenSet.access_token);
92
+ }
93
+ else {
94
+ userInfo = tokenSet.claims();
95
+ }
96
+ }
97
+ catch (e) {
98
+ throw handleError(e);
99
+ }
100
+ const { identifierKey, allowPublicRegistration, requireVerifiedEmail } = this.config;
101
+ const email = userInfo.email;
102
+ // Fallback to email if explicit identifier not found
103
+ const identifier = (_a = userInfo[identifierKey !== null && identifierKey !== void 0 ? identifierKey : 'sub']) !== null && _a !== void 0 ? _a : email;
104
+ if (!identifier) {
105
+ logger_1.default.warn(`Failed to find user identifier for provider "${this.config.provider}"`);
106
+ throw new exceptions_1.InvalidCredentialsException();
107
+ }
108
+ const userId = await this.fetchUserId(identifier);
109
+ if (userId) {
110
+ // Update user refreshToken if provided
111
+ if (tokenSet.refresh_token) {
112
+ await this.usersService.updateOne(userId, {
113
+ auth_data: JSON.stringify({ refreshToken: tokenSet.refresh_token }),
114
+ });
115
+ }
116
+ return userId;
117
+ }
118
+ const isEmailVerified = !requireVerifiedEmail || userInfo.email_verified;
119
+ // Is public registration allowed?
120
+ if (!allowPublicRegistration || !isEmailVerified) {
121
+ throw new exceptions_1.InvalidCredentialsException();
122
+ }
123
+ await this.usersService.createOne({
124
+ provider: this.config.provider,
125
+ first_name: userInfo.given_name,
126
+ last_name: userInfo.family_name,
127
+ email: email,
128
+ external_identifier: identifier,
129
+ role: this.config.defaultRoleId,
130
+ auth_data: tokenSet.refresh_token && JSON.stringify({ refreshToken: tokenSet.refresh_token }),
131
+ });
132
+ return (await this.fetchUserId(identifier));
133
+ }
134
+ async login(user) {
135
+ return this.refresh(user, null);
136
+ }
137
+ async refresh(user, sessionData) {
138
+ let authData = user.auth_data;
139
+ if (typeof authData === 'string') {
140
+ try {
141
+ authData = JSON.parse(authData);
142
+ }
143
+ catch {
144
+ logger_1.default.warn(`Session data isn't valid JSON: ${authData}`);
145
+ }
146
+ }
147
+ if (!(authData === null || authData === void 0 ? void 0 : authData.refreshToken)) {
148
+ return sessionData;
149
+ }
150
+ try {
151
+ const client = await this.client;
152
+ const tokenSet = await client.refresh(authData.refreshToken);
153
+ return { accessToken: tokenSet.access_token };
154
+ }
155
+ catch (e) {
156
+ throw handleError(e);
157
+ }
158
+ }
159
+ }
160
+ exports.OpenIDAuthDriver = OpenIDAuthDriver;
161
+ const handleError = (e) => {
162
+ if (e instanceof openid_client_1.errors.OPError) {
163
+ if (e.error === 'invalid_grant') {
164
+ // Invalid token
165
+ return new exceptions_1.InvalidCredentialsException();
166
+ }
167
+ // Server response error
168
+ return new exceptions_1.ServiceUnavailableException('Service returned unexpected response', {
169
+ service: 'openid',
170
+ message: e.error_description,
171
+ });
172
+ }
173
+ else if (e instanceof openid_client_1.errors.RPError) {
174
+ // Internal client error
175
+ return new exceptions_1.InvalidCredentialsException();
176
+ }
177
+ return e;
178
+ };
179
+ function createOpenIDAuthRouter(providerName) {
180
+ const router = (0, express_1.Router)();
181
+ router.get('/', (0, async_handler_1.default)(async (req, res) => {
182
+ const provider = (0, auth_1.getAuthProvider)(providerName);
183
+ const codeVerifier = provider.generateCodeVerifier();
184
+ const token = jsonwebtoken_1.default.sign({ verifier: codeVerifier, redirect: req.query.redirect }, env_1.default.SECRET, {
185
+ expiresIn: '5m',
186
+ issuer: 'directus',
187
+ });
188
+ res.cookie(`openid.${providerName}`, token, {
189
+ httpOnly: true,
190
+ sameSite: 'lax',
191
+ });
192
+ return res.redirect(await provider.generateAuthUrl(codeVerifier));
193
+ }), respond_1.respond);
194
+ router.get('/callback', (0, async_handler_1.default)(async (req, res, next) => {
195
+ var _a;
196
+ let tokenData;
197
+ try {
198
+ tokenData = jsonwebtoken_1.default.verify(req.cookies[`openid.${providerName}`], env_1.default.SECRET, { issuer: 'directus' });
199
+ }
200
+ catch (e) {
201
+ throw new exceptions_1.InvalidCredentialsException();
202
+ }
203
+ const { verifier, redirect } = tokenData;
204
+ const authenticationService = new services_1.AuthenticationService({
205
+ accountability: {
206
+ ip: req.ip,
207
+ userAgent: req.get('user-agent'),
208
+ role: null,
209
+ },
210
+ schema: req.schema,
211
+ });
212
+ let authResponse;
213
+ try {
214
+ res.clearCookie(`openid.${providerName}`);
215
+ if (!req.query.code || !req.query.state) {
216
+ logger_1.default.warn(`Couldn't extract OpenID code or state from query: ${JSON.stringify(req.query)}`);
217
+ }
218
+ authResponse = await authenticationService.login(providerName, {
219
+ code: req.query.code,
220
+ codeVerifier: verifier,
221
+ state: req.query.state,
222
+ });
223
+ }
224
+ catch (error) {
225
+ logger_1.default.warn(error);
226
+ if (redirect) {
227
+ let reason = 'UNKNOWN_EXCEPTION';
228
+ if (error instanceof exceptions_1.ServiceUnavailableException) {
229
+ reason = 'SERVICE_UNAVAILABLE';
230
+ }
231
+ else if (error instanceof exceptions_1.InvalidCredentialsException) {
232
+ reason = 'INVALID_USER';
233
+ }
234
+ return res.redirect(`${redirect.split('?')[0]}?reason=${reason}`);
235
+ }
236
+ throw error;
237
+ }
238
+ const { accessToken, refreshToken, expires } = authResponse;
239
+ if (redirect) {
240
+ res.cookie(env_1.default.REFRESH_TOKEN_COOKIE_NAME, refreshToken, {
241
+ httpOnly: true,
242
+ domain: env_1.default.REFRESH_TOKEN_COOKIE_DOMAIN,
243
+ maxAge: (0, ms_1.default)(env_1.default.REFRESH_TOKEN_TTL),
244
+ secure: (_a = env_1.default.REFRESH_TOKEN_COOKIE_SECURE) !== null && _a !== void 0 ? _a : false,
245
+ sameSite: env_1.default.REFRESH_TOKEN_COOKIE_SAME_SITE || 'strict',
246
+ });
247
+ return res.redirect(redirect);
248
+ }
249
+ res.locals.payload = {
250
+ data: { access_token: accessToken, refresh_token: refreshToken, expires },
251
+ };
252
+ next();
253
+ }), respond_1.respond);
254
+ return router;
255
+ }
256
+ exports.createOpenIDAuthRouter = createOpenIDAuthRouter;
package/dist/auth.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  import { AuthDriver } from './auth/auth';
2
2
  export declare function getAuthProvider(provider: string): AuthDriver;
3
+ export declare function registerAuthProviders(): Promise<void>;
package/dist/auth.js CHANGED
@@ -3,37 +3,30 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.getAuthProvider = void 0;
6
+ exports.registerAuthProviders = exports.getAuthProvider = void 0;
7
7
  const database_1 = __importDefault(require("./database"));
8
8
  const env_1 = __importDefault(require("./env"));
9
9
  const logger_1 = __importDefault(require("./logger"));
10
- const drivers_1 = require("./auth/drivers/");
10
+ const drivers_1 = require("./auth/drivers");
11
11
  const constants_1 = require("./constants");
12
12
  const exceptions_1 = require("./exceptions");
13
13
  const get_config_from_env_1 = require("./utils/get-config-from-env");
14
+ const get_schema_1 = require("./utils/get-schema");
14
15
  const utils_1 = require("@directus/shared/utils");
15
16
  const providerNames = (0, utils_1.toArray)(env_1.default.AUTH_PROVIDERS);
16
17
  const providers = new Map();
17
18
  function getAuthProvider(provider) {
18
- // When providers haven't been registered yet
19
- if (providerNames.length !== providers.size) {
20
- registerProviders();
21
- }
22
19
  if (!providers.has(provider)) {
23
20
  throw new exceptions_1.InvalidConfigException('Auth provider not configured', { provider });
24
21
  }
25
22
  return providers.get(provider);
26
23
  }
27
24
  exports.getAuthProvider = getAuthProvider;
28
- function getProviderInstance(driver, config) {
29
- switch (driver) {
30
- case 'local':
31
- return new drivers_1.LocalAuthDriver((0, database_1.default)(), config);
32
- }
33
- }
34
- function registerProviders() {
25
+ async function registerAuthProviders() {
26
+ const options = { knex: (0, database_1.default)(), schema: await (0, get_schema_1.getSchema)() };
27
+ const defaultProvider = getProviderInstance('local', options);
35
28
  // Register default provider
36
- providers.set(constants_1.DEFAULT_AUTH_PROVIDER, getProviderInstance('local', {}));
29
+ providers.set(constants_1.DEFAULT_AUTH_PROVIDER, defaultProvider);
37
30
  if (!env_1.default.AUTH_PROVIDERS) {
38
31
  return;
39
32
  }
@@ -49,7 +42,7 @@ function registerProviders() {
49
42
  logger_1.default.warn(`Missing driver definition for "${name}" auth provider.`);
50
43
  return;
51
44
  }
52
- const provider = getProviderInstance(driver, { provider: name, ...config });
45
+ const provider = getProviderInstance(driver, options, { provider: name, ...config });
53
46
  if (!provider) {
54
47
  logger_1.default.warn(`Invalid "${driver}" auth driver.`);
55
48
  return;
@@ -57,3 +50,16 @@ function registerProviders() {
57
50
  providers.set(name, provider);
58
51
  });
59
52
  }
53
+ exports.registerAuthProviders = registerAuthProviders;
54
+ function getProviderInstance(driver, options, config = {}) {
55
+ switch (driver) {
56
+ case 'local':
57
+ return new drivers_1.LocalAuthDriver(options, config);
58
+ case 'oauth2':
59
+ return new drivers_1.OAuth2AuthDriver(options, config);
60
+ case 'openid':
61
+ return new drivers_1.OpenIDAuthDriver(options, config);
62
+ case 'ldap':
63
+ return new drivers_1.LDAPAuthDriver(options, config);
64
+ }
65
+ }
package/dist/cache.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import Keyv from 'keyv';
2
2
  export declare function getCache(): {
3
3
  cache: Keyv | null;
4
- schemaCache: Keyv | null;
4
+ systemCache: Keyv;
5
5
  };
6
6
  export declare function flushCaches(): Promise<void>;
package/dist/cache.js CHANGED
@@ -11,23 +11,23 @@ const logger_1 = __importDefault(require("./logger"));
11
11
  const get_config_from_env_1 = require("./utils/get-config-from-env");
12
12
  const validate_env_1 = require("./utils/validate-env");
13
13
  let cache = null;
14
- let schemaCache = null;
14
+ let systemCache = null;
15
15
  function getCache() {
16
16
  if (env_1.default.CACHE_ENABLED === true && cache === null) {
17
17
  (0, validate_env_1.validateEnv)(['CACHE_NAMESPACE', 'CACHE_TTL', 'CACHE_STORE']);
18
18
  cache = getKeyvInstance((0, ms_1.default)(env_1.default.CACHE_TTL));
19
19
  cache.on('error', (err) => logger_1.default.warn(err, `[cache] ${err}`));
20
20
  }
21
- if (env_1.default.CACHE_SCHEMA !== false && schemaCache === null) {
22
- schemaCache = getKeyvInstance(typeof env_1.default.CACHE_SCHEMA === 'string' ? (0, ms_1.default)(env_1.default.CACHE_SCHEMA) : undefined, '_schema');
23
- schemaCache.on('error', (err) => logger_1.default.warn(err, `[cache] ${err}`));
21
+ if (systemCache === null) {
22
+ systemCache = getKeyvInstance(undefined, '_system');
23
+ systemCache.on('error', (err) => logger_1.default.warn(err, `[cache] ${err}`));
24
24
  }
25
- return { cache, schemaCache };
25
+ return { cache, systemCache };
26
26
  }
27
27
  exports.getCache = getCache;
28
28
  async function flushCaches() {
29
- const { schemaCache, cache } = getCache();
30
- await (schemaCache === null || schemaCache === void 0 ? void 0 : schemaCache.clear());
29
+ const { systemCache, cache } = getCache();
30
+ await (systemCache === null || systemCache === void 0 ? void 0 : systemCache.clear());
31
31
  await (cache === null || cache === void 0 ? void 0 : cache.clear());
32
32
  }
33
33
  exports.flushCaches = flushCaches;
@@ -36,7 +36,7 @@ async function init() {
36
36
  const db = (0, create_db_connection_1.default)(dbClient, credentials);
37
37
  try {
38
38
  await (0, run_2.default)(db);
39
- await (0, run_1.default)(db, 'latest');
39
+ await (0, run_1.default)(db, 'latest', false);
40
40
  }
41
41
  catch (err) {
42
42
  process.stdout.write('\nSomething went wrong while seeding the database:\n');
package/dist/cli/index.js CHANGED
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.createCli = void 0;
7
7
  const commander_1 = require("commander");
8
8
  const server_1 = require("../server");
9
- const emitter_1 = require("../emitter");
9
+ const emitter_1 = __importDefault(require("../emitter"));
10
10
  const extensions_1 = require("../extensions");
11
11
  const bootstrap_1 = __importDefault(require("./commands/bootstrap"));
12
12
  const count_1 = __importDefault(require("./commands/count"));
@@ -23,7 +23,7 @@ async function createCli() {
23
23
  const program = new commander_1.Command();
24
24
  const extensionManager = (0, extensions_1.getExtensionManager)();
25
25
  await extensionManager.initialize({ schedule: false });
26
- await (0, emitter_1.emitAsyncSafe)('cli.init.before', { program });
26
+ await emitter_1.default.emitInit('cli.before', { program });
27
27
  program.name('directus').usage('[command] [options]');
28
28
  program.version(pkg.version, '-v, --version');
29
29
  program.command('start').description('Start the Directus API').action(server_1.startServer);
@@ -83,7 +83,7 @@ async function createCli() {
83
83
  .option('-y, --yes', `Assume "yes" as answer to all prompts and run non-interactively`)
84
84
  .argument('<path>', 'Path to snapshot file')
85
85
  .action(apply_1.apply);
86
- await (0, emitter_1.emitAsyncSafe)('cli.init.after', { program });
86
+ await emitter_1.default.emitInit('cli.after', { program });
87
87
  return program;
88
88
  }
89
89
  exports.createCli = createCli;