- Rust 50.9%
- TypeScript 45.8%
- CSS 2.9%
- Dockerfile 0.4%
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> |
||
|---|---|---|
| .cargo | ||
| .storybook | ||
| .vscode | ||
| .zed | ||
| app | ||
| proto | ||
| public | ||
| server | ||
| src-tauri | ||
| stories/ui | ||
| .dockerignore | ||
| .gitattributes | ||
| .gitignore | ||
| components.json | ||
| deno.jsonc | ||
| deno.lock | ||
| Dockerfile | ||
| package.json | ||
| react-router.config.ts | ||
| README.md | ||
| tsconfig.json | ||
| tsconfig.node.json | ||
| vite.config.ts | ||
| vitest.shims.d.ts | ||
microclimate
A LiveKit-based alternative to Mumble and Discord.
Requires
- Rust
- Deno
protoc, the protobuffer compiler- A
server/.envfile, seeserver/.env.examplefor 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 devfrom Powershell. Usingcmdseems to work just fine, though. - I recommend using Yaak for testing gRPC features without a client
- Use
cargo test export_bindingsfromsrc-tauri/to generate TypeScript types from structs using ts-rs