Include require auth middleware and login route

This commit is contained in:
GW_MC
2025-12-18 18:25:57 +08:00
parent b0c11c7c67
commit ccd8bc7aa1
15 changed files with 326 additions and 39 deletions

View File

@@ -28,13 +28,16 @@ pub struct Claims {
#[async_trait::async_trait]
pub trait AuthenticationService: Send + Sync {
async fn generate_jwt(&self, user_id: Uuid, duration_secs: u64)
-> Result<String, ServiceError>;
async fn generate_jwt(
&self,
user_id: Uuid,
duration_secs: u64,
) -> Result<(String, Claims), ServiceError>;
async fn is_valid_jwt(
&self,
token: &str,
target_sub: Option<String>,
) -> Result<bool, ServiceError>;
) -> Result<Option<Claims>, ServiceError>;
async fn parse_jwt(&self, token: &str) -> Result<Claims, ServiceError>;
async fn invalidate_jwt(&self, token: &str) -> Result<(), ServiceError>;
async fn refresh_jwt(&self, token: &str, duration_secs: u64) -> Result<String, ServiceError>;
@@ -76,7 +79,7 @@ impl AuthenticationService for AuthenticationServiceImpl {
&self,
user_id: Uuid,
duration_secs: u64,
) -> Result<String, ServiceError> {
) -> Result<(String, Claims), ServiceError> {
let header = Header::default();
let expiration = chrono::Utc::now()
.checked_add_signed(chrono::Duration::seconds(duration_secs as i64))
@@ -95,23 +98,23 @@ impl AuthenticationService for AuthenticationServiceImpl {
&EncodingKey::from_secret(self.secret.as_ref()),
)
.map_err(|e| ServiceError::InternalError(format!("JWT generation error: {}", e)))?;
Ok(token)
Ok((token, claims))
}
async fn is_valid_jwt(
&self,
token: &str,
target_sub: Option<String>,
) -> Result<bool, ServiceError> {
) -> Result<Option<Claims>, ServiceError> {
let mut validation = Validation::default();
if let Some(expected_sub) = target_sub {
validation.sub = Some(expected_sub);
}
let decoding_key = DecodingKey::from_secret(self.secret.as_ref());
match decode::<Claims>(token, &decoding_key, &validation) {
Ok(_) => Ok(true),
Ok(data) => Ok(Some(data.claims)),
Err(err) => match *err.kind() {
InvalidToken | InvalidSubject | ExpiredSignature => Ok(false),
InvalidToken | InvalidSubject | ExpiredSignature => Ok(None),
_ => Err(ServiceError::InternalError(format!(
"JWT validation error: {}",
err
@@ -156,7 +159,7 @@ impl AuthenticationService for AuthenticationServiceImpl {
let user_id = Uuid::parse_str(&claims.sub).map_err(|e| {
ServiceError::InternalError(format!("Invalid user ID in JWT claims: {}", e))
})?;
let new_token = self.generate_jwt(user_id, duration_secs).await?;
let (new_token, _) = self.generate_jwt(user_id, duration_secs).await?;
Ok(new_token)
}
@@ -181,7 +184,7 @@ mod tests {
let service = AuthenticationServiceImpl::new(Some("secret".to_string()));
let user_id = Uuid::new_v4();
let token = service
let (token, _) = service
.generate_jwt(user_id, 60)
.await
.expect("generate jwt");
@@ -190,8 +193,7 @@ mod tests {
.is_valid_jwt(&token, None)
.await
.expect("validate jwt");
assert!(valid, "Generated token should be valid");
assert!(valid.is_some(), "Generated token should be valid");
let claims = service.parse_jwt(&token).await.expect("parse jwt");
assert_eq!(claims.sub, user_id.to_string());
}
@@ -201,11 +203,14 @@ mod tests {
let service = AuthenticationServiceImpl::new(Some("secret".to_string()));
let user_id = Uuid::new_v4();
let token = service.generate_jwt(user_id, 60).await.unwrap();
let (token, _) = service.generate_jwt(user_id, 60).await.unwrap();
let other_sub = Uuid::new_v4().to_string();
let valid = service.is_valid_jwt(&token, Some(other_sub)).await.unwrap();
assert!(!valid, "Token should be invalid for a different subject");
assert!(
valid.is_none(),
"Token should be invalid for a different subject"
);
}
#[tokio::test]
@@ -221,7 +226,7 @@ mod tests {
let service = AuthenticationServiceImpl::new(Some("secret".to_string()));
let user_id = Uuid::new_v4();
let token = service.generate_jwt(user_id, 60).await.unwrap();
let (token, _) = service.generate_jwt(user_id, 60).await.unwrap();
let new_token = service.refresh_jwt(&token, 120).await.unwrap();
let claims = service.parse_jwt(&new_token).await.unwrap();
@@ -234,11 +239,11 @@ mod tests {
let service = AuthenticationServiceImpl::new(Some("secret".to_string()));
let user_id = Uuid::new_v4();
let token = service.generate_jwt(user_id, 1).await.unwrap();
let (token, _) = service.generate_jwt(user_id, 1).await.unwrap();
sleep(Duration::from_secs(2)).await;
let valid = service.is_valid_jwt(&token, None).await.unwrap();
assert!(!valid, "Token should be expired and thus invalid");
assert!(valid.is_none(), "Token should be expired and thus invalid");
}
#[tokio::test]
@@ -246,7 +251,7 @@ mod tests {
let service = AuthenticationServiceImpl::new(Some("secret".to_string()));
let user_id = Uuid::new_v4();
let token = service.generate_jwt(user_id, 1).await.unwrap();
let (token, _) = service.generate_jwt(user_id, 1).await.unwrap();
service.invalidate_jwt(&token).await.unwrap();