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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
|
use argon2::{Argon2, PasswordHash, PasswordVerifier};
use axum::extract::State;
use axum_extra::{
headers::{authorization::Basic, Authorization},
TypedHeader,
};
use uuid::Uuid;
use crate::state::AppState;
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() -> axum::Router<AppState> {
use axum::routing::get;
axum::Router::new()
.route("/issue", get(issue))
.route("/refresh", get(refresh))
.merge(credentials::router())
}
pub async fn issue(
State(state): State<AppState>,
TypedHeader(Authorization(basic)): TypedHeader<Authorization<Basic>>,
) -> Result<(AccessClaims, RefreshClaims), Error> {
let uuid = Uuid::try_parse(basic.username())?;
let p: String = sqlx::query_scalar!("SELECT password_hash FROM credential WHERE id = $1", uuid)
.fetch_optional(&state.pool)
.await?
.ok_or(Error::LoginInvalid)?;
Argon2::default().verify_password(basic.password().as_bytes(), &PasswordHash::new(&p)?)?;
let refresh = RefreshClaims::issue(uuid);
let access = refresh.refresh();
Ok((access, refresh))
}
pub async fn refresh(claims: RefreshClaims) -> AccessClaims {
claims.refresh()
}
#[cfg(test)]
mod tests {
use super::*;
use axum::{
body::Body,
http::{header::AUTHORIZATION, Request, StatusCode},
Router,
};
use axum_extra::headers::authorization::Credentials;
use sqlx::PgPool;
use tower::ServiceExt;
use crate::tests::{setup_test_env, TestResult};
#[sqlx::test(fixtures(path = "../fixtures", scripts("users")))]
async fn test_issue_ok(pool: PgPool) -> TestResult {
setup_test_env();
let router = Router::new().merge(router()).with_state(AppState { pool });
let auth = Authorization::basic(
"4c14f795-86f0-4361-a02f-0edb966fb145",
"solongandthanksforallthefish",
);
let request = Request::builder()
.uri("/issue")
.method("GET")
.header(AUTHORIZATION, auth.0.encode())
.body(Body::empty())?;
let response = router.oneshot(request).await?;
assert_eq!(StatusCode::OK, response.status());
Ok(())
}
#[sqlx::test(fixtures(path = "../fixtures", scripts("users")))]
async fn test_issue_unauthorized(pool: PgPool) -> TestResult {
setup_test_env();
let router = Router::new().merge(router()).with_state(AppState { pool });
let auth = Authorization::basic("4c14f795-86f0-4361-a02f-0edb966fb145", "hunter2");
let request = Request::builder()
.uri("/issue")
.method("GET")
.header(AUTHORIZATION, auth.0.encode())
.body(Body::empty())?;
let response = router.oneshot(request).await?;
assert_eq!(StatusCode::UNAUTHORIZED, response.status());
Ok(())
}
}
|