UNPKG

@directus/api

Version:

Directus is a real-time API and App dashboard for managing SQL database content

166 lines (164 loc) 7.42 kB
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 };