summaryrefslogtreecommitdiff
path: root/server
diff options
context:
space:
mode:
Diffstat (limited to 'server')
-rw-r--r--server/Cargo.lock67
-rw-r--r--server/Cargo.toml1
-rw-r--r--server/src/errors.rs7
-rw-r--r--server/src/main.rs4
-rw-r--r--server/src/models/auth.rs63
-rw-r--r--server/src/models/mod.rs1
-rw-r--r--server/src/models/user.rs22
-rw-r--r--server/src/routes/auth.rs22
-rw-r--r--server/src/routes/mod.rs1
9 files changed, 183 insertions, 5 deletions
diff --git a/server/Cargo.lock b/server/Cargo.lock
index 9148b0d..514f558 100644
--- a/server/Cargo.lock
+++ b/server/Cargo.lock
@@ -38,6 +38,15 @@ dependencies = [
]
[[package]]
+name = "android_system_properties"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7ed72e1635e121ca3e79420540282af22da58be50de153d36f81ddc6b83aa9e"
+dependencies = [
+ "libc",
+]
+
+[[package]]
name = "ansi_term"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -213,6 +222,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
+name = "chrono"
+version = "0.4.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1"
+dependencies = [
+ "iana-time-zone",
+ "js-sys",
+ "num-integer",
+ "num-traits",
+ "time 0.1.44",
+ "wasm-bindgen",
+ "winapi",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
+
+[[package]]
name = "cpufeatures"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -413,7 +443,7 @@ checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6"
dependencies = [
"cfg-if",
"libc",
- "wasi",
+ "wasi 0.11.0+wasi-snapshot-preview1",
]
[[package]]
@@ -565,6 +595,19 @@ dependencies = [
]
[[package]]
+name = "iana-time-zone"
+version = "0.1.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad2bfd338099682614d3ee3fe0cd72e0b6a41ca6a87f6a74a3bd593c91650501"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "js-sys",
+ "wasm-bindgen",
+ "winapi",
+]
+
+[[package]]
name = "idna"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -675,6 +718,7 @@ version = "0.1.0"
dependencies = [
"async-trait",
"axum",
+ "chrono",
"http-body",
"jsonwebtoken",
"once_cell",
@@ -745,7 +789,7 @@ checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf"
dependencies = [
"libc",
"log",
- "wasi",
+ "wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys",
]
@@ -1218,7 +1262,7 @@ dependencies = [
"num-bigint",
"num-traits",
"thiserror",
- "time",
+ "time 0.3.13",
]
[[package]]
@@ -1411,6 +1455,17 @@ dependencies = [
[[package]]
name = "time"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
+dependencies = [
+ "libc",
+ "wasi 0.10.0+wasi-snapshot-preview1",
+ "winapi",
+]
+
+[[package]]
+name = "time"
version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db76ff9fa4b1458b3c7f077f3ff9887394058460d21e634355b273aaf11eea45"
@@ -1752,6 +1807,12 @@ dependencies = [
[[package]]
name = "wasi"
+version = "0.10.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
+
+[[package]]
+name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
diff --git a/server/Cargo.toml b/server/Cargo.toml
index 8527fdb..f5cf075 100644
--- a/server/Cargo.toml
+++ b/server/Cargo.toml
@@ -19,3 +19,4 @@ tower-http = { version = "0.3.4", features = ["trace", "compression-br", "propag
sqlx = { version = "0.6", features = [ "runtime-tokio-rustls", "postgres" ] }
sha256 = "1.0.3"
validator = { version = "0.16.0", features = ["derive"] }
+chrono = "0.4"
diff --git a/server/src/errors.rs b/server/src/errors.rs
index 9221fea..d991132 100644
--- a/server/src/errors.rs
+++ b/server/src/errors.rs
@@ -9,6 +9,8 @@ pub enum AppError {
Generic,
Database,
BadRequest(String),
+ NotFound,
+ TokenCreation,
}
impl IntoResponse for AppError {
@@ -23,6 +25,11 @@ impl IntoResponse for AppError {
"Error with database connection".to_string(),
),
AppError::BadRequest(value) => (StatusCode::BAD_REQUEST, value),
+ AppError::NotFound => (StatusCode::NOT_FOUND, "Element not found".to_string()),
+ AppError::TokenCreation => (
+ StatusCode::INTERNAL_SERVER_ERROR,
+ "Token creation error".to_string(),
+ ),
};
let body = Json(json!({
diff --git a/server/src/main.rs b/server/src/main.rs
index e2536ca..3436e2c 100644
--- a/server/src/main.rs
+++ b/server/src/main.rs
@@ -31,7 +31,9 @@ async fn create_app() -> Router {
logger::setup();
let _ = db::setup().await;
- let api_routes = Router::new().nest("/users", routes::user::create_route());
+ let api_routes = Router::new()
+ .nest("/users", routes::user::create_route())
+ .nest("/auth", routes::auth::create_route());
Router::new()
.nest("/v1", api_routes)
diff --git a/server/src/models/auth.rs b/server/src/models/auth.rs
new file mode 100644
index 0000000..03b198b
--- /dev/null
+++ b/server/src/models/auth.rs
@@ -0,0 +1,63 @@
+use crate::errors::AppError;
+use chrono::{Duration, Local};
+use jsonwebtoken::{encode, DecodingKey, EncodingKey, Header, Validation};
+use once_cell::sync::Lazy;
+use serde::{Deserialize, Serialize};
+
+struct Keys {
+ encoding: EncodingKey,
+ decoding: DecodingKey,
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct Claims {
+ user_id: i32,
+ exp: usize,
+}
+
+#[derive(Serialize)]
+pub struct AuthBody {
+ access_token: String,
+ token_type: String,
+}
+
+static KEYS: Lazy<Keys> = Lazy::new(|| {
+ let secret = std::env::var("JWT_SECRET").expect("JWT_SECRET must be set");
+ Keys::new(secret.as_bytes())
+});
+
+impl Keys {
+ fn new(secret: &[u8]) -> Self {
+ Self {
+ encoding: EncodingKey::from_secret(secret),
+ decoding: DecodingKey::from_secret(secret),
+ }
+ }
+}
+
+impl Claims {
+ pub fn new(user_id: i32) -> Self {
+ let expiration = Local::now() + Duration::days(2);
+
+ Self {
+ user_id,
+ exp: expiration.timestamp() as usize,
+ }
+ }
+
+ pub fn get_token(&self) -> Result<String, AppError> {
+ let token = encode(&Header::default(), &self, &KEYS.encoding)
+ .map_err(|_| AppError::TokenCreation)?;
+
+ Ok(token)
+ }
+}
+
+impl AuthBody {
+ pub fn new(access_token: String) -> Self {
+ Self {
+ access_token,
+ token_type: "Bearer".to_string(),
+ }
+ }
+}
diff --git a/server/src/models/mod.rs b/server/src/models/mod.rs
index 22d12a3..f9bae3d 100644
--- a/server/src/models/mod.rs
+++ b/server/src/models/mod.rs
@@ -1 +1,2 @@
+pub mod auth;
pub mod user;
diff --git a/server/src/models/user.rs b/server/src/models/user.rs
index 964f04a..38ec121 100644
--- a/server/src/models/user.rs
+++ b/server/src/models/user.rs
@@ -16,7 +16,7 @@ pub struct User {
#[derive(Deserialize, Serialize)]
pub struct UserList {
- id: i32,
+ pub id: i32,
email: String,
is_staff: Option<bool>,
}
@@ -61,6 +61,26 @@ impl User {
Ok(rec)
}
+ pub async fn find(user: User) -> Result<UserList, AppError> {
+ let pool = unsafe { get_client() };
+
+ let crypted_password = sha256::digest(user.password);
+
+ let rec = sqlx::query_as!(
+ UserList,
+ r#"
+ SELECT id, email, is_staff FROM "users"
+ WHERE email = $1 AND password = $2
+ "#,
+ user.email,
+ crypted_password
+ )
+ .fetch_one(pool)
+ .await?;
+
+ Ok(rec)
+ }
+
pub async fn list() -> Result<Vec<UserList>, AppError> {
let pool = unsafe { get_client() };
let rows = sqlx::query_as!(UserList, r#"SELECT id, email, is_staff FROM users"#)
diff --git a/server/src/routes/auth.rs b/server/src/routes/auth.rs
new file mode 100644
index 0000000..629ed33
--- /dev/null
+++ b/server/src/routes/auth.rs
@@ -0,0 +1,22 @@
+use crate::errors::AppError;
+use crate::models::{
+ auth::{AuthBody, Claims},
+ user::{User, UserCreate},
+};
+use axum::{routing::post, Json, Router};
+
+pub fn create_route() -> Router {
+ Router::new().route("/login", post(make_login))
+}
+
+async fn make_login(Json(payload): Json<UserCreate>) -> Result<Json<AuthBody>, AppError> {
+ let user = User::new(payload.email, payload.password);
+ match User::find(user).await {
+ Ok(user) => {
+ let claims = Claims::new(user.id);
+ let token = claims.get_token()?;
+ Ok(Json(AuthBody::new(token)))
+ }
+ Err(_) => Err(AppError::NotFound),
+ }
+}
diff --git a/server/src/routes/mod.rs b/server/src/routes/mod.rs
index 22d12a3..f9bae3d 100644
--- a/server/src/routes/mod.rs
+++ b/server/src/routes/mod.rs
@@ -1 +1,2 @@
+pub mod auth;
pub mod user;