Use JWT token for access token
This commit is contained in:
parent
69bb2816b9
commit
94c601119a
@ -2,9 +2,14 @@ use actix::{Actor, AsyncContext, Context, Handler};
|
|||||||
use actix::Message;
|
use actix::Message;
|
||||||
|
|
||||||
use crate::constants::*;
|
use crate::constants::*;
|
||||||
|
use crate::data::access_token::AccessToken;
|
||||||
|
use crate::data::app_config::AppConfig;
|
||||||
use crate::data::client::ClientID;
|
use crate::data::client::ClientID;
|
||||||
use crate::data::code_challenge::CodeChallenge;
|
use crate::data::code_challenge::CodeChallenge;
|
||||||
|
use crate::data::jwt_signer::JWTSigner;
|
||||||
use crate::data::user::UserID;
|
use crate::data::user::UserID;
|
||||||
|
use crate::utils::err::Res;
|
||||||
|
use crate::utils::string_utils::rand_str;
|
||||||
use crate::utils::time::time;
|
use crate::utils::time::time;
|
||||||
|
|
||||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, Eq, PartialEq)]
|
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, Eq, PartialEq)]
|
||||||
@ -20,9 +25,8 @@ pub struct Session {
|
|||||||
|
|
||||||
pub authorization_code: String,
|
pub authorization_code: String,
|
||||||
pub authorization_code_expire_at: u64,
|
pub authorization_code_expire_at: u64,
|
||||||
pub authorization_code_used: bool,
|
|
||||||
|
|
||||||
pub access_token: String,
|
pub access_token: Option<String>,
|
||||||
pub access_token_expire_at: u64,
|
pub access_token_expire_at: u64,
|
||||||
pub refresh_token: String,
|
pub refresh_token: String,
|
||||||
pub refresh_token_expire_at: u64,
|
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.authorization_code_expire_at < time() && self.access_token_expire_at < time()
|
||||||
&& self.refresh_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)]
|
#[derive(Message)]
|
||||||
@ -54,10 +78,6 @@ pub struct FindSessionByRefreshToken(pub String);
|
|||||||
#[rtype(result = "Option<Session>")]
|
#[rtype(result = "Option<Session>")]
|
||||||
pub struct FindSessionByAccessToken(pub String);
|
pub struct FindSessionByAccessToken(pub String);
|
||||||
|
|
||||||
#[derive(Message)]
|
|
||||||
#[rtype(result = "()")]
|
|
||||||
pub struct MarkAuthorizationCodeUsed(pub String);
|
|
||||||
|
|
||||||
#[derive(Message)]
|
#[derive(Message)]
|
||||||
#[rtype(result = "()")]
|
#[rtype(result = "()")]
|
||||||
pub struct UpdateSession(pub Session);
|
pub struct UpdateSession(pub Session);
|
||||||
@ -121,23 +141,11 @@ impl Handler<FindSessionByAccessToken> for OpenIDSessionsActor {
|
|||||||
fn handle(&mut self, msg: FindSessionByAccessToken, _ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: FindSessionByAccessToken, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
self.session
|
self.session
|
||||||
.iter()
|
.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()
|
.cloned()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Handler<MarkAuthorizationCodeUsed> 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<UpdateSession> for OpenIDSessionsActor {
|
impl Handler<UpdateSession> for OpenIDSessionsActor {
|
||||||
type Result = ();
|
type Result = ();
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ use askama::Template;
|
|||||||
use crate::actors::{openid_sessions_actor, users_actor};
|
use crate::actors::{openid_sessions_actor, users_actor};
|
||||||
use crate::actors::openid_sessions_actor::{OpenIDSessionsActor, Session, SessionID};
|
use crate::actors::openid_sessions_actor::{OpenIDSessionsActor, Session, SessionID};
|
||||||
use crate::actors::users_actor::UsersActor;
|
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::controllers::base_controller::FatalErrorPage;
|
||||||
use crate::data::app_config::AppConfig;
|
use crate::data::app_config::AppConfig;
|
||||||
use crate::data::client::{ClientID, ClientManager};
|
use crate::data::client::{ClientID, ClientManager};
|
||||||
@ -139,11 +139,10 @@ pub async fn authorize(user: CurrentUser, id: Identity, query: web::Query<Author
|
|||||||
redirect_uri,
|
redirect_uri,
|
||||||
authorization_code: rand_str(OPEN_ID_AUTHORIZATION_CODE_LEN),
|
authorization_code: rand_str(OPEN_ID_AUTHORIZATION_CODE_LEN),
|
||||||
authorization_code_expire_at: time() + OPEN_ID_AUTHORIZATION_CODE_TIMEOUT,
|
authorization_code_expire_at: time() + OPEN_ID_AUTHORIZATION_CODE_TIMEOUT,
|
||||||
authorization_code_used: false,
|
access_token: None,
|
||||||
access_token: rand_str(OPEN_ID_ACCESS_TOKEN_LEN),
|
|
||||||
access_token_expire_at: time() + OPEN_ID_ACCESS_TOKEN_TIMEOUT,
|
access_token_expire_at: time() + OPEN_ID_ACCESS_TOKEN_TIMEOUT,
|
||||||
refresh_token: rand_str(OPEN_ID_REFRESH_TOKEN_LEN),
|
refresh_token: "".to_string(),
|
||||||
refresh_token_expire_at: time() + OPEN_ID_REFRESH_TOKEN_TIMEOUT,
|
refresh_token_expire_at: 0,
|
||||||
nonce: query.0.nonce,
|
nonce: query.0.nonce,
|
||||||
code_challenge,
|
code_challenge,
|
||||||
};
|
};
|
||||||
@ -272,7 +271,7 @@ pub async fn token(req: HttpRequest,
|
|||||||
&query.authorization_code_query,
|
&query.authorization_code_query,
|
||||||
&query.refresh_token_query) {
|
&query.refresh_token_query) {
|
||||||
("authorization_code", Some(q), _) => {
|
("authorization_code", Some(q), _) => {
|
||||||
let session: Session = match sessions
|
let mut session: Session = match sessions
|
||||||
.send(openid_sessions_actor::FindSessionByAuthorizationCode(q.code.clone()))
|
.send(openid_sessions_actor::FindSessionByAuthorizationCode(q.code.clone()))
|
||||||
.await.unwrap()
|
.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!"));
|
return Ok(error_response(&query, "invalid_request", "Authorization code already used!"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark session as used
|
session.regenerate_access_and_refresh_tokens(&app_config, &jwt_signer)?;
|
||||||
sessions.send(openid_sessions_actor::MarkAuthorizationCodeUsed(session.authorization_code))
|
|
||||||
|
sessions.send(openid_sessions_actor::UpdateSession(session.clone()))
|
||||||
.await.unwrap();
|
.await.unwrap();
|
||||||
|
|
||||||
|
|
||||||
// Generate id token
|
// Generate id token
|
||||||
let token = IdToken {
|
let id_token = IdToken {
|
||||||
issuer: app_config.website_origin.to_string(),
|
issuer: app_config.website_origin.to_string(),
|
||||||
subject_identifier: session.user,
|
subject_identifier: session.user,
|
||||||
audience: session.client.0.to_string(),
|
audience: session.client.0.to_string(),
|
||||||
@ -331,11 +331,11 @@ pub async fn token(req: HttpRequest,
|
|||||||
};
|
};
|
||||||
|
|
||||||
TokenResponse {
|
TokenResponse {
|
||||||
access_token: session.access_token,
|
access_token: session.access_token.expect("Missing access token!"),
|
||||||
token_type: "Bearer",
|
token_type: "Bearer",
|
||||||
refresh_token: session.refresh_token,
|
refresh_token: session.refresh_token,
|
||||||
expires_in: session.access_token_expire_at - time(),
|
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!"));
|
return Ok(error_response(&query, "access_denied", "Refresh token has expired!"));
|
||||||
}
|
}
|
||||||
|
|
||||||
session.refresh_token = rand_str(OPEN_ID_REFRESH_TOKEN_LEN);
|
session.regenerate_access_and_refresh_tokens(&app_config, &jwt_signer)?;
|
||||||
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();
|
|
||||||
|
|
||||||
sessions
|
sessions
|
||||||
.send(openid_sessions_actor::UpdateSession(session.clone()))
|
.send(openid_sessions_actor::UpdateSession(session.clone()))
|
||||||
.await.unwrap();
|
.await.unwrap();
|
||||||
|
|
||||||
TokenResponse {
|
TokenResponse {
|
||||||
access_token: session.access_token,
|
access_token: session.access_token.expect("Missing access token!"),
|
||||||
token_type: "Bearer",
|
token_type: "Bearer",
|
||||||
refresh_token: session.refresh_token,
|
refresh_token: session.refresh_token,
|
||||||
expires_in: session.access_token_expire_at - time(),
|
expires_in: session.access_token_expire_at - time(),
|
||||||
|
34
src/data/access_token.rs
Normal file
34
src/data/access_token.rs
Normal file
@ -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<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct CustomAccessTokenClaims {
|
||||||
|
rand_val: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AccessToken {
|
||||||
|
pub fn to_jwt_claims(self) -> JWTClaims<CustomAccessTokenClaims> {
|
||||||
|
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
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -10,3 +10,4 @@ pub mod jwt_signer;
|
|||||||
pub mod id_token;
|
pub mod id_token;
|
||||||
pub mod code_challenge;
|
pub mod code_challenge;
|
||||||
pub mod open_id_user_info;
|
pub mod open_id_user_info;
|
||||||
|
pub mod access_token;
|
Loading…
Reference in New Issue
Block a user