diff options
author | Toby Vincent <tobyv@tobyvin.dev> | 2024-04-16 13:56:26 -0500 |
---|---|---|
committer | Toby Vincent <tobyv@tobyvin.dev> | 2024-04-16 13:56:26 -0500 |
commit | e607eb77d4253adfb15c8a4ce08684e16ae96674 (patch) | |
tree | 921e6d002d9e3dc761f5d1bb7fea82abd2045919 /src/auth | |
parent | 469cbc20853bcae0e74922f16f7a969d1b7a9a67 (diff) |
refactor(auth): move credential resource to module
Diffstat (limited to 'src/auth')
-rw-r--r-- | src/auth/credentials.rs | 66 | ||||
-rw-r--r-- | src/auth/jwt.rs | 23 |
2 files changed, 89 insertions, 0 deletions
diff --git a/src/auth/credentials.rs b/src/auth/credentials.rs new file mode 100644 index 0000000..7f92048 --- /dev/null +++ b/src/auth/credentials.rs @@ -0,0 +1,66 @@ +use argon2::{ + password_hash::{rand_core::OsRng, SaltString}, + Argon2, PasswordHasher, +}; +use axum::{ + extract::{Path, State}, + http::StatusCode, +}; +use axum_extra::{ + headers::{authorization::Basic, Authorization}, + routing::Resource, + TypedHeader, +}; +use uuid::Uuid; + +use crate::state::AppState; + +use super::{error::Error, AccessClaims, RefreshClaims}; + +pub fn router() -> Resource<AppState> { + Resource::named("credentials") + .create(create) + .destroy(destroy) +} + +pub async fn create( + State(state): State<AppState>, + TypedHeader(Authorization(basic)): TypedHeader<Authorization<Basic>>, +) -> Result<(StatusCode, (AccessClaims, RefreshClaims)), Error> { + let salt = SaltString::generate(&mut OsRng); + let password_hash = Argon2::default().hash_password(basic.password().as_bytes(), &salt)?; + + let uuid = sqlx::query!( + "INSERT INTO credential (password_hash) VALUES ($1) RETURNING id", + password_hash.to_string() + ) + .fetch_optional(&state.pool) + .await? + .ok_or(Error::Registration)? + .id; + + let refresh = RefreshClaims::issue(uuid); + let access = refresh.refresh(); + + Ok((StatusCode::CREATED, (access, refresh))) +} + +pub async fn destroy(State(state): State<AppState>, Path(uuid): Path<Uuid>) -> Result<(), Error> { + let mut tx = state.pool.begin().await?; + let rows = sqlx::query!("DELETE FROM credential WHERE id = $1", uuid) + .execute(&mut *tx) + .await? + .rows_affected(); + + match rows { + 0 => Err(Error::UserNotFound), + 1 => { + tx.commit().await?; + Ok(()) + } + _ => { + tracing::error!("Delete query affected {rows} rows. This should not happen."); + Ok(()) + } + } +} diff --git a/src/auth/jwt.rs b/src/auth/jwt.rs index 9d70b94..0d7b593 100644 --- a/src/auth/jwt.rs +++ b/src/auth/jwt.rs @@ -40,3 +40,26 @@ impl Jwt { jsonwebtoken::decode(token, &self.decoding, &self.validation).map_err(Into::into) } } + +#[cfg(test)] +mod tests { + use super::*; + + use crate::{ + auth::AccessClaims, + tests::{setup_test_env, TestResult}, + }; + + #[test] + fn test_jwt_encode_decode() -> TestResult { + setup_test_env(); + + let claims = AccessClaims::issue(uuid::Uuid::new_v4()); + let token = JWT.encode(&claims)?; + let decoded = JWT.decode(&token)?.claims; + + assert_eq!(claims, decoded); + + Ok(()) + } +} |