feat(auth): Keycloak role 기반 관리자 판별과 도구 카탈로그 개편
- auth.js: realm/client role 추출 → roles·isAdmin 세션 저장 - config.js: ADMIN_ROLE + 도구 URL 8종 환경변수화 - sites.js: 개발4 + 인프라4(adminOnly) 재구성, toolsForUser(isAdmin) - .project-env.example: ADMIN_ROLE·도구 URL 항목 추가 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -20,3 +20,17 @@ DATABASE_URL=postgresql://portal_app:7e2950ce6ed0c5a1457ea5b5@postgresql-6ox-pgb
|
|||||||
OIDC_ISSUER=https://keycloak.bokdev.in/realms/bokdev
|
OIDC_ISSUER=https://keycloak.bokdev.in/realms/bokdev
|
||||||
OIDC_CLIENT_ID=portal
|
OIDC_CLIENT_ID=portal
|
||||||
OIDC_CLIENT_SECRET=REPLACE_WITH_PORTAL_CLIENT_SECRET
|
OIDC_CLIENT_SECRET=REPLACE_WITH_PORTAL_CLIENT_SECRET
|
||||||
|
|
||||||
|
# --- 권한 ---
|
||||||
|
# 이 Keycloak 역할(realm role)을 가진 사용자에게만 인프라 도구(OpenEverest/MinIO/Keycloak/LiteLLM) 노출.
|
||||||
|
ADMIN_ROLE=admin
|
||||||
|
|
||||||
|
# --- 도구 URL (기본값 있음 · 도메인/폐쇄망 대응으로 덮어쓰기) ---
|
||||||
|
# CODER_URL=https://coder.bokdev.in/api/v2/users/oidc/callback
|
||||||
|
# GITEA_URL=https://gitea.bokdev.in/user/oauth2/keycloak
|
||||||
|
# KUBERO_URL=https://kubero.bokdev.in
|
||||||
|
# HARBOR_URL=https://harbor.bokdev.in
|
||||||
|
# OPENEVEREST_URL=https://openeverest.bokdev.in
|
||||||
|
# MINIO_URL=https://minioc.bokdev.in
|
||||||
|
# KEYCLOAK_URL=https://keycloak.bokdev.in/admin
|
||||||
|
# LITELLM_URL=https://litellm.bokdev.in
|
||||||
|
|||||||
10
src/auth.js
10
src/auth.js
@@ -42,10 +42,20 @@ export async function handleCallback(req) {
|
|||||||
nonce,
|
nonce,
|
||||||
});
|
});
|
||||||
const claims = tokenSet.claims();
|
const claims = tokenSet.claims();
|
||||||
|
// Keycloak realm 역할: realm_access.roles. (클라이언트 역할은 resource_access[client].roles)
|
||||||
|
const realmRoles = (claims.realm_access && claims.realm_access.roles) || [];
|
||||||
|
const clientRoles =
|
||||||
|
(claims.resource_access &&
|
||||||
|
claims.resource_access[config.oidc.clientId] &&
|
||||||
|
claims.resource_access[config.oidc.clientId].roles) ||
|
||||||
|
[];
|
||||||
|
const roles = [...new Set([...realmRoles, ...clientRoles])];
|
||||||
return {
|
return {
|
||||||
sabun: claims.preferred_username, // 사번
|
sabun: claims.preferred_username, // 사번
|
||||||
name: claims.name || claims.given_name || claims.preferred_username,
|
name: claims.name || claims.given_name || claims.preferred_username,
|
||||||
email: claims.email,
|
email: claims.email,
|
||||||
|
roles,
|
||||||
|
isAdmin: roles.includes(config.adminRole),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,21 @@ export const config = {
|
|||||||
// redirect 는 baseUrl + /auth/callback 로 구성
|
// redirect 는 baseUrl + /auth/callback 로 구성
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 관리자 판별용 Keycloak 역할 이름. 이 role 을 가진 사용자에게만 인프라 도구 노출.
|
||||||
|
adminRole: process.env.ADMIN_ROLE || "admin",
|
||||||
|
|
||||||
|
// 도구(사이트) URL — 폐쇄망/도메인 변경 대비 환경변수로 덮어쓰기 가능.
|
||||||
|
tools: {
|
||||||
|
coder: process.env.CODER_URL || "https://coder.bokdev.in/api/v2/users/oidc/callback",
|
||||||
|
gitea: process.env.GITEA_URL || "https://gitea.bokdev.in/user/oauth2/keycloak",
|
||||||
|
kubero: process.env.KUBERO_URL || "https://kubero.bokdev.in",
|
||||||
|
harbor: process.env.HARBOR_URL || "https://harbor.bokdev.in",
|
||||||
|
openeverest: process.env.OPENEVEREST_URL || "https://openeverest.bokdev.in",
|
||||||
|
minio: process.env.MINIO_URL || "https://minioc.bokdev.in",
|
||||||
|
keycloak: process.env.KEYCLOAK_URL || "https://keycloak.bokdev.in/admin",
|
||||||
|
litellm: process.env.LITELLM_URL || "https://litellm.bokdev.in",
|
||||||
|
},
|
||||||
|
|
||||||
// 포털 표시 이름 (변경 가능)
|
// 포털 표시 이름 (변경 가능)
|
||||||
brand: process.env.PORTAL_BRAND || "AI DEV",
|
brand: process.env.PORTAL_BRAND || "AI DEV",
|
||||||
};
|
};
|
||||||
|
|||||||
45
src/sites.js
45
src/sites.js
@@ -1,29 +1,30 @@
|
|||||||
// 포털에 모아둘 사이트 카탈로그. 카테고리: 개발 핵심 / AI·데이터.
|
// 대시보드 "개발 도구" 카탈로그.
|
||||||
// icon 은 이모지(추가 의존성 없이 GitHub 감성 카드에 표시).
|
|
||||||
//
|
//
|
||||||
// url: 각 앱의 'OIDC 로그인 바로 시작' 엔드포인트를 쓰면, Keycloak SSO 세션이 있을 때
|
// url: 각 앱의 'OIDC 로그인 바로 시작' 엔드포인트를 쓰면, Keycloak SSO 세션이 있을 때
|
||||||
// 로그인 폼 없이 즉시 통과(자동 로그인)된다. 포털도 같은 Keycloak realm 으로 로그인하므로
|
// 로그인 폼 없이 즉시 통과(자동 로그인)된다. 포털도 같은 Keycloak realm 으로 로그인하므로
|
||||||
// 포털 로그인 후 이 링크를 누르면 바로 들어간다.
|
// 포털 로그인 후 이 링크를 누르면 바로 들어간다.
|
||||||
// - Coder: /api/v2/users/oidc/callback (code 없으면 keycloak authorize 로 307)
|
// - Coder: /api/v2/users/oidc/callback (code 없으면 keycloak authorize 로 307)
|
||||||
// - Gitea: /user/oauth2/keycloak (auth source 명=keycloak, 307 로 keycloak authorize)
|
// - Gitea: /user/oauth2/keycloak (auth source 명=keycloak, 307 로 keycloak authorize)
|
||||||
// SSO 미지원(Kubero=oauth2 미완성, Harbor 등)은 기본 URL 유지.
|
// SSO 미지원(Kubero/Harbor 등)은 기본 URL 유지.
|
||||||
export const siteGroups = [
|
//
|
||||||
{
|
// adminOnly: true 인 항목은 관리자(role=admin)에게만 렌더(숨김, disabled 아님).
|
||||||
group: "개발 핵심",
|
// icon: 이모지(폐쇄망에서도 외부 의존 없이 표시). desc: 카드 한 줄 설명.
|
||||||
sites: [
|
import { config } from "./config.js";
|
||||||
{ name: "Coder", url: "https://coder.bokdev.in/api/v2/users/oidc/callback", icon: "💻", desc: "웹 개발 워크스페이스 (VS Code + AI CLI)", sso: true },
|
|
||||||
{ name: "Gitea", url: "https://gitea.bokdev.in/user/oauth2/keycloak", icon: "🍵", desc: "사내 Git 저장소", sso: true },
|
export const sites = [
|
||||||
{ name: "Kubero", url: "https://kubero.bokdev.in", icon: "🚀", desc: "앱 배포 PaaS (*.apps.bokdev.in)" },
|
// --- 개발 핵심 (모두에게) ---
|
||||||
{ name: "Harbor", url: "https://harbor.bokdev.in", icon: "📦", desc: "컨테이너 이미지 레지스트리" },
|
{ key: "coder", name: "Coder", desc: "클라우드 IDE", url: config.tools.coder, icon: "💻", sso: true },
|
||||||
],
|
{ key: "gitea", name: "Gitea", desc: "Git 저장소", url: config.tools.gitea, icon: "🍵", sso: true },
|
||||||
},
|
{ key: "kubero", name: "Kubero", desc: "배포 (PaaS)", url: config.tools.kubero, icon: "🚀" },
|
||||||
{
|
{ key: "harbor", name: "Harbor", desc: "컨테이너 레지스트리", url: config.tools.harbor, icon: "📦" },
|
||||||
group: "AI · 데이터",
|
// --- 인프라/운영 (관리자 전용) ---
|
||||||
sites: [
|
{ key: "openeverest", name: "OpenEverest", desc: "DB 콘솔", url: config.tools.openeverest, icon: "🗄️", adminOnly: true },
|
||||||
{ name: "LiteLLM", url: "https://litellm.bokdev.in", icon: "🤖", desc: "AI 게이트웨이 (모델 키)" },
|
{ key: "minio", name: "MinIO", desc: "오브젝트 스토리지", url: config.tools.minio, icon: "🪣", adminOnly: true },
|
||||||
{ name: "Chat", url: "https://chat.bokdev.in", icon: "💬", desc: "사내 챗 (Ollama)" },
|
{ key: "keycloak", name: "Keycloak", desc: "계정 / SSO 관리", url: config.tools.keycloak, icon: "🔑", adminOnly: true },
|
||||||
{ name: "OpenEverest", url: "https://openeverest.bokdev.in",icon: "🗄️", desc: "DBaaS (DB 프로비저닝)" },
|
{ key: "litellm", name: "LiteLLM", desc: "LLM 게이트웨이", url: config.tools.litellm, icon: "🤖", adminOnly: true },
|
||||||
{ name: "MinIO", url: "https://minioc.bokdev.in", icon: "🪣", desc: "오브젝트 스토리지 콘솔" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// 역할에 맞는 도구만 추려서 반환. 일반 사용자는 adminOnly 카드를 아예 받지 않는다.
|
||||||
|
export function toolsForUser(isAdmin) {
|
||||||
|
return sites.filter((s) => isAdmin || !s.adminOnly);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user