# Authentication Guide

This guide covers authentication, sessions, and user management for the REST Sun Grass backend.

## Contents

- [Authentication endpoints](#authentication-endpoints)
- [How the flow works](#how-the-flow-works)
- [Tokens](#tokens)
- [Authentication endpoint usage](#authentication-endpoint-usage)
- [Session concepts](#session-concepts)
- [Session lifecycle](#session-lifecycle)
- [User management endpoints](#user-management-endpoints)
- [Common problems](#common-problems)
- [Session debugging checklist](#session-debugging-checklist)

---

## Authentication Endpoints

Authentication routes are exposed under `/auth`.

| Endpoint | Purpose |
|----------|---------|
| `POST /auth/login` | Validates the user against Actinia, creates a local backend session, and returns a new access/refresh token pair |
| `POST /auth/refresh` | Validates the current refresh token, rotates the token pair for the same session, and extends the session lifetime |
| `POST /auth/logout` | Revokes the active session identified by the refresh token |
| `GET /auth/me` | Returns the authenticated user identity together with the current session id and session expiry |

## How the Flow Works

The backend uses Actinia as the identity source, but it manages its own local session lifecycle for API access and workflow isolation.

The following diagram summarizes the authentication and session workflow:

![Authentication and session workflow](diagrams/auth-session-workflow-diagram.png)

*Figure 1: Authentication and session workflow, from Actinia credential validation to local session creation, token issuance, and session-backed API access.*

In this workflow, Actinia is used as the external identity source, while the backend manages the local session and token lifecycle. After successful credential validation, the backend creates or updates the local user record, issues an access token and a refresh token, and then uses the session-backed `sid` to authorize later protected requests.

## Tokens

The backend issues two JWTs:

| Token | Main use | Default validity |
|-------|----------|------------------|
| **Access token** | Sent in `Authorization: Bearer <ACCESS_TOKEN>` for protected endpoints | `86400` seconds (`24 hours`) |
| **Refresh token** | Used to rotate the token pair and to revoke the active session | `604800` seconds (`7 days`) |

## Authentication Endpoint Usage

The authentication routes are typically used in the following order: login first, `GET /auth/me` while the session is active, `POST /auth/refresh` when a new token pair is needed, and `POST /auth/logout` when the session should be revoked.

### Login (`POST /auth/login`)

This endpoint accepts:

- `username` as form data
- `password` as form data

On success, it:

- validates the credentials against Actinia
- syncs or creates the local user record
- creates a local backend session
- returns a new access token and refresh token

Example:

```bash
curl -X POST "http://localhost:8000/auth/login" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "username=actinia-gdi&password=actinia-gdi"
```

### Check Current Session (`GET /auth/me`)

This endpoint returns the authenticated identity together with the current session id and session expiry timestamp.

Example:

```bash
curl -X GET "http://localhost:8000/auth/me" \
  -H "Authorization: Bearer <ACCESS_TOKEN>"
```

### Refresh Tokens (`POST /auth/refresh`)

This endpoint validates the current refresh token, rotates the token pair, and keeps the same backend session active.

Example:

```bash
curl -X POST "http://localhost:8000/auth/refresh?refresh_token=<REFRESH_TOKEN>"
```

### Logout (`POST /auth/logout`)

This endpoint revokes the session associated with the provided refresh token.

Example:

```bash
curl -X POST "http://localhost:8000/auth/logout?refresh_token=<REFRESH_TOKEN>"
```

---

## Session Concepts

Most solar workflows in this system are session-aware. When a user logs in, the backend creates a session and later uses that session to:

- isolate intermediate files
- isolate GRASS mapsets
- attach metadata to jobs and outputs
- resolve cached context from Valkey

In practice, protected solar endpoints expect:

- a valid bearer token
- an active session
- workflow context that belongs to the same active session

### Session

A backend-authenticated working context. Many solar endpoints derive:

- `session_id`
- `user_id`
- `mapset = session_<session_id>`

from the access token.

### Location

The GRASS location for the current workflow. It is created once based on the workflow EPSG, and then remains fixed for the later stages of the same workflow.

### Mapset

Most user-scoped solar outputs are tied to:

```text
session_<session_id>
```

Example:

```text
session_1
```

## Session Lifecycle

The normal lifecycle is:

1. `POST /auth/login` authenticates the user and creates a session row.
2. The response returns an access token and a refresh token.
3. Protected endpoints use the access token and derive the active `session_id`.
4. `POST /auth/refresh` rotates the token pair for the same session.
5. `POST /auth/logout` revokes the session.

```mermaid
flowchart LR
    A[Login] --> B[Session row]
    B --> C[sid]
    C --> D[Protected APIs]
    C --> E[Refresh token]
    E --> F[Refresh]
    F --> D
    E --> G[Logout]
    G --> H[Revoked session]
```

### How `session_id` Becomes `mapset`

Many solar services derive the working GRASS mapset from the active session.

The rule is:

```text
mapset = session_<session_id>
```

Example:

```text
session_id = 1
mapset = session_1
```

```mermaid
flowchart LR
    A[Access token] --> B[sid = 1]
    B --> C[session_id = 1]
    C --> D[mapset = session_1]
    D --> E[Solar workflows use <br/> this mapset]
```

This matters because many later workflows look up prerequisite outputs using the same session-derived mapset.

If an earlier dataset was created in another session, a later workflow in the current session may not find it even if a similar file exists on disk.

### Token Expiry vs Session Expiry

These concepts are related but not identical.

- **Access token expiry** — controls how long the access token itself is valid (default 24 hours)
- **Refresh token expiry** — controls how long the refresh token can rotate tokens (default 7 days)
- **Session expiry** — stored in the database session row, aligned to the refresh token expiry, checked by the backend in addition to JWT validation

A JWT may look structurally valid but a request can still fail if the underlying session is inactive or expired.

### What Happens After Logout

When `POST /auth/logout` succeeds:

- the session row is marked inactive
- the session gets a revoke timestamp
- the revoke reason is recorded as `user_logout`

Protected workflows will stop accepting that session even if an old token string is still present somewhere.

---

## User Management Endpoints

User routes are exposed under `/users`.

### Self-Service Routes

- `GET /users/me`
- `PATCH /users/me`
- `GET /users/me/preferences`
- `PATCH /users/me/preferences`

### Admin Routes

- `GET /users`
- `POST /users/{user_id}`
- `GET /users/{user_id}`
- `DELETE /users/{user_id}`

### Read Current Profile

Use:

```text
GET /users/me
```

### Update Current Profile

Use:

```text
PATCH /users/me
```

This updates mutable profile fields such as:

- `full_name`
- `email`

### Read Preferences

Use:

```text
GET /users/me/preferences
```

### Update Preferences

Use:

```text
PATCH /users/me/preferences
```

This merges the provided preference object into the stored preferences.

### Admin: List Actinia Users

Use:

```text
GET /users
```

Requires `admin` or `superadmin` privileges.

### Admin: Create User

Use:

```text
POST /users/{user_id}?password=...&group=...
```

Notes:

- `user_id` in the path is the Actinia username
- `password` is passed as a query parameter
- `group` is passed as a query parameter
- optional local profile fields can be passed in the request body

### Admin: Read User Details

Use:

```text
GET /users/{user_id}
```

Note: this endpoint currently expects a numeric local user id in the path.

### Admin: Delete User

Use:

```text
DELETE /users/{user_id}
```

Note: this endpoint currently uses the Actinia username in the path.

Because the admin read and delete endpoints currently use different path meanings, check Swagger `/docs` before using them in automation.

---

## Common Problems

### `401 Invalid credentials`

Cause:

- the username or password was rejected by Actinia

### `503 Failed to connect to Actinia`

Cause:

- the backend could not reach Actinia during credential validation or user synchronization
- this usually indicates an infrastructure, container, or network problem rather than a bad username/password

### `401 Missing, invalid, or expired access token`

Cause:

- the access token is missing, expired, or no longer tied to an active session

### `401 Invalid, expired, or revoked refresh token`

Cause:

- the refresh token is no longer usable

### `403 Admin privileges required`

Cause:

- the current user is not `admin` or `superadmin`

### `invalid_session`

Cause:

- the access token is invalid, expired, or no longer linked to an active session

### Session Exists But Workflow Still Fails

Cause:

- the session is valid but a required earlier workflow output has not been generated yet
- or the later workflow cannot resolve the required per-session context

### Context Missing In Valkey

Cause:

- files may exist but the cached session metadata was not found

Fix:

- rerun the prerequisite workflow or regenerate the required dataset within the same active session

### `Session inactive` Or `Session expired`

Cause:

- the JWT may still be present but the backing session row is no longer active

## Session Debugging Checklist

If a protected workflow fails and you suspect a session issue, check these in order:

1. Can `GET /auth/me` still be called successfully with the current access token?
2. Does the returned `sid` match the session you think you are using?
3. Are the prerequisite workflows supposed to exist in this same session?
4. Has the session already been logged out, revoked, or expired?
5. If refresh is needed, is the refresh token still within its valid lifetime?
6. If the workflow depends on cached context, was that context created inside this same session?
