Browse Source

feat(auth): add auth API routes - register/login/logout/refresh/me

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
npmrun 1 week ago
parent
commit
9e55ec8f57
  1. 52
      server/api/auth/login.post.ts
  2. 12
      server/api/auth/logout.post.ts
  3. 37
      server/api/auth/me.get.ts
  4. 27
      server/api/auth/refresh.post.ts
  5. 41
      server/api/auth/register.post.ts

52
server/api/auth/login.post.ts

@ -0,0 +1,52 @@
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 ?? "登录失败" } };
}
});

12
server/api/auth/logout.post.ts

@ -0,0 +1,12 @@
import { authService } from "../service/auth";
export default defineEventHandler(async (event) => {
const refreshToken = getCookie(event, "refresh_token");
if (refreshToken) {
await authService.logout(refreshToken);
}
deleteCookie(event, "refresh_token", { path: "/" });
return { success: true };
});

37
server/api/auth/me.get.ts

@ -0,0 +1,37 @@
import { verifyAccessToken } from "../service/auth/lib/jwt";
import { dbGlobal } from "@/drizzle-pkg/lib/db";
import { users } from "@/drizzle-pkg/lib/schema/auth";
import { eq } from "drizzle-orm";
export default defineEventHandler(async (event) => {
const accessToken = getHeader(event, "authorization")?.replace("Bearer ", "");
if (!accessToken) {
setResponseStatus(event, 401);
return { error: { code: "TOKEN_EXPIRED", message: "未登录" } };
}
const payload = await verifyAccessToken(accessToken);
if (!payload) {
setResponseStatus(event, 401);
return { error: { code: "TOKEN_EXPIRED", message: "Token 无效" } };
}
const [user] = await dbGlobal
.select({
id: users.id,
email: users.email,
username: users.username,
role: users.role,
status: users.status,
})
.from(users)
.where(eq(users.id, payload.userId))
.limit(1);
if (!user) {
setResponseStatus(event, 404);
return { error: { code: "NOT_FOUND", message: "用户不存在" } };
}
return { user };
});

27
server/api/auth/refresh.post.ts

@ -0,0 +1,27 @@
import { authService } from "../service/auth";
export default defineEventHandler(async (event) => {
const refreshToken = getCookie(event, "refresh_token");
if (!refreshToken) {
setResponseStatus(event, 401);
return { error: { code: "TOKEN_EXPIRED", message: "未登录" } };
}
try {
const { accessToken, newRefreshToken } = await authService.refreshToken(refreshToken);
setCookie(event, "refresh_token", newRefreshToken, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "strict",
maxAge: 7 * 24 * 60 * 60,
path: "/",
});
return { accessToken };
} catch (err: unknown) {
const e = err as { code?: string; message?: string };
setResponseStatus(event, 401);
return { error: { code: e.code ?? "TOKEN_EXPIRED", message: e.message ?? "Token 无效" } };
}
});

41
server/api/auth/register.post.ts

@ -0,0 +1,41 @@
import { z } from "zod";
import { authService } from "../service/auth";
import { checkRateLimit } from "../service/auth/lib/rate-limit";
const RegisterSchema = z.object({
email: z.string().email(),
password: z.string(),
username: z.string().min(2).max(32),
});
export default defineEventHandler(async (event) => {
const ip = getHeader(event, "x-forwarded-for") ?? "unknown";
const userAgent = getHeader(event, "user-agent") ?? undefined;
const { allowed, retryAfterMs } = checkRateLimit(ip);
if (!allowed) {
setResponseStatus(event, 429);
return { error: { code: "RATE_LIMITED", message: "操作过于频繁,请稍后再试" } };
}
const body = await readBody(event);
const parsed = RegisterSchema.safeParse(body);
if (!parsed.success) {
setResponseStatus(event, 400);
return { error: { code: "BAD_REQUEST", message: "参数错误" } };
}
try {
const user = await authService.register({
...parsed.data,
ip,
userAgent,
});
setResponseStatus(event, 201);
return { user };
} catch (err: unknown) {
const e = err as { code?: string; message?: string };
setResponseStatus(event, e.code === "EMAIL_EXISTS" ? 409 : 400);
return { error: { code: e.code ?? "UNKNOWN", message: e.message ?? "注册失败" } };
}
});
Loading…
Cancel
Save