directus

Форк
0
279 строк · 7.3 Кб
1
import { useEnv } from '@directus/env';
2
import { ErrorCode, InvalidPayloadError, isDirectusError } from '@directus/errors';
3
import type { Accountability } from '@directus/types';
4
import type { Request } from 'express';
5
import { Router } from 'express';
6
import {
7
	createLDAPAuthRouter,
8
	createLocalAuthRouter,
9
	createOAuth2AuthRouter,
10
	createOpenIDAuthRouter,
11
	createSAMLAuthRouter,
12
} from '../auth/drivers/index.js';
13
import { DEFAULT_AUTH_PROVIDER, REFRESH_COOKIE_OPTIONS, SESSION_COOKIE_OPTIONS } from '../constants.js';
14
import { useLogger } from '../logger.js';
15
import { respond } from '../middleware/respond.js';
16
import { AuthenticationService } from '../services/authentication.js';
17
import { UsersService } from '../services/users.js';
18
import type { AuthenticationMode } from '../types/auth.js';
19
import asyncHandler from '../utils/async-handler.js';
20
import { getAuthProviders } from '../utils/get-auth-providers.js';
21
import { getIPFromReq } from '../utils/get-ip-from-req.js';
22
import { getSecret } from '../utils/get-secret.js';
23
import isDirectusJWT from '../utils/is-directus-jwt.js';
24
import { verifyAccessJWT } from '../utils/jwt.js';
25

26
const router = Router();
27
const env = useEnv();
28
const logger = useLogger();
29

30
const authProviders = getAuthProviders();
31

32
for (const authProvider of authProviders) {
33
	let authRouter: Router | undefined;
34

35
	switch (authProvider.driver) {
36
		case 'local':
37
			authRouter = createLocalAuthRouter(authProvider.name);
38
			break;
39

40
		case 'oauth2':
41
			authRouter = createOAuth2AuthRouter(authProvider.name);
42
			break;
43

44
		case 'openid':
45
			authRouter = createOpenIDAuthRouter(authProvider.name);
46
			break;
47

48
		case 'ldap':
49
			authRouter = createLDAPAuthRouter(authProvider.name);
50
			break;
51

52
		case 'saml':
53
			authRouter = createSAMLAuthRouter(authProvider.name);
54
			break;
55
	}
56

57
	if (!authRouter) {
58
		logger.warn(`Couldn't create login router for auth provider "${authProvider.name}"`);
59
		continue;
60
	}
61

62
	router.use(`/login/${authProvider.name}`, authRouter);
63
}
64

65
if (!env['AUTH_DISABLE_DEFAULT']) {
66
	router.use('/login', createLocalAuthRouter(DEFAULT_AUTH_PROVIDER));
67
}
68

69
function getCurrentMode(req: Request): AuthenticationMode {
70
	if (req.body.mode) {
71
		return req.body.mode as AuthenticationMode;
72
	}
73

74
	if (req.body.refresh_token) {
75
		return 'json';
76
	}
77

78
	return 'cookie';
79
}
80

81
function getCurrentRefreshToken(req: Request, mode: AuthenticationMode): string | undefined {
82
	if (mode === 'json') {
83
		return req.body.refresh_token;
84
	}
85

86
	if (mode === 'cookie') {
87
		return req.cookies[env['REFRESH_TOKEN_COOKIE_NAME'] as string];
88
	}
89

90
	if (mode === 'session') {
91
		const token = req.cookies[env['SESSION_COOKIE_NAME'] as string];
92

93
		if (isDirectusJWT(token)) {
94
			const payload = verifyAccessJWT(token, getSecret());
95
			return payload.session;
96
		}
97
	}
98

99
	return undefined;
100
}
101

102
router.post(
103
	'/refresh',
104
	asyncHandler(async (req, res, next) => {
105
		const accountability: Accountability = {
106
			ip: getIPFromReq(req),
107
			role: null,
108
		};
109

110
		const userAgent = req.get('user-agent')?.substring(0, 1024);
111
		if (userAgent) accountability.userAgent = userAgent;
112

113
		const origin = req.get('origin');
114
		if (origin) accountability.origin = origin;
115

116
		const authenticationService = new AuthenticationService({
117
			accountability: accountability,
118
			schema: req.schema,
119
		});
120

121
		const mode = getCurrentMode(req);
122
		const currentRefreshToken = getCurrentRefreshToken(req, mode);
123

124
		if (!currentRefreshToken) {
125
			throw new InvalidPayloadError({
126
				reason: `The refresh token is required in either the payload or cookie`,
127
			});
128
		}
129

130
		const { accessToken, refreshToken, expires } = await authenticationService.refresh(currentRefreshToken, {
131
			session: mode === 'session',
132
		});
133

134
		const payload = { expires } as { expires: number; access_token?: string; refresh_token?: string };
135

136
		if (mode === 'json') {
137
			payload.refresh_token = refreshToken;
138
			payload.access_token = accessToken;
139
		}
140

141
		if (mode === 'cookie') {
142
			res.cookie(env['REFRESH_TOKEN_COOKIE_NAME'] as string, refreshToken, REFRESH_COOKIE_OPTIONS);
143
			payload.access_token = accessToken;
144
		}
145

146
		if (mode === 'session') {
147
			res.cookie(env['SESSION_COOKIE_NAME'] as string, accessToken, SESSION_COOKIE_OPTIONS);
148
		}
149

150
		res.locals['payload'] = { data: payload };
151
		return next();
152
	}),
153
	respond,
154
);
155

156
router.post(
157
	'/logout',
158
	asyncHandler(async (req, res, next) => {
159
		const accountability: Accountability = {
160
			ip: getIPFromReq(req),
161
			role: null,
162
		};
163

164
		const userAgent = req.get('user-agent')?.substring(0, 1024);
165
		if (userAgent) accountability.userAgent = userAgent;
166

167
		const origin = req.get('origin');
168
		if (origin) accountability.origin = origin;
169

170
		const authenticationService = new AuthenticationService({
171
			accountability: accountability,
172
			schema: req.schema,
173
		});
174

175
		const mode = getCurrentMode(req);
176
		const currentRefreshToken = getCurrentRefreshToken(req, mode);
177

178
		if (!currentRefreshToken) {
179
			throw new InvalidPayloadError({
180
				reason: `The refresh token is required in either the payload or cookie`,
181
			});
182
		}
183

184
		await authenticationService.logout(currentRefreshToken);
185

186
		if (req.cookies[env['REFRESH_TOKEN_COOKIE_NAME'] as string]) {
187
			res.clearCookie(env['REFRESH_TOKEN_COOKIE_NAME'] as string, REFRESH_COOKIE_OPTIONS);
188
		}
189

190
		if (req.cookies[env['SESSION_COOKIE_NAME'] as string]) {
191
			res.clearCookie(env['SESSION_COOKIE_NAME'] as string, SESSION_COOKIE_OPTIONS);
192
		}
193

194
		return next();
195
	}),
196
	respond,
197
);
198

199
router.post(
200
	'/password/request',
201
	asyncHandler(async (req, _res, next) => {
202
		if (typeof req.body.email !== 'string') {
203
			throw new InvalidPayloadError({ reason: `"email" field is required` });
204
		}
205

206
		const accountability: Accountability = {
207
			ip: getIPFromReq(req),
208
			role: null,
209
		};
210

211
		const userAgent = req.get('user-agent')?.substring(0, 1024);
212
		if (userAgent) accountability.userAgent = userAgent;
213

214
		const origin = req.get('origin');
215
		if (origin) accountability.origin = origin;
216

217
		const service = new UsersService({ accountability, schema: req.schema });
218

219
		try {
220
			await service.requestPasswordReset(req.body.email, req.body.reset_url || null);
221
			return next();
222
		} catch (err: any) {
223
			if (isDirectusError(err, ErrorCode.InvalidPayload)) {
224
				throw err;
225
			} else {
226
				logger.warn(err, `[email] ${err}`);
227
				return next();
228
			}
229
		}
230
	}),
231
	respond,
232
);
233

234
router.post(
235
	'/password/reset',
236
	asyncHandler(async (req, _res, next) => {
237
		if (typeof req.body.token !== 'string') {
238
			throw new InvalidPayloadError({ reason: `"token" field is required` });
239
		}
240

241
		if (typeof req.body.password !== 'string') {
242
			throw new InvalidPayloadError({ reason: `"password" field is required` });
243
		}
244

245
		const accountability: Accountability = {
246
			ip: getIPFromReq(req),
247
			role: null,
248
		};
249

250
		const userAgent = req.get('user-agent')?.substring(0, 1024);
251
		if (userAgent) accountability.userAgent = userAgent;
252

253
		const origin = req.get('origin');
254
		if (origin) accountability.origin = origin;
255

256
		const service = new UsersService({ accountability, schema: req.schema });
257
		await service.resetPassword(req.body.token, req.body.password);
258
		return next();
259
	}),
260
	respond,
261
);
262

263
router.get(
264
	'/',
265
	asyncHandler(async (req, res, next) => {
266
		const sessionOnly =
267
			'sessionOnly' in req.query && (req.query['sessionOnly'] === '' || Boolean(req.query['sessionOnly']));
268

269
		res.locals['payload'] = {
270
			data: getAuthProviders({ sessionOnly }),
271
			disableDefault: env['AUTH_DISABLE_DEFAULT'],
272
		};
273

274
		return next();
275
	}),
276
	respond,
277
);
278

279
export default router;
280

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.