summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorToby Vincent <tobyv@tobyvin.dev>2024-04-16 13:56:26 -0500
committerToby Vincent <tobyv@tobyvin.dev>2024-04-16 13:56:26 -0500
commite607eb77d4253adfb15c8a4ce08684e16ae96674 (patch)
tree921e6d002d9e3dc761f5d1bb7fea82abd2045919
parent469cbc20853bcae0e74922f16f7a969d1b7a9a67 (diff)
refactor(auth): move credential resource to module
-rw-r--r--src/api/users.rs4
-rw-r--r--src/auth.rs51
-rw-r--r--src/auth/credentials.rs66
-rw-r--r--src/auth/jwt.rs23
4 files changed, 100 insertions, 44 deletions
diff --git a/src/api/users.rs b/src/api/users.rs
index 45d69fe..7b378ef 100644
--- a/src/api/users.rs
+++ b/src/api/users.rs
@@ -57,8 +57,8 @@ pub async fn create(
return Err(Error::EmailExists);
}
- // TODO: Move this into a tower service
- let (status, (access, refresh)) = crate::auth::create(
+ // TODO: Move this into a micro service
+ let (status, (access, refresh)) = crate::auth::credentials::create(
State(state.clone()),
TypedHeader(Authorization::basic(&email, &password)),
)
diff --git a/src/auth.rs b/src/auth.rs
index d2cfb3e..a27deb2 100644
--- a/src/auth.rs
+++ b/src/auth.rs
@@ -1,11 +1,7 @@
-use argon2::{
- password_hash::{rand_core::OsRng, SaltString},
- Argon2, PasswordHash, PasswordHasher, PasswordVerifier,
-};
-use axum::{extract::State, http::StatusCode, Router};
+use argon2::{Argon2, PasswordHash, PasswordVerifier};
+use axum::{extract::State, routing::get, Router};
use axum_extra::{
headers::{authorization::Basic, Authorization},
- routing::Resource,
TypedHeader,
};
use uuid::Uuid;
@@ -17,11 +13,15 @@ use self::{error::Error, jwt::JWT};
pub use self::claims::{AccessClaims, RefreshClaims};
pub mod claims;
+pub mod credentials;
pub mod error;
pub mod jwt;
pub fn router() -> Router<AppState> {
- axum::Router::new().merge(Resource::named("users").index(issue).create(create))
+ Router::new()
+ .route("/issue", get(issue))
+ .route("/refresh", get(refresh))
+ .merge(credentials::router())
}
pub async fn issue(
@@ -42,28 +42,6 @@ pub async fn issue(
Ok((access, refresh))
}
-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 refresh(claims: RefreshClaims) -> AccessClaims {
claims.refresh()
}
@@ -83,17 +61,6 @@ mod tests {
use crate::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(())
- }
-
#[sqlx::test(fixtures(path = "../fixtures", scripts("users")))]
async fn test_issue_ok(pool: PgPool) -> TestResult {
setup_test_env();
@@ -106,7 +73,7 @@ mod tests {
);
let request = Request::builder()
- .uri("/users")
+ .uri("/issue")
.method("GET")
.header(AUTHORIZATION, auth.0.encode())
.body(Body::empty())?;
@@ -127,7 +94,7 @@ mod tests {
let auth = Authorization::basic("4c14f795-86f0-4361-a02f-0edb966fb145", "hunter2");
let request = Request::builder()
- .uri("/users")
+ .uri("/issue")
.method("GET")
.header(AUTHORIZATION, auth.0.encode())
.body(Body::empty())?;
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(())
+ }
+}