Дать обработчику увидеть user, которого добавил в запрос auth-middleware
Auth-middleware проверяет токен и кладёт в запрос req.user. Обработчики ниже читают req.user, и сейчас он типизирован как any — или, хуже, проект слил объявление user в каждый Express.Request, и обработчики без авторизации тоже считают, что он там есть.
Требования: реализуйте withAuth так, чтобы обёрнутый обработчик видел req.user как User — обязательный, а не необязательный, — а обычный необёрнутый обработчик не видел user вовсе. Никакого глобального дополнения типов, никакого any, никаких приведений внутри обработчика.
import type { Request, RequestHandler, Response } from "express";
interface User { id: string; role: "admin" | "user" }
type AuthedRequest = Request & { user: User };
function withAuth(
handler: (req: AuthedRequest, res: Response) => void,
): RequestHandler {
// ваш код здесь
}
Допишите реализацию.
Не дополняйте глобальный Request. Внедрённое свойство — это пересечение Request & { user: User }. withAuth возвращает обычный RequestHandler, который кладёт user и зовёт внутренний обработчик с суженным запросом: только он видит user, и видит обязательным.
- ✗Глобально дополнять
Request, из-за чего каждый обработчик верит в наличиеuser - ✗Делать
userнеобязательным и сыпать утверждения не-null в каждом обработчике - ✗Регистрировать в роутере обработчик, требующий
AuthedRequest, напрямую
- →Как сложить две такие обёртки, чтобы обработчик видел и
user, иtenant? - →Почему глобальное дополнение ослабляет гарантию даже при необязательном свойстве?
Решение
import type { Request, RequestHandler, Response } from "express";
interface User { id: string; role: "admin" | "user" }
type AuthedRequest = Request & { user: User };
function withAuth(
handler: (req: AuthedRequest, res: Response) => void,
): RequestHandler {
return (req, res, next) => {
const user = verifyToken(req.header("authorization"));
if (!user) {
res.status(401).json({ error: "unauthorized" });
return;
}
const authed = Object.assign(req, { user }) as AuthedRequest; // ← единственное приведение
try {
handler(authed, res);
} catch (err) {
next(err);
}
};
}
Использование:
app.get("/me", withAuth((req, res) => {
res.json({ id: req.user.id }); // user: User — обязательный, без ?. и без !
}));
app.get("/health", (req, res) => {
req.user;
// ~~~~ Property 'user' does not exist on type 'Request' — и правильно
});
Почему не глобальное дополнение
declare global { namespace Express { interface Request { user?: User } } } добавляет user каждому запросу в проекте. Обработчик без авторизации тоже начинает его «видеть», а необязательность лечится россыпью req.user! — то есть тем же приведением, только размазанным по всему коду.
Обёртка переворачивает это: контекст живёт в типе того обработчика, который его получил, а небезопасное место ровно одно — строка Object.assign, где значение действительно кладут в объект.