diff --git a/.project-env.example b/.project-env.example index 73275c1..13b42b4 100644 --- a/.project-env.example +++ b/.project-env.example @@ -20,3 +20,17 @@ DATABASE_URL=postgresql://portal_app:7e2950ce6ed0c5a1457ea5b5@postgresql-6ox-pgb OIDC_ISSUER=https://keycloak.bokdev.in/realms/bokdev OIDC_CLIENT_ID=portal 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 diff --git a/src/auth.js b/src/auth.js index 15a9231..51f2e0d 100644 --- a/src/auth.js +++ b/src/auth.js @@ -42,10 +42,20 @@ export async function handleCallback(req) { nonce, }); 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 { sabun: claims.preferred_username, // 사번 name: claims.name || claims.given_name || claims.preferred_username, email: claims.email, + roles, + isAdmin: roles.includes(config.adminRole), }; } diff --git a/src/config.js b/src/config.js index e319b9d..4462d78 100644 --- a/src/config.js +++ b/src/config.js @@ -13,6 +13,21 @@ export const config = { // 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", }; diff --git a/src/sites.js b/src/sites.js index 44f74b9..c98c6cb 100644 --- a/src/sites.js +++ b/src/sites.js @@ -1,29 +1,30 @@ -// 포털에 모아둘 사이트 카탈로그. 카테고리: 개발 핵심 / AI·데이터. -// icon 은 이모지(추가 의존성 없이 GitHub 감성 카드에 표시). +// 대시보드 "개발 도구" 카탈로그. // // url: 각 앱의 'OIDC 로그인 바로 시작' 엔드포인트를 쓰면, Keycloak SSO 세션이 있을 때 // 로그인 폼 없이 즉시 통과(자동 로그인)된다. 포털도 같은 Keycloak realm 으로 로그인하므로 // 포털 로그인 후 이 링크를 누르면 바로 들어간다. // - Coder: /api/v2/users/oidc/callback (code 없으면 keycloak authorize 로 307) // - Gitea: /user/oauth2/keycloak (auth source 명=keycloak, 307 로 keycloak authorize) -// SSO 미지원(Kubero=oauth2 미완성, Harbor 등)은 기본 URL 유지. -export const siteGroups = [ - { - group: "개발 핵심", - sites: [ - { 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 }, - { name: "Kubero", url: "https://kubero.bokdev.in", icon: "🚀", desc: "앱 배포 PaaS (*.apps.bokdev.in)" }, - { name: "Harbor", url: "https://harbor.bokdev.in", icon: "📦", desc: "컨테이너 이미지 레지스트리" }, - ], - }, - { - group: "AI · 데이터", - sites: [ - { name: "LiteLLM", url: "https://litellm.bokdev.in", icon: "🤖", desc: "AI 게이트웨이 (모델 키)" }, - { name: "Chat", url: "https://chat.bokdev.in", icon: "💬", desc: "사내 챗 (Ollama)" }, - { name: "OpenEverest", url: "https://openeverest.bokdev.in",icon: "🗄️", desc: "DBaaS (DB 프로비저닝)" }, - { name: "MinIO", url: "https://minioc.bokdev.in", icon: "🪣", desc: "오브젝트 스토리지 콘솔" }, - ], - }, +// SSO 미지원(Kubero/Harbor 등)은 기본 URL 유지. +// +// adminOnly: true 인 항목은 관리자(role=admin)에게만 렌더(숨김, disabled 아님). +// icon: 이모지(폐쇄망에서도 외부 의존 없이 표시). desc: 카드 한 줄 설명. +import { config } from "./config.js"; + +export const sites = [ + // --- 개발 핵심 (모두에게) --- + { 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: "📦" }, + // --- 인프라/운영 (관리자 전용) --- + { key: "openeverest", name: "OpenEverest", desc: "DB 콘솔", url: config.tools.openeverest, icon: "🗄️", adminOnly: true }, + { key: "minio", name: "MinIO", desc: "오브젝트 스토리지", url: config.tools.minio, icon: "🪣", adminOnly: true }, + { key: "keycloak", name: "Keycloak", desc: "계정 / SSO 관리", url: config.tools.keycloak, icon: "🔑", adminOnly: true }, + { key: "litellm", name: "LiteLLM", desc: "LLM 게이트웨이", url: config.tools.litellm, icon: "🤖", adminOnly: true }, ]; + +// 역할에 맞는 도구만 추려서 반환. 일반 사용자는 adminOnly 카드를 아예 받지 않는다. +export function toolsForUser(isAdmin) { + return sites.filter((s) => isAdmin || !s.adminOnly); +}