# Flowchart Fitur Expired Password — Aplikasi E-Memo (OPIS)

> **Versi:** 1.0  
> **Tanggal:** 2026-07-29  
> **Berlaku untuk:** E-Memo (OPIS)

---

## 1. Ringkasan Fitur

Fitur **Expired Password** memastikan user mengganti password secara berkala (setiap **60 hari**). Sistem terdiri dari 4 lapisan utama:

| Lapisan | File | Deskripsi |
|---|---|---|
| **Model** | `app/User.php` | Method `isPasswordExpired()` — cek apakah password sudah >60 hari |
| **Middleware 1** | `CheckPasswordExpiry.php` | Blokir akses & arahkan ke halaman ganti password jika expired |
| **Middleware 2** | `CheckForcePasswordChange.php` | Paksa ganti password jika admin set flag `force_password_change` |
| **Scheduler** | `NotifyPasswordExpiry.php` | Kirim email + push notification H-7 dan H-1 sebelum expired |
| **Frontend** | `PasswordExpiryWarningModal.vue` | Modal peringatan visual di halaman user |

---

## 2. Logika Utama: `isPasswordExpired()`

```php
// app/User.php
public function isPasswordExpired(): bool
{
    $lastChanged = $this->password_changed_at ?? $this->created_at;
    return Carbon::now()->diffInDays($lastChanged) >= 60;
}
```

- Jika `password_changed_at` **NULL** → fallback ke `created_at`
- Jika selisih hari ≥ 60 → **password dinyatakan expired**

---

## 3. Flow Chart

### 3.1 Flow Saat Login

```mermaid
flowchart TD
    A([User Buka Halaman Login]) --> B[Input Email & Password]
    B --> C{Kredensial Valid?}
    C -- Tidak --> D[/Redirect ke Login\nError: 'Wrong email or password'/]
    C -- Ya --> E{isPasswordExpired?}
    E -- Ya --> F[Auth::logout]
    F --> G[/Redirect ke Login\nError: 'Akun dikunci, password kadaluarsa'\nHubungi administrator/]
    E -- Tidak --> H{role user?}
    H -- role = 2 Super --> I[/Redirect → superadmin.dashboard/]
    H -- role = 1 Admin --> J[/Redirect → admin.dashboard/]
    H -- role = 0 User --> K[/Redirect → user.dashboard/]
```

**File:** `app/Http/Controllers/Auth/LoginController.php` — method `login()`

---

### 3.2 Flow Middleware `CheckPasswordExpiry` (Setiap Request Terproteksi)

```mermaid
flowchart TD
    A([Incoming HTTP Request]) --> B{User sudah login?}
    B -- Tidak --> C[Lanjut ke middleware berikutnya]
    B -- Ya --> D{Route adalah\npassword.change /\npassword.change.save /\nlogout?}
    D -- Ya --> C
    D -- Tidak --> E{isPasswordExpired?}
    E -- Ya --> F[Auth::logout\nInvalidate Session\nRegenerate CSRF Token]
    F --> G[/Redirect → /login\nError: 'Akun dikunci, password kadaluarsa'\nHubungi administrator/]
    E -- Tidak --> H{Hitung sisa hari\ndaysRemaining = 60 - daysUsed}
    H --> I{daysRemaining ≤ 7\nDAN > 0?}
    I -- Ya --> J[Share ke Inertia:\npasswordExpiryWarning\ndays_remaining, change_url]
    J --> K[Lanjut ke Controller]
    I -- Tidak --> K
```

**File:** `app/Http/Middleware/CheckPasswordExpiry.php`

> **Catatan:** Middleware ini diterapkan pada semua route group: `admin`, `superadmin`, dan `user` (lihat `routes/web.php`).

---

### 3.3 Flow Middleware `CheckForcePasswordChange`

```mermaid
flowchart TD
    A([Incoming Request]) --> B{User sudah login?}
    B -- Tidak --> C[Lanjut ke middleware berikutnya]
    B -- Ya --> D{Route adalah\npassword.change /\npassword.change.save /\nlogout?}
    D -- Ya --> C
    D -- Tidak --> E{user.force_password_change\n= true?}
    E -- Tidak --> C
    E -- Ya --> F{Request expects JSON?}
    F -- Ya --> G[/Response 403 JSON:\nmessage + redirect URL/]
    F -- Tidak --> H[/Redirect → /password/change\nInfo: 'Admin telah set password sementara'/]
```

**File:** `app/Http/Middleware/CheckForcePasswordChange.php`

> Middleware ini dijalankan **setelah** `password.expiry`, sehingga urutan prioritas adalah: Expired → Force Change → Akses Normal.

---

### 3.4 Flow Ganti Password

```mermaid
flowchart TD
    A([User buka /password/change]) --> B[Form: current_password, password, password_confirmation]
    B --> C{Validasi form}
    C -- Gagal --> D[/Kembali ke form dengan error validasi/]
    C -- Lulus --> E{current_password\ncocok dengan hash?}
    E -- Tidak --> F[/Error: 'Password saat ini tidak sesuai'/]
    E -- Ya --> G{password baru\n=== password lama?}
    G -- Ya --> H[/Error: 'Password baru tidak boleh sama'/]
    G -- Tidak --> I[Update user:\npassword = Hash::make baru\npassword_changed_at = now\nforce_password_change = false]
    I --> J{Tentukan dashboard\nberdasarkan role}
    J -- role 2 Super --> K[/Redirect → superadmin.dashboard\nSuccess: 'Password changed!'/]
    J -- role 1 Admin --> L[/Redirect → admin.dashboard\nSuccess: 'Password changed!'/]
    J -- role 0 User --> M[/Redirect → user.dashboard\nSuccess: 'Password changed!'/]
```

**File:** `app/Http/Controllers/User/SettingController.php` — method `ChangePassword()`

**Aturan validasi password baru:**
- Minimal 8 karakter
- Harus mengandung minimal 1 huruf besar
- Harus mengandung minimal 1 angka
- Harus mengandung minimal 1 karakter spesial

---

### 3.5 Flow Modal Peringatan Frontend (`PasswordExpiryWarningModal.vue`)

```mermaid
flowchart TD
    A([Halaman dimuat / Inertia navigate]) --> B{passwordExpiredNow\ndi-share dari server?}
    B -- Ya --> C[/Tampilkan Modal EXPIRED\nWarna merah tua, tidak bisa ditutup\nTombol: 'Ganti Password Sekarang'/]
    B -- Tidak --> D{passwordExpiryWarning\ndi-share dari server?}
    D -- Tidak --> E[Modal tidak tampil]
    D -- Ya --> F{sessionStorage sudah\nada key pwExpWarn_TANGGAL?}
    F -- Ya sudah dismiss hari ini --> E
    F -- Belum --> G{days_remaining ≤ 1?}
    G -- Ya URGENT --> H[/Tampilkan Modal URGENT\nWarna merah, boleh ditutup\nCountdown + pesan 'Segera ganti'/]
    G -- Tidak WARNING --> I[/Tampilkan Modal WARNING\nWarna kuning, boleh ditutup\nCountdown hari tersisa/]
    H --> J{User klik 'Ingatkan Nanti'?}
    I --> J
    J -- Ya --> K[Simpan key ke sessionStorage\nModal hilang hingga besok]
    J -- Tidak\nklik Ganti Password --> L[/Redirect ke /password/change/]
```

**File:** `resources/js/components/PasswordExpiryWarningModal.vue`

| Kondisi | Warna Header | Bisa Ditutup | Pesan |
|---|---|---|---|
| `passwordExpiredNow` | Merah tua (`#842029`) | ❌ Tidak | Password sudah kadaluarsa, wajib ganti |
| `days_remaining ≤ 1` | Merah (`#f8d7da`) | ✅ Ya | Segera ganti, akses akan diblokir |
| `days_remaining 2–7` | Kuning (`#fff3cd`) | ✅ Ya | Password akan kadaluarsa dalam N hari |

---

### 3.6 Flow Notifikasi Otomatis (Scheduler)

```mermaid
flowchart TD
    A([Scheduler jalan setiap hari jam 08:00]) --> B[Jalankan: php artisan password:notify-expiry]
    B --> C{Loop untuk H-7 dan H-1}
    C --> D["Query users WHERE\nDATE(password_changed_at) = today - 60 - daysLeft"]
    D --> E{Ada user yang cocok?}
    E -- Tidak --> F[Log: tidak ada user, lewati]
    E -- Ya --> G[Loop tiap user]
    G --> H[Kirim Email reminder\nMail: PasswordExpiryReminderMail]
    G --> I{User punya\nPush Token?}
    I -- Tidak --> J[Skip push]
    I -- Ya --> K[Kirim Push Notification via Expo SDK]
    K --> L{Response Expo: DeviceNotRegistered?}
    L -- Ya --> M[Hapus token tidak valid\ndari tabel push_tokens]
    L -- Tidak --> N[Log sukses]
    H --> O([Selesai])
    J --> O
    N --> O
    M --> O
```

**File:** `app/Console/Commands/NotifyPasswordExpiry.php`

**Trigger:** H-7 dan H-1 sebelum expired (bukan rentang, tapi tanggal **tepat** pada hari ke-53 dan ke-59 sejak ganti password terakhir).

---

## 4. Relasi Antar-Komponen

```mermaid
flowchart LR
    DB[(users\npassword_changed_at\nforce_password_change)]
    Model["User.php\nisPasswordExpired()"]
    MW1["Middleware\nCheckPasswordExpiry"]
    MW2["Middleware\nCheckForcePasswordChange"]
    Login["LoginController\nlogin()"]
    Setting["SettingController\nChangePassword()"]
    Modal["PasswordExpiryWarningModal.vue"]
    Scheduler["NotifyPasswordExpiry\n[Artisan Command]"]
    Routes["routes/web.php\nadmin / superadmin / user groups"]

    DB --> Model
    Model --> MW1
    Model --> Login
    MW1 --> Modal
    MW1 --> Routes
    MW2 --> Routes
    Setting --> DB
    Scheduler --> DB
    Routes --> MW1
    Routes --> MW2
```

---

## 5. Ringkasan State Machine Password

```mermaid
stateDiagram-v2
    [*] --> Normal : Login berhasil, password < 60 hari
    Normal --> Warning : password_changed_at >= 53 hari\n(H-7 atau lebih dekat)\nModal kuning muncul
    Warning --> Urgent : days_remaining ≤ 1\nModal merah muncul
    Normal --> Expired : password_changed_at >= 60 hari
    Warning --> Expired : Tidak ganti, melewati 60 hari
    Urgent --> Expired : Tidak ganti, melewati 60 hari
    Expired --> [*] : Akun diblokir saat login / request\nHubungi admin atau\nganti password via /password/change
    Normal --> ForceChange : Admin set force_password_change = true
    ForceChange --> Normal : User ganti password berhasil
    Expired --> Normal : User ganti password berhasil\npassword_changed_at = now()
```

---

## 6. Route yang Relevan

| Route Name | URL | Middleware | Keterangan |
|---|---|---|---|
| `password.change` | `GET /password/change` | `auth` only | Form ganti password — **tanpa** password.expiry |
| `password.change.save` | `POST /password/change` | `auth` only | Proses ganti password |
| `user.*` | `/dashboard`, dll. | `auth, is_user, password.expiry, force.pw.change` | Semua halaman user |
| `admin.*` | `/admin/...` | `auth, is_admin, password.expiry, force.pw.change` | Semua halaman admin |
| `super.*` | `/superadmin/...` | `auth, is_super, password.expiry, force.pw.change` | Semua halaman superadmin |
| `super.user.unlock` | `POST /superadmin/user/{user}/unlock` | `auth, is_super` | Admin unlock akun yang dikunci |

---

## 7. File Referensi

| Komponen | Path |
|---|---|
| User Model | `app/User.php` |
| Middleware Expired | `app/Http/Middleware/CheckPasswordExpiry.php` |
| Middleware Force Change | `app/Http/Middleware/CheckForcePasswordChange.php` |
| Login Controller | `app/Http/Controllers/Auth/LoginController.php` |
| Setting Controller | `app/Http/Controllers/User/SettingController.php` |
| Artisan Command | `app/Console/Commands/NotifyPasswordExpiry.php` |
| Frontend Modal | `resources/js/components/PasswordExpiryWarningModal.vue` |
| Layout User | `resources/js/Shared/UserLayout.vue` |
| Layout Super | `resources/js/Shared/SuperLayout.vue` |
| Routes | `routes/web.php` |
| Email Template | `resources/views/emails/passwordExpiryReminder.blade.php` |
