feat: add admin accounts and image templates
This commit is contained in:
@@ -21,7 +21,7 @@ export type AuthRuntimeConfig = {
|
||||
|
||||
export function getAuthRuntimeConfig(): AuthRuntimeConfig {
|
||||
const authBaseUrl = trimTrailingSlash(envValue("ZHINIAN_AUTH_BASE_URL", "AUTH_BASE"));
|
||||
const clientId = envValue("ZHINIAN_AUTH_CLIENT_ID", "AUTH_CLIENT_ID") || "customPC";
|
||||
const clientId = envValue("ZHINIAN_AUTH_CLIENT_ID", "AUTH_CLIENT_ID") || "app";
|
||||
const clientSecret = envValue("ZHINIAN_AUTH_CLIENT_SECRET", "AUTH_CLIENT_SECRET");
|
||||
const scope = envValue("ZHINIAN_AUTH_SCOPE", "AUTH_SCOPE") || "server";
|
||||
const issuer = envValue("ZHINIAN_AUTH_ISSUER", "AUTH_ISSUER") || "https://pig4cloud.com";
|
||||
|
||||
77
lib/auth/permissions.ts
Normal file
77
lib/auth/permissions.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import type { 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"
|
||||
];
|
||||
|
||||
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?.trim();
|
||||
if (!configured) return DEFAULT_ADMIN_AUTHORITIES;
|
||||
return configured
|
||||
.split(/[\n,]+/)
|
||||
.map((item) => item.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
export function configuredAdminUsers(): string[] {
|
||||
const configured = process.env.ZHINIAN_ADMIN_USERS?.trim();
|
||||
if (!configured) return DEFAULT_ADMIN_USERS;
|
||||
return configured
|
||||
.split(/[\n,]+/)
|
||||
.map((item) => item.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
export function hasAdminAccess(
|
||||
user: AuthUser | null | undefined,
|
||||
adminAuthorities = configuredAdminAuthorities(),
|
||||
adminUsers = configuredAdminUsers()
|
||||
): boolean {
|
||||
if (!user) return false;
|
||||
const allowedUsers = new Set(adminUsers.map(normalizeAccountName));
|
||||
const identities = [user.username, user.subject, user.displayName, user.id]
|
||||
.map((item) => item ? normalizeAccountName(item) : "")
|
||||
.filter(Boolean);
|
||||
if (identities.some((identity) => allowedUsers.has(identity))) return true;
|
||||
|
||||
const allowed = new Set(adminAuthorities.map(normalizeAuthority));
|
||||
return user.authorities.some((authority) => {
|
||||
const normalized = normalizeAuthority(authority);
|
||||
return allowed.has(normalized) || ADMIN_PREFIXES.some((prefix) => normalized.startsWith(prefix));
|
||||
});
|
||||
}
|
||||
|
||||
export function normalizeAuthority(value: string): string {
|
||||
return value.trim().replace(/[-\s]+/g, "_").toUpperCase();
|
||||
}
|
||||
|
||||
function normalizeAccountName(value: string): string {
|
||||
return value.trim().toLowerCase();
|
||||
}
|
||||
@@ -14,10 +14,14 @@ export type AuthSession = {
|
||||
user: AuthUser;
|
||||
issuedAt: number;
|
||||
expiresAt: number;
|
||||
accessToken?: string;
|
||||
tokenType?: string;
|
||||
};
|
||||
|
||||
const textEncoder = new TextEncoder();
|
||||
const textDecoder = new TextDecoder();
|
||||
const DEFAULT_COOKIE_CHUNK_SIZE = 3000;
|
||||
const DEFAULT_COOKIE_MAX_CHUNKS = 20;
|
||||
|
||||
export async function createSignedJsonValue(value: unknown, secret: string): Promise<string> {
|
||||
const payload = bytesToBase64Url(textEncoder.encode(JSON.stringify(value)));
|
||||
@@ -42,6 +46,39 @@ export async function createSessionCookieValue(session: AuthSession, secret: str
|
||||
return createSignedJsonValue(session, secret);
|
||||
}
|
||||
|
||||
export function chunkCookieValue(value: string, chunkSize = DEFAULT_COOKIE_CHUNK_SIZE): string[] {
|
||||
if (value.length <= chunkSize) return [value];
|
||||
const chunks: string[] = [];
|
||||
for (let index = 0; index < value.length; index += chunkSize) {
|
||||
chunks.push(value.slice(index, index + chunkSize));
|
||||
}
|
||||
return chunks;
|
||||
}
|
||||
|
||||
export function chunkedCookieName(baseName: string, index: number): string {
|
||||
return index === 0 ? baseName : `${baseName}.${index}`;
|
||||
}
|
||||
|
||||
export function chunkedCookieNames(baseName: string, maxChunks = DEFAULT_COOKIE_MAX_CHUNKS): string[] {
|
||||
return Array.from({ length: maxChunks }, (_, index) => chunkedCookieName(baseName, index));
|
||||
}
|
||||
|
||||
export function readChunkedCookieValue(
|
||||
baseName: string,
|
||||
getValue: (name: string) => string | undefined,
|
||||
maxChunks = DEFAULT_COOKIE_MAX_CHUNKS
|
||||
): string | undefined {
|
||||
const first = getValue(baseName);
|
||||
if (!first) return undefined;
|
||||
let value = first;
|
||||
for (let index = 1; index < maxChunks; index += 1) {
|
||||
const chunk = getValue(chunkedCookieName(baseName, index));
|
||||
if (!chunk) break;
|
||||
value += chunk;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export async function parseSessionCookieValue(
|
||||
value: string | undefined,
|
||||
secret: string,
|
||||
@@ -53,6 +90,8 @@ export async function parseSessionCookieValue(
|
||||
if (session.expiresAt <= nowSeconds) return null;
|
||||
return {
|
||||
...session,
|
||||
accessToken: typeof session.accessToken === "string" ? session.accessToken : undefined,
|
||||
tokenType: typeof session.tokenType === "string" ? session.tokenType : undefined,
|
||||
user: {
|
||||
...session.user,
|
||||
authorities: Array.isArray(session.user.authorities) ? session.user.authorities : [],
|
||||
|
||||
Reference in New Issue
Block a user