mirror of
https://github.com/directus/directus.git
synced 2026-01-27 22:57:56 -05:00
* new saml branch * put saml info back in * put saml info back in * clean up code * validate saml config * validate schema * Add saml auth flow tests * use RelayState for redirects * Update tests for RelayState * Fix linting * remove validateMeta as samlify does it already * Fix linting * change catch on login * Update api/src/auth/drivers/saml.ts Co-authored-by: Aiden Foxx <aiden.foxx.mail@gmail.com> * remove login since not needed here * clear cookie if set on logout * empty login method * invalidate logout in db * if relayState and login failed, redirect back with a reason * Cleanup linter warnings * Remove range from packages * Opinions opinions opinions opinions Just a couple personal opinion cleanup pieces Co-authored-by: ian <licitdev@gmail.com> Co-authored-by: Aiden Foxx <aiden.foxx.mail@gmail.com> Co-authored-by: rijkvanzanten <rijkvanzanten@me.com>
210 lines
5.2 KiB
TypeScript
210 lines
5.2 KiB
TypeScript
import { Router } from 'express';
|
|
import env from '../env';
|
|
import { InvalidPayloadException } from '../exceptions';
|
|
import { respond } from '../middleware/respond';
|
|
import { AuthenticationService, UsersService } from '../services';
|
|
import asyncHandler from '../utils/async-handler';
|
|
import { getAuthProviders } from '../utils/get-auth-providers';
|
|
import logger from '../logger';
|
|
import {
|
|
createLocalAuthRouter,
|
|
createOAuth2AuthRouter,
|
|
createOpenIDAuthRouter,
|
|
createLDAPAuthRouter,
|
|
createSAMLAuthRouter,
|
|
} from '../auth/drivers';
|
|
import { DEFAULT_AUTH_PROVIDER } from '../constants';
|
|
import { getIPFromReq } from '../utils/get-ip-from-req';
|
|
import { COOKIE_OPTIONS } from '../constants';
|
|
|
|
const router = Router();
|
|
|
|
const authProviders = getAuthProviders();
|
|
|
|
for (const authProvider of authProviders) {
|
|
let authRouter: Router | undefined;
|
|
|
|
switch (authProvider.driver) {
|
|
case 'local':
|
|
authRouter = createLocalAuthRouter(authProvider.name);
|
|
break;
|
|
|
|
case 'oauth2':
|
|
authRouter = createOAuth2AuthRouter(authProvider.name);
|
|
break;
|
|
|
|
case 'openid':
|
|
authRouter = createOpenIDAuthRouter(authProvider.name);
|
|
break;
|
|
|
|
case 'ldap':
|
|
authRouter = createLDAPAuthRouter(authProvider.name);
|
|
break;
|
|
|
|
case 'saml':
|
|
authRouter = createSAMLAuthRouter(authProvider.name);
|
|
break;
|
|
}
|
|
|
|
if (!authRouter) {
|
|
logger.warn(`Couldn't create login router for auth provider "${authProvider.name}"`);
|
|
continue;
|
|
}
|
|
|
|
router.use(`/login/${authProvider.name}`, authRouter);
|
|
}
|
|
|
|
if (!env.AUTH_DISABLE_DEFAULT) {
|
|
router.use('/login', createLocalAuthRouter(DEFAULT_AUTH_PROVIDER));
|
|
}
|
|
|
|
router.post(
|
|
'/refresh',
|
|
asyncHandler(async (req, res, next) => {
|
|
const accountability = {
|
|
ip: getIPFromReq(req),
|
|
userAgent: req.get('user-agent'),
|
|
origin: req.get('origin'),
|
|
role: null,
|
|
};
|
|
|
|
const authenticationService = new AuthenticationService({
|
|
accountability: accountability,
|
|
schema: req.schema,
|
|
});
|
|
|
|
const currentRefreshToken = req.body.refresh_token || req.cookies[env.REFRESH_TOKEN_COOKIE_NAME];
|
|
|
|
if (!currentRefreshToken) {
|
|
throw new InvalidPayloadException(`"refresh_token" is required in either the JSON payload or Cookie`);
|
|
}
|
|
|
|
const mode: 'json' | 'cookie' = req.body.mode || (req.body.refresh_token ? 'json' : 'cookie');
|
|
|
|
const { accessToken, refreshToken, expires } = await authenticationService.refresh(currentRefreshToken);
|
|
|
|
const payload = {
|
|
data: { access_token: accessToken, expires },
|
|
} as Record<string, Record<string, any>>;
|
|
|
|
if (mode === 'json') {
|
|
payload.data.refresh_token = refreshToken;
|
|
}
|
|
|
|
if (mode === 'cookie') {
|
|
res.cookie(env.REFRESH_TOKEN_COOKIE_NAME, refreshToken, COOKIE_OPTIONS);
|
|
}
|
|
|
|
res.locals.payload = payload;
|
|
return next();
|
|
}),
|
|
respond
|
|
);
|
|
|
|
router.post(
|
|
'/logout',
|
|
asyncHandler(async (req, res, next) => {
|
|
const accountability = {
|
|
ip: getIPFromReq(req),
|
|
userAgent: req.get('user-agent'),
|
|
origin: req.get('origin'),
|
|
role: null,
|
|
};
|
|
|
|
const authenticationService = new AuthenticationService({
|
|
accountability: accountability,
|
|
schema: req.schema,
|
|
});
|
|
|
|
const currentRefreshToken = req.body.refresh_token || req.cookies[env.REFRESH_TOKEN_COOKIE_NAME];
|
|
|
|
if (!currentRefreshToken) {
|
|
throw new InvalidPayloadException(`"refresh_token" is required in either the JSON payload or Cookie`);
|
|
}
|
|
|
|
await authenticationService.logout(currentRefreshToken);
|
|
|
|
if (req.cookies[env.REFRESH_TOKEN_COOKIE_NAME]) {
|
|
res.clearCookie(env.REFRESH_TOKEN_COOKIE_NAME, {
|
|
httpOnly: true,
|
|
domain: env.REFRESH_TOKEN_COOKIE_DOMAIN,
|
|
secure: env.REFRESH_TOKEN_COOKIE_SECURE ?? false,
|
|
sameSite: (env.REFRESH_TOKEN_COOKIE_SAME_SITE as 'lax' | 'strict' | 'none') || 'strict',
|
|
});
|
|
}
|
|
|
|
return next();
|
|
}),
|
|
respond
|
|
);
|
|
|
|
router.post(
|
|
'/password/request',
|
|
asyncHandler(async (req, res, next) => {
|
|
if (typeof req.body.email !== 'string') {
|
|
throw new InvalidPayloadException(`"email" field is required.`);
|
|
}
|
|
|
|
const accountability = {
|
|
ip: getIPFromReq(req),
|
|
userAgent: req.get('user-agent'),
|
|
origin: req.get('origin'),
|
|
role: null,
|
|
};
|
|
|
|
const service = new UsersService({ accountability, schema: req.schema });
|
|
|
|
try {
|
|
await service.requestPasswordReset(req.body.email, req.body.reset_url || null);
|
|
return next();
|
|
} catch (err: any) {
|
|
if (err instanceof InvalidPayloadException) {
|
|
throw err;
|
|
} else {
|
|
logger.warn(err, `[email] ${err}`);
|
|
return next();
|
|
}
|
|
}
|
|
}),
|
|
respond
|
|
);
|
|
|
|
router.post(
|
|
'/password/reset',
|
|
asyncHandler(async (req, res, next) => {
|
|
if (typeof req.body.token !== 'string') {
|
|
throw new InvalidPayloadException(`"token" field is required.`);
|
|
}
|
|
|
|
if (typeof req.body.password !== 'string') {
|
|
throw new InvalidPayloadException(`"password" field is required.`);
|
|
}
|
|
|
|
const accountability = {
|
|
ip: getIPFromReq(req),
|
|
userAgent: req.get('user-agent'),
|
|
origin: req.get('origin'),
|
|
role: null,
|
|
};
|
|
|
|
const service = new UsersService({ accountability, schema: req.schema });
|
|
await service.resetPassword(req.body.token, req.body.password);
|
|
return next();
|
|
}),
|
|
respond
|
|
);
|
|
|
|
router.get(
|
|
'/',
|
|
asyncHandler(async (req, res, next) => {
|
|
res.locals.payload = {
|
|
data: getAuthProviders(),
|
|
disableDefault: env.AUTH_DISABLE_DEFAULT,
|
|
};
|
|
return next();
|
|
}),
|
|
respond
|
|
);
|
|
|
|
export default router;
|