mirror of
https://github.com/n08i40k/schedule-parser-rusted.git
synced 2025-12-06 09:47:50 +03:00
Полностью рабочая авторизация
This commit is contained in:
92
Cargo.lock
generated
92
Cargo.lock
generated
@@ -29,7 +29,7 @@ dependencies = [
|
|||||||
"actix-rt",
|
"actix-rt",
|
||||||
"actix-service",
|
"actix-service",
|
||||||
"actix-utils",
|
"actix-utils",
|
||||||
"base64",
|
"base64 0.22.1",
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"brotli",
|
"brotli",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -287,12 +287,31 @@ dependencies = [
|
|||||||
"windows-targets 0.52.6",
|
"windows-targets 0.52.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64"
|
||||||
|
version = "0.13.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.22.1"
|
version = "0.22.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bcrypt"
|
||||||
|
version = "0.17.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "92758ad6077e4c76a6cadbce5005f666df70d4f13b19976b1a8062eef880040f"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.22.1",
|
||||||
|
"blowfish",
|
||||||
|
"getrandom 0.3.2",
|
||||||
|
"subtle",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.9.0"
|
version = "2.9.0"
|
||||||
@@ -308,6 +327,16 @@ dependencies = [
|
|||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "blowfish"
|
||||||
|
version = "0.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
"cipher",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "brotli"
|
name = "brotli"
|
||||||
version = "7.0.0"
|
version = "7.0.0"
|
||||||
@@ -436,6 +465,16 @@ dependencies = [
|
|||||||
"half",
|
"half",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cipher"
|
||||||
|
version = "0.4.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
|
||||||
|
dependencies = [
|
||||||
|
"crypto-common",
|
||||||
|
"inout",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.5.32"
|
version = "4.5.32"
|
||||||
@@ -722,6 +761,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"block-buffer",
|
"block-buffer",
|
||||||
"crypto-common",
|
"crypto-common",
|
||||||
|
"subtle",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -991,6 +1031,15 @@ version = "0.5.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e"
|
checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hmac"
|
||||||
|
version = "0.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
|
||||||
|
dependencies = [
|
||||||
|
"digest",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http"
|
name = "http"
|
||||||
version = "0.2.12"
|
version = "0.2.12"
|
||||||
@@ -1304,6 +1353,15 @@ dependencies = [
|
|||||||
"hashbrown",
|
"hashbrown",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "inout"
|
||||||
|
version = "0.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ipnet"
|
name = "ipnet"
|
||||||
version = "2.11.0"
|
version = "2.11.0"
|
||||||
@@ -1355,6 +1413,21 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jwt"
|
||||||
|
version = "0.16.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6204285f77fe7d9784db3fdc449ecce1a0114927a51d5a41c4c7a292011c015f"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.13.1",
|
||||||
|
"crypto-common",
|
||||||
|
"digest",
|
||||||
|
"hmac",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"sha2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "language-tags"
|
name = "language-tags"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
@@ -1782,7 +1855,7 @@ version = "0.12.15"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb"
|
checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64 0.22.1",
|
||||||
"bytes",
|
"bytes",
|
||||||
"encoding_rs",
|
"encoding_rs",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
@@ -1927,13 +2000,17 @@ name = "schedule-parser-rusted"
|
|||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-web",
|
"actix-web",
|
||||||
|
"bcrypt",
|
||||||
"chrono",
|
"chrono",
|
||||||
"diesel",
|
"diesel",
|
||||||
"diesel-derive-enum",
|
"diesel-derive-enum",
|
||||||
"dotenvy",
|
"dotenvy",
|
||||||
|
"hmac",
|
||||||
|
"jwt",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"schedule_parser",
|
"schedule_parser",
|
||||||
"serde",
|
"serde",
|
||||||
|
"sha2",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -2045,6 +2122,17 @@ dependencies = [
|
|||||||
"digest",
|
"digest",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sha2"
|
||||||
|
version = "0.10.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cpufeatures",
|
||||||
|
"digest",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "shlex"
|
name = "shlex"
|
||||||
version = "1.3.0"
|
version = "1.3.0"
|
||||||
|
|||||||
@@ -8,6 +8,10 @@ edition = "2024"
|
|||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
bcrypt = "0.17.0"
|
||||||
|
jwt = "0.16.0"
|
||||||
|
hmac = "0.12.1"
|
||||||
|
sha2 = "0.10.8"
|
||||||
diesel = { version = "2.2.8", features = ["postgres"] }
|
diesel = { version = "2.2.8", features = ["postgres"] }
|
||||||
diesel-derive-enum = { git = "https://github.com/Havunen/diesel-derive-enum.git", features = ["postgres"] }
|
diesel-derive-enum = { git = "https://github.com/Havunen/diesel-derive-enum.git", features = ["postgres"] }
|
||||||
dotenvy = "0.15.7"
|
dotenvy = "0.15.7"
|
||||||
|
|||||||
34
src/database/driver.rs
Normal file
34
src/database/driver.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
pub mod schema;
|
pub mod schema;
|
||||||
pub mod models;
|
pub mod models;
|
||||||
|
pub mod driver;
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
|
use diesel::{AsExpression, FromSqlRow};
|
||||||
use serde::Serialize;
|
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"]
|
#[ExistingTypePath = "crate::database::schema::sql_types::UserRole"]
|
||||||
#[DbValueStyle = "UPPERCASE"]
|
#[DbValueStyle = "UPPERCASE"]
|
||||||
#[serde(rename_all = "UPPERCASE")]
|
#[serde(rename_all = "UPPERCASE")]
|
||||||
@@ -11,9 +12,9 @@ pub enum UserRole {
|
|||||||
Admin,
|
Admin,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Queryable, Selectable, Serialize)]
|
#[derive(Identifiable, AsChangeset, Queryable, Selectable, Serialize)]
|
||||||
#[diesel(table_name = crate::database::schema::users)]
|
#[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 struct User {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub username: String,
|
pub username: String,
|
||||||
|
|||||||
11
src/main.rs
11
src/main.rs
@@ -1,16 +1,17 @@
|
|||||||
use crate::routes::auth::sign_in::sign_in;
|
use crate::routes::auth::sign_in::sign_in;
|
||||||
use crate::xls_downloader::basic_impl::BasicXlsDownloader;
|
use crate::xls_downloader::basic_impl::BasicXlsDownloader;
|
||||||
use actix_web::{web, App, HttpServer};
|
use actix_web::{App, HttpServer, web};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use diesel::{Connection, PgConnection};
|
use diesel::{Connection, PgConnection};
|
||||||
use dotenvy::dotenv;
|
use dotenvy::dotenv;
|
||||||
use schedule_parser::schema::ScheduleEntity;
|
use schedule_parser::schema::ScheduleEntity;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Mutex;
|
|
||||||
use std::env;
|
use std::env;
|
||||||
|
use std::sync::{Mutex, MutexGuard};
|
||||||
|
|
||||||
mod database;
|
mod database;
|
||||||
mod routes;
|
mod routes;
|
||||||
|
mod utility;
|
||||||
mod xls_downloader;
|
mod xls_downloader;
|
||||||
|
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
@@ -28,6 +29,12 @@ pub struct AppState {
|
|||||||
database: Mutex<PgConnection>,
|
database: Mutex<PgConnection>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AppState {
|
||||||
|
pub fn connection(&self) -> MutexGuard<PgConnection> {
|
||||||
|
self.database.lock().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[actix_web::main]
|
#[actix_web::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
dotenv().ok();
|
dotenv().ok();
|
||||||
|
|||||||
@@ -1,26 +1,34 @@
|
|||||||
|
use crate::database::driver;
|
||||||
use crate::database::models::User;
|
use crate::database::models::User;
|
||||||
use crate::routes::auth::schema::SignInErrCode::IncorrectCredentials;
|
use crate::routes::auth::schema::SignInErrCode::IncorrectCredentials;
|
||||||
use crate::routes::auth::schema::{SignInDto, SignInResult};
|
use crate::routes::auth::schema::{SignInDto, SignInResult};
|
||||||
use crate::AppState;
|
use crate::{AppState, utility};
|
||||||
use actix_web::{post, web};
|
use actix_web::{post, web};
|
||||||
use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl, SelectableHelper};
|
use diesel::SaveChangesDsl;
|
||||||
use std::ops::DerefMut;
|
use std::ops::DerefMut;
|
||||||
use web::Json;
|
use web::Json;
|
||||||
|
|
||||||
#[post("/sign-in")]
|
#[post("/sign-in")]
|
||||||
pub async fn sign_in(data: Json<SignInDto>, app_state: web::Data<AppState>) -> Json<SignInResult> {
|
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 {
|
user.access_token =
|
||||||
let mut lock = app_state.database.lock().unwrap();
|
utility::jwt::encode(&user.id).expect("Failed to generate jet token");
|
||||||
let connection = lock.deref_mut();
|
|
||||||
|
|
||||||
users
|
user.save_changes::<User>(conn)
|
||||||
.filter(username.eq(data.username.clone()))
|
.expect("Failed to update user");
|
||||||
.select(User::as_select())
|
|
||||||
.first(connection)
|
SignInResult::ok(&user)
|
||||||
} {
|
|
||||||
Ok(user) => Json(SignInResult::ok(&user)),
|
|
||||||
Err(_) => Json(SignInResult::err(IncorrectCredentials)),
|
|
||||||
}
|
}
|
||||||
|
Ok(false) | Err(_) => SignInResult::err(IncorrectCredentials),
|
||||||
|
},
|
||||||
|
|
||||||
|
Err(_) => SignInResult::err(IncorrectCredentials),
|
||||||
|
};
|
||||||
|
|
||||||
|
Json(result)
|
||||||
}
|
}
|
||||||
|
|||||||
76
src/utility/jwt.rs
Normal file
76
src/utility/jwt.rs
Normal 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
1
src/utility/mod.rs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pub mod jwt;
|
||||||
Reference in New Issue
Block a user