# Password Management

Source: https://www.potatoannotator.com/docs/deployment/password-management

*New in v2.4.0*

Potato's authentication system uses **PBKDF2-SHA256 with 100,000 iterations** and per-user salts — the same approach recommended by NIST for secure password storage. This page covers how passwords are stored, how to reset them, and how to persist credentials across server restarts.

## Security Implementation

Passwords are stored in `salt$hash` format:
- 32-character hex salt, unique per user
- 64-character SHA-256 hash of `salt + password`
- Constant-time comparison via `hmac.compare_digest` to prevent timing attacks

Existing plaintext passwords in `user_config.jsonl` files are **automatically re-hashed with unique salts** when Potato loads them — no manual migration needed.

## Default Configuration

By default, Potato uses in-memory authentication. Annotators must be listed in the config:

```yaml
authentication:
  method: in_memory
  require_password: true

user_config:
  users:
    - username: "annotator1"
      password: "initial-password"   # will be hashed on first load
    - username: "annotator2"
      password: "initial-password"
```

## Persistent Credentials

In-memory credentials are lost on restart. For persistent storage, use a file or database backend.

### File-Based Persistence

```yaml
authentication:
  method: in_memory
  user_config_path: /shared/path/to/user_config.jsonl
```

Potato writes hashed credentials to this file. On restart it reads them back — passwords set or changed during a session persist across restarts.

### Database Backend

**SQLite** (no additional dependencies):

```yaml
authentication:
  method: database
  database_url: "sqlite:///auth/users.db"
```

**PostgreSQL** (requires `psycopg2-binary`):

```yaml
authentication:
  method: database
  database_url: "postgresql://user:password@localhost:5432/potato_auth"
```

Database tables are created automatically on first startup.

**Note:** `method: database` and `user_config_path` are mutually exclusive — choose one persistence strategy.

## Resetting Passwords

### Admin CLI

Reset a password from the command line:

```bash
# Reset a specific user's password
potato reset-password config.yaml --username annotator1

# Prompted for new password interactively
```

### Admin API

Reset programmatically with an API key:

```bash
curl -X POST http://localhost:8000/admin/reset_password \
  -H "X-API-Key: $ADMIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"username": "annotator1", "new_password": "new-secure-password"}'
```

### Self-Service Token Reset

Potato supports a user-initiated reset flow. Users go to `/forgot-password`, enter their username, and receive a single-use reset token. They visit `/reset/<token>` to set a new password.

Tokens are valid for **24 hours** and can only be used once. Potato does not send emails — the admin distributes the reset link manually.

**Enable in config:**

```yaml
authentication:
  method: database
  database_url: "sqlite:///auth/users.db"
  allow_password_reset: true
  reset_token_ttl_hours: 24
```

**Admin workflow:**

```bash
# Generate a reset token for a user
curl -X POST http://localhost:8000/admin/generate_reset_token \
  -H "X-API-Key: $ADMIN_API_KEY" \
  -d '{"username": "annotator1"}'

# Returns: {"reset_url": "https://your-server.com/reset/abc123..."}
# Share this URL with the annotator
```

## Passwordless Mode

For classroom demos, quick studies, or tasks using external authentication (MTurk, Prolific), you can disable passwords entirely:

```yaml
authentication:
  method: in_memory
  require_password: false
```

Annotators enter any username to log in — no password prompt appears. Not recommended for sensitive data or tasks requiring verified identity.

See [Passwordless Login](/docs/features/passwordless-login) for details.

## Complete Reference

```yaml
authentication:
  method: in_memory       # in_memory | database | oauth | clerk

  # In-memory options
  require_password: true
  user_config_path: path/to/users.jsonl   # optional persistence

  # Database options (mutually exclusive with user_config_path)
  database_url: "sqlite:///auth/users.db"

  # Self-service reset
  allow_password_reset: true
  reset_token_ttl_hours: 24

user_config:
  users:
    - username: "researcher"
      password: "secure-passphrase"
      role: admin
    - username: "annotator1"
      password: "initial-pass"
      role: annotator
```

## Further Reading

- [SSO & OAuth Authentication](/docs/deployment/sso-oauth) — sign in with Google, GitHub, or institutional SSO
- [Passwordless Login](/docs/features/passwordless-login) — username-only access for open tasks
- [Production Setup](/docs/deployment/production-setup) — HTTPS and reverse proxy configuration
- [Admin Dashboard](/docs/features/admin-dashboard) — managing annotator accounts

For implementation details, see the [source documentation](https://github.com/davidjurgens/potato/blob/master/docs/password_management.md).
