forked from EduCraft/curriculum-project-hub
fix(auth): POST /auth/logout 接受任意 Content-Type
该端点不读 body,但调用方(curl -d、Postman、部分 HTTP 客户端)常给空 POST 自动带上 Content-Type。Fastify 默认只有 JSON parser,遇到别的媒体类型在解析 阶段就以 415 FST_ERR_CTP_INVALID_MEDIA_TYPE 拒掉,进不到 handler。 修法是给它一个丢弃 body 的 catch-all parser,**封装在自己的 register 作用域 内**。不能加到外层实例上:admin plugin 没有 fastify-plugin 封装,那样会让全站 每个 POST/PUT/PATCH 都接受 form-urlencoded。而 form-urlencoded 是跨站 HTML form 唯一能发出的媒体类型(application/json 会触发 CORS preflight),"只认 JSON"本身是一层 CSRF 纵深防御 —— 当前 sameSite=lax 还挡着,但不该为这个端点 全局放掉。 两处细节: - "*" 只兜没有专属 parser 的媒体类型。内建 JSON parser 优先级更高,空 body 会 被它判成 FST_ERR_CTP_EMPTY_JSON_BODY(400),故在本作用域内一并覆盖。 - 用 parseAs:"string" 让 Fastify 读完流(否则连接不释放),而非手写 payload.resume()。 前端未改 —— 原本不带 Content-Type 的发法一直是 204,是正确的。 测试 5 个 case,最后一个是护栏:断言作用域外的 POST 路由发 form-encoded 仍为 415,防止以后有人把 parser 提到外层。
This commit is contained in:
@@ -268,9 +268,30 @@ export async function registerAuthRoutes(app: FastifyInstance, config: AuthRoute
|
||||
}
|
||||
});
|
||||
|
||||
app.post("/auth/logout", async (_request, reply) => {
|
||||
reply.clearCookie(SESSION_COOKIE_NAME, { path: "/" });
|
||||
return reply.status(204).send();
|
||||
// 退出登录不读 body,但调用方(curl -d、Postman、部分 HTTP 客户端)常给空
|
||||
// POST 自动带上 Content-Type。Fastify 默认只有 JSON parser,遇到别的媒体类型
|
||||
// 会在解析阶段以 415 拒掉,进不到 handler —— 对一个"无输入"的端点没有意义。
|
||||
//
|
||||
// 这里用 register 起一个封装作用域,catch-all parser 只在其中生效。
|
||||
// **不要**把 parser 加到外层 app 上:admin plugin 没有 fastify-plugin 封装,
|
||||
// 那样会让全站每个 POST/PUT/PATCH 都接受 form-urlencoded。而 form-urlencoded
|
||||
// 是跨站 HTML form 唯一能发出的媒体类型(application/json 会触发 CORS
|
||||
// preflight),"只认 JSON"本身是一层 CSRF 纵深防御,不能为了这个端点全局放掉。
|
||||
await app.register(async (scope) => {
|
||||
// parseAs:"string" 让 Fastify 负责读完流(否则连接不释放),这里直接丢掉内容
|
||||
// —— 该端点不接受任何输入。
|
||||
// "*" 只兜没有专属 parser 的媒体类型;内建 JSON parser 优先级更高,空 body
|
||||
// 会被它判成 FST_ERR_CTP_EMPTY_JSON_BODY(400),所以要在本作用域内覆盖掉。
|
||||
for (const mediaType of ["*", "application/json"]) {
|
||||
scope.addContentTypeParser(mediaType, { parseAs: "string" }, (_request, _body, done) => {
|
||||
done(null, undefined);
|
||||
});
|
||||
}
|
||||
|
||||
scope.post("/auth/logout", async (_request, reply) => {
|
||||
reply.clearCookie(SESSION_COOKIE_NAME, { path: "/" });
|
||||
return reply.status(204).send();
|
||||
});
|
||||
});
|
||||
|
||||
app.get("/auth/feishu/complete", async (request, reply) => {
|
||||
|
||||
Reference in New Issue
Block a user