directus 9.2.0 → 9.4.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/app.js +5 -3
- package/dist/auth/auth.d.ts +4 -6
- package/dist/auth/auth.js +5 -9
- package/dist/auth/drivers/ldap.d.ts +3 -3
- package/dist/auth/drivers/ldap.js +11 -4
- package/dist/auth/drivers/local.d.ts +2 -2
- package/dist/auth/drivers/local.js +5 -12
- package/dist/auth/drivers/oauth2.d.ts +4 -4
- package/dist/auth/drivers/oauth2.js +47 -21
- package/dist/auth/drivers/openid.d.ts +4 -4
- package/dist/auth/drivers/openid.js +35 -19
- package/dist/cli/commands/bootstrap/index.js +3 -2
- package/dist/cli/commands/init/index.js +3 -7
- package/dist/cli/commands/schema/apply.js +1 -1
- package/dist/cli/utils/defaults.d.ts +11 -0
- package/dist/cli/utils/defaults.js +14 -0
- package/dist/constants.d.ts +8 -0
- package/dist/constants.js +16 -2
- package/dist/controllers/shares.d.ts +2 -0
- package/dist/controllers/shares.js +212 -0
- package/dist/controllers/users.js +21 -9
- package/dist/database/migrations/20211211A-add-shares.d.ts +3 -0
- package/dist/database/migrations/20211211A-add-shares.js +37 -0
- package/dist/database/run-ast.js +5 -5
- package/dist/database/system-data/app-access-permissions/app-access-permissions.yaml +0 -15
- package/dist/database/system-data/app-access-permissions/index.d.ts +1 -0
- package/dist/database/system-data/app-access-permissions/index.js +4 -2
- package/dist/database/system-data/app-access-permissions/schema-access-permissions.yaml +17 -0
- package/dist/database/system-data/collections/collections.yaml +3 -0
- package/dist/database/system-data/fields/sessions.yaml +1 -1
- package/dist/database/system-data/fields/shares.yaml +73 -0
- package/dist/database/system-data/fields/users.yaml +1 -1
- package/dist/database/system-data/relations/relations.yaml +15 -0
- package/dist/emitter.d.ts +3 -2
- package/dist/emitter.js +13 -6
- package/dist/exceptions/index.d.ts +2 -0
- package/dist/exceptions/index.js +2 -0
- package/dist/exceptions/invalid-token.d.ts +4 -0
- package/dist/exceptions/invalid-token.js +10 -0
- package/dist/exceptions/unexpected-response.d.ts +4 -0
- package/dist/exceptions/unexpected-response.js +10 -0
- package/dist/extensions.d.ts +1 -0
- package/dist/extensions.js +10 -4
- package/dist/middleware/authenticate.js +5 -15
- package/dist/middleware/check-ip.js +9 -6
- package/dist/middleware/respond.js +4 -1
- package/dist/services/activity.d.ts +2 -1
- package/dist/services/activity.js +2 -2
- package/dist/services/authentication.d.ts +2 -7
- package/dist/services/authentication.js +81 -41
- package/dist/services/authorization.js +3 -3
- package/dist/services/collections.d.ts +1 -2
- package/dist/services/collections.js +2 -2
- package/dist/services/files.d.ts +2 -2
- package/dist/services/files.js +14 -8
- package/dist/services/graphql.js +16 -5
- package/dist/services/index.d.ts +1 -0
- package/dist/services/index.js +1 -0
- package/dist/services/items.d.ts +1 -15
- package/dist/services/notifications.d.ts +2 -2
- package/dist/services/permissions.d.ts +2 -2
- package/dist/services/roles.d.ts +2 -2
- package/dist/services/shares.d.ts +17 -0
- package/dist/services/shares.js +135 -0
- package/dist/services/specifications.js +1 -1
- package/dist/services/users.d.ts +2 -2
- package/dist/services/users.js +8 -6
- package/dist/services/webhooks.d.ts +2 -2
- package/dist/tests/database/migrations/run.test.d.ts +1 -0
- package/dist/tests/database/migrations/run.test.js +29 -0
- package/dist/types/ast.d.ts +3 -3
- package/dist/types/auth.d.ts +31 -0
- package/dist/types/extensions.d.ts +2 -0
- package/dist/types/items.d.ts +14 -0
- package/dist/utils/apply-query.d.ts +0 -38
- package/dist/utils/apply-query.js +67 -69
- package/dist/utils/apply-snapshot.js +69 -14
- package/dist/utils/get-ast-from-query.js +3 -3
- package/dist/utils/get-permissions.d.ts +2 -2
- package/dist/utils/get-permissions.js +117 -72
- package/dist/utils/get-relation-type.d.ts +1 -1
- package/dist/utils/get-relation-type.js +1 -1
- package/dist/utils/merge-permissions-for-share.d.ts +5 -0
- package/dist/utils/merge-permissions-for-share.js +116 -0
- package/dist/utils/merge-permissions.d.ts +13 -1
- package/dist/utils/merge-permissions.js +29 -21
- package/dist/utils/reduce-schema.d.ts +2 -2
- package/dist/utils/reduce-schema.js +7 -7
- package/dist/utils/user-name.js +3 -0
- package/package.json +14 -13
package/dist/app.js
CHANGED
|
@@ -51,6 +51,7 @@ const settings_1 = __importDefault(require("./controllers/settings"));
|
|
|
51
51
|
const users_1 = __importDefault(require("./controllers/users"));
|
|
52
52
|
const utils_1 = __importDefault(require("./controllers/utils"));
|
|
53
53
|
const webhooks_1 = __importDefault(require("./controllers/webhooks"));
|
|
54
|
+
const shares_1 = __importDefault(require("./controllers/shares"));
|
|
54
55
|
const database_1 = require("./database");
|
|
55
56
|
const emitter_1 = __importDefault(require("./emitter"));
|
|
56
57
|
const env_1 = __importDefault(require("./env"));
|
|
@@ -112,14 +113,14 @@ async function createApp() {
|
|
|
112
113
|
});
|
|
113
114
|
app.use((0, cookie_parser_1.default)());
|
|
114
115
|
app.use(extract_token_1.default);
|
|
115
|
-
app.use((
|
|
116
|
+
app.use((_req, res, next) => {
|
|
116
117
|
res.setHeader('X-Powered-By', 'Directus');
|
|
117
118
|
next();
|
|
118
119
|
});
|
|
119
120
|
if (env_1.default.CORS_ENABLED === true) {
|
|
120
121
|
app.use(cors_1.default);
|
|
121
122
|
}
|
|
122
|
-
app.get('/', (
|
|
123
|
+
app.get('/', (_req, res, next) => {
|
|
123
124
|
if (env_1.default.ROOT_REDIRECT) {
|
|
124
125
|
res.redirect(env_1.default.ROOT_REDIRECT);
|
|
125
126
|
}
|
|
@@ -133,7 +134,7 @@ async function createApp() {
|
|
|
133
134
|
// Set the App's base path according to the APIs public URL
|
|
134
135
|
const html = await fs_extra_1.default.readFile(adminPath, 'utf8');
|
|
135
136
|
const htmlWithBase = html.replace(/<base \/>/, `<base href="${adminUrl.toString({ rootRelative: true })}/" />`);
|
|
136
|
-
const noCacheIndexHtmlHandler = (
|
|
137
|
+
const noCacheIndexHtmlHandler = (_req, res) => {
|
|
137
138
|
res.setHeader('Cache-Control', 'no-cache');
|
|
138
139
|
res.send(htmlWithBase);
|
|
139
140
|
};
|
|
@@ -173,6 +174,7 @@ async function createApp() {
|
|
|
173
174
|
app.use('/roles', roles_1.default);
|
|
174
175
|
app.use('/server', server_1.default);
|
|
175
176
|
app.use('/settings', settings_1.default);
|
|
177
|
+
app.use('/shares', shares_1.default);
|
|
176
178
|
app.use('/users', users_1.default);
|
|
177
179
|
app.use('/utils', utils_1.default);
|
|
178
180
|
app.use('/webhooks', webhooks_1.default);
|
package/dist/auth/auth.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Knex } from 'knex';
|
|
2
|
-
import { AuthDriverOptions, SchemaOverview, User
|
|
2
|
+
import { AuthDriverOptions, SchemaOverview, User } from '../types';
|
|
3
3
|
export declare abstract class AuthDriver {
|
|
4
4
|
knex: Knex;
|
|
5
5
|
schema: SchemaOverview;
|
|
@@ -28,20 +28,18 @@ export declare abstract class AuthDriver {
|
|
|
28
28
|
* @throws InvalidCredentialsException
|
|
29
29
|
* @returns Data to be stored with the session
|
|
30
30
|
*/
|
|
31
|
-
login(_user: User, _payload: Record<string, any>): Promise<
|
|
31
|
+
login(_user: User, _payload: Record<string, any>): Promise<void>;
|
|
32
32
|
/**
|
|
33
33
|
* Handle user session refresh
|
|
34
34
|
*
|
|
35
35
|
* @param _user User information
|
|
36
|
-
* @param _sessionData Session data
|
|
37
36
|
* @throws InvalidCredentialsException
|
|
38
37
|
*/
|
|
39
|
-
refresh(_user: User
|
|
38
|
+
refresh(_user: User): Promise<void>;
|
|
40
39
|
/**
|
|
41
40
|
* Handle user session termination
|
|
42
41
|
*
|
|
43
42
|
* @param _user User information
|
|
44
|
-
* @param _sessionData Session data
|
|
45
43
|
*/
|
|
46
|
-
logout(_user: User
|
|
44
|
+
logout(_user: User): Promise<void>;
|
|
47
45
|
}
|
package/dist/auth/auth.js
CHANGED
|
@@ -15,28 +15,24 @@ class AuthDriver {
|
|
|
15
15
|
* @returns Data to be stored with the session
|
|
16
16
|
*/
|
|
17
17
|
async login(_user, _payload) {
|
|
18
|
-
|
|
19
|
-
return null;
|
|
18
|
+
return;
|
|
20
19
|
}
|
|
21
20
|
/**
|
|
22
21
|
* Handle user session refresh
|
|
23
22
|
*
|
|
24
23
|
* @param _user User information
|
|
25
|
-
* @param _sessionData Session data
|
|
26
24
|
* @throws InvalidCredentialsException
|
|
27
25
|
*/
|
|
28
|
-
async refresh(_user
|
|
29
|
-
|
|
30
|
-
return sessionData;
|
|
26
|
+
async refresh(_user) {
|
|
27
|
+
return;
|
|
31
28
|
}
|
|
32
29
|
/**
|
|
33
30
|
* Handle user session termination
|
|
34
31
|
*
|
|
35
32
|
* @param _user User information
|
|
36
|
-
* @param _sessionData Session data
|
|
37
33
|
*/
|
|
38
|
-
async logout(_user
|
|
39
|
-
|
|
34
|
+
async logout(_user) {
|
|
35
|
+
return;
|
|
40
36
|
}
|
|
41
37
|
}
|
|
42
38
|
exports.AuthDriver = AuthDriver;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Router } from 'express';
|
|
2
2
|
import { Client } from 'ldapjs';
|
|
3
3
|
import { AuthDriver } from '../auth';
|
|
4
|
-
import { AuthDriverOptions, User
|
|
4
|
+
import { AuthDriverOptions, User } from '../../types';
|
|
5
5
|
import { UsersService } from '../../services';
|
|
6
6
|
export declare class LDAPAuthDriver extends AuthDriver {
|
|
7
7
|
bindClient: Client;
|
|
@@ -15,7 +15,7 @@ export declare class LDAPAuthDriver extends AuthDriver {
|
|
|
15
15
|
private fetchUserId;
|
|
16
16
|
getUserID(payload: Record<string, any>): Promise<string>;
|
|
17
17
|
verify(user: User, password?: string): Promise<void>;
|
|
18
|
-
login(user: User, payload: Record<string, any>): Promise<
|
|
19
|
-
refresh(user: User): Promise<
|
|
18
|
+
login(user: User, payload: Record<string, any>): Promise<void>;
|
|
19
|
+
refresh(user: User): Promise<void>;
|
|
20
20
|
}
|
|
21
21
|
export declare function createLDAPAuthRouter(provider: string): Router;
|
|
@@ -87,6 +87,12 @@ class LDAPAuthDriver extends auth_1.AuthDriver {
|
|
|
87
87
|
}
|
|
88
88
|
});
|
|
89
89
|
});
|
|
90
|
+
res.on('end', (result) => {
|
|
91
|
+
if ((result === null || result === void 0 ? void 0 : result.status) === 0) {
|
|
92
|
+
// Handle edge case with IBM systems where authenticated bind user could not fetch their DN
|
|
93
|
+
reject(new exceptions_1.UnexpectedResponseException('Failed to find bind user record'));
|
|
94
|
+
}
|
|
95
|
+
});
|
|
90
96
|
});
|
|
91
97
|
});
|
|
92
98
|
}
|
|
@@ -94,7 +100,10 @@ class LDAPAuthDriver extends auth_1.AuthDriver {
|
|
|
94
100
|
const { userDn, userAttribute, userScope } = this.config;
|
|
95
101
|
return new Promise((resolve, reject) => {
|
|
96
102
|
// Search for the user in LDAP by attribute
|
|
97
|
-
this.bindClient.search(userDn, {
|
|
103
|
+
this.bindClient.search(userDn, {
|
|
104
|
+
filter: new ldapjs_1.EqualityFilter({ attribute: userAttribute !== null && userAttribute !== void 0 ? userAttribute : 'cn', value: identifier }),
|
|
105
|
+
scope: userScope !== null && userScope !== void 0 ? userScope : 'one',
|
|
106
|
+
}, (err, res) => {
|
|
98
107
|
if (err) {
|
|
99
108
|
reject(handleError(err));
|
|
100
109
|
return;
|
|
@@ -151,7 +160,7 @@ class LDAPAuthDriver extends auth_1.AuthDriver {
|
|
|
151
160
|
// Search for the user info in LDAP by group attribute
|
|
152
161
|
this.bindClient.search(groupDn, {
|
|
153
162
|
attributes: ['cn'],
|
|
154
|
-
filter:
|
|
163
|
+
filter: new ldapjs_1.EqualityFilter({ attribute: groupAttribute !== null && groupAttribute !== void 0 ? groupAttribute : 'member', value: userDn }),
|
|
155
164
|
scope: groupScope !== null && groupScope !== void 0 ? groupScope : 'one',
|
|
156
165
|
}, (err, res) => {
|
|
157
166
|
if (err) {
|
|
@@ -251,7 +260,6 @@ class LDAPAuthDriver extends auth_1.AuthDriver {
|
|
|
251
260
|
}
|
|
252
261
|
async login(user, payload) {
|
|
253
262
|
await this.verify(user, payload.password);
|
|
254
|
-
return null;
|
|
255
263
|
}
|
|
256
264
|
async refresh(user) {
|
|
257
265
|
await this.validateBindClient();
|
|
@@ -259,7 +267,6 @@ class LDAPAuthDriver extends auth_1.AuthDriver {
|
|
|
259
267
|
if ((userInfo === null || userInfo === void 0 ? void 0 : userInfo.userAccountControl) && userInfo.userAccountControl & INVALID_ACCOUNT_FLAGS) {
|
|
260
268
|
throw new exceptions_1.InvalidCredentialsException();
|
|
261
269
|
}
|
|
262
|
-
return null;
|
|
263
270
|
}
|
|
264
271
|
}
|
|
265
272
|
exports.LDAPAuthDriver = LDAPAuthDriver;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Router } from 'express';
|
|
2
2
|
import { AuthDriver } from '../auth';
|
|
3
|
-
import { User
|
|
3
|
+
import { User } from '../../types';
|
|
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>;
|
|
7
|
-
login(user: User, payload: Record<string, any>): Promise<
|
|
7
|
+
login(user: User, payload: Record<string, any>): Promise<void>;
|
|
8
8
|
}
|
|
9
9
|
export declare function createLocalAuthRouter(provider: string): Router;
|
|
@@ -6,7 +6,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.createLocalAuthRouter = exports.LocalAuthDriver = void 0;
|
|
7
7
|
const express_1 = require("express");
|
|
8
8
|
const argon2_1 = __importDefault(require("argon2"));
|
|
9
|
-
const ms_1 = __importDefault(require("ms"));
|
|
10
9
|
const joi_1 = __importDefault(require("joi"));
|
|
11
10
|
const auth_1 = require("../auth");
|
|
12
11
|
const exceptions_1 = require("../../exceptions");
|
|
@@ -14,6 +13,7 @@ const services_1 = require("../../services");
|
|
|
14
13
|
const async_handler_1 = __importDefault(require("../../utils/async-handler"));
|
|
15
14
|
const env_1 = __importDefault(require("../../env"));
|
|
16
15
|
const respond_1 = require("../../middleware/respond");
|
|
16
|
+
const constants_1 = require("../../constants");
|
|
17
17
|
class LocalAuthDriver extends auth_1.AuthDriver {
|
|
18
18
|
async getUserID(payload) {
|
|
19
19
|
if (!payload.email) {
|
|
@@ -36,20 +36,19 @@ class LocalAuthDriver extends auth_1.AuthDriver {
|
|
|
36
36
|
}
|
|
37
37
|
async login(user, payload) {
|
|
38
38
|
await this.verify(user, payload.password);
|
|
39
|
-
return null;
|
|
40
39
|
}
|
|
41
40
|
}
|
|
42
41
|
exports.LocalAuthDriver = LocalAuthDriver;
|
|
43
42
|
function createLocalAuthRouter(provider) {
|
|
44
43
|
const router = (0, express_1.Router)();
|
|
45
|
-
const
|
|
44
|
+
const userLoginSchema = joi_1.default.object({
|
|
46
45
|
email: joi_1.default.string().email().required(),
|
|
47
46
|
password: joi_1.default.string().required(),
|
|
48
47
|
mode: joi_1.default.string().valid('cookie', 'json'),
|
|
49
48
|
otp: joi_1.default.string(),
|
|
50
49
|
}).unknown();
|
|
51
50
|
router.post('/', (0, async_handler_1.default)(async (req, res, next) => {
|
|
52
|
-
var _a
|
|
51
|
+
var _a;
|
|
53
52
|
const accountability = {
|
|
54
53
|
ip: req.ip,
|
|
55
54
|
userAgent: req.get('user-agent'),
|
|
@@ -59,7 +58,7 @@ function createLocalAuthRouter(provider) {
|
|
|
59
58
|
accountability: accountability,
|
|
60
59
|
schema: req.schema,
|
|
61
60
|
});
|
|
62
|
-
const { error } =
|
|
61
|
+
const { error } = userLoginSchema.validate(req.body);
|
|
63
62
|
if (error) {
|
|
64
63
|
throw new exceptions_1.InvalidPayloadException(error.message);
|
|
65
64
|
}
|
|
@@ -72,13 +71,7 @@ function createLocalAuthRouter(provider) {
|
|
|
72
71
|
payload.data.refresh_token = refreshToken;
|
|
73
72
|
}
|
|
74
73
|
if (mode === 'cookie') {
|
|
75
|
-
res.cookie(env_1.default.REFRESH_TOKEN_COOKIE_NAME, refreshToken,
|
|
76
|
-
httpOnly: true,
|
|
77
|
-
domain: env_1.default.REFRESH_TOKEN_COOKIE_DOMAIN,
|
|
78
|
-
maxAge: (0, ms_1.default)(env_1.default.REFRESH_TOKEN_TTL),
|
|
79
|
-
secure: (_b = env_1.default.REFRESH_TOKEN_COOKIE_SECURE) !== null && _b !== void 0 ? _b : false,
|
|
80
|
-
sameSite: env_1.default.REFRESH_TOKEN_COOKIE_SAME_SITE || 'strict',
|
|
81
|
-
});
|
|
74
|
+
res.cookie(env_1.default.REFRESH_TOKEN_COOKIE_NAME, refreshToken, constants_1.COOKIE_OPTIONS);
|
|
82
75
|
}
|
|
83
76
|
res.locals.payload = payload;
|
|
84
77
|
return next();
|
|
@@ -2,7 +2,7 @@ import { Router } from 'express';
|
|
|
2
2
|
import { Client } from 'openid-client';
|
|
3
3
|
import { LocalAuthDriver } from './local';
|
|
4
4
|
import { UsersService } from '../../services';
|
|
5
|
-
import { AuthDriverOptions, User
|
|
5
|
+
import { AuthDriverOptions, User } from '../../types';
|
|
6
6
|
export declare class OAuth2AuthDriver extends LocalAuthDriver {
|
|
7
7
|
client: Client;
|
|
8
8
|
redirectUrl: string;
|
|
@@ -10,10 +10,10 @@ export declare class OAuth2AuthDriver extends LocalAuthDriver {
|
|
|
10
10
|
config: Record<string, any>;
|
|
11
11
|
constructor(options: AuthDriverOptions, config: Record<string, any>);
|
|
12
12
|
generateCodeVerifier(): string;
|
|
13
|
-
generateAuthUrl(codeVerifier: string): string;
|
|
13
|
+
generateAuthUrl(codeVerifier: string, prompt?: boolean): string;
|
|
14
14
|
private fetchUserId;
|
|
15
15
|
getUserID(payload: Record<string, any>): Promise<string>;
|
|
16
|
-
login(user: User): Promise<
|
|
17
|
-
refresh(user: User
|
|
16
|
+
login(user: User): Promise<void>;
|
|
17
|
+
refresh(user: User): Promise<void>;
|
|
18
18
|
}
|
|
19
19
|
export declare function createOAuth2AuthRouter(providerName: string): Router;
|
|
@@ -32,7 +32,8 @@ class OAuth2AuthDriver extends local_1.LocalAuthDriver {
|
|
|
32
32
|
authorization_endpoint: authorizeUrl,
|
|
33
33
|
token_endpoint: accessUrl,
|
|
34
34
|
userinfo_endpoint: profileUrl,
|
|
35
|
-
|
|
35
|
+
// Required for openid providers (openid flow should be preferred!)
|
|
36
|
+
issuer: additionalConfig.issuerUrl,
|
|
36
37
|
});
|
|
37
38
|
this.client = new issuer.Client({
|
|
38
39
|
client_id: clientId,
|
|
@@ -44,17 +45,20 @@ class OAuth2AuthDriver extends local_1.LocalAuthDriver {
|
|
|
44
45
|
generateCodeVerifier() {
|
|
45
46
|
return openid_client_1.generators.codeVerifier();
|
|
46
47
|
}
|
|
47
|
-
generateAuthUrl(codeVerifier) {
|
|
48
|
+
generateAuthUrl(codeVerifier, prompt = false) {
|
|
48
49
|
var _a;
|
|
49
50
|
try {
|
|
50
51
|
const codeChallenge = openid_client_1.generators.codeChallenge(codeVerifier);
|
|
52
|
+
const paramsConfig = typeof this.config.params === 'object' ? this.config.params : {};
|
|
51
53
|
return this.client.authorizationUrl({
|
|
52
54
|
scope: (_a = this.config.scope) !== null && _a !== void 0 ? _a : 'email',
|
|
55
|
+
access_type: 'offline',
|
|
56
|
+
prompt: prompt ? 'consent' : undefined,
|
|
57
|
+
...paramsConfig,
|
|
53
58
|
code_challenge: codeChallenge,
|
|
54
59
|
code_challenge_method: 'S256',
|
|
55
60
|
// Some providers require state even with PKCE
|
|
56
61
|
state: codeChallenge,
|
|
57
|
-
access_type: 'offline',
|
|
58
62
|
});
|
|
59
63
|
}
|
|
60
64
|
catch (e) {
|
|
@@ -72,6 +76,7 @@ class OAuth2AuthDriver extends local_1.LocalAuthDriver {
|
|
|
72
76
|
async getUserID(payload) {
|
|
73
77
|
var _a;
|
|
74
78
|
if (!payload.code || !payload.codeVerifier) {
|
|
79
|
+
logger_1.default.trace('[OAuth2] No code or codeVerifier in payload');
|
|
75
80
|
throw new exceptions_1.InvalidCredentialsException();
|
|
76
81
|
}
|
|
77
82
|
let tokenSet;
|
|
@@ -112,6 +117,7 @@ class OAuth2AuthDriver extends local_1.LocalAuthDriver {
|
|
|
112
117
|
}
|
|
113
118
|
// Is public registration allowed?
|
|
114
119
|
if (!allowPublicRegistration) {
|
|
120
|
+
logger_1.default.trace(`[OAuth2] User doesn't exist, and public registration not allowed for provider "${this.config.provider}"`);
|
|
115
121
|
throw new exceptions_1.InvalidCredentialsException();
|
|
116
122
|
}
|
|
117
123
|
await this.usersService.createOne({
|
|
@@ -124,9 +130,9 @@ class OAuth2AuthDriver extends local_1.LocalAuthDriver {
|
|
|
124
130
|
return (await this.fetchUserId(identifier));
|
|
125
131
|
}
|
|
126
132
|
async login(user) {
|
|
127
|
-
return this.refresh(user
|
|
133
|
+
return this.refresh(user);
|
|
128
134
|
}
|
|
129
|
-
async refresh(user
|
|
135
|
+
async refresh(user) {
|
|
130
136
|
let authData = user.auth_data;
|
|
131
137
|
if (typeof authData === 'string') {
|
|
132
138
|
try {
|
|
@@ -136,15 +142,19 @@ class OAuth2AuthDriver extends local_1.LocalAuthDriver {
|
|
|
136
142
|
logger_1.default.warn(`Session data isn't valid JSON: ${authData}`);
|
|
137
143
|
}
|
|
138
144
|
}
|
|
139
|
-
if (
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
145
|
+
if (authData === null || authData === void 0 ? void 0 : authData.refreshToken) {
|
|
146
|
+
try {
|
|
147
|
+
const tokenSet = await this.client.refresh(authData.refreshToken);
|
|
148
|
+
// Update user refreshToken if provided
|
|
149
|
+
if (tokenSet.refresh_token) {
|
|
150
|
+
await this.usersService.updateOne(user.id, {
|
|
151
|
+
auth_data: JSON.stringify({ refreshToken: tokenSet.refresh_token }),
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
catch (e) {
|
|
156
|
+
throw handleError(e);
|
|
157
|
+
}
|
|
148
158
|
}
|
|
149
159
|
}
|
|
150
160
|
}
|
|
@@ -152,19 +162,23 @@ exports.OAuth2AuthDriver = OAuth2AuthDriver;
|
|
|
152
162
|
const handleError = (e) => {
|
|
153
163
|
if (e instanceof openid_client_1.errors.OPError) {
|
|
154
164
|
if (e.error === 'invalid_grant') {
|
|
165
|
+
logger_1.default.trace(e, `[OAuth2] Invalid grant.`);
|
|
155
166
|
// Invalid token
|
|
156
|
-
return new exceptions_1.
|
|
167
|
+
return new exceptions_1.InvalidTokenException();
|
|
157
168
|
}
|
|
169
|
+
logger_1.default.trace(e, `[OAuth2] Unknown OP error.`);
|
|
158
170
|
// Server response error
|
|
159
171
|
return new exceptions_1.ServiceUnavailableException('Service returned unexpected response', {
|
|
160
|
-
service: '
|
|
172
|
+
service: 'oauth2',
|
|
161
173
|
message: e.error_description,
|
|
162
174
|
});
|
|
163
175
|
}
|
|
164
176
|
else if (e instanceof openid_client_1.errors.RPError) {
|
|
165
177
|
// Internal client error
|
|
178
|
+
logger_1.default.trace(e, `[OAuth2] Unknown RP error.`);
|
|
166
179
|
return new exceptions_1.InvalidCredentialsException();
|
|
167
180
|
}
|
|
181
|
+
logger_1.default.trace(e, `[OAuth2] Unknown error.`);
|
|
168
182
|
return e;
|
|
169
183
|
};
|
|
170
184
|
function createOAuth2AuthRouter(providerName) {
|
|
@@ -172,7 +186,8 @@ function createOAuth2AuthRouter(providerName) {
|
|
|
172
186
|
router.get('/', (req, res) => {
|
|
173
187
|
const provider = (0, auth_1.getAuthProvider)(providerName);
|
|
174
188
|
const codeVerifier = provider.generateCodeVerifier();
|
|
175
|
-
const
|
|
189
|
+
const prompt = !!req.query.prompt;
|
|
190
|
+
const token = jsonwebtoken_1.default.sign({ verifier: codeVerifier, redirect: req.query.redirect, prompt }, env_1.default.SECRET, {
|
|
176
191
|
expiresIn: '5m',
|
|
177
192
|
issuer: 'directus',
|
|
178
193
|
});
|
|
@@ -180,7 +195,7 @@ function createOAuth2AuthRouter(providerName) {
|
|
|
180
195
|
httpOnly: true,
|
|
181
196
|
sameSite: 'lax',
|
|
182
197
|
});
|
|
183
|
-
return res.redirect(provider.generateAuthUrl(codeVerifier));
|
|
198
|
+
return res.redirect(provider.generateAuthUrl(codeVerifier, prompt));
|
|
184
199
|
}, respond_1.respond);
|
|
185
200
|
router.get('/callback', (0, async_handler_1.default)(async (req, res, next) => {
|
|
186
201
|
var _a;
|
|
@@ -189,9 +204,10 @@ function createOAuth2AuthRouter(providerName) {
|
|
|
189
204
|
tokenData = jsonwebtoken_1.default.verify(req.cookies[`oauth2.${providerName}`], env_1.default.SECRET, { issuer: 'directus' });
|
|
190
205
|
}
|
|
191
206
|
catch (e) {
|
|
207
|
+
logger_1.default.warn(e, `[OAuth2] Couldn't verify OAuth2 cookie`);
|
|
192
208
|
throw new exceptions_1.InvalidCredentialsException();
|
|
193
209
|
}
|
|
194
|
-
const { verifier, redirect } = tokenData;
|
|
210
|
+
const { verifier, redirect, prompt } = tokenData;
|
|
195
211
|
const authenticationService = new services_1.AuthenticationService({
|
|
196
212
|
accountability: {
|
|
197
213
|
ip: req.ip,
|
|
@@ -204,7 +220,7 @@ function createOAuth2AuthRouter(providerName) {
|
|
|
204
220
|
try {
|
|
205
221
|
res.clearCookie(`oauth2.${providerName}`);
|
|
206
222
|
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)}`);
|
|
223
|
+
logger_1.default.warn(`[OAuth2]Couldn't extract OAuth2 code or state from query: ${JSON.stringify(req.query)}`);
|
|
208
224
|
}
|
|
209
225
|
authResponse = await authenticationService.login(providerName, {
|
|
210
226
|
code: req.query.code,
|
|
@@ -213,7 +229,10 @@ function createOAuth2AuthRouter(providerName) {
|
|
|
213
229
|
});
|
|
214
230
|
}
|
|
215
231
|
catch (error) {
|
|
216
|
-
|
|
232
|
+
// Prompt user for a new refresh_token if invalidated
|
|
233
|
+
if (error instanceof exceptions_1.InvalidTokenException && !prompt) {
|
|
234
|
+
return res.redirect(`./?${redirect ? `redirect=${redirect}&` : ''}prompt=true`);
|
|
235
|
+
}
|
|
217
236
|
if (redirect) {
|
|
218
237
|
let reason = 'UNKNOWN_EXCEPTION';
|
|
219
238
|
if (error instanceof exceptions_1.ServiceUnavailableException) {
|
|
@@ -222,8 +241,15 @@ function createOAuth2AuthRouter(providerName) {
|
|
|
222
241
|
else if (error instanceof exceptions_1.InvalidCredentialsException) {
|
|
223
242
|
reason = 'INVALID_USER';
|
|
224
243
|
}
|
|
244
|
+
else if (error instanceof exceptions_1.InvalidTokenException) {
|
|
245
|
+
reason = 'INVALID_TOKEN';
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
logger_1.default.warn(error, `[OAuth2] Unexpected error during OAuth2 login`);
|
|
249
|
+
}
|
|
225
250
|
return res.redirect(`${redirect.split('?')[0]}?reason=${reason}`);
|
|
226
251
|
}
|
|
252
|
+
logger_1.default.warn(error, `[OAuth2] Unexpected error during OAuth2 login`);
|
|
227
253
|
throw error;
|
|
228
254
|
}
|
|
229
255
|
const { accessToken, refreshToken, expires } = authResponse;
|
|
@@ -2,7 +2,7 @@ import { Router } from 'express';
|
|
|
2
2
|
import { Client } from 'openid-client';
|
|
3
3
|
import { LocalAuthDriver } from './local';
|
|
4
4
|
import { UsersService } from '../../services';
|
|
5
|
-
import { AuthDriverOptions, User
|
|
5
|
+
import { AuthDriverOptions, User } from '../../types';
|
|
6
6
|
export declare class OpenIDAuthDriver extends LocalAuthDriver {
|
|
7
7
|
client: Promise<Client>;
|
|
8
8
|
redirectUrl: string;
|
|
@@ -10,10 +10,10 @@ export declare class OpenIDAuthDriver extends LocalAuthDriver {
|
|
|
10
10
|
config: Record<string, any>;
|
|
11
11
|
constructor(options: AuthDriverOptions, config: Record<string, any>);
|
|
12
12
|
generateCodeVerifier(): string;
|
|
13
|
-
generateAuthUrl(codeVerifier: string): Promise<string>;
|
|
13
|
+
generateAuthUrl(codeVerifier: string, prompt?: boolean): Promise<string>;
|
|
14
14
|
private fetchUserId;
|
|
15
15
|
getUserID(payload: Record<string, any>): Promise<string>;
|
|
16
|
-
login(user: User): Promise<
|
|
17
|
-
refresh(user: User
|
|
16
|
+
login(user: User): Promise<void>;
|
|
17
|
+
refresh(user: User): Promise<void>;
|
|
18
18
|
}
|
|
19
19
|
export declare function createOpenIDAuthRouter(providerName: string): Router;
|
|
@@ -50,18 +50,21 @@ class OpenIDAuthDriver extends local_1.LocalAuthDriver {
|
|
|
50
50
|
generateCodeVerifier() {
|
|
51
51
|
return openid_client_1.generators.codeVerifier();
|
|
52
52
|
}
|
|
53
|
-
async generateAuthUrl(codeVerifier) {
|
|
53
|
+
async generateAuthUrl(codeVerifier, prompt = false) {
|
|
54
54
|
var _a;
|
|
55
55
|
try {
|
|
56
56
|
const client = await this.client;
|
|
57
57
|
const codeChallenge = openid_client_1.generators.codeChallenge(codeVerifier);
|
|
58
|
+
const paramsConfig = typeof this.config.params === 'object' ? this.config.params : {};
|
|
58
59
|
return client.authorizationUrl({
|
|
59
60
|
scope: (_a = this.config.scope) !== null && _a !== void 0 ? _a : 'openid profile email',
|
|
61
|
+
access_type: 'offline',
|
|
62
|
+
prompt: prompt ? 'consent' : undefined,
|
|
63
|
+
...paramsConfig,
|
|
60
64
|
code_challenge: codeChallenge,
|
|
61
65
|
code_challenge_method: 'S256',
|
|
62
66
|
// Some providers require state even with PKCE
|
|
63
67
|
state: codeChallenge,
|
|
64
|
-
access_type: 'offline',
|
|
65
68
|
});
|
|
66
69
|
}
|
|
67
70
|
catch (e) {
|
|
@@ -132,9 +135,9 @@ class OpenIDAuthDriver extends local_1.LocalAuthDriver {
|
|
|
132
135
|
return (await this.fetchUserId(identifier));
|
|
133
136
|
}
|
|
134
137
|
async login(user) {
|
|
135
|
-
return this.refresh(user
|
|
138
|
+
return this.refresh(user);
|
|
136
139
|
}
|
|
137
|
-
async refresh(user
|
|
140
|
+
async refresh(user) {
|
|
138
141
|
let authData = user.auth_data;
|
|
139
142
|
if (typeof authData === 'string') {
|
|
140
143
|
try {
|
|
@@ -144,16 +147,20 @@ class OpenIDAuthDriver extends local_1.LocalAuthDriver {
|
|
|
144
147
|
logger_1.default.warn(`Session data isn't valid JSON: ${authData}`);
|
|
145
148
|
}
|
|
146
149
|
}
|
|
147
|
-
if (
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
150
|
+
if (authData === null || authData === void 0 ? void 0 : authData.refreshToken) {
|
|
151
|
+
try {
|
|
152
|
+
const client = await this.client;
|
|
153
|
+
const tokenSet = await client.refresh(authData.refreshToken);
|
|
154
|
+
// Update user refreshToken if provided
|
|
155
|
+
if (tokenSet.refresh_token) {
|
|
156
|
+
await this.usersService.updateOne(user.id, {
|
|
157
|
+
auth_data: JSON.stringify({ refreshToken: tokenSet.refresh_token }),
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
catch (e) {
|
|
162
|
+
throw handleError(e);
|
|
163
|
+
}
|
|
157
164
|
}
|
|
158
165
|
}
|
|
159
166
|
}
|
|
@@ -162,7 +169,7 @@ const handleError = (e) => {
|
|
|
162
169
|
if (e instanceof openid_client_1.errors.OPError) {
|
|
163
170
|
if (e.error === 'invalid_grant') {
|
|
164
171
|
// Invalid token
|
|
165
|
-
return new exceptions_1.
|
|
172
|
+
return new exceptions_1.InvalidTokenException();
|
|
166
173
|
}
|
|
167
174
|
// Server response error
|
|
168
175
|
return new exceptions_1.ServiceUnavailableException('Service returned unexpected response', {
|
|
@@ -181,7 +188,8 @@ function createOpenIDAuthRouter(providerName) {
|
|
|
181
188
|
router.get('/', (0, async_handler_1.default)(async (req, res) => {
|
|
182
189
|
const provider = (0, auth_1.getAuthProvider)(providerName);
|
|
183
190
|
const codeVerifier = provider.generateCodeVerifier();
|
|
184
|
-
const
|
|
191
|
+
const prompt = !!req.query.prompt;
|
|
192
|
+
const token = jsonwebtoken_1.default.sign({ verifier: codeVerifier, redirect: req.query.redirect, prompt }, env_1.default.SECRET, {
|
|
185
193
|
expiresIn: '5m',
|
|
186
194
|
issuer: 'directus',
|
|
187
195
|
});
|
|
@@ -189,7 +197,7 @@ function createOpenIDAuthRouter(providerName) {
|
|
|
189
197
|
httpOnly: true,
|
|
190
198
|
sameSite: 'lax',
|
|
191
199
|
});
|
|
192
|
-
return res.redirect(await provider.generateAuthUrl(codeVerifier));
|
|
200
|
+
return res.redirect(await provider.generateAuthUrl(codeVerifier, prompt));
|
|
193
201
|
}), respond_1.respond);
|
|
194
202
|
router.get('/callback', (0, async_handler_1.default)(async (req, res, next) => {
|
|
195
203
|
var _a;
|
|
@@ -198,9 +206,10 @@ function createOpenIDAuthRouter(providerName) {
|
|
|
198
206
|
tokenData = jsonwebtoken_1.default.verify(req.cookies[`openid.${providerName}`], env_1.default.SECRET, { issuer: 'directus' });
|
|
199
207
|
}
|
|
200
208
|
catch (e) {
|
|
209
|
+
logger_1.default.warn(e, `[OpenID] Couldn't verify OpenID cookie`);
|
|
201
210
|
throw new exceptions_1.InvalidCredentialsException();
|
|
202
211
|
}
|
|
203
|
-
const { verifier, redirect } = tokenData;
|
|
212
|
+
const { verifier, redirect, prompt } = tokenData;
|
|
204
213
|
const authenticationService = new services_1.AuthenticationService({
|
|
205
214
|
accountability: {
|
|
206
215
|
ip: req.ip,
|
|
@@ -213,7 +222,7 @@ function createOpenIDAuthRouter(providerName) {
|
|
|
213
222
|
try {
|
|
214
223
|
res.clearCookie(`openid.${providerName}`);
|
|
215
224
|
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)}`);
|
|
225
|
+
logger_1.default.warn(`[OpenID] Couldn't extract OpenID code or state from query: ${JSON.stringify(req.query)}`);
|
|
217
226
|
}
|
|
218
227
|
authResponse = await authenticationService.login(providerName, {
|
|
219
228
|
code: req.query.code,
|
|
@@ -222,6 +231,10 @@ function createOpenIDAuthRouter(providerName) {
|
|
|
222
231
|
});
|
|
223
232
|
}
|
|
224
233
|
catch (error) {
|
|
234
|
+
// Prompt user for a new refresh_token if invalidated
|
|
235
|
+
if (error instanceof exceptions_1.InvalidTokenException && !prompt) {
|
|
236
|
+
return res.redirect(`./?${redirect ? `redirect=${redirect}&` : ''}prompt=true`);
|
|
237
|
+
}
|
|
225
238
|
logger_1.default.warn(error);
|
|
226
239
|
if (redirect) {
|
|
227
240
|
let reason = 'UNKNOWN_EXCEPTION';
|
|
@@ -231,6 +244,9 @@ function createOpenIDAuthRouter(providerName) {
|
|
|
231
244
|
else if (error instanceof exceptions_1.InvalidCredentialsException) {
|
|
232
245
|
reason = 'INVALID_USER';
|
|
233
246
|
}
|
|
247
|
+
else if (error instanceof exceptions_1.InvalidTokenException) {
|
|
248
|
+
reason = 'INVALID_TOKEN';
|
|
249
|
+
}
|
|
234
250
|
return res.redirect(`${redirect.split('?')[0]}?reason=${reason}`);
|
|
235
251
|
}
|
|
236
252
|
throw error;
|
|
@@ -30,6 +30,7 @@ const logger_1 = __importDefault(require("../../../logger"));
|
|
|
30
30
|
const get_schema_1 = require("../../../utils/get-schema");
|
|
31
31
|
const services_1 = require("../../../services");
|
|
32
32
|
const database_1 = __importStar(require("../../../database"));
|
|
33
|
+
const defaults_1 = require("../../utils/defaults");
|
|
33
34
|
async function bootstrap({ skipAdminInit }) {
|
|
34
35
|
logger_1.default.info('Initializing bootstrap...');
|
|
35
36
|
const database = (0, database_1.default)();
|
|
@@ -75,7 +76,7 @@ async function waitForDatabase(database) {
|
|
|
75
76
|
async function createDefaultAdmin(schema) {
|
|
76
77
|
logger_1.default.info('Setting up first admin role...');
|
|
77
78
|
const rolesService = new services_1.RolesService({ schema });
|
|
78
|
-
const role = await rolesService.createOne(
|
|
79
|
+
const role = await rolesService.createOne(defaults_1.defaultAdminRole);
|
|
79
80
|
logger_1.default.info('Adding first admin user...');
|
|
80
81
|
const usersService = new services_1.UsersService({ schema });
|
|
81
82
|
let adminEmail = env_1.default.ADMIN_EMAIL;
|
|
@@ -88,5 +89,5 @@ async function createDefaultAdmin(schema) {
|
|
|
88
89
|
adminPassword = (0, nanoid_1.nanoid)(12);
|
|
89
90
|
logger_1.default.info(`No admin password provided. Defaulting to "${adminPassword}"`);
|
|
90
91
|
}
|
|
91
|
-
await usersService.createOne({ email: adminEmail, password: adminPassword, role });
|
|
92
|
+
await usersService.createOne({ email: adminEmail, password: adminPassword, role, ...defaults_1.defaultAdminUser });
|
|
92
93
|
}
|