1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
|
use argon2::{
password_hash::{rand_core::OsRng, SaltString},
Argon2, PasswordHasher,
};
use axum::{
extract::{Path, State},
http::StatusCode,
Json,
};
use axum_extra::routing::Resource;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::state::AppState;
use super::{error::Error, AccessClaims, RefreshClaims};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Credential {
pub password: String,
}
pub fn router() -> Resource<AppState> {
Resource::named("credentials")
.create(create)
.destroy(destroy)
}
pub async fn create(
State(state): State<AppState>,
Json(Credential { password }): Json<Credential>,
) -> Result<(StatusCode, (AccessClaims, RefreshClaims)), Error> {
let salt = SaltString::generate(&mut OsRng);
let password_hash = Argon2::default().hash_password(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();
if rows == 0 {
return Err(Error::UserNotFound);
} else if rows > 1 {
tracing::warn!("DELETE query affected {rows} rows. This should not happen.");
}
tx.commit().await?;
Ok(())
}
|