@directus/api
Version:
Directus is a real-time API and App dashboard for managing SQL database content
166 lines (164 loc) • 7.42 kB
JavaScript
import async_handler_default from "../utils/async-handler.js";
import { useLogger } from "../logger/index.js";
import { DEFAULT_AUTH_PROVIDER, REFRESH_COOKIE_OPTIONS, SESSION_COOKIE_OPTIONS } from "../constants.js";
import { getSecret } from "../utils/get-secret.js";
import { createDefaultAccountability } from "../permissions/utils/create-default-accountability.js";
import { verifyAccessJWT } from "../utils/jwt.js";
import { getAuthProviders } from "../utils/get-auth-providers.js";
import { getEntitlementManager } from "../license/entitlements/manager.js";
import { UsersService } from "../services/users.js";
import { respond } from "../middleware/respond.js";
import { getIPFromReq } from "../utils/get-ip-from-req.js";
import { createLocalAuthRouter } from "../auth/drivers/local.js";
import { isSSOBypassAllowed } from "../license/utils/is-sso-bypass-allowed.js";
import { createOAuth2AuthRouter } from "../auth/drivers/oauth2.js";
import { createOpenIDAuthRouter } from "../auth/drivers/openid.js";
import { createLDAPAuthRouter } from "../auth/drivers/ldap.js";
import { createSAMLAuthRouter } from "../auth/drivers/saml.js";
import "../auth/drivers/index.js";
import { AuthenticationService } from "../services/authentication.js";
import isDirectusJWT from "../utils/is-directus-jwt.js";
import "../license/index.js";
import { useEnv } from "@directus/env";
import { ErrorCode, InvalidPayloadError, isDirectusError } from "@directus/errors";
import { toBoolean } from "@directus/utils";
import { Router } from "express";
//#region src/controllers/auth.ts
const router = Router();
const env = useEnv();
const logger = useLogger();
const authProviders = getAuthProviders();
for (const authProvider of authProviders) {
let authRouter;
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);
}
function getCurrentMode(req) {
if (req.body.mode) return req.body.mode;
if (req.body.refresh_token) return "json";
return "cookie";
}
function getCurrentRefreshToken(req, mode) {
if (mode === "json") return req.body.refresh_token;
if (mode === "cookie") return req.cookies[env["REFRESH_TOKEN_COOKIE_NAME"]];
if (mode === "session") {
const token = req.cookies[env["SESSION_COOKIE_NAME"]];
if (isDirectusJWT(token)) return verifyAccessJWT(token, getSecret()).session;
}
}
router.use("/login", createLocalAuthRouter(DEFAULT_AUTH_PROVIDER));
router.post("/refresh", async_handler_default(async (req, res, next) => {
const accountability = createDefaultAccountability({ ip: getIPFromReq(req) });
const userAgent = req.get("user-agent")?.substring(0, 1024);
if (userAgent) accountability.userAgent = userAgent;
const origin = req.get("origin");
if (origin) accountability.origin = origin;
const authenticationService = new AuthenticationService({
accountability,
schema: req.schema
});
const mode = getCurrentMode(req);
const currentRefreshToken = getCurrentRefreshToken(req, mode);
if (!currentRefreshToken) throw new InvalidPayloadError({ reason: `The refresh token is required in either the payload or cookie` });
const { accessToken, refreshToken, expires } = await authenticationService.refresh(currentRefreshToken, { session: mode === "session" });
const payload = { expires };
if (mode === "json") {
payload.refresh_token = refreshToken;
payload.access_token = accessToken;
}
if (mode === "cookie") {
res.cookie(env["REFRESH_TOKEN_COOKIE_NAME"], refreshToken, REFRESH_COOKIE_OPTIONS);
payload.access_token = accessToken;
}
if (mode === "session") res.cookie(env["SESSION_COOKIE_NAME"], accessToken, SESSION_COOKIE_OPTIONS);
res.locals["payload"] = { data: payload };
return next();
}), respond);
router.post("/logout", async_handler_default(async (req, res, next) => {
const accountability = createDefaultAccountability({ ip: getIPFromReq(req) });
const userAgent = req.get("user-agent")?.substring(0, 1024);
if (userAgent) accountability.userAgent = userAgent;
const origin = req.get("origin");
if (origin) accountability.origin = origin;
const authenticationService = new AuthenticationService({
accountability,
schema: req.schema
});
const currentRefreshToken = getCurrentRefreshToken(req, getCurrentMode(req));
if (!currentRefreshToken) throw new InvalidPayloadError({ reason: `The refresh token is required in either the payload or cookie` });
await authenticationService.logout(currentRefreshToken);
if (req.cookies[env["REFRESH_TOKEN_COOKIE_NAME"]]) res.clearCookie(env["REFRESH_TOKEN_COOKIE_NAME"], REFRESH_COOKIE_OPTIONS);
if (req.cookies[env["SESSION_COOKIE_NAME"]]) res.clearCookie(env["SESSION_COOKIE_NAME"], SESSION_COOKIE_OPTIONS);
return next();
}), respond);
router.post("/password/request", async_handler_default(async (req, _res, next) => {
if (typeof req.body.email !== "string") throw new InvalidPayloadError({ reason: `"email" field is required` });
const accountability = createDefaultAccountability({ ip: getIPFromReq(req) });
const userAgent = req.get("user-agent")?.substring(0, 1024);
if (userAgent) accountability.userAgent = userAgent;
const origin = req.get("origin");
if (origin) accountability.origin = origin;
const service = new UsersService({
accountability,
schema: req.schema
});
try {
await service.requestPasswordReset(req.body.email, req.body.reset_url || null);
return next();
} catch (err) {
if (isDirectusError(err, ErrorCode.InvalidPayload)) throw err;
else {
logger.warn(err, `[email] ${err}`);
return next();
}
}
}), respond);
router.post("/password/reset", async_handler_default(async (req, _res, next) => {
if (typeof req.body.token !== "string") throw new InvalidPayloadError({ reason: `"token" field is required` });
if (typeof req.body.password !== "string") throw new InvalidPayloadError({ reason: `"password" field is required` });
const accountability = createDefaultAccountability({ ip: getIPFromReq(req) });
const userAgent = req.get("user-agent")?.substring(0, 1024);
if (userAgent) accountability.userAgent = userAgent;
const origin = req.get("origin");
if (origin) accountability.origin = origin;
await new UsersService({
accountability,
schema: req.schema
}).resetPassword(req.body.token, req.body.password);
return next();
}), respond);
router.get("/", async_handler_default(async (req, res, next) => {
let providers = getAuthProviders({ sessionOnly: "sessionOnly" in req.query && (req.query["sessionOnly"] === "" || Boolean(req.query["sessionOnly"])) });
const isSSOEnabled = getEntitlementManager().isEntitled("sso_enabled");
if (providers.length > 0 && !isSSOEnabled) {
if (!await isSSOBypassAllowed()) providers = [];
}
res.locals["payload"] = {
data: providers,
disableDefault: isSSOEnabled ? toBoolean(env["AUTH_DISABLE_DEFAULT"]) : false
};
return next();
}), respond);
var auth_default = router;
//#endregion
export { auth_default as default };