Автентикация
Модулът auth/ управлява целия lifecycle на автентикацията: OAuth вход чрез browser redirect, JWT съхранение и валидация, периодично обновяване на токени, и Stripe checkout интеграция.
State Machine
AuthState enum
pub enum AuthState {
NotLoggedIn,
WaitingForBrowser { started: Instant, port: u16 },
LoggedIn(AccountInfo),
OfflineValid { account: AccountInfo, days_remaining: u32 },
Expired,
DeviceLimitBlocked { retry_local_time: String },
}
Browser Auth Flow (Login)
Race условия
Потокът използва tokio::select! за race между три futures:
tokio::select! {
Some(params) = params_rx.recv() => { /* callback получен */ },
_ = tokio::time::sleep(Duration::from_secs(60)) => { /* timeout */ },
_ = cancel_rx => { /* потребителят отмени */ },
}
Checkout Flow (Stripe)
Подобен на login flow-а, но:
- Изисква съществуващ JWT (Bearer auth за
/billing/create-checkout) - Отваря страница за избор на план (
/billing/choose-plan) - Потребителят минава през Stripe Checkout
- Timeout е 120 секунди (вместо 60)
- При успех получава нов JWT с
plan: "pro"
JWT управление
Структура на Claims
pub struct Claims {
pub sub: String, // email
pub user_id: String,
pub plan: String, // "free" или "pro"
pub device_id: String, // machine ID
pub provider: String, // "google", "azure", "github"
pub iat: i64, // issued at (Unix epoch)
pub exp: i64, // expiry (Unix epoch)
}
Token Storage
Токените се съхраняват в системния credential manager чрез keyring crate:
| Запис | Service Name | User |
|---|---|---|
| JWT access token | dictaro | jwt_token |
| Refresh token | dictaro | jwt_refresh |
На Windows това е Windows Credential Manager. На macOS -- Apple Keychain.
Token Validation
Валидацията използва вграден RS256 public key:
const RS256_PUBLIC_KEY_PEM: &str = "-----BEGIN PUBLIC KEY-----...";
pub fn validate_token(token: &str) -> Result<Claims, TokenError> {
let decoding_key = DecodingKey::from_rsa_pem(...);
let mut validation = Validation::new(Algorithm::RS256);
validation.leeway = 60; // 60-секунден tolerance за clock skew
jsonwebtoken::decode::<Claims>(token, &decoding_key, &validation)
}
Валидацията е изцяло client-side -- не се прави мрежова заявка. Това позволява offline работа.
Периодично обновяване на токени
| Параметър | Стойност |
|---|---|
REFRESH_INTERVAL_SECS | 3600 (1 час) |
REFRESH_BACKOFF_SECS | 300 (5 минути при неуспех) |
Restore при стартиране
При стартиране AuthManager::new() автоматично:
- Чете JWT от keyring (
token::get_token()) - Валидира го локално (
token::validate_token()) - Ако е валиден:
state = LoggedIn(AccountInfo) - Ако е невалиден: изтрива го и
state = NotLoggedIn - Ако няма токен:
state = NotLoggedIn
Device Limit
Licensing сървърът ограничава броя акаунти на устройство. При превишаване:
- Callback URL съдържа
error=device_limit&retry_at=RFC3339&cooldown_seconds=N retry_atсе конвертира от UTC към локално времеstate = DeviceLimitBlocked { retry_local_time: "15:23" }- UI показва кога потребителят може да опита отново
Customer Portal
start_portal() е fire-and-forget операция:
- POST към
/account/portal-urlс Bearer JWT - Получава Stripe Customer Portal URL
- Отваря го в browser-а
- Потребителят управлява абонамента си директно в Stripe Portal
- Няма callback --
AuthStateне се променя