login and signup
This commit is contained in:
33
Cargo.lock
generated
33
Cargo.lock
generated
@@ -70,6 +70,7 @@ name = "api"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"argon2",
|
||||||
"axum",
|
"axum",
|
||||||
"chrono",
|
"chrono",
|
||||||
"dotenv",
|
"dotenv",
|
||||||
@@ -93,6 +94,18 @@ dependencies = [
|
|||||||
"derive_arbitrary",
|
"derive_arbitrary",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "argon2"
|
||||||
|
version = "0.5.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072"
|
||||||
|
dependencies = [
|
||||||
|
"base64ct",
|
||||||
|
"blake2",
|
||||||
|
"cpufeatures",
|
||||||
|
"password-hash",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-trait"
|
name = "async-trait"
|
||||||
version = "0.1.83"
|
version = "0.1.83"
|
||||||
@@ -222,6 +235,15 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "blake2"
|
||||||
|
version = "0.10.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
|
||||||
|
dependencies = [
|
||||||
|
"digest",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "block-buffer"
|
name = "block-buffer"
|
||||||
version = "0.10.4"
|
version = "0.10.4"
|
||||||
@@ -1039,6 +1061,17 @@ dependencies = [
|
|||||||
"windows-targets 0.52.6",
|
"windows-targets 0.52.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "password-hash"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
|
||||||
|
dependencies = [
|
||||||
|
"base64ct",
|
||||||
|
"rand_core",
|
||||||
|
"subtle",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "paste"
|
name = "paste"
|
||||||
version = "1.0.15"
|
version = "1.0.15"
|
||||||
|
|||||||
@@ -23,3 +23,4 @@ sqlx = { version = "0.8.2", features = [
|
|||||||
uuid = "1.11.0"
|
uuid = "1.11.0"
|
||||||
chrono = "0.4.39"
|
chrono = "0.4.39"
|
||||||
dotenv = "0.15.0"
|
dotenv = "0.15.0"
|
||||||
|
argon2 = "0.5.3"
|
||||||
|
|||||||
45
src/v1/auth/login.rs
Normal file
45
src/v1/auth/login.rs
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
use crate::*;
|
||||||
|
use argon2::{
|
||||||
|
password_hash::{PasswordHash, PasswordVerifier},
|
||||||
|
Argon2,
|
||||||
|
};
|
||||||
|
use sqlx::query;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, ToSchema)]
|
||||||
|
pub struct LoginBody {
|
||||||
|
username: String,
|
||||||
|
password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Login
|
||||||
|
#[utoipa::path(post, path = "/login", responses((status = OK, body = String)), tag = super::AUTH_TAG)]
|
||||||
|
pub async fn login(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
Json(body): Json<LoginBody>,
|
||||||
|
) -> Result<String, AppError> {
|
||||||
|
let user = query!(
|
||||||
|
"SELECT id, real_name, username, email, password_hash, is_admin, created_at
|
||||||
|
FROM users
|
||||||
|
WHERE username = $1",
|
||||||
|
body.username,
|
||||||
|
)
|
||||||
|
.fetch_one(&*state.db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let argon2 = Argon2::default();
|
||||||
|
let hash = PasswordHash::new(&body.password).expect("Password hashing failed");
|
||||||
|
|
||||||
|
if !argon2
|
||||||
|
.verify_password(body.password.as_bytes(), &hash)
|
||||||
|
.is_ok()
|
||||||
|
{
|
||||||
|
return Err(AppError::Error(Errors::Unauthorized));
|
||||||
|
}
|
||||||
|
|
||||||
|
// technically I should be using a JWT and returning it to the client
|
||||||
|
// since this will be a mobile app.
|
||||||
|
|
||||||
|
// let jwt = ...
|
||||||
|
|
||||||
|
Ok(user.id.to_string())
|
||||||
|
}
|
||||||
@@ -1,13 +1,15 @@
|
|||||||
use crate::state::AppState;
|
use crate::AppState;
|
||||||
|
|
||||||
pub(super) use super::*;
|
pub(super) use super::*;
|
||||||
|
|
||||||
pub mod signup;
|
pub mod signup;
|
||||||
|
pub mod login;
|
||||||
|
|
||||||
pub const AUTH_TAG: &str = "auth";
|
pub const AUTH_TAG: &str = "auth";
|
||||||
|
|
||||||
pub fn router(state: AppState) -> OpenApiRouter {
|
pub fn router(state: AppState) -> OpenApiRouter {
|
||||||
OpenApiRouter::new()
|
OpenApiRouter::new()
|
||||||
.routes(routes!(signup::signup))
|
.routes(routes!(signup::signup))
|
||||||
|
.routes(routes!(login::login))
|
||||||
.with_state(state)
|
.with_state(state)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
use crate::*;
|
use crate::*;
|
||||||
use sqlx::query;
|
use sqlx::query;
|
||||||
|
|
||||||
|
use argon2::{
|
||||||
|
password_hash::{rand_core::OsRng, PasswordHasher, SaltString},
|
||||||
|
Argon2,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, ToSchema)]
|
#[derive(Serialize, Deserialize, ToSchema)]
|
||||||
pub struct SignupBody {
|
pub struct SignupBody {
|
||||||
real_name: String,
|
real_name: String,
|
||||||
@@ -10,11 +15,19 @@ pub struct SignupBody {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Sign up
|
/// Sign up
|
||||||
#[utoipa::path(get, path = "/signup", responses((status = OK, body = String)), tag = super::AUTH_TAG)]
|
#[utoipa::path(post, path = "/signup", responses((status = OK, body = String)), tag = super::AUTH_TAG)]
|
||||||
pub async fn signup(
|
pub async fn signup(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Json(body): Json<SignupBody>,
|
Json(body): Json<SignupBody>,
|
||||||
) -> Result<String, AppError> {
|
) -> Result<String, AppError> {
|
||||||
|
let salt = SaltString::generate(&mut OsRng);
|
||||||
|
let argon2 = Argon2::default();
|
||||||
|
|
||||||
|
let hash = argon2
|
||||||
|
.hash_password(body.password.as_bytes(), &salt)
|
||||||
|
.expect("Password hashing failed")
|
||||||
|
.to_string();
|
||||||
|
|
||||||
let user = query!(
|
let user = query!(
|
||||||
"INSERT INTO users (real_name, username, email, password_hash)
|
"INSERT INTO users (real_name, username, email, password_hash)
|
||||||
VALUES ($1, $2, $3, $4)
|
VALUES ($1, $2, $3, $4)
|
||||||
@@ -22,7 +35,7 @@ pub async fn signup(
|
|||||||
body.real_name,
|
body.real_name,
|
||||||
body.username,
|
body.username,
|
||||||
body.email,
|
body.email,
|
||||||
body.password,
|
hash
|
||||||
)
|
)
|
||||||
.fetch_one(&*state.db)
|
.fetch_one(&*state.db)
|
||||||
.await?;
|
.await?;
|
||||||
|
|||||||
Reference in New Issue
Block a user