feat: add local auth billing and usage management
This commit is contained in:
1 parent
f642b5e71f
commit
196fdde83f
119 files changed
+15695
-2650
No files matched your search
+3
-6
@@ -33,13 +33,10 @@ export function getAuthRuntimeConfig(options: { clientMode?: AuthClientMode } =
|
||||
const sessionSecret = envValue("ZHINIAN_AUTH_SESSION_SECRET", "AUTH_SESSION_SECRET", "NEXTAUTH_SECRET");
|
||||
const explicitRequired = boolEnv("ZHINIAN_AUTH_REQUIRED");
|
||||
const disabled = boolEnv("ZHINIAN_AUTH_DISABLED") === true;
|
||||
const hasAnyAuthConfig = Boolean(authBaseUrl || client.clientSecret || sessionSecret);
|
||||
const required = disabled ? false : explicitRequired ?? (process.env.NODE_ENV === "production" || Boolean(authBaseUrl));
|
||||
const wantsConfiguration = required || hasAnyAuthConfig;
|
||||
const required = disabled ? false : explicitRequired ?? (process.env.NODE_ENV === "production" || Boolean(sessionSecret));
|
||||
const wantsConfiguration = required || Boolean(sessionSecret);
|
||||
const missing: string[] = [];
|
||||
|
||||
if (wantsConfiguration && !authBaseUrl) missing.push("ZHINIAN_AUTH_BASE_URL");
|
||||
if (wantsConfiguration && !client.clientSecret) missing.push(client.missingSecretKey);
|
||||
if (wantsConfiguration && !sessionSecret) missing.push("ZHINIAN_AUTH_SESSION_SECRET");
|
||||
|
||||
return {
|
||||
@@ -51,7 +48,7 @@ export function getAuthRuntimeConfig(options: { clientMode?: AuthClientMode } =
|
||||
tokenUrl: endpointUrl(authBaseUrl, "ZHINIAN_AUTH_TOKEN_URL", "/oauth2/token"),
|
||||
jwksUrl: endpointUrl(authBaseUrl, "ZHINIAN_AUTH_JWKS_URL", "/oauth2/jwks"),
|
||||
logoutUrl: endpointUrl(authBaseUrl, "ZHINIAN_AUTH_LOGOUT_URL", "/token/logout"),
|
||||
clientId: client.clientId,
|
||||
clientId: "platform",
|
||||
clientSecret: client.clientSecret,
|
||||
scope,
|
||||
issuer,
|
||||
|
||||
+25
-33
@@ -1,45 +1,26 @@
|
||||
import type { AuthUser } from "@/lib/auth/session";
|
||||
import type { AuthSession, AuthUser } from "@/lib/auth/session";
|
||||
|
||||
const DEFAULT_ADMIN_AUTHORITIES = [
|
||||
"ROLE_ADMIN",
|
||||
"ROLE_1",
|
||||
"1",
|
||||
"ADMIN",
|
||||
"SUPER_ADMIN",
|
||||
"SYS_ADMIN",
|
||||
"ZHINIAN_ADMIN",
|
||||
"sys_user_view",
|
||||
"sys_user_add",
|
||||
"sys_user_edit",
|
||||
"sys_role_view",
|
||||
"sys_log_view",
|
||||
"sys_config_view",
|
||||
"sys_client_view"
|
||||
"ZHINIAN_ADMIN"
|
||||
];
|
||||
|
||||
const DEFAULT_ADMIN_USERS = [
|
||||
"ceshiop"
|
||||
];
|
||||
|
||||
const ADMIN_PREFIXES = [
|
||||
"SYS_USER_",
|
||||
"SYS_ROLE_",
|
||||
"SYS_MENU_",
|
||||
"SYS_LOG_",
|
||||
"SYS_CONFIG_",
|
||||
"SYS_CLIENT_",
|
||||
"ADMIN:"
|
||||
];
|
||||
|
||||
export function configuredAdminAuthorities(): string[] {
|
||||
const configured = process.env.ZHINIAN_ADMIN_AUTHORITIES;
|
||||
if (configured === undefined) return DEFAULT_ADMIN_AUTHORITIES;
|
||||
if (configured === undefined || !configured.trim()) return DEFAULT_ADMIN_AUTHORITIES;
|
||||
return splitConfiguredList(configured);
|
||||
}
|
||||
|
||||
export function configuredAdminUsers(): string[] {
|
||||
const configured = process.env.ZHINIAN_ADMIN_USERS;
|
||||
if (configured === undefined) return DEFAULT_ADMIN_USERS;
|
||||
if (configured === undefined || !configured.trim()) return DEFAULT_ADMIN_USERS;
|
||||
return splitConfiguredList(configured);
|
||||
}
|
||||
|
||||
@@ -49,18 +30,35 @@ export function hasAdminAccess(
|
||||
adminUsers?: string[]
|
||||
): boolean {
|
||||
if (!user) return false;
|
||||
if (user.role) return user.role === "super_admin" || user.role === "organization_admin";
|
||||
const allowedUsers = new Set((adminUsers ?? configuredAdminUsers()).map(normalizeAccountName));
|
||||
const identities = [user.username, user.subject, user.displayName, user.id]
|
||||
const identities = [user.username, user.subject]
|
||||
.map((item) => item ? normalizeAccountName(item) : "")
|
||||
.filter(Boolean);
|
||||
if (identities.some((identity) => allowedUsers.has(identity))) return true;
|
||||
|
||||
if (!shouldUseAuthorityGrants(adminAuthorities)) return false;
|
||||
const allowed = new Set((adminAuthorities ?? configuredAdminAuthorities()).map(normalizeAuthority));
|
||||
return user.authorities.some((authority) => allowed.has(normalizeAuthority(authority)));
|
||||
}
|
||||
|
||||
export function hasSuperAdminAccess(user: AuthUser | null | undefined): boolean {
|
||||
if (!user) return false;
|
||||
if (user.role) return user.role === "super_admin";
|
||||
const allowedAuthorities = new Set(configuredAdminAuthorities().map(normalizeAuthority));
|
||||
return user.authorities.some((authority) => {
|
||||
const normalized = normalizeAuthority(authority);
|
||||
return allowed.has(normalized) || ADMIN_PREFIXES.some((prefix) => normalized.startsWith(prefix));
|
||||
});
|
||||
return normalized === "SUPER_ADMIN" || normalized === "ROLE_SUPER_ADMIN";
|
||||
}) || (user.username ? configuredAdminUsers().some((item) => normalizeAccountName(item) === normalizeAccountName(user.username || "")) : false) ||
|
||||
user.authorities.some((authority) => allowedAuthorities.has(normalizeAuthority(authority)) && normalizeAuthority(authority) === "SUPER_ADMIN");
|
||||
}
|
||||
|
||||
export function hasOrganizationAdminAccess(user: AuthUser | null | undefined): boolean {
|
||||
if (!user) return false;
|
||||
return user.role === "organization_admin";
|
||||
}
|
||||
|
||||
export function hasAdminSessionAccess(session: AuthSession | null | undefined): boolean {
|
||||
return session?.authMode === "admin" && hasAdminAccess(session.user);
|
||||
}
|
||||
|
||||
export function normalizeAuthority(value: string): string {
|
||||
@@ -77,9 +75,3 @@ function splitConfiguredList(value: string): string[] {
|
||||
.map((item) => item.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function shouldUseAuthorityGrants(explicitAuthorities?: string[]): boolean {
|
||||
if (explicitAuthorities !== undefined) return true;
|
||||
if (process.env.ZHINIAN_ADMIN_AUTHORITIES !== undefined) return true;
|
||||
return process.env.ZHINIAN_ADMIN_USERS === undefined;
|
||||
}
|
||||
+13
-1
@@ -1,19 +1,30 @@
|
||||
import type { PlatformRole } from "@/lib/types";
|
||||
|
||||
export type AuthUser = {
|
||||
id: string;
|
||||
subject: string;
|
||||
username?: string;
|
||||
phone?: string;
|
||||
displayName: string;
|
||||
clientId: string;
|
||||
tenantId?: string;
|
||||
organizationId?: string;
|
||||
organizationName?: string;
|
||||
role?: PlatformRole;
|
||||
status?: "active" | "disabled";
|
||||
authorities: string[];
|
||||
scope: string[];
|
||||
};
|
||||
|
||||
export type AuthMode = "user" | "admin";
|
||||
|
||||
export type AuthSession = {
|
||||
version: 1;
|
||||
authMode: AuthMode;
|
||||
user: AuthUser;
|
||||
issuedAt: number;
|
||||
expiresAt: number;
|
||||
sessionVersion?: number;
|
||||
accessToken?: string;
|
||||
tokenType?: string;
|
||||
};
|
||||
@@ -84,12 +95,13 @@ export async function parseSessionCookieValue(
|
||||
secret: string,
|
||||
nowSeconds = Math.floor(Date.now() / 1000)
|
||||
): Promise<AuthSession | null> {
|
||||
const session = await parseSignedJsonValue<AuthSession>(value, secret);
|
||||
const session = await parseSignedJsonValue<Omit<AuthSession, "authMode"> & { authMode?: unknown }>(value, secret);
|
||||
if (!session || session.version !== 1) return null;
|
||||
if (!session.user?.id || !session.user.clientId || !session.expiresAt) return null;
|
||||
if (session.expiresAt <= nowSeconds) return null;
|
||||
return {
|
||||
...session,
|
||||
authMode: session.authMode === "admin" ? "admin" : "user",
|
||||
accessToken: typeof session.accessToken === "string" ? session.accessToken : undefined,
|
||||
tokenType: typeof session.tokenType === "string" ? session.tokenType : undefined,
|
||||
user: {
|
||||
|
||||
Reference in new issue
Block a user