**# Authentication Flow - Frappe/ERPNext Integration

## Overview

The authentication system integrates with Frappe/ERPNext API using a session-based approach with SID (Session ID) and CSRF tokens stored in FlutterSecureStorage.

## Complete Flow

### 1. App Startup (Check Saved Session)

**When**: User opens the app

**Process**:
1. `Auth` provider's `build()` method is called
2. Checks if user session exists in FlutterSecureStorage
3. If logged-in session exists (userId != public_api@dbiz.com), returns User entity
4. Otherwise returns `null` (user not logged in)
5. **Note**: Public session is NOT fetched on startup to avoid provider disposal issues

**Important**: The public session will be fetched lazily when needed:
- Before login (on login page load)
- Before registration (when loading cities/customer groups)
- Before any API call that requires session (via `ensureSession()`)

**API Endpoint**: `POST /api/method/dbiz_common.dbiz_common.api.auth.get_session`

**Request**:
```bash
curl -X POST 'https://land.dbiz.com/api/method/dbiz_common.dbiz_common.api.auth.get_session' \
  -H 'Content-Type: application/json' \
  -d ''
```

**Response**:
```json
{
  "session_expired": 1,
  "message": {
    "data": {
      "sid": "8c39b583...",
      "csrf_token": "f8a7754a9ce5..."
    }
  },
  "home_page": "/app",
  "full_name": "Guest"
}
```

**Storage** (FlutterSecureStorage):
- `frappe_sid`: "8c39b583..."
- `frappe_csrf_token`: "f8a7754a9ce5..."
- `frappe_full_name`: "Guest"
- `frappe_user_id`: "public_api@dbiz.com"

---

### 2. Initialize Public Session (When Needed)

**When**: Before login or registration, or before any API call

**Process**:
1. Call `ref.read(initializeFrappeSessionProvider.future)` on the page
2. Checks if session exists in FlutterSecureStorage
3. If no session, calls `FrappeAuthService.getSession()`
4. Stores public session (sid, csrf_token) in FlutterSecureStorage

**Usage Example**:
```dart
// In login page or registration page initState/useEffect
@override
void initState() {
  super.initState();
  // Initialize session when page loads
  WidgetsBinding.instance.addPostFrameCallback((_) {
    ref.read(initializeFrappeSessionProvider.future);
  });
}
```

Or use `FutureBuilder`:
```dart
FutureBuilder(
  future: ref.read(initializeFrappeSessionProvider.future),
  builder: (context, snapshot) {
    if (snapshot.connectionState == ConnectionState.waiting) {
      return LoadingIndicator();
    }
    return LoginForm(); // or RegistrationForm
  },
)
```

### 3. Loading Cities & Customer Groups (Using Public Session)

**When**: User navigates to registration screen

**Process**:
1. Session initialized (if not already) via `initializeFrappeSessionProvider`
2. `AuthRemoteDataSource.getCities()` is called
3. Gets stored session from FlutterSecureStorage
4. Calls API with session headers
5. Returns list of cities for address selection

**API Endpoint**: `POST /api/method/frappe.client.get_list`

**Request**:
```bash
curl -X POST 'https://land.dbiz.com/api/method/frappe.client.get_list' \
  -H 'Cookie: sid=8c39b583...; full_name=Guest; system_user=no; user_id=public_api%40dbiz.com; user_image=' \
  -H 'X-Frappe-CSRF-Token: f8a7754a9ce5...' \
  -H 'Content-Type: application/json' \
  -d '{
    "doctype": "City",
    "fields": ["city_name", "name", "code"],
    "limit_page_length": 0
  }'
```

**Response**:
```json
{
  "message": [
    {"city_name": "Hồ Chí Minh", "name": "HCM", "code": "HCM"},
    {"city_name": "Hà Nội", "name": "HN", "code": "HN"}
  ]
}
```

**Similarly for Customer Groups**:
```json
{
  "doctype": "Customer Group",
  "fields": ["customer_group_name", "name", "value"],
  "filters": {
    "is_group": 0,
    "is_active": 1,
    "customer": 1
  }
}
```

---

### 4. User Login (Get Authenticated Session)

**When**: User enters phone number and password, clicks login

**Process**:
1. `Auth.login()` is called with phone number
2. Gets current session from FlutterSecureStorage
3. Calls `AuthRemoteDataSource.login()` with phone + current session
4. API returns new authenticated session
5. `FrappeAuthService.login()` stores new session in FlutterSecureStorage
6. Dio interceptor automatically uses new session for all subsequent requests
7. Returns `User` entity with user data

**API Endpoint**: `POST /api/method/building_material.building_material.api.auth.login`

**Request**:
```bash
curl -X POST 'https://land.dbiz.com/api/method/building_material.building_material.api.auth.login' \
  -H 'Cookie: sid=8c39b583...' \
  -H 'X-Frappe-CSRF-Token: f8a7754a9ce5...' \
  -H 'Content-Type: application/json' \
  -d '{
    "username": "0123456789",
    "googleid": null,
    "facebookid": null,
    "zaloid": null
  }'
```

**Response**:
```json
{
  "session_expired": 1,
  "message": {
    "data": {
      "sid": "new_authenticated_sid_123...",
      "csrf_token": "new_csrf_token_456..."
    }
  },
  "home_page": "/app",
  "full_name": "Nguyễn Văn A"
}
```

**Storage Update** (FlutterSecureStorage):
- `frappe_sid`: "new_authenticated_sid_123..."
- `frappe_csrf_token`: "new_csrf_token_456..."
- `frappe_full_name`: "Nguyễn Văn A"
- `frappe_user_id`: "0123456789"

---

### 5. Authenticated API Requests

**When**: User makes any API request after login

**Process**:
1. `AuthInterceptor.onRequest()` is called
2. Reads session from FlutterSecureStorage
3. Builds cookie header with all required fields
4. Adds headers to request

**Cookie Header Format**:
```
Cookie: sid=new_authenticated_sid_123...; full_name=Nguyễn Văn A; system_user=no; user_id=0123456789; user_image=
X-Frappe-CSRF-Token: new_csrf_token_456...
```

**Example**: Getting products
```bash
curl -X POST 'https://land.dbiz.com/api/method/frappe.client.get_list' \
  -H 'Cookie: sid=new_authenticated_sid_123...; full_name=Nguyễn%20Văn%20A; system_user=no; user_id=0123456789; user_image=' \
  -H 'X-Frappe-CSRF-Token: new_csrf_token_456...' \
  -H 'Content-Type: application/json' \
  -d '{
    "doctype": "Item",
    "fields": ["item_name", "item_code", "standard_rate"],
    "limit_page_length": 20
  }'
```

---

### 6. User Logout

**When**: User clicks logout button

**Process**:
1. `Auth.logout()` is called
2. Clears session from both:
   - `AuthLocalDataSource` (legacy Hive)
   - `FrappeAuthService` (FlutterSecureStorage)
3. Gets new public session for next login/registration
4. Returns `null` (user logged out)

**Storage Cleared**:
- `frappe_sid`
- `frappe_csrf_token`
- `frappe_full_name`
- `frappe_user_id`

**New Public Session**: Immediately calls `getSession()` again to get fresh public session

---

## File Structure

### Core Services
- `lib/core/services/frappe_auth_service.dart` - Centralized session management
- `lib/core/models/frappe_session_model.dart` - Session response model
- `lib/core/network/api_interceptor.dart` - Dio interceptor for adding session headers

### Auth Feature
- `lib/features/auth/data/datasources/auth_remote_datasource.dart` - API calls (login, getCities, getCustomerGroups, register)
- `lib/features/auth/data/datasources/auth_local_datasource.dart` - Legacy Hive storage
- `lib/features/auth/presentation/providers/auth_provider.dart` - State management

### Key Components

**FrappeAuthService**:
```dart
class FrappeAuthService {
  Future<FrappeSessionResponse> getSession(); // Get public session
  Future<FrappeSessionResponse> login(String phone, {String? password}); // Login
  Future<Map<String, String>?> getStoredSession(); // Read from storage
  Future<Map<String, String>> ensureSession(); // Ensure session exists
  Future<Map<String, String>> getHeaders(); // Get headers for API calls
  Future<void> clearSession(); // Clear on logout
}
```

**AuthRemoteDataSource**:
```dart
class AuthRemoteDataSource {
  Future<GetSessionResponse> getSession(); // Wrapper for Frappe getSession
  Future<GetSessionResponse> login({phone, csrfToken, sid, password}); // Login API
  Future<List<City>> getCities({csrfToken, sid}); // Get cities for registration
  Future<List<CustomerGroup>> getCustomerGroups({csrfToken, sid}); // Get customer groups
  Future<Map<String, dynamic>> register({...}); // Register new user
}
```

**Auth Provider**:
```dart
@riverpod
class Auth extends _$Auth {
  @override
  Future<User?> build(); // Initialize session on app startup

  Future<void> login({phoneNumber, password}); // Login flow
  Future<void> logout(); // Logout and get new public session
}
```

**AuthInterceptor**:
```dart
class AuthInterceptor extends Interceptor {
  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    // Read from FlutterSecureStorage
    // Build cookie header
    // Add to request headers
  }
}
```

---

## Session Storage

All session data is stored in **FlutterSecureStorage** (encrypted):

| Key | Description | Example |
|-----|-------------|---------|
| `frappe_sid` | Session ID | "8c39b583..." |
| `frappe_csrf_token` | CSRF Token | "f8a7754a9ce5..." |
| `frappe_full_name` | User's full name | "Nguyễn Văn A" |
| `frappe_user_id` | User ID (phone or email) | "0123456789" or "public_api@dbiz.com" |

---

## Public vs Authenticated Session

### Public Session
- **User ID**: `public_api@dbiz.com`
- **Full Name**: "Guest"
- **Used for**: Registration, loading cities/customer groups
- **Obtained**: On app startup, after logout

### Authenticated Session
- **User ID**: User's phone number (e.g., "0123456789")
- **Full Name**: User's actual name (e.g., "Nguyễn Văn A")
- **Used for**: All user-specific operations (orders, cart, profile)
- **Obtained**: After successful login

---

## Error Handling

All API calls use proper exception handling:

- **401 Unauthorized**: `UnauthorizedException` - Session expired or invalid
- **404 Not Found**: `NotFoundException` - Endpoint not found
- **Network errors**: `NetworkException` - Connection failed
- **Validation errors**: `ValidationException` - Invalid data

---

## Future Enhancements

1. **Password Support**: Currently reserved but not sent. When backend supports password:
   ```dart
   Future<GetSessionResponse> login({
     required String phone,
     required String csrfToken,
     required String sid,
     String? password, // Remove nullable, make required
   }) async {
     // Add 'password': password to request body
   }
   ```

2. **Token Refresh**: Implement automatic token refresh on 401 errors

3. **Session Expiry**: Add session expiry tracking and automatic re-authentication

4. **Biometric Login**: Store phone number and use biometric for quick re-login

---

## Testing the Flow

### 1. Test Public Session
```dart
final frappeService = ref.read(frappeAuthServiceProvider).value!;
final session = await frappeService.getSession();
print('SID: ${session.sid}');
print('CSRF: ${session.csrfToken}');
```

### 2. Test Login
```dart
final auth = ref.read(authProvider.notifier);
await auth.login(
  phoneNumber: '0123456789',
  password: 'not_used_yet',
);
```

### 3. Test Authenticated Request
```dart
final remoteDataSource = ref.read(authRemoteDataSourceProvider).value!;
final cities = await remoteDataSource.getCities(
  csrfToken: 'from_storage',
  sid: 'from_storage',
);
```

### 4. Test Logout
```dart
await ref.read(authProvider.notifier).logout();
```

---

## Debugging

Enable cURL logging to see all requests:

**In `dio_client.dart`**:
```dart
dio.interceptors.add(CurlLoggerDioInterceptor());
```

**Console Output**:
```
╔══════════════════════════════════════════════════════════════
║ POST https://land.dbiz.com/api/method/building_material.building_material.api.auth.login
║ Headers: {Cookie: [HIDDEN], X-Frappe-CSRF-Token: [HIDDEN], ...}
║ Body: {username: 0123456789, googleid: null, ...}
╚══════════════════════════════════════════════════════════════

╔══════════════════════════════════════════════════════════════
║ Response: {session_expired: 1, message: {...}, full_name: Nguyễn Văn A}
╚══════════════════════════════════════════════════════════════
```

---

## Summary

The authentication flow is now fully integrated with Frappe/ERPNext:

1. ✅ App startup checks for saved user session
2. ✅ Public session fetched lazily when needed (via `initializeFrappeSessionProvider`)
3. ✅ Public session used for cities/customer groups
4. ✅ Login updates session to authenticated
5. ✅ All API requests use session from FlutterSecureStorage
6. ✅ Dio interceptor automatically adds headers
7. ✅ Logout clears session and gets new public session
8. ✅ cURL logging for debugging
9. ✅ No provider disposal errors

All session management is centralized in `FrappeAuthService` with automatic integration via `AuthInterceptor`.**
