Skip to main content

๐Ÿ“ฑ Flutter RBAC Enterprise v4 (L0โ€“L4)

Blueprint arsitektur Flutter mobile-first ERP untuk sistem MStore yang scalable dari warung (L0) hingga public company (L4).
Berbasis prinsip Security โ†’ Efficiency โ†’ Ease โ†’ Style.

๐ŸŽฏ Prinsip Desain Utama

RBAC Contextual

Role menentukan apa yang terlihat & dapat diakses, bukan struktur folder.

Feature-First Modular

Struktur berbasis domain (finance, hr, inventory), bukan per role.

Tenant & Entity Isolation

L3+ siap multi-entity, tiap entitas terpisah context.

Global Policy Integration

L4 integrasi OPA, Casbin, IAM, dan regional policy (GDPR/SOX).

๐Ÿงฉ Struktur Folder (7 Domain Architecture)

lib/
โ”œโ”€โ”€ app/
โ”‚   โ”œโ”€โ”€ main.dart
โ”‚   โ”œโ”€โ”€ routes.dart
โ”‚   โ”œโ”€โ”€ app_provider.dart
โ”‚   โ”œโ”€โ”€ rbac/
โ”‚   โ”‚   โ”œโ”€โ”€ rbac_context.dart       # Global RBAC provider
โ”‚   โ”‚   โ”œโ”€โ”€ rbac_guard.dart         # RoleGuard untuk navigasi
โ”‚   โ”‚   โ”œโ”€โ”€ rbac_policy.dart        # Static & dynamic rule loader
โ”‚   โ”‚   โ””โ”€โ”€ rbac_registry.dart      # Sinkronisasi ke backend
โ”‚   โ”œโ”€โ”€ config/
โ”‚   โ”‚   โ”œโ”€โ”€ env.dart
โ”‚   โ”‚   โ”œโ”€โ”€ constants.dart
โ”‚   โ”‚   โ””โ”€โ”€ menu_registry.yaml      # Backend-driven menu (L4)
โ”‚   โ””โ”€โ”€ localization/
โ”‚       โ””โ”€โ”€ *.arb                   # i18n (GDPR, SOX notices)
โ”‚
โ”œโ”€โ”€ core/
โ”‚   โ”œโ”€โ”€ theme/
โ”‚   โ”œโ”€โ”€ services/                   # API, Storage, Logger
โ”‚   โ”‚   โ”œโ”€โ”€ audit_service.dart      # UI audit trail
โ”‚   โ”‚   โ””โ”€โ”€ rbac_service.dart       # RBAC API integration
โ”‚   โ”œโ”€โ”€ utils/
โ”‚   โ”œโ”€โ”€ widgets/
โ”‚   โ””โ”€โ”€ constants.dart
โ”‚
โ”œโ”€โ”€ features/
โ”‚   โ”œโ”€โ”€ core/                       # Auth, Profile, Settings
โ”‚   โ”œโ”€โ”€ ops/                        # POS, Sales, Inventory, Procurement
โ”‚   โ”œโ”€โ”€ support/                    # HR, Finance, CRM, Marketing, CS
โ”‚   โ”œโ”€โ”€ governance/                 # Audit, Policy, SoD
โ”‚   โ”œโ”€โ”€ analytics/                  # Dashboard, KPI, BI, Forecast
โ”‚   โ”œโ”€โ”€ holding/                    # Multi-Entity, Consolidation
โ”‚   โ””โ”€โ”€ global/                     # IAM, SecurityOps, ESG, CSR
โ”‚
โ””โ”€โ”€ shared/
    โ”œโ”€โ”€ components/
    โ”œโ”€โ”€ mixins/
    โ”œโ”€โ”€ extensions/
    โ””โ”€โ”€ styles/

โš™๏ธ RBACContext v2 (Multi-Level Aware)

import 'package:flutter/foundation.dart';

/// RBAC Context yang mendukung multi-level (L0-L4)
class RBACContext extends ChangeNotifier {
  final String userId;
  final String role;
  final String businessLevel; // L0-L4
  final String? entityId;     // Untuk Holding (L3+)
  final String? region;       // Untuk Global (L4)
  final List<String> permissions;
  final Map<String, dynamic>? metadata;

  RBACContext({
    required this.userId,
    required this.role,
    required this.businessLevel,
    this.entityId,
    this.region,
    required this.permissions,
    this.metadata,
  });

  /// Check apakah user punya permission tertentu
  bool can(String code) => permissions.contains(code);
  
  /// Check apakah business level minimal tertentu
  bool isAtLeast(String level) {
    const levels = ['L0', 'L1', 'L2', 'L3', 'L4'];
    return levels.indexOf(businessLevel) >= levels.indexOf(level);
  }
  
  /// Check apakah role cocok dengan salah satu dari list
  bool hasRole(List<String> roles) => roles.contains(role);
  
  /// Get permission metadata
  Map<String, dynamic>? getPermissionMeta(String code) {
    return metadata?['permissions']?[code];
  }
}
Usage:
// Di app level
ChangeNotifierProvider<RBACContext>(
  create: (_) => RBACContext(
    userId: user.id,
    role: user.role,
    businessLevel: merchant.businessLevel,
    permissions: user.permissions,
  ),
);

// Di widget
final rbac = context.watch<RBACContext>();
if (rbac.can('FIN_REPORT_VIEW')) {
  // Show finance report button
}

๐Ÿ” RoleGuard Widget

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

/// Widget guard untuk mengontrol akses berdasarkan role
class RoleGuard extends StatelessWidget {
  final List<String> allowedRoles;
  final Widget child;
  final Widget? fallback;

  const RoleGuard({
    Key? key,
    required this.allowedRoles,
    required this.child,
    this.fallback,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final rbac = context.watch<RBACContext>();
    
    if (!rbac.hasRole(allowedRoles)) {
      return fallback ?? 
        const Scaffold(
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Icon(Icons.lock, size: 64, color: Colors.grey),
                SizedBox(height: 16),
                Text('๐Ÿšซ Akses Ditolak'),
                Text('Anda tidak memiliki izin untuk halaman ini'),
              ],
            ),
          ),
        );
    }
    
    return child;
  }
}
Usage:
// Di routes
GoRoute(
  path: '/finance/reports',
  builder: (context, state) => RoleGuard(
    allowedRoles: ['FIN-MGR', 'ACC-MGR', 'AUD'],
    child: FinanceReportPage(),
  ),
);

๐Ÿ“‹ RBAC Policy Loader

import 'dart:convert';
import 'package:flutter/services.dart';

/// Loader untuk RBAC policy dari YAML atau API
class RBACPolicy {
  final Map<String, List<String>> rolePermissions;
  final Map<String, dynamic> sodRules;
  
  RBACPolicy({
    required this.rolePermissions,
    required this.sodRules,
  });
  
  /// Load dari local YAML
  static Future<RBACPolicy> fromAsset(String path) async {
    final yamlString = await rootBundle.loadString(path);
    final data = jsonDecode(yamlString); // Use yaml package in production
    
    return RBACPolicy(
      rolePermissions: Map<String, List<String>>.from(
        data['role_permissions'].map(
          (k, v) => MapEntry(k, List<String>.from(v)),
        ),
      ),
      sodRules: data['sod_rules'],
    );
  }
  
  /// Load dari backend API
  static Future<RBACPolicy> fromAPI(String endpoint) async {
    // Implementation dengan http client
    // final response = await http.get(Uri.parse(endpoint));
    // return RBACPolicy.fromJson(jsonDecode(response.body));
    throw UnimplementedError();
  }
  
  /// Check permission untuk role tertentu
  bool roleHasPermission(String role, String permission) {
    return rolePermissions[role]?.contains(permission) ?? false;
  }
  
  /// Check SoD violation
  bool checkSoDViolation(List<String> userRoles) {
    final violations = sodRules['violations'] as List;
    for (var rule in violations) {
      final conflictRoles = List<String>.from(rule['roles']);
      if (userRoles.toSet().containsAll(conflictRoles.toSet())) {
        return true; // Ada violation
      }
    }
    return false;
  }
}

๐Ÿ“Š Dynamic Menu Registry

File: assets/config/menu_registry.yaml
menus:
  - code: DASHBOARD
    label: Dashboard
    icon: home
    path: /dashboard
    roles: [L0_OWNER, L1_OWN-MGR, L2_OWN-MGR]
    level: L0
    
  - code: FIN_REPORT_PL
    label: Laporan Laba Rugi
    icon: chart-line
    path: /finance/report/pl
    roles: [FIN-MGR, OWN-MGR, AUD]
    level: L1
    
  - code: HR_EMPLOYEE_LIST
    label: Data Karyawan
    icon: users
    path: /hr/employee
    roles: [HR-MGR, HR-OPS, OWN-MGR]
    level: L1
    
  - code: INV_PRODUCT_LIST
    label: Daftar Produk
    icon: box
    path: /inventory/products
    roles: [INV-MGR, INV-OPS, L0_OWNER]
    level: L0
    
  - code: APPROVAL_QUEUE
    label: Antrian Persetujuan
    icon: check-circle
    path: /approval/queue
    roles: [APV-L1, APV-L2, OWN-MGR]
    level: L2
    
  - code: AUDIT_LOGS
    label: Audit Logs
    icon: file-text
    path: /audit/logs
    roles: [AUD, AUD-GRP, ADM-SYS]
    level: L2
Loader:
class MenuRegistry {
  static Future<List<MenuItem>> loadMenus(RBACContext rbac) async {
    final yamlString = await rootBundle.loadString(
      'assets/config/menu_registry.yaml'
    );
    
    final data = jsonDecode(yamlString);
    final menus = (data['menus'] as List)
        .map((m) => MenuItem.fromMap(m))
        .where((m) => m.roles.contains(rbac.role))
        .where((m) => _isLevelAllowed(m.level, rbac.businessLevel))
        .toList();
    
    return menus;
  }
  
  static bool _isLevelAllowed(String menuLevel, String userLevel) {
    const levels = ['L0', 'L1', 'L2', 'L3', 'L4'];
    return levels.indexOf(userLevel) >= levels.indexOf(menuLevel);
  }
}

class MenuItem {
  final String code;
  final String label;
  final String icon;
  final String path;
  final List<String> roles;
  final String level;
  
  MenuItem({
    required this.code,
    required this.label,
    required this.icon,
    required this.path,
    required this.roles,
    required this.level,
  });
  
  factory MenuItem.fromMap(Map<String, dynamic> map) {
    return MenuItem(
      code: map['code'],
      label: map['label'],
      icon: map['icon'],
      path: map['path'],
      roles: List<String>.from(map['roles']),
      level: map['level'],
    );
  }
}

๐Ÿง  SoD Enforcement (L2+)

class SoDValidator {
  final RBACPolicy policy;
  
  SoDValidator(this.policy);
  
  /// Validate SoD sebelum assign role
  ValidationResult validate(List<String> userRoles, String newRole) {
    final allRoles = [...userRoles, newRole];
    
    if (policy.checkSoDViolation(allRoles)) {
      return ValidationResult(
        isValid: false,
        message: 'SoD Violation: Role $newRole tidak bisa dikombinasi dengan role yang sudah ada',
        severity: 'critical',
      );
    }
    
    return ValidationResult(isValid: true);
  }
}

class ValidationResult {
  final bool isValid;
  final String? message;
  final String? severity;
  
  ValidationResult({
    required this.isValid,
    this.message,
    this.severity,
  });
}

๐Ÿข EntityScope (L3+ Holding)

class EntityScope extends InheritedWidget {
  final String entityId;
  final String entityName;
  
  const EntityScope({
    Key? key,
    required this.entityId,
    required this.entityName,
    required Widget child,
  }) : super(key: key, child: child);
  
  static EntityScope of(BuildContext context) {
    final scope = context.dependOnInheritedWidgetOfExactType<EntityScope>();
    assert(scope != null, 'EntityScope not found in context');
    return scope!;
  }
  
  @override
  bool updateShouldNotify(EntityScope oldWidget) {
    return entityId != oldWidget.entityId;
  }
}
Usage:
EntityScope(
  entityId: "ENT-01",
  entityName: "PT Musholla Store Jakarta",
  child: FinanceDashboard(),
);

// Di child widget
final entity = EntityScope.of(context);
print('Current entity: ${entity.entityName}');

๐Ÿ” Audit Trail Service

import 'package:http/http.dart' as http;
import 'dart:convert';

class AuditService {
  final String baseUrl;
  
  AuditService(this.baseUrl);
  
  /// Log user action untuk audit trail
  Future<void> logUserAction(
    String event, {
    Map<String, dynamic>? meta,
  }) async {
    try {
      await http.post(
        Uri.parse('$baseUrl/v2/audit/ui'),
        headers: {'Content-Type': 'application/json'},
        body: jsonEncode({
          'event': event,
          'timestamp': DateTime.now().toIso8601String(),
          'meta': meta,
        }),
      );
    } catch (e) {
      // Log error tapi jangan block UI
      print('Audit log failed: $e');
    }
  }
}
Usage:
// Di button handler
onPressed: () {
  auditService.logUserAction(
    'CREATE_INVOICE',
    meta: {
      'amount': 200000,
      'customer_id': 'CUST-001',
    },
  );
  createInvoice();
}

๐ŸŒ Global Compliance (L4)

IAM Integration

class IAMService {
  Future<AuthResult> loginWithOAuth2(String provider) async {
    // OAuth2 / OIDC implementation
    // Keycloak, Auth0, Azure AD
    throw UnimplementedError();
  }
  
  Future<void> refreshToken() async {
    // Token refresh dari /v2/core/auth/refresh
    throw UnimplementedError();
  }
  
  Future<List<String>> syncRoles() async {
    // Role sync dari /v2/core/roles
    throw UnimplementedError();
  }
}

Regional Policy

File: lib/app/localization/app_en.arb
{
  "gdpr_warning": "Your data is processed in accordance with GDPR policy.",
  "sox_warning": "Audit active: All actions are recorded (SOX-404).",
  "iso_compliance": "This system is ISO 27001 compliant."
}

๐Ÿงช Testing Blueprint

import 'package:flutter_test/flutter_test.dart';

void main() {
  group('RBACContext Tests', () {
    test('Role FIN-MGR can access finance reports', () {
      final ctx = RBACContext(
        userId: '123',
        role: 'FIN-MGR',
        businessLevel: 'L2',
        permissions: ['FIN_REPORT_PL', 'FIN_REPORT_VIEW'],
      );
      
      expect(ctx.can('FIN_REPORT_PL'), true);
      expect(ctx.can('HR_EMPLOYEE_VIEW'), false);
    });
    
    test('L2 business level includes L0 and L1 features', () {
      final ctx = RBACContext(
        userId: '123',
        role: 'OWN-MGR',
        businessLevel: 'L2',
        permissions: [],
      );
      
      expect(ctx.isAtLeast('L0'), true);
      expect(ctx.isAtLeast('L1'), true);
      expect(ctx.isAtLeast('L2'), true);
      expect(ctx.isAtLeast('L3'), false);
    });
  });
  
  group('SoD Validation Tests', () {
    test('Detect SoD violation', () {
      final policy = RBACPolicy(
        rolePermissions: {},
        sodRules: {
          'violations': [
            {'roles': ['FIN-AP', 'ACC-MGR']}
          ]
        },
      );
      
      final validator = SoDValidator(policy);
      final result = validator.validate(['FIN-AP'], 'ACC-MGR');
      
      expect(result.isValid, false);
    });
  });
}

๐Ÿ“Š Feature Matrix by Level

FeatureL0L1L2L3L4Implementation
Role-based UIโœ…โœ…โœ…โœ…โœ…RBACContext
SoD Enforcementโ€“โš ๏ธโœ…โœ…โœ…SoDValidator
Multi-Entityโ€“โ€“โ€“โœ…โœ…EntityScope
Dynamic Menuโš ๏ธโœ…โœ…โœ…โœ…MenuRegistry
Audit Trailโ€“โš ๏ธโœ…โœ…โœ…AuditService
IAM/SSOโ€“โ€“โ€“โœ…โœ…IAMService
Policy Syncโ€“โ€“โš ๏ธโœ…โœ…RBACPolicy.fromAPI
i18n Regionalโ€“โ€“โ€“โš ๏ธโœ….arb files
Offline Supportโœ…โœ…โœ…โ€“โ€“Hive/Drift

๐Ÿ“ฆ Dependencies

dependencies:
  flutter:
    sdk: flutter
  
  # State Management
  provider: ^6.1.1
  riverpod: ^2.4.9  # Alternative
  
  # Routing
  go_router: ^13.0.0
  
  # Data Models
  freezed_annotation: ^2.4.1
  json_annotation: ^4.8.1
  
  # Networking
  http: ^1.2.0
  dio: ^5.4.0
  
  # Local Storage
  hive: ^2.2.3
  hive_flutter: ^1.1.0
  drift: ^2.14.1
  
  # Localization
  flutter_localizations:
    sdk: flutter
  intl: ^0.19.0
  
  # Utils
  yaml: ^3.1.2

dev_dependencies:
  flutter_test:
    sdk: flutter
  
  # Code Generation
  build_runner: ^2.4.7
  freezed: ^2.4.6
  json_serializable: ^6.7.1
  
  # Testing
  mockito: ^5.4.4
  integration_test:
    sdk: flutter

โœ… Implementation Checklist

Phase 1: Core RBAC (Week 1-2)

  • Implement RBACContext provider
  • Implement RoleGuard widget
  • Update role_middleware.dart
  • Create rbac_policy.dart
  • Add unit tests

Phase 2: Dynamic Menu (Week 3)

  • Create menu_registry.yaml
  • Implement MenuRegistry loader
  • Update navigation with dynamic menu
  • Add menu visibility tests

Phase 3: Audit & Compliance (Week 4)

  • Implement AuditService
  • Add audit logging to critical actions
  • Setup SoD validation
  • Add compliance warnings

Phase 4: L3+ Features (Week 5-6)

  • Implement EntityScope
  • Add IAM integration
  • Setup regional policy
  • Multi-entity testing


๐ŸŽฏ Success Metrics

MetricTargetMeasurement
Code Coverage>80%Unit + Widget tests
SoD Compliance100%No violations in production
Audit Trail100%All critical actions logged
Menu Load Time< 100msDynamic menu rendering
Role Check Performance< 10msPermission validation
Build Size Impact< 500KBAdditional code overhead

๐Ÿ”ฅ Best Practices

  1. Always use RBACContext untuk permission checks
  2. Never hardcode role checks di UI code
  3. Use RoleGuard untuk semua protected routes
  4. Log critical actions dengan AuditService
  5. Validate SoD sebelum role assignment
  6. Test across all levels (L0-L4)
  7. Keep policy sync dengan backend
  8. Document permission codes di constants

Status: โœ… Production-Ready Blueprint
Last Updated: November 5, 2025
Version: 4.0.0