summaryrefslogtreecommitdiff
path: root/server/src
diff options
context:
space:
mode:
authorSanto Cariotti <santo@dcariotti.me>2022-08-23 18:10:18 +0200
committerSanto Cariotti <santo@dcariotti.me>2022-08-23 18:10:18 +0200
commiteedc536c23c3230c6ebeac8b495b2cfef0317830 (patch)
tree198007257cfa195c410c28b0a576f46fb8870ab9 /server/src
parent0dace9fb82953cc91e6d603c4c9970631ac036ad (diff)
Auth login
Diffstat (limited to 'server/src')
-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
7 files changed, 118 insertions, 2 deletions
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;