Something went wrong. Try again.
WIP experiment with 95% vibe coding
Something went wrong. Try again.
9.2 kB
Markdown
at main
Authentication System Design #
Component: Authentication System (Rust frontend + TypeScript backend) Status: Implemented Related: Backend Adapter, Persistence System
Overview #
Tiny Workshop uses z.AI as the API provider for Claude access. Authentication is simple: users provide their z.AI API key, which is stored securely in the OS keychain.
Design Goals #
- Simple: Just an API key - no OAuth flows or token management
- Secure: Use OS keychain for credential storage, never log credentials
- Persistent: API key survives app restarts
- Graceful errors: Handle invalid/missing keys with clear user feedback
Architecture #
┌──────────────────────────────────────────────────────────────┐
│ Frontend (Rust) │
├──────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ CredentialManager │ │
│ │ │ │
│ │ - Check for stored API key in keychain │ │
│ │ - Prompt user for key if not found │ │
│ │ - Store API key after user input │ │
│ │ - Pass to backend via IPC init message │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────┘
│ IPC (api_key in init message)
▼
┌──────────────────────────────────────────────────────────────┐
│ Backend Adapter (Bun) │
├──────────────────────────────────────────────────────────────┤
│ │
│ Sets environment variables: │
│ - ANTHROPIC_AUTH_TOKEN = api_key │
│ - ANTHROPIC_BASE_URL = https://api.z.ai/api/anthropic │
│ │
│ Passes to Claude Agent SDK │
│ │
└──────────────────────────────────────────────────────────────┘
│
│ SDK requests
▼
https://api.z.ai/api/anthropic
User Flow #
First Launch (No API Key) #
- App starts, checks keychain for stored API key
- No key found → shows system message: "Please enter your z.AI API key"
- User enters API key in chat input
- Frontend stores key in keychain
- Frontend sends
initmessage to backend with API key - Backend sets environment variables and starts SDK
- If successful: normal operation begins
- If auth fails: backend sends
auth_statuswithauthenticated: falseand error message
Subsequent Launches #
- App starts, loads API key from keychain
- Frontend sends
initmessage with stored API key - Backend authenticates with z.AI
- Normal operation begins
Invalid API Key #
- Backend receives
initwith API key - SDK fails authentication with z.AI
- Backend sends
auth_statuswithauthenticated: falseand error message - Frontend shows error and prompts for new API key
Credential Manager #
The CredentialManager in Rust handles API key storage:
pub struct CredentialManager;
impl CredentialManager {
/// Store the z.AI API key.
pub fn store_api_key(key: &str) -> Result<(), CredentialError>;
/// Get the stored API key.
pub fn get_api_key() -> Option<String>;
/// Clear the stored API key (logout).
pub fn clear() -> Result<(), CredentialError>;
/// Check if we have a stored API key.
pub fn has_api_key() -> bool;
/// Check if the OS keychain is available.
pub fn keychain_available() -> bool;
/// Get which storage backend is being used.
pub fn storage_type() -> StorageType;
}
Storage Constants #
const SERVICE_NAME: &str = "agent-study-api-key";
const API_KEY_KEY: &str = "zai_api_key";
const TOKEN_FILE_NAME: &str = "zai_api_key";
Platform-Specific Keychain Behavior #
| Platform | Keychain | Access Permission |
|---|---|---|
| macOS | macOS Keychain | User prompted on first access |
| Windows | Windows Credential Manager | No prompt (per-user storage) |
| Linux | Secret Service API (requires D-Bus) | Depends on keyring daemon (gnome-keyring, KWallet) |
Linux Fallback #
When Secret Service is unavailable on Linux (no gnome-keyring, KWallet, etc.), the credential manager automatically falls back to file-based storage:
Storage Location: ~/.agent-study/zai_api_key
Security Measures:
- File permissions: 0600 (user-only read/write)
- Atomic writes: temp file + rename pattern to prevent corruption
- Warning logged at startup when using fallback
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StorageType {
/// OS keychain (Windows Credential Manager, macOS Keychain, Linux Secret Service)
Keychain,
/// Config file fallback (~/.agent-study/zai_api_key)
ConfigFile,
}
IPC Messages #
Frontend → Backend #
init #
{
type: 'init';
version: number; // Protocol version (currently 1)
working_dir: string; // Absolute path to project directory
api_key?: string; // z.AI API key (optional)
resume_session_id?: string; // SDK session ID to resume (optional)
}
Backend → Frontend #
auth_status #
{
type: 'auth_status';
authenticated: boolean; // Currently authenticated?
error?: string; // Error message (only when authenticated=false)
}
Backend Environment Variables #
The backend adapter sets these environment variables for the Claude Agent SDK:
| Variable | Value |
|---|---|
ANTHROPIC_AUTH_TOKEN |
User's z.AI API key |
ANTHROPIC_BASE_URL |
https://api.z.ai/api/anthropic |
The SDK then uses these automatically for all API requests.
Error Handling #
#[derive(Debug)]
pub enum CredentialError {
/// Error from the OS keychain
Keychain(keyring::Error),
/// Error from file I/O operations
Io(std::io::Error),
}
User-Facing Error Messages #
| Error | User Message | Action |
|---|---|---|
| No API key | "Please enter your z.AI API key" | Show input prompt |
| Invalid API key | "Invalid API key" | Show input prompt again |
| Keychain error | "Unable to access keychain: {details}" | Fallback to config file on Linux |
Security Considerations #
- Never log API keys: Redact from logs, error messages, debug output
- Keychain encryption: OS keychain encrypts data at rest
- Config file warning: Linux fallback uses file permissions only (not encrypted)
- Memory handling: API key kept in memory only as long as needed
Credential Redaction #
impl Debug for ApiKey {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "ApiKey(***)")
}
}
Testing #
Unit Tests #
#[test]
fn test_storage_type_detection() {
let storage = CredentialManager::storage_type();
// Should return Keychain on macOS/Windows, either on Linux
println!("Detected storage type: {storage:?}");
}
Manual Testing Checklist #
- Fresh install: App prompts for API key, stores in keychain
- Restart: API key loaded from keychain automatically
- Invalid key: Error shown, can retry with new key
- Clear and re-enter: Can logout and enter new key
- Linux fallback: When no Secret Service, uses file storage
Related Documents #
- Backend Adapter - SDK integration
- IPC Protocol - Message schemas
- Persistence System - Storage patterns