mirror of
https://github.com/readur/readur.git
synced 2026-01-01 20:10:29 -06:00
91 lines
2.4 KiB
Rust
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
|
|
}
|
|
} |