kontenhumas-be/docs/MULTI_CLIENT_ACCESS_GUIDE.md

429 lines
10 KiB
Markdown
Raw Normal View History

2025-09-30 13:34:56 +00:00
# Multi-Client Access & Client Hierarchy Guide
## 📋 Overview
Sistem ini mendukung **hierarchical multi-tenancy** dengan fitur:
- ✅ Parent-Child Client relationships (unlimited depth)
- ✅ User dapat memiliki akses ke multiple clients
- ✅ Super Admin dengan akses ke semua clients
- ✅ Fine-grained access control (read, write, admin, owner)
- ✅ Automatic sub-client inheritance
---
## 🏗️ Arsitektur
### Client Types
1. **Standalone Client** - Client mandiri tanpa parent/child
2. **Parent Client** - Client yang memiliki sub-clients
3. **Sub Client** - Client yang berada di bawah parent client
### User Types
1. **Super Admin** - Platform administrator, akses ke SEMUA clients
2. **Multi-Client User** - User dengan akses ke beberapa clients (misal: manager regional)
3. **Single-Client User** - User regular dengan akses ke 1 client saja
---
## 🗄️ Database Schema
### Entity: Clients
```go
type Clients struct {
ID uuid.UUID
Name string
Description *string
ClientType string // 'parent_client', 'sub_client', 'standalone'
ParentClientId *uuid.UUID // Reference to parent
Settings *string // JSONB custom settings
MaxUsers *int
MaxStorage *int64
CreatedById *uint
IsActive *bool
}
```
### Entity: UserClientAccess (Many-to-Many)
```go
type UserClientAccess struct {
ID uint
UserId uint
ClientId uuid.UUID
AccessType string // 'read', 'write', 'admin', 'owner'
CanManage *bool // Can manage client settings
CanDelegate *bool // Can give access to other users
IncludeSubClients *bool // Auto-access to all sub-clients
GrantedById *uint
IsActive *bool
}
```
### Entity: Users (Updated)
```go
type Users struct {
// ... existing fields
IsSuperAdmin *bool // Platform super admin
ClientId *uuid.UUID // Primary client
ClientAccesses []UserClientAccess // Multiple client access
}
```
---
## 🚀 Migration Steps
### Step 1: Run Database Migration
```bash
# Migration akan otomatis dijalankan saat aplikasi start
# Pastikan config migrate = true di config.toml
go run main.go
```
### Step 2: Migrate Existing Data
```sql
-- 1. Set semua existing clients sebagai 'standalone'
UPDATE clients
SET client_type = 'standalone'
WHERE client_type IS NULL;
-- 2. Create super admin user (contoh)
UPDATE users
SET is_super_admin = true
WHERE id = 1; -- Ganti dengan ID admin utama Anda
-- 3. Migrasi user existing ke UserClientAccess (optional)
-- Untuk user yang perlu akses ke multiple clients
INSERT INTO user_client_access (user_id, client_id, access_type, can_manage, is_active, created_at, updated_at)
SELECT
id as user_id,
client_id,
'admin' as access_type,
true as can_manage,
true as is_active,
NOW(),
NOW()
FROM users
WHERE client_id IS NOT NULL
AND is_super_admin = false;
```
### Step 3: Update Code
#### Option A: Gunakan Middleware V2 (Recommended)
Update di `config/webserver/webserver.config.go`:
```go
// Ganti middleware lama
// app.Use(middleware.ClientMiddleware(db.DB))
// Dengan middleware baru
app.Use(middleware.ClientMiddlewareV2(db.DB))
```
#### Option B: Bertahap (Backward Compatible)
Kedua middleware bisa berjalan bersamaan:
```go
// Keep old middleware for backward compatibility
app.Use(middleware.ClientMiddleware(db.DB))
// Routes baru bisa pakai V2
apiV2 := app.Group("/api/v2")
apiV2.Use(middleware.ClientMiddlewareV2(db.DB))
```
---
## 💻 Contoh Penggunaan
### 1. Setup Parent-Child Clients
```go
// Create Parent Client
parentClient := entity.Clients{
ID: uuid.New(),
Name: "Polda Metro Jaya",
ClientType: "parent_client",
IsActive: &trueVal,
}
db.Create(&parentClient)
// Create Sub Clients
subClient1 := entity.Clients{
ID: uuid.New(),
Name: "Polres Jakarta Pusat",
ClientType: "sub_client",
ParentClientId: &parentClient.ID,
IsActive: &trueVal,
}
db.Create(&subClient1)
subClient2 := entity.Clients{
ID: uuid.New(),
Name: "Polres Jakarta Barat",
ClientType: "sub_client",
ParentClientId: &parentClient.ID,
IsActive: &trueVal,
}
db.Create(&subClient2)
```
### 2. Grant Multi-Client Access
```go
import "netidhub-saas-be/app/database/entity"
// User bisa manage multiple clients
access1 := entity.UserClientAccess{
UserId: managerUser.ID,
ClientId: parentClient.ID,
AccessType: "admin",
CanManage: &trueVal,
IncludeSubClients: &trueVal, // Auto-access semua sub-clients
IsActive: &trueVal,
}
db.Create(&access1)
access2 := entity.UserClientAccess{
UserId: managerUser.ID,
ClientId: anotherClient.ID,
AccessType: "read",
IsActive: &trueVal,
}
db.Create(&access2)
```
### 3. Update Repository dengan Multi-Client Filter
**OLD CODE:**
```go
func (r *Repository) GetAll(clientId *uuid.UUID, req request.QueryRequest) {
query := r.DB.Model(&entity.Articles{})
if clientId != nil {
query = query.Where("client_id = ?", clientId)
}
// ... rest of code
}
```
**NEW CODE:**
```go
import middlewareUtils "netidhub-saas-be/utils/middleware"
func (r *Repository) GetAll(c *fiber.Ctx, req request.QueryRequest) {
query := r.DB.Model(&entity.Articles{})
// Automatically filter by accessible clients
query = middlewareUtils.AddMultiClientFilter(query, c)
// ... rest of code
}
```
### 4. Check User Access di Controller
```go
import (
customMiddleware "netidhub-saas-be/app/middleware"
clientUtils "netidhub-saas-be/utils/client"
)
func (ctrl *Controller) GetArticle(c *fiber.Ctx) error {
articleId := c.Params("id")
// Get user info
userId := c.Locals(customMiddleware.UserIDContextKey).(uint)
isSuperAdmin := customMiddleware.IsSuperAdmin(c)
// Get article
var article entity.Articles
db.First(&article, articleId)
// Check access
hasAccess, _ := clientUtils.HasAccessToClient(
db,
userId,
*article.ClientId,
isSuperAdmin,
)
if !hasAccess {
return c.Status(403).JSON(fiber.Map{
"error": "Access denied",
})
}
return c.JSON(article)
}
```
### 5. Get Client Hierarchy
```go
import clientUtils "netidhub-saas-be/utils/client"
func (ctrl *Controller) GetClientInfo(c *fiber.Ctx) error {
clientId := c.Params("id")
clientUUID, _ := uuid.Parse(clientId)
// Get full hierarchy
client, err := clientUtils.GetClientHierarchy(db, clientUUID)
return c.JSON(fiber.Map{
"client": client,
"parent": client.ParentClient,
"sub_clients": client.SubClients,
})
}
```
---
## 🎯 Use Cases
### Use Case 1: Regional Manager
**Scenario:** Manager regional yang mengawasi 3 Polres
```go
// Grant access dengan IncludeSubClients
access := entity.UserClientAccess{
UserId: regionalManagerId,
ClientId: parentPolresId,
AccessType: "admin",
IncludeSubClients: &trueVal, // Auto-akses semua polres di bawahnya
CanManage: &trueVal,
IsActive: &trueVal,
}
```
### Use Case 2: Super Admin Dashboard
```go
func (ctrl *AdminController) GetAllArticles(c *fiber.Ctx) error {
// Super admin bisa lihat semua artikel dari semua clients
if !customMiddleware.IsSuperAdmin(c) {
return c.Status(403).JSON(fiber.Map{
"error": "Super admin only",
})
}
var articles []entity.Articles
// No client filtering for super admin
db.Find(&articles)
return c.JSON(articles)
}
```
### Use Case 3: User Switching Between Clients
```go
// User bisa switch antara clients yang dia punya akses
func (ctrl *UserController) SwitchClient(c *fiber.Ctx) error {
targetClientId := c.Params("client_id")
clientUUID, _ := uuid.Parse(targetClientId)
userId := c.Locals(customMiddleware.UserIDContextKey).(uint)
// Verify user has access
hasAccess, _ := clientUtils.HasAccessToClient(
db,
userId,
clientUUID,
false,
)
if !hasAccess {
return c.Status(403).JSON(fiber.Map{
"error": "No access to this client",
})
}
// Update user's primary client or set in session
// Return success
}
```
---
## 🔒 Security Considerations
1. **Always validate client access** sebelum menampilkan/memodifikasi data
2. **Super admin flag** harus di-protect dengan ketat (database constraint)
3. **Audit trail** untuk perubahan UserClientAccess
4. **Cascade rules** untuk delete parent client
5. **Rate limiting** untuk multi-client queries
---
## 🐛 Troubleshooting
### Issue: User tidak bisa akses client
```go
// Debug: Check accessible clients
accessibleClients, _ := clientUtils.GetAccessibleClientIDs(db, userId, false)
fmt.Println("Accessible clients:", accessibleClients)
```
### Issue: Sub-clients tidak muncul
```go
// Pastikan IncludeSubClients = true
db.Model(&entity.UserClientAccess{}).
Where("user_id = ?", userId).
Update("include_sub_clients", true)
```
### Issue: Super admin tidak bisa akses
```go
// Verify super admin flag
var user entity.Users
db.First(&user, userId)
fmt.Println("Is Super Admin:", *user.IsSuperAdmin)
```
---
## 📊 Performance Tips
1. **Index** pada `parent_client_id` dan `user_id, client_id` (sudah ada di entity)
2. **Cache** accessible client IDs per user (Redis recommended)
3. **Batch queries** untuk sub-client retrieval
4. **Pagination** untuk multi-client data
---
## 🔄 Backward Compatibility
Kode lama tetap berfungsi:
-`middleware.GetClientID(c)` masih tersedia
-`ClientId` di Users tetap ada sebagai primary client
- ✅ Single-client filtering tetap work
- ✅ Header `X-Client-Key` masih didukung
Migration ke V2 bisa dilakukan bertahap per module.
---
## 📞 Support
Untuk pertanyaan atau issue, silakan hubungi tim development.