Can request refresh tokens

This commit is contained in:
Pierre HUBERT 2022-04-14 17:02:47 +02:00
parent 8a005c4185
commit 078a913f6a
2 changed files with 143 additions and 54 deletions

View File

@ -45,10 +45,18 @@ pub struct PushNewSession(pub Session);
#[rtype(result = "Option<Session>")] #[rtype(result = "Option<Session>")]
pub struct FindSessionByAuthorizationCode(pub String); pub struct FindSessionByAuthorizationCode(pub String);
#[derive(Message)]
#[rtype(result = "Option<Session>")]
pub struct FindSessionByRefreshToken(pub String);
#[derive(Message)] #[derive(Message)]
#[rtype(result = "()")] #[rtype(result = "()")]
pub struct MarkAuthorizationCodeUsed(pub String); pub struct MarkAuthorizationCodeUsed(pub String);
#[derive(Message)]
#[rtype(result = "()")]
pub struct UpdateSession(pub Session);
#[derive(Default)] #[derive(Default)]
pub struct OpenIDSessionsActor { pub struct OpenIDSessionsActor {
session: Vec<Session>, session: Vec<Session>,
@ -91,6 +99,17 @@ impl Handler<FindSessionByAuthorizationCode> for OpenIDSessionsActor {
} }
} }
impl Handler<FindSessionByRefreshToken> for OpenIDSessionsActor {
type Result = Option<Session>;
fn handle(&mut self, msg: FindSessionByRefreshToken, _ctx: &mut Self::Context) -> Self::Result {
self.session
.iter()
.find(|f| f.refresh_token.eq(&msg.0))
.cloned()
}
}
impl Handler<MarkAuthorizationCodeUsed> for OpenIDSessionsActor { impl Handler<MarkAuthorizationCodeUsed> for OpenIDSessionsActor {
type Result = (); type Result = ();
@ -101,4 +120,15 @@ impl Handler<MarkAuthorizationCodeUsed> for OpenIDSessionsActor {
r.authorization_code_used = true; r.authorization_code_used = true;
} }
} }
}
impl Handler<UpdateSession> for OpenIDSessionsActor {
type Result = ();
fn handle(&mut self, msg: UpdateSession, _ctx: &mut Self::Context) -> Self::Result {
if let Some(r) = self.session.iter().enumerate()
.find(|f| f.1.session_id.eq(&msg.0.session_id)).map(|f| f.0) {
self.session[r] = msg.0;
}
}
} }

View File

@ -152,13 +152,29 @@ pub async fn authorize(user: CurrentUser, id: Identity, query: web::Query<Author
))).finish() ))).finish()
} }
#[derive(Debug, serde::Deserialize)]
pub struct TokenAuthorizationCodeQuery {
redirect_uri: String,
code: String,
}
#[derive(Debug, serde::Deserialize)]
pub struct TokenRefreshTokenQuery {
refresh_token: String,
}
#[derive(Debug, serde::Deserialize)] #[derive(Debug, serde::Deserialize)]
pub struct TokenQuery { pub struct TokenQuery {
grant_type: String, grant_type: String,
client_id: Option<ClientID>, client_id: Option<ClientID>,
client_secret: Option<String>, client_secret: Option<String>,
redirect_uri: String,
code: String, #[serde(flatten)]
authorization_code_query: Option<TokenAuthorizationCodeQuery>,
#[serde(flatten)]
refresh_token_query: Option<TokenRefreshTokenQuery>,
} }
#[derive(Debug, serde::Serialize)] #[derive(Debug, serde::Serialize)]
@ -167,7 +183,8 @@ pub struct TokenResponse {
token_type: &'static str, token_type: &'static str,
refresh_token: String, refresh_token: String,
expires_in: u64, expires_in: u64,
id_token: String, #[serde(skip_serializing_if = "Option::is_none")]
id_token: Option<String>,
} }
pub async fn token(req: HttpRequest, pub async fn token(req: HttpRequest,
@ -176,8 +193,7 @@ pub async fn token(req: HttpRequest,
app_config: web::Data<AppConfig>, app_config: web::Data<AppConfig>,
sessions: web::Data<Addr<OpenIDSessionsActor>>, sessions: web::Data<Addr<OpenIDSessionsActor>>,
jwt_signer: web::Data<JWTSigner>) -> actix_web::Result<HttpResponse> { jwt_signer: web::Data<JWTSigner>) -> actix_web::Result<HttpResponse> {
// TODO : add refresh tokens : https://openid.net/specs/openid-connect-core-1_0.html#RefreshTokens // TODO : check auth challenge : https://oa.dnc.global/-fr-.html?page=unarticle&id_article=148&lang=fr
// TODO : check auth challenge
// Extraction authentication information // Extraction authentication information
let authorization_header = req.headers().get("authorization"); let authorization_header = req.headers().get("authorization");
@ -226,68 +242,111 @@ pub async fn token(req: HttpRequest,
return Ok(HttpResponse::Unauthorized().body("Client secret is invalid!")); return Ok(HttpResponse::Unauthorized().body("Client secret is invalid!"));
} }
if query.grant_type != "authorization_code" { let token_response = match (query.grant_type.as_str(),
log::warn!("Token request failed: Grant type unsupported! {:#?}", query.0); &query.authorization_code_query,
return Ok(HttpResponse::BadRequest().body("Grant type unsupported!")); &query.refresh_token_query) {
} ("authorization_code", Some(q), _) => {
let session: Session = match sessions
.send(openid_sessions_actor::FindSessionByAuthorizationCode(q.code.clone()))
.await.unwrap()
{
None => {
log::warn!("Token request failed: Session not found! {:#?}", query.0);
return Ok(HttpResponse::NotFound().body("Session not found!"));
}
Some(s) => s,
};
let session: Session = match sessions if session.client != client.id {
.send(openid_sessions_actor::FindSessionByAuthorizationCode(query.code.clone())) log::warn!("Token request failed: Client mismatch! {:#?}", query.0);
.await.unwrap() return Ok(HttpResponse::Unauthorized().body("Client mismatch!"));
{ }
None => {
log::warn!("Token request failed: Session not found! {:#?}", query.0); if session.redirect_uri != q.redirect_uri {
return Ok(HttpResponse::NotFound().body("Session not found!")); log::warn!("Token request failed: Invalid redirect URI! {:#?}", query.0);
return Ok(HttpResponse::Unauthorized().body("Invalid redirect URI!"));
}
if session.authorization_code_expire_at < time() {
log::warn!("Token request failed: Authorization code expired! {:#?}", query.0);
return Ok(HttpResponse::Unauthorized().body("Authorization code expired!"));
}
if session.authorization_code_used {
log::warn!("Token request failed: Authorization already used! {:#?}", query.0);
return Ok(HttpResponse::Unauthorized().body("Authorization already used!"));
}
// Mark session as used
sessions.send(openid_sessions_actor::MarkAuthorizationCodeUsed(session.authorization_code))
.await.unwrap();
// Generate id token
let token = IdToken {
issuer: app_config.website_origin.to_string(),
subject_identifier: session.user,
audience: session.client.0.to_string(),
expiration_time: session.access_token_expire_at,
issued_at: time(),
auth_time: session.auth_time,
nonce: session.nonce,
};
TokenResponse {
access_token: session.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())?),
}
} }
Some(s) => s,
};
if session.client != client.id { ("refresh_token", _, Some(q)) => {
log::warn!("Token request failed: Client mismatch! {:#?}", query.0); let mut session: Session = match sessions
return Ok(HttpResponse::Unauthorized().body("Client mismatch!")); .send(openid_sessions_actor::FindSessionByRefreshToken(q.refresh_token.clone()))
} .await.unwrap()
{
None => {
log::warn!("Token refresh failed: Session not found! {:#?}", query.0);
return Ok(HttpResponse::NotFound().body("Session not found!"));
}
Some(s) => s,
};
if session.redirect_uri != query.redirect_uri { if session.client != client.id {
log::warn!("Token request failed: Invalid redirect URI! {:#?}", query.0); log::warn!("Token request failed: Client mismatch! {:#?}", query.0);
return Ok(HttpResponse::Unauthorized().body("Invalid redirect URI!")); return Ok(HttpResponse::Unauthorized().body("Client mismatch!"));
} }
if session.authorization_code_expire_at < time() { session.refresh_token = rand_str(OPEN_ID_REFRESH_TOKEN_LEN);
log::warn!("Token request failed: Authorization code expired! {:#?}", query.0); session.refresh_token_expire_at = OPEN_ID_REFRESH_TOKEN_TIMEOUT + time();
return Ok(HttpResponse::Unauthorized().body("Authorization code expired!")); session.access_token = rand_str(OPEN_ID_ACCESS_TOKEN_LEN);
} session.access_token_expire_at = OPEN_ID_ACCESS_TOKEN_TIMEOUT + time();
if session.authorization_code_used { sessions
log::warn!("Token request failed: Authorization already used! {:#?}", query.0); .send(openid_sessions_actor::UpdateSession(session.clone()))
return Ok(HttpResponse::Unauthorized().body("Authorization already used!")); .await.unwrap();
}
// Mark session as used TokenResponse {
sessions.send(openid_sessions_actor::MarkAuthorizationCodeUsed(session.authorization_code)) access_token: session.access_token,
.await.unwrap(); token_type: "Bearer",
refresh_token: session.refresh_token,
expires_in: session.access_token_expire_at - time(),
id_token: None,
}
}
_ => {
// Generate id token log::warn!("Token request failed: Grant type unsupported or incomplete! {:#?}", query.0);
let token = IdToken { return Ok(HttpResponse::BadRequest().body("Grant type unsupported!"));
issuer: app_config.website_origin.to_string(), }
subject_identifier: session.user,
audience: session.client.0.to_string(),
expiration_time: session.access_token_expire_at,
issued_at: time(),
auth_time: session.auth_time,
nonce: session.nonce,
}; };
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok()
.append_header(("Cache-Control", "no-store")) .append_header(("Cache-Control", "no-store"))
.append_header(("Pragam", "no-cache")) .append_header(("Pragam", "no-cache"))
.json(TokenResponse { .json(token_response))
access_token: session.access_token,
token_type: "Bearer",
refresh_token: session.refresh_token,
expires_in: session.access_token_expire_at - time(),
id_token: jwt_signer.sign_token(token.to_jwt_claims())?
}))
} }
#[derive(serde::Serialize)] #[derive(serde::Serialize)]