backend: working auth

This commit is contained in:
Sphereso 2024-07-08 12:55:11 +02:00
parent 8c3becf843
commit ab22d6d830
9 changed files with 172 additions and 10 deletions

View file

@ -1,26 +1,123 @@
use jsonwebtoken::{EncodingKey, Header, Validation};
use core::fmt;
use std::{pin::Pin, time::SystemTime};
use actix_web::{
error::ErrorUnauthorized,
http::{header, StatusCode},
web, Error, FromRequest, HttpRequest, HttpResponse, ResponseError,
};
use futures::Future;
use jsonwebtoken::{DecodingKey, EncodingKey, Header, Validation};
use migration::token;
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 fn verify(token: &str) {
let validation = Validation::new(jsonwebtoken::Algorithm::HS256);
// jsonwebtoken::decode(token, , validation)
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(AuthorizationError)?;
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))
})
}
}
#[derive(Debug)]
struct AuthorizationError;
impl fmt::Display for AuthorizationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.status_code(), f)
}
}
impl ResponseError for AuthorizationError {
fn status_code(&self) -> actix_web::http::StatusCode {
StatusCode::UNAUTHORIZED
}
fn error_response(&self) -> actix_web::HttpResponse<actix_web::body::BoxBody> {
HttpResponse::build(self.status_code()).finish()
}
}

View file

@ -3,6 +3,7 @@ use actix_web::{
web, HttpResponse, Responder,
};
use argon2::{Argon2, PasswordHash, PasswordVerifier};
use jsonwebtoken::EncodingKey;
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
use serde::Deserialize;
@ -38,7 +39,8 @@ impl AuthController {
.verify_password(login.password.as_bytes(), &parsed_hash)
.map_err(ErrorBadRequest)?;
let jwt = create_jwt(user, jwt_secret).map_err(ErrorInternalServerError)?;
let jwt = create_jwt(user, &EncodingKey::from_secret(jwt_secret.as_bytes()))
.map_err(ErrorInternalServerError)?;
Ok(HttpResponse::Ok().body(jwt))
}
}

View file

@ -7,7 +7,7 @@ use sea_orm::{ActiveModelTrait, ActiveValue, EntityTrait};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::AppState;
use crate::{auth::AuthedUser, AppState};
pub struct UserController;
@ -36,7 +36,10 @@ impl From<entity::user::Model> for UserWithoutPassword {
}
impl UserController {
pub async fn list_users(state: web::Data<AppState>) -> actix_web::Result<impl Responder> {
pub async fn list_users(
state: web::Data<AppState>,
_executor: AuthedUser,
) -> actix_web::Result<impl Responder> {
let db = &state.db;
let users = entity::prelude::User::find()
.all(db)

View file

@ -12,7 +12,7 @@ mod routes;
#[derive(Clone)]
struct AppState {
db: DatabaseConnection,
secret: EncodingKey,
secret: String,
}
#[actix_web::main]
@ -37,7 +37,7 @@ async fn main() -> std::io::Result<()> {
let state = AppState {
db: conn,
secret: EncodingKey::from_secret(jwt_secret.as_bytes()),
secret: jwt_secret,
};
println!("Listening for connections...");