Полностью рабочая авторизация

This commit is contained in:
2025-03-22 22:44:52 +04:00
parent 9f7460973e
commit ba86dfc3fe
9 changed files with 241 additions and 21 deletions

34
src/database/driver.rs Normal file
View File

@@ -0,0 +1,34 @@
pub mod users {
use crate::database::models::User;
use crate::database::schema::fcm::user_id;
use crate::database::schema::users::dsl::users;
use crate::database::schema::users::dsl::*;
use diesel::{ExpressionMethods, QueryResult};
use diesel::{PgConnection, SelectableHelper};
use diesel::{QueryDsl, RunQueryDsl};
use std::ops::DerefMut;
use std::sync::Mutex;
pub fn get(connection: &Mutex<PgConnection>, _id: String) -> QueryResult<User> {
let mut lock = connection.lock().unwrap();
let con = lock.deref_mut();
users
.filter(id.eq(_id))
.select(User::as_select())
.first(con)
}
pub fn get_by_username(
connection: &Mutex<PgConnection>,
_username: String,
) -> QueryResult<User> {
let mut lock = connection.lock().unwrap();
let con = lock.deref_mut();
users
.filter(username.eq(_username))
.select(User::as_select())
.first(con)
}
}

View File

@@ -1,2 +1,3 @@
pub mod schema;
pub mod models;
pub mod driver;

View File

@@ -1,7 +1,8 @@
use diesel::prelude::*;
use diesel::{AsExpression, FromSqlRow};
use serde::Serialize;
#[derive(diesel_derive_enum::DbEnum, Serialize, Debug)]
#[derive(diesel_derive_enum::DbEnum, Serialize, Debug, Clone, Copy, PartialEq)]
#[ExistingTypePath = "crate::database::schema::sql_types::UserRole"]
#[DbValueStyle = "UPPERCASE"]
#[serde(rename_all = "UPPERCASE")]
@@ -11,9 +12,9 @@ pub enum UserRole {
Admin,
}
#[derive(Queryable, Selectable, Serialize)]
#[derive(Identifiable, AsChangeset, Queryable, Selectable, Serialize)]
#[diesel(table_name = crate::database::schema::users)]
#[diesel(check_for_backend(diesel::pg::Pg))]
#[changeset_options(treat_none_as_null = "true")]
pub struct User {
pub id: String,
pub username: String,

View File

@@ -1,16 +1,17 @@
use crate::routes::auth::sign_in::sign_in;
use crate::xls_downloader::basic_impl::BasicXlsDownloader;
use actix_web::{web, App, HttpServer};
use actix_web::{App, HttpServer, web};
use chrono::{DateTime, Utc};
use diesel::{Connection, PgConnection};
use dotenvy::dotenv;
use schedule_parser::schema::ScheduleEntity;
use std::collections::HashMap;
use std::sync::Mutex;
use std::env;
use std::sync::{Mutex, MutexGuard};
mod database;
mod routes;
mod utility;
mod xls_downloader;
pub struct AppState {
@@ -28,6 +29,12 @@ pub struct AppState {
database: Mutex<PgConnection>,
}
impl AppState {
pub fn connection(&self) -> MutexGuard<PgConnection> {
self.database.lock().unwrap()
}
}
#[actix_web::main]
async fn main() {
dotenv().ok();

View File

@@ -1,26 +1,34 @@
use crate::database::driver;
use crate::database::models::User;
use crate::routes::auth::schema::SignInErrCode::IncorrectCredentials;
use crate::routes::auth::schema::{SignInDto, SignInResult};
use crate::AppState;
use crate::{AppState, utility};
use actix_web::{post, web};
use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl, SelectableHelper};
use diesel::SaveChangesDsl;
use std::ops::DerefMut;
use web::Json;
#[post("/sign-in")]
pub async fn sign_in(data: Json<SignInDto>, app_state: web::Data<AppState>) -> Json<SignInResult> {
use crate::database::schema::users::dsl::*;
let result = match driver::users::get_by_username(&app_state.database, data.username.clone()) {
Ok(mut user) => match bcrypt::verify(&data.password, &user.password) {
Ok(true) => {
let mut lock = app_state.connection();
let conn = lock.deref_mut();
match {
let mut lock = app_state.database.lock().unwrap();
let connection = lock.deref_mut();
user.access_token =
utility::jwt::encode(&user.id).expect("Failed to generate jet token");
users
.filter(username.eq(data.username.clone()))
.select(User::as_select())
.first(connection)
} {
Ok(user) => Json(SignInResult::ok(&user)),
Err(_) => Json(SignInResult::err(IncorrectCredentials)),
}
user.save_changes::<User>(conn)
.expect("Failed to update user");
SignInResult::ok(&user)
}
Ok(false) | Err(_) => SignInResult::err(IncorrectCredentials),
},
Err(_) => SignInResult::err(IncorrectCredentials),
};
Json(result)
}

76
src/utility/jwt.rs Normal file
View File

@@ -0,0 +1,76 @@
use chrono::{DateTime, Duration, Utc};
use hmac::{Hmac, Mac};
use jwt::{SignWithKey, Token, VerifyWithKey};
use sha2::Sha256;
use std::collections::BTreeMap;
use std::env;
use std::sync::LazyLock;
static JWT_SECRET: LazyLock<Hmac<Sha256>> = LazyLock::new(|| {
let secret = env::var("JWT_SECRET").expect("JWT_SECRET must be set");
Hmac::new_from_slice(secret.as_bytes()).expect("Hmac::new_from_slice failed")
});
#[derive(Debug)]
pub enum VerifyError {
JwtError(jwt::Error),
InvalidSignature,
NoExpirationTag,
Expired,
NoId,
}
pub fn verify_and_decode(token: &String) -> Result<String, VerifyError> {
let jwt = &*JWT_SECRET;
let result: Result<BTreeMap<String, String>, jwt::Error> = token.verify_with_key(jwt);
match result {
Ok(claims) => match claims.get("exp") {
None => Err(VerifyError::NoExpirationTag),
Some(exp) => {
let exp_date = DateTime::from_timestamp(exp.parse::<i64>().unwrap(), 0)
.expect("Failed to parse expiration time");
if Utc::now() > exp_date {
return Err(VerifyError::Expired);
}
match claims.get("id").cloned() {
None => Err(VerifyError::NoId),
Some(id) => Ok(id),
}
}
},
Err(err) => Err(match err {
jwt::Error::InvalidSignature => VerifyError::InvalidSignature,
_ => VerifyError::JwtError(err),
}),
}
}
pub fn encode(id: &String) -> Result<String, jwt::Error> {
let header = jwt::Header {
type_: Some(jwt::header::HeaderType::JsonWebToken),
..Default::default()
};
let mut claims = BTreeMap::new();
let iat = Utc::now();
let exp = iat + Duration::days(365 * 4);
let iat_str = iat.timestamp().to_string();
let exp_str = exp.timestamp().to_string();
claims.insert("id", id.as_str());
claims.insert("iat", iat_str.as_str());
claims.insert("exp", exp_str.as_str());
match Token::new(header, claims).sign_with_key(&*JWT_SECRET) {
Ok(token) => Ok(token.as_str().to_string()),
Err(err) => Err(err),
}
}

1
src/utility/mod.rs Normal file
View File

@@ -0,0 +1 @@
pub mod jwt;