import { cookies } from "next/headers"; import { SESSION_COOKIE_NAME, getAuthRuntimeConfig } from "@/lib/auth/config"; import { hasAdminSessionAccess, hasSuperAdminAccess } from "@/lib/auth/permissions"; import { chunkedCookieNames, parseSessionCookieValue, readChunkedCookieValue, type AuthSession, type AuthUser } from "@/lib/auth/session"; import { DEFAULT_OWNER_ID } from "@/lib/server/runtime"; import { loadPlatformAuthorizationSnapshot } from "@/lib/server/auth/platform-authorization-store"; import { authorizePlatformSession } from "@/lib/server/auth/platform-session"; export class AuthRequiredError extends Error { status = 401; constructor(message = "请先登录。") { super(message); this.name = "AuthRequiredError"; } } export class AuthConfigurationError extends Error { status = 503; constructor(message: string) { super(message); this.name = "AuthConfigurationError"; } } const localUser: AuthUser = { id: DEFAULT_OWNER_ID, subject: DEFAULT_OWNER_ID, username: "13800000000", phone: "13800000000", displayName: "智念用户", clientId: "local-dev", role: "super_admin", organizationId: "org-demo", organizationName: "演示组织", authorities: ["zhinian_admin"], scope: [] }; function localSession(): AuthSession { const now = Math.floor(Date.now() / 1000); return { version: 1, authMode: "admin", user: localUser, issuedAt: now, expiresAt: now + 24 * 60 * 60 }; } type PlatformAuthUser = AuthUser & { role: NonNullable; status: "active"; }; type GoCurrentSessionResponse = { authRequired: boolean; authConfigured: boolean; } & ( | { authenticated: false; authMode: null; user: null } | { authenticated: true; authMode: AuthSession["authMode"]; user: PlatformAuthUser } ); function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null; } function isStringArray(value: unknown): value is string[] { return Array.isArray(value) && value.every((item) => typeof item === "string"); } function parsePlatformAuthUser(value: unknown): PlatformAuthUser | null { if (!isRecord(value)) return null; const role = value.role; if ( (role !== "user" && role !== "organization_admin" && role !== "super_admin") || value.status !== "active" || typeof value.id !== "string" || !value.id || value.subject !== value.id || typeof value.displayName !== "string" || typeof value.clientId !== "string" || !isStringArray(value.authorities) || !isStringArray(value.scope) ) { return null; } const organizationId = typeof value.organizationId === "string" && value.organizationId.trim() ? value.organizationId : undefined; if (role !== "super_admin" && !organizationId) return null; const user: PlatformAuthUser = { id: value.id, subject: value.id, displayName: value.displayName, clientId: value.clientId, role, status: "active", authorities: [...value.authorities], scope: [...value.scope] }; if (typeof value.username === "string") user.username = value.username; if (typeof value.phone === "string") user.phone = value.phone; if (typeof value.tenantId === "string") user.tenantId = value.tenantId; if (organizationId) user.organizationId = organizationId; if (typeof value.organizationName === "string") user.organizationName = value.organizationName; return user; } function parseGoCurrentSessionResponse(value: unknown): GoCurrentSessionResponse { if ( !isRecord(value) || typeof value.authenticated !== "boolean" || typeof value.authRequired !== "boolean" || typeof value.authConfigured !== "boolean" ) { throw new Error("Go auth/me returned an invalid response."); } if (!value.authenticated) { if (value.authMode !== null || value.user !== null) { throw new Error("Go auth/me returned an invalid anonymous response."); } return { authenticated: false, authRequired: value.authRequired, authConfigured: value.authConfigured, authMode: null, user: null }; } const user = parsePlatformAuthUser(value.user); if ( !value.authConfigured || (value.authMode !== "user" && value.authMode !== "admin") || !user || value.authMode !== (user.role === "user" ? "user" : "admin") ) { throw new Error("Go auth/me returned an invalid authenticated response."); } return { authenticated: true, authRequired: value.authRequired, authConfigured: value.authConfigured, authMode: value.authMode, user }; } async function authorizeSessionThroughGo(session: AuthSession, cookieHeader: string, baseUrl: string) { const endpoint = new URL(`${baseUrl.replace(/\/+$/, "")}/api/auth/me`); if ((endpoint.protocol !== "http:" && endpoint.protocol !== "https:") || endpoint.username || endpoint.password) { throw new AuthConfigurationError("ZHINIAN_GO_INTERNAL_BASE_URL 必须是无凭据的 HTTP(S) 地址。"); } const response = await fetch(endpoint.toString(), { cache: "no-store", headers: { cookie: cookieHeader } }); if (!response.ok) throw new Error(`Go auth/me request failed with status ${response.status}.`); const currentSession = parseGoCurrentSessionResponse(await response.json()); if (!currentSession.authenticated) return null; if (currentSession.user.id !== session.user.id || currentSession.user.clientId !== "platform") { throw new Error("Go auth/me returned a mismatched authenticated user."); } return { ...session, authMode: currentSession.authMode, user: currentSession.user } satisfies AuthSession; } export async function getOptionalAuthSession(): Promise { const config = getAuthRuntimeConfig(); if (!config.sessionSecret) return null; const cookieStore = await cookies(); const session = await parseSessionCookieValue( readChunkedCookieValue(SESSION_COOKIE_NAME, (name) => cookieStore.get(name)?.value), config.sessionSecret ); if (!session) return null; if (session.user.clientId !== "platform") return null; const internalGoBaseUrl = process.env.ZHINIAN_GO_INTERNAL_BASE_URL?.trim(); if (internalGoBaseUrl) { const cookieHeader = chunkedCookieNames(SESSION_COOKIE_NAME) .map((name) => { const value = cookieStore.get(name)?.value; return value === undefined ? null : `${name}=${value}`; }) .filter((value): value is string => value !== null) .join("; "); return authorizeSessionThroughGo(session, cookieHeader, internalGoBaseUrl); } const authorization = await authorizePlatformSession(session, loadPlatformAuthorizationSnapshot); return authorization.outcome === "authenticated" ? authorization.session : null; } export async function requireAppSession(): Promise { const session = await getOptionalAuthSession(); if (session) return session; const config = getAuthRuntimeConfig(); if (!config.required) return localSession(); if (!config.configured) { throw new AuthConfigurationError(`认证配置不完整:${config.missing.join(", ") || "未知配置"}`); } throw new AuthRequiredError(); } export async function requireAppUser(): Promise { return (await requireAppSession()).user; } export class AdminRequiredError extends Error { status = 403; constructor(message = "需要管理员权限。") { super(message); this.name = "AdminRequiredError"; } } export async function requireAdminUser(): Promise { return (await requireAdminSession()).user; } export async function requireAdminSession(): Promise { const session = await requireAppSession(); if (!hasAdminSessionAccess(session)) throw new AdminRequiredError(); return session; } export class SuperAdminRequiredError extends Error { status = 403; constructor(message = "需要超级管理员权限。") { super(message); this.name = "SuperAdminRequiredError"; } } export async function requireSuperAdminSession(): Promise { const session = await requireAppSession(); if (!hasSuperAdminAccess(session.user)) throw new SuperAdminRequiredError(); return session; } export async function requireSuperAdminUser(): Promise { return (await requireSuperAdminSession()).user; } export async function getShellAuthState(): Promise<{ user: AuthUser | null; authRequired: boolean; authConfigured: boolean; isAdmin: boolean; isSuperAdmin: boolean; }> { const config = getAuthRuntimeConfig(); const session = await getOptionalAuthSession(); const shellSession = session || (!config.required ? localSession() : null); return { user: shellSession?.user || null, authRequired: config.required, authConfigured: config.configured, isAdmin: hasAdminSessionAccess(shellSession), isSuperAdmin: hasSuperAdminAccess(shellSession?.user) }; }