diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 47a5f8e..15f10fa 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -24,7 +24,7 @@ jobs:
- name: Create .env.test
run: touch .env.test
- name: Run tests
- run: cargo test -p schedule-parser-rusted -p schedule_parser
+ run: cargo test
env:
DATABASE_URL: ${{ secrets.TEST_DATABASE_URL }}
JWT_SECRET: "test-secret-at-least-256-bits-used"
\ No newline at end of file
diff --git a/.idea/discord.xml b/.idea/discord.xml
new file mode 100644
index 0000000..912db82
--- /dev/null
+++ b/.idea/discord.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/schedule-parser-rusted.iml b/.idea/schedule-parser-rusted.iml
index 38f6e7d..eff20f9 100644
--- a/.idea/schedule-parser-rusted.iml
+++ b/.idea/schedule-parser-rusted.iml
@@ -2,9 +2,9 @@
-
+
diff --git a/Cargo.lock b/Cargo.lock
index e0b3b06..19250d8 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -29,7 +29,7 @@ dependencies = [
"actix-rt",
"actix-service",
"actix-utils",
- "base64 0.22.1",
+ "base64",
"bitflags",
"brotli",
"bytes",
@@ -287,12 +287,6 @@ dependencies = [
"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]]
name = "base64"
version = "0.22.1"
@@ -305,7 +299,7 @@ version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92758ad6077e4c76a6cadbce5005f666df70d4f13b19976b1a8062eef880040f"
dependencies = [
- "base64 0.22.1",
+ "base64",
"blowfish",
"getrandom 0.3.2",
"subtle",
@@ -327,6 +321,15 @@ dependencies = [
"generic-array",
]
+[[package]]
+name = "block-buffer"
+version = "0.11.0-rc.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a229bfd78e4827c91b9b95784f69492c1b77c1ab75a45a8a037b139215086f94"
+dependencies = [
+ "hybrid-array",
+]
+
[[package]]
name = "blowfish"
version = "0.9.1"
@@ -477,7 +480,7 @@ version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
- "crypto-common",
+ "crypto-common 0.1.6",
"inout",
]
@@ -515,6 +518,12 @@ dependencies = [
"encoding_rs",
]
+[[package]]
+name = "const-oid"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cb3c4a0d3776f7535c32793be81d6d5fec0d48ac70955d9834e643aa249a52f"
+
[[package]]
name = "cookie"
version = "0.16.2"
@@ -637,6 +646,15 @@ dependencies = [
"typenum",
]
+[[package]]
+name = "crypto-common"
+version = "0.2.0-rc.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "170d71b5b14dec99db7739f6fc7d6ec2db80b78c3acb77db48392ccc3d8a9ea0"
+dependencies = [
+ "hybrid-array",
+]
+
[[package]]
name = "darling"
version = "0.20.10"
@@ -679,6 +697,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e"
dependencies = [
"powerfmt",
+ "serde",
]
[[package]]
@@ -765,9 +784,19 @@ version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
- "block-buffer",
- "crypto-common",
- "subtle",
+ "block-buffer 0.10.4",
+ "crypto-common 0.1.6",
+]
+
+[[package]]
+name = "digest"
+version = "0.11.0-pre.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c478574b20020306f98d61c8ca3322d762e1ff08117422ac6106438605ea516"
+dependencies = [
+ "block-buffer 0.11.0-rc.4",
+ "const-oid",
+ "crypto-common 0.2.0-rc.2",
]
[[package]]
@@ -961,8 +990,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
+ "js-sys",
"libc",
"wasi 0.11.0+wasi-snapshot-preview1",
+ "wasm-bindgen",
]
[[package]]
@@ -995,7 +1026,7 @@ dependencies = [
"futures-sink",
"futures-util",
"http 0.2.12",
- "indexmap",
+ "indexmap 2.8.0",
"slab",
"tokio",
"tokio-util",
@@ -1014,7 +1045,7 @@ dependencies = [
"futures-core",
"futures-sink",
"http 1.3.1",
- "indexmap",
+ "indexmap 2.8.0",
"slab",
"tokio",
"tokio-util",
@@ -1031,6 +1062,12 @@ dependencies = [
"crunchy",
]
+[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+
[[package]]
name = "hashbrown"
version = "0.15.2"
@@ -1050,13 +1087,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e"
[[package]]
-name = "hmac"
-version = "0.12.1"
+name = "hex"
+version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
-dependencies = [
- "digest",
-]
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "hostname"
@@ -1125,6 +1159,15 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
+[[package]]
+name = "hybrid-array"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4dab50e193aebe510fe0e40230145820e02f48dae0cf339ea4204e6e708ff7bd"
+dependencies = [
+ "typenum",
+]
+
[[package]]
name = "hyper"
version = "1.6.0"
@@ -1371,6 +1414,17 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2"
+[[package]]
+name = "indexmap"
+version = "1.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
+dependencies = [
+ "autocfg",
+ "hashbrown 0.12.3",
+ "serde",
+]
+
[[package]]
name = "indexmap"
version = "2.8.0"
@@ -1378,7 +1432,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058"
dependencies = [
"equivalent",
- "hashbrown",
+ "hashbrown 0.15.2",
+ "serde",
]
[[package]]
@@ -1442,18 +1497,18 @@ dependencies = [
]
[[package]]
-name = "jwt"
-version = "0.16.0"
+name = "jsonwebtoken"
+version = "9.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6204285f77fe7d9784db3fdc449ecce1a0114927a51d5a41c4c7a292011c015f"
+checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde"
dependencies = [
- "base64 0.13.1",
- "crypto-common",
- "digest",
- "hmac",
+ "base64",
+ "js-sys",
+ "pem",
+ "ring",
"serde",
"serde_json",
- "sha2",
+ "simple_asn1",
]
[[package]]
@@ -1575,12 +1630,31 @@ dependencies = [
"tempfile",
]
+[[package]]
+name = "num-bigint"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
+dependencies = [
+ "num-integer",
+ "num-traits",
+]
+
[[package]]
name = "num-conv"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
+[[package]]
+name = "num-integer"
+version = "0.1.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
+dependencies = [
+ "num-traits",
+]
+
[[package]]
name = "num-traits"
version = "0.2.19"
@@ -1694,6 +1768,16 @@ dependencies = [
"windows-targets 0.52.6",
]
+[[package]]
+name = "pem"
+version = "3.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3"
+dependencies = [
+ "base64",
+ "serde",
+]
+
[[package]]
name = "percent-encoding"
version = "2.3.1"
@@ -1958,7 +2042,7 @@ version = "0.12.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb"
dependencies = [
- "base64 0.22.1",
+ "base64",
"bytes",
"encoding_rs",
"futures-core",
@@ -2119,38 +2203,30 @@ dependencies = [
[[package]]
name = "schedule-parser-rusted"
-version = "0.3.0"
+version = "0.4.0"
dependencies = [
"actix-http",
"actix-web",
"bcrypt",
- "chrono",
- "diesel",
- "diesel-derive-enum",
- "dotenvy",
- "hmac",
- "jwt",
- "mime",
- "objectid",
- "reqwest",
- "schedule_parser",
- "serde",
- "serde_json",
- "sha2",
- "tokio",
-]
-
-[[package]]
-name = "schedule_parser"
-version = "0.2.0"
-dependencies = [
"calamine",
"chrono",
"criterion",
+ "diesel",
+ "diesel-derive-enum",
+ "dotenvy",
"fuzzy-matcher",
+ "jsonwebtoken",
+ "mime",
+ "objectid",
+ "rand 0.9.0",
"regex",
+ "reqwest",
"serde",
+ "serde_json",
"serde_repr",
+ "serde_with",
+ "sha2",
+ "tokio",
]
[[package]]
@@ -2237,6 +2313,36 @@ dependencies = [
"serde",
]
+[[package]]
+name = "serde_with"
+version = "3.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa"
+dependencies = [
+ "base64",
+ "chrono",
+ "hex",
+ "indexmap 1.9.3",
+ "indexmap 2.8.0",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "serde_with_macros",
+ "time 0.3.40",
+]
+
+[[package]]
+name = "serde_with_macros"
+version = "3.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e"
+dependencies = [
+ "darling",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
[[package]]
name = "sha1"
version = "0.10.6"
@@ -2245,18 +2351,18 @@ checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
dependencies = [
"cfg-if",
"cpufeatures",
- "digest",
+ "digest 0.10.7",
]
[[package]]
name = "sha2"
-version = "0.10.8"
+version = "0.11.0-pre.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
+checksum = "19b4241d1a56954dce82cecda5c8e9c794eef6f53abe5e5216bac0a0ea71ffa7"
dependencies = [
"cfg-if",
"cpufeatures",
- "digest",
+ "digest 0.11.0-pre.10",
]
[[package]]
@@ -2280,6 +2386,18 @@ version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
+[[package]]
+name = "simple_asn1"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb"
+dependencies = [
+ "num-bigint",
+ "num-traits",
+ "thiserror",
+ "time 0.3.40",
+]
+
[[package]]
name = "slab"
version = "0.4.9"
@@ -3142,7 +3260,7 @@ dependencies = [
"crossbeam-utils",
"displaydoc",
"flate2",
- "indexmap",
+ "indexmap 2.8.0",
"memchr",
"thiserror",
"zopfli",
diff --git a/Cargo.toml b/Cargo.toml
index dc6567f..9c79236 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,27 +1,35 @@
-[workspace]
-members = ["lib/schedule_parser"]
-
[package]
name = "schedule-parser-rusted"
-version = "0.3.0"
+version = "0.4.0"
edition = "2024"
publish = false
[dependencies]
-actix-http = "3.10.0"
actix-web = "4.10.2"
bcrypt = "0.17.0"
-chrono = "0.4.40"
+calamine = "0.26.1"
+chrono = { version = "0.4.40", features = ["serde"] }
diesel = { version = "2.2.8", features = ["postgres"] }
diesel-derive-enum = { git = "https://github.com/Havunen/diesel-derive-enum.git", features = ["postgres"] }
dotenvy = "0.15.7"
-hmac = "0.12.1"
-jwt = "0.16.0"
+fuzzy-matcher = "0.3.7"
+jsonwebtoken = { version = "9.3.1", features = ["use_pem"] }
mime = "0.3.17"
objectid = "0.2.0"
+regex = "1.11.1"
reqwest = "0.12.15"
-schedule_parser = { path = "./lib/schedule_parser" }
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140"
-sha2 = "0.10.8"
+serde_with = "3.12.0"
+serde_repr = "0.1.20"
+sha2 = "0.11.0-pre.5"
tokio = { version = "1.44.1", features = ["macros", "rt-multi-thread"] }
+rand = "0.9.0"
+
+[dev-dependencies]
+actix-http = "3.10.0"
+criterion = "0.5.1"
+
+[[bench]]
+name = "parse"
+harness = false
diff --git a/lib/schedule_parser/benches/parse.rs b/benches/parse.rs
similarity index 67%
rename from lib/schedule_parser/benches/parse.rs
rename to benches/parse.rs
index 5891b45..fefb411 100644
--- a/lib/schedule_parser/benches/parse.rs
+++ b/benches/parse.rs
@@ -1,8 +1,9 @@
use criterion::{Criterion, criterion_group, criterion_main};
-use schedule_parser::parse_xls;
+
+use schedule_parser_rusted::parser::parse_xls;
pub fn bench_parse_xls(c: &mut Criterion) {
- let buffer: Vec = include_bytes!("../../../schedule.xls").to_vec();
+ let buffer: Vec = include_bytes!("../schedule.xls").to_vec();
c.bench_function("parse_xls", |b| b.iter(|| parse_xls(&buffer)));
}
diff --git a/lib/schedule_parser/Cargo.toml b/lib/schedule_parser/Cargo.toml
deleted file mode 100644
index 4b1906b..0000000
--- a/lib/schedule_parser/Cargo.toml
+++ /dev/null
@@ -1,23 +0,0 @@
-[package]
-name = "schedule_parser"
-version = "0.2.0"
-edition = "2024"
-
-[lib]
-name = "schedule_parser"
-path = "src/lib/lib.rs"
-
-[dependencies]
-serde = { version = "1.0.219", features = ["derive"] }
-serde_repr = "0.1.20"
-chrono = { version = "0.4.40", features = ["serde"] }
-calamine = "0.26.1"
-regex = "1.11.1"
-fuzzy-matcher = "0.3.7"
-
-[dev-dependencies]
-criterion = "0.5.1"
-
-[[bench]]
-name = "parse"
-harness = false
diff --git a/src/app_state.rs b/src/app_state.rs
index 0afbd42..3d8c432 100644
--- a/src/app_state.rs
+++ b/src/app_state.rs
@@ -2,9 +2,9 @@ use crate::xls_downloader::basic_impl::BasicXlsDownloader;
use actix_web::web;
use chrono::{DateTime, Utc};
use diesel::{Connection, PgConnection};
-use schedule_parser::schema::ParseResult;
use std::env;
use std::sync::{Mutex, MutexGuard};
+use crate::parser::schema::ParseResult;
pub struct Schedule {
pub etag: String,
diff --git a/src/database/driver.rs b/src/database/driver.rs
index 4f40a8e..df2aa29 100644
--- a/src/database/driver.rs
+++ b/src/database/driver.rs
@@ -2,13 +2,13 @@ pub mod users {
use crate::database::models::User;
use crate::database::schema::users::dsl::users;
use crate::database::schema::users::dsl::*;
- use diesel::{insert_into, ExpressionMethods, QueryResult};
+ use diesel::{ExpressionMethods, QueryResult, insert_into};
use diesel::{PgConnection, SelectableHelper};
use diesel::{QueryDsl, RunQueryDsl};
use std::ops::DerefMut;
use std::sync::Mutex;
- pub fn get(connection: &Mutex, _id: String) -> QueryResult {
+ pub fn get(connection: &Mutex, _id: &String) -> QueryResult {
let mut lock = connection.lock().unwrap();
let con = lock.deref_mut();
@@ -20,7 +20,7 @@ pub mod users {
pub fn get_by_username(
connection: &Mutex,
- _username: String,
+ _username: &String,
) -> QueryResult {
let mut lock = connection.lock().unwrap();
let con = lock.deref_mut();
@@ -30,8 +30,21 @@ pub mod users {
.select(User::as_select())
.first(con)
}
+
+ pub fn get_by_vk_id(
+ connection: &Mutex,
+ _vk_id: i32,
+ ) -> QueryResult {
+ let mut lock = connection.lock().unwrap();
+ let con = lock.deref_mut();
- pub fn contains_by_username(connection: &Mutex, _username: String) -> bool {
+ users
+ .filter(vk_id.eq(_vk_id))
+ .select(User::as_select())
+ .first(con)
+ }
+
+ pub fn contains_by_username(connection: &Mutex, _username: &String) -> bool {
let mut lock = connection.lock().unwrap();
let con = lock.deref_mut();
@@ -45,7 +58,21 @@ pub mod users {
}
}
- pub fn delete_by_username(connection: &Mutex, _username: String) -> bool {
+ pub fn contains_by_vk_id(connection: &Mutex, _vk_id: i32) -> bool {
+ let mut lock = connection.lock().unwrap();
+ let con = lock.deref_mut();
+
+ match users
+ .filter(vk_id.eq(_vk_id))
+ .count()
+ .get_result::(con)
+ {
+ Ok(count) => count > 0,
+ Err(_) => false,
+ }
+ }
+
+ pub fn delete_by_username(connection: &Mutex, _username: &String) -> bool {
let mut lock = connection.lock().unwrap();
let con = lock.deref_mut();
@@ -61,11 +88,14 @@ pub mod users {
insert_into(users).values(user).execute(con)
}
-
+
pub fn insert_or_ignore(connection: &Mutex, user: &User) -> QueryResult {
let mut lock = connection.lock().unwrap();
let con = lock.deref_mut();
- insert_into(users).values(user).on_conflict_do_nothing().execute(con)
+ insert_into(users)
+ .values(user)
+ .on_conflict_do_nothing()
+ .execute(con)
}
}
diff --git a/src/database/models.rs b/src/database/models.rs
index 8b2555a..2c0cd6b 100644
--- a/src/database/models.rs
+++ b/src/database/models.rs
@@ -23,4 +23,4 @@ pub struct User {
pub group: String,
pub role: UserRole,
pub version: String,
-}
+}
\ No newline at end of file
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..b2819a7
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1 @@
+pub mod parser;
\ No newline at end of file
diff --git a/src/main.rs b/src/main.rs
index 859001f..b93981f 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,5 +1,6 @@
use crate::app_state::{AppState, app_state};
-use crate::routes::auth::sign_in::sign_in;
+use crate::routes::auth::sign_in::{sign_in_default, sign_in_vk};
+use crate::routes::auth::sign_up::{sign_up_default, sign_up_vk};
use actix_web::{App, HttpServer, web};
use dotenvy::dotenv;
@@ -7,19 +8,24 @@ mod app_state;
mod database;
mod routes;
-#[cfg(test)]
mod test_env;
mod utility;
mod xls_downloader;
+mod parser;
+
#[actix_web::main]
async fn main() {
dotenv().ok();
HttpServer::new(move || {
- let schedule_scope = web::scope("/auth").service(sign_in);
- let api_scope = web::scope("/api/v1").service(schedule_scope);
+ let auth_scope = web::scope("/auth")
+ .service(sign_in_default)
+ .service(sign_in_vk)
+ .service(sign_up_default)
+ .service(sign_up_vk);
+ let api_scope = web::scope("/api/v1").service(auth_scope);
App::new().app_data(move || app_state()).service(api_scope)
})
diff --git a/lib/schedule_parser/src/lib/lib.rs b/src/parser/mod.rs
similarity index 98%
rename from lib/schedule_parser/src/lib/lib.rs
rename to src/parser/mod.rs
index d5897d2..cc3a31c 100644
--- a/lib/schedule_parser/src/lib/lib.rs
+++ b/src/parser/mod.rs
@@ -1,6 +1,6 @@
-use crate::LessonParseResult::{Lessons, Street};
-use crate::schema::LessonType::Break;
-use crate::schema::{
+use crate::parser::LessonParseResult::{Lessons, Street};
+use crate::parser::schema::LessonType::Break;
+use crate::parser::schema::{
Day, Lesson, LessonSubGroup, LessonTime, LessonType, ParseResult, ScheduleEntry,
};
use calamine::{Reader, Xls, open_workbook_from_rs};
@@ -690,13 +690,18 @@ pub fn parse_xls(buffer: &Vec) -> ParseResult {
}
#[cfg(test)]
-mod tests {
+pub mod tests {
use super::*;
+ pub fn test_result() -> ParseResult {
+ let buffer: Vec = include_bytes!("../../schedule.xls").to_vec();
+
+ parse_xls(&buffer)
+ }
+
#[test]
fn read() {
- let buffer: Vec = include_bytes!("../../../../schedule.xls").to_vec();
- let result = parse_xls(&buffer);
+ let result = test_result();
assert_ne!(result.groups.len(), 0);
assert_ne!(result.teachers.len(), 0);
diff --git a/lib/schedule_parser/src/lib/schema.rs b/src/parser/schema.rs
similarity index 100%
rename from lib/schedule_parser/src/lib/schema.rs
rename to src/parser/schema.rs
diff --git a/src/routes/auth/mod.rs b/src/routes/auth/mod.rs
index 49e270c..7122840 100644
--- a/src/routes/auth/mod.rs
+++ b/src/routes/auth/mod.rs
@@ -1,3 +1,3 @@
-mod schema;
pub mod sign_in;
pub mod sign_up;
+mod shared;
diff --git a/src/routes/auth/schema.rs b/src/routes/auth/schema.rs
deleted file mode 100644
index 4a0c12b..0000000
--- a/src/routes/auth/schema.rs
+++ /dev/null
@@ -1,109 +0,0 @@
-pub mod sign_in {
- use crate::database::models::User;
- use crate::routes::schema::shared::{ErrorToHttpCode, IResponse};
- use crate::routes::schema::user;
- use actix_web::http::StatusCode;
- use serde::{Deserialize, Serialize};
-
- #[derive(Deserialize, Serialize)]
- pub struct Request {
- pub username: String,
- pub password: String,
- }
-
- pub type Response = IResponse;
-
- #[derive(Serialize)]
- pub struct ResponseErr {
- code: ErrorCode,
- }
-
- #[derive(Serialize)]
- #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
- pub enum ErrorCode {
- IncorrectCredentials,
- InvalidVkAccessToken,
- }
-
- pub trait ResponseExt {
- fn ok(user: &User) -> Self;
- fn err(code: ErrorCode) -> Response;
- }
-
- impl ResponseExt for Response {
- fn ok(user: &User) -> Self {
- IResponse(Ok(user::ResponseOk::from_user(&user)))
- }
-
- fn err(code: ErrorCode) -> Response {
- IResponse(Err(ResponseErr { code }))
- }
- }
-
- impl ErrorToHttpCode for ResponseErr {
- fn to_http_status_code(&self) -> StatusCode {
- StatusCode::NOT_ACCEPTABLE
- }
- }
-}
-
-pub mod sign_up {
- use crate::database::models::{User, UserRole};
- use crate::routes::schema::shared::{ErrorToHttpCode, IResponse};
- use crate::routes::schema::user;
- use actix_web::http::StatusCode;
- use serde::{Deserialize, Serialize};
-
- #[derive(Serialize, Deserialize)]
- pub struct Request {
- pub username: String,
- pub password: String,
- pub group: String,
- pub role: UserRole,
- pub version: String,
- }
-
- pub type Response = IResponse;
-
- #[derive(Serialize)]
- #[serde(rename_all = "camelCase")]
- pub struct ResponseOk {
- id: String,
- access_token: String,
- group: String,
- }
-
- #[derive(Serialize)]
- pub struct ResponseErr {
- code: ErrorCode,
- }
-
- #[derive(Serialize)]
- #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
- pub enum ErrorCode {
- DisallowedRole,
- InvalidGroupName,
- UsernameAlreadyExists,
- }
-
- pub trait ResponseExt {
- fn ok(user: &User) -> Self;
- fn err(code: ErrorCode) -> Self;
- }
-
- impl ResponseExt for Response {
- fn ok(user: &User) -> Self {
- IResponse(Ok(user::ResponseOk::from_user(&user)))
- }
-
- fn err(code: ErrorCode) -> Response {
- Self(Err(ResponseErr { code }))
- }
- }
-
- impl ErrorToHttpCode for ResponseErr {
- fn to_http_status_code(&self) -> StatusCode {
- StatusCode::NOT_ACCEPTABLE
- }
- }
-}
diff --git a/src/routes/auth/shared.rs b/src/routes/auth/shared.rs
new file mode 100644
index 0000000..f2c833d
--- /dev/null
+++ b/src/routes/auth/shared.rs
@@ -0,0 +1,96 @@
+use crate::utility::jwt::DEFAULT_ALGORITHM;
+use jsonwebtoken::errors::ErrorKind;
+use jsonwebtoken::{decode, DecodingKey, Validation};
+use serde::{Deserialize, Serialize};
+use std::env;
+use std::sync::LazyLock;
+
+#[derive(Deserialize, Serialize)]
+struct TokenData {
+ iis: String,
+ sub: i32,
+ app: i32,
+ exp: i32,
+ iat: i32,
+ jti: i32,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+struct Claims {
+ sub: String,
+ iis: String,
+ jti: i32,
+ app: i32,
+}
+
+#[derive(Debug, PartialEq)]
+pub enum Error {
+ JwtError(ErrorKind),
+ InvalidSignature,
+ InvalidToken,
+ Expired,
+ UnknownIssuer(String),
+ UnknownType(i32),
+ UnknownClientId(i32),
+}
+
+//noinspection SpellCheckingInspection
+const VK_PUBLIC_KEY: &str = concat!(
+ "-----BEGIN PUBLIC KEY-----\n",
+ "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvsvJlhFX9Ju/pvCz1frB\n",
+ "DgJs592VjdwQuRAmnlJAItyHkoiDIOEocPzgcUBTbDf1plDcTyO2RCkUt0pz0WK6\n",
+ "6HNhpJyIfARjaWHeUlv4TpuHXAJJsBKklkU2gf1cjID+40sWWYjtq5dAkXnSJUVA\n",
+ "UR+sq0lJ7GmTdJtAr8hzESqGEcSP15PTs7VUdHZ1nkC2XgkuR8KmKAUb388ji1Q4\n",
+ "n02rJNOPQgd9r0ac4N2v/yTAFPXumO78N25bpcuWf5vcL9e8THk/U2zt7wf+aAWL\n",
+ "748e0pREqNluTBJNZfmhC79Xx6GHtwqHyyduiqfPmejmiujNM/rqnA4e30Tg86Yn\n",
+ "cNZ6vLJyF72Eva1wXchukH/aLispbY+EqNPxxn4zzCWaLKHG87gaCxpVv9Tm0jSD\n",
+ "2es22NjrUbtb+2pAGnXbyDp2eGUqw0RrTQFZqt/VcmmSCE45FlcZMT28otrwG1ZB\n",
+ "kZAb5Js3wLEch3ZfYL8sjhyNRPBmJBrAvzrd8qa3rdUjkC9sKyjGAaHu2MNmFl1Y\n",
+ "JFQ3J54tGpkGgJjD7Kz3w0K6OiPDlVCNQN5sqXm24fCw85Pbi8SJiaLTp/CImrs1\n",
+ "Z3nHW5q8hljA7OGmqfOP0nZS/5zW9GHPyepsI1rW6CympYLJ15WeNzePxYS5KEX9\n",
+ "EncmkSD9b45ge95hJeJZteUCAwEAAQ==\n",
+ "-----END PUBLIC KEY-----"
+);
+
+static VK_ID_CLIENT_ID: LazyLock = LazyLock::new(|| {
+ env::var("VK_ID_CLIENT_ID")
+ .expect("VK_ID_CLIENT_ID must be set")
+ .parse::()
+ .expect("VK_ID_CLIENT_ID must be i32")
+});
+
+pub fn parse_vk_id(token_str: &String) -> Result {
+ let dkey = DecodingKey::from_rsa_pem(VK_PUBLIC_KEY.as_bytes()).unwrap();
+
+ match decode::(&token_str, &dkey, &Validation::new(DEFAULT_ALGORITHM)) {
+ Ok(token_data) => {
+ let claims = token_data.claims;
+
+ if claims.iis != "VK" {
+ Err(Error::UnknownIssuer(claims.iis))
+ } else if claims.jti != 21 {
+ Err(Error::UnknownType(claims.jti))
+ } else if claims.app != *VK_ID_CLIENT_ID {
+ Err(Error::UnknownClientId(claims.app))
+ } else {
+ match claims.sub.parse::() {
+ Ok(sub) => Ok(sub),
+ Err(_) => Err(Error::InvalidToken),
+ }
+ }
+ }
+ Err(err) => Err(match err.into_kind() {
+ ErrorKind::InvalidToken => Error::InvalidToken,
+ ErrorKind::InvalidSignature => Error::InvalidSignature,
+ ErrorKind::InvalidAlgorithmName => Error::InvalidToken,
+ ErrorKind::MissingRequiredClaim(_) => Error::InvalidToken,
+ ErrorKind::ExpiredSignature => Error::Expired,
+ ErrorKind::InvalidAlgorithm => Error::InvalidToken,
+ ErrorKind::MissingAlgorithm => Error::InvalidToken,
+ ErrorKind::Base64(_) => Error::InvalidToken,
+ ErrorKind::Json(_) => Error::InvalidToken,
+ ErrorKind::Utf8(_) => Error::InvalidToken,
+ kind => Error::JwtError(kind),
+ }),
+ }
+}
diff --git a/src/routes/auth/sign_in.rs b/src/routes/auth/sign_in.rs
index 0d3147f..f36c116 100644
--- a/src/routes/auth/sign_in.rs
+++ b/src/routes/auth/sign_in.rs
@@ -1,47 +1,138 @@
+use self::schema::*;
use crate::database::driver;
use crate::database::models::User;
-use crate::routes::auth::schema;
-use crate::{AppState, utility};
+use crate::routes::auth::shared::parse_vk_id;
+use crate::routes::auth::sign_in::schema::ErrorCode;
+use crate::routes::auth::sign_in::schema::SignInData::{Default, Vk};
+use crate::{utility, AppState};
use actix_web::{post, web};
use diesel::SaveChangesDsl;
use std::ops::DerefMut;
use web::Json;
-#[post("/sign-in")]
-pub async fn sign_in(
- data: Json,
- app_state: web::Data,
-) -> schema::sign_in::Response {
- use schema::sign_in::*;
+async fn sign_in(data: SignInData, app_state: &web::Data) -> Response {
+ let user = match &data {
+ Default(data) => driver::users::get_by_username(&app_state.database, &data.username),
+ Vk(id) => driver::users::get_by_vk_id(&app_state.database, *id),
+ };
- 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();
-
- user.access_token = utility::jwt::encode(&user.id);
-
- user.save_changes::(conn)
- .expect("Failed to update user");
-
- Response::ok(&user)
+ match user {
+ Ok(mut user) => {
+ if let Default(data) = data {
+ match bcrypt::verify(&data.password, &user.password) {
+ Ok(result) => {
+ if !result {
+ return Response::err(ErrorCode::IncorrectCredentials);
+ }
+ }
+ Err(_) => {
+ return Response::err(ErrorCode::IncorrectCredentials);
+ }
+ }
}
- Ok(false) | Err(_) => Response::err(ErrorCode::IncorrectCredentials),
- },
+
+ let mut lock = app_state.connection();
+ let conn = lock.deref_mut();
+
+ user.access_token = utility::jwt::encode(&user.id);
+
+ user.save_changes::(conn)
+ .expect("Failed to update user");
+
+ Response::ok(&user)
+ }
Err(_) => Response::err(ErrorCode::IncorrectCredentials),
}
}
+#[post("/sign-in")]
+pub async fn sign_in_default(data: Json, app_state: web::Data) -> Response {
+ sign_in(Default(data.into_inner()), &app_state).await
+}
+
+#[post("/sign-in-vk")]
+pub async fn sign_in_vk(data_json: Json, app_state: web::Data) -> Response {
+ let data = data_json.into_inner();
+
+ match parse_vk_id(&data.access_token) {
+ Ok(id) => sign_in(Vk(id), &app_state).await,
+ Err(_) => Response::err(ErrorCode::InvalidVkAccessToken),
+ }
+}
+
+mod schema {
+ use crate::database::models::User;
+ use crate::routes::schema::{user, ErrorToHttpCode, IResponse};
+ use actix_web::http::StatusCode;
+ use serde::{Deserialize, Serialize};
+
+ #[derive(Deserialize, Serialize)]
+ pub struct Request {
+ pub username: String,
+ pub password: String,
+ }
+
+ pub mod vk {
+ use serde::{Deserialize, Serialize};
+
+ #[derive(Serialize, Deserialize)]
+ #[serde(rename_all = "camelCase")]
+ pub struct Request {
+ pub access_token: String,
+ }
+ }
+
+ pub type Response = IResponse;
+
+ #[derive(Serialize)]
+ pub struct ResponseErr {
+ code: ErrorCode,
+ }
+
+ #[derive(Serialize)]
+ #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
+ pub enum ErrorCode {
+ IncorrectCredentials,
+ InvalidVkAccessToken,
+ }
+
+ pub trait ResponseExt {
+ fn ok(user: &User) -> Self;
+ fn err(code: ErrorCode) -> Response;
+ }
+
+ impl ResponseExt for Response {
+ fn ok(user: &User) -> Self {
+ IResponse(Ok(user::ResponseOk::from_user(&user)))
+ }
+
+ fn err(code: ErrorCode) -> Response {
+ IResponse(Err(ResponseErr { code }))
+ }
+ }
+
+ impl ErrorToHttpCode for ResponseErr {
+ fn to_http_status_code(&self) -> StatusCode {
+ StatusCode::NOT_ACCEPTABLE
+ }
+ }
+
+ /// Internal
+
+ pub enum SignInData {
+ Default(Request),
+ Vk(i32),
+ }
+}
+
#[cfg(test)]
mod tests {
- use crate::app_state::app_state;
+ use super::schema::*;
use crate::database::driver;
use crate::database::models::{User, UserRole};
- use crate::routes::auth::schema;
- use crate::routes::auth::sign_in::sign_in;
- use crate::test_env::tests::{static_app_state, test_app, test_env};
+ use crate::routes::auth::sign_in::sign_in_default;
+ use crate::test_env::tests::{static_app_state, test_app, test_app_state, test_env};
use crate::utility;
use actix_http::StatusCode;
use actix_web::dev::ServiceResponse;
@@ -50,8 +141,8 @@ mod tests {
use sha2::{Digest, Sha256};
use std::fmt::Write;
- async fn sign_in_client(data: schema::sign_in::Request) -> ServiceResponse {
- let app = test_app(app_state(), sign_in).await;
+ async fn sign_in_client(data: Request) -> ServiceResponse {
+ let app = test_app(test_app_state(), sign_in_default).await;
let req = test::TestRequest::with_uri("/sign-in")
.method(Method::POST)
@@ -100,7 +191,7 @@ mod tests {
async fn sign_in_ok() {
prepare("test::sign_in_ok".to_string());
- let resp = sign_in_client(schema::sign_in::Request {
+ let resp = sign_in_client(Request {
username: "test::sign_in_ok".to_string(),
password: "example".to_string(),
})
@@ -113,7 +204,7 @@ mod tests {
async fn sign_in_err() {
prepare("test::sign_in_err".to_string());
- let invalid_username = sign_in_client(schema::sign_in::Request {
+ let invalid_username = sign_in_client(Request {
username: "test::sign_in_err::username".to_string(),
password: "example".to_string(),
})
@@ -121,7 +212,7 @@ mod tests {
assert_eq!(invalid_username.status(), StatusCode::NOT_ACCEPTABLE);
- let invalid_password = sign_in_client(schema::sign_in::Request {
+ let invalid_password = sign_in_client(Request {
username: "test::sign_in_err".to_string(),
password: "bad_password".to_string(),
})
diff --git a/src/routes/auth/sign_up.rs b/src/routes/auth/sign_up.rs
index a2e6d6e..ae47c05 100644
--- a/src/routes/auth/sign_up.rs
+++ b/src/routes/auth/sign_up.rs
@@ -1,22 +1,19 @@
+use self::schema::*;
+use crate::AppState;
use crate::database::driver;
-use crate::database::models::{User, UserRole};
-use crate::routes::auth::schema;
-use crate::{utility, AppState};
+use crate::database::models::UserRole;
+use crate::routes::auth::shared::{Error, parse_vk_id};
use actix_web::{post, web};
-use objectid::ObjectId;
+use rand::{Rng, rng};
use web::Json;
-#[post("/sign-up")]
-pub async fn sign_up(
- data: Json,
- app_state: web::Data,
-) -> schema::sign_up::Response {
- use schema::sign_up::*;
-
+async fn sign_up(data: SignUpData, app_state: &web::Data) -> Response {
+ // If user selected forbidden role.
if data.role == UserRole::Admin {
return Response::err(ErrorCode::DisallowedRole);
}
+ // If specified group doesn't exist in schedule.
let schedule_opt = app_state.schedule.lock().unwrap();
if let Some(schedule) = &*schedule_opt {
@@ -25,37 +22,193 @@ pub async fn sign_up(
}
}
- if driver::users::contains_by_username(&app_state.database, data.username.clone()) {
+ // If user with specified username already exists.
+ if driver::users::contains_by_username(&app_state.database, &data.username) {
return Response::err(ErrorCode::UsernameAlreadyExists);
}
- let id = ObjectId::new().unwrap().to_string();
- let access_token = utility::jwt::encode(&id);
-
- let user = User {
- id,
- username: data.username.clone(),
- password: bcrypt::hash(data.password.as_str(), bcrypt::DEFAULT_COST).unwrap(),
- vk_id: None,
- access_token,
- group: data.group.clone(),
- role: data.role.clone(),
- version: data.version.clone(),
- };
+ // If user with specified VKID already exists.
+ if let Some(id) = data.vk_id {
+ if driver::users::contains_by_vk_id(&app_state.database, id) {
+ return Response::err(ErrorCode::VkAlreadyExists);
+ }
+ }
+ let user = data.to_user();
driver::users::insert(&app_state.database, &user).unwrap();
Response::ok(&user)
}
+#[post("/sign-up")]
+pub async fn sign_up_default(data_json: Json, app_state: web::Data) -> Response {
+ let data = data_json.into_inner();
+
+ sign_up(
+ SignUpData {
+ username: data.username,
+ password: data.password,
+ vk_id: None,
+ group: data.group,
+ role: data.role,
+ version: data.version,
+ },
+ &app_state,
+ )
+ .await
+}
+
+#[post("/sign-up-vk")]
+pub async fn sign_up_vk(data_json: Json, app_state: web::Data) -> Response {
+ let data = data_json.into_inner();
+
+ match parse_vk_id(&data.access_token) {
+ Ok(id) => {
+ sign_up(
+ SignUpData {
+ username: data.username,
+ password: rng()
+ .sample_iter(&rand::distr::Alphanumeric)
+ .take(16)
+ .map(char::from)
+ .collect(),
+ vk_id: Some(id),
+ group: data.group,
+ role: data.role,
+ version: data.version,
+ },
+ &app_state,
+ )
+ .await
+ }
+ Err(err) => {
+ if err != Error::Expired {
+ eprintln!("Failed to parse vk id token!");
+ eprintln!("{:?}", err);
+ }
+
+ Response::err(ErrorCode::InvalidVkAccessToken)
+ }
+ }
+}
+
+mod schema {
+ use crate::database::models::{User, UserRole};
+ use crate::routes::schema::{ErrorToHttpCode, IResponse, user};
+ use crate::utility;
+ use actix_web::http::StatusCode;
+ use objectid::ObjectId;
+ use serde::{Deserialize, Serialize};
+
+ /// WEB
+
+ #[derive(Serialize, Deserialize)]
+ pub struct Request {
+ pub username: String,
+ pub password: String,
+ pub group: String,
+ pub role: UserRole,
+ pub version: String,
+ }
+
+ pub mod vk {
+ use crate::database::models::UserRole;
+ use serde::{Deserialize, Serialize};
+
+ #[derive(Serialize, Deserialize)]
+ #[serde(rename_all = "camelCase")]
+ pub struct Request {
+ pub access_token: String,
+ pub username: String,
+ pub group: String,
+ pub role: UserRole,
+ pub version: String,
+ }
+ }
+
+ pub type Response = IResponse;
+
+ #[derive(Serialize)]
+ #[serde(rename_all = "camelCase")]
+ pub struct ResponseOk {
+ id: String,
+ access_token: String,
+ group: String,
+ }
+
+ #[derive(Serialize)]
+ pub struct ResponseErr {
+ code: ErrorCode,
+ }
+
+ #[derive(Serialize)]
+ #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
+ pub enum ErrorCode {
+ DisallowedRole,
+ InvalidGroupName,
+ UsernameAlreadyExists,
+ InvalidVkAccessToken,
+ VkAlreadyExists,
+ }
+
+ pub trait ResponseExt {
+ fn ok(user: &User) -> Self;
+ fn err(code: ErrorCode) -> Self;
+ }
+
+ impl ResponseExt for Response {
+ fn ok(user: &User) -> Self {
+ IResponse(Ok(user::ResponseOk::from_user(&user)))
+ }
+
+ fn err(code: ErrorCode) -> Response {
+ Self(Err(ResponseErr { code }))
+ }
+ }
+
+ impl ErrorToHttpCode for ResponseErr {
+ fn to_http_status_code(&self) -> StatusCode {
+ StatusCode::NOT_ACCEPTABLE
+ }
+ }
+
+ /// Internal
+
+ pub struct SignUpData {
+ pub username: String,
+ pub password: String,
+ pub vk_id: Option,
+ pub group: String,
+ pub role: UserRole,
+ pub version: String,
+ }
+
+ impl SignUpData {
+ pub fn to_user(self) -> User {
+ let id = ObjectId::new().unwrap().to_string();
+ let access_token = utility::jwt::encode(&id);
+
+ User {
+ id,
+ username: self.username,
+ password: bcrypt::hash(self.password, bcrypt::DEFAULT_COST).unwrap(),
+ vk_id: self.vk_id,
+ access_token,
+ group: self.group,
+ role: self.role,
+ version: self.version,
+ }
+ }
+ }
+}
+
#[cfg(test)]
mod tests {
- use crate::app_state::app_state;
use crate::database::driver;
use crate::database::models::UserRole;
- use crate::routes::auth::schema;
- use crate::routes::auth::sign_up::sign_up;
- use crate::test_env::tests::{static_app_state, test_app, test_env};
+ use crate::routes::auth::sign_up::schema::Request;
+ use crate::routes::auth::sign_up::sign_up_default;
+ use crate::test_env::tests::{static_app_state, test_app, test_app_state, test_env};
use actix_http::StatusCode;
use actix_web::dev::ServiceResponse;
use actix_web::http::Method;
@@ -68,11 +221,11 @@ mod tests {
}
async fn sign_up_client(data: SignUpPartial) -> ServiceResponse {
- let app = test_app(app_state(), sign_up).await;
+ let app = test_app(test_app_state(), sign_up_default).await;
let req = test::TestRequest::with_uri("/sign-up")
.method(Method::POST)
- .set_json(schema::sign_up::Request {
+ .set_json(Request {
username: data.username.clone(),
password: "example".to_string(),
group: data.group.clone(),
@@ -91,7 +244,7 @@ mod tests {
test_env();
let app_state = static_app_state();
- driver::users::delete_by_username(&app_state.database, "test::sign_up_valid".to_string());
+ driver::users::delete_by_username(&app_state.database, &"test::sign_up_valid".to_string());
// test
@@ -114,7 +267,7 @@ mod tests {
let app_state = static_app_state();
driver::users::delete_by_username(
&app_state.database,
- "test::sign_up_multiple".to_string(),
+ &"test::sign_up_multiple".to_string(),
);
let create = sign_up_client(SignUpPartial {
@@ -150,4 +303,19 @@ mod tests {
assert_eq!(resp.status(), StatusCode::NOT_ACCEPTABLE);
}
+
+ #[actix_web::test]
+ async fn sign_up_invalid_group() {
+ test_env();
+
+ // test
+ let resp = sign_up_client(SignUpPartial {
+ username: "test::sign_up_invalid_group".to_string(),
+ group: "invalid_group".to_string(),
+ role: UserRole::Student,
+ })
+ .await;
+
+ assert_eq!(resp.status(), StatusCode::NOT_ACCEPTABLE);
+ }
}
diff --git a/src/routes/schema.rs b/src/routes/schema.rs
index 6bac389..0ed3d78 100644
--- a/src/routes/schema.rs
+++ b/src/routes/schema.rs
@@ -1,39 +1,37 @@
-pub mod shared {
- use actix_web::body::EitherBody;
- use actix_web::error::JsonPayloadError;
- use actix_web::http::StatusCode;
- use actix_web::{HttpRequest, HttpResponse, Responder};
- use serde::Serialize;
+use actix_web::body::EitherBody;
+use actix_web::error::JsonPayloadError;
+use actix_web::http::StatusCode;
+use actix_web::{HttpRequest, HttpResponse, Responder};
+use serde::Serialize;
- pub struct IResponse(pub Result);
+pub struct IResponse(pub Result);
- pub trait ErrorToHttpCode {
- fn to_http_status_code(&self) -> StatusCode;
- }
+pub trait ErrorToHttpCode {
+ fn to_http_status_code(&self) -> StatusCode;
+}
- impl Responder for IResponse {
- type Body = EitherBody;
+impl Responder for IResponse {
+ type Body = EitherBody;
- fn respond_to(self, _: &HttpRequest) -> HttpResponse {
- match serde_json::to_string(&self.0) {
- Ok(body) => {
- let code = match &self.0 {
- Ok(_) => StatusCode::OK,
- Err(e) => e.to_http_status_code(),
- };
+ fn respond_to(self, _: &HttpRequest) -> HttpResponse {
+ match serde_json::to_string(&self.0) {
+ Ok(body) => {
+ let code = match &self.0 {
+ Ok(_) => StatusCode::OK,
+ Err(e) => e.to_http_status_code(),
+ };
- match HttpResponse::build(code)
- .content_type(mime::APPLICATION_JSON)
- .message_body(body)
- {
- Ok(res) => res.map_into_left_body(),
- Err(err) => HttpResponse::from_error(err).map_into_right_body(),
- }
+ match HttpResponse::build(code)
+ .content_type(mime::APPLICATION_JSON)
+ .message_body(body)
+ {
+ Ok(res) => res.map_into_left_body(),
+ Err(err) => HttpResponse::from_error(err).map_into_right_body(),
}
+ }
- Err(err) => {
- HttpResponse::from_error(JsonPayloadError::Serialize(err)).map_into_right_body()
- }
+ Err(err) => {
+ HttpResponse::from_error(JsonPayloadError::Serialize(err)).map_into_right_body()
}
}
}
diff --git a/src/test_env.rs b/src/test_env.rs
index 58e2aca..e1d6318 100644
--- a/src/test_env.rs
+++ b/src/test_env.rs
@@ -1,9 +1,10 @@
#[cfg(test)]
pub(crate) mod tests {
- use crate::app_state::{AppState, app_state};
+ use crate::app_state::{app_state, AppState, Schedule};
use actix_web::dev::{HttpServiceFactory, Service, ServiceResponse};
- use actix_web::{App, test, web};
+ use actix_web::{test, web, App};
use std::sync::LazyLock;
+ use crate::parser::tests::test_result;
pub fn test_env() {
dotenvy::from_path(".env.test").expect("Failed to load test environment file");
@@ -19,8 +20,22 @@ pub(crate) mod tests {
test::init_service(App::new().app_data(app_state).service(factory)).await
}
+ pub fn test_app_state() -> web::Data {
+ let state = app_state();
+ let mut schedule_lock = state.schedule.lock().unwrap();
+
+ *schedule_lock = Some(Schedule {
+ etag: "".to_string(),
+ updated_at: Default::default(),
+ parsed_at: Default::default(),
+ data: test_result(),
+ });
+
+ state.clone()
+ }
+
pub fn static_app_state() -> web::Data {
- static STATE: LazyLock> = LazyLock::new(|| app_state());
+ static STATE: LazyLock> = LazyLock::new(|| test_app_state());
STATE.clone()
}
diff --git a/src/utility/jwt.rs b/src/utility/jwt.rs
index c510391..e758e06 100644
--- a/src/utility/jwt.rs
+++ b/src/utility/jwt.rs
@@ -1,88 +1,91 @@
-use chrono::DateTime;
use chrono::Duration;
-use chrono::TimeZone;
use chrono::Utc;
-use hmac::{Hmac, Mac};
-use jwt::{SignWithKey, Token, VerifyWithKey};
-use sha2::Sha256;
-use std::collections::BTreeMap;
+use jsonwebtoken::errors::ErrorKind;
+use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Header, Validation, decode};
+use serde::{Deserialize, Serialize};
+use serde_with::DisplayFromStr;
+use serde_with::serde_as;
use std::env;
use std::mem::discriminant;
use std::sync::LazyLock;
-static JWT_SECRET: LazyLock> = LazyLock::new(|| {
+static DECODING_KEY: LazyLock = 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")
+ DecodingKey::from_secret(secret.as_bytes())
+});
+
+static ENCODING_KEY: LazyLock = LazyLock::new(|| {
+ let secret = env::var("JWT_SECRET").expect("JWT_SECRET must be set");
+
+ EncodingKey::from_secret(secret.as_bytes())
});
#[derive(Debug)]
-pub enum VerifyError {
- JwtError(jwt::Error),
+pub enum Error {
InvalidSignature,
- InvalidToken,
+ InvalidToken(ErrorKind),
Expired,
}
-impl PartialEq for VerifyError {
+impl PartialEq for Error {
fn eq(&self, other: &Self) -> bool {
discriminant(self) == discriminant(other)
}
}
-pub fn verify_and_decode(token: &String) -> Result {
- let jwt = &*JWT_SECRET;
+#[serde_as]
+#[derive(Debug, Serialize, Deserialize)]
+struct Claims {
+ id: String,
+ #[serde_as(as = "DisplayFromStr")]
+ iat: u64,
+ #[serde_as(as = "DisplayFromStr")]
+ exp: u64,
+}
- let result: Result, jwt::Error> = token.verify_with_key(jwt);
+pub(crate) const DEFAULT_ALGORITHM: Algorithm = Algorithm::HS256;
+
+pub fn verify_and_decode(token: &String) -> Result {
+ let mut validation = Validation::new(DEFAULT_ALGORITHM);
+
+ validation.required_spec_claims.remove("exp");
+ validation.validate_exp = false;
+
+ let result = decode::(&token, &*DECODING_KEY, &validation);
match result {
- Ok(claims) => {
- let exp = claims.get("exp").unwrap();
- let exp_date = DateTime::from_timestamp(exp.parse::().unwrap(), 0)
- .expect("Failed to parse expiration time");
-
- if Utc::now() > exp_date {
- return Err(VerifyError::Expired);
+ Ok(token_data) => {
+ if token_data.claims.exp < Utc::now().timestamp().unsigned_abs() {
+ Err(Error::Expired)
+ } else {
+ Ok(token_data.claims.id)
}
-
- Ok(claims.get("id").cloned().unwrap())
}
- Err(err) => Err(match err {
- jwt::Error::InvalidSignature | jwt::Error::RustCryptoMac(_) => {
- VerifyError::InvalidSignature
- }
- jwt::Error::Format | jwt::Error::Base64(_) | jwt::Error::NoClaimsComponent => {
- VerifyError::InvalidToken
- }
-
- _ => VerifyError::JwtError(err),
+ Err(err) => Err(match err.into_kind() {
+ ErrorKind::InvalidSignature => Error::InvalidSignature,
+ ErrorKind::ExpiredSignature => Error::Expired,
+ kind => Error::InvalidToken(kind),
}),
}
}
pub fn encode(id: &String) -> String {
- let header = jwt::Header {
- type_: Some(jwt::header::HeaderType::JsonWebToken),
+ let header = Header {
+ typ: Some(String::from("JWT")),
..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();
+ let claims = Claims {
+ id: id.clone(),
+ iat: iat.timestamp().unsigned_abs(),
+ exp: exp.timestamp().unsigned_abs(),
+ };
- claims.insert("id", id.as_str());
- claims.insert("iat", iat_str.as_str());
- claims.insert("exp", exp_str.as_str());
-
- Token::new(header, claims)
- .sign_with_key(&*JWT_SECRET)
- .unwrap()
- .as_str()
- .to_string()
+ jsonwebtoken::encode(&header, &claims, &*ENCODING_KEY).unwrap()
}
#[cfg(test)]
@@ -105,7 +108,10 @@ mod tests {
let result = verify_and_decode(&token);
assert!(result.is_err());
- assert_eq!(result.err().unwrap(), VerifyError::InvalidToken);
+ assert_eq!(
+ result.err().unwrap(),
+ Error::InvalidToken(ErrorKind::InvalidToken)
+ );
}
#[test]
@@ -116,7 +122,7 @@ mod tests {
let result = verify_and_decode(&token);
assert!(result.is_err());
- assert_eq!(result.err().unwrap(), VerifyError::InvalidSignature);
+ assert_eq!(result.err().unwrap(), Error::InvalidSignature);
}
#[test]
@@ -127,7 +133,7 @@ mod tests {
let result = verify_and_decode(&token);
assert!(result.is_err());
- assert_eq!(result.err().unwrap(), VerifyError::Expired);
+ assert_eq!(result.err().unwrap(), Error::Expired);
}
#[test]