use std::sync::Arc; use actix_jwt_session::*; use actix_web::http::{Method, StatusCode}; use actix_web::web::{Data, Json}; use actix_web::HttpResponse; use actix_web::{get, post}; use actix_web::{http::header::ContentType, test, App}; use jsonwebtoken::*; use serde::{Deserialize, Serialize}; use uuid::Uuid; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] struct Claims { id: Uuid, subject: String, } impl actix_jwt_session::Claims for Claims { fn jti(&self) -> Uuid { self.id } fn subject(&self) -> &str { &self.subject } } #[tokio::test(flavor = "multi_thread")] async fn not_authenticated() { let redis = { use redis_async_pool::{RedisConnectionManager, RedisPool}; RedisPool::new( RedisConnectionManager::new( redis::Client::open("redis://localhost:6379").expect("Fail to connect to redis"), true, None, ), 5, ) }; let keys = JwtSigningKeys::generate(false).unwrap(); let (storage, factory) = RedisMiddlewareFactory::::build( Arc::new(keys.encoding_key), Arc::new(keys.decoding_key), Algorithm::EdDSA, ) .with_pool(redis.clone()) .with_jwt_header(JWT_HEADER_NAME) .with_refresh_header(REFRESH_HEADER_NAME) .with_jwt_cookie(JWT_COOKIE_NAME) .with_refresh_cookie(REFRESH_COOKIE_NAME) .finish(); let app = App::new() .app_data(Data::new(storage.clone())) .wrap(factory.clone()) .app_data(Data::new(redis.clone())) .app_data(Data::new(JwtTtl(Duration::days(10)))) .app_data(Data::new(RefreshTtl(Duration::days(10)))) .service(sign_in) .service(sign_out) .service(session) .service(root); let app = actix_web::test::init_service(app).await; let res = test::call_service( &app, test::TestRequest::default() .insert_header(ContentType::plaintext()) .to_request(), ) .await; assert!(res.status().is_success()); let res = test::call_service( &app, test::TestRequest::default() .uri("/s") .insert_header(ContentType::plaintext()) .to_request(), ) .await; assert_eq!(res.status(), StatusCode::UNAUTHORIZED); let origina_claims = Claims { id: Uuid::new_v4(), subject: "foo".to_string(), }; let res = test::call_service( &app, test::TestRequest::default() .uri("/in") .method(actix_web::http::Method::POST) .insert_header(ContentType::json()) .set_json(&origina_claims) .to_request(), ) .await; assert_eq!(res.status(), StatusCode::OK); let auth_bearer = res .headers() .get(JWT_HEADER_NAME) .unwrap() .to_str() .unwrap(); let refresh_bearer = res .headers() .get(REFRESH_HEADER_NAME) .unwrap() .to_str() .unwrap(); let res = test::call_service( &app, test::TestRequest::default() .uri("/s") .method(Method::GET) .insert_header((JWT_HEADER_NAME, auth_bearer)) .to_request(), ) .await; assert_eq!(res.status(), StatusCode::OK); let res = test::call_service( &app, test::TestRequest::default() .uri("/out") .method(Method::POST) .insert_header((JWT_HEADER_NAME, auth_bearer)) .to_request(), ) .await; assert_eq!(res.status(), StatusCode::OK); let res = test::try_call_service( &app, test::TestRequest::default() .uri("/s") .method(Method::GET) .insert_header((JWT_HEADER_NAME, auth_bearer)) .to_request(), ) .await; let err = res .expect_err("Must be unauthorized") .as_error::() .expect("Must be authorization error") .clone(); assert_eq!(err, actix_jwt_session::Error::InvalidSession); } #[post("/in")] async fn sign_in( store: Data, claims: Json, jwt_ttl: Data, refresh_ttl: Data, ) -> Result { let claims = claims.into_inner(); let store = store.into_inner(); let pair = store .clone() .store(claims, *jwt_ttl.into_inner(), *refresh_ttl.into_inner()) .await .unwrap(); Ok(HttpResponse::Ok() .append_header((JWT_HEADER_NAME, pair.jwt.encode().unwrap())) .append_header((REFRESH_HEADER_NAME, pair.refresh.encode().unwrap())) .finish()) } #[post("/out")] async fn sign_out(store: Data, auth: Authenticated) -> HttpResponse { let store = store.into_inner(); store.erase::(auth.id).await.unwrap(); HttpResponse::Ok().finish() } #[get("/s")] async fn session(auth: Authenticated) -> HttpResponse { HttpResponse::Ok().json(&*auth) } #[get("/")] async fn root() -> HttpResponse { HttpResponse::Ok().finish() }