Files
NianAIGC/lib/server/auth/local.ts

104 lines
3.4 KiB
TypeScript

import type { NextResponse } from "next/server";
import { SESSION_COOKIE_NAME, shouldUseSecureAuthCookie } from "@/lib/auth/config";
import type { AuthMode, AuthSession, AuthUser } from "@/lib/auth/session";
import type { PlatformOrganization, PlatformUserRecord } from "@/lib/types";
import { clearSessionCookieValues, setSessionCookieValue } from "@/lib/server/auth/session-cookie";
import { createSessionCookieValue } from "@/lib/auth/session";
import { getPlatformOrganization } from "@/lib/server/account-store";
export const LOCAL_SESSION_TTL_SECONDS = 24 * 60 * 60;
const ipAttempts = new Map<string, { count: number; resetAt: number }>();
export function authUserFromPlatformRecord(user: PlatformUserRecord, organization?: PlatformOrganization | null): AuthUser {
const role = user.role;
const authorities = role === "super_admin"
? ["ROLE_SUPER_ADMIN", "SUPER_ADMIN"]
: role === "organization_admin"
? ["ROLE_ORGANIZATION_ADMIN", "ORGANIZATION_ADMIN"]
: ["ROLE_USER"];
return {
id: user.id,
subject: user.id,
username: user.phone,
phone: user.phone,
displayName: user.displayName,
clientId: "platform",
organizationId: user.organizationId,
organizationName: organization?.name,
role,
status: user.status,
authorities,
scope: []
};
}
export async function createPlatformSession(user: PlatformUserRecord): Promise<AuthSession> {
const organization = user.organizationId ? await getPlatformOrganization(user.organizationId) : null;
const now = Math.floor(Date.now() / 1000);
const authMode: AuthMode = user.role === "user" ? "user" : "admin";
return {
version: 1,
authMode,
user: authUserFromPlatformRecord(user, organization),
issuedAt: now,
expiresAt: now + LOCAL_SESSION_TTL_SECONDS,
sessionVersion: user.sessionVersion
};
}
export async function setPlatformSessionCookie(
response: NextResponse,
requestUrl: string,
session: AuthSession
) {
const secret = process.env.ZHINIAN_AUTH_SESSION_SECRET || process.env.AUTH_SESSION_SECRET || process.env.NEXTAUTH_SECRET;
if (!secret) throw new Error("ZHINIAN_AUTH_SESSION_SECRET 未配置。");
setSessionCookieValue(
response,
requestUrl,
await createSessionCookieValue(session, secret),
new Date(session.expiresAt * 1000)
);
}
export function clearPlatformSessionCookies(response: NextResponse, requestUrl: string) {
clearSessionCookieValues(response, requestUrl);
response.cookies.set(SESSION_COOKIE_NAME, "", {
httpOnly: true,
sameSite: "lax",
secure: shouldUseSecureAuthCookie(requestUrl),
path: "/",
maxAge: 0
});
}
export function clientIpFromRequest(request: Request): string {
return request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ||
request.headers.get("x-real-ip")?.trim() ||
"unknown";
}
export function checkIpLoginRateLimit(ip: string): void {
const now = Date.now();
const current = ipAttempts.get(ip);
if (!current || current.resetAt <= now) {
ipAttempts.set(ip, { count: 1, resetAt: now + 15 * 60 * 1000 });
return;
}
if (current.count >= 30) {
const error = new Error("请求过于频繁,请稍后再试。") as Error & { status: number };
error.status = 429;
throw error;
}
current.count += 1;
}
export function clearIpLoginRateLimit(ip: string): void {
ipAttempts.delete(ip);
}
export function resetLocalAuthRateLimitForTests(): void {
ipAttempts.clear();
}