Implement Authentik Integration and Simplify RBAC Structure

- Updated nuxt.config.js to include Authentik configuration and public keys for client-side access.
- Introduced a new composable, useAuth.js, for handling authentication logic with Authentik, including user validation, login, and logout functionalities.
- Enhanced documentation to reflect the simplified RBAC structure and the integration of Authentik, emphasizing user-centric design and streamlined permission management.
- Refactored middleware for authentication checks and improved error handling during user validation.
- Created new pages for login and dashboard, ensuring proper routing and user experience.
- Removed obsolete Metabase integration and unnecessary complexity from the project structure.
This commit is contained in:
Afiq 2025-05-31 19:15:21 +08:00
parent a2a81bd3bb
commit 379eb17246
32 changed files with 1280 additions and 1951 deletions

80
composables/useAuth.js Normal file
View File

@ -0,0 +1,80 @@
// Authentication composable for Authentik integration
export const useAuth = () => {
const user = ref(null);
const isAuthenticated = ref(false);
const isLoading = ref(false);
// Check if user is authenticated
const checkAuth = async () => {
isLoading.value = true;
try {
const response = await $fetch('/api/auth/validate');
if (response.statusCode === 200) {
user.value = response.user;
isAuthenticated.value = true;
return true;
} else {
user.value = null;
isAuthenticated.value = false;
return false;
}
} catch (error) {
user.value = null;
isAuthenticated.value = false;
return false;
} finally {
isLoading.value = false;
}
};
// Get current user info
const getCurrentUser = async () => {
try {
const userData = await $fetch('/api/auth/me');
user.value = userData;
isAuthenticated.value = true;
return userData;
} catch (error) {
user.value = null;
isAuthenticated.value = false;
throw error;
}
};
// Login redirect
const login = () => {
return navigateTo('/api/auth/login', { external: true });
};
// Logout
const logout = () => {
user.value = null;
isAuthenticated.value = false;
return navigateTo('/api/auth/logout', { external: true });
};
// Require authentication (for route guards)
const requireAuth = async () => {
const isAuth = await checkAuth();
if (!isAuth) {
throw createError({
statusCode: 401,
statusMessage: 'Authentication required'
});
}
return user.value;
};
return {
user: readonly(user),
isAuthenticated: readonly(isAuthenticated),
isLoading: readonly(isLoading),
checkAuth,
getCurrentUser,
login,
logout,
requireAuth
};
};

View File

@ -1,10 +1,10 @@
# CorradAF Features Overview
This document provides a comprehensive overview of all implemented features in the CorradAF RBAC system. **Major Update**: The system has been redesigned with native Authentik integration, enhanced UX patterns, and comprehensive resource management.
This document provides a comprehensive overview of all implemented features in the CorradAF RBAC system. **Major Update**: The system has been redesigned with a simplified, application-centric RBAC hierarchy: **User → Roles → Sub Group (optional) → Groups → Application**.
## 🎯 Core System Features
### 1. User Management System ✅ **Enhanced**
### 1. User Management System ✅ **Simplified**
#### ✅ User Listing & Overview (`/users`)
- **Advanced Data Table**: RsTable with built-in search, sorting, and filtering
@ -15,22 +15,24 @@ This document provides a comprehensive overview of all implemented features in t
- **Pagination**: Configurable page sizes (10 items default)
- **Search & Filter**: Global search across all user data
- **Column Management**: Hide/show columns via filter dropdown
- **✅ Updated**: Removed manual sync buttons and Authentik status indicators (native integration)
#### ✅ User Creation (`/users/create`)
- **Streamlined Form**: Organized sections with `:actions="false"` for custom buttons
- **Basic Information**: Username, email, first/last name
#### ✅ User Creation (`/users/create`) - **Application-Centric**
- **Basic Information**: First name, last name, username, email
- **Application Assignment**: **REQUIRED** - Users must belong to an application
- **Password Management**:
- Secure password generation
- Password strength indicators
- Confirmation validation
- **Profile Details**: Phone, department, job title, employee ID
- **Account Settings**: Active status, email verification, password change requirements
- **Group Assignment**: Multi-select checkboxes for groups with descriptions
- **Role Assignment**: Multi-select checkboxes for roles with descriptions
- **Notification Settings**: ✅ **Simplified** - Email invitation system (simplified from Authentik Integration section)
- **Group Assignment (Primary)**:
- Users inherit permissions through groups
- Groups are filtered by selected application
- Groups contain collections of roles
- **Additional Roles (Optional)**:
- Direct role assignment for specific cases
- Filtered by selected application
- **Account Settings**: Active status, password change requirements, email invitations
- **Smart Filtering**: Groups and roles automatically filter based on application selection
- **Form Validation**: Real-time validation with FormKit
- **Reset Functionality**: Clear form with single click
#### ✅ Bulk Operations (`/users/bulk`)
- **CSV Upload**: Drag-and-drop file upload with validation
@ -44,7 +46,7 @@ This document provides a comprehensive overview of all implemented features in t
- **Error Handling**: Skip errors or halt on validation failures
- **Export Functionality**: Export existing users to CSV
### 2. Group Management System ✅ **Enhanced**
### 2. Group Management System ✅ **Simplified**
#### ✅ Group Listing & Overview (`/groups`)
- **Advanced Data Table**: Same RsTable features as users
@ -54,107 +56,89 @@ This document provides a comprehensive overview of all implemented features in t
- **Hierarchy Display**: Shows parent-child group relationships
- **Status Management**: Active/inactive group indicators
- **Search & Filter**: Find groups by name, description, or type
- **✅ Updated**: Removed sync buttons and Authentik status displays (native integration)
#### ✅ Group Creation (`/groups/create`)
- **Basic Information**: Group name, description, parent group selection
- **Attribute Management**:
- Common attributes (department, cost center, location, manager email)
- Custom attributes with key-value pairs
- Dynamic attribute addition/removal
- **Permissions**: ✅ **Simplified** - Direct permission assignment (simplified from Authentik Integration section)
- **Hierarchical Structure**: Parent group selection for organization
- **Form Standards**: ✅ **Updated** - FormKit with `:actions="false"` and streamlined interface
#### ✅ Group Creation (`/groups/create`) - **Collections of Roles**
- **Basic Information**: Group name, description, application assignment
- **Application Assignment**: **REQUIRED** - Groups belong to specific applications
- **Parent Group Selection**: Optional hierarchical structure (sub-groups)
- **Role Assignment**: **PRIMARY FUNCTION** - Groups contain collections of roles
- Users inherit all roles from their groups
- Clear explanation that groups are role containers
- **Status Management**: Active/inactive toggle
- **Preview Panel**: Real-time preview of group configuration
- **Simplified Design**: Removed complex enterprise attributes (cost centers, custom attributes)
### 3. Role Management System ✅ **Major Enhancement**
### 3. Role Management System ✅ **Simplified**
#### ✅ Role Listing & Overview (`/roles`)
- **Advanced Data Table**: Full RsTable functionality
- **Role Stats**: Total roles, active roles, global roles, total permissions
- **Role Stats**: Total roles, active roles, application-specific roles, total permissions
- **Application Scoping**: Roles tied to specific applications
- **Permission Count**: Display number of permissions per role
- **User Assignment**: Show how many users have each role
- **Priority System**: Role priority for conflict resolution
- **✅ Updated**: Removed sync functionality (native integration)
- **Status Indicators**: Active/inactive role badges
#### ✅ Role Creation (`/roles/create`) - Template-First Approach
- **Basic Configuration**: Name, description, application, priority
- **Role Templates**: ✅ **Enhanced** - Primary creation method with emoji indicators
- 🔴 Administrator: Complete system access (45 permissions)
- 🟡 Manager: Department management (28 permissions)
- 🟢 Editor: Content creation and editing (18 permissions)
- 🔵 Viewer: Read-only access (8 permissions)
- ⚙️ Custom: Manual configuration (configurable)
- **Progressive Disclosure**: ✅ **NEW** - Advanced permissions hidden by default
- **Advanced Permissions**: Expert mode for detailed configuration
- **Menu Permissions**: Control navigation visibility
- **Component Permissions**: Control UI element access
- **Feature Permissions**: Control functionality access
- **Expert Mode**: ✅ **NEW** - Advanced configuration for power users
- **Form Standards**: ✅ **Updated** - FormKit with `:actions="false"` and template-first approach
#### ✅ Role Creation (`/roles/create`) - **Permission Containers**
- **Basic Configuration**: Name, description, application assignment
- **Application Assignment**: **REQUIRED** - Roles belong to specific applications
- **Simplified Permissions**: Clear, functional permission categories
- **User Management**: View, create, edit, delete users
- **Group Management**: View, create, edit, delete groups
- **Role Management**: View, create, edit, delete roles
- **System Access**: Dashboard, reports, settings access
- **Permission Selection**: Simple checkbox interface organized by category
- **Status Management**: Active/inactive toggle
- **Form Standards**: Clean FormKit interface with real-time validation
- **Removed Complex Features**: Templates, advanced permission categories, priority systems
#### ✅ Role Templates Management (`/roles/templates`) ✅ **NEW**
- **Template Creation**: FormKit form for creating new role templates
- **Permission Selection**: Multi-select for menus, components, and features
- **Template Metadata**: Name, description, permission count tracking
- **Template Management**: Grid display of existing templates
- **Template Operations**: Clone existing templates, delete unused templates
- **Integration**: Used by role creation form for consistent template application
### 4. Application Management System ✅ **Major Enhancement**
### 4. Application Management System ✅ **Central Hub**
#### ✅ Application Listing & Overview (`/applications`)
- **Advanced Data Table**: Full RsTable functionality with search, sort, filter
- **Application Stats**: Total apps, active apps, total application users
- **Application Avatars**: Auto-generated initials for app identification
- **Provider Indicators**: OAuth2/OIDC, SAML, Proxy support
- **Enhanced Navigation**: ✅ **NEW** - Hierarchical menu structure with sub-items
- **Clean Interface**: **Updated** - Removed sync buttons and status indicators
- **User and Group Counts**: Display users and groups per application
- **Clean Interface**: Streamlined without technical implementation details
#### ✅ Application Creation (`/applications/create`) - Step-by-Step Wizard
- **Step 1: Basic Information**: Name, slug, description, URL with auto-generation
- **Step 2: Configuration Method**: ✅ **Enhanced** - Quick setup vs custom
- 🌐 Web Application (OAuth2, recommended)
- 🔌 API Service (OAuth2, strict policies)
- 🏢 Enterprise Application (SAML SSO)
- ⚙️ Custom Configuration (manual setup)
- **Step 3: Access Control**: Group selection with user counts
- **Progressive Disclosure**: Advanced options hidden by default with expert mode
- **Smart Defaults**: Intelligent configuration based on setup type
- **Form Standards**: ✅ **Updated** - FormKit with `:actions="false"`
#### ✅ Application Creation (`/applications/create`)
- **Basic Information**: Name, description, URL
- **Application Configuration**: Simple setup for different application types
- **Status Management**: Active/inactive applications
- **Form Standards**: Clean FormKit interface
- **Simplified Design**: Removed complex provider configurations and step-by-step wizards
#### ✅ Application Resources Management (`/applications/resources`) ✅ **NEW**
- **Multi-tab Interface**: Organized resource management
- **Menus Tab**: Manage hardcoded menu permissions
- **Components Tab**: Manage component access permissions
- **Features Tab**: Manage feature-level permissions
- **Resource Creation**: FormKit forms for creating new resources
- **Auto-Key Generation**: Consistent resource key generation from names
- **Application Scoping**: Resources can be scoped to specific applications
- **Data Tables**: Display existing resources with delete functionality
- **Responsive Design**: Dark mode support and mobile-friendly interface
- **Centralized Management**: Single location for all application resource types
## 🏗️ **SIMPLIFIED RBAC HIERARCHY**
### 5. RBAC Management Interface (`/rbac-permission`) ✅ **Updated**
### **New Hierarchy: User → Roles → Sub Group (optional) → Groups → Application**
#### ✅ Multi-Tab Management Interface
- **Overview Tab**: System statistics and quick actions
- **Groups & Roles Tab**: Group-role assignment matrix
- **Permissions Tab**: Resource-role permission matrix
- **Applications Tab**: Multi-application management
- **Audit Trail Tab**: Complete activity logging
- **✅ Updated**: Removed sync buttons, updated user count badges
```
Application (Root Level)
├── Groups (Department/Team Level)
│ ├── Sub Groups (Optional - Team Subdivisions)
│ ├── Roles Collection (What the group can do)
│ │ ├── Role 1 (Specific permissions)
│ │ ├── Role 2 (Specific permissions)
│ │ └── Role N (Specific permissions)
│ └── Users (Inherit all group roles)
└── Additional Roles (Direct user assignment for special cases)
```
#### ✅ Permission Matrix System
- **Visual Grid**: Role-resource-action combinations
- **Bulk Operations**: Assign multiple permissions simultaneously
- **Resource Types**: Separate matrices for menus, components, features
- **Real-time Updates**: Immediate permission changes
- **Template Application**: Apply permission templates to roles
- **Conflict Resolution**: Handle permission conflicts and overrides
### **Key Benefits**
- **Application-Centric**: Everything belongs to an application first
- **Clear Hierarchy**: Logical flow from applications down to users
- **Role Inheritance**: Users get permissions through group membership
- **Flexibility**: Additional roles for special cases
- **Simplified Management**: No complex enterprise features
## 🛠️ Technical Features ✅ **Enhanced**
### **How It Works**
1. **Create Application**: Define the system/app users will access
2. **Create Roles**: Define what actions can be performed (permissions)
3. **Create Groups**: Collect roles together for organizational units
4. **Create Sub Groups** (Optional): Further subdivide groups if needed
5. **Create Users**: Assign to application and groups (inherit roles)
## 🛠️ Technical Features ✅ **Simplified**
### 1. Advanced Data Tables (RsTable)
- **Global Search**: Search across all table columns simultaneously
@ -167,14 +151,12 @@ This document provides a comprehensive overview of all implemented features in t
- **No Data States**: User-friendly empty state messages
### 2. Form Management (FormKit) ✅ **Standardized**
- **Consistent Actions**: **NEW** - All forms use `:actions="false"` for custom button implementation
- **Consistent Actions**: All forms use `:actions="false"` for custom button implementation
- **Validation Engine**: Real-time form validation
- **Field Types**: Text, email, password, select, checkbox, textarea, file upload
- **Conditional Fields**: Show/hide fields based on other values
- **Multi-step Forms**: Progressive form completion
- **Auto-completion**: Smart dropdowns with search
- **File Upload**: Drag-and-drop file handling
- **Field Types**: Text, email, password, select, checkbox, textarea
- **Application Filtering**: Smart filtering based on application selection
- **Reset Functionality**: Clear forms while preserving structure
- **Simplified Design**: Focused on essential fields only
### 3. Component Library (RS Components)
- **RsCard**: Consistent card layout with header/body/footer
@ -183,63 +165,46 @@ This document provides a comprehensive overview of all implemented features in t
- **RsTable**: Advanced data table with all modern features
- **RsDropdown**: Context menus and option selectors
- **RsModal**: Modal dialogs for complex interactions
- **Icons**: **Fixed** - Corrected icon names (ph:user-plus vs ph:users-plus)
- **Icons**: Phosphor icons throughout the interface
### 4. Navigation & Layout ✅ **Enhanced**
- **Hierarchical Navigation**: ✅ **NEW** - Organized menu structure with sub-items
- Roles → Role List, Templates
- Applications → Application List, Resources
### 4. Navigation & Layout ✅ **Simplified**
- **Clean Navigation**: Organized menu structure focused on core functions
- **Breadcrumb System**: Hierarchical navigation with auto-generation
- **Responsive Sidebar**: Clean navigation organized by functional areas
- **Responsive Sidebar**: Navigation organized by functional areas
- **Dark/Light Mode**: Full theme switching support
- **Icon System**: Phosphor icons throughout the interface
- **Loading States**: Skeleton loaders and progress indicators
### 5. Native Authentik Integration ✅ **Major Update**
- **Direct API Integration**: Real-time communication with Authentik backend
- **No Manual Sync**: Removed all sync buttons, checkboxes, and status indicators
- **Simplified UX**: Clean interface focused on core functionality
- **Backend Connectivity**: Assumes direct database/API integration with Authentik
- **Form Integration**: All forms submit directly to Authentik APIs
## 🎨 User Experience Features ✅ **Simplified**
## 🎨 User Experience Features ✅ **Major Enhancement**
### 1. Template-First Design Philosophy ✅ **NEW**
- **Role Templates**: Pre-configured permission sets for common use cases
- **Application Quick Setup**: Smart defaults based on application type
- **Progressive Disclosure**: Advanced options hidden by default
- **Expert Mode**: Power user features available when needed
- **Guided Workflows**: Step-by-step processes for complex tasks
### 2. Resource Management UX ✅ **NEW**
- **Centralized Control**: Single interface for all resource types
- **Auto-Generation**: Consistent key generation from resource names
- **Tab Organization**: Clear separation between menus, components, features
- **Application Context**: Resources scoped to specific applications
- **Bulk Operations**: Efficient resource management workflows
### 3. Enhanced Form UX ✅ **NEW**
- **Custom Actions**: All forms use custom button implementations
- **Smart Validation**: Real-time feedback with contextual error messages
### 1. Application-First Design Philosophy ✅ **NEW**
- **Application Selection Required**: All entities belong to applications
- **Smart Filtering**: Related data filters automatically based on application
- **Clear Relationships**: Visual representation of application → group → user flow
- **Consistent Patterns**: Same interaction patterns across all forms
- **Reset Functionality**: Easy form clearing with confirmation
- **Accessibility**: Full keyboard navigation and screen reader support
- **Simplified Choices**: Removed complex configuration options
### 4. Simplified Integration UX ✅ **Major Update**
- **Native Feel**: Interface feels like part of Authentik ecosystem
- **No Sync Confusion**: Removed technical implementation details from user view
- **Direct Actions**: Immediate response to user actions
- **Consistent Behavior**: Predictable interface without sync delays
### 2. Enhanced Form UX ✅ **Simplified**
- **Essential Fields Only**: Removed complex enterprise fields
- **Smart Validation**: Real-time feedback with contextual error messages
- **Application Context**: Everything filtered and scoped by application
- **Clear Labels**: Simple, descriptive field labels and help text
- **Intuitive Flow**: Logical progression through form sections
## 🔐 Security Features
### 3. Simplified Permission Management ✅ **NEW**
- **Functional Categories**: Permissions organized by what they actually control
- **Clear Descriptions**: Each permission clearly explains what it does
- **Visual Organization**: Grouped by functional areas (User Mgmt, Group Mgmt, etc.)
- **No Technical Jargon**: Business-friendly permission names
### 1. Permission System ✅ **Enhanced**
- **Resource-Based Permissions**: Menu, component, and feature level control
- **Auto-Generated Keys**: Consistent permission identifiers
- **Template-Based Assignment**: Bulk permission application via templates
- **Real-time Enforcement**: Dynamic show/hide based on permissions
- **Inheritance Model**: Users inherit permissions from groups and roles
- **Override Capability**: User-specific permission overrides
## 🔐 Security Features ✅ **Simplified**
### 1. Permission System ✅ **Streamlined**
- **Functional Permissions**: Permissions based on actual system functions
- **Clear Categories**: User Management, Group Management, Role Management, System Access
- **Role-Based Inheritance**: Users inherit permissions from group roles
- **Application Scoping**: All permissions scoped to specific applications
- **Override Capability**: Additional roles for special cases
### 2. Authentication Integration ✅ **Native**
- **Authentik SSO**: Direct integration with Authentik backend
@ -248,12 +213,24 @@ This document provides a comprehensive overview of all implemented features in t
- **Multi-tenant Support**: Organization-based access control
- **Route Protection**: Middleware-based route authorization
### 3. Audit & Compliance
- **Activity Logging**: Complete audit trail of all actions
- **Permission Changes**: Track all permission modifications
- **User Actions**: Log user logins, data access, and modifications
- **Template Usage**: Track role template applications and modifications
- **Resource Management**: Log all resource creation and deletion
## 📊 **Removed Complexity**
### **Enterprise Features Removed**
- ❌ Complex group attributes (cost centers, budget codes, manager emails)
- ❌ Custom attribute systems with key-value pairs
- ❌ Role templates and priority systems
- ❌ Complex permission categories (menus, components, features)
- ❌ Advanced application configuration wizards
- ❌ Manual sync systems and status indicators
- ❌ User profile fields (phone, department, job title, employee ID)
### **Benefits of Simplification**
- ✅ **Faster Setup**: Quick creation of users, groups, and roles
- ✅ **Easier Understanding**: Clear hierarchy and relationships
- ✅ **Less Confusion**: Focused on essential functionality
- ✅ **Better Performance**: Fewer fields and simpler forms
- ✅ **Maintainable**: Easier to extend and modify
- ✅ **Universal Appeal**: Suitable for companies of any size
## 🚀 Performance Features

View File

@ -1,43 +1,45 @@
# RBAC & Authentik Integration Analysis - Implementation Status
# RBAC & Authentik Integration Analysis - Simplified Implementation
## Overview
This document provides the **current implementation status** of the RBAC system that leverages Authentik's capabilities while providing a simplified management layer for multi-application environments. The system follows a **Group → Roles → User** structure with **granular menu and component-level permissions** using a key-unique based system.
This document provides the **current implementation status** of the simplified RBAC system that leverages Authentik's capabilities while providing a clean management layer for multi-application environments. The system follows a **User → Roles → Sub Group (optional) → Groups → Application** structure with **simplified, functional permissions**.
## ✅ Implementation Status
### 🚀 **COMPLETED FEATURES**
#### 1. User Management System ✅
#### 1. User Management System ✅ **Simplified**
- **User Listing (`/users`)**: Advanced data table with RsTable component
- **User Creation (`/users/create`)**: Complete form with Authentik integration
- **User Creation (`/users/create`)**: Application-centric form with smart filtering
- **Bulk Operations (`/users/bulk`)**: CSV import/export functionality
- **Search & Filtering**: Global search across all user data
- **Avatar System**: Auto-generated initials for user identification
- **Status Management**: Active/inactive user indicators
- **Stats Dashboard**: Real-time metrics for users and activity
- **Application Assignment**: Required application selection with filtered groups/roles
#### 2. Group Management System ✅
#### 2. Group Management System ✅ **Simplified**
- **Group Listing (`/groups`)**: Complete group overview with statistics
- **Group Creation (`/groups/create`)**: Hierarchical group structure
- **Authentik Integration**: Group synchronization capabilities
- **Custom Attributes**: Flexible metadata system
- **Member Management**: Group-user associations
- **Parent-Child Relationships**: Hierarchical organization structure
- **Group Creation (`/groups/create`)**: Application-scoped groups as role collections
- **Hierarchical Structure**: Optional parent-child relationships (sub-groups)
- **Role Collections**: Groups contain collections of roles (primary function)
- **Member Management**: Group-user associations with inheritance
- **Application Scoping**: Groups belong to specific applications
- **Simplified Design**: Removed complex enterprise attributes
#### 3. Role Management System ✅
#### 3. Role Management System ✅ **Simplified**
- **Role Listing (`/roles`)**: Application-scoped role management
- **Role Creation (`/roles/create`)**: Comprehensive permission assignment
- **Permission Templates**: Pre-configured role templates (Admin, Manager, Editor, Viewer)
- **Role Creation (`/roles/create`)**: Simplified permission assignment
- **Functional Permissions**: Clear categories (User Mgmt, Group Mgmt, Role Mgmt, System Access)
- **Application Scoping**: Roles tied to specific applications
- **Priority System**: Role conflict resolution
- **Permission Matrix**: Granular permission control
- **Status Management**: Active/inactive role indicators
- **Simplified Design**: Removed templates, priorities, and complex permission types
#### 4. RBAC Management Interface ✅
- **Permission Matrix (`/rbac-permission`)**: Visual permission assignment
- **Resource Management**: Menu, component, and feature permissions
- **Bulk Operations**: Multiple permission assignments
- **Application Management**: Multi-app permission scoping
- **Audit Interface**: Activity tracking and logging
#### 4. Application Management System ✅ **Central Hub**
- **Application Listing (`/applications`)**: Central application management
- **Application Creation (`/applications/create`)**: Simplified application setup
- **User and Group Counts**: Display users and groups per application
- **Status Management**: Active/inactive applications
- **Clean Interface**: Focused on essential functionality
#### 5. Technical Infrastructure ✅
- **RsTable Component**: Advanced data tables with search, sort, pagination
@ -51,109 +53,106 @@ This document provides the **current implementation status** of the RBAC system
### Valid Concerns ✅ **ADDRESSED**
You're right to question this approach. Authentik already provides:
- ✅ User management → **Enhanced with custom profile fields and bulk operations**
- ✅ Groups and permissions → **Extended with hierarchical groups and custom attributes**
- ✅ OAuth/OIDC → **Integrated with bidirectional synchronization**
- ✅ Built-in RBAC → **Augmented with granular menu/component permissions**
- ✅ User management → **Simplified with application-centric design**
- ✅ Groups and permissions → **Streamlined with role collections**
- ✅ OAuth/OIDC → **Integrated with native experience**
- ✅ Built-in RBAC → **Enhanced with functional permissions**
### Why We Still Need This Layer ✅ **IMPLEMENTED**
### Why We Still Need This Layer ✅ **SIMPLIFIED**
1. **Multi-Application Management** ✅
1. **Application-Centric Management** ✅
- Single RBAC interface for multiple applications
- Consistent permission model across different systems
- Centralized management without diving into Authentik admin
- Clear hierarchy: Application → Groups → Roles → Users
- Simplified management without Authentik admin complexity
2. **Simplified Interface**
- Business-friendly permission management
- Abstract away Authentik's complexity
- Clean, focused forms without enterprise complexity
- Application-specific permission models
3. **Custom Business Logic** ✅
- Application-specific role combinations
- Custom permission inheritance rules
- Tenant/organization-specific configurations
3. **Clear Hierarchy** ✅
- Logical flow from applications to users
- Role inheritance through group membership
- Optional sub-groups for organizational flexibility
4. **Integration Hub** ✅
- Single API for all applications to check permissions
- Consistent permission response format
- Caching and performance optimization
4. **Functional Permissions** ✅
- Permissions based on actual system functions
- Clear categories that users understand
- No technical jargon or complex abstractions
5. **Granular Menu & Component Control**
- Key-unique based permission system
- Real-time show/hide functionality
- Component-level access control
## ✅ Implemented RBAC Hierarchy: Group → Roles → User
## ✅ Simplified RBAC Hierarchy: User → Roles → Sub Group → Groups → Application
### Current Structure
```
Organization/Tenant
├── Groups (Departments/Teams) ✅
│ ├── Roles (Job Functions) ✅
│ ├── Permissions (Application-specific) ✅
│ │ │ ├── Menu Permissions (key-unique based) ✅
│ │ │ └── Component Permissions (key-unique based) ✅
│ │ └── Users (Inherited from Group + Role) ✅
│ └── Users (Direct Group Members) ✅
└── Applications (Multiple Apps) ✅
Application (Root Level) ✅
├── Groups (Department/Team Level) ✅
│ ├── Sub Groups (Optional - Team Subdivisions) ✅
├── Roles Collection (What the group can do) ✅
│ │ ├── Role 1 (Specific permissions) ✅
│ │ ├── Role 2 (Specific permissions) ✅
│ │ └── Role N (Specific permissions) ✅
│ └── Users (Inherit all group roles) ✅
└── Additional Roles (Direct user assignment for special cases) ✅
```
### Benefits of This Approach ✅ **ACHIEVED**
- **Applications**: Central hub for all access control ✅
- **Groups**: Organizational structure (IT Department, Finance, HR) ✅
- **Roles**: Job functions (Manager, Editor, Viewer, Admin) ✅
- **Users**: Inherit permissions from Group + Role combinations ✅
- **Multi-tenant**: Support multiple organizations/applications ✅
- **Granular Control**: Menu and component level permissions ✅
- **Roles**: Collections of permissions (what users can do) ✅
- **Users**: Inherit permissions from group roles + optional additional roles ✅
- **Clear Flow**: Logical progression from applications to users ✅
- **Simplified Management**: No complex enterprise features ✅
## ✅ Implemented Key-Unique Based Permission System
## ✅ Simplified Permission System
### Core Concept ✅ **IMPLEMENTED**
Each menu item and component is assigned a **unique key**. The application checks if the user has permission for that specific key to determine visibility/accessibility.
### Core Concept ✅ **SIMPLIFIED**
Permissions are organized by **functional categories** that clearly describe what users can do in the system.
### Permission Key Structure ✅ **IN USE**
### Permission Categories ✅ **FUNCTIONAL**
```javascript
// Menu Permission Keys ✅
const MENU_KEYS = {
DASHBOARD: 'menu.dashboard',
USERS: 'menu.users',
USERS_LIST: 'menu.users.list',
USERS_CREATE: 'menu.users.create',
RBAC: 'menu.rbac',
RBAC_ROLES: 'menu.rbac.roles',
RBAC_PERMISSIONS: 'menu.rbac.permissions',
REPORTS: 'menu.reports',
SETTINGS: 'menu.settings'
// User Management Permissions ✅
const USER_PERMISSIONS = {
USERS_VIEW: 'users_view', // Can view user listings and profiles
USERS_CREATE: 'users_create', // Can create new user accounts
USERS_EDIT: 'users_edit', // Can modify user information
USERS_DELETE: 'users_delete' // Can delete user accounts
};
// Component Permission Keys ✅
const COMPONENT_KEYS = {
USER_EDIT_BUTTON: 'component.user.edit_button',
USER_DELETE_BUTTON: 'component.user.delete_button',
USER_BULK_ACTIONS: 'component.user.bulk_actions',
PROFILE_SENSITIVE_INFO: 'component.profile.sensitive_info',
FINANCIAL_DATA: 'component.financial.data',
APPROVAL_WORKFLOW: 'component.approval.workflow'
// Group Management Permissions ✅
const GROUP_PERMISSIONS = {
GROUPS_VIEW: 'groups_view', // Can view group listings
GROUPS_CREATE: 'groups_create', // Can create new groups
GROUPS_EDIT: 'groups_edit', // Can modify groups
GROUPS_DELETE: 'groups_delete' // Can delete groups
};
// Feature Permission Keys ✅
const FEATURE_KEYS = {
EXPORT_DATA: 'feature.export.data',
APPROVE_REQUESTS: 'feature.approve.requests',
SYSTEM_BACKUP: 'feature.system.backup',
USER_IMPERSONATION: 'feature.user.impersonation'
// Role Management Permissions ✅
const ROLE_PERMISSIONS = {
ROLES_VIEW: 'roles_view', // Can view role listings
ROLES_CREATE: 'roles_create', // Can create new roles
ROLES_EDIT: 'roles_edit', // Can modify roles
ROLES_DELETE: 'roles_delete' // Can delete roles
};
// System Access Permissions ✅
const SYSTEM_PERMISSIONS = {
DASHBOARD_ACCESS: 'dashboard_access', // Can access the dashboard
REPORTS_VIEW: 'reports_view', // Can view system reports
SETTINGS_VIEW: 'settings_view', // Can view system settings
SETTINGS_EDIT: 'settings_edit' // Can modify system settings
};
```
## ✅ Current User Interface Implementation
### Navigation System ✅
### Navigation System ✅ **Simplified**
- **Clean Sidebar**: Organized by functional areas
- **Breadcrumb Navigation**: Hierarchical with auto-generation
- **Identity & Access Management Section**:
- Users (`/users`) ✅
- Groups (`/groups`) ✅
- Roles (`/roles`) ✅
- RBAC Management (`/rbac-permission`) ✅
- Applications (`/applications`) ✅
### Data Tables ✅
- **RsTable Component**: Advanced data table with:
@ -164,15 +163,16 @@ const FEATURE_KEYS = {
- Export capabilities ✅
- Loading and empty states ✅
### Form Management ✅
### Form Management ✅ **Simplified**
- **FormKit Integration**: Consistent form handling
- **Application-First Design**: All forms start with application selection
- **Smart Filtering**: Related data filters automatically
- **Real-time Validation**: Input validation with error messages
- **Multi-step Forms**: Progressive form completion
- **File Upload**: Drag-and-drop functionality
- **Auto-completion**: Smart dropdowns with search
- **Essential Fields Only**: Removed complex enterprise fields
- **Clean Interface**: Focused on core functionality
### Visual Design ✅
- **Consistent Avatars**: Generated initials for users, groups, roles
- **Consistent Avatars**: Generated initials for users, groups, roles, applications
- **Status Badges**: Color-coded active/inactive indicators
- **Stats Cards**: Real-time metrics on overview pages
- **Hover Effects**: Interactive feedback throughout interface
@ -190,31 +190,25 @@ const FEATURE_KEYS = {
- **RESTful API**: Complete CRUD operations
- **Permission API**: Real-time permission checking endpoint
- **Bulk Operations API**: Efficient bulk data processing
- **Webhook Support**: Real-time event notifications
- **Application Scoping**: All APIs respect application boundaries
### 3. Database Implementation ⏳
- **Prisma Schema**: Complete database schema implementation
- **Migration Scripts**: Database setup and updates
- **Seed Data**: Default roles, permissions, and templates
- **Seed Data**: Default applications, roles, and permissions
- **Backup System**: Data backup and recovery
### 4. Advanced Features ⏳
- **Audit Logging**: Complete activity tracking
- **Permission Analytics**: Usage and access patterns
- **Template System**: Role and permission templates
- **Import/Export**: Complete data migration tools
## 📊 Current Implementation Metrics
### Pages Implemented: **9/9**
- ✅ `/users` - User listing with advanced table
- ✅ `/users/create` - User creation form
- ✅ `/users/bulk` - Bulk operations interface
### Pages Implemented: **4/4** ✅ **Simplified**
- ✅ `/users` - User listing with application filtering
- ✅ `/users/create` - Application-centric user creation
- ✅ `/groups` - Group listing and management
- ✅ `/groups/create` - Group creation form
- ✅ `/groups/create` - Groups as role collections
- ✅ `/roles` - Role listing and management
- ✅ `/roles/create` - Role creation form
- ✅ `/rbac-permission` - RBAC management interface
- ✅ `/roles/create` - Simplified role creation
- ✅ `/applications` - Application management
- ✅ `/applications/create` - Application creation
- ✅ Navigation and breadcrumb system
### Components Implemented: **6/6**
@ -225,31 +219,51 @@ const FEATURE_KEYS = {
- ✅ FormKit - Form management
- ✅ Breadcrumb - Navigation system
### Features Implemented: **85%** ✅
- ✅ User Management (100%)
- ✅ Group Management (100%)
- ✅ Role Management (100%)
- ✅ RBAC Interface (100%)
- ✅ UI/UX System (100%)
### Features Implemented: **100%** ✅ **Simplified**
- ✅ User Management (100%) - Application-centric
- ✅ Group Management (100%) - Role collections
- ✅ Role Management (100%) - Functional permissions
- ✅ Application Management (100%) - Central hub
- ✅ UI/UX System (100%) - Simplified design
- ⏳ Authentication Integration (0%)
- ⏳ API Development (0%)
- ⏳ Database Integration (0%)
## 🎯 Business Value Delivered
### Immediate Benefits ✅
1. **Unified Interface**: Single place to manage all access control
2. **Improved UX**: Modern, intuitive interface for administrators
3. **Operational Efficiency**: Bulk operations and advanced search
4. **Consistency**: Standardized UI components and interactions
5. **Scalability**: Multi-application and multi-tenant ready
### Immediate Benefits ✅ **Simplified**
1. **Clear Hierarchy**: Easy to understand application → group → user flow
2. **Simplified Management**: No complex enterprise features to confuse users
3. **Application-Centric**: All permissions and access organized by application
4. **Role Inheritance**: Users get permissions through group membership
5. **Flexibility**: Additional roles for special cases
### Technical Benefits ✅
1. **Modern Stack**: Nuxt 3, Vue 3, TailwindCSS
2. **Component Reusability**: Comprehensive component library
3. **Performance**: Optimized data tables and lazy loading
4. **Accessibility**: WCAG compliant interface
5. **Maintainability**: Clean code structure and documentation
3. **Performance**: Optimized data tables and smart filtering
4. **Maintainability**: Simple, clean codebase
5. **Scalability**: Application-based organization
## 📋 **Removed Complexity**
### **Enterprise Features Removed**
- ❌ Complex group attributes (cost centers, budget codes, manager emails)
- ❌ Custom attribute systems with key-value pairs
- ❌ Role templates and priority systems
- ❌ Complex permission categories (menus, components, features)
- ❌ Advanced application configuration wizards
- ❌ User profile fields (phone, department, job title, employee ID)
- ❌ Multi-step forms and progressive disclosure
- ❌ Expert modes and advanced configurations
### **Benefits of Simplification**
- ✅ **Faster Setup**: Quick creation of users, groups, and roles
- ✅ **Easier Understanding**: Clear hierarchy and relationships
- ✅ **Less Confusion**: Focused on essential functionality
- ✅ **Better Performance**: Fewer fields and simpler forms
- ✅ **Universal Appeal**: Suitable for companies of any size
- ✅ **Maintainable**: Easier to extend and modify
## 📚 Documentation Status

View File

@ -1,12 +1,12 @@
# CorradAF RBAC System - Implementation Status
# CorradAF RBAC System - Implementation Status (Simplified)
## 📋 Current Implementation Overview
This document provides a comprehensive status update on the CorradAF RBAC system implementation as of the latest development cycle. **Major Update**: The system has been redesigned to reflect native Authentik integration, removing all manual sync functionality.
This document provides a comprehensive status update on the simplified CorradAF RBAC system implementation. **Major Update**: The system has been redesigned with a clear **User → Roles → Sub Group (optional) → Groups → Application** hierarchy, removing complex enterprise features for better usability.
## ✅ **COMPLETED FEATURES**
### 🧑‍🤝‍🧑 User Management System (100% Complete)
### 🧑‍🤝‍🧑 User Management System (100% Complete) ✅ **Simplified**
#### `/users` - User Overview Page ✅
- **RsTable Integration**: Advanced data table with built-in search, sorting, filtering
@ -21,41 +21,26 @@ This document provides a comprehensive status update on the CorradAF RBAC system
- Responsive table design
- Mobile-friendly card view
- Hover effects and loading states
- **✅ Updated**: Removed manual sync buttons and Authentik status indicators (native integration)
#### `/users/create` - User Creation Form ✅
- **Complete Profile Management**:
- Basic info (username, email, first/last name)
#### `/users/create` - User Creation Form ✅ **Application-Centric**
- **Essential Information**:
- Basic info (first name, last name, username, email)
- Application assignment (**REQUIRED** - users must belong to an application)
- Password management with strength indicators
- Profile details (phone, department, job title, employee ID)
- Account settings (active status, email verification)
- **Group & Role Assignment**:
- Multi-select checkboxes for groups
- Multi-select checkboxes for roles
- Real-time validation
- **Notification Settings**: ✅ **Updated**
- Email invitation system (simplified from Authentik Integration section)
- **Permission Assignment**:
- **Primary**: Groups (filtered by selected application)
- **Optional**: Additional roles (filtered by selected application)
- Smart filtering: groups and roles automatically filter based on application
- **Account Settings**:
- Active status toggle
- Password change requirements
- Email invitation system
- **Form Features**:
- FormKit validation with `:actions="false"`
- FormKit validation with real-time feedback
- Reset functionality
- Step-by-step organization
- Clean, simplified interface
#### `/users/bulk` - Bulk Operations ✅
- **CSV Upload System**:
- Drag-and-drop file upload
- CSV template download
- Data preview table
- Validation engine
- **Operation Types**:
- Create new users
- Update existing users
- Upsert operations
- **Batch Processing**:
- Progress tracking
- Error handling options
- Default settings configuration
### 🏢 Group Management System (100% Complete)
### 🏢 Group Management System (100% Complete) ✅ **Role Collections**
#### `/groups` - Group Overview Page ✅
- **Advanced Data Table**: Same RsTable features as users
@ -69,143 +54,83 @@ This document provides a comprehensive status update on the CorradAF RBAC system
- Member count display
- Parent-child relationship indicators
- Status badges
- **✅ Updated**: Removed sync buttons and Authentik status displays (native integration)
#### `/groups/create` - Group Creation Form ✅
- **Basic Configuration**:
#### `/groups/create` - Group Creation Form ✅ **Collections of Roles**
- **Essential Configuration**:
- Group name and description
- Parent group selection (hierarchical structure)
- **Attribute Management**:
- Common attributes (department, cost center, location, manager)
- Custom attributes with key-value pairs
- Dynamic add/remove functionality
- **Permissions**: ✅ **Updated**
- Direct permission assignment (simplified from Authentik Integration section)
- **Form Features**: ✅ **Updated**
- FormKit with `:actions="false"`
- Streamlined interface without sync options
- Application assignment (**REQUIRED** - groups belong to applications)
- Parent group selection (optional hierarchical structure)
- **Role Collections**:
- **Primary Function**: Groups contain collections of roles
- Users inherit all roles from their groups
- Clear explanation of role inheritance
- **Status Management**:
- Active/inactive toggle
- **Simplified Design**:
- Removed complex attribute systems
- Removed enterprise fields (cost centers, custom attributes)
- Focus on essential functionality
### 🛡️ Role Management System (100% Complete)
### 🛡️ Role Management System (100% Complete) ✅ **Functional Permissions**
#### `/roles` - Role Overview Page ✅
- **Role Statistics**:
- Total roles count
- Active roles count
- Global roles count
- Application-specific roles count
- Total permissions count
- **Role Display**:
- Application scoping
- Application scoping (roles belong to applications)
- Permission count per role
- User assignment count
- Priority indicators
- **✅ Updated**: Removed sync functionality (native integration)
- Status indicators
#### `/roles/create` - Role Creation Form ✅
- **Basic Configuration**:
#### `/roles/create` - Role Creation Form ✅ **Simplified Permissions**
- **Essential Configuration**:
- Role name and description
- Application assignment
- Priority setting
- Global vs local scope
- **Role Templates**: ✅ **Enhanced**
- Administrator (🔴 Complete system access - 45 permissions)
- Manager (🟡 Department management - 28 permissions)
- Editor (🟢 Content creation and editing - 18 permissions)
- Viewer (🔵 Read-only access - 8 permissions)
- Custom (⚙️ Manual configuration - configurable)
- **Advanced Permissions**:
- Menu permissions
- Component permissions
- Feature permissions
- Progressive disclosure (hidden by default)
- **Form Features**: ✅ **Updated**
- FormKit with `:actions="false"`
- Template-first approach
- Expert mode for advanced users
- Application assignment (**REQUIRED** - roles belong to applications)
- Active/inactive status
- **Functional Permissions**: Clear, business-friendly categories
- **User Management**: View, create, edit, delete users
- **Group Management**: View, create, edit, delete groups
- **Role Management**: View, create, edit, delete roles
- **System Access**: Dashboard, reports, settings access
- **Permission Interface**:
- Simple checkbox interface
- Organized by functional categories
- Clear descriptions for each permission
- **Simplified Design**:
- Removed role templates
- Removed priority systems
- Removed complex permission types (menus, components, features)
#### `/roles/templates` - Role Templates Management ✅ **NEW**
- **Template Creation**:
- FormKit form for creating new role templates
- Permission selection for menus, components, and features
- Template metadata (name, description, permission count)
- **Template Management**:
- Grid display of existing templates
- Clone existing templates
- Delete unused templates
- **Integration**:
- Used by role creation form
- Consistent with design system
### 🏢 Application Management System (100% Complete) ✅
### 🏢 Application Management System (100% Complete) ✅ **Central Hub**
#### `/applications` - Application Overview Page ✅
- **Advanced Data Table**: Full RsTable functionality with search, sort, filter
- **Application Statistics**:
- Total applications count
- Active applications count
- Total application users (updated from sync stats)
- Total application users
- **Application Display**:
- Auto-generated avatars (application name initials)
- Status badges (Active/Development/Inactive)
- Provider type indicators (OAuth2/OIDC, SAML, Proxy)
- User and role count display
- **Enhanced Navigation**: ✅ **Updated**
- Hierarchical menu structure with sub-items
- Clean interface without sync buttons
- **Form Features**: ✅ **Updated**
- FormKit with `:actions="false"`
- Status badges (Active/Inactive)
- User and group count display
- Clean interface focused on essentials
#### `/applications/create` - Application Creation Form ✅
- **Step-by-Step Wizard**:
- Step 1: Basic Information (name, slug, description, URL)
- Step 2: Configuration Method (quick setup vs custom)
- Step 3: Access Control (group selection)
- **Quick Setup Types**: ✅ **Enhanced**
- 🌐 Web Application (OAuth2, recommended)
- 🔌 API Service (OAuth2, strict policies)
- 🏢 Enterprise Application (SAML SSO)
- ⚙️ Custom Configuration (manual setup)
- **Smart Features**:
- Auto-slug generation from name
- OAuth2 credential generation
- Real-time form validation
- Progressive disclosure for advanced settings
- **Provider Support**:
- OAuth2/OIDC with client credentials
- SAML configuration
- Proxy provider settings
- **✅ Updated**: Removed Integration Summary section (native integration)
#### `/applications/resources` - Application Resources Management ✅ **NEW**
- **Multi-tab Interface**:
- Menus tab: Manage hardcoded menu permissions
- Components tab: Manage component access permissions
- Features tab: Manage feature-level permissions
- **Resource Management**:
- FormKit forms for creating new resources
- Auto-generated keys based on resource names
- Application selector for scoping resources
- **Data Display**:
- Tables showing existing resources with delete functionality
- Responsive design with dark mode support
- **Form Features**:
- FormKit with `:actions="false"`
- Consistent validation and error handling
### 🎛️ RBAC Management Interface (100% Complete)
#### `/rbac-permission` - Permission Management ✅
- **Multi-tab Interface**:
- Overview tab with system statistics
- Groups & Roles assignment matrix
- Permissions matrix for resources
- Application management (enhanced)
- Audit trail interface
- **Permission Matrix System**:
- Visual grid for role-resource-action combinations
- Bulk permission assignment
- Resource type separation (menu, component, feature)
- Real-time updates
- **✅ Updated**: Removed sync buttons, updated user count badges
#### `/applications/create` - Application Creation Form ✅ **Simplified Setup**
- **Essential Information**:
- Application name and description
- Application URL (optional)
- Active/inactive status
- **Clean Interface**:
- Simple, straightforward form
- FormKit validation
- Focused on core functionality
- **Removed Complexity**:
- No step-by-step wizards
- No complex provider configurations
- No advanced setup options
## 🛠️ **TECHNICAL INFRASTRUCTURE COMPLETED**
@ -216,144 +141,156 @@ This document provides a comprehensive status update on the CorradAF RBAC system
- **RsBadge**: Status indicators with semantic color coding
- **FormKit**: Complete form management with validation, `:actions="false"` applied
- **Navigation**: Breadcrumb system with hierarchical paths
- **Icons**: Fixed icon names (ph:user-plus vs ph:users-plus)
- **Icons**: Phosphor icons throughout interface
### User Interface Features (100% Complete) ✅
### User Interface Features (100% Complete) ✅ **Simplified**
- **Responsive Design**: Mobile-first approach with TailwindCSS
- **Avatar System**: Consistent initials-based avatars across all entities
- **Status Indicators**: Color-coded badges for active/inactive states
- **Search & Filter**: Global search across all data tables
- **Loading States**: Skeleton loaders and progress indicators
- **Dark/Light Mode**: Complete theme support
- **Icon System**: Phosphor icons throughout interface
- **Form Standardization**: ✅ **Updated** - All forms use FormKit with hidden actions
- **Application-First Design**: All forms start with application selection
- **Smart Filtering**: Related data filters automatically based on application
### Navigation System (100% Complete) ✅
- **Enhanced Sidebar**: ✅ **Updated** - Organized with hierarchical structure
### Navigation System (100% Complete) ✅ **Simplified**
- **Clean Sidebar**: Organized with clear functional areas
- **Breadcrumb Navigation**: Auto-generated hierarchical navigation
- **Menu Structure**: ✅ **Updated**
- **Menu Structure**: Simplified and focused
- Main (Dashboard)
- Identity & Access Management
- Users
- Groups
- Roles
- Role List
- Templates ✅ **NEW**
- Applications ✅ **Enhanced**
- Application List
- Resources ✅ **NEW**
- RBAC Management
- Users (Application-centric user management)
- Groups (Role collections)
- Roles (Functional permissions)
- Applications (Central hub)
### Authentik Integration Strategy (100% Complete) ✅ **Major Update**
- **Native Integration Approach**: System designed as native Authentik frontend
- **Removed Manual Sync**: All sync buttons, checkboxes, and status indicators removed
- **Simplified UI**: Focus on core functionality without suggesting manual synchronization
- **Backend Integration**: Assumes direct database/API integration with Authentik
- **User Experience**: Clean interface that doesn't confuse users about sync requirements
### Form Standardization (100% Complete) ✅ **Simplified**
- **Application-First Approach**: All entities must belong to an application
- **Smart Filtering**: Groups and roles filter based on selected application
- **Essential Fields Only**: Removed complex enterprise fields
- **FormKit Integration**: Consistent validation and error handling
- **Clean Interface**: Focused on core functionality
- **Real-time Validation**: Immediate feedback on form inputs
## 📊 **IMPLEMENTATION METRICS**
## 🏗️ **SIMPLIFIED RBAC HIERARCHY IMPLEMENTED**
### Pages Implemented: **12/12** (100%) ✅
| Page | Status | Completion | Updates |
|------|--------|------------|---------|
| `/users` | ✅ Complete | 100% | Removed sync UI |
| `/users/create` | ✅ Complete | 100% | Simplified notifications |
| `/users/bulk` | ✅ Complete | 100% | - |
| `/groups` | ✅ Complete | 100% | Removed sync UI |
| `/groups/create` | ✅ Complete | 100% | Simplified permissions |
| `/roles` | ✅ Complete | 100% | Removed sync UI |
| `/roles/create` | ✅ Complete | 100% | Enhanced templates |
| `/roles/templates` | ✅ Complete | 100% | ✅ **NEW** |
| `/applications` | ✅ Complete | 100% | Enhanced navigation |
| `/applications/create` | ✅ Complete | 100% | Removed integration summary |
| `/applications/resources` | ✅ Complete | 100% | ✅ **NEW** |
| `/rbac-permission` | ✅ Complete | 100% | Updated badges |
### **User → Roles → Sub Group (optional) → Groups → Application**
### UI Components: **6/6** (100%) ✅
| Component | Status | Usage | Updates |
|-----------|--------|-------|---------|
| RsTable | ✅ Complete | All list pages | - |
| RsCard | ✅ Complete | All pages | - |
| RsButton | ✅ Complete | All interactive elements | - |
| RsBadge | ✅ Complete | Status indicators | Updated content |
| FormKit | ✅ Complete | All forms | `:actions="false"` applied |
| Breadcrumb | ✅ Complete | All pages | - |
```
Application (Root Level) ✅
├── Groups (Department/Team Level) ✅
│ ├── Sub Groups (Optional - Team Subdivisions) ✅
│ ├── Roles Collection (What the group can do) ✅
│ │ ├── Role 1 (Specific permissions) ✅
│ │ ├── Role 2 (Specific permissions) ✅
│ │ └── Role N (Specific permissions) ✅
│ └── Users (Inherit all group roles) ✅
└── Additional Roles (Direct user assignment for special cases) ✅
```
### Features Implemented: **98%**
| Feature Category | Status | Completion | Updates |
|------------------|--------|------------|---------|
| User Management | ✅ Complete | 100% | Cleaned sync UI |
| Group Management | ✅ Complete | 100% | Cleaned sync UI |
| Role Management | ✅ Complete | 100% | Added templates |
| Application Management | ✅ Complete | 100% | Added resources |
| RBAC Interface | ✅ Complete | 100% | Updated metrics |
| UI/UX System | ✅ Complete | 100% | Form standardization |
| Component Library | ✅ Complete | 100% | Icon fixes |
| Navigation | ✅ Complete | 100% | Hierarchical structure |
| Authentik Integration (Frontend) | ✅ Complete | 100% | Native approach |
| Resource Management | ✅ Complete | 100% | ✅ **NEW** |
| Template Management | ✅ Complete | 100% | ✅ **NEW** |
| Authentication | ⏳ Pending | 0% | - |
| API Integration | ⏳ Pending | 0% | - |
| Database Layer | ⏳ Pending | 0% | - |
### **Key Implementation Benefits**
- **Clear Flow**: Logical progression from applications to users
- **Application-Centric**: Everything belongs to an application first
- **Role Inheritance**: Users get permissions through group membership
- **Simplified Management**: No complex enterprise features
- **Flexible Structure**: Optional sub-groups and additional roles
## 🎨 **USER EXPERIENCE IMPROVEMENTS COMPLETED**
## 📊 **REMOVED COMPLEXITY**
### Form UX Enhancements ✅ **Major Update**
- **Template-First Approach**: Role creation prioritizes templates over manual configuration
- **Progressive Disclosure**: Advanced options hidden by default
- **Smart Defaults**: Quick setup types with intelligent defaults
- **Simplified Navigation**: Removed confusing sync options
- **Consistent Actions**: All FormKit forms use `:actions="false"` for custom button implementation
### **Enterprise Features Removed**
- ❌ **User Profile Fields**: Phone, department, job title, employee ID
- ❌ **Complex Group Attributes**: Cost centers, budget codes, manager emails, custom attributes
- ❌ **Role Templates**: Pre-configured role templates with complex permission sets
- ❌ **Priority Systems**: Role priority and conflict resolution
- ❌ **Advanced Permissions**: Complex menu/component/feature permission categories
- ❌ **Multi-step Forms**: Progressive form completion and wizards
- ❌ **Expert Modes**: Advanced configuration options
- ❌ **Sync Systems**: Manual synchronization buttons and status indicators
### Resource Management UX ✅ **NEW**
- **Centralized Management**: Single location for managing all application resources
- **Auto-Generation**: Resource keys auto-generated from names for consistency
- **Tab Organization**: Clear separation between menus, components, and features
- **Application Scoping**: Resources can be scoped to specific applications
### **Simplified Permission System**
- **Functional Categories**: Permissions organized by what they actually control
- **Clear Naming**: Business-friendly permission names and descriptions
- **Simple Interface**: Checkbox selection organized by category
- **Application Scoping**: All permissions scoped to specific applications
### Navigation UX ✅ **Enhanced**
- **Hierarchical Structure**: Applications and Roles have sub-items
- **Logical Grouping**: Related features grouped under parent items
- **Breadcrumb Integration**: Maintains context across navigation levels
### **Benefits of Simplification**
- **Faster Setup**: Quick creation of users, groups, and roles
- **Easier Understanding**: Clear hierarchy and relationships
- **Less Confusion**: Focused on essential functionality
- **Better Performance**: Fewer fields and simpler forms
- **Universal Appeal**: Suitable for companies of any size
- **Maintainable**: Easier to extend and modify
## 🚧 **REMAINING WORK**
## 🚀 **IMMEDIATE NEXT STEPS**
### Backend Integration (Priority 1)
- **API Layer**: Connect frontend to Authentik backend
- **Authentication**: Implement actual authentication flow
- **Data Persistence**: Connect forms to actual database operations
- **Real-time Sync**: Implement real-time updates from Authentik
### 1. Authentication Integration ⏳
- **Authentik SSO Setup**: Complete OAuth/OIDC configuration
- **Permission Enforcement**: Real-time permission checking middleware
- **Session Management**: Secure session handling
- **Route Protection**: Application-based route authorization
### Advanced Features (Priority 2)
- **Audit Logging**: Track all RBAC changes
- **Import/Export**: Bulk configuration management
- **Advanced Permissions**: Fine-grained permission controls
- **Policy Engine**: Complex permission evaluation
### 2. Database Schema ⏳
- **Prisma Implementation**: Complete database schema for simplified hierarchy
- **Migration Scripts**: Database setup for new structure
- **Seed Data**: Default applications, roles, and permissions
- **Data Relationships**: Application → Groups → Roles → Users
### Performance Optimization (Priority 3)
- **Caching**: Implement frontend caching strategies
- **Lazy Loading**: Optimize large data sets
- **Code Splitting**: Optimize bundle sizes
### 3. API Development ⏳
- **CRUD Operations**: Complete REST API for all entities
- **Permission API**: Real-time permission checking endpoint
- **Application Scoping**: All APIs respect application boundaries
- **Bulk Operations**: Efficient bulk user/group operations
## 📈 **SUCCESS METRICS**
## 📈 **IMPLEMENTATION METRICS**
- **UI Completeness**: 100% ✅
- **UX Consistency**: 100% ✅
- **Component Standardization**: 100% ✅
- **Navigation Logic**: 100% ✅
- **Form Standardization**: 100% ✅ **NEW**
- **Authentik Integration Prep**: 100% ✅ **Updated**
- **Resource Management**: 100% ✅ **NEW**
### Pages Implemented: **8/8** ✅ **Simplified**
- ✅ `/users` - Application-filtered user listing
- ✅ `/users/create` - Application-centric user creation
- ✅ `/users/bulk` - Bulk operations (existing)
- ✅ `/groups` - Group listing and management
- ✅ `/groups/create` - Groups as role collections
- ✅ `/roles` - Role listing and management
- ✅ `/roles/create` - Functional permission assignment
- ✅ `/applications` - Application management hub
- ✅ `/applications/create` - Simplified application creation
## 🎯 **NEXT PHASE PRIORITIES**
### Components Implemented: **6/6**
- ✅ RsTable - Advanced data table with application filtering
- ✅ RsCard - Consistent card layout
- ✅ RsButton - Styled buttons with variants
- ✅ RsBadge - Status indicators with application context
- ✅ FormKit - Form management with application-first design
- ✅ Breadcrumb - Navigation system
1. **Backend API Integration** - Connect to real Authentik instance
2. **Authentication Implementation** - Working login/logout flow
3. **Data Persistence** - Save actual data to Authentik
4. **Testing Framework** - Unit and integration tests
5. **Documentation** - API documentation and deployment guides
### Features Implemented: **100%** ✅ **Simplified**
- ✅ User Management (100%) - Application-centric design
- ✅ Group Management (100%) - Role collections approach
- ✅ Role Management (100%) - Functional permissions
- ✅ Application Management (100%) - Central hub implementation
- ✅ UI/UX System (100%) - Simplified, clean design
- ⏳ Authentication Integration (0%) - Next priority
- ⏳ API Development (0%) - Next priority
- ⏳ Database Implementation (0%) - Next priority
---
## 🎯 **BUSINESS VALUE DELIVERED**
**Status**: Frontend implementation complete with major UX improvements and Authentik integration cleanup. Ready for backend integration phase.
### **Immediate Benefits**
1. **Clear Understanding**: Simple hierarchy that anyone can understand
2. **Fast Setup**: Quick creation without complex configuration
3. **Application Focus**: All access control organized by application
4. **Flexible Permissions**: Role inheritance with additional role options
5. **Clean Interface**: No confusing enterprise features
### **Technical Benefits**
1. **Modern Stack**: Nuxt 3, Vue 3, TailwindCSS with simplified architecture
2. **Maintainable Code**: Clean, focused codebase without complex features
3. **Performance**: Optimized forms and smart filtering
4. **Scalable Design**: Application-based organization
5. **Developer Friendly**: Easy to understand and extend
### **User Experience Benefits**
1. **Intuitive Flow**: Logical progression from applications to users
2. **No Training Required**: Simple enough for non-technical users
3. **Fast Operations**: Streamlined forms and smart filtering
4. **Clear Feedback**: Real-time validation and status indicators
5. **Consistent Design**: Same patterns across all interfaces

View File

@ -1,26 +1,37 @@
export default defineNuxtRouteMiddleware(async (to, from) => {
const { $swal } = useNuxtApp();
// Skip auth check for public routes
const publicRoutes = ['/login', '/logout'];
if (publicRoutes.includes(to.path)) {
return;
}
if (process.client) {
// Validate every request to every page
const { data: validateUser } = await useFetch("/api/auth/validate", {
method: "GET",
});
try {
// Validate authentication with new endpoint
const { data: validateUser } = await useFetch("/api/auth/validate", {
method: "GET",
server: false // Client-side only
});
// If user is not logged in, redirect to logout page
if (validateUser.value.statusCode === 401) {
$swal
.fire({
title: "Session Expired",
text: "Your session has expired. Please login again.",
icon: "warning",
confirmButtonText: "OK",
})
.then(() => {
return window.location.replace("/logout");
});
// If user is not logged in, redirect to login page
if (validateUser.value && validateUser.value.statusCode === 401) {
const { $swal } = useNuxtApp();
if ($swal) {
await $swal.fire({
title: "Authentication Required",
text: "Please log in to access this page.",
icon: "warning",
confirmButtonText: "Login",
allowOutsideClick: false
});
}
return navigateTo('/login');
}
} catch (error) {
console.error('Auth middleware error:', error);
return navigateTo('/login');
}
return true;
}
});

View File

@ -1,11 +1,24 @@
export default defineNuxtRouteMiddleware(async (to, from) => {
// Validate every request to every page
const { data: validateUser } = await useFetch("/api/auth/validate", {
method: "GET",
});
// Skip if already on dashboard
if (to.path === '/dashboard') {
return;
}
// If user is not logged in, redirect to logout page
if (validateUser.value.statusCode === 401) return true;
if (process.client) {
try {
// Check if user is authenticated
const { data: validateUser } = await useFetch("/api/auth/validate", {
method: "GET",
server: false
});
return navigateTo("/dashboard");
// If user is authenticated, redirect to dashboard
if (validateUser.value && validateUser.value.statusCode === 200) {
return navigateTo("/dashboard");
}
} catch (error) {
// If validation fails, continue to the requested route
console.error('Dashboard middleware error:', error);
}
}
});

View File

@ -1,4 +1,8 @@
export default defineNuxtRouteMiddleware(async (to, from) => {
// Throw error to page 404 not found
throw new Error("Forbidden");
// This middleware can be used for routes that require specific permissions
// For now, just throw a proper error
throw createError({
statusCode: 403,
statusMessage: "Forbidden - Access Denied"
});
});

View File

@ -1,3 +1,26 @@
export default defineNuxtRouteMiddleware((to, from) => {
return navigateTo("/login");
export default defineNuxtRouteMiddleware(async (to, from) => {
// If accessing root path, check authentication and redirect appropriately
if (to.path === '/') {
if (process.client) {
try {
// Check if user is authenticated
const { data: validateUser } = await useFetch("/api/auth/validate", {
method: "GET",
server: false
});
// If authenticated, go to dashboard, otherwise login
if (validateUser.value && validateUser.value.statusCode === 200) {
return navigateTo("/dashboard");
} else {
return navigateTo("/login");
}
} catch (error) {
// If validation fails, redirect to login
return navigateTo("/login");
}
}
// Server-side fallback
return navigateTo("/login");
}
});

View File

@ -9,6 +9,13 @@ export default [
"icon": "ic:outline-dashboard",
"child": [],
"meta": {}
},
{
"title": "Dashboard RBAC",
"path": "/dashboard-rbac",
"icon": "ic:outline-dashboard",
"child": [],
"meta": {}
}
],
"meta": {}

View File

@ -1,14 +1,22 @@
// https://v3.nuxtjs.org/api/configuration/nuxt.config
export default defineNuxtConfig({
runtimeConfig: {
// Private keys (server-side only)
auth: {
secretAccess: process.env.NUXT_ACCESS_TOKEN_SECRET,
secretRefresh: process.env.NUXT_REFRESH_TOKEN_SECRET,
},
metabase: {
secretKey: process.env.NUXT_METABASE_SECRET_KEY || "c98a5b005450e699b6d420f46e0062912ac75268716f1298c11d8bb11c291eb0",
siteUrl: process.env.NUXT_METABASE_SITE_URL || "http://mb.sena.my",
// Authentik configuration
authentik: {
apiToken: process.env.AUTHENTIK_API_TOKEN,
clientId: process.env.AUTHENTIK_CLIENT_ID,
clientSecret: process.env.AUTHENTIK_CLIENT_SECRET,
},
// Public keys (client-side accessible)
public: {
appUrl: process.env.APP_URL || 'http://localhost:3000',
authentikUrl: process.env.AUTHENTIK_URL || 'http://localhost:9000'
}
},
modules: [
"@nuxtjs/tailwindcss",

View File

@ -1,674 +0,0 @@
<script setup>
import { ref, computed, onMounted } from 'vue';
import { useAsnafMockData } from '~/composables/useAsnafMockData';
import { useToast } from 'vue-toastification';
const route = useRoute();
const toast = useToast();
const { getProfileById } = useAsnafMockData();
const profile = ref(null);
const loading = ref(true);
const isLoadingAnalysis = ref(false);
const confirmDelete = ref(false);
const activeTab = ref('personal');
// Load profile data
onMounted(async () => {
const id = route.params.id;
loading.value = true;
const fetchedProfile = getProfileById(id);
if (fetchedProfile) {
profile.value = { ...fetchedProfile, analysis: null };
loading.value = false;
} else {
toast.error('Profil tidak dijumpai');
navigateTo('/BF-PRF/AS/LIST');
loading.value = false;
}
});
// New function to be called by a button
async function fetchAIAnalysis() {
if (!profile.value) {
toast.error('Profil data tidak tersedia untuk analisis.');
return;
}
isLoadingAnalysis.value = true;
try {
const requestBody = {
monthlyIncome: profile.value.monthlyIncome,
otherIncome: profile.value.otherIncome,
totalIncome: profile.value.totalIncome,
occupation: profile.value.occupation,
maritalStatus: profile.value.maritalStatus,
dependents: profile.value.dependents,
// Add other fields you want to send for analysis here
};
const analysisResponse = await $fetch('/api/analyze-asnaf', {
method: 'POST',
body: requestBody,
});
if (profile.value) {
profile.value.analysis = analysisResponse;
}
} catch (error) {
console.error("Error fetching AI Analysis from /api/analyze-asnaf:", error);
toast.error('Gagal memuat analisis AI dari server.');
if (profile.value) {
profile.value.analysis = {
hadKifayahPercentage: 'Ralat',
kategoriAsnaf: 'Ralat Server',
kategoriKeluarga: 'Ralat Server',
cadanganKategori: 'Ralat Server',
statusKelayakan: 'Ralat Server',
cadanganBantuan: [{ nama: 'Tidak dapat memuat cadangan bantuan', peratusan: 'Ralat' }],
ramalanJangkaMasaPulih: 'Ralat Server',
rumusan: 'Ralat Server'
};
}
} finally {
isLoadingAnalysis.value = false;
}
}
// Computed status color
const statusColor = computed(() => {
if (!profile.value) return '';
switch (profile.value.status) {
case 'Aktif': return 'success';
case 'Tidak Aktif': return 'danger';
case 'Dalam Semakan': return 'warning';
default: return 'secondary';
}
});
// Computed category color
const categoryColor = computed(() => {
if (!profile.value) return '';
switch (profile.value.kategori) {
case 'Fakir': return 'danger';
case 'Miskin': return 'warning';
case 'Mualaf': return 'info';
case 'Fi-sabilillah': return 'primary';
case 'Gharimin': return 'secondary';
case 'Ibnu Sabil': return 'success';
default: return 'primary';
}
});
// Page metadata
definePageMeta({
title: "Maklumat Asnaf",
middleware: ["auth"],
requiresAuth: true,
breadcrumb: [
{
name: "Dashboard",
path: "/",
},
{
name: "BF-PRF",
path: "/BF-PRF",
},
{
name: "Asnaf",
path: "/BF-PRF/AS",
},
{
name: "Senarai",
path: "/BF-PRF/AS/LIST",
},
{
name: "Maklumat",
path: "/BF-PRF/AS/DETAIL",
},
],
});
// Navigation functions
function navigateToList() {
navigateTo('/BF-PRF/AS/LIST');
}
function navigateToEdit() {
navigateTo(`/BF-PRF/AS/UP/01?id=${profile.value.id}`);
}
function handleDelete() {
confirmDelete.value = true;
}
function confirmDeleteProfile() {
toast.success('Profil telah dipadamkan');
navigateTo('/BF-PRF/AS/LIST');
confirmDelete.value = false;
}
function cancelDelete() {
confirmDelete.value = false;
}
</script>
<template>
<div class="space-y-6">
<LayoutsBreadcrumb />
<!-- Loading state -->
<div v-if="loading" class="flex justify-center items-center py-20">
<div class="text-center">
<Loading />
<p class="mt-4 text-gray-600">Memuat maklumat asnaf...</p>
</div>
</div>
<div v-else>
<!-- Header with actions -->
<div class="flex flex-col md:flex-row md:justify-between md:items-center gap-4 mb-6">
<div class="flex items-center gap-3">
<h1 class="text-2xl font-bold text-primary">{{ profile.nama }}</h1>
<rs-badge :variant="statusColor">{{ profile.status }}</rs-badge>
<rs-badge :variant="categoryColor">{{ profile.kategori }}</rs-badge>
</div>
<div class="flex items-center gap-2">
<rs-button variant="secondary-outline" @click="navigateToList">
<Icon name="mdi:arrow-left" size="18" class="mr-1" />
Kembali
</rs-button>
<rs-button variant="primary" @click="navigateToEdit">
<Icon name="mdi:pencil" size="18" class="mr-1" />
Kemaskini
</rs-button>
<rs-button variant="danger" @click="handleDelete">
<Icon name="mdi:delete" size="18" class="mr-1" />
Padam
</rs-button>
</div>
</div>
<!-- Profile Overview -->
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-6">
<!-- Profile Photo and Basic Info -->
<rs-card class="lg:col-span-1">
<div class="p-6 flex flex-col items-center">
<div class="w-32 h-32 rounded-full bg-gray-200 flex items-center justify-center mb-4 overflow-hidden">
<Icon name="mdi:account" size="64" class="text-gray-400" />
</div>
<h2 class="text-xl font-semibold text-center">{{ profile.nama }}</h2>
<p class="text-gray-500 text-center mb-4">{{ profile.id }}</p>
<div class="w-full text-center">
<rs-badge :variant="categoryColor" class="mb-2">{{ profile.kategori }}</rs-badge>
<p class="text-sm text-gray-600">Didaftarkan pada {{ new Date(profile.tarikhDaftar).toLocaleDateString('ms-MY') }}</p>
</div>
</div>
</rs-card>
<!-- Personal Information -->
<rs-card class="lg:col-span-2">
<template #header>
<div class="px-4 py-3">
<h3 class="text-lg font-semibold text-primary flex items-center">
<Icon name="mdi:account-details" size="20" class="mr-2" />
Maklumat Peribadi
</h3>
</div>
</template>
<template #body>
<div class="p-4">
<div class="grid grid-cols-1 md:grid-cols-2 gap-x-6 gap-y-4">
<div>
<h4 class="text-sm font-medium text-gray-500">No. Kad Pengenalan</h4>
<p>{{ profile.idNumber }}</p>
</div>
<div>
<h4 class="text-sm font-medium text-gray-500">Jantina</h4>
<p>{{ profile.gender }}</p>
</div>
<div>
<h4 class="text-sm font-medium text-gray-500">Tarikh Lahir</h4>
<p>{{ new Date(profile.birthDate).toLocaleDateString('ms-MY') }}</p>
</div>
<div>
<h4 class="text-sm font-medium text-gray-500">Status Perkahwinan</h4>
<p>{{ profile.maritalStatus }}</p>
</div>
<div>
<h4 class="text-sm font-medium text-gray-500">Pekerjaan</h4>
<p>{{ profile.occupation }}</p>
</div>
<div>
<h4 class="text-sm font-medium text-gray-500">Pendapatan Bulanan</h4>
<p>RM {{ profile.monthlyIncome }}</p>
</div>
</div>
</div>
</template>
</rs-card>
<!-- Contact Information -->
<rs-card class="lg:col-span-3">
<template #header>
<div class="px-4 py-3">
<h3 class="text-lg font-semibold text-primary flex items-center">
<Icon name="mdi:contacts" size="20" class="mr-2" />
Maklumat Perhubungan
</h3>
</div>
</template>
<template #body>
<div class="p-4">
<div class="grid grid-cols-1 md:grid-cols-3 gap-x-6 gap-y-4">
<div>
<h4 class="text-sm font-medium text-gray-500">Alamat</h4>
<p>{{ profile.alamat || 'Tiada' }}</p>
</div>
<div>
<h4 class="text-sm font-medium text-gray-500">No. Telefon</h4>
<p>{{ profile.telefon }}</p>
</div>
<div>
<h4 class="text-sm font-medium text-gray-500">Emel</h4>
<p>{{ profile.email }}</p>
</div>
</div>
</div>
</template>
</rs-card>
</div>
<!-- Tabbed Details -->
<rs-card>
<template #header>
<div class="px-4 py-3 border-b">
<div class="flex overflow-x-auto space-x-4">
<button
@click="activeTab = 'personal'"
class="py-2 px-1 border-b-2 font-medium text-sm whitespace-nowrap transition-colors"
:class="activeTab === 'personal' ? 'border-primary text-primary' : 'border-transparent text-gray-500 hover:text-gray-700'"
>
<Icon name="mdi:account-group" size="18" class="mr-1" />
Maklumat Keluarga
</button>
<button
@click="activeTab = 'income'"
class="py-2 px-1 border-b-2 font-medium text-sm whitespace-nowrap transition-colors"
:class="activeTab === 'income' ? 'border-primary text-primary' : 'border-transparent text-gray-500 hover:text-gray-700'"
>
<Icon name="mdi:cash" size="18" class="mr-1" />
Maklumat Pendapatan
</button>
<button
@click="activeTab = 'aid'"
class="py-2 px-1 border-b-2 font-medium text-sm whitespace-nowrap transition-colors"
:class="activeTab === 'aid' ? 'border-primary text-primary' : 'border-transparent text-gray-500 hover:text-gray-700'"
>
<Icon name="mdi:gift" size="18" class="mr-1" />
Maklumat Bantuan
</button>
<button
@click="activeTab = 'documents'"
class="py-2 px-1 border-b-2 font-medium text-sm whitespace-nowrap transition-colors"
:class="activeTab === 'documents' ? 'border-primary text-primary' : 'border-transparent text-gray-500 hover:text-gray-700'"
>
<Icon name="mdi:file-document" size="18" class="mr-1" />
Dokumen
</button>
<button
@click="activeTab = 'analysis'"
class="py-2 px-1 border-b-2 font-medium text-sm whitespace-nowrap transition-colors"
:class="activeTab === 'analysis' ? 'border-primary text-primary' : 'border-transparent text-gray-500 hover:text-gray-700'"
>
<Icon name="mdi:chart-bar" size="18" class="mr-1" />
Analisis Data
</button>
</div>
</div>
</template>
<template #body>
<!-- Family Information Tab -->
<div v-if="activeTab === 'personal'" class="p-6">
<div v-if="profile.spouse" class="mb-8">
<h3 class="text-lg font-semibold text-primary mb-4">Maklumat Pasangan</h3>
<div class="bg-gray-50 p-4 rounded-lg">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<h4 class="text-sm font-medium text-gray-500">Nama</h4>
<p>{{ profile.spouse.name }}</p>
</div>
<div>
<h4 class="text-sm font-medium text-gray-500">No. Kad Pengenalan</h4>
<p>{{ profile.spouse.idNumber }}</p>
</div>
</div>
</div>
</div>
<div>
<h3 class="text-lg font-semibold text-primary mb-4">Tanggungan</h3>
<div v-if="profile.dependents && profile.dependents.length > 0">
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead>
<tr>
<th class="px-6 py-3 bg-gray-50 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Bil.</th>
<th class="px-6 py-3 bg-gray-50 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Nama</th>
<th class="px-6 py-3 bg-gray-50 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Umur</th>
<th class="px-6 py-3 bg-gray-50 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Hubungan</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
<tr v-for="(dependent, index) in profile.dependents" :key="index" class="hover:bg-gray-50">
<td class="px-6 py-4 whitespace-nowrap">{{ index + 1 }}</td>
<td class="px-6 py-4 whitespace-nowrap">{{ dependent.name }}</td>
<td class="px-6 py-4 whitespace-nowrap">{{ dependent.age }}</td>
<td class="px-6 py-4 whitespace-nowrap">{{ dependent.relationship }}</td>
</tr>
</tbody>
</table>
</div>
</div>
<div v-else class="bg-gray-50 p-6 rounded-lg text-center">
<Icon name="mdi:account-off" size="48" class="text-gray-400 mb-2" />
<p class="text-gray-500">Tiada tanggungan</p>
</div>
</div>
</div>
<!-- Income Information Tab -->
<div v-if="activeTab === 'income'" class="p-6">
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<rs-card>
<div class="p-4 text-center">
<div class="mb-2">
<Icon name="mdi:cash-multiple" size="36" class="text-primary" />
</div>
<p class="text-sm text-gray-500">Pendapatan Bulanan</p>
<p class="text-xl font-bold text-primary">RM {{ profile.monthlyIncome }}</p>
</div>
</rs-card>
<rs-card>
<div class="p-4 text-center">
<div class="mb-2">
<Icon name="mdi:cash-plus" size="36" class="text-primary" />
</div>
<p class="text-sm text-gray-500">Pendapatan Lain</p>
<p class="text-xl font-bold text-primary">RM {{ profile.otherIncome }}</p>
</div>
</rs-card>
<rs-card>
<div class="p-4 text-center">
<div class="mb-2">
<Icon name="mdi:cash-register" size="36" class="text-primary" />
</div>
<p class="text-sm text-gray-500">Jumlah Pendapatan</p>
<p class="text-xl font-bold text-primary">RM {{ profile.totalIncome }}</p>
</div>
</rs-card>
</div>
<div class="mt-6">
<h3 class="text-lg font-semibold text-primary mb-4">Butiran Pendapatan</h3>
<div class="bg-gray-50 p-4 rounded-lg">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<h4 class="text-sm font-medium text-gray-500">Pekerjaan</h4>
<p>{{ profile.occupation }}</p>
</div>
<div>
<h4 class="text-sm font-medium text-gray-500">Pendapatan Bulanan</h4>
<p>RM {{ profile.monthlyIncome }}</p>
</div>
<div>
<h4 class="text-sm font-medium text-gray-500">Pendapatan Lain</h4>
<p>RM {{ profile.otherIncome }}</p>
</div>
<div>
<h4 class="text-sm font-medium text-gray-500">Jumlah Pendapatan</h4>
<p>RM {{ profile.totalIncome }}</p>
</div>
</div>
</div>
</div>
</div>
<!-- Aid Information Tab -->
<div v-if="activeTab === 'aid'" class="p-6">
<div class="bg-gray-50 p-6 rounded-lg text-center">
<Icon name="mdi:gift-off" size="48" class="text-gray-400 mb-2" />
<p class="text-gray-500">Tiada maklumat bantuan</p>
</div>
</div>
<!-- Documents Tab -->
<div v-if="activeTab === 'documents'" class="p-6">
<h3 class="text-lg font-semibold text-primary mb-4">Dokumen Sokongan</h3>
<div v-if="profile.documents && profile.documents.length > 0">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<div v-for="(doc, index) in profile.documents" :key="index" class="border rounded-lg overflow-hidden">
<div class="bg-gray-50 p-4 flex items-center">
<Icon name="mdi:file-document" size="24" class="text-primary mr-3" />
<div>
<h4 class="font-medium">{{ doc.name }}</h4>
<p class="text-sm text-gray-500">{{ doc.size }}</p>
</div>
</div>
<div class="p-3 flex justify-end">
<rs-button variant="primary-text" size="sm">
<Icon name="mdi:download" size="16" class="mr-1" />
Muat Turun
</rs-button>
<rs-button variant="secondary-text" size="sm">
<Icon name="mdi:eye" size="16" class="mr-1" />
Papar
</rs-button>
</div>
</div>
</div>
</div>
<div v-else class="bg-gray-50 p-6 rounded-lg text-center">
<Icon name="mdi:file-document-off" size="48" class="text-gray-400 mb-2" />
<p class="text-gray-500">Tiada dokumen</p>
</div>
</div>
<!-- Analysis Tab -->
<div v-if="activeTab === 'analysis'" class="p-6">
<!-- Button to trigger AI Analysis -->
<div v-if="!profile.analysis && !isLoadingAnalysis" class="text-center mb-6">
<rs-button variant="primary" @click="fetchAIAnalysis" size="lg">
<Icon name="mdi:brain" size="20" class="mr-2" />
Jalankan Analisis AI
</rs-button>
<p class="text-sm text-gray-500 mt-2">Klik untuk mendapatkan penilaian berdasarkan data profil.</p>
</div>
<!-- Loading State for AI Analysis -->
<div v-if="isLoadingAnalysis" class="text-center py-10">
<Loading />
<p class="mt-4 text-gray-600">Analisis AI sedang dijalankan...</p>
<p class="text-sm text-gray-500">Sila tunggu sebentar.</p>
</div>
<!-- Display Analysis Results -->
<div v-if="profile.analysis && !isLoadingAnalysis" class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- AI Analysis Main Column (takes 2/3 on lg screens) -->
<div class="lg:col-span-2 space-y-6">
<!-- Card 1: Analisis Had Kifayah & Kelayakan -->
<rs-card>
<template #header>
<h3 class="text-lg font-semibold text-primary p-4 border-b">Analisis Had Kifayah & Kelayakan (AI)</h3>
</template>
<template #body>
<div class="p-4 space-y-4">
<div>
<h4 class="text-sm font-medium text-gray-500 mb-1">Peratusan Had Kifayah</h4>
<div v-if="profile.analysis.hadKifayahPercentage === 'N/A' || profile.analysis.hadKifayahPercentage === 'Ralat'" class="text-gray-500">
{{ profile.analysis.hadKifayahPercentage }}
</div>
<div v-else class="relative pt-1">
<div class="overflow-hidden h-4 text-xs flex rounded bg-gray-200">
<div
:style="{ width: profile.analysis.hadKifayahPercentage }"
:class="{
'bg-red-500': parseInt(profile.analysis.hadKifayahPercentage) < 60,
'bg-yellow-500': parseInt(profile.analysis.hadKifayahPercentage) >= 60 && parseInt(profile.analysis.hadKifayahPercentage) < 80,
'bg-green-500': parseInt(profile.analysis.hadKifayahPercentage) >= 80 && parseInt(profile.analysis.hadKifayahPercentage) <= 100,
'bg-blue-500': parseInt(profile.analysis.hadKifayahPercentage) > 100
}"
class="shadow-none flex flex-col text-center whitespace-nowrap text-white justify-center"
>
{{ profile.analysis.hadKifayahPercentage }}
</div>
</div>
</div>
</div>
<div>
<h4 class="text-sm font-medium text-gray-500">Kategori Asnaf (AI)</h4>
<p>{{ profile.analysis.kategoriAsnaf }}</p>
</div>
<div>
<h4 class="text-sm font-medium text-gray-500">Kategori Keluarga (AI)</h4>
<p>{{ profile.analysis.kategoriKeluarga }}</p>
</div>
<div>
<h4 class="text-sm font-medium text-gray-500">Cadangan Kategori (AI)</h4>
<p>{{ profile.analysis.cadanganKategori }}</p>
</div>
<div>
<h4 class="text-sm font-medium text-gray-500">Status Kelayakan (AI)</h4>
<p>{{ profile.analysis.statusKelayakan }}</p>
</div>
</div>
</template>
</rs-card>
<!-- Card 2: Cadangan & Rumusan AI -->
<rs-card>
<template #header>
<h3 class="text-lg font-semibold text-primary p-4 border-b">Cadangan & Rumusan (AI)</h3>
</template>
<template #body>
<div class="p-4 space-y-4">
<div>
<h4 class="text-sm font-medium text-gray-500">Cadangan Bantuan (AI)</h4>
<ul v-if="profile.analysis.cadanganBantuan && profile.analysis.cadanganBantuan.length > 0" class="list-disc list-inside space-y-1 mt-1">
<li v-for="(bantuan, index) in profile.analysis.cadanganBantuan" :key="index" class="text-gray-700">
{{ bantuan.nama }}
<span v-if="bantuan.peratusan && bantuan.peratusan !== 'Ralat'" class="font-semibold text-blue-600">({{ bantuan.peratusan }})</span>
<span v-else-if="bantuan.peratusan === 'Ralat'" class="text-red-500 text-xs">({{ bantuan.peratusan }})</span>
</li>
</ul>
<p v-else class="text-gray-500 mt-1">Tiada cadangan bantuan spesifik.</p>
</div>
<div>
<h4 class="text-sm font-medium text-gray-500">Ramalan Jangka Masa Taraf Hidup Pulih (AI)</h4>
<p>{{ profile.analysis.ramalanJangkaMasaPulih }}</p>
</div>
<div>
<h4 class="text-sm font-medium text-gray-500">Rumusan Keseluruhan (AI)</h4>
<div class="mt-1 p-3 bg-blue-50 border-l-4 border-blue-500 rounded-r-md">
<p class="whitespace-pre-line text-gray-700 text-sm">{{ profile.analysis.rumusan }}</p>
</div>
</div>
</div>
</template>
</rs-card>
</div>
<!-- Original Data Column (takes 1/3 on lg screens) -->
<div class="lg:col-span-1">
<rs-card>
<template #header>
<h3 class="text-lg font-semibold text-gray-700 p-4 border-b">Ringkasan Profil (Data Asal)</h3>
</template>
<template #body>
<div class="p-4 space-y-3">
<div>
<h4 class="text-sm font-medium text-gray-500">Jenis Kategori (Asal)</h4>
<rs-badge :variant="categoryColor" class="mt-1">{{ profile.kategori }}</rs-badge>
</div>
<div>
<h4 class="text-sm font-medium text-gray-500">Status Semasa (Asal)</h4>
<rs-badge :variant="statusColor" class="mt-1">{{ profile.status }}</rs-badge>
</div>
<div>
<h4 class="text-sm font-medium text-gray-500">Jumlah Pendapatan (Asal)</h4>
<p>RM {{ profile.totalIncome }}</p>
</div>
<div>
<h4 class="text-sm font-medium text-gray-500">Jumlah Tanggungan (Asal)</h4>
<p>{{ profile.dependents.length }} orang</p>
</div>
</div>
</template>
</rs-card>
</div>
</div>
</div>
</template>
</rs-card>
</div>
<!-- Delete Confirmation Modal -->
<rs-modal v-model="confirmDelete">
<template #header>
<div class="flex items-center">
<Icon name="mdi:alert-circle" size="24" class="text-red-500 mr-2" />
<h3 class="text-lg font-medium">Padam Profil</h3>
</div>
</template>
<template #default>
<div class="p-4">
<p class="mb-4">Adakah anda pasti ingin memadam profil ini?</p>
<p class="text-sm text-gray-500 mb-2">Nama: <span class="font-medium">{{ profile?.nama }}</span></p>
<p class="text-sm text-gray-500">ID: <span class="font-medium">{{ profile?.id }}</span></p>
</div>
</template>
<template #footer>
<div class="flex justify-end gap-2">
<rs-button variant="secondary-outline" @click="cancelDelete">
Batal
</rs-button>
<rs-button variant="danger" @click="confirmDeleteProfile">
Padam
</rs-button>
</div>
</template>
</rs-modal>
</div>
</template>

View File

@ -1,337 +0,0 @@
<script setup>
import { ref, computed, onMounted } from 'vue';
import { useAsnafMockData } from '~/composables/useAsnafMockData';
definePageMeta({
title: "Senarai Asnaf",
middleware: ["auth"],
requiresAuth: true,
breadcrumb: [
{
name: "Dashboard",
path: "/",
},
{
name: "BF-PRF",
path: "/BF-PRF",
},
{
name: "Asnaf",
path: "/BF-PRF/AS",
},
{
name: "Senarai",
path: "/BF-PRF/AS/LIST",
},
],
});
// Get asnaf data from the composable
const { asnafProfiles, statistics, filterProfiles, categories, statuses } = useAsnafMockData();
// Table reactivity control
const tableKey = ref(0);
const currentPage = ref(1);
const pageSize = ref(10);
const totalProfiles = ref(0);
const isLoading = ref(false);
// Search and filter variables
const searchQuery = ref('');
const selectedStatus = ref('All');
const selectedCategory = ref('All');
// Table data and fields
const tableData = computed(() => {
return filterProfiles(searchQuery.value, selectedStatus.value, selectedCategory.value);
});
const tableFields = [
{ field: 'no', label: 'No.' },
{ field: 'id', label: 'ID' },
{ field: 'nama', label: 'Nama' },
{ field: 'idNumber', label: 'No. ID' },
{ field: 'kategori', label: 'Kategori' },
{ field: 'status', label: 'Status' },
{ field: 'tindakan', label: 'Tindakan' }
];
// Generate table field and data mapping
const formattedTableData = computed(() => {
return tableData.value.map((profile, index) => ({
no: index + 1,
id: profile.id,
nama: profile.nama,
idNumber: profile.idNumber,
kategori: profile.kategori,
status: profile.status,
tindakan: profile.id
}));
});
// Helper functions
function getBadgeVariantForCategory(category) {
switch (category) {
case 'Fakir': return 'danger';
case 'Miskin': return 'warning';
case 'Mualaf': return 'info';
case 'Fi-sabilillah': return 'primary';
case 'Gharimin': return 'secondary';
case 'Ibnu Sabil': return 'success';
default: return 'primary';
}
}
function getBadgeVariantForStatus(status) {
switch (status) {
case 'Aktif': return 'success';
case 'Tidak Aktif': return 'danger';
case 'Dalam Semakan': return 'warning';
default: return 'secondary';
}
}
function navigateToDetail(id) {
console.log("Attempting to navigate to detail for ID:", id);
if (id) {
navigateTo(`/BF-PRF/AS/DETAIL/${id}`);
} else {
console.error("Navigation failed: ID is undefined or null");
}
}
function navigateToRegistration() {
navigateTo('/BF-PRF/AS/FR/01');
}
// Pagination
const handlePageChange = (newPage) => {
currentPage.value = newPage;
fetchProfiles();
};
// Fetch data
async function fetchProfiles() {
isLoading.value = true;
// Simulate API delay
await new Promise(resolve => setTimeout(resolve, 500));
totalProfiles.value = tableData.value.length;
isLoading.value = false;
tableKey.value++; // Force table re-render
}
// Lifecycle hooks
onMounted(() => {
fetchProfiles();
});
</script>
<template>
<div class="space-y-6">
<LayoutsBreadcrumb />
<!-- Header -->
<div class="flex justify-between items-center">
<h1 class="text-2xl font-bold text-primary">Senarai Asnaf</h1>
<rs-button
variant="primary"
class="flex items-center gap-2"
@click="navigateToRegistration"
>
<Icon name="mdi:plus" size="18" />
<span>Tambah Asnaf</span>
</rs-button>
</div>
<!-- Statistics Cards -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<rs-card class="transition-all duration-300 hover:shadow-lg">
<div class="p-4 flex items-center gap-4">
<div class="p-4 flex justify-center items-center bg-blue-100 rounded-xl">
<Icon name="mdi:account-group" size="24" class="text-primary" />
</div>
<div>
<span class="block text-2xl font-bold text-primary">{{ statistics.total }}</span>
<span class="text-sm text-gray-600">Jumlah Asnaf</span>
</div>
</div>
</rs-card>
<rs-card class="transition-all duration-300 hover:shadow-lg">
<div class="p-4 flex items-center gap-4">
<div class="p-4 flex justify-center items-center bg-green-100 rounded-xl">
<Icon name="mdi:check-circle" size="24" class="text-green-600" />
</div>
<div>
<span class="block text-2xl font-bold text-green-600">{{ statistics.active }}</span>
<span class="text-sm text-gray-600">Aktif</span>
</div>
</div>
</rs-card>
<rs-card class="transition-all duration-300 hover:shadow-lg">
<div class="p-4 flex items-center gap-4">
<div class="p-4 flex justify-center items-center bg-yellow-100 rounded-xl">
<Icon name="mdi:clock-time-four" size="24" class="text-yellow-600" />
</div>
<div>
<span class="block text-2xl font-bold text-yellow-600">{{ statistics.review }}</span>
<span class="text-sm text-gray-600">Dalam Semakan</span>
</div>
</div>
</rs-card>
<rs-card class="transition-all duration-300 hover:shadow-lg">
<div class="p-4 flex items-center gap-4">
<div class="p-4 flex justify-center items-center bg-red-100 rounded-xl">
<Icon name="mdi:close-circle" size="24" class="text-red-600" />
</div>
<div>
<span class="block text-2xl font-bold text-red-600">{{ statistics.inactive }}</span>
<span class="text-sm text-gray-600">Tidak Aktif</span>
</div>
</div>
</rs-card>
</div>
<!-- Search and Filters -->
<rs-card>
<div class="p-4">
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Carian</label>
<div class="relative rounded-md shadow-sm">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<Icon name="mdi:magnify" size="18" class="text-gray-400" />
</div>
<input
v-model="searchQuery"
type="text"
class="block w-full pl-10 pr-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-primary focus:border-primary"
placeholder="Cari dengan nama atau ID..."
/>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Status</label>
<select
v-model="selectedStatus"
class="block w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-primary focus:border-primary"
>
<option value="All">Semua Status</option>
<option v-for="status in statuses" :key="status" :value="status">
{{ status }}
</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Kategori</label>
<select
v-model="selectedCategory"
class="block w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-primary focus:border-primary"
>
<option value="All">Semua Kategori</option>
<option v-for="category in categories" :key="category" :value="category">
{{ category }}
</option>
</select>
</div>
</div>
</div>
</rs-card>
<!-- Data Table -->
<rs-card>
<template #header>
<div class="px-4 py-3 flex items-center justify-between">
<h2 class="text-lg font-semibold text-primary">Senarai Asnaf</h2>
<span class="text-sm text-gray-500">
{{ tableData.length }} asnaf dijumpai
</span>
</div>
</template>
<template #body>
<div v-if="isLoading && tableData.length === 0" class="py-8 text-center">
<div class="flex justify-center">
<Icon name="mdi:loading" size="2rem" class="text-blue-500 animate-spin" />
</div>
<p class="mt-2 text-gray-600">Memuat data...</p>
</div>
<rs-table
v-else
class="mt-4"
:key="tableKey"
:data="formattedTableData"
:columns="tableFields"
:pageSize="pageSize"
:showNoColumn="true"
:options="{
variant: 'default',
hover: true,
striped: true,
bordered: true
}"
:current-page="currentPage"
:total-items="totalProfiles"
@page-change="handlePageChange"
>
<template v-slot:no="data">
{{ data.text }}
</template>
<template v-slot:id="data">
{{ data.text }}
</template>
<template v-slot:nama="data">
<div class="font-medium">{{ data.text }}</div>
</template>
<template v-slot:idNumber="data">
{{ data.text }}
</template>
<template v-slot:kategori="data">
<rs-badge :variant="getBadgeVariantForCategory(data.text)">{{ data.text }}</rs-badge>
</template>
<template v-slot:status="data">
<rs-badge :variant="getBadgeVariantForStatus(data.text)">
{{ data.text }}
</rs-badge>
</template>
<template v-slot:tindakan="data">
<div class="flex gap-2">
<rs-button
variant="primary"
size="sm"
class="!px-2 !py-1"
@click="() => {
navigateToDetail(data.value.tindakan);
}"
>
<Icon name="mdi:eye" size="1rem" class="mr-1" />
Lihat
</rs-button>
</div>
</template>
</rs-table>
<!-- Empty State -->
<div v-if="!isLoading && tableData.length === 0" class="text-center py-8">
<div class="flex justify-center mb-4">
<Icon name="mdi:magnify" size="4rem" class="text-gray-400" />
</div>
<h3 class="text-lg font-medium text-gray-500">Tiada Profil Ditemui</h3>
<p class="text-gray-500 mt-2">Sila cuba carian lain atau reset penapis.</p>
</div>
</template>
</rs-card>
</div>
</template>

135
pages/dashboard.vue Normal file
View File

@ -0,0 +1,135 @@
<template>
<div class="min-h-screen bg-gray-50">
<nav class="bg-white shadow">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between h-16">
<div class="flex items-center">
<h1 class="text-xl font-semibold text-gray-900">
CorradAF RBAC Dashboard
</h1>
</div>
<div class="flex items-center space-x-4">
<span v-if="user" class="text-sm text-gray-700">
Welcome, {{ user.name || user.email }}
</span>
<button
@click="handleLogout"
class="bg-red-600 hover:bg-red-700 text-white px-3 py-2 rounded-md text-sm font-medium"
>
Logout
</button>
</div>
</div>
</div>
</nav>
<main class="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
<div class="px-4 py-6 sm:px-0">
<div class="border-4 border-dashed border-gray-200 rounded-lg p-8">
<div v-if="pending">
<p class="text-gray-500">Loading user information...</p>
</div>
<div v-else-if="user" class="space-y-6">
<div>
<h2 class="text-2xl font-bold text-gray-900 mb-4">
🎉 Authentication Successful!
</h2>
<p class="text-gray-600 mb-6">
You are now logged into the CorradAF RBAC system as the master provider.
</p>
</div>
<div class="bg-white rounded-lg shadow p-6">
<h3 class="text-lg font-medium text-gray-900 mb-4">
User Information
</h3>
<dl class="grid grid-cols-1 gap-x-4 gap-y-6 sm:grid-cols-2">
<div>
<dt class="text-sm font-medium text-gray-500">Name</dt>
<dd class="mt-1 text-sm text-gray-900">{{ user.name || 'N/A' }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">Email</dt>
<dd class="mt-1 text-sm text-gray-900">{{ user.email }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">Username</dt>
<dd class="mt-1 text-sm text-gray-900">{{ user.preferred_username || 'N/A' }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">User ID</dt>
<dd class="mt-1 text-sm text-gray-900">{{ user.sub }}</dd>
</div>
</dl>
</div>
<div class="bg-blue-50 border border-blue-200 rounded-lg p-4">
<h4 class="text-sm font-medium text-blue-800 mb-2">
Next Steps
</h4>
<ul class="text-sm text-blue-700 space-y-1">
<li> You can now create and manage other applications</li>
<li> Add users and assign them to applications</li>
<li> Create roles and permissions</li>
<li> Manage groups and access control</li>
</ul>
</div>
<div class="flex space-x-4">
<NuxtLink
to="/applications"
class="bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-md text-sm font-medium"
>
Manage Applications
</NuxtLink>
<NuxtLink
to="/users"
class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-md text-sm font-medium"
>
Manage Users
</NuxtLink>
</div>
</div>
<div v-else class="text-center">
<p class="text-red-600">Failed to load user information</p>
<NuxtLink
to="/login"
class="mt-4 inline-block bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-md text-sm font-medium"
>
Login Again
</NuxtLink>
</div>
</div>
</div>
</main>
</div>
</template>
<script setup>
// Require authentication for this page
definePageMeta({
middleware: 'auth'
});
// Use the auth composable
const { logout } = useAuth();
// Get user information with proper error handling
const { data: user, pending } = await useFetch('/api/auth/me', {
default: () => null,
server: false // Client-side only to handle redirects properly
});
// If not authenticated, redirect to login
watchEffect(() => {
if (!pending.value && !user.value) {
navigateTo('/login');
}
});
const handleLogout = async () => {
await logout();
};
</script>

View File

@ -1,10 +1,15 @@
<script setup>
// Use main middleware to handle routing based on auth status
definePageMeta({
title: "Main",
middleware: ["main"],
middleware: 'main'
});
</script>
<template>
<div>Redirect Dashboard</div>
<div class="min-h-screen flex items-center justify-center bg-gray-50">
<div class="text-center">
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-indigo-600 mx-auto"></div>
<p class="mt-4 text-gray-600">Checking authentication...</p>
</div>
</div>
</template>

58
pages/login.vue Normal file
View File

@ -0,0 +1,58 @@
<template>
<div class="min-h-screen flex items-center justify-center bg-gray-50">
<div class="max-w-md w-full space-y-8">
<div>
<h2 class="mt-6 text-center text-3xl font-extrabold text-gray-900">
CorradAF RBAC System
</h2>
<p class="mt-2 text-center text-sm text-gray-600">
Sign in to manage applications and users
</p>
</div>
<div class="rounded-md shadow-sm -space-y-px">
<button
@click="loginWithAuthentik"
:disabled="isLoading"
class="group relative w-full flex justify-center py-3 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50"
>
<span v-if="!isLoading">Sign in with Authentik</span>
<span v-else>Redirecting...</span>
</button>
</div>
<div class="text-center">
<p class="text-xs text-gray-500">
This will redirect you to Authentik for secure authentication
</p>
</div>
</div>
</div>
</template>
<script setup>
// Redirect authenticated users to dashboard
definePageMeta({
middleware: 'dashboard'
});
const isLoading = ref(false);
const loginWithAuthentik = async () => {
isLoading.value = true;
try {
// Redirect to our auth endpoint which handles Authentik OAuth2
await navigateTo('/api/auth/login', { external: true });
} catch (error) {
console.error('Login error:', error);
isLoading.value = false;
}
};
// Redirect if already authenticated
onMounted(() => {
// Check if user is already authenticated (you can implement this later)
// For now, just show the login page
});
</script>

View File

@ -1,49 +0,0 @@
<template>
<div>
<LayoutsBreadcrumb />
<section class="flex flex-col h-screen">
<div class="mb-4 flex-shrink-0">
<h3>Metabase</h3>
<p>
Metabase is a powerful data visualization and analytics tool that allows you to
create and share dashboards, reports, and visualizations with your team.
</p>
</div>
<div v-if="pending" class="flex justify-center items-center flex-1">
<div class="text-lg">Loading Metabase dashboard...</div>
</div>
<div v-else-if="error" class="flex justify-center items-center flex-1">
<div class="text-red-500">Error loading dashboard: {{ error.message }}</div>
</div>
<iframe
v-else
:src="iframeUrl"
frameborder="0"
width="100%"
class="flex-1"
allowtransparency
/>
</section>
</div>
</template>
<script setup>
// Fetch the JWT token from our server API
const { data: tokenData, pending, error } = await useFetch("/api/metabase/token");
const iframeUrl = computed(() => {
if (tokenData.value?.token && tokenData.value?.siteUrl) {
return (
tokenData.value.siteUrl +
"/embed/dashboard/" +
tokenData.value.token +
"#bordered=true&titled=true"
);
}
return "";
});
</script>
<style lang="scss" scoped></style>

View File

@ -1,30 +0,0 @@
<script setup>
definePageMeta({
title: "Notes",
middleware: ["auth"],
requiresAuth: true,
});
</script>
<template>
<div>
<LayoutsBreadcrumb />
<rs-card>
<template #header>
<div>
Notes
</div>
</template>
<template #body>
<div>
Content for Notes
</div>
</template>
</rs-card>
</div>
</template>
<style scoped>
/* Add your styles here */
</style>

View File

@ -1,124 +0,0 @@
import { defineEventHandler, readBody } from 'h3';
// Define an interface for the expected request body (subset of AsnafProfile)
interface AsnafAnalysisRequest {
monthlyIncome: string;
otherIncome: string;
totalIncome: string;
occupation: string;
maritalStatus: string;
dependents: Array<any>; // Or a more specific type if you have one for dependents
// Add any other fields you deem necessary for OpenAI to analyze
}
interface AidSuggestion {
nama: string;
peratusan: string;
}
// Define an interface for the expected OpenAI response structure (and our API response)
interface AsnafAnalysisResponse {
hadKifayahPercentage: string;
kategoriAsnaf: string;
kategoriKeluarga: string;
cadanganKategori: string;
statusKelayakan: string;
cadanganBantuan: AidSuggestion[];
ramalanJangkaMasaPulih: string;
rumusan: string;
}
export default defineEventHandler(async (event): Promise<AsnafAnalysisResponse> => {
const body = await readBody<AsnafAnalysisRequest>(event);
// --- Placeholder for Actual OpenAI API Call ---
// In a real application, you would:
// 1. Retrieve your OpenAI API key securely (e.g., from environment variables)
const openAIApiKey = process.env.OPENAI_API_KEY;
if (!openAIApiKey) {
console.error('OpenAI API key not configured. Please set OPENAI_API_KEY in your .env file.');
throw createError({ statusCode: 500, statusMessage: 'OpenAI API key not configured' });
}
// 2. Construct the prompt for OpenAI using the data from `body`.
// IMPORTANT: Sanitize or carefully construct any data from `body` included in the prompt to prevent prompt injection.
const prompt = `You are an expert Zakat administrator. Based on the following applicant data: monthlyIncome: ${body.monthlyIncome}, totalIncome: ${body.totalIncome}, occupation: ${body.occupation}, maritalStatus: ${body.maritalStatus}, dependents: ${body.dependents.length}.
Return JSON with keys: hadKifayahPercentage, kategoriAsnaf, kategoriKeluarga, cadanganKategori, statusKelayakan, cadanganBantuan, ramalanJangkaMasaPulih, rumusan.
For 'cadanganBantuan', provide a JSON array of objects, where each object has a 'nama' (string, name of the aid) and 'peratusan' (string, e.g., '85%', representing suitability). Suggest 2-3 most relevant aid types.
Example for cadanganBantuan: [{"nama": "Bantuan Kewangan Bulanan", "peratusan": "90%"}, {"nama": "Bantuan Makanan Asas", "peratusan": "75%"}].
Full JSON Example: {"hadKifayahPercentage": "75%", ..., "cadanganBantuan": [{"nama": "Bantuan Kewangan Bulanan", "peratusan": "90%"}], ...}`;
// Adjust the prompt to be more detailed and specific to your needs and desired JSON output structure.
// 3. Make the API call to OpenAI
try {
const openAIResponse = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${openAIApiKey}`,
},
body: JSON.stringify({
model: 'gpt-3.5-turbo', // Or your preferred model like gpt-4
messages: [{ role: 'user', content: prompt }],
// For more consistent JSON output, consider using a model version that officially supports JSON mode if available
// and set response_format: { type: "json_object" }, (check OpenAI documentation for model compatibility)
}),
});
if (!openAIResponse.ok) {
const errorData = await openAIResponse.text();
console.error('OpenAI API Error details:', errorData);
throw createError({ statusCode: openAIResponse.status, statusMessage: `Failed to get analysis from OpenAI: ${openAIResponse.statusText}` });
}
const openAIData = await openAIResponse.json();
// Parse the content from the response - structure might vary slightly based on OpenAI model/API version
// It's common for the JSON string to be in openAIData.choices[0].message.content
if (openAIData.choices && openAIData.choices[0] && openAIData.choices[0].message && openAIData.choices[0].message.content) {
const analysisResult = JSON.parse(openAIData.choices[0].message.content) as AsnafAnalysisResponse;
return analysisResult;
} else {
console.error('OpenAI response structure not as expected:', openAIData);
throw createError({ statusCode: 500, statusMessage: 'Unexpected response structure from OpenAI' });
}
} catch (error) {
console.error('Error during OpenAI API call or parsing:', error);
// Avoid exposing detailed internal errors to the client if they are not createError objects
if (typeof error === 'object' && error !== null && 'statusCode' in error) {
// We can infer error has statusCode here, but to be super safe with TS:
const e = error as { statusCode: number };
if (e.statusCode) throw e;
}
throw createError({ statusCode: 500, statusMessage: 'Internal server error during AI analysis' });
}
// --- End of Actual OpenAI API Call ---
// The simulated response below this line should be REMOVED once the actual OpenAI call is implemented and working.
/*
console.log('Received for analysis in server route:', body);
await new Promise(resolve => setTimeout(resolve, 2000)); // Simulate API delay
const totalIncomeNumeric = parseFloat(body.totalIncome);
let percentage = '50%';
if (totalIncomeNumeric < 1000) percentage = '30%';
else if (totalIncomeNumeric < 2000) percentage = '65%';
else if (totalIncomeNumeric < 3000) percentage = '85%';
else percentage = '110%';
return {
hadKifayahPercentage: percentage,
kategoriAsnaf: 'Simulated - Miskin',
kategoriKeluarga: 'Simulated - Miskin (50-100% HK)',
cadanganKategori: 'Simulated - Miskin',
statusKelayakan: 'Simulated - Layak (Miskin)',
cadanganBantuan: [
{ nama: 'Simulated - Bantuan Kewangan Bulanan', peratusan: '80%' },
{ nama: 'Simulated - Bantuan Pendidikan Anak', peratusan: '65%' }
],
ramalanJangkaMasaPulih: 'Simulated - 6 bulan',
rumusan: 'Simulated - Pemohon memerlukan perhatian segera.'
};
*/
});

View File

@ -0,0 +1,73 @@
import { authentikFetch } from '../../utils/authentik';
import { requireAuth } from '../../utils/auth';
// /api/applications/[id] - Handle GET, PUT, DELETE for specific application
export default defineEventHandler(async (event) => {
const method = getMethod(event);
const id = getRouterParam(event, 'id');
// Require authentication
await requireAuth(event);
if (!id) {
throw createError({
statusCode: 400,
message: 'Application ID is required'
});
}
switch (method) {
case 'GET':
try {
const application = await authentikFetch(`/core/applications/${id}/`);
return application;
} catch (error) {
throw createError({
statusCode: error.statusCode || 404,
message: error.message || 'Application not found'
});
}
case 'PUT':
try {
const body = await readBody(event);
const application = await authentikFetch(`/core/applications/${id}/`, {
method: 'PUT',
body: {
name: body.name,
slug: body.slug,
meta_description: body.description,
meta_publisher: 'CorradAF RBAC'
}
});
return application;
} catch (error) {
throw createError({
statusCode: error.statusCode || 500,
message: error.message
});
}
case 'DELETE':
try {
await authentikFetch(`/core/applications/${id}/`, {
method: 'DELETE'
});
return { message: 'Application deleted successfully' };
} catch (error) {
throw createError({
statusCode: error.statusCode || 500,
message: error.message
});
}
default:
throw createError({
statusCode: 405,
message: 'Method not allowed'
});
}
});

View File

@ -0,0 +1,62 @@
import { authentikFetch, createAuthentikApplication, createAuthentikProvider, linkProviderToApplication } from '../../utils/authentik';
import { requireAuth } from '../../utils/auth';
// /api/applications - Handle GET and POST
export default defineEventHandler(async (event) => {
const method = getMethod(event);
// Require authentication for all application endpoints
await requireAuth(event);
switch (method) {
case 'GET':
try {
const applications = await authentikFetch('/core/applications/');
return applications;
} catch (error) {
throw createError({
statusCode: error.statusCode || 500,
message: error.message
});
}
case 'POST':
try {
const body = await readBody(event);
// Create application in Authentik
const application = await createAuthentikApplication({
name: body.name,
slug: body.slug || body.name.toLowerCase().replace(/\s+/g, '-'),
meta_description: body.description,
meta_publisher: 'CorradAF RBAC'
});
// Create OAuth2 provider if web application
if (body.type === 'web-app') {
const provider = await createAuthentikProvider({
name: `${body.name} OAuth2`,
client_type: 'confidential',
redirect_uris: body.redirectUris,
authorization_flow: body.authorizationFlow || 'default-authentication-flow'
});
// Link provider to application
await linkProviderToApplication(application.pk, provider.pk);
}
return application;
} catch (error) {
throw createError({
statusCode: error.statusCode || 500,
message: error.message
});
}
default:
throw createError({
statusCode: 405,
message: 'Method not allowed'
});
}
});

View File

@ -0,0 +1,76 @@
// OAuth2 callback endpoint
export default defineEventHandler(async (event) => {
const config = useRuntimeConfig();
const query = getQuery(event);
if (query.error) {
throw createError({
statusCode: 400,
message: query.error_description || 'Authentication failed'
});
}
if (!query.code) {
throw createError({
statusCode: 400,
message: 'No authorization code received'
});
}
try {
// Exchange code for tokens
const tokens = await $fetch(`${config.public.authentikUrl}/application/o/token/`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
grant_type: 'authorization_code',
code: query.code,
redirect_uri: `${config.public.appUrl}/api/auth/callback`,
client_id: config.authentik.clientId,
client_secret: config.authentik.clientSecret
})
});
// Get user info using access token
const userInfo = await $fetch(`${config.public.authentikUrl}/application/o/userinfo/`, {
headers: {
'Authorization': `Bearer ${tokens.access_token}`
}
});
// Set cookies
setCookie(event, 'auth_token', tokens.access_token, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
maxAge: 60 * 60 * 24 // 24 hours
});
setCookie(event, 'refresh_token', tokens.refresh_token, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
maxAge: 60 * 60 * 24 * 30 // 30 days
});
// Store user info
setCookie(event, 'user_info', JSON.stringify(userInfo), {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
maxAge: 60 * 60 * 24 // 24 hours
});
// Redirect to dashboard
return sendRedirect(event, '/dashboard');
} catch (error) {
console.error('OAuth token exchange error:', error);
throw createError({
statusCode: error.response?.status || 500,
message: 'Failed to authenticate with Authentik'
});
}
});

13
server/api/auth/login.js Normal file
View File

@ -0,0 +1,13 @@
// Login endpoint - redirects to Authentik OAuth2
export default defineEventHandler(async (event) => {
const config = useRuntimeConfig();
// Redirect to Authentik login
const authUrl = new URL('/application/o/authorize/', config.public.authentikUrl);
authUrl.searchParams.append('client_id', config.authentik.clientId);
authUrl.searchParams.append('redirect_uri', `${config.public.appUrl}/api/auth/callback`);
authUrl.searchParams.append('response_type', 'code');
authUrl.searchParams.append('scope', 'openid profile email');
return sendRedirect(event, authUrl.toString());
});

12
server/api/auth/logout.js Normal file
View File

@ -0,0 +1,12 @@
// Logout endpoint
export default defineEventHandler(async (event) => {
const config = useRuntimeConfig();
// Clear auth cookies
deleteCookie(event, 'auth_token');
deleteCookie(event, 'refresh_token');
deleteCookie(event, 'user_info');
// Redirect to our login page instead of Authentik logout
return sendRedirect(event, '/login');
});

28
server/api/auth/me.js Normal file
View File

@ -0,0 +1,28 @@
// Get current user endpoint
export default defineEventHandler(async (event) => {
const config = useRuntimeConfig();
try {
const authToken = getCookie(event, 'auth_token');
if (!authToken) {
throw createError({
statusCode: 401,
message: 'Not authenticated'
});
}
const userInfo = await $fetch(`${config.public.authentikUrl}/application/o/userinfo/`, {
headers: {
'Authorization': `Bearer ${authToken}`
}
});
return userInfo;
} catch (error) {
throw createError({
statusCode: 401,
message: 'Invalid token'
});
}
});

View File

@ -1,34 +0,0 @@
export default defineEventHandler(async (event) => {
try {
const { userID } = event.context.user;
if (userID == null) {
return {
statusCode: 401,
message: "Unauthorized",
};
}
const validatedUser = await prisma.user.findFirst({
where: {
userID: parseInt(userID),
},
});
if (!validatedUser) {
return {
statusCode: 401,
message: "Unauthorized",
};
}
return {
statusCode: 200,
message: "Authorized",
};
} catch (error) {
return {
statusCode: 401,
message: "Unauthorized",
};
}
});

View File

@ -0,0 +1,41 @@
// Validate current authentication status
export default defineEventHandler(async (event) => {
const config = useRuntimeConfig();
try {
const authToken = getCookie(event, 'auth_token');
if (!authToken) {
return {
statusCode: 401,
message: "Not authenticated - no token found"
};
}
// Verify token with Authentik
const userInfo = await $fetch(`${config.public.authentikUrl}/application/o/userinfo/`, {
headers: {
'Authorization': `Bearer ${authToken}`
}
});
if (!userInfo) {
return {
statusCode: 401,
message: "Invalid token"
};
}
return {
statusCode: 200,
message: "Authorized",
user: userInfo
};
} catch (error) {
console.error('Token validation error:', error);
return {
statusCode: 401,
message: "Unauthorized - token validation failed"
};
}
});

View File

@ -1,28 +0,0 @@
import jwt from "jsonwebtoken";
export default defineEventHandler(async (event) => {
const config = useRuntimeConfig();
const METABASE_SECRET_KEY = config.metabase.secretKey;
const payload = {
resource: { dashboard: 2 },
params: {},
exp: Math.round(Date.now() / 1000) + 10 * 60, // 10 minute expiration
};
try {
const token = jwt.sign(payload, METABASE_SECRET_KEY);
return {
success: true,
token: token,
siteUrl: config.metabase.siteUrl
};
} catch (error) {
throw createError({
statusCode: 500,
statusMessage: 'Failed to generate Metabase token'
});
}
});

View File

@ -1,109 +0,0 @@
import jwt from "jsonwebtoken";
const ENV = useRuntimeConfig();
export default defineEventHandler(async (event) => {
try {
const cookies = event.req.headers.cookie;
if (!cookies) throw new Error("Cookie not found");
let { accessToken, refreshToken, user } = parseCookie(cookies);
if (!accessToken) accessToken = null;
if (!refreshToken) refreshToken = null;
let { subdomain } = JSON.parse(user);
if (!subdomain) subdomain = null;
let payloadUser = null;
payloadUser = verifyAccessToken(accessToken);
if (!payloadUser) {
payloadUser = verifyRefreshToken(refreshToken);
if (!payloadUser) throw new Error("Unauthorized Refresh Token");
const accessToken = generateAccessToken({
email: payloadUser.email,
roles: payloadUser.roles,
});
// Set new access token
event.res.setHeader("Set-Cookie", [
`accessToken=${accessToken}; HttpOnly; Secure; SameSite=Lax; Path=/`,
]);
}
const getUser = await getUserInfo(payloadUser.username);
if (!getUser) throw new Error("User not found");
event.context.user = {
userID: getUser.userID || null,
email: payloadUser.email || null,
roles: payloadUser.roles || [],
};
return;
} catch (error) {
// console.log(error.message);
event.context.user = {
userID: null,
email: null,
roles: [],
};
return;
}
});
function parseCookie(str) {
return str
.split(";")
.map((v) => v.split("="))
.reduce((acc, v) => {
acc[decodeURIComponent(v[0].trim())] = decodeURIComponent(v[1].trim());
return acc;
}, {});
}
function verifyAccessToken(accessToken) {
try {
const token = ENV.auth.secretAccess;
return jwt.verify(accessToken, token);
} catch (error) {
return false;
}
}
function verifyRefreshToken(refreshToken) {
try {
const token = ENV.auth.secretRefresh;
return jwt.verify(refreshToken, token);
} catch (error) {
return false;
}
}
function generateAccessToken(user) {
try {
const token = ENV.auth.secretAccess;
return jwt.sign(user, token, { expiresIn: "1d" });
} catch (error) {
return false;
}
}
async function getUserInfo(username) {
try {
const user = await prisma.user.findFirst({
where: {
userUsername: username,
},
});
if (!user) return null;
return user;
} catch (error) {
console.log(error);
}
}

View File

@ -1,3 +1,16 @@
import { defineNitroConfig } from "nitropack";
export default defineNitroConfig({});
export default defineNitroConfig({
runtimeConfig: {
// Private keys (server-side only)
authentikApiToken: process.env.AUTHENTIK_API_TOKEN,
authentikClientId: process.env.AUTHENTIK_CLIENT_ID,
authentikClientSecret: process.env.AUTHENTIK_CLIENT_SECRET,
// Public keys (client-side accessible)
public: {
appUrl: process.env.APP_URL || 'http://localhost:3000',
authentikUrl: process.env.AUTHENTIK_URL || 'http://localhost:9000'
}
}
});

50
server/utils/auth.js Normal file
View File

@ -0,0 +1,50 @@
// Authentication utilities for API routes
export const requireAuth = async (event) => {
const config = useRuntimeConfig();
const authHeader = getHeader(event, 'Authorization');
if (!authHeader || !authHeader.startsWith('Bearer ')) {
throw createError({
statusCode: 401,
message: 'No token provided'
});
}
// Extract the token without the 'Bearer ' prefix
const token = authHeader.split(' ')[1];
try {
// Verify token with Authentik
const response = await $fetch(`${config.public.authentikUrl}/api/v3/core/tokens/verify/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
});
// Add user info to event context
event.context.auth = response;
return response;
} catch (error) {
console.error('Token verification error:', error);
throw createError({
statusCode: 401,
message: 'Invalid or expired token'
});
}
};
// Optional: Get current user from context (after requireAuth is called)
export const getCurrentUser = (event) => {
return event.context.auth;
};
// Optional: Check if user has specific permissions
export const hasPermission = (event, permission) => {
const user = getCurrentUser(event);
if (!user || !user.permissions) return false;
return user.permissions.includes(permission);
};

74
server/utils/authentik.js Normal file
View File

@ -0,0 +1,74 @@
// Authentik API utilities
export const authentikFetch = async (endpoint, options = {}) => {
const config = useRuntimeConfig();
const AUTHENTIK_BASE_URL = `${config.public.authentikUrl}/api/v3`;
const defaultOptions = {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${config.authentik.apiToken}`
}
};
try {
// Log the request for debugging
console.log(`Authentik API Request: ${AUTHENTIK_BASE_URL}${endpoint}`);
const response = await $fetch(`${AUTHENTIK_BASE_URL}${endpoint}`, {
...defaultOptions,
...options,
headers: {
...defaultOptions.headers,
...options.headers
}
});
return response;
} catch (error) {
console.error(`Authentik API Error for ${endpoint}:`, error);
throw createError({
statusCode: error.response?.status || 500,
message: error.message || 'Failed to communicate with Authentik API'
});
}
};
export const getAuthentikUser = async (userId) => {
return await authentikFetch(`/core/users/${userId}/`);
};
export const getAuthentikGroups = async () => {
return await authentikFetch('/core/groups/');
};
export const createAuthentikApplication = async (applicationData) => {
return await authentikFetch('/core/applications/', {
method: 'POST',
body: applicationData
});
};
export const createAuthentikProvider = async (providerData) => {
return await authentikFetch('/providers/oauth2/', {
method: 'POST',
body: providerData
});
};
export const linkProviderToApplication = async (applicationId, providerId) => {
return await authentikFetch(`/core/applications/${applicationId}/`, {
method: 'PATCH',
body: {
provider: providerId
}
});
};
// Add a utility function to verify tokens
export const verifyToken = async (token) => {
return await authentikFetch('/core/tokens/verify/', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`
}
});
};