diff --git a/src/actors/openid_sessions_actor.rs b/src/actors/openid_sessions_actor.rs index 9964147..b5aa546 100644 --- a/src/actors/openid_sessions_actor.rs +++ b/src/actors/openid_sessions_actor.rs @@ -45,10 +45,18 @@ pub struct PushNewSession(pub Session); #[rtype(result = "Option")] pub struct FindSessionByAuthorizationCode(pub String); +#[derive(Message)] +#[rtype(result = "Option")] +pub struct FindSessionByRefreshToken(pub String); + #[derive(Message)] #[rtype(result = "()")] pub struct MarkAuthorizationCodeUsed(pub String); +#[derive(Message)] +#[rtype(result = "()")] +pub struct UpdateSession(pub Session); + #[derive(Default)] pub struct OpenIDSessionsActor { session: Vec, @@ -91,6 +99,17 @@ impl Handler for OpenIDSessionsActor { } } +impl Handler for OpenIDSessionsActor { + type Result = Option; + + 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 for OpenIDSessionsActor { type Result = (); @@ -101,4 +120,15 @@ impl Handler for OpenIDSessionsActor { r.authorization_code_used = true; } } +} + +impl Handler 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; + } + } } \ No newline at end of file diff --git a/src/controllers/openid_controller.rs b/src/controllers/openid_controller.rs index 97c4e26..9a252b8 100644 --- a/src/controllers/openid_controller.rs +++ b/src/controllers/openid_controller.rs @@ -152,13 +152,29 @@ pub async fn authorize(user: CurrentUser, id: Identity, query: web::Query, client_secret: Option, - redirect_uri: String, - code: String, + + #[serde(flatten)] + authorization_code_query: Option, + + #[serde(flatten)] + refresh_token_query: Option, } #[derive(Debug, serde::Serialize)] @@ -167,7 +183,8 @@ pub struct TokenResponse { token_type: &'static str, refresh_token: String, expires_in: u64, - id_token: String, + #[serde(skip_serializing_if = "Option::is_none")] + id_token: Option, } pub async fn token(req: HttpRequest, @@ -176,8 +193,7 @@ pub async fn token(req: HttpRequest, app_config: web::Data, sessions: web::Data>, jwt_signer: web::Data) -> actix_web::Result { - // TODO : add refresh tokens : https://openid.net/specs/openid-connect-core-1_0.html#RefreshTokens - // TODO : check auth challenge + // TODO : check auth challenge : https://oa.dnc.global/-fr-.html?page=unarticle&id_article=148&lang=fr // Extraction authentication information 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!")); } - if query.grant_type != "authorization_code" { - log::warn!("Token request failed: Grant type unsupported! {:#?}", query.0); - return Ok(HttpResponse::BadRequest().body("Grant type unsupported!")); - } + let token_response = match (query.grant_type.as_str(), + &query.authorization_code_query, + &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 - .send(openid_sessions_actor::FindSessionByAuthorizationCode(query.code.clone())) - .await.unwrap() - { - None => { - log::warn!("Token request failed: Session not found! {:#?}", query.0); - return Ok(HttpResponse::NotFound().body("Session not found!")); + if session.client != client.id { + log::warn!("Token request failed: Client mismatch! {:#?}", query.0); + return Ok(HttpResponse::Unauthorized().body("Client mismatch!")); + } + + if session.redirect_uri != q.redirect_uri { + 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 { - log::warn!("Token request failed: Client mismatch! {:#?}", query.0); - return Ok(HttpResponse::Unauthorized().body("Client mismatch!")); - } + ("refresh_token", _, Some(q)) => { + let mut session: Session = match sessions + .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 { - log::warn!("Token request failed: Invalid redirect URI! {:#?}", query.0); - return Ok(HttpResponse::Unauthorized().body("Invalid redirect URI!")); - } + if session.client != client.id { + log::warn!("Token request failed: Client mismatch! {:#?}", query.0); + return Ok(HttpResponse::Unauthorized().body("Client mismatch!")); + } - 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!")); - } + 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(); - if session.authorization_code_used { - log::warn!("Token request failed: Authorization already used! {:#?}", query.0); - return Ok(HttpResponse::Unauthorized().body("Authorization already used!")); - } + sessions + .send(openid_sessions_actor::UpdateSession(session.clone())) + .await.unwrap(); - // Mark session as used - sessions.send(openid_sessions_actor::MarkAuthorizationCodeUsed(session.authorization_code)) - .await.unwrap(); + TokenResponse { + access_token: session.access_token, + token_type: "Bearer", + refresh_token: session.refresh_token, + expires_in: session.access_token_expire_at - time(), + id_token: None, + } + } - - // 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, + _ => { + log::warn!("Token request failed: Grant type unsupported or incomplete! {:#?}", query.0); + return Ok(HttpResponse::BadRequest().body("Grant type unsupported!")); + } }; Ok(HttpResponse::Ok() .append_header(("Cache-Control", "no-store")) .append_header(("Pragam", "no-cache")) - .json(TokenResponse { - 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())? - })) + .json(token_response)) } #[derive(serde::Serialize)]