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>")]
|
#[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 = ();
|
||||||
|
|
||||||
@ -102,3 +121,14 @@ impl Handler<MarkAuthorizationCodeUsed> for OpenIDSessionsActor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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()
|
))).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)]
|
||||||
|
Loading…
Reference in New Issue
Block a user