directus 9.19.2 → 9.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/drivers/index.d.ts +1 -0
- package/dist/auth/drivers/index.js +1 -0
- package/dist/auth/drivers/oauth2.js +30 -7
- package/dist/auth/drivers/openid.js +30 -7
- package/dist/auth/drivers/saml.d.ts +14 -0
- package/dist/auth/drivers/saml.js +166 -0
- package/dist/auth.js +2 -0
- package/dist/cli/index.test.js +6 -1
- package/dist/controllers/auth.js +3 -0
- package/dist/controllers/files.js +0 -3
- package/dist/database/system-data/fields/settings.yaml +0 -23
- package/dist/env.js +2 -0
- package/dist/services/authorization.js +3 -1
- package/dist/services/fields.js +9 -1
- package/dist/services/files.d.ts +5 -0
- package/dist/services/files.js +11 -0
- package/dist/services/files.test.js +36 -0
- package/dist/services/graphql/index.js +3 -0
- package/dist/services/graphql/types/bigint.d.ts +2 -0
- package/dist/services/graphql/types/bigint.js +35 -0
- package/dist/utils/get-graphql-type.js +2 -1
- package/package.json +12 -11
|
@@ -1,4 +1,27 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
2
25
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
26
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
27
|
};
|
|
@@ -6,7 +29,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
29
|
exports.createOAuth2AuthRouter = exports.OAuth2AuthDriver = void 0;
|
|
7
30
|
const exceptions_1 = require("@directus/shared/exceptions");
|
|
8
31
|
const utils_1 = require("@directus/shared/utils");
|
|
9
|
-
const express_1 = require("express");
|
|
32
|
+
const express_1 = __importStar(require("express"));
|
|
10
33
|
const flat_1 = __importDefault(require("flat"));
|
|
11
34
|
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
|
|
12
35
|
const ms_1 = __importDefault(require("ms"));
|
|
@@ -81,8 +104,8 @@ class OAuth2AuthDriver extends local_1.LocalAuthDriver {
|
|
|
81
104
|
return user === null || user === void 0 ? void 0 : user.id;
|
|
82
105
|
}
|
|
83
106
|
async getUserID(payload) {
|
|
84
|
-
if (!payload.code || !payload.codeVerifier) {
|
|
85
|
-
logger_1.default.
|
|
107
|
+
if (!payload.code || !payload.codeVerifier || !payload.state) {
|
|
108
|
+
logger_1.default.warn('[OAuth2] No code, codeVerifier or state in payload');
|
|
86
109
|
throw new exceptions_2.InvalidCredentialsException();
|
|
87
110
|
}
|
|
88
111
|
let tokenSet;
|
|
@@ -116,7 +139,7 @@ class OAuth2AuthDriver extends local_1.LocalAuthDriver {
|
|
|
116
139
|
}
|
|
117
140
|
// Is public registration allowed?
|
|
118
141
|
if (!allowPublicRegistration) {
|
|
119
|
-
logger_1.default.
|
|
142
|
+
logger_1.default.warn(`[OAuth2] User doesn't exist, and public registration not allowed for provider "${provider}"`);
|
|
120
143
|
throw new exceptions_2.InvalidCredentialsException();
|
|
121
144
|
}
|
|
122
145
|
try {
|
|
@@ -207,6 +230,9 @@ function createOAuth2AuthRouter(providerName) {
|
|
|
207
230
|
});
|
|
208
231
|
return res.redirect(provider.generateAuthUrl(codeVerifier, prompt));
|
|
209
232
|
}, respond_1.respond);
|
|
233
|
+
router.post('/callback', express_1.default.urlencoded({ extended: false }), (req, res) => {
|
|
234
|
+
res.redirect(303, `./callback?${new URLSearchParams(req.body)}`);
|
|
235
|
+
}, respond_1.respond);
|
|
210
236
|
router.get('/callback', (0, async_handler_1.default)(async (req, res, next) => {
|
|
211
237
|
var _a;
|
|
212
238
|
let tokenData;
|
|
@@ -230,9 +256,6 @@ function createOAuth2AuthRouter(providerName) {
|
|
|
230
256
|
let authResponse;
|
|
231
257
|
try {
|
|
232
258
|
res.clearCookie(`oauth2.${providerName}`);
|
|
233
|
-
if (!req.query.code || !req.query.state) {
|
|
234
|
-
logger_1.default.warn(`[OAuth2] Couldn't extract OAuth2 code or state from query: ${JSON.stringify(req.query)}`);
|
|
235
|
-
}
|
|
236
259
|
authResponse = await authenticationService.login(providerName, {
|
|
237
260
|
code: req.query.code,
|
|
238
261
|
codeVerifier: verifier,
|
|
@@ -1,4 +1,27 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
2
25
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
26
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
27
|
};
|
|
@@ -6,7 +29,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
29
|
exports.createOpenIDAuthRouter = exports.OpenIDAuthDriver = void 0;
|
|
7
30
|
const exceptions_1 = require("@directus/shared/exceptions");
|
|
8
31
|
const utils_1 = require("@directus/shared/utils");
|
|
9
|
-
const express_1 = require("express");
|
|
32
|
+
const express_1 = __importStar(require("express"));
|
|
10
33
|
const flat_1 = __importDefault(require("flat"));
|
|
11
34
|
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
|
|
12
35
|
const ms_1 = __importDefault(require("ms"));
|
|
@@ -91,8 +114,8 @@ class OpenIDAuthDriver extends local_1.LocalAuthDriver {
|
|
|
91
114
|
return user === null || user === void 0 ? void 0 : user.id;
|
|
92
115
|
}
|
|
93
116
|
async getUserID(payload) {
|
|
94
|
-
if (!payload.code || !payload.codeVerifier) {
|
|
95
|
-
logger_1.default.
|
|
117
|
+
if (!payload.code || !payload.codeVerifier || !payload.state) {
|
|
118
|
+
logger_1.default.warn('[OpenID] No code, codeVerifier or state in payload');
|
|
96
119
|
throw new exceptions_2.InvalidCredentialsException();
|
|
97
120
|
}
|
|
98
121
|
let tokenSet;
|
|
@@ -134,7 +157,7 @@ class OpenIDAuthDriver extends local_1.LocalAuthDriver {
|
|
|
134
157
|
const isEmailVerified = !requireVerifiedEmail || userInfo.email_verified;
|
|
135
158
|
// Is public registration allowed?
|
|
136
159
|
if (!allowPublicRegistration || !isEmailVerified) {
|
|
137
|
-
logger_1.default.
|
|
160
|
+
logger_1.default.warn(`[OpenID] User doesn't exist, and public registration not allowed for provider "${provider}"`);
|
|
138
161
|
throw new exceptions_2.InvalidCredentialsException();
|
|
139
162
|
}
|
|
140
163
|
try {
|
|
@@ -226,6 +249,9 @@ function createOpenIDAuthRouter(providerName) {
|
|
|
226
249
|
});
|
|
227
250
|
return res.redirect(await provider.generateAuthUrl(codeVerifier, prompt));
|
|
228
251
|
}), respond_1.respond);
|
|
252
|
+
router.post('/callback', express_1.default.urlencoded({ extended: false }), (req, res) => {
|
|
253
|
+
res.redirect(303, `./callback?${new URLSearchParams(req.body)}`);
|
|
254
|
+
}, respond_1.respond);
|
|
229
255
|
router.get('/callback', (0, async_handler_1.default)(async (req, res, next) => {
|
|
230
256
|
var _a;
|
|
231
257
|
let tokenData;
|
|
@@ -249,9 +275,6 @@ function createOpenIDAuthRouter(providerName) {
|
|
|
249
275
|
let authResponse;
|
|
250
276
|
try {
|
|
251
277
|
res.clearCookie(`openid.${providerName}`);
|
|
252
|
-
if (!req.query.code || !req.query.state) {
|
|
253
|
-
logger_1.default.warn(`[OpenID] Couldn't extract OpenID code or state from query: ${JSON.stringify(req.query)}`);
|
|
254
|
-
}
|
|
255
278
|
authResponse = await authenticationService.login(providerName, {
|
|
256
279
|
code: req.query.code,
|
|
257
280
|
codeVerifier: verifier,
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { UsersService } from '../../services';
|
|
2
|
+
import { AuthDriverOptions, User } from '../../types';
|
|
3
|
+
import { LocalAuthDriver } from './local';
|
|
4
|
+
export declare class SAMLAuthDriver extends LocalAuthDriver {
|
|
5
|
+
idp: any;
|
|
6
|
+
sp: any;
|
|
7
|
+
usersService: UsersService;
|
|
8
|
+
config: Record<string, any>;
|
|
9
|
+
constructor(options: AuthDriverOptions, config: Record<string, any>);
|
|
10
|
+
fetchUserID(identifier: string): Promise<any>;
|
|
11
|
+
getUserID(payload: Record<string, any>): Promise<any>;
|
|
12
|
+
login(_user: User): Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
export declare function createSAMLAuthRouter(providerName: string): import("express-serve-static-core").Router;
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.createSAMLAuthRouter = exports.SAMLAuthDriver = void 0;
|
|
30
|
+
const validator = __importStar(require("@authenio/samlify-node-xmllint"));
|
|
31
|
+
const exceptions_1 = require("@directus/shared/exceptions");
|
|
32
|
+
const express_1 = __importStar(require("express"));
|
|
33
|
+
const samlify = __importStar(require("samlify"));
|
|
34
|
+
const auth_1 = require("../../auth");
|
|
35
|
+
const constants_1 = require("../../constants");
|
|
36
|
+
const env_1 = __importDefault(require("../../env"));
|
|
37
|
+
const exceptions_2 = require("../../exceptions");
|
|
38
|
+
const record_not_unique_1 = require("../../exceptions/database/record-not-unique");
|
|
39
|
+
const logger_1 = __importDefault(require("../../logger"));
|
|
40
|
+
const respond_1 = require("../../middleware/respond");
|
|
41
|
+
const services_1 = require("../../services");
|
|
42
|
+
const async_handler_1 = __importDefault(require("../../utils/async-handler"));
|
|
43
|
+
const get_config_from_env_1 = require("../../utils/get-config-from-env");
|
|
44
|
+
const local_1 = require("./local");
|
|
45
|
+
// tell samlify to use validator...
|
|
46
|
+
samlify.setSchemaValidator(validator);
|
|
47
|
+
class SAMLAuthDriver extends local_1.LocalAuthDriver {
|
|
48
|
+
constructor(options, config) {
|
|
49
|
+
super(options, config);
|
|
50
|
+
this.config = config;
|
|
51
|
+
this.usersService = new services_1.UsersService({ knex: this.knex, schema: this.schema });
|
|
52
|
+
this.sp = samlify.ServiceProvider((0, get_config_from_env_1.getConfigFromEnv)(`AUTH_${config.provider.toUpperCase()}_SP`));
|
|
53
|
+
this.idp = samlify.IdentityProvider((0, get_config_from_env_1.getConfigFromEnv)(`AUTH_${config.provider.toUpperCase()}_IDP`));
|
|
54
|
+
}
|
|
55
|
+
async fetchUserID(identifier) {
|
|
56
|
+
const user = await this.knex
|
|
57
|
+
.select('id')
|
|
58
|
+
.from('directus_users')
|
|
59
|
+
.whereRaw('LOWER(??) = ?', ['external_identifier', identifier.toLowerCase()])
|
|
60
|
+
.first();
|
|
61
|
+
return user === null || user === void 0 ? void 0 : user.id;
|
|
62
|
+
}
|
|
63
|
+
async getUserID(payload) {
|
|
64
|
+
const { provider, emailKey, identifierKey, givenNameKey, familyNameKey, allowPublicRegistration } = this.config;
|
|
65
|
+
const email = payload[emailKey !== null && emailKey !== void 0 ? emailKey : 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress'];
|
|
66
|
+
const identifier = payload[identifierKey !== null && identifierKey !== void 0 ? identifierKey : 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier'];
|
|
67
|
+
const userID = await this.fetchUserID(identifier);
|
|
68
|
+
if (userID)
|
|
69
|
+
return userID;
|
|
70
|
+
if (!allowPublicRegistration) {
|
|
71
|
+
logger_1.default.trace(`[SAML] User doesn't exist, and public registration not allowed for provider "${provider}"`);
|
|
72
|
+
throw new exceptions_2.InvalidCredentialsException();
|
|
73
|
+
}
|
|
74
|
+
const firstName = payload[givenNameKey !== null && givenNameKey !== void 0 ? givenNameKey : 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname'];
|
|
75
|
+
const lastName = payload[familyNameKey !== null && familyNameKey !== void 0 ? familyNameKey : 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname'];
|
|
76
|
+
try {
|
|
77
|
+
return await this.usersService.createOne({
|
|
78
|
+
provider,
|
|
79
|
+
first_name: firstName,
|
|
80
|
+
last_name: lastName,
|
|
81
|
+
email: email,
|
|
82
|
+
external_identifier: identifier.toLowerCase(),
|
|
83
|
+
role: this.config.defaultRoleId,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
if (error instanceof record_not_unique_1.RecordNotUniqueException) {
|
|
88
|
+
logger_1.default.warn(error, '[SAML] Failed to register user. User not unique');
|
|
89
|
+
throw new exceptions_2.InvalidProviderException();
|
|
90
|
+
}
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// There's no local checks to be done when the user is authenticated in the IDP
|
|
95
|
+
async login(_user) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
exports.SAMLAuthDriver = SAMLAuthDriver;
|
|
100
|
+
function createSAMLAuthRouter(providerName) {
|
|
101
|
+
const router = (0, express_1.Router)();
|
|
102
|
+
router.get('/metadata', (0, async_handler_1.default)(async (_req, res) => {
|
|
103
|
+
const { sp } = (0, auth_1.getAuthProvider)(providerName);
|
|
104
|
+
return res.header('Content-Type', 'text/xml').send(sp.getMetadata());
|
|
105
|
+
}));
|
|
106
|
+
router.get('/', (0, async_handler_1.default)(async (req, res) => {
|
|
107
|
+
const { sp, idp } = (0, auth_1.getAuthProvider)(providerName);
|
|
108
|
+
const { context: url } = await sp.createLoginRequest(idp, 'redirect');
|
|
109
|
+
const parsedUrl = new URL(url);
|
|
110
|
+
if (req.query.redirect) {
|
|
111
|
+
parsedUrl.searchParams.append('RelayState', req.query.redirect);
|
|
112
|
+
}
|
|
113
|
+
return res.redirect(parsedUrl.toString());
|
|
114
|
+
}));
|
|
115
|
+
router.post('/logout', (0, async_handler_1.default)(async (req, res) => {
|
|
116
|
+
const { sp, idp } = (0, auth_1.getAuthProvider)(providerName);
|
|
117
|
+
const { context } = await sp.createLogoutRequest(idp, 'redirect', req.body);
|
|
118
|
+
const authService = new services_1.AuthenticationService({ accountability: req.accountability, schema: req.schema });
|
|
119
|
+
if (req.cookies[env_1.default.REFRESH_TOKEN_COOKIE_NAME]) {
|
|
120
|
+
const currentRefreshToken = req.cookies[env_1.default.REFRESH_TOKEN_COOKIE_NAME];
|
|
121
|
+
if (currentRefreshToken) {
|
|
122
|
+
await authService.logout(currentRefreshToken);
|
|
123
|
+
res.clearCookie(env_1.default.REFRESH_TOKEN_COOKIE_NAME, constants_1.COOKIE_OPTIONS);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return res.redirect(context);
|
|
127
|
+
}));
|
|
128
|
+
router.post('/acs', express_1.default.urlencoded({ extended: false }), (0, async_handler_1.default)(async (req, res, next) => {
|
|
129
|
+
var _a;
|
|
130
|
+
const relayState = (_a = req.body) === null || _a === void 0 ? void 0 : _a.RelayState;
|
|
131
|
+
try {
|
|
132
|
+
const { sp, idp } = (0, auth_1.getAuthProvider)(providerName);
|
|
133
|
+
const { extract } = await sp.parseLoginResponse(idp, 'post', req);
|
|
134
|
+
const authService = new services_1.AuthenticationService({ accountability: req.accountability, schema: req.schema });
|
|
135
|
+
const { accessToken, refreshToken, expires } = await authService.login(providerName, extract.attributes);
|
|
136
|
+
res.locals.payload = {
|
|
137
|
+
data: {
|
|
138
|
+
access_token: accessToken,
|
|
139
|
+
refresh_token: refreshToken,
|
|
140
|
+
expires,
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
if (relayState) {
|
|
144
|
+
res.cookie(env_1.default.REFRESH_TOKEN_COOKIE_NAME, refreshToken, constants_1.COOKIE_OPTIONS);
|
|
145
|
+
return res.redirect(relayState);
|
|
146
|
+
}
|
|
147
|
+
return next();
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
if (relayState) {
|
|
151
|
+
let reason = 'UNKNOWN_EXCEPTION';
|
|
152
|
+
if (error instanceof exceptions_1.BaseException) {
|
|
153
|
+
reason = error.code;
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
logger_1.default.warn(error, `[SAML] Unexpected error during SAML login`);
|
|
157
|
+
}
|
|
158
|
+
return res.redirect(`${relayState.split('?')[0]}?reason=${reason}`);
|
|
159
|
+
}
|
|
160
|
+
logger_1.default.warn(error, `[SAML] Unexpected error during SAML login`);
|
|
161
|
+
throw error;
|
|
162
|
+
}
|
|
163
|
+
}), respond_1.respond);
|
|
164
|
+
return router;
|
|
165
|
+
}
|
|
166
|
+
exports.createSAMLAuthRouter = createSAMLAuthRouter;
|
package/dist/auth.js
CHANGED
|
@@ -63,5 +63,7 @@ function getProviderInstance(driver, options, config = {}) {
|
|
|
63
63
|
return new drivers_1.OpenIDAuthDriver(options, config);
|
|
64
64
|
case 'ldap':
|
|
65
65
|
return new drivers_1.LDAPAuthDriver(options, config);
|
|
66
|
+
case 'saml':
|
|
67
|
+
return new drivers_1.SAMLAuthDriver(options, config);
|
|
66
68
|
}
|
|
67
69
|
}
|
package/dist/cli/index.test.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const path_1 = __importDefault(require("path"));
|
|
3
7
|
const index_1 = require("./index");
|
|
4
8
|
jest.mock('../../src/env', () => ({
|
|
5
9
|
...jest.requireActual('../../src/env').default,
|
|
@@ -16,7 +20,8 @@ jest.mock('@directus/shared/utils/node/get-extensions', () => ({
|
|
|
16
20
|
getPackageExtensions: jest.fn(() => Promise.resolve([])),
|
|
17
21
|
getLocalExtensions: jest.fn(() => Promise.resolve([customCliExtension])),
|
|
18
22
|
}));
|
|
19
|
-
|
|
23
|
+
const customHookPath = path_1.default.resolve('/hooks/custom-cli', 'index.js');
|
|
24
|
+
jest.doMock(customHookPath, () => customCliHook, { virtual: true });
|
|
20
25
|
const customCliExtension = {
|
|
21
26
|
path: `/hooks/custom-cli`,
|
|
22
27
|
name: 'custom-cli',
|
package/dist/controllers/auth.js
CHANGED
|
@@ -32,6 +32,9 @@ for (const authProvider of authProviders) {
|
|
|
32
32
|
case 'ldap':
|
|
33
33
|
authRouter = (0, drivers_1.createLDAPAuthRouter)(authProvider.name);
|
|
34
34
|
break;
|
|
35
|
+
case 'saml':
|
|
36
|
+
authRouter = (0, drivers_1.createSAMLAuthRouter)(authProvider.name);
|
|
37
|
+
break;
|
|
35
38
|
}
|
|
36
39
|
if (!authRouter) {
|
|
37
40
|
logger_1.default.warn(`Couldn't create login router for auth provider "${authProvider.name}"`);
|
|
@@ -101,9 +101,6 @@ const multipartHandler = (req, res, next) => {
|
|
|
101
101
|
};
|
|
102
102
|
exports.multipartHandler = multipartHandler;
|
|
103
103
|
router.post('/', (0, async_handler_1.default)(exports.multipartHandler), (0, async_handler_1.default)(async (req, res, next) => {
|
|
104
|
-
if (req.is('multipart/form-data') === false) {
|
|
105
|
-
throw new exceptions_1.UnsupportedMediaTypeException(`Unsupported Content-Type header`);
|
|
106
|
-
}
|
|
107
104
|
const service = new services_1.FilesService({
|
|
108
105
|
accountability: req.accountability,
|
|
109
106
|
schema: req.schema,
|
|
@@ -9,9 +9,6 @@ fields:
|
|
|
9
9
|
options:
|
|
10
10
|
iconRight: title
|
|
11
11
|
placeholder: $t:field_options.directus_settings.project_name_placeholder
|
|
12
|
-
translations:
|
|
13
|
-
language: en-US
|
|
14
|
-
translations: Name
|
|
15
12
|
width: half
|
|
16
13
|
|
|
17
14
|
- field: project_descriptor
|
|
@@ -19,9 +16,6 @@ fields:
|
|
|
19
16
|
options:
|
|
20
17
|
iconRight: title
|
|
21
18
|
placeholder: $t:field_options.directus_settings.project_name_placeholder
|
|
22
|
-
translations:
|
|
23
|
-
language: en-US
|
|
24
|
-
translations: Name
|
|
25
19
|
width: half
|
|
26
20
|
|
|
27
21
|
- field: project_url
|
|
@@ -29,9 +23,6 @@ fields:
|
|
|
29
23
|
options:
|
|
30
24
|
iconRight: link
|
|
31
25
|
placeholder: https://example.com
|
|
32
|
-
translations:
|
|
33
|
-
language: en-US
|
|
34
|
-
translations: Website
|
|
35
26
|
width: half
|
|
36
27
|
|
|
37
28
|
- field: default_language
|
|
@@ -39,9 +30,6 @@ fields:
|
|
|
39
30
|
options:
|
|
40
31
|
iconRight: language
|
|
41
32
|
placeholder: en-US
|
|
42
|
-
translations:
|
|
43
|
-
language: en-US
|
|
44
|
-
translations: Default Language
|
|
45
33
|
width: half
|
|
46
34
|
|
|
47
35
|
- field: branding_divider
|
|
@@ -57,31 +45,20 @@ fields:
|
|
|
57
45
|
- field: project_color
|
|
58
46
|
interface: select-color
|
|
59
47
|
note: $t:field_options.directus_settings.project_color_note
|
|
60
|
-
translations:
|
|
61
|
-
language: en-US
|
|
62
|
-
translations: Brand Color
|
|
63
48
|
width: half
|
|
64
49
|
|
|
65
50
|
- field: project_logo
|
|
66
51
|
interface: file
|
|
67
52
|
note: $t:field_options.directus_settings.project_logo_note
|
|
68
|
-
translations:
|
|
69
|
-
language: en-US
|
|
70
|
-
translations: Brand Logo
|
|
71
53
|
width: half
|
|
72
54
|
|
|
73
55
|
- field: public_foreground
|
|
74
56
|
interface: file
|
|
75
|
-
translations:
|
|
76
|
-
language: en-US
|
|
77
|
-
translations: Login Foreground
|
|
78
57
|
width: half
|
|
79
58
|
|
|
80
59
|
- field: public_background
|
|
81
60
|
interface: file
|
|
82
61
|
translations:
|
|
83
|
-
language: en-US
|
|
84
|
-
translations: Login Background
|
|
85
62
|
width: half
|
|
86
63
|
|
|
87
64
|
- field: public_note
|
package/dist/env.js
CHANGED
|
@@ -290,8 +290,10 @@ class AuthorizationService {
|
|
|
290
290
|
for (const field of requiredPermissions[collection]) {
|
|
291
291
|
if (field.startsWith('$FOLLOW'))
|
|
292
292
|
continue;
|
|
293
|
-
|
|
293
|
+
const fieldName = (0, strip_function_1.stripFunction)(field);
|
|
294
|
+
if (!allowedFields.includes(fieldName)) {
|
|
294
295
|
throw new exceptions_2.ForbiddenException();
|
|
296
|
+
}
|
|
295
297
|
}
|
|
296
298
|
}
|
|
297
299
|
}
|
package/dist/services/fields.js
CHANGED
|
@@ -303,6 +303,11 @@ class FieldsService {
|
|
|
303
303
|
const record = field.meta
|
|
304
304
|
? await this.knex.select('id').from('directus_fields').where({ collection, field: field.field }).first()
|
|
305
305
|
: null;
|
|
306
|
+
if ((hookAdjustedField.type === 'alias' ||
|
|
307
|
+
this.schema.collections[collection].fields[field.field].type === 'alias') &&
|
|
308
|
+
hookAdjustedField.type !== this.schema.collections[collection].fields[field.field].type) {
|
|
309
|
+
throw new exceptions_1.InvalidPayloadException('Alias type cannot be changed');
|
|
310
|
+
}
|
|
306
311
|
if (hookAdjustedField.schema) {
|
|
307
312
|
const existingColumn = await this.schemaInspector.columnInfo(collection, hookAdjustedField.field);
|
|
308
313
|
if (!(0, lodash_1.isEqual)(existingColumn, hookAdjustedField.schema)) {
|
|
@@ -390,7 +395,10 @@ class FieldsService {
|
|
|
390
395
|
// If the current field is a m2o, delete the related o2m if it exists and remove the relationship
|
|
391
396
|
if (isM2O) {
|
|
392
397
|
await relationsService.deleteOne(collection, field);
|
|
393
|
-
if (relation.related_collection &&
|
|
398
|
+
if (relation.related_collection &&
|
|
399
|
+
((_a = relation.meta) === null || _a === void 0 ? void 0 : _a.one_field) &&
|
|
400
|
+
relation.related_collection !== collection &&
|
|
401
|
+
relation.meta.one_field !== field) {
|
|
394
402
|
await fieldsService.deleteField(relation.related_collection, relation.meta.one_field);
|
|
395
403
|
}
|
|
396
404
|
}
|
package/dist/services/files.d.ts
CHANGED
|
@@ -18,6 +18,11 @@ export declare class FilesService extends ItemsService {
|
|
|
18
18
|
* Import a single file from an external URL
|
|
19
19
|
*/
|
|
20
20
|
importOne(importURL: string, body: Partial<File>): Promise<PrimaryKey>;
|
|
21
|
+
/**
|
|
22
|
+
* Create a file (only applicable when it is not a multipart/data POST request)
|
|
23
|
+
* Useful for associating metadata with existing file in storage
|
|
24
|
+
*/
|
|
25
|
+
createOne(data: Partial<File>, opts?: MutationOptions): Promise<PrimaryKey>;
|
|
21
26
|
/**
|
|
22
27
|
* Delete a file
|
|
23
28
|
*/
|
package/dist/services/files.js
CHANGED
|
@@ -251,6 +251,17 @@ class FilesService extends items_1.ItemsService {
|
|
|
251
251
|
};
|
|
252
252
|
return await this.uploadOne(fileResponse.data, payload);
|
|
253
253
|
}
|
|
254
|
+
/**
|
|
255
|
+
* Create a file (only applicable when it is not a multipart/data POST request)
|
|
256
|
+
* Useful for associating metadata with existing file in storage
|
|
257
|
+
*/
|
|
258
|
+
async createOne(data, opts) {
|
|
259
|
+
if (!data.type) {
|
|
260
|
+
throw new exceptions_1.InvalidPayloadException(`"type" is required`);
|
|
261
|
+
}
|
|
262
|
+
const key = await super.createOne(data, opts);
|
|
263
|
+
return key;
|
|
264
|
+
}
|
|
254
265
|
/**
|
|
255
266
|
* Delete a file
|
|
256
267
|
*/
|
|
@@ -7,6 +7,7 @@ const exifr_1 = __importDefault(require("exifr"));
|
|
|
7
7
|
const knex_1 = __importDefault(require("knex"));
|
|
8
8
|
const knex_mock_client_1 = require("knex-mock-client");
|
|
9
9
|
const _1 = require(".");
|
|
10
|
+
const exceptions_1 = require("../exceptions");
|
|
10
11
|
jest.mock('exifr');
|
|
11
12
|
jest.mock('../../src/database/index', () => {
|
|
12
13
|
return { getDatabaseClient: jest.fn().mockReturnValue('postgres') };
|
|
@@ -21,8 +22,43 @@ describe('Integration Tests', () => {
|
|
|
21
22
|
});
|
|
22
23
|
afterEach(() => {
|
|
23
24
|
tracker.reset();
|
|
25
|
+
jest.clearAllMocks();
|
|
24
26
|
});
|
|
25
27
|
describe('Services / Files', () => {
|
|
28
|
+
describe('createOne', () => {
|
|
29
|
+
let service;
|
|
30
|
+
let superCreateOne;
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
service = new _1.FilesService({
|
|
33
|
+
knex: db,
|
|
34
|
+
schema: { collections: {}, relations: [] },
|
|
35
|
+
});
|
|
36
|
+
superCreateOne = jest.spyOn(_1.ItemsService.prototype, 'createOne').mockImplementation(jest.fn());
|
|
37
|
+
});
|
|
38
|
+
it('throws InvalidPayloadException when "type" is not provided', async () => {
|
|
39
|
+
try {
|
|
40
|
+
await service.createOne({
|
|
41
|
+
title: 'Test File',
|
|
42
|
+
storage: 'local',
|
|
43
|
+
filename_download: 'test_file',
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
expect(err).toBeInstanceOf(exceptions_1.InvalidPayloadException);
|
|
48
|
+
expect(err.message).toBe('"type" is required');
|
|
49
|
+
}
|
|
50
|
+
expect(superCreateOne).not.toHaveBeenCalled();
|
|
51
|
+
});
|
|
52
|
+
it('creates a file entry when "type" is provided', async () => {
|
|
53
|
+
await service.createOne({
|
|
54
|
+
title: 'Test File',
|
|
55
|
+
storage: 'local',
|
|
56
|
+
filename_download: 'test_file',
|
|
57
|
+
type: 'application/octet-stream',
|
|
58
|
+
});
|
|
59
|
+
expect(superCreateOne).toHaveBeenCalled();
|
|
60
|
+
});
|
|
61
|
+
});
|
|
26
62
|
describe('getMetadata', () => {
|
|
27
63
|
let service;
|
|
28
64
|
let exifrParseSpy;
|
|
@@ -51,6 +51,7 @@ const string_or_float_1 = require("./types/string-or-float");
|
|
|
51
51
|
const void_1 = require("./types/void");
|
|
52
52
|
const add_path_to_validation_error_1 = require("./utils/add-path-to-validation-error");
|
|
53
53
|
const hash_1 = require("./types/hash");
|
|
54
|
+
const bigint_1 = require("./types/bigint");
|
|
54
55
|
const validationRules = Array.from(graphql_1.specifiedRules);
|
|
55
56
|
if (env_1.default.GRAPHQL_INTROSPECTION === false) {
|
|
56
57
|
validationRules.push(graphql_1.NoSchemaIntrospectionCustomRule);
|
|
@@ -668,6 +669,7 @@ class GraphQLService {
|
|
|
668
669
|
case graphql_1.GraphQLBoolean:
|
|
669
670
|
filterOperatorType = BooleanFilterOperators;
|
|
670
671
|
break;
|
|
672
|
+
case bigint_1.GraphQLBigInt:
|
|
671
673
|
case graphql_1.GraphQLInt:
|
|
672
674
|
case graphql_1.GraphQLFloat:
|
|
673
675
|
filterOperatorType = NumberFilterOperators;
|
|
@@ -717,6 +719,7 @@ class GraphQLService {
|
|
|
717
719
|
fields: Object.values(collection.fields).reduce((acc, field) => {
|
|
718
720
|
const graphqlType = (0, get_graphql_type_1.getGraphQLType)(field.type, field.special);
|
|
719
721
|
switch (graphqlType) {
|
|
722
|
+
case bigint_1.GraphQLBigInt:
|
|
720
723
|
case graphql_1.GraphQLInt:
|
|
721
724
|
case graphql_1.GraphQLFloat:
|
|
722
725
|
acc[field.field] = {
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GraphQLBigInt = void 0;
|
|
4
|
+
const graphql_1 = require("graphql");
|
|
5
|
+
exports.GraphQLBigInt = new graphql_1.GraphQLScalarType({
|
|
6
|
+
name: 'GraphQLBigInt',
|
|
7
|
+
description: 'BigInt value',
|
|
8
|
+
serialize(value) {
|
|
9
|
+
if (typeof value !== 'number') {
|
|
10
|
+
throw new Error('Value must be a Number');
|
|
11
|
+
}
|
|
12
|
+
return value.toString();
|
|
13
|
+
},
|
|
14
|
+
parseValue(value) {
|
|
15
|
+
if (typeof value !== 'string') {
|
|
16
|
+
throw new Error('Value must be a String');
|
|
17
|
+
}
|
|
18
|
+
return parseNumberValue(value);
|
|
19
|
+
},
|
|
20
|
+
parseLiteral(ast) {
|
|
21
|
+
if (ast.kind !== graphql_1.Kind.STRING) {
|
|
22
|
+
throw new Error('Value must be a String');
|
|
23
|
+
}
|
|
24
|
+
return parseNumberValue(ast.value);
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
function parseNumberValue(input) {
|
|
28
|
+
if (!/[+-]?([0-9]+[.])?[0-9]+/.test(input))
|
|
29
|
+
return input;
|
|
30
|
+
const value = parseInt(input);
|
|
31
|
+
if (isNaN(value) || value < Number.MIN_SAFE_INTEGER || value > Number.MAX_SAFE_INTEGER) {
|
|
32
|
+
throw new Error('Invalid GraphQLBigInt');
|
|
33
|
+
}
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
@@ -6,6 +6,7 @@ const graphql_compose_1 = require("graphql-compose");
|
|
|
6
6
|
const date_1 = require("../services/graphql/types/date");
|
|
7
7
|
const geojson_1 = require("../services/graphql/types/geojson");
|
|
8
8
|
const hash_1 = require("../services/graphql/types/hash");
|
|
9
|
+
const bigint_1 = require("../services/graphql/types/bigint");
|
|
9
10
|
function getGraphQLType(localType, special) {
|
|
10
11
|
if (special.includes('conceal')) {
|
|
11
12
|
return hash_1.GraphQLHash;
|
|
@@ -14,7 +15,7 @@ function getGraphQLType(localType, special) {
|
|
|
14
15
|
case 'boolean':
|
|
15
16
|
return graphql_1.GraphQLBoolean;
|
|
16
17
|
case 'bigInteger':
|
|
17
|
-
return
|
|
18
|
+
return bigint_1.GraphQLBigInt;
|
|
18
19
|
case 'integer':
|
|
19
20
|
return graphql_1.GraphQLInt;
|
|
20
21
|
case 'decimal':
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "directus",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.20.0",
|
|
4
4
|
"license": "GPL-3.0-only",
|
|
5
5
|
"homepage": "https://github.com/directus/directus#readme",
|
|
6
6
|
"description": "Directus is a real-time API and App dashboard for managing SQL database content.",
|
|
@@ -66,16 +66,16 @@
|
|
|
66
66
|
],
|
|
67
67
|
"dependencies": {
|
|
68
68
|
"@aws-sdk/client-ses": "3.190.0",
|
|
69
|
-
"@directus/app": "9.
|
|
70
|
-
"@directus/drive": "9.
|
|
71
|
-
"@directus/drive-azure": "9.
|
|
72
|
-
"@directus/drive-gcs": "9.
|
|
73
|
-
"@directus/drive-s3": "9.
|
|
74
|
-
"@directus/extensions-sdk": "9.
|
|
69
|
+
"@directus/app": "9.20.0",
|
|
70
|
+
"@directus/drive": "9.20.0",
|
|
71
|
+
"@directus/drive-azure": "9.20.0",
|
|
72
|
+
"@directus/drive-gcs": "9.20.0",
|
|
73
|
+
"@directus/drive-s3": "9.20.0",
|
|
74
|
+
"@directus/extensions-sdk": "9.20.0",
|
|
75
75
|
"@directus/format-title": "9.15.0",
|
|
76
|
-
"@directus/schema": "9.
|
|
77
|
-
"@directus/shared": "9.
|
|
78
|
-
"@directus/specs": "9.
|
|
76
|
+
"@directus/schema": "9.20.0",
|
|
77
|
+
"@directus/shared": "9.20.0",
|
|
78
|
+
"@directus/specs": "9.20.0",
|
|
79
79
|
"@godaddy/terminus": "4.11.2",
|
|
80
80
|
"@rollup/plugin-alias": "4.0.0",
|
|
81
81
|
"@rollup/plugin-virtual": "3.0.0",
|
|
@@ -142,6 +142,7 @@
|
|
|
142
142
|
"rate-limiter-flexible": "2.3.12",
|
|
143
143
|
"resolve-cwd": "3.0.0",
|
|
144
144
|
"rollup": "3.2.3",
|
|
145
|
+
"samlify": "2.8.6",
|
|
145
146
|
"sanitize-html": "2.7.2",
|
|
146
147
|
"sharp": "0.31.1",
|
|
147
148
|
"snappy": "7.2.0",
|
|
@@ -167,9 +168,9 @@
|
|
|
167
168
|
},
|
|
168
169
|
"gitHead": "24621f3934dc77eb23441331040ed13c676ceffd",
|
|
169
170
|
"devDependencies": {
|
|
171
|
+
"@authenio/samlify-node-xmllint": "2.0.0",
|
|
170
172
|
"@otplib/preset-default": "12.0.1",
|
|
171
173
|
"@types/async": "3.2.15",
|
|
172
|
-
"@types/body-parser": "1.19.2",
|
|
173
174
|
"@types/busboy": "1.5.0",
|
|
174
175
|
"@types/bytes": "3.1.1",
|
|
175
176
|
"@types/cookie-parser": "1.4.3",
|