From 2856b1bbd823742406af84cb2a384f2c066d39ec Mon Sep 17 00:00:00 2001 From: Mitchell Marino Date: Tue, 4 Apr 2023 20:13:30 -0500 Subject: [PATCH] added signin endpoint --- src/main.rs | 99 ++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 91 insertions(+), 8 deletions(-) diff --git a/src/main.rs b/src/main.rs index 8334360..c1b8c25 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,16 +1,28 @@ use axum::{ - extract::{Query, State}, + extract::State, http::StatusCode, response::IntoResponse, routing::{get, post}, Json, Router, }; +use jsonwebtoken::{EncodingKey, Header}; use serde::{Deserialize, Serialize}; use serde_json::json; use sqlx::postgres::PgPoolOptions; use sqlx::PgPool; use std::net::SocketAddr; +#[derive(Clone, Serialize, Deserialize, Debug, Hash)] +struct Claims { + username: String, +} + +#[derive(Clone)] +struct AppState { + db_pool: PgPool, + jwt_key: EncodingKey, +} + #[tokio::main] async fn main() { // initialize tracing @@ -21,6 +33,8 @@ async fn main() { let db_url = std::env::var("DATABASE_URL") .expect("Set `DATABASE_URL` to the url of the postgres database."); + let jwt_secret = std::env::var("SAAPI_JWT_SECRET").unwrap_or("Sdb\\y5PP`,hmG+98".into()); + let pool = PgPoolOptions::new() .max_connections(50) .connect(&db_url) @@ -33,7 +47,11 @@ async fn main() { .route("/", get(root)) // `POST /users` goes to `create_user` .route("/user/signup", post(signup)) - .with_state(pool); + .route("/user/signin", post(signin)) + .with_state(AppState { + db_pool: pool, + jwt_key: EncodingKey::from_secret(jwt_secret.as_bytes()), + }); // run our app with hyper // `axum::Server` is a re-export of `hyper::Server` @@ -50,20 +68,23 @@ async fn root() -> &'static str { "Hello, World!" } -async fn signup(State(pool): State, Json(signup): Json) -> impl IntoResponse { +async fn signup( + State(app_state): State, + Json(signup): Json, +) -> impl IntoResponse { // insert your application logic here let pass_hash = sha256::digest(&*signup.password); let result = sqlx::query!( r#" -INSERT INTO users (username, password) -VALUES ( $1, $2 ) -RETURNING id; + INSERT INTO users (username, password) + VALUES ( $1, $2 ) + RETURNING id; "#, signup.username, pass_hash.as_bytes(), ) - .fetch_one(&pool) + .fetch_one(&app_state.db_pool) .await; match result { @@ -92,13 +113,75 @@ RETURNING id; } } -// the input to our `create_user` handler +async fn signin( + State(app_state): State, + Json(signin): Json, +) -> impl IntoResponse { + let pass_hash = sha256::digest(&*signin.password); + + let result = sqlx::query!( + r#" + SELECT username + FROM users + WHERE username = $1 AND password = $2 + "#, + signin.username, + pass_hash.as_bytes(), + ) + .fetch_optional(&app_state.db_pool) + .await; + + match result { + Ok(Some(user)) => { + let claims = Claims { + username: user.username, + }; + let token = match jsonwebtoken::encode(&Header::default(), &claims, &app_state.jwt_key) + { + Ok(token) => token, + Err(err) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(json!({ + "error": format!("Unknown error signing in when creating JWT: {}", err) + })), + ) + } + }; + + return (StatusCode::OK, Json(json!({ "data": token }))); + } + Ok(None) => { + return ( + StatusCode::UNAUTHORIZED, + Json(json!({ + "error": format!("Incorrect username or password") + })), + ) + } + Err(err) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(json!({ + "error": format!("Unknown error signing in: {}", err) + })), + ) + } + } +} + #[derive(Clone, Debug, Deserialize)] struct Signup { username: String, password: String, } +#[derive(Clone, Debug, Deserialize)] +struct Signin { + username: String, + password: String, +} + // the output to our `create_user` handler #[derive(Serialize, sqlx::FromRow)] struct User {