LiveKit-based alternative to Mumble and Discord
  • Rust 50.9%
  • TypeScript 45.8%
  • CSS 2.9%
  • Dockerfile 0.4%
Find a file
Fable d0bcc72de2 Implement client-side ATProto OAuth login flow (#74)
Implements the Tauri client-side login flow, completes the authentication loop started in #68, and closes #9 and #70.

## What's included

### Auth gRPC integration (`src-tauri/src/auth/`)

**`AuthManager`** — new manager responsible for the full session lifecycle:
- `start_login(server_url, identifier)` — persists the server URL to the store, connects the auth gRPC client, calls `InitiateLogin`, and returns `{ oauth_url, session_id }`
- `await_login(session_id)` — opens the `AwaitLogin` server-streaming call and blocks until the JWT arrives, then stores it in `tauri-plugin-store`
- `get_session()` — single authoritative check for "is the user authenticated?"; returns `Some(Session)` only when both `server-url` and `jwt` are present in the store
- `clear_session()` — atomically removes both store keys on logout

**`Session` struct** — pairs `server_url` and `jwt` as a first-class value. The JWT and server URL are two fields of one fact ("I am logged into this server"), so they are managed together and checked together.

**`start_login` command** — opens the OAuth URL in the system browser via `tauri-plugin-opener` after `InitiateLogin` succeeds.

**`await_login` command** — coordination point between managers: once the JWT lands, triggers `channels.connect(session.server_url)` so the home screen is fully ready without a separate step.

**`get_session` / `logout` commands** — replace `get_server_url` / `set_server_url` for session management.

### Session ownership refactor

`ChannelsManager` previously owned the server URL (stored it in `tauri-plugin-store`, exposed `get_server_url` / `set_server_url`). This was incorrect: the server URL is only meaningful in the context of an authenticated session, and treating it as independent mutable state allowed the app to be in inconsistent positions (URL set, no JWT; JWT set, wrong URL).

Changes:
- `ChannelsManager` no longer holds a store reference or knows about the server URL. `new()` takes no arguments. It is now pure infrastructure: gRPC connectivity, LiveKit rooms, audio.
- `AuthManager` takes over `CONFIG_KEY_SERVER_URL` from the store and persists it in `start_login` (before the browser opens, so it survives a crash mid-flow).
- `set_server_url` / `get_server_url` Tauri commands removed.
- `lib.rs` startup now calls `auth_manager.get_session()` to check for a persisted session and connect channels if one is found.

### `build.rs`

`InitiateLoginResponse` gains `#[serde(rename_all = "camelCase")]` so it serialises as `{ oauthUrl, sessionId }`, matching what the XState actor expects.

### Frontend

**`connect.tsx` / `loginMachine`** — drives the full login flow:
1. User submits server URL + ATProto identifier
2. `start_login` invoke → `{ oauthUrl, sessionId }` returned, browser opens to OAuth URL
3. `await_login(sessionId)` invoke blocks in the background while the user completes OAuth
4. On completion, machine transitions and navigates to `/server`

**`authenticated.tsx`** — `clientLoader` now calls `get_session` instead of `get_server_url`; redirects to `/connect` if no session.

**`settings.tsx`** — "Change Server" calls `logout` and redirects to `/connect` directly.

## Closes

- Closes #9 (server-side OAuth flow was #68; this PR adds the client-side half)
- Closes #70 (login UI in Tauri client)
- Closes #69 (verify JWTs server-side)

## Notes for reviewer

- The `ChannelsManager` will gain an `Arc<AuthManager>` reference when the JWT interceptor is implemented (#69). The dependency direction is `ChannelsManager → AuthManager` (channels needs the JWT, not the other way around). The current refactor intentionally prepares for this without introducing the circular reference that would result from the opposite arrangement.
- `Session` is not yet exported as a TypeScript binding — the `authenticated.tsx` loader uses an inline type. This can be formalised with `ts-rs` when the session shape stabilises.

Co-authored-by: Graham Barber <green.cheese8030@fastmail.com>
Reviewed-on: #74
Reviewed-by: seb <sebastiancbenjamin@gmail.com>
Co-authored-by: Fable <quiet.sign9113@fastmail.com>
Co-committed-by: Fable <quiet.sign9113@fastmail.com>
2026-03-06 16:05:10 -08:00
.cargo Implement room connection, handle local and remote audio streams, refactor client state management (#40) 2026-02-25 10:21:07 -08:00
.storybook Integrate Storybook and ts-rs (#30) 2026-02-19 09:20:49 -08:00
.vscode init tauri app 2026-02-10 18:22:49 -08:00
.zed add zed debugger config for server 2026-02-15 14:23:47 -08:00
app Implement client-side ATProto OAuth login flow (#74) 2026-03-06 16:05:10 -08:00
proto Implement client-side ATProto OAuth login flow (#74) 2026-03-06 16:05:10 -08:00
public Setting up react-router and shadcn 2026-02-11 19:29:18 -08:00
server Implement client-side ATProto OAuth login flow (#74) 2026-03-06 16:05:10 -08:00
src-tauri Implement client-side ATProto OAuth login flow (#74) 2026-03-06 16:05:10 -08:00
stories/ui Integrate Storybook and ts-rs (#30) 2026-02-19 09:20:49 -08:00
.dockerignore Add container build configuration (#32) 2026-02-18 11:01:38 -08:00
.gitattributes Adding server url page and channels sidebar 2026-02-18 17:28:39 -08:00
.gitignore Use inset sidebar (#49) 2026-02-26 16:25:03 -08:00
components.json Adding server url page and channels sidebar 2026-02-18 17:28:39 -08:00
deno.jsonc Implement room connection, handle local and remote audio streams, refactor client state management (#40) 2026-02-25 10:21:07 -08:00
deno.lock Implement client-side ATProto OAuth login flow (#74) 2026-03-06 16:05:10 -08:00
Dockerfile Triage and fix myriad TLS problems (#45) 2026-02-26 21:54:09 -08:00
package.json Implement client-side ATProto OAuth login flow (#74) 2026-03-06 16:05:10 -08:00
react-router.config.ts Setting up react-router and shadcn 2026-02-11 19:29:18 -08:00
README.md Implement server-side ATProto OAuth login flow (#68) 2026-03-03 22:37:40 -08:00
tsconfig.json Adding server url page and channels sidebar 2026-02-18 17:28:39 -08:00
tsconfig.node.json init tauri app 2026-02-10 18:22:49 -08:00
vite.config.ts Implement room connection, handle local and remote audio streams, refactor client state management (#40) 2026-02-25 10:21:07 -08:00
vitest.shims.d.ts Integrate Storybook and ts-rs (#30) 2026-02-19 09:20:49 -08:00

microclimate

A LiveKit-based alternative to Mumble and Discord.

Requires

  • Rust
  • Deno
  • protoc, the protobuffer compiler
  • A server/.env file, see server/.env.example for required values

Project structure

The server and Tauri client are separate Cargo workspaces. Most server-side Cargo commands should be run from the server/ directory; most client-side commands are run from the project root (via Deno tasks) or src-tauri/.

Setup

Server

The server uses SQLx compile-time query verification, which requires either a live database or a pre-generated query cache. The cache is already committed to the repository under server/.sqlx/, so a live database is only needed when you change the schema.

# Copy and fill in the environment file
cp server/.env.example server/.env

# JWT_SECRET is required — the server uses it to sign session tokens issued
# after a successful ATProto OAuth login. Use a long random value, e.g.:
#   openssl rand -base64 32

# (First time only, or after changing migrations) Install sqlx-cli and
# run migrations to set up the database
cargo install sqlx-cli --no-default-features --features sqlite
cd server && sqlx migrate run

# (After changing migrations) Regenerate the SQLx query cache and commit it
cd server && cargo sqlx prepare

Note: In production, migrations run automatically when the server starts.

Client

No additional setup is required beyond what Deno installs automatically.

Running

# Start the gRPC server (from server/)
cd server && cargo run

# Start the Tauri desktop app (from project root, use cmd.exe on Windows)
deno task tauri dev

Note: cargo build from the project root only builds the Tauri client. To build the server, run cargo build from server/.

Tips

  • If you're developing on Windows, I've observed that the Tauri app will not render when executing deno task tauri dev from Powershell. Using cmd seems to work just fine, though.
  • I recommend using Yaak for testing gRPC features without a client
  • Use cargo test export_bindings from src-tauri/ to generate TypeScript types from structs using ts-rs