Include require auth middleware and login route
This commit is contained in:
@@ -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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user