This commit is contained in:
Mitchell Marino 2023-04-07 09:48:39 -05:00
parent a5a75ce2ac
commit a93704f998
3 changed files with 148 additions and 150 deletions

13
src/jwt.rs Normal file
View File

@ -0,0 +1,13 @@
use jsonwebtoken::EncodingKey;
use serde::{Deserialize, Serialize};
#[derive(Clone, Serialize, Deserialize, Debug, Hash)]
pub struct Claims {
pub id: i32,
pub username: String,
}
pub fn get_jwt_secret() -> EncodingKey {
let secret = std::env::var("SAAPI_JWT_SECRET").unwrap_or("Sdb\\y5PP`,hmG+98".into());
EncodingKey::from_secret(secret.as_bytes())
}

View File

@ -1,27 +1,21 @@
mod events;
mod jwt;
mod user;
use axum::{
extract::State,
http::StatusCode,
response::IntoResponse,
routing::{get, post},
Json, Router,
Router,
};
use jsonwebtoken::{EncodingKey, Header};
use serde::{Deserialize, Serialize};
use serde_json::json;
use jsonwebtoken::EncodingKey;
use sqlx::postgres::PgPoolOptions;
use sqlx::PgPool;
use std::net::SocketAddr;
#[derive(Clone, Serialize, Deserialize, Debug, Hash)]
struct Claims {
id: i32,
username: String,
}
use user::{signin, signup};
#[derive(Clone)]
struct AppState {
db_pool: PgPool,
jwt_key: EncodingKey,
pub struct AppState {
pub db_pool: PgPool,
pub jwt_key: EncodingKey,
}
#[tokio::main]
@ -34,9 +28,9 @@ 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 jwt_key = jwt::get_jwt_secret();
let pool = PgPoolOptions::new()
let db_pool = PgPoolOptions::new()
.max_connections(50)
.connect(&db_url)
.await
@ -44,15 +38,10 @@ async fn main() {
// build our application with a route
let app = Router::new()
// `GET /` goes to `root`
.route("/", get(root))
// `POST /users` goes to `create_user`
.route("/user/signup", post(signup))
.route("/user/signin", post(signin))
.with_state(AppState {
db_pool: pool,
jwt_key: EncodingKey::from_secret(jwt_secret.as_bytes()),
});
.with_state(AppState { db_pool, jwt_key });
// run our app with hyper
// `axum::Server` is a re-export of `hyper::Server`
@ -68,125 +57,3 @@ async fn main() {
async fn root() -> &'static str {
"Hello, World!"
}
async fn signup(
State(app_state): State<AppState>,
Json(signup): Json<Signup>,
) -> 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;
"#,
signup.username,
pass_hash.as_bytes(),
)
.fetch_one(&app_state.db_pool)
.await;
match result {
Ok(record) => (StatusCode::CREATED, Json(json!({"id": record.id}))),
Err(err) => {
// See if we get an error for a duplicate usename
if let sqlx::Error::Database(database_err) = &err {
if let Some(err_code) = database_err.code() {
if err_code == "23505" {
return (
StatusCode::BAD_REQUEST,
Json(json!({
"error": format!("username '{}' is taken.", signup.username)
})),
);
}
}
}
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({
"error": format!("Unknown error signing up: {}", err)
})),
)
}
}
}
async fn signin(
State(app_state): State<AppState>,
Json(signin): Json<Signin>,
) -> impl IntoResponse {
let pass_hash = sha256::digest(&*signin.password);
let result = sqlx::query!(
r#"
SELECT id, 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 {
id: user.id,
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 {
id: u64,
username: String,
}

View File

@ -1,9 +1,127 @@
use rocket::Route;
use axum::{extract::State, http::StatusCode, response::IntoResponse, Json};
use jsonwebtoken::Header;
use serde::{Deserialize, Serialize};
use serde_json::json;
pub fn routes() -> Vec<Route> {
routes![
use crate::{jwt::Claims, AppState};
]
#[derive(Clone, Debug, Deserialize)]
pub struct Signup {
username: String,
password: String,
}
#[derive(Clone, Debug, Deserialize)]
pub struct Signin {
username: String,
password: String,
}
#[derive(Serialize, sqlx::FromRow)]
struct User {
id: u64,
username: String,
}
pub async fn signup(
State(app_state): State<AppState>,
Json(signup): Json<Signup>,
) -> 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;
"#,
signup.username,
pass_hash.as_bytes(),
)
.fetch_one(&app_state.db_pool)
.await;
match result {
Ok(record) => (StatusCode::CREATED, Json(json!({"id": record.id}))),
Err(err) => {
// See if we get an error for a duplicate usename
if let sqlx::Error::Database(database_err) = &err {
if let Some(err_code) = database_err.code() {
if err_code == "23505" {
return (
StatusCode::BAD_REQUEST,
Json(json!({
"error": format!("username '{}' is taken.", signup.username)
})),
);
}
}
}
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({
"error": format!("Unknown error signing up: {}", err)
})),
)
}
}
}
pub async fn signin(
State(app_state): State<AppState>,
Json(signin): Json<Signin>,
) -> impl IntoResponse {
let pass_hash = sha256::digest(&*signin.password);
let result = sqlx::query!(
r#"
SELECT id, 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 {
id: user.id,
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)
})),
)
}
}
}