improve error handling, cleanup, jwt
This commit is contained in:
parent
618d1c40d6
commit
443bfa29eb
52
Cargo.lock
generated
52
Cargo.lock
generated
@ -35,7 +35,7 @@ dependencies = [
|
|||||||
"brotli",
|
"brotli",
|
||||||
"bytes",
|
"bytes",
|
||||||
"bytestring",
|
"bytestring",
|
||||||
"derive_more",
|
"derive_more 0.99.19",
|
||||||
"encoding_rs",
|
"encoding_rs",
|
||||||
"flate2",
|
"flate2",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
@ -151,7 +151,7 @@ dependencies = [
|
|||||||
"bytestring",
|
"bytestring",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"cookie",
|
"cookie",
|
||||||
"derive_more",
|
"derive_more 0.99.19",
|
||||||
"encoding_rs",
|
"encoding_rs",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
@ -528,10 +528,13 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-web",
|
"actix-web",
|
||||||
"bcrypt",
|
"bcrypt",
|
||||||
|
"derive_more 2.0.1",
|
||||||
"dotenvy",
|
"dotenvy",
|
||||||
|
"hmac",
|
||||||
"jwt",
|
"jwt",
|
||||||
"sea-orm",
|
"sea-orm",
|
||||||
"serde",
|
"serde",
|
||||||
|
"sha2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -601,6 +604,15 @@ version = "0.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
|
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "convert_case"
|
||||||
|
version = "0.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-segmentation",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cookie"
|
name = "cookie"
|
||||||
version = "0.16.2"
|
version = "0.16.2"
|
||||||
@ -713,13 +725,35 @@ version = "0.99.19"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3da29a38df43d6f156149c9b43ded5e018ddff2a855cf2cfd62e8cd7d079c69f"
|
checksum = "3da29a38df43d6f156149c9b43ded5e018ddff2a855cf2cfd62e8cd7d079c69f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"convert_case",
|
"convert_case 0.4.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"rustc_version",
|
"rustc_version",
|
||||||
"syn 2.0.98",
|
"syn 2.0.98",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "derive_more"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678"
|
||||||
|
dependencies = [
|
||||||
|
"derive_more-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "derive_more-impl"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3"
|
||||||
|
dependencies = [
|
||||||
|
"convert_case 0.7.1",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.98",
|
||||||
|
"unicode-xid",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "digest"
|
name = "digest"
|
||||||
version = "0.10.7"
|
version = "0.10.7"
|
||||||
@ -2826,6 +2860,18 @@ version = "0.1.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0"
|
checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-segmentation"
|
||||||
|
version = "1.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-xid"
|
||||||
|
version = "0.2.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "url"
|
name = "url"
|
||||||
version = "2.5.4"
|
version = "2.5.4"
|
||||||
|
|||||||
@ -14,3 +14,6 @@ dotenvy = "0.15"
|
|||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
bcrypt = "0.17"
|
bcrypt = "0.17"
|
||||||
jwt = "0.16"
|
jwt = "0.16"
|
||||||
|
hmac = "0.12"
|
||||||
|
sha2 = "0.10"
|
||||||
|
derive_more = { version = "2.0", features = ["full"] }
|
||||||
|
|||||||
4
src/config.rs
Normal file
4
src/config.rs
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct AppConfig {
|
||||||
|
pub jwt_token: String,
|
||||||
|
}
|
||||||
@ -1,12 +1,11 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
endpoints::prelude::*,
|
endpoints::prelude::*,
|
||||||
entities::{prelude::User, user},
|
entities::{prelude::User, user},
|
||||||
models::{error::ErrorResponse, user::UserModel},
|
|
||||||
state::AppState,
|
state::AppState,
|
||||||
utils::PasswordHasher,
|
utils::{errors::GenericError, hasher, jwt},
|
||||||
};
|
};
|
||||||
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
|
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
|
||||||
use serde::Deserialize;
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct LoginRequest {
|
struct LoginRequest {
|
||||||
@ -14,23 +13,32 @@ struct LoginRequest {
|
|||||||
password: String,
|
password: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct LoginResponse {
|
||||||
|
token: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[post("/login")]
|
#[post("/login")]
|
||||||
pub async fn handler(body: Json<LoginRequest>, state: Data<AppState>) -> impl Responder {
|
pub async fn handler(
|
||||||
|
body: Json<LoginRequest>,
|
||||||
|
state: Data<AppState>,
|
||||||
|
) -> Result<impl Responder, GenericError> {
|
||||||
let user = User::find()
|
let user = User::find()
|
||||||
.filter(user::Column::Email.eq(body.email.clone()))
|
.filter(user::Column::Email.eq(body.email.clone()))
|
||||||
.one(&state.db)
|
.one(&state.db)
|
||||||
.await
|
.await
|
||||||
.expect("database error");
|
.map_err(|e| GenericError::new(e.to_string().as_str()))?;
|
||||||
|
|
||||||
if user.is_none() {
|
if user.is_none() || !hasher::verify(&body.password, &user.as_ref().unwrap().password) {
|
||||||
return HttpResponse::Unauthorized().json(ErrorResponse::new(401, "invalid email"));
|
return Err(GenericError::new("invalid credentials"));
|
||||||
}
|
}
|
||||||
|
|
||||||
let user = user.unwrap();
|
let user = user.unwrap();
|
||||||
|
|
||||||
if !PasswordHasher::verify(&body.password, &user.password) {
|
let token = jwt::sign(
|
||||||
return HttpResponse::Unauthorized().json(ErrorResponse::new(401, "invalid password"));
|
user.id.to_string().as_str(),
|
||||||
}
|
state.config.jwt_token.as_str(),
|
||||||
|
)?;
|
||||||
|
|
||||||
HttpResponse::Ok().json(UserModel::from(user))
|
Ok(Json(LoginResponse { token }))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,12 @@
|
|||||||
|
use actix_web::web::ServiceConfig;
|
||||||
|
|
||||||
pub mod auth;
|
pub mod auth;
|
||||||
pub mod misc;
|
pub mod misc;
|
||||||
mod prelude;
|
mod prelude;
|
||||||
pub mod users;
|
pub mod users;
|
||||||
|
|
||||||
|
pub fn register(cfg: &mut ServiceConfig) {
|
||||||
|
cfg.service(auth::scope());
|
||||||
|
cfg.service(misc::scope());
|
||||||
|
cfg.service(users::scope());
|
||||||
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
pub use actix_web::{
|
pub use actix_web::{
|
||||||
HttpResponse, Responder, get, post,
|
Responder, get, post,
|
||||||
web::{Data, Json},
|
web::{Data, Json},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,11 +2,15 @@ use sea_orm::EntityTrait;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
endpoints::prelude::*, entities::prelude::User, models::user::UserModel, state::AppState,
|
endpoints::prelude::*, entities::prelude::User, models::user::UserModel, state::AppState,
|
||||||
|
utils::errors::GenericError,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
pub async fn handler(state: Data<AppState>) -> impl Responder {
|
pub async fn handler(state: Data<AppState>) -> Result<impl Responder, GenericError> {
|
||||||
let users = User::find().all(&state.db).await.expect("database error");
|
let users = User::find()
|
||||||
|
.all(&state.db)
|
||||||
|
.await
|
||||||
|
.map_err(|e| GenericError::new(e.to_string().as_str()))?;
|
||||||
|
|
||||||
HttpResponse::Ok().json(UserModel::from_many(users))
|
Ok(Json(UserModel::from_many(users)))
|
||||||
}
|
}
|
||||||
|
|||||||
14
src/main.rs
14
src/main.rs
@ -1,3 +1,4 @@
|
|||||||
|
pub mod config;
|
||||||
pub mod endpoints;
|
pub mod endpoints;
|
||||||
pub mod entities;
|
pub mod entities;
|
||||||
pub mod models;
|
pub mod models;
|
||||||
@ -7,6 +8,7 @@ pub mod utils;
|
|||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
use actix_web::{App, HttpServer, web};
|
use actix_web::{App, HttpServer, web};
|
||||||
|
use config::AppConfig;
|
||||||
use sea_orm::Database;
|
use sea_orm::Database;
|
||||||
use state::AppState;
|
use state::AppState;
|
||||||
|
|
||||||
@ -29,18 +31,18 @@ async fn main() -> Result<(), std::io::Error> {
|
|||||||
|
|
||||||
// TODO: migrations
|
// TODO: migrations
|
||||||
|
|
||||||
let state = AppState { db };
|
let config = AppConfig {
|
||||||
|
jwt_token: env::var("JWT_SECRET").expect("JWT_SECRET is required!"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let state = AppState { db, config };
|
||||||
|
|
||||||
println!("starting a server at {}:{}", host, port);
|
println!("starting a server at {}:{}", host, port);
|
||||||
|
|
||||||
let app_factory = move || {
|
let app_factory = move || {
|
||||||
App::new()
|
App::new()
|
||||||
.app_data(web::Data::new(state.clone()))
|
.app_data(web::Data::new(state.clone()))
|
||||||
.configure(|cfg| {
|
.configure(|cfg| endpoints::register(cfg))
|
||||||
cfg.service(endpoints::auth::scope());
|
|
||||||
cfg.service(endpoints::misc::scope());
|
|
||||||
cfg.service(endpoints::users::scope());
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
HttpServer::new(app_factory)
|
HttpServer::new(app_factory)
|
||||||
|
|||||||
@ -1,20 +0,0 @@
|
|||||||
use serde::Serialize;
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
pub struct ErrorResponse {
|
|
||||||
pub status_code: i16,
|
|
||||||
pub message: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ErrorResponse {
|
|
||||||
pub fn server_error(message: &str) -> Self {
|
|
||||||
Self::new(500, message)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new(status_code: i16, message: &str) -> Self {
|
|
||||||
Self {
|
|
||||||
status_code,
|
|
||||||
message: String::from(message),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,2 +1 @@
|
|||||||
pub mod error;
|
|
||||||
pub mod user;
|
pub mod user;
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
use sea_orm::DatabaseConnection;
|
use sea_orm::DatabaseConnection;
|
||||||
|
|
||||||
|
use crate::config::AppConfig;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
pub db: DatabaseConnection,
|
pub db: DatabaseConnection,
|
||||||
|
pub config: AppConfig,
|
||||||
}
|
}
|
||||||
|
|||||||
23
src/utils/errors.rs
Normal file
23
src/utils/errors.rs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
use actix_web::{HttpResponse, error};
|
||||||
|
use derive_more::derive::{Display, Error};
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
#[derive(Debug, Display, Serialize, Error)]
|
||||||
|
#[display("GenericError: {error}")]
|
||||||
|
pub struct GenericError {
|
||||||
|
pub error: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GenericError {
|
||||||
|
pub fn new(message: &str) -> Self {
|
||||||
|
GenericError {
|
||||||
|
error: String::from(message),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl error::ResponseError for GenericError {
|
||||||
|
fn error_response(&self) -> actix_web::HttpResponse<actix_web::body::BoxBody> {
|
||||||
|
HttpResponse::build(self.status_code()).json(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,14 +1,12 @@
|
|||||||
pub struct PasswordHasher {}
|
pub struct PasswordHasher {}
|
||||||
|
|
||||||
impl PasswordHasher {
|
// DO NOT CHANGE THIS VALUE OR HELL WILL RISE
|
||||||
// DO NOT CHANGE THIS VALUE OR HELL WILL RISE
|
pub const BCRYPT_PASSWORD_COST: u32 = 13;
|
||||||
pub const BCRYPT_PASSWORD_COST: u32 = 13;
|
|
||||||
|
|
||||||
pub fn hash(password: &str) -> Option<String> {
|
pub fn hash(password: &str) -> Option<String> {
|
||||||
bcrypt::hash(password, Self::BCRYPT_PASSWORD_COST).ok()
|
bcrypt::hash(password, BCRYPT_PASSWORD_COST).ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn verify(password: &str, hash: &str) -> bool {
|
pub fn verify(password: &str, hash: &str) -> bool {
|
||||||
bcrypt::verify(password, hash).is_ok_and(|v| v)
|
bcrypt::verify(password, hash).is_ok_and(|v| v)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
18
src/utils/jwt.rs
Normal file
18
src/utils/jwt.rs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
use hmac::{Hmac, Mac};
|
||||||
|
use jwt::SignWithKey;
|
||||||
|
use sha2::Sha384;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
use super::errors::GenericError;
|
||||||
|
|
||||||
|
pub fn sign(user_id: &str, jwt_secret: &str) -> Result<String, GenericError> {
|
||||||
|
let key: Hmac<Sha384> =
|
||||||
|
Hmac::new_from_slice(jwt_secret.as_bytes()).map_err(|e| GenericError::new(&e.to_string()))?;
|
||||||
|
|
||||||
|
let mut claims = BTreeMap::new();
|
||||||
|
claims.insert("sub", user_id);
|
||||||
|
|
||||||
|
claims
|
||||||
|
.sign_with_key(&key)
|
||||||
|
.map_err(|e| GenericError::new(&e.to_string()))
|
||||||
|
}
|
||||||
@ -1,3 +1,3 @@
|
|||||||
mod hasher;
|
pub mod errors;
|
||||||
|
pub mod hasher;
|
||||||
pub use hasher::PasswordHasher;
|
pub mod jwt;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user