Alisa/crates/backend/src/auth.rs
2024-07-09 10:00:31 +02:00

98 lines
2.7 KiB
Rust

use std::{pin::Pin, time::SystemTime};
use actix_web::{error::ErrorUnauthorized, http::header, web, Error, FromRequest, HttpRequest};
use futures::Future;
use jsonwebtoken::{DecodingKey, EncodingKey, Header};
use sea_orm::EntityTrait;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::AppState;
#[derive(Deserialize, Serialize)]
struct Claims {
sub: Uuid,
name: String,
exp: u64,
}
pub fn create_jwt(
user: entity::user::Model,
key: &EncodingKey,
) -> Result<String, jsonwebtoken::errors::Error> {
let time = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.expect("SystemTime before UNIX EPOCH")
.as_secs()
+ 86400;
let claims = Claims {
sub: user.id,
name: user.name,
exp: time,
};
jsonwebtoken::encode(&Header::default(), &claims, key)
}
pub struct AuthedUser(entity::user::Model);
impl AuthedUser {
fn parse_token(req: &HttpRequest) -> Result<Claims, Error> {
let header = req
.headers()
.get(header::AUTHORIZATION)
.ok_or(ErrorUnauthorized("Unauthorized"))?;
if header.len() < 8 {
return Err(ErrorUnauthorized("Unauthorized"));
}
let mut parts = header
.to_str()
.map_err(|_| ErrorUnauthorized("Unauthorized"))?
.splitn(2, ' ');
match parts.next() {
Some("Bearer") => {}
_ => return Err(ErrorUnauthorized("Unauthorized")),
}
let token = parts.next().ok_or(ErrorUnauthorized("Unauthorized"))?;
let key = &req.app_data::<web::Data<AppState>>().unwrap().secret;
let validation = jsonwebtoken::Validation::new(jsonwebtoken::Algorithm::HS256);
let claims = jsonwebtoken::decode::<Claims>(
token,
&DecodingKey::from_secret(key.as_bytes()),
&validation,
)
.map_err(|e| ErrorUnauthorized(e))?;
Ok(claims.claims)
}
}
impl FromRequest for AuthedUser {
type Error = actix_web::Error;
type Future = Pin<Box<dyn Future<Output = Result<Self, Self::Error>>>>;
fn from_request(
req: &actix_web::HttpRequest,
_payload: &mut actix_web::dev::Payload,
) -> Self::Future {
let res = AuthedUser::parse_token(req);
let state = req.app_data::<web::Data<AppState>>().unwrap().db.clone();
Box::pin(async move {
let claims = res?;
let id = claims.sub;
let user = entity::prelude::User::find_by_id(id)
.one(&state)
.await
.map_err(|_| ErrorUnauthorized("Unauthorized"))?
.ok_or(ErrorUnauthorized("Unauthorized"))?;
Ok(AuthedUser(user))
})
}
}