Returns new pair after refresh session
This commit is contained in:
parent
0666c74068
commit
96ddd2c51b
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -178,7 +178,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "actix-jwt-session"
|
||||
version = "1.0.0"
|
||||
version = "1.0.1"
|
||||
dependencies = [
|
||||
"actix-web",
|
||||
"argon2",
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-jwt-session"
|
||||
version = "1.0.0"
|
||||
version = "1.0.1"
|
||||
edition = "2021"
|
||||
description = "Full featured JWT session managment for actix"
|
||||
license = "MIT"
|
||||
|
@ -385,3 +385,7 @@ struct AccountModel {
|
||||
* Build-in hashing functions
|
||||
* Build-in TTL structures
|
||||
* Documentation
|
||||
|
||||
1.0.1
|
||||
|
||||
* Returns new pair after refresh lifetime
|
||||
|
@ -311,12 +311,46 @@
|
||||
//!
|
||||
//! #[get("/session/refresh")]
|
||||
//! async fn refresh_session(
|
||||
//! auth: Authenticated<RefreshToken>,
|
||||
//! refresh_token: Authenticated<RefreshToken>,
|
||||
//! storage: Data<SessionStorage>,
|
||||
//! ) -> HttpResponse {
|
||||
//! let storage = storage.into_inner();
|
||||
//! storage.refresh(auth.refresh_jti).await.unwrap();
|
||||
//! HttpResponse::Ok().json(&*auth)
|
||||
//! let s = storage.into_inner();
|
||||
//! let pair = match s.refresh::<AppClaims>(refresh_token.access_jti()).await {
|
||||
//! Err(e) => {
|
||||
//! tracing::warn!("Failed to refresh token: {e}");
|
||||
//! return HttpResponse::BadRequest().finish();
|
||||
//! }
|
||||
//! Ok(pair) => pair,
|
||||
//! };
|
||||
//!
|
||||
//! let encrypted_jwt = match pair.jwt.encode() {
|
||||
//! Ok(text) => text,
|
||||
//! Err(e) => {
|
||||
//! tracing::warn!("Failed to encode claims: {e}");
|
||||
//! return HttpResponse::InternalServerError().finish();
|
||||
//! }
|
||||
//! };
|
||||
//! let encrypted_refresh = match pair.refresh.encode() {
|
||||
//! Err(e) => {
|
||||
//! tracing::warn!("Failed to encode claims: {e}");
|
||||
//! return HttpResponse::InternalServerError().finish();
|
||||
//! }
|
||||
//! Ok(text) => text,
|
||||
//! };
|
||||
//! HttpResponse::Ok()
|
||||
//! .append_header((
|
||||
//! actix_jwt_session::JWT_HEADER_NAME,
|
||||
//! format!("Bearer {encrypted_jwt}").as_str(),
|
||||
//! ))
|
||||
//! .append_header((
|
||||
//! actix_jwt_session::REFRESH_HEADER_NAME,
|
||||
//! format!("Bearer {}", encrypted_refresh).as_str(),
|
||||
//! ))
|
||||
//! .append_header((
|
||||
//! "ACX-JWT-TTL",
|
||||
//! (pair.refresh.issues_at + pair.refresh.refresh_ttl.0).to_string(),
|
||||
//! ))
|
||||
//! .finish()
|
||||
//! }
|
||||
//!
|
||||
//! #[get("/")]
|
||||
@ -540,13 +574,20 @@ pub trait Claims:
|
||||
/// use actix_web::web::Data;
|
||||
/// use actix_jwt_session::*;
|
||||
///
|
||||
/// #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
/// pub struct AppClaims { id: uuid::Uuid, sub: String }
|
||||
/// impl actix_jwt_session::Claims for AppClaims {
|
||||
/// fn jti(&self) -> uuid::Uuid { self.id }
|
||||
/// fn subject(&self) -> &str { &self.sub }
|
||||
/// }
|
||||
///
|
||||
/// #[get("/session/refresh")]
|
||||
/// async fn refresh_session(
|
||||
/// auth: Authenticated<RefreshToken>,
|
||||
/// storage: Data<SessionStorage>,
|
||||
/// ) -> HttpResponse {
|
||||
/// let storage = storage.into_inner();
|
||||
/// storage.refresh(auth.refresh_jti).await.unwrap();
|
||||
/// storage.refresh::<AppClaims>(auth.refresh_jti).await.unwrap();
|
||||
/// HttpResponse::Ok().json(&*auth)
|
||||
/// }
|
||||
/// ```
|
||||
@ -687,16 +728,16 @@ impl actix_web::ResponseError for Error {
|
||||
/// use actix_web::HttpResponse;
|
||||
/// use actix_jwt_session::Authenticated;
|
||||
///
|
||||
/// # #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
/// # pub struct Claims { id: uuid::Uuid, sub: String }
|
||||
/// # impl actix_jwt_session::Claims for Claims {
|
||||
/// # fn jti(&self) -> uuid::Uuid { self.id }
|
||||
/// # fn subject(&self) -> &str { &self.sub }
|
||||
/// # }
|
||||
/// #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
/// pub struct AppClaims { id: uuid::Uuid, sub: String }
|
||||
/// impl actix_jwt_session::Claims for AppClaims {
|
||||
/// fn jti(&self) -> uuid::Uuid { self.id }
|
||||
/// fn subject(&self) -> &str { &self.sub }
|
||||
/// }
|
||||
///
|
||||
/// // If there's no JWT in request server will automatically returns 401
|
||||
/// #[get("/session")]
|
||||
/// async fn read_session(session: Authenticated<Claims>) -> HttpResponse {
|
||||
/// async fn read_session(session: Authenticated<AppClaims>) -> HttpResponse {
|
||||
/// let encoded = session.encode().unwrap(); // JWT as encrypted string
|
||||
/// HttpResponse::Ok().finish()
|
||||
/// }
|
||||
@ -955,30 +996,47 @@ impl SessionStorage {
|
||||
/// ```
|
||||
/// use actix_jwt_session::SessionStorage;
|
||||
/// use actix_web::{Error, HttpResponse};
|
||||
/// # #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
/// # pub struct Claims { id: uuid::Uuid, sub: String }
|
||||
/// # impl actix_jwt_session::Claims for Claims {
|
||||
/// # fn jti(&self) -> uuid::Uuid { self.id }
|
||||
/// # fn subject(&self) -> &str { &self.sub }
|
||||
/// # }
|
||||
///
|
||||
/// #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
/// pub struct AppClaims { id: uuid::Uuid, sub: String }
|
||||
/// impl actix_jwt_session::Claims for AppClaims {
|
||||
/// fn jti(&self) -> uuid::Uuid { self.id }
|
||||
/// fn subject(&self) -> &str { &self.sub }
|
||||
/// }
|
||||
///
|
||||
/// async fn extend_tokens_lifetime(
|
||||
/// session_storage: SessionStorage,
|
||||
/// jti: uuid::Uuid
|
||||
/// ) -> Result<HttpResponse, Error> {
|
||||
/// session_storage.refresh(jti).await?;
|
||||
/// session_storage.refresh::<AppClaims>(jti).await?;
|
||||
/// Ok(HttpResponse::Ok().finish())
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn refresh(&self, refresh_jti: uuid::Uuid) -> Result<(), Error> {
|
||||
pub async fn refresh<ClaimsType: Claims>(
|
||||
&self,
|
||||
refresh_jti: uuid::Uuid,
|
||||
) -> Result<Pair<ClaimsType>, Error> {
|
||||
let mut record = self.load_pair_by_refresh(refresh_jti).await?;
|
||||
let mut refresh_token = record.refresh_token()?;
|
||||
let ttl = refresh_token.refresh_ttl;
|
||||
refresh_token.issues_at = OffsetDateTime::now_utc();
|
||||
record.set_refresh_token(refresh_token)?;
|
||||
self.store_pair(record, ttl).await?;
|
||||
self.store_pair(record.clone(), ttl).await?;
|
||||
|
||||
Ok(())
|
||||
let claims = SessionRecord::from_field::<ClaimsType>(&record.jwt)?;
|
||||
let refresh = SessionRecord::from_field::<RefreshToken>(&record.refresh_token)?;
|
||||
Ok(Pair {
|
||||
jwt: Authenticated {
|
||||
claims: Arc::new(claims),
|
||||
jwt_encoding_key: self.jwt_encoding_key.clone(),
|
||||
algorithm: self.algorithm,
|
||||
},
|
||||
refresh: Authenticated {
|
||||
claims: Arc::new(refresh),
|
||||
jwt_encoding_key: self.jwt_encoding_key.clone(),
|
||||
algorithm: self.algorithm,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/// Save claims in storage in a way claims can be loaded from database using `jti` as [uuid::Uuid] (JWT ID)
|
||||
|
@ -11,7 +11,7 @@ use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Session middleware factory builder
|
||||
///
|
||||
///
|
||||
/// It should be constructed with [SessionMiddlewareFactory::build].
|
||||
pub struct SessionMiddlewareBuilder<ClaimsType: Claims> {
|
||||
pub(crate) jwt_encoding_key: Arc<EncodingKey>,
|
||||
@ -108,7 +108,7 @@ impl<ClaimsType: Claims> SessionMiddlewareBuilder<ClaimsType> {
|
||||
/// Factory creates middlware for every single request.
|
||||
///
|
||||
/// All fields here are immutable and have atomic access and only pointer is copied so are very cheap
|
||||
///
|
||||
///
|
||||
/// Example:
|
||||
///
|
||||
/// ```
|
||||
@ -128,7 +128,7 @@ impl<ClaimsType: Claims> SessionMiddlewareBuilder<ClaimsType> {
|
||||
/// 5,
|
||||
/// )
|
||||
/// };
|
||||
///
|
||||
///
|
||||
/// // load or create new keys in `./config`
|
||||
/// let keys = JwtSigningKeys::load_or_create();
|
||||
///
|
||||
|
@ -229,7 +229,7 @@ async fn refresh_session(
|
||||
storage: Data<SessionStorage>,
|
||||
) -> HttpResponse {
|
||||
let storage = storage.into_inner();
|
||||
storage.refresh(auth.refresh_jti).await.unwrap();
|
||||
storage.refresh::<Claims>(auth.refresh_jti).await.unwrap();
|
||||
HttpResponse::Ok().json(&*auth)
|
||||
}
|
||||
|
||||
|
@ -300,6 +300,10 @@ async fn login_inner(
|
||||
actix_jwt_session::REFRESH_HEADER_NAME,
|
||||
format!("Bearer {}", encrypted_refresh).as_str(),
|
||||
))
|
||||
.append_header((
|
||||
"ACX-JWT-TTL",
|
||||
(pair.refresh.issues_at + pair.refresh.refresh_ttl.0).to_string(),
|
||||
))
|
||||
.append_header(("HX-Redirect", "/"))
|
||||
.append_header(("HX-Retarget", "main"))
|
||||
.cookie(
|
||||
@ -350,13 +354,42 @@ async fn refresh_token(
|
||||
storage: Data<SessionStorage>,
|
||||
) -> HttpResponse {
|
||||
let s = storage.into_inner();
|
||||
match s.refresh(refresh_token.access_jti()).await {
|
||||
let pair = match s.refresh::<Claims>(refresh_token.access_jti()).await {
|
||||
Err(e) => {
|
||||
tracing::warn!("Failed to refresh token: {e}");
|
||||
HttpResponse::BadRequest().finish()
|
||||
return HttpResponse::BadRequest().finish();
|
||||
}
|
||||
Ok(_) => HttpResponse::Ok().finish(),
|
||||
}
|
||||
Ok(pair) => pair,
|
||||
};
|
||||
|
||||
let encrypted_jwt = match pair.jwt.encode() {
|
||||
Ok(text) => text,
|
||||
Err(e) => {
|
||||
tracing::warn!("Failed to encode claims: {e}");
|
||||
return HttpResponse::InternalServerError().finish();
|
||||
}
|
||||
};
|
||||
let encrypted_refresh = match pair.refresh.encode() {
|
||||
Err(e) => {
|
||||
tracing::warn!("Failed to encode claims: {e}");
|
||||
return HttpResponse::InternalServerError().finish();
|
||||
}
|
||||
Ok(text) => text,
|
||||
};
|
||||
HttpResponse::Ok()
|
||||
.append_header((
|
||||
actix_jwt_session::JWT_HEADER_NAME,
|
||||
format!("Bearer {encrypted_jwt}").as_str(),
|
||||
))
|
||||
.append_header((
|
||||
actix_jwt_session::REFRESH_HEADER_NAME,
|
||||
format!("Bearer {}", encrypted_refresh).as_str(),
|
||||
))
|
||||
.append_header((
|
||||
"ACX-JWT-TTL",
|
||||
(pair.refresh.issues_at + pair.refresh.refresh_ttl.0).to_string(),
|
||||
))
|
||||
.finish()
|
||||
}
|
||||
|
||||
#[autometrics]
|
||||
|
Loading…
Reference in New Issue
Block a user