/
VVRONGG
/
backside
Обзор
Документация
Войти
/
VVRONGG
/
backside
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
1
CI/CD
Аналитика
Безопасность
master
src/auth.rs
95 строк
3 KB
VVrongg
feat: delta-sync + routes API, hardened and tested
22 май 2026, 16:13
22 май 2026, 16:13
9d7239f
Код
Авторство
О чём код?
//! Minimal admin auth. One administrator whose credentials live in the //! environment (`ADMIN_USERNAME` / `ADMIN_PASSWORD`); on a correct login we //! issue a JWT signed with `JWT_SECRET`. Write endpoints require a valid token //! via the [`AdminClaims`] extractor; reads and sync stay public. //! //! Swap the env credentials for a users table with hashed passwords when more //! than one admin is needed. use std::sync::LazyLock; use axum::{extract::FromRequestParts, http::header::AUTHORIZATION, http::request::Parts}; use chrono::{Duration, Utc}; use jsonwebtoken::{DecodingKey, EncodingKey, Header, Validation, decode, encode}; use serde::{Deserialize, Serialize}; use crate::error::AppError; static JWT_SECRET: LazyLock<String> = LazyLock::new(|| std::env::var("JWT_SECRET").expect("JWT_SECRET must be set")); const TOKEN_TTL_HOURS: i64 = 24; #[derive(Serialize, Deserialize)] pub struct Claims { /// Subject — the admin username. pub sub: String, /// Expiry as a unix timestamp (seconds). pub exp: usize, } #[derive(Deserialize)] pub struct LoginRequest { pub username: String, pub password: String, } #[derive(Serialize)] pub struct LoginResponse { pub token: String, } /// Verify credentials against the environment and return a signed JWT. pub fn login(req: &LoginRequest) -> Result<String, AppError> { let user = std::env::var("ADMIN_USERNAME").unwrap_or_default(); let pass = std::env::var("ADMIN_PASSWORD").unwrap_or_default(); // reject if creds aren't configured, so a misconfigured server isn't open if user.is_empty() || pass.is_empty() || req.username != user || req.password != pass { return Err(AppError::Unauthorized); } create_token(&req.username) } fn create_token(username: &str) -> Result<String, AppError> { let exp = (Utc::now() + Duration::hours(TOKEN_TTL_HOURS)).timestamp() as usize; let claims = Claims { sub: username.to_owned(), exp, }; encode( &Header::default(), &claims, &EncodingKey::from_secret(JWT_SECRET.as_bytes()), ) .map_err(|e| AppError::Internal(e.into())) } /// Extractor that succeeds only for requests carrying a valid admin JWT in /// `Authorization: Bearer <token>`. Add it as a handler argument to gate writes. pub struct AdminClaims(pub Claims); impl<S> FromRequestParts<S> for AdminClaims where S: Send + Sync, { type Rejection = AppError; async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> { let token = parts .headers .get(AUTHORIZATION) .and_then(|v| v.to_str().ok()) .and_then(|v| v.strip_prefix("Bearer ")) .ok_or(AppError::Unauthorized)?; let data = decode::<Claims>( token, &DecodingKey::from_secret(JWT_SECRET.as_bytes()), &Validation::default(), ) .map_err(|_| AppError::Unauthorized)?; Ok(AdminClaims(data.claims)) } }