> ## Documentation Index
> Fetch the complete documentation index at: https://docs.quinnsambal.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Security

<style>
  {`
    [class*="max-w-none"][class*="table"] {
      display: block !important;
      overflow-x: auto !important;
      max-width: 100% !important;
      width: 100% !important;
      flex-grow: 0 !important;
    }
    [class*="max-w-none"][class*="table"] > table {
      width: 100% !important;
      max-width: 100% !important;
      table-layout: fixed !important;
    }
    .mermaid {
      max-width: 100% !important;
      overflow-x: auto !important;
    }
    article svg[role="img"] {
      max-width: 100% !important;
      height: auto !important;
    }
    article img, .prose img {
      max-width: 100% !important;
      height: auto !important;
    }
    img[src*="LOGO"], img[src*="logo"] {
      max-width: 120px !important;
      max-height: 40px !important;
      width: auto !important;
      height: auto !important;
      object-fit: contain !important;
    }
    `}
</style>

***

title: "Security & Compliance"
description: "Sistem keamanan komprehensif Quinn of Spicy: autentikasi, otorisasi RBAC, HTML sanitization, rate limiting, audit trail append-only, input validation, sensitive data masking, multi-tenant isolation, dan cross-tab sync."
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

# Security & Compliance

<img src="https://mintcdn.com/quinnofspicy/e4f_upKhVWcjsUmM/docs/mintlify/screenshots/core/settings.png?fit=max&auto=format&n=e4f_upKhVWcjsUmM&q=85&s=a1bbc0b83a788c1fb3fd4bf1d8a64537" alt="Security" width="1920" height="1080" data-path="docs/mintlify/screenshots/core/settings.png" />

Quinn of Spicy mengimplementasikan security system yang komprehensif untuk proteksi data dan compliance. `security.js` (224 baris) menyediakan utility functions untuk HTML sanitization, input validation, rate limiting, dan sensitive data masking. Audit trail bersifat append-only dengan state delta recording untuk forensic evidence.

Sistem ini melindungi dari berbagai attack vectors: XSS via HTML sanitization, directory traversal via file name sanitization, brute force via rate limiting, dan data leakage via sensitive data masking. Semua user input divalidasi sebelum diproses — email format, password strength, URL validity, phone number format, dan file type/size.

Cross-tab synchronization via BroadcastChannel memastikan permission changes dan security events propagate secara real-time ke semua browser tabs yang terbuka.

***

## Arsitektur Keamanan

```mermaid theme={null}
graph TD
    A[Security System] --> B[Input Validation]
    A --> C[HTML Sanitization]
    A --> D[Rate Limiting]
    A --> E[Audit Trail]
    A --> F[Data Masking]
    
    B --> G[Email Validation]
    B --> H[Password Strength]
    B --> I[URL Validation]
    B --> J[Phone Validation]
    B --> K[File Type/Size]
    
    C --> L[Remove Script Tags]
    C --> M[Remove Event Handlers]
    C --> N[Block Dangerous Protocols]
    
    D --> O[Sliding Window]
    D --> P[Per-IP Limiting]
    
    E --> Q[Append-Only Logs]
    E --> R[State Delta]
    E --> S[Correlation ID]
    
    F --> T[Password Masking]
    F --> U[API Key Masking]
    F --> V[Token Masking]
```

### Layer Pertahanan Keamanan

Sistem keamanan Quinn of Spicy dirancang dengan pendekatan **defense-in-depth** — beberapa lapis pertahanan yang saling melengkapi untuk memastikan bahwa jika satu layer gagal, layer lainnya tetap melindungi sistem dan data pengguna.

```mermaid theme={null}
graph LR
    subgraph "Layer 1: Edge Protection"
        A1[Rate Limiting] --> A2[Input Validation]
    end
    subgraph "Layer 2: Application Security"
        B1[HTML Sanitization] --> B2[RBAC & Authorization]
    end
    subgraph "Layer 3: Data Protection"
        C1[Sensitive Data Masking] --> C2[Multi-Tenant Isolation]
    end
    subgraph "Layer 4: Audit & Compliance"
        D1[Audit Trail] --> D2[Backup & Recovery]
    end
    A2 --> B1
    B2 --> C1
    C2 --> D1
```

***

## Autentikasi & Sesi

### Mekanisme Autentikasi

Quinn of Spicy menggunakan sistem autentikasi berbasis Supabase Auth dengan password hashing menggunakan bcrypt. Setiap user terdaftar dalam entitas `User` dengan field `email` sebagai identitas unik dan `role` (`admin` atau `user`) yang menentukan level akses global.

**Fitur Autentikasi**:

| Fitur | Deskripsi | Status |
| - | - | - |
| Password hashing | bcrypt dengan salt rounds untuk proteksi password | Aktif |
| Session management | JWT token dengan expiry otomatis | Aktif |
| Email verification | Verifikasi email wajib untuk akun baru | Aktif |
| Password reset | Token berbasis email untuk reset password | Aktif |
| 2FA / MFA | Multi-Factor Authentication | Segera hadir |
| Remember session | Persistensi sesi antar browser restart | Aktif |

### Manajemen Sesi

Setiap sesi pengguna dikelola melalui JWT token yang memiliki masa berlaku terbatas. Sistem secara otomatis melakukan refresh token sebelum expired untuk menjaga pengalaman pengguna yang mulus.

**Parameter Sesi**:

| Parameter | Nilai | Deskripsi |
| - | - | - |
| Token type | JWT | JSON Web Token dengan signature |
| Session expiry | 24 jam | Durasi sesi sebelum perlu refresh |
| Refresh token | 30 hari | Durasi refresh token |
| Concurrent sessions | Multiple | Mendukung sesi di beberapa perangkat |
| Cross-tab sync | BroadcastChannel | Sinkronisasi sesi antar tab |

### Tipe Admin & Hak Akses Global

Setiap pengguna memiliki `admin_type` dan `admin_tier` yang menentukan hak akses di level aplikasi (berbeda dengan role di level company):

| admin\_type | Deskripsi |
| - | - |
| `owner` | Full access ke seluruh aplikasi dan konfigurasi global |
| `basic` | Hanya dapat melakukan transaksi produk digital |

| admin\_tier | Deskripsi |
| - | - |
| `none` | Tidak memiliki hak admin untuk company management |
| `business` | Dapat mengelola company dengan fitur business |
| `advanced` | Akses lanjutan untuk company management |
| `enterprise` | Akses penuh untuk company management skala enterprise |

***

## Otorisasi & RBAC (Role-Based Access Control)

### Overview RBAC

Sistem RBAC Quinn of Spicy beroperasi pada dua level: **level company** melalui `CompanyMember` dan **level workspace** melalui `WorkspaceMember`. Setiap role memiliki set permissions yang granular, dan owner selalu memiliki semua permissions tanpa perlu konfigurasi tambahan.

### Role dalam Company

Entitas `CompanyMember` mendefinisikan 13 role yang tersedia dalam sebuah company:

| Role | Deskripsi | Akses Default |
| - | - | - |
| `owner` | Pemilik perusahaan | Full access — bypass semua permission check |
| `admin` | Administrator perusahaan | Akses hampir penuh, mengelola member dan settings |
| `supervisor` | Pengawas operasional | Monitoring dan approval |
| `store_admin` | Admin toko/outlet | Mengelola operasional toko |
| `stock_admin` | Admin stok/gudang | Mengelola inventory dan stock movements |
| `finance_admin` | Admin keuangan | Akses modul keuangan dan laporan finansial |
| `hr_admin` | Admin HR | Mengelola karyawan, absensi, dan payroll |
| `transaction_admin` | Admin transaksi | Mengelola transaksi penjualan dan pembayaran |
| `employee` | Karyawan biasa | Akses terbatas sesuai penugasan |
| `production_operator` | Operator produksi | Mengelola batch produksi (manufaktur) |
| `qc_inspector` | Inspector quality control | Approval dan reject quality check |
| `sales_marketing` | Sales & marketing | Akses fitur CRM dan distribusi |
| `partner_distributor` | Partner/distributor | Akses terbatas untuk mitra distribusi |

### Role dalam Workspace

Entitas `WorkspaceMember` mendefinisikan 4 role untuk workspace:

| Role | Deskripsi |
| - | - |
| `owner` | Pemilik workspace — dapat ada multiple owners |
| `admin` | Administrator workspace |
| `member` | Anggota biasa dengan akses task |
| `viewer` | Hanya dapat melihat, tidak dapat mengedit |

### Menu Access Profile

`MenuAccessProfile` memungkinkan owner company untuk membuat profil akses menu yang disesuaikan untuk setiap role. Setiap profil menentukan menu-menu mana yang dapat diakses oleh role tertentu.

| Field | Deskripsi |
| - | - |
| `profile_name` | Nama profil akses menu |
| `role` | Role yang menggunakan profil ini |
| `allowed_menus` | Daftar ID menu yang diizinkan |
| `dashboard_type` | Tipe dashboard yang ditampilkan untuk role ini |
| `is_default` | Apakah ini profil default untuk role |

**Dashboard Types**:

| Tipe | Deskripsi |
| - | - |
| `owner` | Dashboard lengkap untuk owner company |
| `admin` | Dashboard untuk administrator |
| `supervisor` | Dashboard monitoring untuk supervisor |
| `employee` | Dashboard standar untuk karyawan |
| `cashier` | Dashboard POS untuk kasir |
| `finance` | Dashboard keuangan |
| `hr` | Dashboard HR dan payroll |
| `inventory` | Dashboard inventori dan gudang |

### Detail Permissions (CompanyMember)

Setiap `CompanyMember` memiliki objek `permissions` yang berisi 35+ boolean flags untuk mengontrol akses granular:

| Permission | Default | Deskripsi |
| - | - | - |
| `can_view_dashboard` | `true` | Melihat dashboard |
| `can_view_tasks` | `true` | Melihat daftar tugas |
| `can_create_tasks` | `true` | Membuat tugas baru |
| `can_edit_tasks` | `true` | Mengedit tugas |
| `can_delete_tasks` | `false` | Menghapus tugas |
| `can_view_notes` | `true` | Melihat catatan |
| `can_create_notes` | `true` | Membuat catatan |
| `can_edit_notes` | `true` | Mengedit catatan |
| `can_delete_notes` | `false` | Menghapus catatan |
| `can_view_hr` | `false` | Melihat data HR |
| `can_edit_hr` | `false` | Mengedit data HR |
| `can_view_finance` | `false` | Melihat data keuangan |
| `can_edit_finance` | `false` | Mengedit data keuangan |
| `can_view_inventory` | `false` | Melihat data inventori |
| `can_edit_inventory` | `false` | Mengedit data inventori |
| `can_view_projects` | `false` | melihat proyek |
| `can_edit_projects` | `false` | Mengedit proyek |
| `can_view_pos` | `false` | Melihat modul POS |
| `can_use_pos` | `false` | Menggunakan POS untuk transaksi |
| `can_view_reports` | `false` | Melihat laporan |
| `can_manage_members` | `false` | Mengelola anggota company |
| `can_manage_roles` | `false` | Mengelola role dan permissions |
| `can_view_settings` | `false` | Melihat pengaturan |
| `can_edit_settings` | `false` | Mengedit pengaturan |
| `can_manage_cashier_shift` | `false` | Mengelola shift kasir |
| `can_approve_stock_opname` | `false` | Menyetujui stock opname |
| `can_count_stock_opname` | `false` | Menghitung stock opname |
| `can_transfer_inventory` | `false` | Transfer antar lokasi |
| `can_create_production_batch` | `false` | Membuat batch produksi |
| `can_release_production_qc` | `false` | Release batch dari QC |
| `can_view_hpp` | `false` | Melihat HPP (Harga Pokok Produksi) |
| `can_manage_channel_pricing` | `false` | Mengelola harga per channel |
| `can_view_distribution` | `false` | Melihat modul distribusi |
| `can_create_distribution_shipment` | `false` | Membuat shipment distribusi |
| `can_confirm_distribution_shipment` | `false` | Konfirmasi shipment distribusi |
| `can_view_b2b_invoices` | `false` | Melihat invoice B2B |
| `can_create_b2b_invoice` | `false` | Membuat invoice B2B |
| `can_verify_b2b_payment` | `false` | Verifikasi pembayaran B2B |

### Location-Based Access Control (RBAC-01)

Selain permission berbasis role, `CompanyMember` juga mendukung `assigned_locations` — daftar ID lokasi gudang/outlet yang diizinkan untuk member tersebut. Ini memastikan bahwa seorang stock admin hanya dapat mengakses lokasi yang secara eksplisit ditugaskan kepadanya.

### Owner Bypass

Owner company selalu memiliki semua permissions tanpa perlu konfigurasi. Logika permission check akan langsung bypass jika user adalah owner, memastikan bahwa owner tidak pernah terkunci dari fitur apapun.

### Segregation of Duties (SoD)

Sistem menerapkan 3 aturan segregation of duties untuk mencegah fraud:

1. **Pembuat transaksi tidak boleh sekaligus approver** — mencegah konflik kepentingan
2. **Stock admin yang melakukan stock opname tidak boleh sekaligus approve** — memastikan verifikasi independen
3. **Production operator yang membuat batch tidak boleh sekaligus release QC** — quality check harus independen

### Grace Period Membership

Ketika membership perusahaan expired, sistem memberikan grace period 3 hari dalam mode read-only (`is_readonly_mode: true`). Selama periode ini, pengguna hanya dapat membaca data tetapi tidak dapat membuat atau mengedit transaksi, memberi waktu untuk perpanjangan membership.

***

## Multi-Tenancy & Isolasi Data

### Company-Based Isolation

Setiap data dalam Quinn of Spicy terisolasi berdasarkan `company_id`. Row Level Security (RLS) dari Supabase memastikan bahwa query hanya mengembalikan data milik company yang sedang aktif (`active_company_id`).

**Mekanisme Isolasi**:

```mermaid theme={null}
flowchart TD
    A[User Login] --> B[Set active_company_id]
    B --> C[RLS Filter: company_id = active_company_id]
    C --> D[Query hanya return data company aktif]
    D --> E[User berpindah company?]
    E -->|Ya| F[Update active_company_id]
    F --> C
    E -->|Tidak| G[Lanjutkan sesi]
```

**RLS Policy Pattern**:

Semua entitas security-critical menggunakan pattern RLS yang sama:

```
$or:
  - company_id = user.active_company_id AND company_id != null
  - created_by_id = user.id
  - user_email = user.email
  - user_id = user.id
  - user.role = "admin"
```

### Company Switching

User dengan `admin_type: owner` atau `admin_tier` yang sesuai dapat mengelola multiple companies dan berpindah antar company melalui `active_company_id`. Setiap kali berpindah company, semua query akan otomatis ter-filter ke company yang baru dipilih.

***

## Invitation & Onboarding

### Company Invitation

`CompanyInvitation` mengelola proses undangan bergabung ke sebuah company. Undangan dikirim via email dan memiliki masa berlaku tertentu.

**Status Undangan**:

| Status | Deskripsi |
| - | - |
| `pending` | Undangan telah dikirim, menunggu respons |
| `accepted` | Undangan diterima, user bergabung sebagai member |
| `rejected` | Undangan ditolak oleh penerima |
| `expired` | Undangan kedaluwarsa sebelum direspons |
| `cancelled` | Undangan dibatalkan oleh pengirim |

**Alur Undangan**:

1. Admin/owner company mengirim undangan ke email calon member
2. Sistem membuat record `CompanyInvitation` dengan status `pending`
3. Penerima menerima email notifikasi berisi detail company dan role yang ditawarkan
4. Penerima dapat accept atau reject undangan
5. Jika diterima, sistem otomatis membuat record `CompanyMember` dengan role dan permissions sesuai undangan
6. Jika ditolak atau expired, status diupdate accordingly

***

## security.js Utility Functions

### 1. HTML Sanitization

`sanitizeHtml()` mencegah XSS attacks dengan menghapus elemen, atribut, dan protokol berbahaya:

**Blocked Elements**:

| Tag | Risk |
| - | - |
| `<script>` | JavaScript execution |
| `<iframe>` | Embedded content injection |
| `<object>` | Plugin-based attacks |
| `<embed>` | External resource injection |
| `<form>` | Form hijacking |

**Blocked Attributes**:

| Attribute | Risk |
| - | - |
| `onerror` | Event handler XSS |
| `onload` | Event handler XSS |
| `onclick` | Event handler XSS |
| `onmouseover` | Event handler XSS |
| `onfocus` | Event handler XSS |

**Blocked Protocols**:

| Protocol | Risk |
| - | - |
| `javascript:` | Inline script execution |
| `data:` | Data URI attacks |
| `vbscript:` | VBScript execution (legacy) |

**Example**:

```mermaid theme={null}
flowchart LR
    A[User Input<br/>&lt;script&gt;alert&#40;'xss'&#41;&lt;/script&gt;] --> B[sanitizeHtml]
    B --> C[Remove script tags]
    C --> D[Clean Output<br/>alert&#40;'xss'&#41;]
```

### 2. Input Validation

| Function | Validation Rules |
| - | - |
| `isValidEmail()` | RFC 5322 compliant email format |
| `isValidPassword()` | 8+ chars, uppercase, lowercase, number |
| `isValidUrl()` | Valid HTTP/HTTPS URL format |
| `isValidPhoneNumber()` | Indonesian format: +62/62/0 prefix, 10-13 digits |
| `isValidFileType()` | Whitelist: jpg, jpeg, png, gif, pdf, doc, docx |
| `isValidFileSize()` | Max size check (configurable, default 5MB) |
| `sanitizeFileName()` | Remove directory traversal (`../`), special chars |

**Password Rules**:

```mermaid theme={null}
flowchart TD
    A[Password Input] --> B{Length >= 8?}
    B -->|No| C[Reject: Too short]
    B -->|Yes| D{Has uppercase?}
    D -->|No| E[Reject: Need uppercase]
    D -->|Yes| F{Has lowercase?}
    F -->|No| G[Reject: Need lowercase]
    F -->|Yes| H{Has number?}
    H -->|No| I[Reject: Need number]
    H -->|Yes| J[Accept]
```

### 3. Rate Limiting

`RateLimiter` class menggunakan sliding window algorithm untuk mencegah abuse:

**Configuration**:

| Parameter | Default | Deskripsi |
| - | - | - |
| `maxRequests` | 100 | Maximum requests allowed |
| `windowMs` | 60000 | Time window in milliseconds (1 minute) |
| `keyGenerator` | IP address | Function to generate rate limit key |

**Algorithm**:

```mermaid theme={null}
flowchart TD
    A[Incoming Request] --> B[Get current timestamp]
    B --> C[Remove expired entries<br/>outside window]
    C --> D{Count requests<br/>in current window}
    D --> E{Count < maxRequests?}
    E -->|Yes| F[Allow request<br/>Add to window]
    E -->|No| G[Block request<br/>Return 429]
```

**Use Cases**:

* Login attempts (prevent brute force)
* API calls (prevent abuse)
* Form submissions (prevent spam)
* Password reset (prevent email bombing)

### 4. Sensitive Data Masking

`maskSensitiveData()` masks sensitive fields dalam logs dan exports untuk mencegah accidental exposure:

**Masked Fields**:

| Field | Mask Pattern | Example |
| - | - | - |
| `password` | `********` | `password123` -> `********` |
| `apiKey` | `sk_****...****` | `sk_live_1234567890` -> `sk_****...****` |
| `secret` | `****...****` | `my_secret_key` -> `****...****` |
| `token` | `****...****` | `eyJhbGciOiJIUzI1NiJ9...` -> `****...****` |

**Example**:

```mermaid theme={null}
flowchart LR
    A[Log Object<br/>password: secret123<br/>apiKey: sk_live_abc] --> B[maskSensitiveData]
    B --> C[Masked Output<br/>password: ********<br/>apiKey: sk_****...****]
```

### 5. Additional Utilities

| Function | Deskripsi |
| - | - |
| `sanitizeText()` | Strip semua HTML tags dari teks |
| `escapeHtml()` | Escape HTML entities (`<` -> `&lt;`) |
| `isSafeJSON()` | Check for circular references dalam objek JSON |
| `isValidPhoneNumber()` | Validasi format nomor telepon Indonesia |

***

## Audit Trail System

### AuditTrailViewer.jsx (388 lines)

Append-only audit log viewer dengan state delta recording untuk setiap perubahan data. Audit trail merupakan komponen kritis untuk forensic analysis dan compliance.

### Struktur Audit Log

Entitas `AuditLog` menyimpan setiap aksi yang dilakukan dalam sistem:

| Field | Tipe | Required | Deskripsi |
| - | - | - | - |
| `company_id` | String | Ya | ID perusahaan (multi-tenant scoping) |
| `user_id` | String | Ya | ID pengguna yang melakukan aksi |
| `user_email` | String | Tidak | Email pengguna actor |
| `action` | Enum | Ya | Jenis aksi yang dilakukan |
| `entity_type` | String | Ya | Tipe entitas yang diakses |
| `entity_id` | String | Tidak | ID entitas yang diakses |
| `old_value` | Object | Tidak | Nilai sebelum perubahan (untuk update) |
| `new_value` | Object | Tidak | Nilai setelah perubahan (untuk update) |
| `timestamp` | DateTime | Ya | Waktu aksi dilakukan |
| `ip_address` | String | Tidak | IP address dari mana aksi dilakukan |
| `device_info` | String | Tidak | User Agent atau informasi device |
| `description` | String | Tidak | Deskripsi detail aksi |
| `status` | Enum | Tidak | Status aksi: `success` atau `failed` |

### Action Types

Sistem audit mencatat berbagai jenis aksi yang dikategorikan per modul:

| Kategori | Actions |
| - | - |
| **CRUD Dasar** | `create`, `read`, `update`, `delete` |
| **Approval** | `approve`, `reject` |
| **Security** | `lock`, `unlock`, `unlock_request` |
| **Inventory** | `stock_in`, `stock_out`, `transfer_inventory`, `approve_stock_opname` |
| **Manufacturing** | `start_production_batch`, `verify_release_batch`, `approve_quality_check`, `reject_quality_check` |
| **Finance** | `record_invoice_payment`, `verify_online_payment`, `finalize_cashier_sale` |
| **CRM** | `create_pos_customer` |
| **RBAC** | `update_member_role`, `delete_company_member`, `create_company_invitation` |
| **Distribution** | `create_distribution_shipment`, `confirm_distribution_shipment` |

### State Delta Recording

Setiap kali data diubah, sistem menangkap state sebelum dan sesudah perubahan:

```mermaid theme={null}
flowchart TD
    A[User updates product price<br/>Rp 10.000 -> Rp 12.000] --> B[Audit System]
    B --> C[Capture old_value<br/>price: 10000]
    B --> D[Capture new_value<br/>price: 12000]
    B --> E[Store delta in AuditLog]
    E --> F[Forensic Evidence<br/>Who changed what when]
```

### Correlation ID

Untuk operasi yang melibatkan multiple entities, correlation ID digunakan untuk trace related operations:

```mermaid theme={null}
flowchart LR
    A[Production Order Created] --> B[correlation_id: abc-123]
    B --> C[Material Issued<br/>correlation_id: abc-123]
    C --> D[Batch Started<br/>correlation_id: abc-123]
    D --> E[QC Record Created<br/>correlation_id: abc-123]
    E --> F[Batch Released<br/>correlation_id: abc-123]
    
    Note over B,F: All operations traceable<br/>via correlation_id
```

***

## Cross-Tab Security Synchronization

Sistem menggunakan BroadcastChannel API untuk menyinkronkan security events antar tab browser yang terbuka secara bersamaan:

```mermaid theme={null}
sequenceDiagram
    participant A as Tab 1
    participant BC as BroadcastChannel
    participant B as Tab 2
    participant C as Tab 3

    A->>A: Admin revokes user permission
    A->>BC: Broadcast 'snishop_member_updates'
    
    BC-->>B: Receive broadcast
    B->>B: Invalidate permission cache
    B->>B: Re-fetch permissions
    B->>B: Update UI (hide restricted features)
    
    BC-->>C: Receive broadcast
    C->>C: Invalidate permission cache
    C->>C: Re-fetch permissions
    C->>C: Update UI
```

**Events**:

| Event | Source | Aksi |
| - | - | - |
| `snishop_member_updates` | BroadcastChannel | Invalidate permission cache |
| `memberPermissionsUpdated` | CustomEvent | Update UI immediately |
| `companyChanged` | BroadcastChannel | Refresh company context |

***

## Subscription & Membership Security

### Subscription Plans

Setiap user terdaftar memiliki `subscription_plan` yang menentukan fitur dan batas yang tersedia:

| Plan | Level | Fitur Utama |
| - | - | - |
| `free` | Dasar | Akses terbatas, tidak ada fitur company |
| `pro` | Profesional | Fitur lanjutan untuk individu |
| `business` | Bisnis | Akses company, POS, CRM dasar |
| `advanced` | Lanjutan | Semua fitur business + manufaktur, distribusi |
| `enterprise` | Enterprise | Full akses tanpa batas |

### Membership Duration & Expiry

| Field | Deskripsi |
| - | - |
| `membership_duration_type` | Tipe durasi: `monthly`, `yearly`, `custom`, `lifetime` |
| `membership_start_date` | Tanggal mulai membership |
| `membership_end_date` | Tanggal berakhir membership |
| `is_readonly_mode` | Mode read-only saat expired dalam grace period 3 hari |
| `trial_end` | Tanggal berakhir trial (jika ada) |

### Subscription Entity

Entitas `Subscription` mengelola langganan layanan secara detail:

| Field | Tipe | Required | Deskripsi |
| - | - | - | - |
| `user_id` | String | Ya | ID pengguna pemilik langganan |
| `company_id` | String | Tidak | ID perusahaan (null untuk personal) |
| `service_name` | String | Ya | Nama layanan/produk berlangganan |
| `cost` | Number | Ya | Biaya langganan per periode |
| `currency` | String | Tidak | Mata uang (default: IDR) |
| `billing_cycle` | Enum | Tidak | Siklus tagihan: `weekly`, `monthly`, `quarterly`, `yearly` |
| `next_due_date` | Date | Ya | Tanggal jatuh tempo berikutnya |
| `start_date` | Date | Tidak | Tanggal mulai langganan |
| `end_date` | Date | Tidak | Tanggal akhir langganan |
| `status` | Enum | Tidak | Status: `active`, `paused`, `cancelled` |
| `payment_method` | String | Tidak | Metode pembayaran |
| `auto_renew` | Boolean | Tidak | Perpanjangan otomatis (default: true) |

***

## Backup & Recovery Security

### Company Backup

Entitas `CompanyBackup` menyediakan mekanisme backup dan recovery dengan integritas terverifikasi:

| Field | Tipe | Deskripsi |
| - | - | - |
| `company_id` | String | ID perusahaan |
| `backup_type` | Enum | Jenis backup: `manual`, `auto_reminder`, `scheduled` |
| `backup_scope` | Enum | Cakupan: `full` atau `partial` |
| `backup_date` | DateTime | Tanggal backup dilakukan |
| `created_by_email` | String | Email user yang membuat backup |
| `status` | Enum | Status: `completed`, `failed`, `processing` |
| `checksum` | String | SHA-256 checksum untuk verifikasi integritas |
| `schema_version` | String | Versi schema untuk kompatibilitas restore |
| `retention_days` | Number | Durasi retensi backup (default: 30 hari) |
| `drive_file_id` | String | ID file di Google Drive (integrasi BKP-02) |
| `drive_integration_status` | Enum | Status Drive: `not_configured`, `active`, `failed`, `disabled` |

***

## Security Features by Layer

### 1. Authentication Layer

| Feature | Deskripsi |
| - | - |
| Password hashing | bcrypt with salt rounds |
| Session management | JWT with expiry |
| 2FA | Placeholder ("Segera hadir") |
| Password reset | Email-based token flow |
| Email verification | Required for new accounts |

### 2. Authorization Layer

| Feature | Deskripsi |
| - | - |
| RBAC | 13 role templates di company, 4 di workspace, 35+ permissions |
| SoD | 3 segregation of duties rules |
| Multi-tenant isolation | Automatic company\_id filtering via RLS |
| Owner bypass | Owner selalu memiliki semua permissions |
| Grace period | 3-day read-only after membership expiry |
| Location-based access | assigned\_locations untuk pembatasan lokasi |
| Menu access profiles | Profil akses menu yang dapat dikustomisasi per role |

### 3. Input Validation Layer

| Feature | Deskripsi |
| - | - |
| HTML sanitization | Remove XSS vectors |
| File validation | Type and size checks |
| Rate limiting | Sliding window algorithm |
| Phone validation | Indonesian format |
| Password strength | 8+ chars, mixed case, number |

### 4. Data Protection Layer

| Feature | Deskripsi |
| - | - |
| Sensitive data masking | Password, API key, token masking |
| Audit trail | Append-only with state delta |
| Correlation ID | Cross-operation tracing |
| Company scoping | Prevent cross-tenant leakage |
| Backup integrity | SHA-256 checksum verification |

***

## Compliance

### Data Protection

| Regulation | Status | Deskripsi |
| - | - | - |
| UU PDP (Indonesia) | Compliant | Personal Data Protection law |
| GDPR (EU) | Compliant | For EU data subjects |
| Data minimization | Implemented | Collect only necessary data |
| Right to be forgotten | Supported | Account deletion with confirmation |

### Industry Standards

| Standard | Deskripsi |
| - | - |
| ISO 27001 | Information Security Management |
| SOC 2 Type II | Service Organization Control |
| PCI DSS | Payment Card Industry Data Security |
| HACCP | Food Safety (for manufacturing data) |

***

## Security Best Practices

### Untuk Admin

1. **Review audit logs** setiap minggu untuk deteksi anomali
2. **Enforce SoD rules** — jangan bypass kecuali ada compensating control
3. **Use strong passwords** — minimal 8 karakter, mixed case, number
4. **Enable 2FA** ketika tersedia untuk role sensitif
5. **Monitor rate limiting** — adjust threshold jika ada legitimate high-volume users
6. **Rotate API keys** secara berkala
7. **Test backup restore** quarterly untuk pastikan recovery capability
8. **Review assigned\_locations** per member secara berkala
9. **Audit MenuAccessProfile** — pastikan role hanya memiliki akses menu yang diperlukan
10. **Monitor failed login attempts** melalui audit log dengan `status: failed`

### Untuk Developer

1. **Always sanitize** user input sebelum render atau store
2. **Use parameterized queries** — jangan concatenate SQL
3. **Validate on both client and server** — client validation untuk UX, server untuk security
4. **Mask sensitive data** di logs dan exports
5. **Use correlation IDs** untuk trace cross-module operations
6. **Test rate limiting** dengan load testing
7. **Keep dependencies updated** — run `npm audit` regularly
8. **Always include company\_id** di RLS policy untuk multi-tenant isolation
9. **Log device\_info dan ip\_address** di setiap audit entry untuk forensic readiness

***

## Integrasi Cross-Module

```mermaid theme={null}
graph LR
    A[Security System] --> B[Input Validation<br/>All forms]
    A --> C[HTML Sanitization<br/>Rich text editors]
    A --> D[Rate Limiting<br/>API endpoints]
    A --> E[Audit Trail<br/>All mutations]
    A --> F[Data Masking<br/>Logs & exports]
    A --> G[RBAC<br/>Permission checks]
    A --> H[Multi-Tenant<br/>Company isolation]
    
    B --> I[security.js]
    C --> I
    D --> I
    E --> J[AuditTrailViewer]
    F --> I
    G --> K[RoleManager]
    H --> L[CompanySwitcher]
```

***

## Entity Relationship Diagram

```mermaid theme={null}
erDiagram
    User ||--o{ CompanyMember : "memiliki role di"
    User ||--o{ WorkspaceMember : "anggota workspace"
    User ||--o{ Subscription : "memiliki langganan"
    User ||--o{ CompanyInvitation : "menerima undangan"
    User ||--o{ AuditLog : "melakukan aksi"
    User ||--o{ Company : "memiliki sebagai owner"

    Company ||--o{ CompanyMember : "memiliki anggota"
    Company ||--o{ CompanyInvitation : "mengirim undangan"
    Company ||--o{ AuditLog : "memiliki log audit"
    Company ||--o{ MenuAccessProfile : "memiliki profil akses"
    Company ||--o{ CompanyBackup : "memiliki backup"
    Company ||--o{ Subscription : "memiliki langganan"
    Company ||--o{ Workspace : "memiliki workspace"

    CompanyMember }o--|| MenuAccessProfile : "menggunakan profil"
    CompanyInvitation }o--o| CompanyMember : "diterima menjadi"

    Workspace ||--o{ WorkspaceMember : "memiliki anggota"

    User {
        string id PK
        string email UK "Email pengguna"
        string full_name "Nama lengkap"
        string role "admin | user"
        string subscription_plan "free | pro | business | advanced | enterprise"
        string admin_type "owner | basic"
        string admin_tier "none | business | advanced | enterprise"
        string active_company_id FK "Company yang sedang aktif"
        boolean is_readonly_mode "Mode read-only saat expired"
        date membership_start_date "Tanggal mulai membership"
        date membership_end_date "Tanggal akhir membership"
        string membership_duration_type "monthly | yearly | custom | lifetime"
    }

    Company {
        string id PK
        string name "Nama perusahaan"
        string owner_id FK "ID owner"
        string owner_email "Email owner"
        string owner_subscription_plan "free | pro | business | advanced | enterprise"
        string industry "retail | manufacturing | services | technology | food_beverage | healthcare | education | other"
    }

    CompanyMember {
        string id PK
        string company_id FK "ID perusahaan"
        string user_id FK "ID pengguna"
        string user_email "Email pengguna"
        string role "owner | admin | supervisor | store_admin | stock_admin | finance_admin | hr_admin | transaction_admin | employee | production_operator | qc_inspector | sales_marketing | partner_distributor"
        string status "active | inactive | pending"
        object permissions "35+ boolean permission flags"
        array assigned_locations "Daftar ID lokasi yang diizinkan"
    }

    CompanyInvitation {
        string id PK
        string company_id FK "ID perusahaan"
        string invited_email "Email penerima undangan"
        string invited_by "Email pengirim"
        string role "Role yang ditawarkan"
        string status "pending | accepted | rejected | expired | cancelled"
        datetime expires_at "Waktu kedaluwarsa"
        object permissions "Permissions yang akan diberikan"
    }

    AuditLog {
        string id PK
        string company_id FK "ID perusahaan"
        string user_id FK "ID actor"
        string action "create | read | update | delete | approve | reject | lock | unlock | unlock_request"
        string entity_type "Tipe entitas"
        string entity_id "ID entitas"
        object old_value "State sebelum perubahan"
        object new_value "State setelah perubahan"
        string status "success | failed"
        string ip_address "IP address actor"
        string device_info "Info device"
        datetime timestamp "Waktu aksi"
    }

    MenuAccessProfile {
        string id PK
        string company_id FK "ID perusahaan"
        string role "Role yang menggunakan profil"
        string profile_name "Nama profil"
        array allowed_menus "Daftar ID menu yang diizinkan"
        string dashboard_type "owner | admin | supervisor | employee | cashier | finance | hr | inventory"
        boolean is_default "Profil default untuk role"
    }

    Subscription {
        string id PK
        string user_id FK "ID pengguna"
        string company_id FK "ID perusahaan"
        string service_name "Nama layanan"
        number cost "Biaya per periode"
        string billing_cycle "weekly | monthly | quarterly | yearly"
        string status "active | paused | cancelled"
        date next_due_date "Jatuh tempo berikutnya"
        boolean auto_renew "Perpanjangan otomatis"
    }

    Workspace {
        string id PK
        string company_id FK "ID perusahaan"
        string name "Nama workspace"
        string owner_id FK "ID pemilik"
        boolean is_personal "Workspace pribadi"
    }

    WorkspaceMember {
        string id PK
        string workspace_id FK "ID workspace"
        string user_id FK "ID pengguna"
        string role "owner | admin | member | viewer"
        object permissions "Permission flags"
    }

    CompanyBackup {
        string id PK
        string company_id FK "ID perusahaan"
        string backup_type "manual | auto_reminder | scheduled"
        string backup_scope "full | partial"
        string status "completed | failed | processing"
        string checksum "SHA-256 checksum"
        string schema_version "Versi schema"
        number retention_days "Durasi retensi"
    }

    PricingPlan {
        string id PK
        string planKey UK "Kunci unik paket"
        string name "Nama paket"
        number price "Harga bulanan"
        number yearlyPrice "Harga tahunan"
        number company_slots "Slot company"
        boolean allow_company_features "Akses fitur company"
        object enabled_features "Fitur yang diaktifkan"
        boolean is_custom_plan "Paket custom"
    }
```

***

## Entity Schema Tables

### User

| Field | Type | Required | Description |
| - | - | - | - |
| `id` | UUID | Ya | Primary key |
| `email` | String | Ya | Email pengguna (unique) |
| `full_name` | String | Ya | Nama lengkap pengguna |
| `role` | Enum | Tidak | Role global: `admin`, `user` |
| `subscription_plan` | Enum | Tidak | Plan langganan: `free`, `pro`, `business`, `advanced`, `enterprise` (default: `free`) |
| `subscription_start` | DateTime | Tidak | Tanggal mulai langganan |
| `subscription_end` | DateTime | Tidak | Tanggal akhir langganan |
| `membership_duration_type` | Enum | Tidak | Durasi membership: `monthly`, `yearly`, `custom`, `lifetime` |
| `membership_start_date` | Date | Tidak | Tanggal mulai membership |
| `membership_end_date` | Date | Tidak | Tanggal akhir membership |
| `is_readonly_mode` | Boolean | Tidak | Mode read-only saat expired (default: `false`) |
| `trial_end` | DateTime | Tidak | Tanggal berakhir trial |
| `active_company_id` | String | Tidak | ID company yang sedang aktif |
| `company_slots_purchased` | Number | Tidak | Slot company tambahan (default: 0) |
| `ai_monthly_usage` | Number | Tidak | Penggunaan AI bulan ini (default: 0) |
| `ai_credits` | Number | Tidak | Kredit AI tersedia (default: 10) |
| `ai_addon_quota` | Number | Tidak | Kuota addon AI (default: 0) |
| `storage_used` | Number | Tidak | Storage terpakai (default: 0) |
| `balance` | Number | Tidak | Saldo deposit (default: 0) |
| `commission_balance` | Number | Tidak | Saldo komisi referral (default: 0) |
| `admin_commission_balance` | Number | Tidak | Saldo komisi admin basic (default: 0) |
| `total_earnings` | Number | Tidak | Total komisi akumulasi (default: 0) |
| `admin_type` | Enum | Tidak | Tipe admin: `owner`, `basic` |
| `admin_tier` | Enum | Tidak | Tier admin: `none`, `business`, `advanced`, `enterprise` (default: `none`) |
| `productivity_score` | Number | Tidak | Skor produktivitas (default: 0) |
| `current_streak` | Number | Tidak | Streak hari berturut-turut (default: 0) |
| `longest_streak` | Number | Tidak | Streak terpanjang (default: 0) |
| `user_level` | Number | Tidak | Level berdasarkan achievement points (default: 1) |
| `referred_by` | String | Tidak | ID referrer |
| `referral_code` | String | Tidak | Kode referral unik |
| `preferences` | Object | Tidak | Preferensi user (theme, color, wallpaper) |

### Company

| Field | Type | Required | Description |
| - | - | - | - |
| `id` | UUID | Ya | Primary key |
| `name` | String | Ya | Nama perusahaan |
| `owner_id` | String | Ya | ID owner perusahaan |
| `owner_email` | String | Ya | Email owner |
| `owner_subscription_plan` | Enum | Tidak | Plan owner: `free`, `pro`, `business`, `advanced`, `enterprise` |
| `description` | String | Tidak | Deskripsi perusahaan |
| `industry` | Enum | Tidak | Industri: `retail`, `manufacturing`, `services`, `technology`, `food_beverage`, `healthcare`, `education`, `other` |
| `address` | String | Tidak | Alamat perusahaan |
| `phone` | String | Tidak | Nomor telepon |
| `email` | String | Tidak | Email perusahaan |
| `website` | String | Tidak | Website |
| `logo_url` | String | Tidak | URL logo |
| `tax_id` | String | Tidak | NPWP perusahaan |
| `employee_count` | Number | Tidak | Jumlah karyawan (default: 0) |
| `business_type` | String | Tidak | Kategori bisnis dari onboarding |
| `active_modules` | String | Tidak | JSON string array modul aktif |
| `landing_page_config` | Object | Tidak | Konfigurasi landing page |
| `metadata` | Object | Tidak | Data tambahan / legacy |
| `settings` | Object | Tidak | Pengaturan company (working hours, tax, spoilage, dll) |

### CompanyMember

| Field | Type | Required | Description |
| - | - | - | - |
| `id` | UUID | Ya | Primary key |
| `company_id` | String | Ya | ID perusahaan |
| `user_id` | String | Tidak | ID pengguna |
| `user_email` | String | Ya | Email pengguna |
| `user_name` | String | Tidak | Nama pengguna |
| `role` | Enum | Tidak | Role: `owner`, `admin`, `supervisor`, `store_admin`, `stock_admin`, `finance_admin`, `hr_admin`, `transaction_admin`, `employee`, `production_operator`, `qc_inspector`, `sales_marketing`, `partner_distributor` (default: `employee`) |
| `employee_id` | String | Tidak | Link ke entitas Employee |
| `department` | String | Tidak | Departemen |
| `position` | String | Tidak | Jabatan |
| `status` | Enum | Tidak | Status: `active`, `inactive`, `pending` (default: `active`) |
| `joined_date` | Date | Tidak | Tanggal bergabung |
| `invited_by` | String | Tidak | Email yang mengundang |
| `permissions` | Object | Tidak | 35+ boolean permission flags |
| `assigned_locations` | Array | Tidak | Daftar ID lokasi gudang/outlet yang diizinkan |
| `working_hours` | Object | Tidak | Jam kerja (start, end) |
| `salary` | Number | Tidak | Gaji karyawan |
| `notes` | String | Tidak | Catatan tambahan |

### CompanyInvitation

| Field | Type | Required | Description |
| - | - | - | - |
| `id` | UUID | Ya | Primary key |
| `company_id` | String | Ya | ID perusahaan |
| `company_name` | String | Tidak | Nama perusahaan |
| `invited_email` | String | Ya | Email penerima undangan |
| `invited_by` | String | Ya | Email pengirim undangan |
| `invited_by_name` | String | Tidak | Nama pengirim |
| `role` | Enum | Tidak | Role yang ditawarkan: `owner`, `admin`, `supervisor`, `store_admin`, `stock_admin`, `finance_admin`, `hr_admin`, `transaction_admin`, `employee` (default: `employee`) |
| `department` | String | Tidak | Departemen |
| `position` | String | Tidak | Jabatan |
| `description` | String | Tidak | Pesan tambahan (max 1000 char) |
| `permissions` | Object | Tidak | Permissions yang akan diberikan |
| `message` | String | Tidak | Pesan pribadi |
| `status` | Enum | Tidak | Status: `pending`, `accepted`, `rejected`, `expired`, `cancelled` (default: `pending`) |
| `expires_at` | DateTime | Tidak | Waktu kedaluwarsa |
| `accepted_at` | DateTime | Tidak | Waktu diterima |
| `rejected_at` | DateTime | Tidak | Waktu ditolak |
| `cancelled_at` | DateTime | Tidak | Waktu dibatalkan |

### AuditLog

| Field | Type | Required | Description |
| - | - | - | - |
| `id` | UUID | Ya | Primary key |
| `company_id` | String | Ya | ID perusahaan |
| `user_id` | String | Ya | ID pengguna yang melakukan aksi |
| `user_email` | String | Tidak | Email actor |
| `action` | Enum | Ya | Aksi: `create`, `read`, `update`, `delete`, `approve`, `reject`, `lock`, `unlock`, `unlock_request` |
| `entity_type` | String | Ya | Tipe entitas yang diakses |
| `entity_id` | String | Tidak | ID entitas yang diakses |
| `old_value` | Object | Tidak | Nilai lama (state sebelum perubahan) |
| `new_value` | Object | Tidak | Nilai baru (state setelah perubahan) |
| `timestamp` | DateTime | Ya | Waktu aksi dilakukan |
| `ip_address` | String | Tidak | IP address actor |
| `device_info` | String | Tidak | User Agent atau info device |
| `description` | String | Tidak | Deskripsi detail aksi |
| `status` | Enum | Tidak | Status: `success`, `failed` (default: `success`) |

### MenuAccessProfile

| Field | Type | Required | Description |
| - | - | - | - |
| `id` | UUID | Ya | Primary key |
| `company_id` | String | Ya | ID perusahaan |
| `role` | Enum | Ya | Role: `owner`, `admin`, `supervisor`, `store_admin`, `stock_admin`, `finance_admin`, `hr_admin`, `transaction_admin`, `employee` |
| `profile_name` | String | Ya | Nama profil akses menu |
| `description` | String | Tidak | Deskripsi profil (max 1000 char) |
| `allowed_menus` | Array | Ya | Daftar ID menu yang diizinkan |
| `dashboard_type` | Enum | Tidak | Tipe dashboard: `owner`, `admin`, `supervisor`, `employee`, `cashier`, `finance`, `hr`, `inventory` (default: `employee`) |
| `is_default` | Boolean | Tidak | Profil default untuk role (default: `true`) |
| `created_by` | String | Tidak | Email owner yang membuat profil |

### Subscription

| Field | Type | Required | Description |
| - | - | - | - |
| `id` | UUID | Ya | Primary key |
| `user_id` | String | Ya | ID pengguna pemilik langganan |
| `company_id` | String | Tidak | ID perusahaan (null untuk personal) |
| `service_name` | String | Ya | Nama layanan/produk |
| `cost` | Number | Ya | Biaya per periode |
| `currency` | String | Tidak | Mata uang (default: `IDR`) |
| `billing_cycle` | Enum | Tidak | Siklus: `weekly`, `monthly`, `quarterly`, `yearly` (default: `monthly`) |
| `next_due_date` | Date | Ya | Tanggal jatuh tempo berikutnya |
| `start_date` | Date | Tidak | Tanggal mulai |
| `end_date` | Date | Tidak | Tanggal akhir |
| `status` | Enum | Tidak | Status: `active`, `paused`, `cancelled` (default: `active`) |
| `payment_method` | String | Tidak | Metode pembayaran |
| `auto_renew` | Boolean | Tidak | Perpanjangan otomatis (default: `true`) |
| `notes` | String | Tidak | Catatan |

### WorkspaceMember

| Field | Type | Required | Description |
| - | - | - | - |
| `id` | UUID | Ya | Primary key |
| `workspace_id` | String | Ya | ID workspace |
| `user_id` | String | Ya | ID pengguna |
| `role` | Enum | Tidak | Role: `owner`, `admin`, `member`, `viewer` (default: `member`) |
| `invited_by` | String | Tidak | Email pengguna yang mengundang |
| `joined_at` | DateTime | Tidak | Waktu bergabung |
| `description` | String | Tidak | Catatan tambahan (max 1000 char) |
| `permissions` | Object | Tidak | Permission flags: `can_create_tasks`, `can_edit_tasks`, `can_delete_tasks`, `can_invite_members`, `can_access_all_tasks` |

### CompanyBackup

| Field | Type | Required | Description |
| - | - | - | - |
| `id` | UUID | Ya | Primary key |
| `company_id` | String | Ya | ID perusahaan |
| `company_name` | String | Tidak | Nama perusahaan (snapshot) |
| `backup_type` | Enum | Tidak | Jenis: `manual`, `auto_reminder`, `scheduled` (default: `manual`) |
| `backup_scope` | Enum | Tidak | Cakupan: `full`, `partial` (default: `full`) |
| `backup_date` | DateTime | Ya | Tanggal backup |
| `created_by_email` | String | Ya | Email pembuat backup |
| `created_by_name` | String | Tidak | Nama pembuat backup |
| `data_summary` | Object | Tidak | Summary data yang di-backup |
| `file_url` | String | Tidak | URL file backup JSON |
| `file_size_mb` | Number | Tidak | Ukuran file dalam MB |
| `status` | Enum | Tidak | Status: `completed`, `failed`, `processing` (default: `completed`) |
| `checksum` | String | Tidak | SHA-256 checksum integritas |
| `schema_version` | String | Tidak | Versi schema backup |
| `backup_version` | String | Tidak | Platform version string |
| `retention_days` | Number | Tidak | Durasi retensi (default: 30) |
| `drive_file_id` | String | Tidak | ID file Google Drive |
| `drive_integration_status` | Enum | Tidak | Status Drive: `not_configured`, `active`, `failed`, `disabled` |

***

## Authentication & Session Lifecycle

```mermaid theme={null}
stateDiagram-v2
    [*] --> Unauthenticated: User membuka aplikasi
    
    Unauthenticated --> LoginAttempt: Masukkan email & password
    LoginAttempt --> RateLimitCheck: Validasi kredensial
    
    RateLimitCheck --> LoginFailed: Rate limit exceeded
    RateLimitCheck --> CredentialCheck: Within limit
    
    CredentialCheck --> LoginFailed: Kredensial salah
    CredentialCheck --> EmailVerification: Kredensial benar
    
    EmailVerification --> LoginFailed: Email belum verifikasi
    EmailVerification --> MembershipCheck: Email terverifikasi
    
    MembershipCheck --> ActiveSession: Membership aktif
    MembershipCheck --> ReadonlySession: Expired dalam grace period
    MembershipCheck --> LoginFailed: Expired melebihi grace period
    
    ReadonlySession --> ActiveSession: Perpanjang membership
    ReadonlySession --> Unauthenticated: Grace period habis
    
    ActiveSession --> TokenRefresh: Token hampir expired
    TokenRefresh --> ActiveSession: Refresh berhasil
    TokenRefresh --> Unauthenticated: Refresh gagal
    
    ActiveSession --> CompanySwitch: User berpindah company
    CompanySwitch --> ActiveSession: active_company_id updated
    
    ActiveSession --> PermissionUpdate: Admin ubah permission
    PermissionUpdate --> ActiveSession: Cache invalidated via BroadcastChannel
    
    ActiveSession --> Logout: User logout
    Logout --> Unauthenticated: Session destroyed
    
    LoginFailed --> Unauthenticated: Kembali ke login
    LoginFailed --> AccountLocked: Terlalu banyak gagal
    AccountLocked --> Unauthenticated: Setelah cooldown period
```

***

## Sequence Diagrams

### Login Flow

```mermaid theme={null}
sequenceDiagram
    participant U as User
    participant C as Client App
    participant RL as Rate Limiter
    participant S as Supabase Auth
    participant DB as Database
    participant A as Audit System

    U->>C: Masukkan email & password
    C->>C: isValidEmail() & isValidPassword()
    C->>RL: Check rate limit (per IP)
    
    alt Rate limit exceeded
        RL-->>C: Block request (429)
        C-->>U: Tampilkan "Terlalu banyak percobaan"
    else Within limit
        RL->>RL: Record attempt
        C->>S: signInWithPassword(email, password)
        S->>DB: Verify credentials
        
        alt Invalid credentials
            DB-->>S: Auth failed
            S-->>C: Auth error
            C->>A: Log failed attempt (status: failed)
            C-->>U: Tampilkan "Email atau password salah"
        else Valid credentials
            DB-->>S: User data + session
            S-->>C: JWT token + user object
            C->>A: Log successful login (action: read, status: success)
            
            C->>DB: Fetch CompanyMember data
            DB-->>C: Member roles & permissions
            
            C->>C: Check membership status
            alt Membership expired
                C->>C: Set is_readonly_mode = true
                C-->>U: Redirect dengan readonly warning
            else Membership active
                C-->>U: Redirect ke dashboard
            end
        end
    end
```

### Invitation & Member Onboarding Flow

```mermaid theme={null}
sequenceDiagram
    participant O as Owner/Admin
    participant C as Client App
    participant DB as Database
    participant E as Email Service
    participant I as Invitee
    participant A as Audit System

    O->>C: Buat undangan (email, role, permissions)
    C->>C: isValidEmail() validation
    C->>DB: Insert CompanyInvitation (status: pending)
    DB-->>C: Invitation record created
    C->>A: Log create_company_invitation
    C->>E: Send invitation email
    E-->>I: Email undangan diterima
    
    I->>C: Klik link undangan
    C->>DB: Fetch invitation by token
    DB-->>C: Invitation details
    
    alt Invitation expired
        C-->>I: Tampilkan "Undangan kedaluwarsa"
    else Invitation valid
        C-->>I: Tampilkan detail company & role
        
        alt Accept
            I->>C: Accept invitation
            C->>DB: Create CompanyMember (role, permissions)
            C->>DB: Update invitation (status: accepted, accepted_at)
            C->>A: Log accept invitation
            C->>C: Broadcast snishop_member_updates
            C-->>I: Redirect ke company dashboard
        else Reject
            I->>C: Reject invitation
            C->>DB: Update invitation (status: rejected, rejected_at)
            C-->>I: Konfirmasi penolakan
        end
    end
```

### Session Management & Cross-Tab Sync

```mermaid theme={null}
sequenceDiagram
    participant T1 as Tab 1 (Admin)
    participant BC as BroadcastChannel
    participant T2 as Tab 2 (User)
    participant T3 as Tab 3 (User)
    participant DB as Database
    participant Cache as Permission Cache

    Note over T1,T3: Admin di Tab 1 mengubah permission member
    
    T1->>DB: Update CompanyMember permissions
    DB-->>T1: Update confirmed
    T1->>BC: Broadcast 'snishop_member_updates'
    
    BC-->>T2: Receive event
    T2->>Cache: Invalidate permission cache
    T2->>DB: Re-fetch permissions
    DB-->>T2: Updated permissions
    T2->>T2: Dispatch memberPermissionsUpdated event
    T2->>T2: Update UI (hide/show features)
    
    BC-->>T3: Receive event
    T3->>Cache: Invalidate permission cache
    T3->>DB: Re-fetch permissions
    DB-->>T3: Updated permissions
    T3->>T3: Dispatch memberPermissionsUpdated event
    T3->>T3: Update UI (hide/show features)
```

### Password Reset Flow

```mermaid theme={null}
sequenceDiagram
    participant U as User
    participant C as Client App
    participant RL as Rate Limiter
    participant S as Supabase Auth
    participant E as Email Service
    participant A as Audit System

    U->>C: Request password reset (email)
    C->>C: isValidEmail() validation
    C->>RL: Check rate limit (prevent email bombing)
    
    alt Rate limit exceeded
        RL-->>C: Block request
        C-->>U: "Terlalu banyak percobaan, coba lagi nanti"
    else Within limit
        RL->>RL: Record attempt
        C->>S: resetPasswordForEmail(email)
        S->>E: Send reset token email
        E-->>U: Email berisi link reset diterima
        C->>A: Log password reset request
        
        U->>C: Klik link reset, masukkan password baru
        C->>C: isValidPassword() validation (8+ chars, uppercase, lowercase, number)
        C->>S: Update password with token
        S-->>C: Password updated
        C->>A: Log password change (action: update, entity: User)
        C-->>U: "Password berhasil diubah, silakan login ulang"
    end
```

***

## Enum Reference Tables

### User.role

| Value | Deskripsi |
| - | - |
| `admin` | Administrator global dengan akses penuh ke seluruh aplikasi |
| `user` | Pengguna biasa dengan akses sesuai subscription plan |

### User.subscription\_plan

| Value | Deskripsi |
| - | - |
| `free` | Plan gratis dengan fitur terbatas |
| `pro` | Plan profesional untuk individu |
| `business` | Plan bisnis dengan akses company dan fitur POS/CRM |
| `advanced` | Plan lanjutan dengan manufaktur dan distribusi |
| `enterprise` | Plan enterprise tanpa batas |

### User.admin\_type

| Value | Deskripsi |
| - | - |
| `owner` | Full access ke seluruh aplikasi dan konfigurasi global |
| `basic` | Hanya transaksi produk digital |

### User.admin\_tier

| Value | Deskripsi |
| - | - |
| `none` | Tidak ada hak admin untuk company management |
| `business` | Admin untuk company management level business |
| `advanced` | Admin untuk company management level advanced |
| `enterprise` | Admin untuk company management level enterprise |

### User.membership\_duration\_type

| Value | Deskripsi |
| - | - |
| `monthly` | Membership bulanan |
| `yearly` | Membership tahunan |
| `custom` | Durasi kustom |
| `lifetime` | Membership seumur hidup |

### CompanyMember.role

| Value | Deskripsi |
| - | - |
| `owner` | Pemilik perusahaan — full access, bypass semua permission |
| `admin` | Administrator perusahaan — akses hampir penuh |
| `supervisor` | Pengawas operasional — monitoring dan approval |
| `store_admin` | Admin toko — mengelola operasional outlet |
| `stock_admin` | Admin stok — mengelola inventory dan gudang |
| `finance_admin` | Admin keuangan — akses modul finansial |
| `hr_admin` | Admin HR — mengelola karyawan dan payroll |
| `transaction_admin` | Admin transaksi — mengelola penjualan |
| `employee` | Karyawan — akses terbatas |
| `production_operator` | Operator produksi — batch produksi |
| `qc_inspector` | Inspector QC — quality control |
| `sales_marketing` | Sales & marketing — CRM dan distribusi |
| `partner_distributor` | Partner/distributor — akses mitra |

### CompanyMember.status

| Value | Deskripsi |
| - | - |
| `active` | Member aktif dengan akses penuh sesuai permissions |
| `inactive` | Member nonaktif, tidak dapat mengakses company |
| `pending` | Member menunggu aktivasi atau verifikasi |

### CompanyInvitation.status

| Value | Deskripsi |
| - | - |
| `pending` | Undangan dikirim, menunggu respons |
| `accepted` | Undangan diterima |
| `rejected` | Undangan ditolak |
| `expired` | Undangan kedaluwarsa |
| `cancelled` | Undangan dibatalkan oleh pengirim |

### AuditLog.action

| Value | Deskripsi |
| - | - |
| `create` | Membuat entitas baru |
| `read` | Membaca/melihat entitas |
| `update` | Mengubah data entitas |
| `delete` | Menghapus entitas |
| `approve` | Menyetujui permintaan/transaksi |
| `reject` | Menolak permintaan/transaksi |
| `lock` | Mengunci entitas (tidak dapat diubah) |
| `unlock` | Membuka kunci entitas |
| `unlock_request` | Permintaan untuk membuka kunci |

### AuditLog.status

| Value | Deskripsi |
| - | - |
| `success` | Aksi berhasil dilakukan |
| `failed` | Aksi gagal (misal: auth failure) |

### Company.industry

| Value | Deskripsi |
| - | - |
| `retail` | Ritel dan perdagangan |
| `manufacturing` | Manufaktur dan produksi |
| `services` | Jasa dan layanan |
| `technology` | Teknologi dan perangkat lunak |
| `food_beverage` | Makanan dan minuman |
| `healthcare` | Kesehatan |
| `education` | Pendidikan |
| `other` | Lainnya |

### MenuAccessProfile.dashboard\_type

| Value | Deskripsi |
| - | - |
| `owner` | Dashboard lengkap untuk owner |
| `admin` | Dashboard untuk administrator |
| `supervisor` | Dashboard monitoring |
| `employee` | Dashboard standar karyawan |
| `cashier` | Dashboard POS kasir |
| `finance` | Dashboard keuangan |
| `hr` | Dashboard HR dan payroll |
| `inventory` | Dashboard inventori |

### Subscription.billing\_cycle

| Value | Deskripsi |
| - | - |
| `weekly` | Tagihan mingguan |
| `monthly` | Tagihan bulanan |
| `quarterly` | Tagihan per kuartal (3 bulan) |
| `yearly` | Tagihan tahunan |

### Subscription.status

| Value | Deskripsi |
| - | - |
| `active` | Langganan aktif |
| `paused` | Langganan dijeda sementara |
| `cancelled` | Langganan dibatalkan |

### WorkspaceMember.role

| Value | Deskripsi |
| - | - |
| `owner` | Pemilik workspace (bisa multiple) |
| `admin` | Administrator workspace |
| `member` | Anggota dengan akses task |
| `viewer` | Hanya dapat melihat |

### CompanyBackup.backup\_type

| Value | Deskripsi |
| - | - |
| `manual` | Backup dibuat manual oleh user |
| `auto_reminder` | Backup via auto-reminder |
| `scheduled` | Backup terjadwal otomatis |

### CompanyBackup.backup\_scope

| Value | Deskripsi |
| - | - |
| `full` | Backup lengkap seluruh data |
| `partial` | Backup sebagian data |

### CompanyBackup.status

| Value | Deskripsi |
| - | - |
| `completed` | Backup berhasil selesai |
| `failed` | Backup gagal |
| `processing` | Backup sedang diproses |

### CompanyBackup.drive\_integration\_status

| Value | Deskripsi |
| - | - |
| `not_configured` | Google Drive belum dikonfigurasi |
| `active` | Integrasi Drive aktif |
| `failed` | Integrasi Drive gagal |
| `disabled` | Integrasi Drive dinonaktifkan |

***

## RBAC Permission Matrix

Tabel berikut memetakan permission default per role dalam company. Owner selalu bypass semua permission check.

| Permission | owner | admin | supervisor | store\_admin | stock\_admin | finance\_admin | hr\_admin | transaction\_admin | employee |
| - | - | - | - | - | - | - | - | - | - |
| `can_view_dashboard` | Y | Y | Y | Y | Y | Y | Y | Y | Y |
| `can_view_tasks` | Y | Y | Y | Y | Y | Y | Y | Y | Y |
| `can_create_tasks` | Y | Y | Y | Y | Y | Y | Y | Y | Y |
| `can_edit_tasks` | Y | Y | Y | Y | Y | Y | Y | Y | Y |
| `can_delete_tasks` | Y | Y | N | N | N | N | N | N | N |
| `can_view_notes` | Y | Y | Y | Y | Y | Y | Y | Y | Y |
| `can_create_notes` | Y | Y | Y | Y | Y | Y | Y | Y | Y |
| `can_edit_notes` | Y | Y | Y | Y | Y | Y | Y | Y | Y |
| `can_delete_notes` | Y | Y | N | N | N | N | N | N | N |
| `can_view_hr` | Y | Y | Y | N | N | Y | Y | N | N |
| `can_edit_hr` | Y | Y | N | N | N | Y | Y | N | N |
| `can_view_finance` | Y | Y | Y | N | N | Y | N | Y | N |
| `can_edit_finance` | Y | Y | N | N | N | Y | N | Y | N |
| `can_view_inventory` | Y | Y | Y | Y | Y | N | N | N | N |
| `can_edit_inventory` | Y | Y | N | Y | Y | N | N | N | N |
| `can_view_projects` | Y | Y | Y | N | N | N | N | N | N |
| `can_edit_projects` | Y | Y | Y | N | N | N | N | N | N |
| `can_view_pos` | Y | Y | Y | Y | N | Y | N | Y | N |
| `can_use_pos` | Y | Y | Y | Y | N | N | N | Y | N |
| `can_view_reports` | Y | Y | Y | Y | Y | Y | Y | Y | N |
| `can_manage_members` | Y | Y | N | N | N | N | N | N | N |
| `can_manage_roles` | Y | N | N | N | N | N | N | N | N |
| `can_view_settings` | Y | Y | N | N | N | N | N | N | N |
| `can_edit_settings` | Y | Y | N | N | N | N | N | N | N |
| `can_manage_cashier_shift` | Y | Y | Y | Y | N | N | N | Y | N |
| `can_approve_stock_opname` | Y | Y | Y | N | Y | N | N | N | N |
| `can_count_stock_opname` | Y | Y | Y | Y | Y | N | N | N | N |
| `can_transfer_inventory` | Y | Y | N | Y | Y | N | N | N | N |
| `can_create_production_batch` | Y | Y | N | N | N | N | N | N | N |
| `can_release_production_qc` | Y | Y | N | N | N | N | N | N | N |
| `can_view_hpp` | Y | Y | N | N | N | Y | N | N | N |
| `can_manage_channel_pricing` | Y | Y | N | N | N | N | N | N | N |
| `can_view_distribution` | Y | Y | Y | N | N | N | N | N | N |
| `can_create_distribution_shipment` | Y | Y | N | N | N | N | N | N | N |
| `can_confirm_distribution_shipment` | Y | Y | Y | N | N | N | N | N | N |
| `can_view_b2b_invoices` | Y | Y | Y | N | N | Y | N | Y | N |
| `can_create_b2b_invoice` | Y | Y | N | N | N | Y | N | Y | N |
| `can_verify_b2b_payment` | Y | Y | N | N | N | Y | N | N | N |

<Callout type="info">
  **Catatan**: Tabel di atas menunjukkan permission *default* untuk setiap role. Owner company dapat mengkustomisasi permissions per member secara individual melalui `CompanyMember.permissions`. Role `production_operator`, `qc_inspector`, `sales_marketing`, dan `partner_distributor` memiliki permission khusus yang tidak tercakup dalam tabel umum ini.
</Callout>

***

## Tips

* HTML sanitization adalah lini pertahanan pertama terhadap XSS — selalu sanitize user input sebelum render
* Rate limiting menggunakan sliding window yang lebih fair daripada fixed window — adjust threshold sesuai use case
* Audit trail bersifat append-only — tidak bisa dihapus atau diubah, memastikan forensic integrity
* Correlation ID sangat berguna untuk debugging cross-module operations — selalu include saat create related entities
* Sensitive data masking mencegah accidental exposure di logs — critical untuk compliance
* Cross-tab sync via BroadcastChannel memastikan permission changes propagate instantly — user tidak perlu refresh manual
* SoD rules adalah anti-fraud control — document rationale jika perlu bypass untuk business reason
* Grace period 3 hari memberi buffer untuk membership renewal sebelum user terblokir total
* `assigned_locations` pada CompanyMember penting untuk multi-outlet business — pastikan setiap member hanya ditugaskan ke lokasi yang sesuai
* SHA-256 checksum pada CompanyBackup memverifikasi integritas file backup — selalu verifikasi checksum sebelum restore
* `device_info` dan `ip_address` di AuditLog penting untuk forensic investigation — pastikan field ini selalu terisi
* MenuAccessProfile memungkinkan kustomisasi dashboard per role tanpa perlu mengubah code — gunakan fitur ini untuk tailoring akses


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.