@@ -0,0 +1,55 @@
|
||||
// server/api/auth/login.post.ts
|
||||
import type { UserLoginRequest, UserLoginResult } from "#shared/types";
|
||||
import { createUpstreamErrorResponse } from "~~/server/utils";
|
||||
|
||||
/**
|
||||
* 允许从请求体透传给上游的字段列表
|
||||
* 只有在这里声明过的字段才会被转发,其余字段一律丢弃,防止参数污染
|
||||
* 与 NewAPI 文档字段保持一致
|
||||
*/
|
||||
const LOGIN_FIELDS = ["username", "password"] as const satisfies ReadonlyArray<
|
||||
keyof UserLoginRequest
|
||||
>;
|
||||
|
||||
/**
|
||||
* POST /api/auth/login
|
||||
*
|
||||
* 登录流程:
|
||||
* 1. 解析并校验前端传来的 JSON 请求体
|
||||
* 2. 从请求体里只挑出文档允许的字段组装 payload
|
||||
* 3. 通过 newApiFetch 转发到上游 NewAPI 登录接口
|
||||
* 4. 上游响应体为空,成功时返回 { code: 0, data: null, msg: "登录成功" }
|
||||
*/
|
||||
export default defineEventHandler(async (event) => {
|
||||
// 读取并反序列化请求体;如果前端没有传 body,readBody 会返回 null
|
||||
const requestBody = await readBody<Partial<UserLoginRequest> | null>(event);
|
||||
|
||||
// 防御性校验:请求体只能是 JSON 对象或空,不接受字符串/数字等原始值
|
||||
if (requestBody !== null && typeof requestBody !== "object") {
|
||||
return createErrorResponse(400, "请求体必须是 JSON 对象");
|
||||
}
|
||||
|
||||
// 白名单过滤:只保留文档里声明的字段,且必须是字符串类型
|
||||
const payload: UserLoginRequest = {};
|
||||
for (const field of LOGIN_FIELDS) {
|
||||
const value = requestBody?.[field];
|
||||
if (typeof value === "string") {
|
||||
payload[field] = value;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// 调用上游 NewAPI 登录接口,baseURL 已在 newApiFetch 内部写死,无需再传
|
||||
await newApiFetch<UserLoginResult>("/api/user/login", {
|
||||
method: "POST",
|
||||
body: payload,
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
});
|
||||
|
||||
return createSuccessResponse(null, "登录成功");
|
||||
} catch (error) {
|
||||
return createUpstreamErrorResponse(error, "登录失败");
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user