You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
52 lines
1.5 KiB
52 lines
1.5 KiB
import { z } from "zod";
|
|
import { authService } from "../service/auth";
|
|
import { checkRateLimit } from "../service/auth/lib/rate-limit";
|
|
|
|
const LoginSchema = z.object({
|
|
email: z.string().email(),
|
|
password: z.string(),
|
|
});
|
|
|
|
export default defineEventHandler(async (event) => {
|
|
const ip = getHeader(event, "x-forwarded-for") ?? "unknown";
|
|
const userAgent = getHeader(event, "user-agent") ?? undefined;
|
|
|
|
const { allowed } = checkRateLimit(ip);
|
|
if (!allowed) {
|
|
setResponseStatus(event, 429);
|
|
return { error: { code: "RATE_LIMITED", message: "操作过于频繁,请稍后再试" } };
|
|
}
|
|
|
|
const body = await readBody(event);
|
|
const parsed = LoginSchema.safeParse(body);
|
|
if (!parsed.success) {
|
|
setResponseStatus(event, 400);
|
|
return { error: { code: "BAD_REQUEST", message: "参数错误" } };
|
|
}
|
|
|
|
try {
|
|
const { user, accessToken, refreshToken } = await authService.login({
|
|
...parsed.data,
|
|
ip,
|
|
userAgent,
|
|
});
|
|
|
|
setCookie(event, "refresh_token", refreshToken, {
|
|
httpOnly: true,
|
|
secure: process.env.NODE_ENV === "production",
|
|
sameSite: "strict",
|
|
maxAge: 7 * 24 * 60 * 60,
|
|
path: "/",
|
|
});
|
|
|
|
return { user, accessToken };
|
|
} catch (err: unknown) {
|
|
const e = err as { code?: string; message?: string };
|
|
const statusMap: Record<string, number> = {
|
|
ACCOUNT_LOCKED: 423,
|
|
INVALID_CREDENTIALS: 401,
|
|
};
|
|
setResponseStatus(event, statusMap[e.code ?? "UNKNOWN"] ?? 400);
|
|
return { error: { code: e.code ?? "UNKNOWN", message: e.message ?? "登录失败" } };
|
|
}
|
|
});
|