Сигурност
Тази страница описва мерките за сигурност, прилагани в Dictaro на всички нива -- автентикация, авторизация, транспорт, rate limiting и управление на секрети.
JWT RS256 жизнен цикъл
Dictaro използва RS256 (RSA-SHA256) JWT токени за автентикация между клиента и licensing сървъра.
Създаване на токен (сървър)
JWT токенът се създава от Go сървъра чрез services.CreateAccessToken():
Алгоритъм: RS256 (RSA + SHA-256)
Подписване: RSA Private Key (2048-bit, PEM PKCS1/PKCS8)
Верификация: RSA Public Key (PEM PKIX)
JWT Claims структура:
| Claim | Тип | Описание |
|---|---|---|
sub | string | Email на потребителя |
user_id | string (UUID) | Уникален идентификатор на потребителя |
plan | string | "free" или "pro" |
device_id | string | SHA-256 на machine ID (ако е предоставен) |
provider | string | OAuth provider (google, azure, github, email) |
iat | int64 | Issued At (Unix timestamp) |
exp | int64 | Expiry (Unix timestamp, iat + JWT_EXPIRY_DAYS * 86400) |
Конфигурация:
JWT_EXPIRY_DAYS: 7 дни (по подразбиране)- Сървърен leeway: 5 секунди (за clock skew)
- Клиентски leeway: 60 секунди
Валидация на токен (клиент)
Клиентът валидира JWT токени локално с вграден RS256 public key:
- Декодира JWT header, проверява
alg == RS256 - Верифицира подписа с embedded PEM public key
- Проверява
expclaim с 60-секунден leeway - Извлича
AccountInfoот claims (email, plan, provider)
Периодичен refresh
Съхранение на токени
| Платформа | Storage механизъм | Достъп |
|---|---|---|
| Windows | Windows Credential Manager | keyring crate, service=dictaro, user=jwt_token |
| macOS | macOS Keychain | keyring crate с apple-native feature |
| Linux | Secret Service API (DBus) | keyring crate (fallback) |
Токените никога не се записват на файловата система, а се съхраняват в защитеното хранилище на ОС.
OAuth2 потоци
Поддържани провайдъри
OAuth конфигурация
| Провайдър | Auth URL | Token URL | Scopes | UserInfo endpoint |
|---|---|---|---|---|
accounts.google.com/o/oauth2/v2/auth | oauth2.googleapis.com/token | openid email profile | googleapis.com/oauth2/v3/userinfo | |
| Azure AD | login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize | login.microsoftonline.com/{tenant}/oauth2/v2.0/token | openid email profile User.Read | graph.microsoft.com/v1.0/me |
| GitHub | github.com/login/oauth/authorize | github.com/login/oauth/access_token | user:email | api.github.com/user + /user/emails |
CSRF защита
OAuth flow-ът използва двуслойна state защита:
- Клиентски state -- SHA-256 на
(timestamp + PID), верифициран при callback на localhost - Сървърен nonce -- 32-byte криптографски random hex, записан в session cookie, верифициран при OAuth callback
Session cookies
Session cookies се управляват чрез gorilla/securecookie:
- HttpOnly: Да (не се достъпва от JavaScript)
- Secure: Да, ако
BASE_URLе HTTPS - SameSite: Lax (по подразбиране на Gin)
- Съдържание:
{oauth_nonce, redirect_uri, app_state, device_id} - Lifetime: Сесийна (изтрива се при затваряне на браузъра)
Email/Password автентикация
В допълнение към OAuth, Dictaro поддържа email/password регистрация:
- Password hashing: bcrypt (
golang.org/x/crypto/bcrypt) - Email верификация: Задължителна преди вход; изпраща се чрез Resend API
- Password reset: Token-based flow с expiry, изпратен по email
- Turnstile защита: Login страницата включва Cloudflare Turnstile challenge
API Key автентикация за ASR
ASR сървърът използва споделен API key за автентикация:
| Параметър | Описание |
|---|---|
SERVER_API_KEY | Споделен ключ, зададен като env variable |
| Транспорт | Изпраща се в JSON metadata при WebSocket connect |
| Валидация | Сървърът проверява ключа преди обработка на аудио |
{
"type": "transcribe",
"api_key": "shared-secret-key",
"language": "bg",
"task": "transcribe",
"machine_id": "device-hash",
"postprocess": true
}
API ключът е вграден в клиента и осигурява базова защита срещу неоторизиран достъп до ASR ресурса.
Cloudflare Turnstile
Bot protection на login страницата:
Конфигурация:
TURNSTILE_SITE_KEY-- публичен ключ за frontend widgetTURNSTILE_SECRET_KEY-- секретен ключ за server-side валидация
Rate Limiting
Per-IP rate limiting с token bucket алгоритъм (golang.org/x/time/rate):
Rate limit конфигурация
| Endpoint категория | Rate | Burst | Max IPs | Endpoints |
|---|---|---|---|---|
| Login | 5/мин | 5 | 10,000 | /auth/login/:provider, /auth/register, /auth/login/email |
| Sensitive | 3/мин | 3 | 10,000 | /auth/confirm-email, /auth/resend-verification, /auth/forgot-password, /auth/reset-password |
| Refresh | 30/мин | 30 | 10,000 | /auth/refresh |
Имплементация
Защити срещу memory exhaustion:
- Maximum 10,000 tracked IP адреса на limiter
- Автоматично почистване на stale entries (> 10 мин неактивност)
- При достигане на maxSize, нови IP адреси получават temporary limiter (без tracking)
CORS конфигурация
CORS middleware прилага whitelist подход -- само изрично разрешени origins получават CORS headers:
# .env конфигурация
CORS_ORIGINS=["http://localhost:3000","http://localhost:9876","https://dictaro.ai"]
| Header | Стойност |
|---|---|
Access-Control-Allow-Origin | Конкретният origin от whitelist-a |
Access-Control-Allow-Credentials | true |
Access-Control-Allow-Methods | GET, POST, PUT, DELETE, OPTIONS |
Access-Control-Allow-Headers | Content-Type, Authorization |
Access-Control-Max-Age | 3600 (1 час preflight cache) |
Fail-closed поведение: Ако CORS_ORIGINS не е конфигуриран (празен масив), никакви CORS headers не се добавят -- кросс-origin заявки са напълно блокирани.
Preflight OPTIONS заявки получават 204 No Content без обработка от route handler-ите.
Security Headers
Всички HTTP отговори от licensing API-то включват следните security headers:
| Header | Стойност | Предназначение |
|---|---|---|
X-Content-Type-Options | nosniff | Предотвратява MIME type sniffing |
X-Frame-Options | DENY | Блокира iframe embedding (clickjacking protection) |
Referrer-Policy | strict-origin-when-cross-origin | Ограничава Referer header при cross-origin навигация |
Strict-Transport-Security | max-age=31536000; includeSubDomains | HSTS -- принуждава HTTPS за 1 година (само при HTTPS BASE_URL) |
Redirect URI валидация
OAuth callback redirect URI-та се валидират срещу конфигуриран whitelist:
# .env конфигурация
ALLOWED_REDIRECT_URIS=["http://localhost","https://app.dictaro.ai"]
Правила за валидация:
- URI трябва да започва с
http://илиhttps:// - Scheme трябва да съвпада с prefix-а
- Host се сравнява точно (не prefix match) -- предотвратява spoofing като
https://app.dictaro.ai.evil.com - Ако prefix-ът указва порт, той трябва да съвпада
- Ако prefix-ът указва path, URI path-ът трябва да започва с него
- В development mode (празен whitelist) -- всички
http://иhttps://URI-та са разрешени
Управление на секрети
Среди и методи
Списък на секретите
| Секрет | Среда | Storage | Предназначение |
|---|---|---|---|
DATABASE_URL | Production | .env + GitHub Secrets | PostgreSQL connection string |
JWT_PRIVATE_KEY | Production | GitHub Secrets → deploy | RS256 подписване на JWT токени |
JWT_PUBLIC_KEY | Production + Client | GitHub Secrets + embedded | RS256 валидация на JWT токени |
SESSION_SECRET | Production | .env | Подписване на session cookies (min 32 chars) |
GOOGLE_CLIENT_ID/SECRET | Production | .env + GitHub Secrets | Google OAuth2 |
AZURE_CLIENT_ID/SECRET | Production | .env + GitHub Secrets | Azure AD OAuth2 |
GITHUB_CLIENT_ID/SECRET | Production | .env + GitHub Secrets | GitHub OAuth2 |
STRIPE_SECRET_KEY | Production | .env + GitHub Secrets | Stripe API достъп |
STRIPE_WEBHOOK_SECRET | Production | .env + GitHub Secrets | Валидация на Stripe webhook signatures |
RESEND_API_KEY | Production | .env + GitHub Secrets | Изпращане на email-и |
TURNSTILE_SITE_KEY | Production | .env | Cloudflare Turnstile (публичен) |
TURNSTILE_SECRET_KEY | Production | .env | Cloudflare Turnstile (сървърен) |
SERVER_API_KEY | ASR Production | .env | Автентикация на ASR заявки |
CLOUDFLARE_TUNNEL_TOKEN | ASR Production | .env | Cloudflare Tunnel за ASR |
CLOUDFLARE_TUNNEL_TOKEN_MONITORING | Production | .env | Cloudflare Tunnel за Grafana |
CLOUDFLARE_TUNNEL_TOKEN_LOKI | Production | .env | Cloudflare Tunnel за Loki |
HF_TOKEN | ASR Production | .env | Hugging Face token за модели |
GRAFANA_PASSWORD | Production | .env | Grafana admin парола |
GRAFANA_AZUREAD_CLIENT_ID/SECRET | Production | .env | Azure AD SSO за Grafana |
Gitignored файлове
Следните файлове и директории са добавени в .gitignore:
*.env,.env.*-- всички environment файловеlicensing-go/keys/-- RSA ключове за JWT*.pem-- PEM ключови файлове
Stripe Webhook сигурност
Stripe webhook-ове се валидират чрез HMAC подпис:
- Stripe подписва payload с
STRIPE_WEBHOOK_SECRET - Сървърът извлича
Stripe-Signatureheader webhook.ConstructEvent()верифицира подписа преди обработка- Body size е ограничен до 1 MB за защита срещу abuse
event, err := webhook.ConstructEvent(payload, sigHeader, h.cfg.StripeWebhookSecret)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"detail": "Invalid Stripe signature"})
return
}
Device Account Limiting
За предотвратяване на злоупотреби с безплатни акаунти, системата ограничава броя на акаунтите от едно устройство:
| Параметър | Стойност | Описание |
|---|---|---|
DEVICE_ACCOUNTS_MAX | 3 | Максимален брой различни акаунти от едно устройство |
COOLDOWN_HOURS | 5 | Период на блокиране при превишаване |
- Device ID се изчислява като SHA-256 на machine-specific идентификатори
- Само за free акаунти -- Pro потребителите не подлежат на device limiting
- При достигане на лимита, сървърът връща
error=device_limitсretry_attimestamp