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>")]
pub struct FindSessionByAuthorizationCode(pub String);
#[derive(Message)]
#[rtype(result = "Option<Session>")]
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<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 {
type Result = ();
@ -101,4 +120,15 @@ impl Handler<MarkAuthorizationCodeUsed> for OpenIDSessionsActor {
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()
}
#[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)]
pub struct TokenQuery {
grant_type: String,
client_id: Option<ClientID>,
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)]
@ -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<String>,
}
pub async fn token(req: HttpRequest,
@ -176,8 +193,7 @@ pub async fn token(req: HttpRequest,
app_config: web::Data<AppConfig>,
sessions: web::Data<Addr<OpenIDSessionsActor>>,
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
// 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)]