2023-08-14 07:38:37 +02:00
|
|
|
use std::sync::Arc;
|
|
|
|
|
2023-08-17 20:50:27 +02:00
|
|
|
use actix_jwt_session::{Authenticated, RedisMiddlewareFactory, HeaderExtractor, SessionStorage};
|
2023-08-14 07:38:37 +02:00
|
|
|
use actix_web::http::StatusCode;
|
2023-08-14 12:30:32 +02:00
|
|
|
use actix_web::web::{Data, Json};
|
2023-08-14 07:38:37 +02:00
|
|
|
use actix_web::HttpResponse;
|
2023-08-14 12:30:32 +02:00
|
|
|
use actix_web::{get, post};
|
2023-08-14 07:38:37 +02:00
|
|
|
use actix_web::{http::header::ContentType, test, App};
|
|
|
|
use jsonwebtoken::*;
|
|
|
|
use ring::rand::SystemRandom;
|
|
|
|
use ring::signature::{Ed25519KeyPair, KeyPair};
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use uuid::Uuid;
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
|
|
struct Claims {
|
|
|
|
id: Uuid,
|
2023-08-17 08:07:11 +02:00
|
|
|
subject: String,
|
2023-08-14 07:38:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
impl actix_jwt_session::Claims for Claims {
|
|
|
|
fn jti(&self) -> Uuid {
|
|
|
|
self.id
|
|
|
|
}
|
2023-08-17 08:07:11 +02:00
|
|
|
fn subject(&self) -> &str {
|
|
|
|
&self.subject
|
|
|
|
}
|
2023-08-14 07:38:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[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().unwrap();
|
|
|
|
let factory = RedisMiddlewareFactory::<Claims>::new(
|
2023-08-14 12:30:32 +02:00
|
|
|
Arc::new(keys.encoding_key),
|
2023-08-14 07:38:37 +02:00
|
|
|
Arc::new(keys.decoding_key),
|
2023-08-14 12:30:32 +02:00
|
|
|
Algorithm::EdDSA,
|
|
|
|
redis.clone(),
|
2023-08-17 08:07:11 +02:00
|
|
|
vec![Box::new(HeaderExtractor::new())]
|
2023-08-14 07:38:37 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
let app = App::new()
|
2023-08-17 20:50:27 +02:00
|
|
|
.app_data(Data::new(factory.storage()))
|
2023-08-14 07:38:37 +02:00
|
|
|
.wrap(factory.clone())
|
|
|
|
.app_data(Data::new(redis.clone()))
|
|
|
|
.service(sign_in)
|
|
|
|
.service(sign_out)
|
|
|
|
.service(session)
|
|
|
|
.service(root);
|
|
|
|
|
|
|
|
let app = actix_web::test::init_service(app).await;
|
|
|
|
|
2023-08-14 12:30:32 +02:00
|
|
|
let res = test::call_service(
|
|
|
|
&app,
|
|
|
|
test::TestRequest::default()
|
|
|
|
.insert_header(ContentType::plaintext())
|
|
|
|
.to_request(),
|
|
|
|
)
|
|
|
|
.await;
|
2023-08-14 07:38:37 +02:00
|
|
|
assert!(res.status().is_success());
|
|
|
|
|
2023-08-14 12:30:32 +02:00
|
|
|
let res = test::call_service(
|
|
|
|
&app,
|
|
|
|
test::TestRequest::default()
|
|
|
|
.uri("/s")
|
|
|
|
.insert_header(ContentType::plaintext())
|
|
|
|
.to_request(),
|
|
|
|
)
|
|
|
|
.await;
|
|
|
|
assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
|
|
|
|
|
2023-08-17 08:07:11 +02:00
|
|
|
let origina_claims = Claims { id: Uuid::new_v4(), subject: "foo".to_string() };
|
2023-08-14 12:30:32 +02:00
|
|
|
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);
|
2023-08-14 07:38:37 +02:00
|
|
|
}
|
|
|
|
|
2023-08-14 12:30:32 +02:00
|
|
|
#[post("/in")]
|
|
|
|
async fn sign_in(
|
2023-08-17 20:50:27 +02:00
|
|
|
store: Data<SessionStorage<Claims>>,
|
2023-08-14 12:30:32 +02:00
|
|
|
claims: Json<Claims>,
|
|
|
|
) -> Result<HttpResponse, actix_web::Error> {
|
|
|
|
let claims = claims.into_inner();
|
|
|
|
let store = store.into_inner();
|
|
|
|
store
|
|
|
|
.clone()
|
|
|
|
.set_by_jti(claims, std::time::Duration::from_secs(300))
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
Ok(HttpResponse::Ok().body(""))
|
2023-08-14 07:38:37 +02:00
|
|
|
}
|
|
|
|
|
2023-08-14 12:30:32 +02:00
|
|
|
#[post("/out")]
|
2023-08-17 20:50:27 +02:00
|
|
|
async fn sign_out(_store: Data<SessionStorage<Claims>>) -> HttpResponse {
|
2023-08-14 07:38:37 +02:00
|
|
|
HttpResponse::Ok().body("")
|
|
|
|
}
|
|
|
|
|
|
|
|
#[get("/s")]
|
|
|
|
async fn session(auth: Authenticated<Claims>) -> HttpResponse {
|
|
|
|
HttpResponse::Ok().json(&*auth)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[get("/")]
|
|
|
|
async fn root() -> HttpResponse {
|
|
|
|
HttpResponse::Ok().body("")
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct JwtSigningKeys {
|
|
|
|
encoding_key: EncodingKey,
|
|
|
|
decoding_key: DecodingKey,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl JwtSigningKeys {
|
|
|
|
fn generate() -> Result<Self, Box<dyn std::error::Error>> {
|
|
|
|
let doc = Ed25519KeyPair::generate_pkcs8(&SystemRandom::new())?;
|
|
|
|
let keypair = Ed25519KeyPair::from_pkcs8(doc.as_ref())?;
|
|
|
|
let encoding_key = EncodingKey::from_ed_der(doc.as_ref());
|
|
|
|
let decoding_key = DecodingKey::from_ed_der(keypair.public_key().as_ref());
|
|
|
|
Ok(JwtSigningKeys {
|
|
|
|
encoding_key,
|
|
|
|
decoding_key,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|