Can request refresh tokens
This commit is contained in:
parent
8a005c4185
commit
078a913f6a
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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)]
|
||||
|
Loading…
Reference in New Issue
Block a user