httpd: Remove session serialization, improve tests
Signed-off-by: Sebastian Martinez <me@sebastinez.dev>
This commit is contained in:
parent
58eeb3ab91
commit
d9f878bc45
|
|
@ -26,7 +26,7 @@ lexopt = { version = "0.2.1" }
|
|||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = { version = "1", features = ["preserve_order"] }
|
||||
thiserror = { version = "1" }
|
||||
time = { version = "0.3.17" }
|
||||
time = { version = "0.3.17", features = ["parsing", "serde"] }
|
||||
tokio = { version = "1.21", default-features = false, features = ["macros", "rt-multi-thread"] }
|
||||
tower-http = { version = "0.3.4", default-features = false, features = ["trace", "cors", "set-header"] }
|
||||
tracing = { version = "0.1.37", default-features = false, features = ["std", "log"] }
|
||||
|
|
|
|||
|
|
@ -1,27 +1,21 @@
|
|||
use radicle::crypto::PublicKey;
|
||||
use serde::{Serialize, Serializer};
|
||||
use time::OffsetDateTime;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use time::{serde::timestamp, OffsetDateTime};
|
||||
|
||||
#[derive(Clone, PartialEq, PartialOrd)]
|
||||
pub struct DateTime(pub OffsetDateTime);
|
||||
|
||||
impl Serialize for DateTime {
|
||||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
serializer.serialize_str(&format!("{}", self.0))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, PartialEq)]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum AuthState {
|
||||
Authorized,
|
||||
Unauthorized,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Session {
|
||||
pub status: AuthState,
|
||||
pub public_key: PublicKey,
|
||||
pub issued_at: DateTime,
|
||||
pub expires_at: DateTime,
|
||||
#[serde(with = "timestamp")]
|
||||
pub issued_at: OffsetDateTime,
|
||||
#[serde(with = "timestamp")]
|
||||
pub expires_at: OffsetDateTime,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,12 +4,13 @@ use axum::extract::State;
|
|||
use axum::response::IntoResponse;
|
||||
use axum::routing::{post, put};
|
||||
use axum::{Json, Router};
|
||||
use hyper::StatusCode;
|
||||
use radicle::crypto::{PublicKey, Signature};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use time::{Duration, OffsetDateTime};
|
||||
|
||||
use crate::api::auth::{AuthState, DateTime, Session};
|
||||
use crate::api::auth::{AuthState, Session};
|
||||
use crate::api::axum_extra::Path;
|
||||
use crate::api::error::Error;
|
||||
use crate::api::Context;
|
||||
|
|
@ -41,24 +42,26 @@ async fn session_create_handler(State(ctx): State<Context>) -> impl IntoResponse
|
|||
.take(32)
|
||||
.collect::<String>();
|
||||
let signer = ctx.profile.signer().map_err(Error::from)?;
|
||||
let expiration_time = OffsetDateTime::now_utc()
|
||||
.checked_add(UNAUTHORIZED_SESSIONS_EXPIRATION)
|
||||
.unwrap();
|
||||
let session = Session {
|
||||
status: AuthState::Unauthorized,
|
||||
public_key: *signer.public_key(),
|
||||
issued_at: DateTime(OffsetDateTime::now_utc()),
|
||||
expires_at: DateTime(expiration_time),
|
||||
issued_at: OffsetDateTime::now_utc(),
|
||||
expires_at: OffsetDateTime::now_utc()
|
||||
.checked_add(UNAUTHORIZED_SESSIONS_EXPIRATION)
|
||||
.unwrap(),
|
||||
};
|
||||
let mut sessions = ctx.sessions.write().await;
|
||||
sessions.insert(session_id.clone(), session.clone());
|
||||
|
||||
Ok::<_, Error>(Json(json!({
|
||||
"sessionId": session_id,
|
||||
"publicKey": session.public_key,
|
||||
"issuedAt": session.issued_at,
|
||||
"expiresAt": session.expires_at
|
||||
})))
|
||||
Ok::<_, Error>((
|
||||
StatusCode::CREATED,
|
||||
Json(json!({
|
||||
"sessionId": session_id,
|
||||
"publicKey": session.public_key,
|
||||
"issuedAt": session.issued_at,
|
||||
"expiresAt": session.expires_at
|
||||
})),
|
||||
))
|
||||
}
|
||||
|
||||
/// Get a session.
|
||||
|
|
@ -73,8 +76,8 @@ async fn session_handler(
|
|||
Ok::<_, Error>(Json(json!({
|
||||
"status": session.status,
|
||||
"publicKey": session.public_key,
|
||||
"issuedAt": session.issued_at,
|
||||
"expiresAt": session.expires_at
|
||||
"issuedAt": session.issued_at.unix_timestamp(),
|
||||
"expiresAt": session.expires_at.unix_timestamp()
|
||||
})))
|
||||
}
|
||||
|
||||
|
|
@ -86,12 +89,12 @@ async fn session_signin_handler(
|
|||
Json(request): Json<AuthChallenge>,
|
||||
) -> impl IntoResponse {
|
||||
let mut sessions = ctx.sessions.write().await;
|
||||
let session = sessions.get(&session_id).ok_or(Error::NotFound)?;
|
||||
let session = sessions.get_mut(&session_id).ok_or(Error::NotFound)?;
|
||||
if session.status == AuthState::Unauthorized {
|
||||
if session.public_key != request.pk {
|
||||
return Err(Error::Auth("Invalid public key"));
|
||||
}
|
||||
if session.expires_at <= DateTime(OffsetDateTime::now_utc()) {
|
||||
if session.expires_at <= OffsetDateTime::now_utc() {
|
||||
return Err(Error::Auth("Session expired"));
|
||||
}
|
||||
let payload = format!("{}:{}", session_id, request.pk);
|
||||
|
|
@ -99,16 +102,10 @@ async fn session_signin_handler(
|
|||
.pk
|
||||
.verify(payload.as_bytes(), &request.sig)
|
||||
.map_err(Error::from)?;
|
||||
let expiration_time = OffsetDateTime::now_utc()
|
||||
session.status = AuthState::Authorized;
|
||||
session.expires_at = OffsetDateTime::now_utc()
|
||||
.checked_add(AUTHORIZED_SESSIONS_EXPIRATION)
|
||||
.unwrap();
|
||||
let session = Session {
|
||||
status: AuthState::Authorized,
|
||||
public_key: request.pk,
|
||||
issued_at: session.issued_at.to_owned(),
|
||||
expires_at: DateTime(expiration_time),
|
||||
};
|
||||
sessions.insert(session_id.clone(), session);
|
||||
|
||||
return Ok::<_, Error>(Json(json!({ "success": true })));
|
||||
}
|
||||
|
|
@ -122,7 +119,10 @@ mod routes {
|
|||
use axum::http::StatusCode;
|
||||
use radicle_cli::commands::rad_web::{self, SessionInfo};
|
||||
|
||||
use crate::api::test::{self, get, post, put};
|
||||
use crate::api::{
|
||||
auth::{AuthState, Session},
|
||||
test::{self, get, post, put},
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_session() {
|
||||
|
|
@ -130,12 +130,19 @@ mod routes {
|
|||
let ctx = test::seed(tmp.path());
|
||||
let app = super::router(ctx.to_owned());
|
||||
|
||||
// Create session
|
||||
// Create session.
|
||||
let response = post(&app, "/sessions", None).await;
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
assert_eq!(response.status(), StatusCode::CREATED);
|
||||
let json = response.json().await;
|
||||
let session_info: SessionInfo = serde_json::from_value(json).unwrap();
|
||||
|
||||
// Check that an unauthorized session has been created.
|
||||
let response = get(&app, format!("/sessions/{}", session_info.session_id)).await;
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
let json = response.json().await;
|
||||
let body: Session = serde_json::from_value(json).unwrap();
|
||||
assert_eq!(body.status, AuthState::Unauthorized);
|
||||
|
||||
// Create request body
|
||||
let signer = ctx.profile.signer().unwrap();
|
||||
let signature = rad_web::sign(signer, &session_info).unwrap();
|
||||
|
|
@ -151,10 +158,13 @@ mod routes {
|
|||
Some(Body::from(body)),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
|
||||
// Check that session has been authorized.
|
||||
let response = get(&app, format!("/sessions/{}", session_info.session_id)).await;
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
let json = response.json().await;
|
||||
let body: Session = serde_json::from_value(json).unwrap();
|
||||
assert_eq!(body.status, AuthState::Authorized);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue