Files
readur/src/auth.rs

91 lines
2.4 KiB
Rust

use anyhow::Result;
use axum::{
extract::FromRequestParts,
http::{request::Parts, HeaderMap, StatusCode},
response::{IntoResponse, Response},
};
use chrono::{Duration, Utc};
use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use uuid::Uuid;
use crate::{models::User, AppState};
#[derive(Debug, Serialize, Deserialize)]
pub struct Claims {
pub sub: Uuid,
pub username: String,
pub exp: usize,
}
pub struct AuthUser {
pub user: User,
}
impl FromRequestParts<Arc<AppState>> for AuthUser {
type Rejection = Response;
async fn from_request_parts(
parts: &mut Parts,
state: &Arc<AppState>,
) -> Result<Self, Self::Rejection> {
let headers = &parts.headers;
let token = extract_token_from_headers(headers)
.ok_or_else(|| (StatusCode::UNAUTHORIZED, "Missing authorization header").into_response())?;
let claims = verify_jwt(&token, &state.config.jwt_secret)
.map_err(|_| (StatusCode::UNAUTHORIZED, "Invalid token").into_response())?;
let user = state
.db
.get_user_by_id(claims.sub)
.await
.map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Database error").into_response())?
.ok_or_else(|| (StatusCode::UNAUTHORIZED, "User not found").into_response())?;
Ok(AuthUser { user })
}
}
pub fn create_jwt(user: &User, secret: &str) -> Result<String> {
let expiration = Utc::now()
.checked_add_signed(Duration::hours(24))
.expect("valid timestamp")
.timestamp();
let claims = Claims {
sub: user.id,
username: user.username.clone(),
exp: expiration as usize,
};
let token = encode(
&Header::default(),
&claims,
&EncodingKey::from_secret(secret.as_bytes()),
)?;
Ok(token)
}
pub fn verify_jwt(token: &str, secret: &str) -> Result<Claims> {
let token_data = decode::<Claims>(
token,
&DecodingKey::from_secret(secret.as_bytes()),
&Validation::default(),
)?;
Ok(token_data.claims)
}
fn extract_token_from_headers(headers: &HeaderMap) -> Option<String> {
let auth_header = headers.get("authorization")?;
let auth_str = auth_header.to_str().ok()?;
if auth_str.starts_with("Bearer ") {
Some(auth_str.trim_start_matches("Bearer ").to_string())
} else {
None
}
}