diff --git a/src/actors/openid_sessions_actor.rs b/src/actors/openid_sessions_actor.rs index abac6c7..e611a15 100644 --- a/src/actors/openid_sessions_actor.rs +++ b/src/actors/openid_sessions_actor.rs @@ -2,9 +2,14 @@ use actix::{Actor, AsyncContext, Context, Handler}; use actix::Message; use crate::constants::*; +use crate::data::access_token::AccessToken; +use crate::data::app_config::AppConfig; use crate::data::client::ClientID; use crate::data::code_challenge::CodeChallenge; +use crate::data::jwt_signer::JWTSigner; use crate::data::user::UserID; +use crate::utils::err::Res; +use crate::utils::string_utils::rand_str; use crate::utils::time::time; #[derive(Clone, Debug, serde::Serialize, serde::Deserialize, Eq, PartialEq)] @@ -20,9 +25,8 @@ pub struct Session { pub authorization_code: String, pub authorization_code_expire_at: u64, - pub authorization_code_used: bool, - pub access_token: String, + pub access_token: Option, pub access_token_expire_at: u64, pub refresh_token: String, pub refresh_token_expire_at: u64, @@ -36,6 +40,26 @@ impl Session { self.authorization_code_expire_at < time() && self.access_token_expire_at < time() && self.refresh_token_expire_at < time() } + + pub fn regenerate_access_and_refresh_tokens(&mut self, + app_config: &AppConfig, + jwt_signer: &JWTSigner) -> Res { + let access_token = AccessToken { + issuer: app_config.website_origin.to_string(), + subject_identifier: self.user.clone(), + issued_at: time(), + exp_time: time() + OPEN_ID_ACCESS_TOKEN_TIMEOUT, + rand_val: rand_str(OPEN_ID_ACCESS_TOKEN_LEN), + nonce: self.nonce.clone(), + }; + self.access_token_expire_at = access_token.exp_time; + self.access_token = Some(jwt_signer.sign_token(access_token.to_jwt_claims())?); + + self.refresh_token = rand_str(OPEN_ID_REFRESH_TOKEN_LEN); + self.refresh_token_expire_at = OPEN_ID_REFRESH_TOKEN_TIMEOUT + time(); + + Ok(()) + } } #[derive(Message)] @@ -54,10 +78,6 @@ pub struct FindSessionByRefreshToken(pub String); #[rtype(result = "Option")] pub struct FindSessionByAccessToken(pub String); -#[derive(Message)] -#[rtype(result = "()")] -pub struct MarkAuthorizationCodeUsed(pub String); - #[derive(Message)] #[rtype(result = "()")] pub struct UpdateSession(pub Session); @@ -121,23 +141,11 @@ impl Handler for OpenIDSessionsActor { fn handle(&mut self, msg: FindSessionByAccessToken, _ctx: &mut Self::Context) -> Self::Result { self.session .iter() - .find(|f| f.access_token.eq(&msg.0)) + .find(|f| f.access_token.as_ref().map(|t| t.eq(&msg.0)).unwrap_or(false)) .cloned() } } -impl Handler for OpenIDSessionsActor { - type Result = (); - - fn handle(&mut self, msg: MarkAuthorizationCodeUsed, _ctx: &mut Self::Context) -> Self::Result { - if let Some(r) = self.session - .iter_mut() - .find(|f| f.authorization_code.eq(&msg.0)) { - r.authorization_code_used = true; - } - } -} - impl Handler for OpenIDSessionsActor { type Result = (); diff --git a/src/controllers/openid_controller.rs b/src/controllers/openid_controller.rs index a6ec30b..9a2510d 100644 --- a/src/controllers/openid_controller.rs +++ b/src/controllers/openid_controller.rs @@ -9,7 +9,7 @@ use askama::Template; use crate::actors::{openid_sessions_actor, users_actor}; use crate::actors::openid_sessions_actor::{OpenIDSessionsActor, Session, SessionID}; use crate::actors::users_actor::UsersActor; -use crate::constants::{AUTHORIZE_URI, CERT_URI, OPEN_ID_ACCESS_TOKEN_LEN, OPEN_ID_ACCESS_TOKEN_TIMEOUT, OPEN_ID_AUTHORIZATION_CODE_LEN, OPEN_ID_AUTHORIZATION_CODE_TIMEOUT, OPEN_ID_REFRESH_TOKEN_LEN, OPEN_ID_REFRESH_TOKEN_TIMEOUT, OPEN_ID_SESSION_LEN, TOKEN_URI, USERINFO_URI}; +use crate::constants::*; use crate::controllers::base_controller::FatalErrorPage; use crate::data::app_config::AppConfig; use crate::data::client::{ClientID, ClientManager}; @@ -139,11 +139,10 @@ pub async fn authorize(user: CurrentUser, id: Identity, query: web::Query { - let session: Session = match sessions + let mut session: Session = match sessions .send(openid_sessions_actor::FindSessionByAuthorizationCode(q.code.clone())) .await.unwrap() { @@ -310,17 +309,18 @@ pub async fn token(req: HttpRequest, } } - if session.authorization_code_used { + if let Some(_) = session.access_token { return Ok(error_response(&query, "invalid_request", "Authorization code already used!")); } - // Mark session as used - sessions.send(openid_sessions_actor::MarkAuthorizationCodeUsed(session.authorization_code)) + session.regenerate_access_and_refresh_tokens(&app_config, &jwt_signer)?; + + sessions.send(openid_sessions_actor::UpdateSession(session.clone())) .await.unwrap(); // Generate id token - let token = IdToken { + let id_token = IdToken { issuer: app_config.website_origin.to_string(), subject_identifier: session.user, audience: session.client.0.to_string(), @@ -331,11 +331,11 @@ pub async fn token(req: HttpRequest, }; TokenResponse { - access_token: session.access_token, + access_token: session.access_token.expect("Missing access token!"), token_type: "Bearer", refresh_token: session.refresh_token, expires_in: session.access_token_expire_at - time(), - id_token: Some(jwt_signer.sign_token(token.to_jwt_claims())?), + id_token: Some(jwt_signer.sign_token(id_token.to_jwt_claims())?), } } @@ -358,17 +358,14 @@ pub async fn token(req: HttpRequest, return Ok(error_response(&query, "access_denied", "Refresh token has expired!")); } - session.refresh_token = rand_str(OPEN_ID_REFRESH_TOKEN_LEN); - session.refresh_token_expire_at = OPEN_ID_REFRESH_TOKEN_TIMEOUT + time(); - session.access_token = rand_str(OPEN_ID_ACCESS_TOKEN_LEN); - session.access_token_expire_at = OPEN_ID_ACCESS_TOKEN_TIMEOUT + time(); + session.regenerate_access_and_refresh_tokens(&app_config, &jwt_signer)?; sessions .send(openid_sessions_actor::UpdateSession(session.clone())) .await.unwrap(); TokenResponse { - access_token: session.access_token, + access_token: session.access_token.expect("Missing access token!"), token_type: "Bearer", refresh_token: session.refresh_token, expires_in: session.access_token_expire_at - time(), diff --git a/src/data/access_token.rs b/src/data/access_token.rs new file mode 100644 index 0000000..bce0584 --- /dev/null +++ b/src/data/access_token.rs @@ -0,0 +1,34 @@ +use jwt_simple::claims::JWTClaims; +use jwt_simple::prelude::Duration; + +pub struct AccessToken { + pub issuer: String, + pub subject_identifier: String, + pub issued_at: u64, + pub exp_time: u64, + pub rand_val: String, + pub nonce: Option, +} + +#[derive(serde::Serialize, serde::Deserialize)] +pub struct CustomAccessTokenClaims { + rand_val: String, +} + +impl AccessToken { + pub fn to_jwt_claims(self) -> JWTClaims { + JWTClaims { + issued_at: Some(Duration::from_secs(self.issued_at)), + expires_at: Some(Duration::from_secs(self.exp_time)), + invalid_before: None, + issuer: Some(self.issuer), + subject: Some(self.subject_identifier), + audiences: None, + jwt_id: None, + nonce: self.nonce, + custom: CustomAccessTokenClaims { + rand_val: self.rand_val + }, + } + } +} \ No newline at end of file diff --git a/src/data/mod.rs b/src/data/mod.rs index cc70b73..b158b61 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -9,4 +9,5 @@ pub mod openid_config; pub mod jwt_signer; pub mod id_token; pub mod code_challenge; -pub mod open_id_user_info; \ No newline at end of file +pub mod open_id_user_info; +pub mod access_token; \ No newline at end of file