# orca.auth

> A module of Orca's API.

Who may act in `orca ui`: users with passwords, their sessions, and a record of what each did.

```
orca user add alice       reads the password from standard input
orca user remove alice    and ends their sessions
orca user list
```

Anyone who can reach the UI can read it. Its actions (start, backfill,
resume, rerun, cancel, rebuild) need a logged-in user once login is on:
when `ORCA_UI_LOGIN=required`, or as soon as any user exists, so a
deployment can't be left open by forgetting the variable once it has users.

Passwords are kept as bcrypt hashes made by Postgres's pgcrypto
(`crypt(password, gen_salt('bf', 12))`), so neither orca nor its logs see
one after it's checked. Logging in makes a session: 32 random bytes from
the system's generator, given to the browser as the cookie `orca_session`
and kept only as their SHA-256 digest, for `ORCA_UI_SESSION_HOURS` (default
12). Every action a user takes is a row of `orca.audit`, and so is every failed
login, which `too_many_failures` counts: after 5 for one name, or 20 from
one address, in 15 minutes, `/login` answers 429 without checking a
password, so a public UI can't be guessed at.

## Functions

### `login_required`

```mojo
def login_required(mut db: State) -> Bool
```

### `add_user`

```mojo
def add_user(mut db: State, name: String, password: String) -> Bool
```

Adds the user, or sets an existing one's password (ending her sessions); True if the user is new.

### `remove_user`

```mojo
def remove_user(mut db: State, name: String) -> Bool
```

Removes the user and their sessions; False if there was none.

### `users`

```mojo
def users(mut db: State) -> List[String]
```

### `too_many_failures`

```mojo
def too_many_failures(mut db: State, name: String, address: String) -> Bool
```

Whether logins for `name`, or from `address`, have failed too often of late to check another: `FAILURES_PER_NAME` for a name or `FAILURES_PER_ADDRESS` from an address in `FAILURE_WINDOW_MINUTES`. Failures are `login-failed` rows of `orca.audit`, with the address as their detail.

### `login_failed`

```mojo
def login_failed(mut db: State, name: String, address: String)
```

### `open_session`

```mojo
def open_session(mut db: State, name: String, password: String) -> String
```

A new session's token if the password is the user's, else empty.

### `session_user`

```mojo
def session_user(mut db: State, token: String) -> String
```

The user whose session `token` is, or empty.

### `close_session`

```mojo
def close_session(mut db: State, token: String)
```

### `audit`

```mojo
def audit(mut db: State, user: String, action: String, run: Int, detail: String)
```

Records that `user` did `action` (the action's path), to `run` (0 for none), with `detail` (what the form gave).

## Aliases

### `SESSION_COOKIE`

```mojo
comptime SESSION_COOKIE = "orca_session"
```

### `FAILURES_PER_NAME`

```mojo
comptime FAILURES_PER_NAME = 5
```

### `FAILURES_PER_ADDRESS`

```mojo
comptime FAILURES_PER_ADDRESS = 20
```

### `FAILURE_WINDOW_MINUTES`

```mojo
comptime FAILURE_WINDOW_MINUTES = 15
```
