diff --git a/apps/web-client/src/app/page.tsx b/apps/web-client/src/app/page.tsx index f886460d..211327a3 100644 --- a/apps/web-client/src/app/page.tsx +++ b/apps/web-client/src/app/page.tsx @@ -1,7 +1,7 @@ 'use client'; import { useAuthStore } from '@/stores/auth-store'; -import { useEffect } from 'react'; +import { useEffect, useState } from 'react'; import { useTranslation } from '@/hooks/use-translation'; /** @@ -16,39 +16,84 @@ export default function Home() { // VI: Lấy trạng thái xác thực từ store const { user, isAuthenticated, isLoading, fetchUser } = useAuthStore(); + // EN: State to track if component is mounted on client + // VI: State để theo dõi xem component đã mount trên client chưa + const [isMounted, setIsMounted] = useState(false); + + // EN: Set mounted state to true on client side + // VI: Đặt trạng thái mounted thành true ở phía client + useEffect(() => { + setIsMounted(true); + }, []); + // EN: Fetch user data on component mount if not authenticated // VI: Fetch dữ liệu user khi component mount nếu chưa xác thực useEffect(() => { - if (!isAuthenticated && !isLoading) { + if (isMounted && !isAuthenticated) { fetchUser(); } - }, [isAuthenticated, isLoading, fetchUser]); + // EN: Only run once when mounted or fetchUser changes (which is stable) + // VI: Chỉ chạy một lần khi mounted hoặc fetchUser thay đổi (ổn định) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isMounted]); + + // EN: Prevent hydration mismatch by not rendering auth-dependent content until mounted + // VI: Ngăn chặn hydration mismatch bằng cách không render nội dung phụ thuộc auth cho đến khi mounted + if (!isMounted) { + return null; + } // EN: Show loading state while checking authentication // VI: Hiển thị trạng thái loading trong khi kiểm tra xác thực if (isLoading) { - return
{t('common.loading')}
; + return
{t('common.loading')}
; } return ( - // EN: Main content area with responsive padding - // VI: Khu vực nội dung chính với padding responsive -
-

{t('home.title')}

+ // EN: Main content area with centered layout for minimal aesthetic + // VI: Khu vực nội dung chính với layout căn giữa cho thẩm mỹ tối giản +
+
+ {/* EN: Hero Title / VI: Tiêu đề Hero */} +

+ {t('home.title')} +

- {/* EN: Conditional rendering based on authentication status / VI: Render có điều kiện dựa trên trạng thái xác thực */} - {isAuthenticated && user ? ( - // EN: Authenticated user welcome message / VI: Thông báo chào mừng người dùng đã xác thực -
-

{t('home.welcome', { email: user.email })}

-

{t('home.role', { role: user.role })}

+ {/* EN: Subtitle/Description / VI: Phụ đề/Mô tả */} +

+ {t('home.description')} +

+ + {/* EN: Conditional rendering based on authentication status / VI: Render có điều kiện dựa trên trạng thái xác thực */} +
+ {isAuthenticated && user ? ( + // EN: Authenticated user welcome message / VI: Thông báo chào mừng người dùng đã xác thực +
+
+ {user.email?.[0].toUpperCase()} +
+

{t('home.welcome', { email: user.email })}

+
+ + {t('home.role', { role: user.role })} +
+
+ ) : ( + // EN: Login prompt for unauthenticated users / VI: Nhắc đăng nhập cho người dùng chưa xác thực +
+

{t('home.pleaseLogin')}

+ +
+ )}
- ) : ( - // EN: Login prompt for unauthenticated users / VI: Nhắc đăng nhập cho người dùng chưa xác thực -
-

{t('home.pleaseLogin')}

-
- )} +
); } diff --git a/apps/web-client/src/i18n/messages/en.json b/apps/web-client/src/i18n/messages/en.json index b1774120..99bd3b82 100644 --- a/apps/web-client/src/i18n/messages/en.json +++ b/apps/web-client/src/i18n/messages/en.json @@ -341,6 +341,7 @@ }, "home": { "title": "GoodGo Platform", + "description": "Experience the next generation of minimal intelligence.", "welcome": "Welcome, {email}!", "role": "Role: {role}", "pleaseLogin": "Please log in to continue." @@ -396,4 +397,4 @@ "enable": "Enable" } } -} +} \ No newline at end of file diff --git a/apps/web-client/src/i18n/messages/vi.json b/apps/web-client/src/i18n/messages/vi.json index 635eaf8e..2ef7a57d 100644 --- a/apps/web-client/src/i18n/messages/vi.json +++ b/apps/web-client/src/i18n/messages/vi.json @@ -341,6 +341,7 @@ }, "home": { "title": "Nền tảng GoodGo", + "description": "Trải nghiệm thế hệ tiếp theo của trí tuệ tối giản.", "welcome": "Chào mừng, {email}!", "role": "Vai trò: {role}", "pleaseLogin": "Vui lòng đăng nhập để tiếp tục." @@ -396,4 +397,4 @@ "enable": "Bật" } } -} +} \ No newline at end of file diff --git a/apps/web-client/src/styles/theme.css b/apps/web-client/src/styles/theme.css index 6388cbf0..f576bae2 100644 --- a/apps/web-client/src/styles/theme.css +++ b/apps/web-client/src/styles/theme.css @@ -14,163 +14,235 @@ */ :root { - /* ============================================ + /* ============================================ EN: Color Palette - Dark Mode (Primary Theme) VI: Bảng màu - Dark Mode (Theme chính) ============================================ */ - /* Background Colors / Màu nền */ - --bg-primary: #0A0A0A; /* Almost black - Main background */ - --bg-secondary: #121212; /* Dark grey - Card/Panel background */ - --bg-tertiary: #1A1A1A; /* Dark grey - Hover states */ - --bg-elevated: #242424; /* Elevated surfaces (modals, dropdowns) */ + /* Background Colors / Màu nền */ + --bg-primary: #000000; + /* Pure black - Main background */ + --bg-secondary: #0A0A0A; + /* Almost black - Card/Panel background */ + --bg-tertiary: #141414; + /* Very dark grey - Hover states */ + --bg-elevated: #1A1A1A; + /* Elevated surfaces (modals, dropdowns) */ - /* Text Colors (WCAG Compliant) / Màu chữ (tuân thủ WCAG) */ - --text-primary: #FAFAFA; /* Off-white - Primary text (4.5:1 contrast) */ - --text-secondary: #E0E0E0; /* Light grey - Secondary text */ - --text-tertiary: #A0A0A0; /* Grey - Tertiary/disabled text */ - --text-inverse: #1A1A1A; /* Dark - Text on light backgrounds */ + /* Text Colors (WCAG Compliant) / Màu chữ (tuân thủ WCAG) */ + --text-primary: #FFFFFF; + /* Pure white - Primary text */ + --text-secondary: #888888; + /* Mid grey - Secondary text */ + --text-tertiary: #444444; + /* Dark grey - Tertiary/disabled text */ + --text-inverse: #000000; + /* Black - Text on light/white backgrounds */ - /* Brand/Accent Colors / Màu thương hiệu/Accent */ - --accent-primary: #3B82F6; /* Primary blue - CTAs, links */ - --accent-secondary: #8B5CF6; /* Purple - Highlights */ - --accent-success: #10B981; /* Green - Success states */ - --accent-warning: #F59E0B; /* Amber - Warnings */ - --accent-error: #EF4444; /* Red - Errors */ - --accent-info: #06B6D4; /* Cyan - Info */ + /* Brand/Accent Colors / Màu thương hiệu/Accent */ + --accent-primary: #FFFFFF; + /* White - Primary actions (High contrast) */ + --accent-secondary: #333333; + /* Dark grey - Secondary actions */ + --accent-success: #10B981; + /* Green - Success states */ + --accent-warning: #F59E0B; + /* Amber - Warnings */ + --accent-error: #EF4444; + /* Red - Errors */ + --accent-info: #06B6D4; + /* Cyan - Info */ - /* Chat Specific Colors / Màu riêng cho Chat */ - --chat-user-bubble: #2563EB; /* Deep blue - User message */ - --chat-ai-bubble: #374151; /* Dark grey - AI message */ - --chat-user-text: #FFFFFF; /* White text on blue */ - --chat-ai-text: #F3F4F6; /* Light text on grey */ - --chat-timestamp: #9CA3AF; /* Timestamp grey */ - --chat-divider: #1F2937; /* Divider between messages */ + /* Chat Specific Colors / Màu riêng cho Chat */ + --chat-user-bubble: #1A1A1A; + /* Dark grey - User message */ + --chat-ai-bubble: transparent; + /* Transparent - AI message (Minimal) */ + --chat-user-text: #FFFFFF; + /* White text */ + --chat-ai-text: #E5E5E5; + /* Off-white text */ + --chat-timestamp: #555555; + /* Dark grey timestamp */ + --chat-divider: #222222; + /* Divider between messages */ - /* Border Colors / Màu viền */ - --border-primary: #2A2A2A; /* Default borders */ - --border-secondary: #3A3A3A; /* Hover borders */ - --border-focus: #3B82F6; /* Focus state - Blue */ + /* Border Colors / Màu viền */ + --border-primary: #222222; + /* Subtle borders */ + --border-secondary: #333333; + /* Hover borders */ + --border-focus: #FFFFFF; + /* Focus state - White */ - /* ============================================ + /* ============================================ EN: Light Mode Colors (Secondary Theme) VI: Màu sắc cho Light Mode (Theme phụ) ============================================ */ - --bg-primary-light: #FFFFFF; - --bg-secondary-light: #F9FAFB; - --bg-tertiary-light: #F3F4F6; - --text-primary-light: #111827; - --text-secondary-light: #4B5563; - --border-primary-light: #E5E7EB; + --bg-primary-light: #F9F9F9; + --bg-secondary-light: #FFFFFF; + --bg-tertiary-light: #F0F0F0; + --text-primary-light: #000000; + --text-secondary-light: #666666; + --border-primary-light: #E5E5E5; - /* ============================================ + /* ============================================ EN: Typography VI: Kiểu chữ ============================================ */ - /* Font Stack / Bộ font */ - --font-sans: -apple-system, BlinkMacSystemFont, "SF Pro Display", "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; - --font-mono: "SF Mono", Consolas, "Liberation Mono", Menlo, monospace; + /* Font Stack / Bộ font */ + --font-sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + --font-mono: "JetBrains Mono", "SF Mono", Consolas, "Liberation Mono", Menlo, monospace; - /* Type Scale / Kích thước chữ */ - --text-6xl: 3.75rem; /* 60px - Hero titles */ - --text-5xl: 3rem; /* 48px - Page titles */ - --text-4xl: 2.25rem; /* 36px - Section headers */ - --text-3xl: 1.875rem; /* 30px - Card headers */ - --text-2xl: 1.5rem; /* 24px - Large body */ - --text-xl: 1.25rem; /* 20px - Emphasized text */ - --text-lg: 1.125rem; /* 18px - Large body */ - --text-base: 1rem; /* 16px - Default body */ - --text-sm: 0.875rem; /* 14px - Small text */ - --text-xs: 0.75rem; /* 12px - Captions */ + /* Type Scale / Kích thước chữ */ + --text-6xl: 3.75rem; + /* 60px - Hero titles */ + --text-5xl: 3rem; + /* 48px - Page titles */ + --text-4xl: 2.25rem; + /* 36px - Section headers */ + --text-3xl: 1.875rem; + /* 30px - Card headers */ + --text-2xl: 1.5rem; + /* 24px - Large body */ + --text-xl: 1.25rem; + /* 20px - Emphasized text */ + --text-lg: 1.125rem; + /* 18px - Large body */ + --text-base: 1rem; + /* 16px - Default body */ + --text-sm: 0.875rem; + /* 14px - Small text */ + --text-xs: 0.75rem; + /* 12px - Captions */ - /* Line Heights / Chiều cao dòng */ - --leading-none: 1; - --leading-tight: 1.1; - --leading-snug: 1.2; - --leading-normal: 1.3; - --leading-relaxed: 1.4; - --leading-loose: 1.5; + /* Line Heights / Chiều cao dòng */ + --leading-none: 1; + --leading-tight: 1.1; + --leading-snug: 1.2; + --leading-normal: 1.5; + --leading-relaxed: 1.625; + --leading-loose: 2; - /* Font Weights / Độ đậm chữ */ - --font-light: 300; /* Light text */ - --font-normal: 400; /* Body text */ - --font-medium: 500; /* Emphasized */ - --font-semibold: 600; /* Headings */ - --font-bold: 700; /* Strong emphasis */ + /* Font Weights / Độ đậm chữ */ + --font-light: 300; + /* Light text */ + --font-normal: 400; + /* Body text */ + --font-medium: 500; + /* Emphasized */ + --font-semibold: 600; + /* Headings */ + --font-bold: 700; + /* Strong emphasis */ - /* ============================================ + /* ============================================ EN: Spacing & Layout VI: Khoảng cách & Bố cục ============================================ */ - /* Base Unit: 4px (0.25rem) / Đơn vị cơ sở: 4px (0.25rem) */ - --space-0: 0; - --space-1: 0.25rem; /* 4px */ - --space-2: 0.5rem; /* 8px */ - --space-3: 0.75rem; /* 12px */ - --space-4: 1rem; /* 16px */ - --space-5: 1.25rem; /* 20px */ - --space-6: 1.5rem; /* 24px */ - --space-8: 2rem; /* 32px */ - --space-10: 2.5rem; /* 40px */ - --space-12: 3rem; /* 48px */ - --space-16: 4rem; /* 64px */ - --space-20: 5rem; /* 80px */ + /* Base Unit: 4px (0.25rem) / Đơn vị cơ sở: 4px (0.25rem) */ + --space-0: 0; + --space-1: 0.25rem; + /* 4px */ + --space-2: 0.5rem; + /* 8px */ + --space-3: 0.75rem; + /* 12px */ + --space-4: 1rem; + /* 16px */ + --space-5: 1.25rem; + /* 20px */ + --space-6: 1.5rem; + /* 24px */ + --space-8: 2rem; + /* 32px */ + --space-10: 2.5rem; + /* 40px */ + --space-12: 3rem; + /* 48px */ + --space-16: 4rem; + /* 64px */ + --space-20: 5rem; + /* 80px */ - /* Container Widths / Chiều rộng container */ - --container-sm: 640px; /* Small devices */ - --container-md: 768px; /* Medium devices */ - --container-lg: 1024px; /* Large devices */ - --container-xl: 1280px; /* Extra large */ - --container-2xl: 1536px; /* 2X large */ - --chat-max-width: 768px; /* Max width for chat messages */ - --sidebar-width: 280px; /* Conversation history sidebar */ + /* Container Widths / Chiều rộng container */ + --container-sm: 640px; + /* Small devices */ + --container-md: 768px; + /* Medium devices */ + --container-lg: 1024px; + /* Large devices */ + --container-xl: 1280px; + /* Extra large */ + --container-2xl: 1536px; + /* 2X large */ + --chat-max-width: 800px; + /* Max width for chat messages */ + --sidebar-width: 280px; + /* Conversation history sidebar */ - /* Border Radius / Bo góc */ - --radius-sm: 0.25rem; /* 4px - Small elements */ - --radius-md: 0.5rem; /* 8px - Buttons, inputs */ - --radius-lg: 0.75rem; /* 12px - Cards */ - --radius-xl: 1rem; /* 16px - Large cards */ - --radius-2xl: 1.5rem; /* 24px - Modals */ - --radius-full: 9999px; /* Full round - Avatars, pills */ + /* Border Radius / Bo góc */ + --radius-sm: 2px; + /* Small elements - sharp */ + --radius-md: 4px; + /* Buttons, inputs - sharp */ + --radius-lg: 8px; + /* Cards - minimal roundness */ + --radius-xl: 12px; + /* Large cards */ + --radius-2xl: 16px; + /* Modals */ + --radius-full: 9999px; + /* Full round - Avatars, pills */ - /* ============================================ + /* ============================================ EN: Shadows (Dark Mode Optimized) VI: Đổ bóng (Tối ưu cho Dark Mode) ============================================ */ - --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.5); - --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.5); - --shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.6); - --shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.7); - --shadow-glow: 0 0 20px rgba(59, 130, 246, 0.3); /* Blue glow for focus */ + --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.8); + --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.8); + --shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.9); + --shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.95); + --shadow-glow: 0 0 20px rgba(255, 255, 255, 0.1); + /* White glow for focus */ - /* ============================================ + /* ============================================ EN: Grid System & Breakpoints VI: Hệ thống lưới & Điểm ngắt ============================================ */ - --screen-sm: 640px; /* Mobile landscape */ - --screen-md: 768px; /* Tablet */ - --screen-lg: 1024px; /* Desktop */ - --screen-xl: 1280px; /* Large desktop */ - --screen-2xl: 1536px; /* Extra large desktop */ + --screen-sm: 640px; + /* Mobile landscape */ + --screen-md: 768px; + /* Tablet */ + --screen-lg: 1024px; + /* Desktop */ + --screen-xl: 1280px; + /* Large desktop */ + --screen-2xl: 1536px; + /* Extra large desktop */ - /* ============================================ + /* ============================================ EN: Animation & Transitions VI: Animation & Chuyển tiếp ============================================ */ - /* Timing Functions / Hàm thời gian */ - --ease-in: cubic-bezier(0.4, 0, 1, 1); - --ease-out: cubic-bezier(0, 0, 0.2, 1); - --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1); - --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1); + /* Timing Functions / Hàm thời gian */ + --ease-in: cubic-bezier(0.4, 0, 1, 1); + --ease-out: cubic-bezier(0, 0, 0.2, 1); + --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1); + --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1); - /* Duration / Thời lượng */ - --duration-fast: 150ms; /* Hover effects */ - --duration-normal: 250ms; /* Default transitions */ - --duration-slow: 350ms; /* Complex animations */ - --duration-slower: 500ms; /* Page transitions */ + /* Duration / Thời lượng */ + --duration-fast: 150ms; + /* Hover effects */ + --duration-normal: 250ms; + /* Default transitions */ + --duration-slow: 350ms; + /* Complex animations */ + --duration-slower: 500ms; + /* Page transitions */ } /* ============================================ @@ -178,14 +250,14 @@ VI: Ghi đè theme cho Light Mode ============================================ */ @media (prefers-color-scheme: light) { - :root { - --bg-primary: var(--bg-primary-light); - --bg-secondary: var(--bg-secondary-light); - --bg-tertiary: var(--bg-tertiary-light); - --text-primary: var(--text-primary-light); - --text-secondary: var(--text-secondary-light); - --border-primary: var(--border-primary-light); - } + :root { + --bg-primary: var(--bg-primary-light); + --bg-secondary: var(--bg-secondary-light); + --bg-tertiary: var(--bg-tertiary-light); + --text-primary: var(--text-primary-light); + --text-secondary: var(--text-secondary-light); + --border-primary: var(--border-primary-light); + } } /* ============================================ @@ -194,15 +266,15 @@ ============================================ */ [data-theme="dark"], .dark { - --bg-primary: #0A0A0A; - --bg-secondary: #121212; - --bg-tertiary: #1A1A1A; - --bg-elevated: #242424; - --text-primary: #FAFAFA; - --text-secondary: #E0E0E0; - --text-tertiary: #A0A0A0; - --border-primary: #2A2A2A; - --border-secondary: #3A3A3A; + --bg-primary: #000000; + --bg-secondary: #0A0A0A; + --bg-tertiary: #141414; + --bg-elevated: #1A1A1A; + --text-primary: #FFFFFF; + --text-secondary: #888888; + --text-tertiary: #444444; + --border-primary: #222222; + --border-secondary: #333333; } /* ============================================ @@ -211,10 +283,10 @@ ============================================ */ [data-theme="light"], .light { - --bg-primary: var(--bg-primary-light); - --bg-secondary: var(--bg-secondary-light); - --bg-tertiary: var(--bg-tertiary-light); - --text-primary: var(--text-primary-light); - --text-secondary: var(--text-secondary-light); - --border-primary: var(--border-primary-light); -} + --bg-primary: var(--bg-primary-light); + --bg-secondary: var(--bg-secondary-light); + --bg-tertiary: var(--bg-tertiary-light); + --text-primary: var(--text-primary-light); + --text-secondary: var(--text-secondary-light); + --border-primary: var(--border-primary-light); +} \ No newline at end of file diff --git a/services/iam-service/src/docs/swagger.ts b/services/iam-service/src/docs/swagger.ts index 50f0d052..aa14f49b 100644 --- a/services/iam-service/src/docs/swagger.ts +++ b/services/iam-service/src/docs/swagger.ts @@ -1,6 +1,7 @@ import { Application } from 'express'; import swaggerJSDoc from 'swagger-jsdoc'; import swaggerUi from 'swagger-ui-express'; +import { appConfig } from '../config/app.config'; /** * EN: Swagger/OpenAPI configuration for API documentation @@ -29,7 +30,7 @@ const options = { description: 'Development server', variables: { port: { - default: '5000', + default: String(appConfig.port), description: 'Port number for the development server', }, }, @@ -104,6 +105,155 @@ const options = { }, required: ['success', 'error', 'timestamp'], }, + RegisterRequest: { + type: 'object', + properties: { + email: { type: 'string', format: 'email', example: 'user@example.com' }, + password: { type: 'string', minLength: 8, example: 'StrongP@ssw0rd!' }, + username: { type: 'string', minLength: 3, example: 'johndoe' }, + }, + required: ['email', 'password'], + }, + RegisterResponse: { + type: 'object', + properties: { + user: { $ref: '#/components/schemas/User' }, + token: { type: 'string' }, + }, + }, + LoginRequest: { + type: 'object', + properties: { + email: { type: 'string', format: 'email', example: 'user@example.com' }, + password: { type: 'string', example: 'StrongP@ssw0rd!' }, + }, + required: ['email', 'password'], + }, + LoginResponse: { + type: 'object', + properties: { + accessToken: { type: 'string' }, + refreshToken: { type: 'string' }, + user: { $ref: '#/components/schemas/User' }, + }, + }, + User: { + type: 'object', + properties: { + id: { type: 'string', example: 'cuid12345' }, + email: { type: 'string', format: 'email' }, + username: { type: 'string' }, + firstName: { type: 'string' }, + lastName: { type: 'string' }, + isActive: { type: 'boolean' }, + isEmailVerified: { type: 'boolean' }, + createdAt: { type: 'string', format: 'date-time' }, + updatedAt: { type: 'string', format: 'date-time' }, + }, + }, + CreateUserRequest: { + type: 'object', + properties: { + email: { type: 'string', format: 'email' }, + username: { type: 'string' }, + firstName: { type: 'string' }, + lastName: { type: 'string' }, + phone: { type: 'string' }, + }, + required: ['email'], + }, + Organization: { + type: 'object', + properties: { + id: { type: 'string' }, + name: { type: 'string' }, + domain: { type: 'string' }, + isActive: { type: 'boolean' }, + parentId: { type: 'string' }, + createdAt: { type: 'string', format: 'date-time' }, + updatedAt: { type: 'string', format: 'date-time' }, + }, + }, + CreateOrganizationRequest: { + type: 'object', + properties: { + name: { type: 'string' }, + domain: { type: 'string' }, + parentId: { type: 'string' }, + settings: { type: 'object' }, + }, + required: ['name'], + }, + UpdateOrganizationRequest: { + type: 'object', + properties: { + name: { type: 'string' }, + domain: { type: 'string' }, + parentId: { type: 'string' }, + settings: { type: 'object' }, + isActive: { type: 'boolean' }, + }, + }, + Group: { + type: 'object', + properties: { + id: { type: 'string' }, + name: { type: 'string' }, + description: { type: 'string' }, + organizationId: { type: 'string' }, + isActive: { type: 'boolean' }, + createdAt: { type: 'string', format: 'date-time' }, + updatedAt: { type: 'string', format: 'date-time' }, + }, + }, + CreateGroupRequest: { + type: 'object', + properties: { + name: { type: 'string' }, + description: { type: 'string' }, + organizationId: { type: 'string' }, + }, + required: ['name'], + }, + UpdateGroupRequest: { + type: 'object', + properties: { + name: { type: 'string' }, + description: { type: 'string' }, + isActive: { type: 'boolean' }, + }, + }, + UserProfile: { + type: 'object', + properties: { + id: { type: 'string' }, + userId: { type: 'string' }, + avatarUrl: { type: 'string' }, + bio: { type: 'string' }, + phoneNumber: { type: 'string' }, + address: { type: 'string' }, + preferences: { type: 'object' }, + }, + }, + UpdateProfileRequest: { + type: 'object', + properties: { + firstName: { type: 'string' }, + lastName: { type: 'string' }, + phone: { type: 'string' }, + avatarUrl: { type: 'string' }, + preferences: { type: 'object' }, + }, + }, + UpdateUserRequest: { + type: 'object', + properties: { + username: { type: 'string' }, + email: { type: 'string', format: 'email' }, + isActive: { type: 'boolean' }, + organizationId: { type: 'string' }, + }, + }, Feature: { type: 'object', properties: { @@ -304,7 +454,17 @@ const options = { }, ], }, - apis: ['./src/routes/*.ts', './src/modules/*/feature.module.ts'], // Paths to files containing OpenAPI definitions + apis: [ + './src/routes/*.ts', + './src/modules/auth/*.routes.ts', + './src/modules/session/*.routes.ts', + './src/modules/oidc/*.routes.ts', + './src/modules/rbac/*.routes.ts', + './src/modules/mfa/*.routes.ts', + './src/modules/identity/*.routes.ts', + './src/modules/access/*.routes.ts', + './src/modules/governance/*.routes.ts' + ], }; /** @@ -357,7 +517,7 @@ export const setupSwagger = (app: Application, basePath: string = '/api-docs') = res.send(JSON.stringify(specs, null, 2)); }); - console.log(`📚 Swagger documentation available at: http://localhost:5000${basePath}`); + console.log(`📚 Swagger documentation available at: http://localhost:${appConfig.port}${basePath}`); }; export { specs }; diff --git a/services/iam-service/src/modules/access/access.routes.ts b/services/iam-service/src/modules/access/access.routes.ts new file mode 100644 index 00000000..1a077e88 --- /dev/null +++ b/services/iam-service/src/modules/access/access.routes.ts @@ -0,0 +1,591 @@ +import { Router } from 'express'; +import { authenticate } from '../../middlewares/auth.middleware'; +import { requirePermission } from '../../middlewares/rbac.middleware'; +import { accessRequestController } from './request'; +import { accessReviewController } from './review'; +import { accessAnalyticsController } from './analytics'; + +/** + * EN: Create and configure Access Management routes + * VI: Tạo và cấu hình routes cho Access Management + */ +export const createAccessRouter = (): Router => { + const router = Router(); + + // ============================================================================= + // EN: Access Request Endpoints + // VI: Endpoints Yêu Cầu Truy Cập + // ============================================================================= + + /** + * @swagger + * /api/{version}/access/requests: + * get: + * summary: List access requests + * description: List access requests visible to the current user + * tags: [Access - Requests] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * - in: query + * name: status + * schema: + * type: string + * enum: [PENDING, APPROVED, REJECTED, CANCELLED] + * - in: query + * name: page + * schema: + * type: integer + * - in: query + * name: limit + * schema: + * type: integer + * responses: + * 200: + * description: Requests list + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/ApiResponse' + * - type: object + * properties: + * data: + * type: array + * items: + * $ref: '#/components/schemas/AccessRequest' + */ + router.get('/requests', authenticate(), accessRequestController.list.bind(accessRequestController)); + + /** + * @swagger + * /api/{version}/access/requests: + * post: + * summary: Create access request + * description: Submit a new access request + * tags: [Access - Requests] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * requestBody: + * required: true + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/CreateAccessRequest' + * responses: + * 201: + * description: Request created + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/ApiResponse' + * - type: object + * properties: + * data: + * $ref: '#/components/schemas/AccessRequest' + */ + router.post('/requests', authenticate(), accessRequestController.create.bind(accessRequestController)); + + /** + * @swagger + * /api/{version}/access/requests/{id}: + * get: + * summary: Get access request + * description: Get access request details + * tags: [Access - Requests] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * - in: path + * name: id + * required: true + * schema: + * type: string + * description: Request ID + * responses: + * 200: + * description: Request details + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/ApiResponse' + * - type: object + * properties: + * data: + * $ref: '#/components/schemas/AccessRequest' + */ + router.get('/requests/:id', authenticate(), accessRequestController.get.bind(accessRequestController)); + + /** + * @swagger + * /api/{version}/access/requests/{id}/approve: + * put: + * summary: Approve access request + * description: Approve a pending access request + * tags: [Access - Requests] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * - in: path + * name: id + * required: true + * schema: + * type: string + * description: Request ID + * requestBody: + * content: + * application/json: + * schema: + * type: object + * properties: + * comment: + * type: string + * responses: + * 200: + * description: Request approved + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ApiResponse' + */ + router.put('/requests/:id/approve', authenticate(), requirePermission('access', 'approve'), accessRequestController.approve.bind(accessRequestController)); + + /** + * @swagger + * /api/{version}/access/requests/{id}/reject: + * put: + * summary: Reject access request + * description: Reject a pending access request + * tags: [Access - Requests] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * - in: path + * name: id + * required: true + * schema: + * type: string + * description: Request ID + * requestBody: + * content: + * application/json: + * schema: + * type: object + * properties: + * comment: + * type: string + * responses: + * 200: + * description: Request rejected + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ApiResponse' + */ + router.put('/requests/:id/reject', authenticate(), requirePermission('access', 'approve'), accessRequestController.reject.bind(accessRequestController)); + + /** + * @swagger + * /api/{version}/access/requests/{id}: + * delete: + * summary: Cancel access request + * description: Cancel a pending access request + * tags: [Access - Requests] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * - in: path + * name: id + * required: true + * schema: + * type: string + * description: Request ID + * responses: + * 200: + * description: Request cancelled + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ApiResponse' + */ + router.delete('/requests/:id', authenticate(), accessRequestController.cancel.bind(accessRequestController)); + + // ============================================================================= + // EN: Access Review Endpoints + // VI: Endpoints Đánh Giá Truy Cập + // ============================================================================= + + /** + * @swagger + * /api/{version}/access/reviews: + * get: + * summary: List access reviews + * description: List access reviews + * tags: [Access - Reviews] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * responses: + * 200: + * description: Reviews list + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/ApiResponse' + * - type: object + * properties: + * data: + * type: array + * items: + * $ref: '#/components/schemas/AccessReview' + */ + router.get('/reviews', authenticate(), requirePermission('access', 'read'), accessReviewController.list.bind(accessReviewController)); + + /** + * @swagger + * /api/{version}/access/reviews: + * post: + * summary: Create access review + * description: Create a new access review campaign + * tags: [Access - Reviews] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * requestBody: + * required: true + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/CreateAccessReviewRequest' + * responses: + * 201: + * description: Review created + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/ApiResponse' + * - type: object + * properties: + * data: + * $ref: '#/components/schemas/AccessReview' + */ + router.post('/reviews', authenticate(), requirePermission('access', 'create'), accessReviewController.create.bind(accessReviewController)); + + /** + * @swagger + * /api/{version}/access/reviews/{id}: + * get: + * summary: Get access review + * description: Get access review details + * tags: [Access - Reviews] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * - in: path + * name: id + * required: true + * schema: + * type: string + * description: Review ID + * responses: + * 200: + * description: Review details + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/ApiResponse' + * - type: object + * properties: + * data: + * $ref: '#/components/schemas/AccessReview' + */ + router.get('/reviews/:id', authenticate(), requirePermission('access', 'read'), accessReviewController.get.bind(accessReviewController)); + + /** + * @swagger + * /api/{version}/access/reviews/{id}/start: + * post: + * summary: Start access review + * description: Start an access review campaign + * tags: [Access - Reviews] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * - in: path + * name: id + * required: true + * schema: + * type: string + * description: Review ID + * responses: + * 200: + * description: Review started + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ApiResponse' + */ + router.post('/reviews/:id/start', authenticate(), requirePermission('access', 'update'), accessReviewController.start.bind(accessReviewController)); + + /** + * @swagger + * /api/{version}/access/reviews/{id}/complete: + * post: + * summary: Complete access review + * description: Complete an access review campaign + * tags: [Access - Reviews] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * - in: path + * name: id + * required: true + * schema: + * type: string + * description: Review ID + * responses: + * 200: + * description: Review completed + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ApiResponse' + */ + router.post('/reviews/:id/complete', authenticate(), requirePermission('access', 'update'), accessReviewController.complete.bind(accessReviewController)); + + /** + * @swagger + * /api/{version}/access/reviews/{id}/items: + * get: + * summary: Get review items + * description: Get items in an access review + * tags: [Access - Reviews] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * - in: path + * name: id + * required: true + * schema: + * type: string + * description: Review ID + * responses: + * 200: + * description: Review items + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/ApiResponse' + * - type: object + * properties: + * data: + * type: array + * items: + * $ref: '#/components/schemas/AccessReviewItem' + */ + router.get('/reviews/:id/items', authenticate(), requirePermission('access', 'read'), accessReviewController.getItems.bind(accessReviewController)); + + /** + * @swagger + * /api/{version}/access/reviews/{id}/items/{itemId}/review: + * put: + * summary: Review item + * description: Make a decision on a review item (Certify/Revoke) + * tags: [Access - Reviews] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * - in: path + * name: id + * required: true + * schema: + * type: string + * description: Review ID + * - in: path + * name: itemId + * required: true + * schema: + * type: string + * description: Item ID + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: [decision] + * properties: + * decision: + * type: string + * enum: [CERTIFY, REVOKE] + * comment: + * type: string + * responses: + * 200: + * description: Item reviewed + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ApiResponse' + */ + router.put('/reviews/:id/items/:itemId/review', authenticate(), requirePermission('access', 'update'), accessReviewController.reviewItem.bind(accessReviewController)); + + // ============================================================================= + // EN: Analytics Endpoints + // VI: Endpoints Analytics + // ============================================================================= + + /** + * @swagger + * /api/{version}/access/analytics/usage: + * get: + * summary: Access usage analytics + * description: Get access usage analytics + * tags: [Access - Analytics] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * responses: + * 200: + * description: Usage analytics + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ApiResponse' + */ + router.get('/analytics/usage', authenticate(), requirePermission('access', 'read'), accessAnalyticsController.usage.bind(accessAnalyticsController)); + + /** + * @swagger + * /api/{version}/access/analytics/permissions: + * get: + * summary: Permissions analytics + * description: Get permissions analytics + * tags: [Access - Analytics] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * responses: + * 200: + * description: Permissions analytics + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ApiResponse' + */ + router.get('/analytics/permissions', authenticate(), requirePermission('access', 'read'), accessAnalyticsController.permissions.bind(accessAnalyticsController)); + + return router; +}; diff --git a/services/iam-service/src/modules/auth/auth.routes.ts b/services/iam-service/src/modules/auth/auth.routes.ts new file mode 100644 index 00000000..22122884 --- /dev/null +++ b/services/iam-service/src/modules/auth/auth.routes.ts @@ -0,0 +1,313 @@ +import { Router } from 'express'; +import { authenticate } from '../../middlewares/auth.middleware'; +import { authController } from './auth.controller'; +import { changePasswordController } from './change-password.controller'; +import { socialAuthController } from '../social/social.controller'; + +/** + * EN: Create and configure auth routes + * VI: Tạo và cấu hình routes cho auth + */ +export const createAuthRouter = (): Router => { + const router = Router(); + + // ============================================================================= + // EN: Authentication Endpoints + // VI: Endpoints Xác Thực + // ============================================================================= + + /** + * @swagger + * /api/{version}/auth/register: + * post: + * summary: Register a new user + * description: Register a new user account with email and password + * tags: [Authentication] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * requestBody: + * required: true + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/RegisterRequest' + * responses: + * 201: + * description: User registered successfully + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/RegisterResponse' + * 400: + * description: Validation error or email already in use + * 500: + * description: Internal server error + */ + router.post('/register', authController.register.bind(authController)); + + /** + * @swagger + * /api/{version}/auth/login: + * post: + * summary: Login user + * description: Authenticate user with email and password to receive access tokens + * tags: [Authentication] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * requestBody: + * required: true + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/LoginRequest' + * responses: + * 200: + * description: Login successful + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/LoginResponse' + * 401: + * description: Invalid credentials + * 500: + * description: Internal server error + */ + router.post('/login', authController.login.bind(authController)); + + /** + * @swagger + * /api/{version}/auth/logout: + * post: + * summary: Logout user + * description: Invalidate current user session and tokens + * tags: [Authentication] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * responses: + * 200: + * description: Logout successful + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ApiResponse' + * 401: + * description: Unauthorized + */ + router.post('/logout', authenticate(), authController.logout.bind(authController)); + + /** + * @swagger + * /api/{version}/auth/refresh: + * post: + * summary: Refresh access token + * description: Get a new access token using a valid refresh token + * tags: [Authentication] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: [refreshToken] + * properties: + * refreshToken: + * type: string + * description: The refresh token + * responses: + * 200: + * description: Token refreshed successfully + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/LoginResponse' + * 401: + * description: Invalid or expired refresh token + */ + router.post('/refresh', authController.refreshToken.bind(authController)); + + /** + * @swagger + * /api/{version}/auth/change-password: + * post: + * summary: Change password + * description: Change the password for the currently authenticated user + * tags: [Authentication] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: [currentPassword, newPassword] + * properties: + * currentPassword: + * type: string + * newPassword: + * type: string + * responses: + * 200: + * description: Password changed successfully + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ApiResponse' + * 400: + * description: Validation error or wrong password + * 401: + * description: Unauthorized + */ + router.post('/change-password', authenticate(), changePasswordController.changePassword.bind(changePasswordController)); + + /** + * @swagger + * /api/{version}/auth/me: + * get: + * summary: Get current user + * description: Get information about the currently authenticated user + * tags: [Authentication] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * responses: + * 200: + * description: User information retrieved + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/ApiResponse' + * - type: object + * properties: + * data: + * type: object + * description: User payload from token + * 401: + * description: Unauthorized + */ + router.get('/me', authenticate(), (req, res) => { + res.json({ + success: true, + data: req.user, + message: 'User information retrieved / Thông tin người dùng đã được lấy', + timestamp: new Date().toISOString(), + }); + }); + + // ============================================================================= + // EN: Social Authentication Endpoints + // VI: Endpoints Xác Thực Mạng Xã Hội + // ============================================================================= + + /** + * @swagger + * /api/{version}/auth/google: + * get: + * summary: Initiate Google OAuth + * description: Redirects to Google login page for OAuth2 authentication + * tags: [Authentication] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * responses: + * 302: + * description: Redirects to Google + */ + router.get('/google', socialAuthController.googleAuth.bind(socialAuthController)); + router.get('/google/callback', socialAuthController.googleCallback.bind(socialAuthController)); + + /** + * @swagger + * /api/{version}/auth/facebook: + * get: + * summary: Initiate Facebook OAuth + * description: Redirects to Facebook login page for OAuth2 authentication + * tags: [Authentication] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * responses: + * 302: + * description: Redirects to Facebook + */ + router.get('/facebook', socialAuthController.facebookAuth.bind(socialAuthController)); + router.get('/facebook/callback', socialAuthController.facebookCallback.bind(socialAuthController)); + + /** + * @swagger + * /api/{version}/auth/github: + * get: + * summary: Initiate GitHub OAuth + * description: Redirects to GitHub login page for OAuth2 authentication + * tags: [Authentication] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * responses: + * 302: + * description: Redirects to GitHub + */ + router.get('/github', socialAuthController.githubAuth.bind(socialAuthController)); + router.get('/github/callback', socialAuthController.githubCallback.bind(socialAuthController)); + + return router; +}; diff --git a/services/iam-service/src/modules/governance/governance.routes.ts b/services/iam-service/src/modules/governance/governance.routes.ts new file mode 100644 index 00000000..59ca24a8 --- /dev/null +++ b/services/iam-service/src/modules/governance/governance.routes.ts @@ -0,0 +1,619 @@ +import { Router } from 'express'; +import { authenticate } from '../../middlewares/auth.middleware'; +import { requirePermission } from '../../middlewares/rbac.middleware'; +import { complianceController } from './compliance'; +import { policyGovernanceController } from './policy'; +import { riskController } from './risk'; +import { reportingController } from './reporting'; + +/** + * EN: Create and configure Governance routes + * VI: Tạo và cấu hình routes cho Governance + */ +export const createGovernanceRouter = (): Router => { + const router = Router(); + + // ============================================================================= + // EN: Compliance Reports Endpoints + // VI: Endpoints Báo Cáo Tuân Thủ + // ============================================================================= + + /** + * @swagger + * /api/{version}/governance/compliance/reports: + * get: + * summary: List compliance reports + * description: List compliance reports + * tags: [Governance - Compliance] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * responses: + * 200: + * description: Reports list + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/ApiResponse' + * - type: object + * properties: + * data: + * type: array + * items: + * $ref: '#/components/schemas/ComplianceReport' + */ + router.get('/compliance/reports', authenticate(), requirePermission('governance', 'read'), complianceController.list.bind(complianceController)); + + /** + * @swagger + * /api/{version}/governance/compliance/reports/generate: + * post: + * summary: Generate compliance report + * description: Generate a new compliance report + * tags: [Governance - Compliance] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * responses: + * 201: + * description: Report generation started + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ApiResponse' + */ + router.post('/compliance/reports/generate', authenticate(), requirePermission('governance', 'create'), complianceController.generate.bind(complianceController)); + + /** + * @swagger + * /api/{version}/governance/compliance/reports/{id}: + * get: + * summary: Get compliance report + * description: Get compliance report details + * tags: [Governance - Compliance] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * - in: path + * name: id + * required: true + * schema: + * type: string + * description: Report ID + * responses: + * 200: + * description: Report details + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/ApiResponse' + * - type: object + * properties: + * data: + * $ref: '#/components/schemas/ComplianceReport' + */ + router.get('/compliance/reports/:id', authenticate(), requirePermission('governance', 'read'), complianceController.get.bind(complianceController)); + + /** + * @swagger + * /api/{version}/governance/compliance/reports/{id}/export: + * get: + * summary: Export compliance report + * description: Export compliance report to file + * tags: [Governance - Compliance] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * - in: path + * name: id + * required: true + * schema: + * type: string + * description: Report ID + * - in: query + * name: format + * schema: + * type: string + * enum: [pdf, csv] + * default: pdf + * responses: + * 200: + * description: Export successful + * content: + * application/json: + * schema: + * type: object // File download + */ + router.get('/compliance/reports/:id/export', authenticate(), requirePermission('governance', 'read'), complianceController.export.bind(complianceController)); + + /** + * @swagger + * /api/{version}/governance/compliance/reports/{id}/publish: + * post: + * summary: Publish compliance report + * description: Publish a compliance report + * tags: [Governance - Compliance] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * - in: path + * name: id + * required: true + * schema: + * type: string + * description: Report ID + * responses: + * 200: + * description: Report published + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ApiResponse' + */ + router.post('/compliance/reports/:id/publish', authenticate(), requirePermission('governance', 'update'), complianceController.publish.bind(complianceController)); + + // ============================================================================= + // EN: Policy Governance Endpoints + // VI: Endpoints Quản Trị Policy + // ============================================================================= + + /** + * @swagger + * /api/{version}/governance/policies/templates: + * get: + * summary: List policy templates + * description: List available policy templates + * tags: [Governance - Policy] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * responses: + * 200: + * description: Templates list + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/ApiResponse' + * - type: object + * properties: + * data: + * type: array + * items: + * $ref: '#/components/schemas/PolicyTemplate' + */ + router.get('/policies/templates', authenticate(), requirePermission('governance', 'read'), policyGovernanceController.getTemplates.bind(policyGovernanceController)); + + /** + * @swagger + * /api/{version}/governance/policies/templates: + * post: + * summary: Create policy template + * description: Create a new policy template + * tags: [Governance - Policy] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * responses: + * 201: + * description: Template created + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ApiResponse' + */ + router.post('/policies/templates', authenticate(), requirePermission('governance', 'create'), policyGovernanceController.createTemplate.bind(policyGovernanceController)); + + /** + * @swagger + * /api/{version}/governance/policies/{id}/versions: + * get: + * summary: Get policy versions + * description: Get version history of a policy + * tags: [Governance - Policy] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * - in: path + * name: id + * required: true + * schema: + * type: string + * description: Policy ID + * responses: + * 200: + * description: Policy versions + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/ApiResponse' + * - type: object + * properties: + * data: + * type: array + * items: + * $ref: '#/components/schemas/PolicyVersion' + */ + router.get('/policies/:id/versions', authenticate(), requirePermission('governance', 'read'), policyGovernanceController.getVersions.bind(policyGovernanceController)); + + /** + * @swagger + * /api/{version}/governance/policies/{id}/test: + * post: + * summary: Test policy + * description: Test a policy against a context + * tags: [Governance - Policy] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * - in: path + * name: id + * required: true + * schema: + * type: string + * description: Policy ID + * requestBody: + * content: + * application/json: + * schema: + * type: object + * properties: + * context: + * type: object + * responses: + * 200: + * description: Test result + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/ApiResponse' + * - type: object + * properties: + * data: + * type: object + * properties: + * allowed: + * type: boolean + */ + router.post('/policies/:id/test', authenticate(), requirePermission('governance', 'read'), policyGovernanceController.test.bind(policyGovernanceController)); + + // ============================================================================= + // EN: Risk Management Endpoints + // VI: Endpoints Quản Lý Rủi Ro + // ============================================================================= + + /** + * @swagger + * /api/{version}/governance/risk/scores: + * get: + * summary: List risk scores + * description: List user risk scores + * tags: [Governance - Risk] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * responses: + * 200: + * description: Risk scores + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/ApiResponse' + * - type: object + * properties: + * data: + * type: array + * items: + * $ref: '#/components/schemas/RiskScore' + */ + router.get('/risk/scores', authenticate(), requirePermission('governance', 'read'), riskController.list.bind(riskController)); + + /** + * @swagger + * /api/{version}/governance/risk/scores/{userId}: + * get: + * summary: Get user risk score + * description: Get risk score for a specific user + * tags: [Governance - Risk] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * - in: path + * name: userId + * required: true + * schema: + * type: string + * description: User ID + * responses: + * 200: + * description: User risk score + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/ApiResponse' + * - type: object + * properties: + * data: + * $ref: '#/components/schemas/RiskScore' + */ + router.get('/risk/scores/:userId', authenticate(), requirePermission('governance', 'read'), riskController.getScore.bind(riskController)); + + /** + * @swagger + * /api/{version}/governance/risk/calculate: + * post: + * summary: Calculate risk score + * description: Trigger risk score calculation + * tags: [Governance - Risk] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * responses: + * 200: + * description: Calculation started + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ApiResponse' + */ + router.post('/risk/calculate', authenticate(), requirePermission('governance', 'update'), riskController.calculate.bind(riskController)); + + /** + * @swagger + * /api/{version}/governance/risk/dashboard: + * get: + * summary: Risk dashboard + * description: Get risk dashboard data + * tags: [Governance - Risk] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * responses: + * 200: + * description: Dashboard data + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ApiResponse' + */ + router.get('/risk/dashboard', authenticate(), requirePermission('governance', 'read'), riskController.dashboard.bind(riskController)); + + // ============================================================================= + // EN: Reporting Endpoints + // VI: Endpoints Báo Cáo + // ============================================================================= + + /** + * @swagger + * /api/{version}/governance/reports/access-summary: + * get: + * summary: Access summary report + * description: Get access summary report + * tags: [Governance - Reports] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * responses: + * 200: + * description: Report data + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ApiResponse' + */ + router.get('/reports/access-summary', authenticate(), requirePermission('governance', 'read'), reportingController.accessSummary.bind(reportingController)); + + /** + * @swagger + * /api/{version}/governance/reports/user-activity: + * get: + * summary: User activity report + * description: Get user activity report + * tags: [Governance - Reports] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * responses: + * 200: + * description: Report data + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ApiResponse' + */ + router.get('/reports/user-activity', authenticate(), requirePermission('governance', 'read'), reportingController.userActivity.bind(reportingController)); + + /** + * @swagger + * /api/{version}/governance/reports/security-events: + * get: + * summary: Security events report + * description: Get security events report + * tags: [Governance - Reports] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * responses: + * 200: + * description: Report data + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ApiResponse' + */ + router.get('/reports/security-events', authenticate(), requirePermission('governance', 'read'), reportingController.securityEvents.bind(reportingController)); + + /** + * @swagger + * /api/{version}/governance/reports/compliance-status: + * get: + * summary: Compliance status report + * description: Get compliance status report + * tags: [Governance - Reports] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * responses: + * 200: + * description: Report data + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ApiResponse' + */ + router.get('/reports/compliance-status', authenticate(), requirePermission('governance', 'read'), reportingController.complianceStatus.bind(reportingController)); + + /** + * @swagger + * /api/{version}/governance/reports/risk-overview: + * get: + * summary: Risk overview report + * description: Get risk overview report + * tags: [Governance - Reports] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * responses: + * 200: + * description: Report data + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ApiResponse' + */ + router.get('/reports/risk-overview', authenticate(), requirePermission('governance', 'read'), reportingController.riskOverview.bind(reportingController)); + + return router; +}; diff --git a/services/iam-service/src/modules/identity/identity.routes.ts b/services/iam-service/src/modules/identity/identity.routes.ts new file mode 100644 index 00000000..e30a3b27 --- /dev/null +++ b/services/iam-service/src/modules/identity/identity.routes.ts @@ -0,0 +1,1252 @@ +import { Router } from 'express'; +import { authenticate } from '../../middlewares/auth.middleware'; +import { requirePermission } from '../../middlewares/rbac.middleware'; +import { userManagementController } from './user'; +import { profileController } from './profile'; +import { verificationController } from './verification'; +import { organizationController } from './organization'; +import { groupController } from './group'; + +/** + * EN: Create and configure Identity routes + * VI: Tạo và cấu hình routes cho Identity + */ +export const createIdentityRouter = (): Router => { + const router = Router(); + + // ============================================================================= + // EN: User Management Endpoints + // VI: Endpoints Quản Lý Người Dùng + // ============================================================================= + + /** + * @swagger + * /api/{version}/identity/users: + * get: + * summary: List users + * description: Retrieve a list of users with pagination and filtering + * tags: [Identity - Users] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * - in: query + * name: page + * schema: + * type: integer + * default: 1 + * - in: query + * name: limit + * schema: + * type: integer + * default: 10 + * - in: query + * name: search + * schema: + * type: string + * responses: + * 200: + * description: Users retrieved successfully + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/ApiResponse' + * - type: object + * properties: + * data: + * type: array + * items: + * $ref: '#/components/schemas/User' + * 401: + * description: Unauthorized + * 403: + * description: Forbidden + */ + router.get('/users', authenticate(), requirePermission('identity', 'read'), userManagementController.list.bind(userManagementController)); + + /** + * @swagger + * /api/{version}/identity/users/{id}: + * get: + * summary: Get user details + * description: Retrieve detailed information about a specific user + * tags: [Identity - Users] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * - in: path + * name: id + * required: true + * schema: + * type: string + * description: User ID + * responses: + * 200: + * description: User retrieved successfully + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/ApiResponse' + * - type: object + * properties: + * data: + * $ref: '#/components/schemas/User' + * 404: + * description: User not found + */ + router.get('/users/:id', authenticate(), requirePermission('identity', 'read'), userManagementController.get.bind(userManagementController)); + + /** + * @swagger + * /api/{version}/identity/users/{id}: + * put: + * summary: Update user + * description: Update user information + * tags: [Identity - Users] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * - in: path + * name: id + * required: true + * schema: + * type: string + * description: User ID + * requestBody: + * required: true + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/UpdateUserRequest' + * responses: + * 200: + * description: User updated successfully + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/ApiResponse' + * - type: object + * properties: + * data: + * $ref: '#/components/schemas/User' + */ + router.put('/users/:id', authenticate(), requirePermission('identity', 'update'), userManagementController.update.bind(userManagementController)); + + /** + * @swagger + * /api/{version}/identity/users/{id}: + * delete: + * summary: Delete user + * description: Delete a user account (soft delete or hard delete depending on policy) + * tags: [Identity - Users] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * - in: path + * name: id + * required: true + * schema: + * type: string + * description: User ID + * responses: + * 200: + * description: User deleted successfully + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ApiResponse' + */ + router.delete('/users/:id', authenticate(), requirePermission('identity', 'delete'), userManagementController.delete.bind(userManagementController)); + + /** + * @swagger + * /api/{version}/identity/users/{id}/deactivate: + * post: + * summary: Deactivate user + * description: Deactivate a user account without deleting it + * tags: [Identity - Users] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * - in: path + * name: id + * required: true + * schema: + * type: string + * description: User ID + * responses: + * 200: + * description: User deactivated successfully + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ApiResponse' + */ + router.post('/users/:id/deactivate', authenticate(), requirePermission('identity', 'update'), userManagementController.deactivate.bind(userManagementController)); + + /** + * @swagger + * /api/{version}/identity/users/{id}/reactivate: + * post: + * summary: Reactivate user + * description: Reactivate a previously deactivated user account + * tags: [Identity - Users] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * - in: path + * name: id + * required: true + * schema: + * type: string + * description: User ID + * responses: + * 200: + * description: User reactivated successfully + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ApiResponse' + */ + router.post('/users/:id/reactivate', authenticate(), requirePermission('identity', 'update'), userManagementController.reactivate.bind(userManagementController)); + + /** + * @swagger + * /api/{version}/identity/users/bulk-import: + * post: + * summary: Bulk import users + * description: Import multiple users from a file or JSON payload + * tags: [Identity - Users] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: [users] + * properties: + * users: + * type: array + * items: + * $ref: '#/components/schemas/CreateUserRequest' + * responses: + * 200: + * description: Users imported successfully + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/ApiResponse' + * - type: object + * properties: + * data: + * type: object + * properties: + * imported: + * type: integer + * failed: + * type: integer + */ + router.post('/users/bulk-import', authenticate(), requirePermission('identity', 'create'), userManagementController.bulkImport.bind(userManagementController)); + + /** + * @swagger + * /api/{version}/identity/users/bulk-export: + * get: + * summary: Bulk export users + * description: Export users to a file (CSV/JSON) + * tags: [Identity - Users] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * - in: query + * name: format + * schema: + * type: string + * enum: [csv, json] + * default: json + * responses: + * 200: + * description: Export successful + * content: + * application/json: + * schema: + * type: object // File download + */ + router.get('/users/bulk-export', authenticate(), requirePermission('identity', 'read'), userManagementController.bulkExport.bind(userManagementController)); + + // ============================================================================= + // EN: Profile Management Endpoints + // VI: Endpoints Quản Lý Profile + // ============================================================================= + + /** + * @swagger + * /api/{version}/identity/users/{id}/profile: + * get: + * summary: Get user profile + * description: Get profile information for a specific user + * tags: [Identity - Profile] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * - in: path + * name: id + * required: true + * schema: + * type: string + * description: User ID + * responses: + * 200: + * description: Profile retrieved successfully + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/ApiResponse' + * - type: object + * properties: + * data: + * $ref: '#/components/schemas/UserProfile' + */ + router.get('/users/:id/profile', authenticate(), profileController.get.bind(profileController)); + + /** + * @swagger + * /api/{version}/identity/users/{id}/profile: + * put: + * summary: Update user profile + * description: Update profile information for a specific user + * tags: [Identity - Profile] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * - in: path + * name: id + * required: true + * schema: + * type: string + * description: User ID + * requestBody: + * required: true + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/UpdateProfileRequest' + * responses: + * 200: + * description: Profile updated successfully + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/ApiResponse' + * - type: object + * properties: + * data: + * $ref: '#/components/schemas/UserProfile' + */ + router.put('/users/:id/profile', authenticate(), profileController.update.bind(profileController)); + + /** + * @swagger + * /api/{version}/identity/users/{id}/profile/avatar: + * post: + * summary: Upload avatar + * description: Upload a new avatar image for the user + * tags: [Identity - Profile] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * - in: path + * name: id + * required: true + * schema: + * type: string + * description: User ID + * requestBody: + * content: + * multipart/form-data: + * schema: + * type: object + * properties: + * avatar: + * type: string + * format: binary + * responses: + * 200: + * description: Avatar uploaded successfully + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ApiResponse' + */ + router.post('/users/:id/profile/avatar', authenticate(), profileController.uploadAvatar.bind(profileController)); + + /** + * @swagger + * /api/{version}/identity/users/{id}/profile/avatar: + * delete: + * summary: Delete avatar + * description: Remove the user's current avatar + * tags: [Identity - Profile] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * - in: path + * name: id + * required: true + * schema: + * type: string + * description: User ID + * responses: + * 200: + * description: Avatar deleted successfully + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ApiResponse' + */ + router.delete('/users/:id/profile/avatar', authenticate(), profileController.deleteAvatar.bind(profileController)); + + // ============================================================================= + // EN: Verification Endpoints + // VI: Endpoints Xác Thực + // ============================================================================= + + /** + * @swagger + * /api/{version}/identity/verification/email/request: + * post: + * summary: Request email verification + * description: Send an email verification link/code to the user + * tags: [Identity - Verification] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * responses: + * 200: + * description: Verification email sent + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ApiResponse' + */ + router.post('/verification/email/request', authenticate(), verificationController.requestEmail.bind(verificationController)); + + /** + * @swagger + * /api/{version}/identity/verification/email/verify: + * post: + * summary: Verify email + * description: Verify user's email address using the token + * tags: [Identity - Verification] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: [token] + * properties: + * token: + * type: string + * responses: + * 200: + * description: Email verified successfully + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ApiResponse' + */ + router.post('/verification/email/verify', verificationController.verifyEmail.bind(verificationController)); + + /** + * @swagger + * /api/{version}/identity/verification/phone/request: + * post: + * summary: Request phone verification + * description: Send an SMS verification code to the user + * tags: [Identity - Verification] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: [phoneNumber] + * properties: + * phoneNumber: + * type: string + * responses: + * 200: + * description: Verification SMS sent + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ApiResponse' + */ + router.post('/verification/phone/request', authenticate(), verificationController.requestPhone.bind(verificationController)); + + /** + * @swagger + * /api/{version}/identity/verification/phone/verify: + * post: + * summary: Verify phone + * description: Verify user's phone number using the code + * tags: [Identity - Verification] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: [code, phoneNumber] + * properties: + * code: + * type: string + * phoneNumber: + * type: string + * responses: + * 200: + * description: Phone verified successfully + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ApiResponse' + */ + router.post('/verification/phone/verify', verificationController.verifyPhone.bind(verificationController)); + + /** + * @swagger + * /api/{version}/identity/verification/{id}/status: + * get: + * summary: Get verification status + * description: Check the verification status of a user + * tags: [Identity - Verification] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * - in: path + * name: id + * required: true + * schema: + * type: string + * description: User ID + * responses: + * 200: + * description: Verification status + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/ApiResponse' + * - type: object + * properties: + * data: + * type: object + * properties: + * emailVerified: + * type: boolean + * phoneVerified: + * type: boolean + */ + router.get('/verification/:id/status', authenticate(), verificationController.getStatus.bind(verificationController)); + + // ============================================================================= + // EN: Organization Endpoints + // VI: Endpoints Tổ Chức + // ============================================================================= + + /** + * @swagger + * /api/{version}/identity/organizations: + * get: + * summary: List organizations + * description: List organizations visible to the current user + * tags: [Identity - Organizations] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * - in: query + * name: page + * schema: + * type: integer + * - in: query + * name: limit + * schema: + * type: integer + * responses: + * 200: + * description: Organizations list + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/ApiResponse' + * - type: object + * properties: + * data: + * type: array + * items: + * $ref: '#/components/schemas/Organization' + */ + router.get('/organizations', authenticate(), requirePermission('identity', 'read'), organizationController.list.bind(organizationController)); + + /** + * @swagger + * /api/{version}/identity/organizations: + * post: + * summary: Create organization + * description: Create a new organization + * tags: [Identity - Organizations] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * requestBody: + * required: true + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/CreateOrganizationRequest' + * responses: + * 201: + * description: Organization created + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/ApiResponse' + * - type: object + * properties: + * data: + * $ref: '#/components/schemas/Organization' + */ + router.post('/organizations', authenticate(), requirePermission('identity', 'create'), organizationController.create.bind(organizationController)); + + /** + * @swagger + * /api/{version}/identity/organizations/{id}: + * get: + * summary: Get organization + * description: Get organization details + * tags: [Identity - Organizations] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * - in: path + * name: id + * required: true + * schema: + * type: string + * description: Organization ID + * responses: + * 200: + * description: Organization details + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/ApiResponse' + * - type: object + * properties: + * data: + * $ref: '#/components/schemas/Organization' + */ + router.get('/organizations/:id', authenticate(), requirePermission('identity', 'read'), organizationController.get.bind(organizationController)); + + /** + * @swagger + * /api/{version}/identity/organizations/{id}: + * put: + * summary: Update organization + * description: Update organization details + * tags: [Identity - Organizations] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * - in: path + * name: id + * required: true + * schema: + * type: string + * description: Organization ID + * requestBody: + * required: true + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/UpdateOrganizationRequest' + * responses: + * 200: + * description: Organization updated + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/ApiResponse' + * - type: object + * properties: + * data: + * $ref: '#/components/schemas/Organization' + */ + router.put('/organizations/:id', authenticate(), requirePermission('identity', 'update'), organizationController.update.bind(organizationController)); + + /** + * @swagger + * /api/{version}/identity/organizations/{id}: + * delete: + * summary: Delete organization + * description: Delete an organization + * tags: [Identity - Organizations] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * - in: path + * name: id + * required: true + * schema: + * type: string + * description: Organization ID + * responses: + * 200: + * description: Organization deleted + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ApiResponse' + */ + router.delete('/organizations/:id', authenticate(), requirePermission('identity', 'delete'), organizationController.delete.bind(organizationController)); + + /** + * @swagger + * /api/{version}/identity/organizations/{id}/users: + * get: + * summary: Get organization users + * description: List users in an organization + * tags: [Identity - Organizations] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * - in: path + * name: id + * required: true + * schema: + * type: string + * description: Organization ID + * responses: + * 200: + * description: Users list + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/ApiResponse' + * - type: object + * properties: + * data: + * type: array + * items: + * $ref: '#/components/schemas/User' + */ + router.get('/organizations/:id/users', authenticate(), requirePermission('identity', 'read'), organizationController.getUsers.bind(organizationController)); + + // ============================================================================= + // EN: Group Endpoints + // VI: Endpoints Nhóm + // ============================================================================= + + /** + * @swagger + * /api/{version}/identity/organizations/{id}/groups: + * get: + * summary: List groups + * description: List groups in an organization + * tags: [Identity - Groups] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * - in: path + * name: id + * required: true + * schema: + * type: string + * description: Organization ID + * responses: + * 200: + * description: Groups list + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/ApiResponse' + * - type: object + * properties: + * data: + * type: array + * items: + * $ref: '#/components/schemas/Group' + */ + router.get('/organizations/:id/groups', authenticate(), requirePermission('identity', 'read'), groupController.list.bind(groupController)); + + /** + * @swagger + * /api/{version}/identity/organizations/{id}/groups: + * post: + * summary: Create group + * description: Create a new group in an organization + * tags: [Identity - Groups] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * - in: path + * name: id + * required: true + * schema: + * type: string + * description: Organization ID + * requestBody: + * required: true + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/CreateGroupRequest' + * responses: + * 201: + * description: Group created + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/ApiResponse' + * - type: object + * properties: + * data: + * $ref: '#/components/schemas/Group' + */ + router.post('/organizations/:id/groups', authenticate(), requirePermission('identity', 'create'), groupController.create.bind(groupController)); + + /** + * @swagger + * /api/{version}/identity/groups/{id}: + * get: + * summary: Get group + * description: Get group details + * tags: [Identity - Groups] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * - in: path + * name: id + * required: true + * schema: + * type: string + * description: Group ID + * responses: + * 200: + * description: Group details + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/ApiResponse' + * - type: object + * properties: + * data: + * $ref: '#/components/schemas/Group' + */ + router.get('/groups/:id', authenticate(), requirePermission('identity', 'read'), groupController.get.bind(groupController)); + + /** + * @swagger + * /api/{version}/identity/groups/{id}: + * put: + * summary: Update group + * description: Update group details + * tags: [Identity - Groups] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * - in: path + * name: id + * required: true + * schema: + * type: string + * description: Group ID + * requestBody: + * required: true + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/UpdateGroupRequest' + * responses: + * 200: + * description: Group updated + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/ApiResponse' + * - type: object + * properties: + * data: + * $ref: '#/components/schemas/Group' + */ + router.put('/groups/:id', authenticate(), requirePermission('identity', 'update'), groupController.update.bind(groupController)); + + /** + * @swagger + * /api/{version}/identity/groups/{id}: + * delete: + * summary: Delete group + * description: Delete a group + * tags: [Identity - Groups] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * - in: path + * name: id + * required: true + * schema: + * type: string + * description: Group ID + * responses: + * 200: + * description: Group deleted + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ApiResponse' + */ + router.delete('/groups/:id', authenticate(), requirePermission('identity', 'delete'), groupController.delete.bind(groupController)); + + /** + * @swagger + * /api/{version}/identity/groups/{id}/members: + * get: + * summary: Get group members + * description: List members of a group + * tags: [Identity - Groups] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * - in: path + * name: id + * required: true + * schema: + * type: string + * description: Group ID + * responses: + * 200: + * description: Members list + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/ApiResponse' + * - type: object + * properties: + * data: + * type: array + * items: + * $ref: '#/components/schemas/User' + */ + router.get('/groups/:id/members', authenticate(), requirePermission('identity', 'read'), groupController.getMembers.bind(groupController)); + + /** + * @swagger + * /api/{version}/identity/groups/{id}/members: + * post: + * summary: Add member to group + * description: Add a user to a group + * tags: [Identity - Groups] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * - in: path + * name: id + * required: true + * schema: + * type: string + * description: Group ID + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: [userId] + * properties: + * userId: + * type: string + * responses: + * 200: + * description: Member added + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ApiResponse' + */ + router.post('/groups/:id/members', authenticate(), requirePermission('identity', 'update'), groupController.addMember.bind(groupController)); + + /** + * @swagger + * /api/{version}/identity/groups/{id}/members/{userId}: + * delete: + * summary: Remove member from group + * description: Remove a user from a group + * tags: [Identity - Groups] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * - in: path + * name: id + * required: true + * schema: + * type: string + * description: Group ID + * - in: path + * name: userId + * required: true + * schema: + * type: string + * description: User ID to remove + * responses: + * 200: + * description: Member removed + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ApiResponse' + */ + router.delete('/groups/:id/members/:userId', authenticate(), requirePermission('identity', 'update'), groupController.removeMember.bind(groupController)); + + return router; +}; diff --git a/services/iam-service/src/modules/mfa/mfa.routes.ts b/services/iam-service/src/modules/mfa/mfa.routes.ts new file mode 100644 index 00000000..4c9eb348 --- /dev/null +++ b/services/iam-service/src/modules/mfa/mfa.routes.ts @@ -0,0 +1,220 @@ +import { Router } from 'express'; +import { authenticate } from '../../middlewares/auth.middleware'; +import { mfaController } from './mfa.controller'; + +/** + * EN: Create and configure MFA routes + * VI: Tạo và cấu hình routes cho MFA + */ +export const createMfaRouter = (): Router => { + const router = Router(); + + /** + * @swagger + * /api/{version}/mfa/totp/enable: + * post: + * summary: Enable TOTP + * description: Initiate process to enable TOTP (Time-based One-Time Password) for the user + * tags: [MFA] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * responses: + * 200: + * description: TOTP setup initiated (returns QR code URL or secret) + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/ApiResponse' + * - type: object + * properties: + * data: + * type: object + * properties: + * secret: + * type: string + * qrCode: + * type: string + * 401: + * description: Unauthorized + */ + router.post('/totp/enable', authenticate(), mfaController.enableTOTP.bind(mfaController)); + + /** + * @swagger + * /api/{version}/mfa/totp/verify: + * post: + * summary: Verify and Enable TOTP + * description: Verify the TOTP code and finalize enabling MFA + * tags: [MFA] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: [code] + * properties: + * code: + * type: string + * description: The 6-digit TOTP code + * responses: + * 200: + * description: TOTP verified and enabled successfully + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ApiResponse' + * 400: + * description: Invalid code + * 401: + * description: Unauthorized + */ + router.post('/totp/verify', authenticate(), mfaController.verifyAndEnableTOTP.bind(mfaController)); + + /** + * @swagger + * /api/{version}/mfa/totp/validate: + * post: + * summary: Validate TOTP + * description: Validate a TOTP code (for login or step-up auth) + * tags: [MFA] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: [code] + * properties: + * code: + * type: string + * description: The 6-digit TOTP code + * responses: + * 200: + * description: Code valid + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/ApiResponse' + * - type: object + * properties: + * data: + * type: object + * properties: + * valid: + * type: boolean + * 401: + * description: Unauthorized + */ + router.post('/totp/validate', authenticate(), mfaController.verifyTOTP.bind(mfaController)); + + /** + * @swagger + * /api/{version}/mfa/disable: + * post: + * summary: Disable MFA + * description: Disable Multi-Factor Authentication for the user + * tags: [MFA] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: [password] + * properties: + * password: + * type: string + * description: Current password for confirmation + * responses: + * 200: + * description: MFA disabled successfully + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ApiResponse' + * 400: + * description: Invalid password + * 401: + * description: Unauthorized + */ + router.post('/disable', authenticate(), mfaController.disableMFA.bind(mfaController)); + + /** + * @swagger + * /api/{version}/mfa/devices: + * get: + * summary: Get MFA devices + * description: List registered MFA devices + * tags: [MFA] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * responses: + * 200: + * description: Devices listed successfully + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/ApiResponse' + * - type: object + * properties: + * data: + * type: array + * items: + * type: object + * 401: + * description: Unauthorized + */ + router.get('/devices', authenticate(), mfaController.getMFADevices.bind(mfaController)); + + return router; +}; diff --git a/services/iam-service/src/modules/oidc/oidc.routes.ts b/services/iam-service/src/modules/oidc/oidc.routes.ts new file mode 100644 index 00000000..1c144bb8 --- /dev/null +++ b/services/iam-service/src/modules/oidc/oidc.routes.ts @@ -0,0 +1,197 @@ +import { Router } from 'express'; +import { authenticate } from '../../middlewares/auth.middleware'; +import { oidcController } from './oidc.controller'; + +/** + * EN: Create and configure OIDC routes + * VI: Tạo và cấu hình routes cho OIDC + */ +export const createOidcRouter = (): Router => { + const router = Router(); + + /** + * @swagger + * /.well-known/openid-configuration: + * get: + * summary: OIDC Discovery + * description: Get OpenID Connect configuration and metadata + * tags: [OIDC] + * responses: + * 200: + * description: OIDC configuration + * content: + * application/json: + * schema: + * type: object + * properties: + * issuer: + * type: string + * authorization_endpoint: + * type: string + * token_endpoint: + * type: string + * userinfo_endpoint: + * type: string + * jwks_uri: + * type: string + */ + // Note: This route is usually mounted at root, not under /api/{version} + // If mounted under /api/{version}, adjust paths accordingly + router.get('/.well-known/openid-configuration', oidcController.discovery.bind(oidcController)); + + /** + * @swagger + * /api/{version}/oidc/authorize: + * get: + * summary: OIDC Authorization + * description: Authorization endpoint for OIDC flow + * tags: [OIDC] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * - in: query + * name: response_type + * required: true + * schema: + * type: string + * - in: query + * name: client_id + * required: true + * schema: + * type: string + * - in: query + * name: redirect_uri + * required: true + * schema: + * type: string + * - in: query + * name: scope + * schema: + * type: string + * - in: query + * name: state + * schema: + * type: string + * responses: + * 302: + * description: Redirect to callback + * 400: + * description: Invalid request + */ + router.get('/authorize', authenticate(), oidcController.authorize.bind(oidcController)); + + /** + * @swagger + * /api/{version}/oidc/token: + * post: + * summary: OIDC Token + * description: Token endpoint for OIDC flow + * tags: [OIDC] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * requestBody: + * required: true + * content: + * application/x-www-form-urlencoded: + * schema: + * type: object + * properties: + * grant_type: + * type: string + * code: + * type: string + * redirect_uri: + * type: string + * client_id: + * type: string + * client_secret: + * type: string + * responses: + * 200: + * description: ID Token and Access Token + * 400: + * description: Invalid request + */ + router.post('/token', oidcController.token.bind(oidcController)); + + /** + * @swagger + * /api/{version}/oidc/userinfo: + * get: + * summary: OIDC UserInfo + * description: Get claims about the authenticated end-user + * tags: [OIDC] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * responses: + * 200: + * description: User information + * content: + * application/json: + * schema: + * type: object + * properties: + * sub: + * type: string + * name: + * type: string + * email: + * type: string + * 401: + * description: Unauthorized + */ + router.get('/userinfo', authenticate(), oidcController.userinfo.bind(oidcController)); + + /** + * @swagger + * /api/{version}/oidc/jwks: + * get: + * summary: OIDC JWKS + * description: JSON Web Key Set for verifying signatures + * tags: [OIDC] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * responses: + * 200: + * description: JWKS + * content: + * application/json: + * schema: + * type: object + * properties: + * keys: + * type: array + * items: + * type: object + */ + router.get('/jwks', oidcController.jwks.bind(oidcController)); + + return router; +}; diff --git a/services/iam-service/src/modules/rbac/rbac.routes.ts b/services/iam-service/src/modules/rbac/rbac.routes.ts new file mode 100644 index 00000000..321a758f --- /dev/null +++ b/services/iam-service/src/modules/rbac/rbac.routes.ts @@ -0,0 +1,234 @@ +import { Router } from 'express'; +import { authenticate } from '../../middlewares/auth.middleware'; +import { requirePermission } from '../../middlewares/rbac.middleware'; +import { rbacController } from './rbac.controller'; + +/** + * EN: Create and configure RBAC routes + * VI: Tạo và cấu hình routes cho RBAC + */ +export const createRbacRouter = (): Router => { + const router = Router(); + + /** + * @swagger + * /api/{version}/rbac/permissions: + * get: + * summary: Get user permissions + * description: Get list of permissions assigned to the current user + * tags: [RBAC] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * responses: + * 200: + * description: Permissions retrieved successfully + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/ApiResponse' + * - type: object + * properties: + * data: + * type: array + * items: + * type: string + * 401: + * description: Unauthorized + */ + router.get('/permissions', authenticate(), rbacController.getUserPermissions.bind(rbacController)); + + /** + * @swagger + * /api/{version}/rbac/roles/assign: + * post: + * summary: Assign role to user + * description: Assign a specific role to a user. Requires 'rbac:assign' permission. + * tags: [RBAC] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: [userId, roleId] + * properties: + * userId: + * type: string + * roleId: + * type: string + * responses: + * 200: + * description: Role assigned successfully + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ApiResponse' + * 401: + * description: Unauthorized + * 403: + * description: Forbidden - Insufficient permissions + * 404: + * description: User or Role not found + */ + router.post('/roles/assign', authenticate(), requirePermission('rbac', 'assign'), rbacController.assignRole.bind(rbacController)); + + /** + * @swagger + * /api/{version}/rbac/roles/revoke: + * post: + * summary: Revoke role from user + * description: Revoke a specific role from a user. Requires 'rbac:revoke' permission. + * tags: [RBAC] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: [userId, roleId] + * properties: + * userId: + * type: string + * roleId: + * type: string + * responses: + * 200: + * description: Role revoked successfully + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ApiResponse' + * 401: + * description: Unauthorized + * 403: + * description: Forbidden - Insufficient permissions + * 404: + * description: User or Role not found + */ + router.post('/roles/revoke', authenticate(), requirePermission('rbac', 'revoke'), rbacController.revokeRole.bind(rbacController)); + + /** + * @swagger + * /api/{version}/rbac/permissions/grant: + * post: + * summary: Grant permission to role + * description: Grant a specific permission to a role. Requires 'rbac:grant' permission. + * tags: [RBAC] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: [roleId, permissionId] + * properties: + * roleId: + * type: string + * permissionId: + * type: string + * responses: + * 200: + * description: Permission granted successfully + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ApiResponse' + * 401: + * description: Unauthorized + * 403: + * description: Forbidden - Insufficient permissions + * 404: + * description: Role or Permission not found + */ + router.post('/permissions/grant', authenticate(), requirePermission('rbac', 'grant'), rbacController.grantPermission.bind(rbacController)); + + /** + * @swagger + * /api/{version}/rbac/permissions/check: + * get: + * summary: Check permission + * description: Check if the current user has a specific permission + * tags: [RBAC] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * - in: query + * name: resource + * required: true + * schema: + * type: string + * - in: query + * name: action + * required: true + * schema: + * type: string + * responses: + * 200: + * description: Permission check result + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/ApiResponse' + * - type: object + * properties: + * data: + * type: object + * properties: + * allowed: + * type: boolean + * 401: + * description: Unauthorized + * 403: + * description: Forbidden + */ + router.get('/permissions/check', authenticate(), rbacController.checkPermission.bind(rbacController)); + + return router; +}; diff --git a/services/iam-service/src/modules/session/session.routes.ts b/services/iam-service/src/modules/session/session.routes.ts new file mode 100644 index 00000000..fbf74e8d --- /dev/null +++ b/services/iam-service/src/modules/session/session.routes.ts @@ -0,0 +1,115 @@ +import { Router } from 'express'; +import { authenticate } from '../../middlewares/auth.middleware'; +import { sessionsController } from './sessions.controller'; + +/** + * EN: Create and configure session routes + * VI: Tạo và cấu hình routes cho session + */ +export const createSessionRouter = (): Router => { + const router = Router(); + + /** + * @swagger + * /api/{version}/sessions: + * get: + * summary: Get active sessions + * description: List all active sessions for the current user + * tags: [Sessions] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * responses: + * 200: + * description: Sessions retrieved successfully + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/ApiResponse' + * - type: object + * properties: + * data: + * type: array + * items: + * $ref: '#/components/schemas/Session' + * 401: + * description: Unauthorized + */ + router.get('/', authenticate(), sessionsController.getUserSessions.bind(sessionsController)); + + /** + * @swagger + * /api/{version}/sessions: + * delete: + * summary: Revoke all sessions + * description: Revoke all active sessions for the current user except the current one + * tags: [Sessions] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * responses: + * 200: + * description: All sessions revoked successfully + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ApiResponse' + * 401: + * description: Unauthorized + */ + router.delete('/', authenticate(), sessionsController.revokeAllSessions.bind(sessionsController)); + + /** + * @swagger + * /api/{version}/sessions/{sessionId}: + * delete: + * summary: Revoke session + * description: Revoke a specific session + * tags: [Sessions] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: version + * required: true + * schema: + * type: string + * default: v1 + * description: API version + * - in: path + * name: sessionId + * required: true + * schema: + * type: string + * description: Session ID to revoke + * responses: + * 200: + * description: Session revoked successfully + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ApiResponse' + * 401: + * description: Unauthorized + * 404: + * description: Session not found + */ + router.delete('/:sessionId', authenticate(), sessionsController.revokeSession.bind(sessionsController)); + + return router; +}; diff --git a/services/iam-service/src/routes/index.ts b/services/iam-service/src/routes/index.ts index f000998a..6e6ae6b1 100644 --- a/services/iam-service/src/routes/index.ts +++ b/services/iam-service/src/routes/index.ts @@ -1,304 +1,57 @@ -import { ApiResponse } from '@goodgo/types'; import { Router } from 'express'; +import { createAuthRouter } from '../modules/auth/auth.routes'; +import { createIdentityRouter } from '../modules/identity/identity.routes'; +import { createAccessRouter } from '../modules/access/access.routes'; +import { createGovernanceRouter } from '../modules/governance/governance.routes'; +import { createSessionRouter } from '../modules/session/session.routes'; +import { createOidcRouter } from '../modules/oidc/oidc.routes'; +import { createRbacRouter } from '../modules/rbac/rbac.routes'; +import { createMfaRouter } from '../modules/mfa/mfa.routes'; - -import { authenticate } from '../middlewares/auth.middleware'; -import { dynamicRateLimit } from '../middlewares/rate-limit.middleware'; -import { requirePermission } from '../middlewares/rbac.middleware'; -import { zeroTrustMiddleware } from '../middlewares/zero-trust.middleware'; -import { accessAnalyticsController } from '../modules/access/analytics'; -import { accessRequestController } from '../modules/access/request'; -import { accessReviewController } from '../modules/access/review'; -import { authController } from '../modules/auth/auth.controller'; -import { changePasswordController } from '../modules/auth/change-password.controller'; -import { complianceController } from '../modules/governance/compliance'; -import { policyGovernanceController } from '../modules/governance/policy'; -import { reportingController } from '../modules/governance/reporting'; -import { riskController } from '../modules/governance/risk'; -import { HealthController } from '../modules/health/health.controller'; -import { groupController } from '../modules/identity/group'; -import { organizationController } from '../modules/identity/organization'; -import { profileController } from '../modules/identity/profile'; -import { userManagementController } from '../modules/identity/user'; -import { verificationController } from '../modules/identity/verification'; -import { MetricsController } from '../modules/metrics/metrics.controller'; -import { mfaController } from '../modules/mfa/mfa.controller'; -import { oidcController } from '../modules/oidc/oidc.controller'; -import { rbacController } from '../modules/rbac/rbac.controller'; -import { sessionsController } from '../modules/session/sessions.controller'; -import { socialAuthController } from '../modules/social/social.controller'; - - +/** + * EN: Create and configure main application router + * VI: Tạo và cấu hình router chính cho ứng dụng + */ export const createRouter = (): Router => { const router = Router(); - const healthController = new HealthController(); - const apiVersion = process.env.API_VERSION || 'v1'; - - // EN: Health check endpoints - // VI: Endpoints kiểm tra sức khỏe - /** - * @swagger - * /health: - * get: - * summary: Basic liveness probe - * description: Returns basic health status for liveness probes - * tags: [Health] - * responses: - * 200: - * description: Service is healthy - * content: - * application/json: - * schema: - * $ref: '#/components/schemas/HealthResponse' - */ - router.get('/health', healthController.health); - - /** - * @swagger - * /health/ready: - * get: - * summary: Readiness probe - * description: Checks if service is ready to handle requests (includes database connectivity) - * tags: [Health] - * responses: - * 200: - * description: Service is ready - * content: - * application/json: - * schema: - * $ref: '#/components/schemas/ReadinessResponse' - * 503: - * description: Service is not ready - * content: - * application/json: - * schema: - * $ref: '#/components/schemas/ErrorResponse' - */ - router.get('/health/ready', healthController.ready); - - /** - * @swagger - * /health/live: - * get: - * summary: Liveness probe - * description: Basic liveness check for container orchestration - * tags: [Health] - * responses: - * 200: - * description: Service is alive - * content: - * application/json: - * schema: - * $ref: '#/components/schemas/LivenessResponse' - */ - router.get('/health/live', healthController.live); - - // EN: Apply zero-trust and rate limiting to all routes - // VI: Áp dụng zero-trust và rate limiting cho tất cả routes - router.use(zeroTrustMiddleware); - router.use(dynamicRateLimit); - - // EN: Auth endpoints - // VI: Endpoints xác thực - router.post(`/api/${apiVersion}/auth/register`, authController.register.bind(authController)); - router.post(`/api/${apiVersion}/auth/login`, authController.login.bind(authController)); - router.post(`/api/${apiVersion}/auth/logout`, authenticate(), authController.logout.bind(authController)); - router.post(`/api/${apiVersion}/auth/refresh`, authController.refreshToken.bind(authController)); - - // EN: Change password - // VI: Đổi mật khẩu - router.post(`/api/${apiVersion}/auth/change-password`, authenticate(), changePasswordController.changePassword.bind(changePasswordController)); - - // EN: Session management endpoints - // VI: Endpoints quản lý session - router.get(`/api/${apiVersion}/sessions`, authenticate(), sessionsController.getUserSessions.bind(sessionsController)); - router.delete(`/api/${apiVersion}/sessions/:sessionId`, authenticate(), sessionsController.revokeSession.bind(sessionsController)); - router.delete(`/api/${apiVersion}/sessions`, authenticate(), sessionsController.revokeAllSessions.bind(sessionsController)); - - // EN: Get current user - // VI: Lấy thông tin người dùng hiện tại - router.get(`/api/${apiVersion}/auth/me`, authenticate(), (req, res) => { - const response: ApiResponse = { - success: true, - data: req.user, - message: 'User information retrieved / Thông tin người dùng đã được lấy', - timestamp: new Date().toISOString(), - }; - res.json(response); + // EN: Health Check + // VI: Kiểm tra trạng thái + router.get('/health', (req, res) => { + res.json({ status: 'ok', timestamp: new Date().toISOString() }); }); - // EN: Social Auth endpoints - // VI: Endpoints xác thực mạng xã hội - router.get(`/api/${apiVersion}/auth/google`, socialAuthController.googleAuth.bind(socialAuthController)); - router.get(`/api/${apiVersion}/auth/google/callback`, socialAuthController.googleCallback.bind(socialAuthController)); - router.get(`/api/${apiVersion}/auth/facebook`, socialAuthController.facebookAuth.bind(socialAuthController)); - router.get(`/api/${apiVersion}/auth/facebook/callback`, socialAuthController.facebookCallback.bind(socialAuthController)); - router.get(`/api/${apiVersion}/auth/github`, socialAuthController.githubAuth.bind(socialAuthController)); - router.get(`/api/${apiVersion}/auth/github/callback`, socialAuthController.githubCallback.bind(socialAuthController)); + // EN: Auth Routes + // VI: Routes xác thực + router.use('/auth', createAuthRouter()); - // EN: OIDC endpoints - // VI: Endpoints OIDC - router.get('/.well-known/openid-configuration', oidcController.discovery.bind(oidcController)); - router.get(`/api/${apiVersion}/oidc/authorize`, authenticate(), oidcController.authorize.bind(oidcController)); - router.post(`/api/${apiVersion}/oidc/token`, oidcController.token.bind(oidcController)); - router.get(`/api/${apiVersion}/oidc/userinfo`, authenticate(), oidcController.userinfo.bind(oidcController)); - router.get(`/api/${apiVersion}/oidc/jwks`, oidcController.jwks.bind(oidcController)); + // EN: Session Routes + // VI: Routes phiên làm việc + router.use('/session', createSessionRouter()); - // EN: RBAC endpoints - // VI: Endpoints RBAC - router.get(`/api/${apiVersion}/rbac/permissions`, authenticate(), rbacController.getUserPermissions.bind(rbacController)); - router.post(`/api/${apiVersion}/rbac/roles/assign`, authenticate(), requirePermission('rbac', 'assign'), rbacController.assignRole.bind(rbacController)); - router.post(`/api/${apiVersion}/rbac/roles/revoke`, authenticate(), requirePermission('rbac', 'revoke'), rbacController.revokeRole.bind(rbacController)); - router.post(`/api/${apiVersion}/rbac/permissions/grant`, authenticate(), requirePermission('rbac', 'grant'), rbacController.grantPermission.bind(rbacController)); - router.get(`/api/${apiVersion}/rbac/permissions/check`, authenticate(), rbacController.checkPermission.bind(rbacController)); + // EN: OIDC Routes + // VI: Routes OpenID Connect + router.use('/oidc', createOidcRouter()); - // EN: MFA endpoints - // VI: Endpoints MFA - router.post(`/api/${apiVersion}/mfa/totp/enable`, authenticate(), mfaController.enableTOTP.bind(mfaController)); - router.post(`/api/${apiVersion}/mfa/totp/verify`, authenticate(), mfaController.verifyAndEnableTOTP.bind(mfaController)); - router.post(`/api/${apiVersion}/mfa/totp/validate`, authenticate(), mfaController.verifyTOTP.bind(mfaController)); - router.post(`/api/${apiVersion}/mfa/disable`, authenticate(), mfaController.disableMFA.bind(mfaController)); - router.get(`/api/${apiVersion}/mfa/devices`, authenticate(), mfaController.getMFADevices.bind(mfaController)); + // EN: RBAC Routes + // VI: Routes phân quyền + router.use('/rbac', createRbacRouter()); - // ============================================================================= - // EN: Identity Management Endpoints - // VI: Endpoints Quản Lý Danh Tính - // ============================================================================= + // EN: MFA Routes + // VI: Routes xác thực đa yếu tố + router.use('/mfa', createMfaRouter()); - // EN: User Management - // VI: Quản lý người dùng - router.get(`/api/${apiVersion}/identity/users`, authenticate(), requirePermission('identity', 'read'), userManagementController.list.bind(userManagementController)); - router.get(`/api/${apiVersion}/identity/users/:id`, authenticate(), requirePermission('identity', 'read'), userManagementController.get.bind(userManagementController)); - router.put(`/api/${apiVersion}/identity/users/:id`, authenticate(), requirePermission('identity', 'update'), userManagementController.update.bind(userManagementController)); - router.delete(`/api/${apiVersion}/identity/users/:id`, authenticate(), requirePermission('identity', 'delete'), userManagementController.delete.bind(userManagementController)); - router.post(`/api/${apiVersion}/identity/users/:id/deactivate`, authenticate(), requirePermission('identity', 'update'), userManagementController.deactivate.bind(userManagementController)); - router.post(`/api/${apiVersion}/identity/users/:id/reactivate`, authenticate(), requirePermission('identity', 'update'), userManagementController.reactivate.bind(userManagementController)); - router.post(`/api/${apiVersion}/identity/users/bulk-import`, authenticate(), requirePermission('identity', 'create'), userManagementController.bulkImport.bind(userManagementController)); - router.get(`/api/${apiVersion}/identity/users/bulk-export`, authenticate(), requirePermission('identity', 'read'), userManagementController.bulkExport.bind(userManagementController)); + // EN: Identity Routes + // VI: Routes định danh + router.use('/identity', createIdentityRouter()); - // EN: Profile Management - // VI: Quản lý profile - router.get(`/api/${apiVersion}/identity/users/:id/profile`, authenticate(), profileController.get.bind(profileController)); - router.put(`/api/${apiVersion}/identity/users/:id/profile`, authenticate(), profileController.update.bind(profileController)); - router.post(`/api/${apiVersion}/identity/users/:id/profile/avatar`, authenticate(), profileController.uploadAvatar.bind(profileController)); - router.delete(`/api/${apiVersion}/identity/users/:id/profile/avatar`, authenticate(), profileController.deleteAvatar.bind(profileController)); + // EN: Access Routes + // VI: Routes truy cập + router.use('/access', createAccessRouter()); - // EN: Identity Verification - // VI: Xác thực danh tính - router.post(`/api/${apiVersion}/identity/verification/email/request`, authenticate(), verificationController.requestEmail.bind(verificationController)); - router.post(`/api/${apiVersion}/identity/verification/email/verify`, verificationController.verifyEmail.bind(verificationController)); - router.post(`/api/${apiVersion}/identity/verification/phone/request`, authenticate(), verificationController.requestPhone.bind(verificationController)); - router.post(`/api/${apiVersion}/identity/verification/phone/verify`, verificationController.verifyPhone.bind(verificationController)); - router.get(`/api/${apiVersion}/identity/verification/:id/status`, authenticate(), verificationController.getStatus.bind(verificationController)); - - // EN: Organizations - // VI: Tổ chức - router.get(`/api/${apiVersion}/identity/organizations`, authenticate(), requirePermission('identity', 'read'), organizationController.list.bind(organizationController)); - router.post(`/api/${apiVersion}/identity/organizations`, authenticate(), requirePermission('identity', 'create'), organizationController.create.bind(organizationController)); - router.get(`/api/${apiVersion}/identity/organizations/:id`, authenticate(), requirePermission('identity', 'read'), organizationController.get.bind(organizationController)); - router.put(`/api/${apiVersion}/identity/organizations/:id`, authenticate(), requirePermission('identity', 'update'), organizationController.update.bind(organizationController)); - router.delete(`/api/${apiVersion}/identity/organizations/:id`, authenticate(), requirePermission('identity', 'delete'), organizationController.delete.bind(organizationController)); - router.get(`/api/${apiVersion}/identity/organizations/:id/users`, authenticate(), requirePermission('identity', 'read'), organizationController.getUsers.bind(organizationController)); - - // EN: Groups - // VI: Nhóm - router.get(`/api/${apiVersion}/identity/organizations/:id/groups`, authenticate(), requirePermission('identity', 'read'), groupController.list.bind(groupController)); - router.post(`/api/${apiVersion}/identity/organizations/:id/groups`, authenticate(), requirePermission('identity', 'create'), groupController.create.bind(groupController)); - router.get(`/api/${apiVersion}/identity/groups/:id`, authenticate(), requirePermission('identity', 'read'), groupController.get.bind(groupController)); - router.put(`/api/${apiVersion}/identity/groups/:id`, authenticate(), requirePermission('identity', 'update'), groupController.update.bind(groupController)); - router.delete(`/api/${apiVersion}/identity/groups/:id`, authenticate(), requirePermission('identity', 'delete'), groupController.delete.bind(groupController)); - router.get(`/api/${apiVersion}/identity/groups/:id/members`, authenticate(), requirePermission('identity', 'read'), groupController.getMembers.bind(groupController)); - router.post(`/api/${apiVersion}/identity/groups/:id/members`, authenticate(), requirePermission('identity', 'update'), groupController.addMember.bind(groupController)); - router.delete(`/api/${apiVersion}/identity/groups/:id/members/:userId`, authenticate(), requirePermission('identity', 'update'), groupController.removeMember.bind(groupController)); - - // ============================================================================= - // EN: Access Management Endpoints - // VI: Endpoints Quản Lý Truy Cập - // ============================================================================= - - // EN: Access Requests - // VI: Yêu cầu truy cập - router.get(`/api/${apiVersion}/access/requests`, authenticate(), accessRequestController.list.bind(accessRequestController)); - router.post(`/api/${apiVersion}/access/requests`, authenticate(), accessRequestController.create.bind(accessRequestController)); - router.get(`/api/${apiVersion}/access/requests/:id`, authenticate(), accessRequestController.get.bind(accessRequestController)); - router.put(`/api/${apiVersion}/access/requests/:id/approve`, authenticate(), requirePermission('access', 'approve'), accessRequestController.approve.bind(accessRequestController)); - router.put(`/api/${apiVersion}/access/requests/:id/reject`, authenticate(), requirePermission('access', 'approve'), accessRequestController.reject.bind(accessRequestController)); - router.delete(`/api/${apiVersion}/access/requests/:id`, authenticate(), accessRequestController.cancel.bind(accessRequestController)); - - // EN: Access Reviews - // VI: Đánh giá truy cập - router.get(`/api/${apiVersion}/access/reviews`, authenticate(), requirePermission('access', 'read'), accessReviewController.list.bind(accessReviewController)); - router.post(`/api/${apiVersion}/access/reviews`, authenticate(), requirePermission('access', 'create'), accessReviewController.create.bind(accessReviewController)); - router.get(`/api/${apiVersion}/access/reviews/:id`, authenticate(), requirePermission('access', 'read'), accessReviewController.get.bind(accessReviewController)); - router.post(`/api/${apiVersion}/access/reviews/:id/start`, authenticate(), requirePermission('access', 'update'), accessReviewController.start.bind(accessReviewController)); - router.post(`/api/${apiVersion}/access/reviews/:id/complete`, authenticate(), requirePermission('access', 'update'), accessReviewController.complete.bind(accessReviewController)); - router.get(`/api/${apiVersion}/access/reviews/:id/items`, authenticate(), requirePermission('access', 'read'), accessReviewController.getItems.bind(accessReviewController)); - router.put(`/api/${apiVersion}/access/reviews/:id/items/:itemId/review`, authenticate(), requirePermission('access', 'update'), accessReviewController.reviewItem.bind(accessReviewController)); - - // EN: Access Analytics - // VI: Phân tích truy cập - router.get(`/api/${apiVersion}/access/analytics/usage`, authenticate(), requirePermission('access', 'read'), accessAnalyticsController.usage.bind(accessAnalyticsController)); - router.get(`/api/${apiVersion}/access/analytics/permissions`, authenticate(), requirePermission('access', 'read'), accessAnalyticsController.permissions.bind(accessAnalyticsController)); - router.get(`/api/${apiVersion}/access/analytics/users/:id/summary`, authenticate(), requirePermission('access', 'read'), accessAnalyticsController.userSummary.bind(accessAnalyticsController)); - router.get(`/api/${apiVersion}/access/analytics/risks`, authenticate(), requirePermission('access', 'read'), accessAnalyticsController.risks.bind(accessAnalyticsController)); - - // ============================================================================= - // EN: Governance Endpoints - // VI: Endpoints Quản Trị - // ============================================================================= - - // EN: Compliance Reports - // VI: Báo cáo tuân thủ - router.get(`/api/${apiVersion}/governance/compliance/reports`, authenticate(), requirePermission('governance', 'read'), complianceController.list.bind(complianceController)); - router.post(`/api/${apiVersion}/governance/compliance/reports/generate`, authenticate(), requirePermission('governance', 'create'), complianceController.generate.bind(complianceController)); - router.get(`/api/${apiVersion}/governance/compliance/reports/:id`, authenticate(), requirePermission('governance', 'read'), complianceController.get.bind(complianceController)); - router.get(`/api/${apiVersion}/governance/compliance/reports/:id/export`, authenticate(), requirePermission('governance', 'read'), complianceController.export.bind(complianceController)); - router.post(`/api/${apiVersion}/governance/compliance/reports/:id/publish`, authenticate(), requirePermission('governance', 'update'), complianceController.publish.bind(complianceController)); - - // EN: Policy Governance - // VI: Quản trị policy - router.get(`/api/${apiVersion}/governance/policies/templates`, authenticate(), requirePermission('governance', 'read'), policyGovernanceController.getTemplates.bind(policyGovernanceController)); - router.post(`/api/${apiVersion}/governance/policies/templates`, authenticate(), requirePermission('governance', 'create'), policyGovernanceController.createTemplate.bind(policyGovernanceController)); - router.get(`/api/${apiVersion}/governance/policies/:id/versions`, authenticate(), requirePermission('governance', 'read'), policyGovernanceController.getVersions.bind(policyGovernanceController)); - router.post(`/api/${apiVersion}/governance/policies/:id/test`, authenticate(), requirePermission('governance', 'read'), policyGovernanceController.test.bind(policyGovernanceController)); - - // EN: Risk Management - // VI: Quản lý rủi ro - router.get(`/api/${apiVersion}/governance/risk/scores`, authenticate(), requirePermission('governance', 'read'), riskController.list.bind(riskController)); - router.get(`/api/${apiVersion}/governance/risk/scores/:userId`, authenticate(), requirePermission('governance', 'read'), riskController.getScore.bind(riskController)); - router.post(`/api/${apiVersion}/governance/risk/calculate`, authenticate(), requirePermission('governance', 'update'), riskController.calculate.bind(riskController)); - router.get(`/api/${apiVersion}/governance/risk/dashboard`, authenticate(), requirePermission('governance', 'read'), riskController.dashboard.bind(riskController)); - - // EN: Reporting - // VI: Báo cáo - router.get(`/api/${apiVersion}/governance/reports/access-summary`, authenticate(), requirePermission('governance', 'read'), reportingController.accessSummary.bind(reportingController)); - router.get(`/api/${apiVersion}/governance/reports/user-activity`, authenticate(), requirePermission('governance', 'read'), reportingController.userActivity.bind(reportingController)); - router.get(`/api/${apiVersion}/governance/reports/security-events`, authenticate(), requirePermission('governance', 'read'), reportingController.securityEvents.bind(reportingController)); - router.get(`/api/${apiVersion}/governance/reports/compliance-status`, authenticate(), requirePermission('governance', 'read'), reportingController.complianceStatus.bind(reportingController)); - router.get(`/api/${apiVersion}/governance/reports/risk-overview`, authenticate(), requirePermission('governance', 'read'), reportingController.riskOverview.bind(reportingController)); - - // EN: Metrics endpoint - // VI: Endpoint metrics - /** - * @swagger - * /metrics: - * get: - * summary: Get Prometheus metrics - * description: Returns application metrics in Prometheus format for monitoring - * tags: [Monitoring] - * responses: - * 200: - * description: Metrics in Prometheus format - * content: - * text/plain: - * schema: - * type: string - * example: "# HELP http_requests_total Total number of HTTP requests\n# TYPE http_requests_total counter\nhttp_requests_total{method=\"GET\",route=\"/health\",status=\"200\"} 42" - */ - const metricsController = new MetricsController(); - router.get('/metrics', metricsController.getMetrics); + // EN: Governance Routes + // VI: Routes quản trị + router.use('/governance', createGovernanceRouter()); return router; }; -