Add /openid/token
route
This commit is contained in:
@ -1,10 +1,11 @@
|
||||
use actix::Addr;
|
||||
use actix_web::{HttpResponse, Responder, web};
|
||||
use actix_web::{HttpRequest, HttpResponse, Responder, web};
|
||||
use actix_web::error::ErrorUnauthorized;
|
||||
use askama::Template;
|
||||
|
||||
use crate::actors::openid_sessions_actor;
|
||||
use crate::actors::openid_sessions_actor::{OpenIDSessionsActor, Session, SessionID};
|
||||
use crate::constants::{AUTHORIZE_URI, OPEN_ID_AUTHORIZATION_CODE_LEN, OPEN_ID_AUTHORIZATION_CODE_TIMEOUT, OPEN_ID_SESSION_LEN, OPEN_ID_TOKEN_LEN, OPEN_ID_TOKEN_TIMEOUT};
|
||||
use crate::constants::{AUTHORIZE_URI, OPEN_ID_ACCESS_TOKEN_LEN, OPEN_ID_ACCESS_TOKEN_TIMEOUT, OPEN_ID_AUTHORIZATION_CODE_LEN, OPEN_ID_AUTHORIZATION_CODE_TIMEOUT, OPEN_ID_REFRESH_TOKEN_LEN, OPEN_ID_REFRESH_TOKEN_TIMEOUT, OPEN_ID_SESSION_LEN, TOKEN_URI};
|
||||
use crate::controllers::base_controller::FatalErrorPage;
|
||||
use crate::data::app_config::AppConfig;
|
||||
use crate::data::client::{ClientID, ClientManager};
|
||||
@ -17,13 +18,14 @@ pub async fn get_configuration(app_conf: web::Data<AppConfig>) -> impl Responder
|
||||
HttpResponse::Ok().json(OpenIDConfig {
|
||||
issuer: app_conf.full_url("/"),
|
||||
authorization_endpoint: app_conf.full_url(AUTHORIZE_URI),
|
||||
token_endpoint: app_conf.full_url("openid/token"),
|
||||
token_endpoint: app_conf.full_url(TOKEN_URI),
|
||||
userinfo_endpoint: app_conf.full_url("openid/userinfo"),
|
||||
jwks_uri: app_conf.full_url("openid/jwks_uri"),
|
||||
scopes_supported: vec!["openid", "profile", "email"],
|
||||
response_types_supported: vec!["code", "id_token", "token id_token"],
|
||||
subject_types_supported: vec!["public"],
|
||||
id_token_signing_alg_values_supported: vec!["RS256"],
|
||||
token_endpoint_auth_methods_supported: vec!["client_secret_post", "client_secret_basic"],
|
||||
claims_supported: vec!["sub", "exp", "name", "given_name", "family_name", "email"],
|
||||
})
|
||||
}
|
||||
@ -123,9 +125,12 @@ pub async fn authorize(user: CurrentUser, query: web::Query<AuthorizeQuery>,
|
||||
user: user.uid.clone(),
|
||||
redirect_uri,
|
||||
authorization_code: rand_str(OPEN_ID_AUTHORIZATION_CODE_LEN),
|
||||
code_expire_on: time() + OPEN_ID_AUTHORIZATION_CODE_TIMEOUT,
|
||||
token: rand_str(OPEN_ID_TOKEN_LEN),
|
||||
token_expire_at: time() + OPEN_ID_TOKEN_TIMEOUT,
|
||||
authorization_code_expire_at: time() + OPEN_ID_AUTHORIZATION_CODE_TIMEOUT,
|
||||
authorization_code_used: false,
|
||||
access_token: rand_str(OPEN_ID_ACCESS_TOKEN_LEN),
|
||||
access_token_expire_at: time() + OPEN_ID_ACCESS_TOKEN_TIMEOUT,
|
||||
refresh_token: rand_str(OPEN_ID_REFRESH_TOKEN_LEN),
|
||||
refresh_token_expire_at: time() + OPEN_ID_REFRESH_TOKEN_TIMEOUT,
|
||||
nonce: query.0.nonce,
|
||||
code_challenge,
|
||||
};
|
||||
@ -140,4 +145,127 @@ pub async fn authorize(user: CurrentUser, query: web::Query<AuthorizeQuery>,
|
||||
urlencoding::encode(&query.0.state),
|
||||
urlencoding::encode(&session.authorization_code)
|
||||
))).finish()
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
pub struct TokenQuery {
|
||||
grant_type: String,
|
||||
client_id: Option<ClientID>,
|
||||
client_secret: Option<String>,
|
||||
redirect_uri: String,
|
||||
code: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
pub struct TokenResponse {
|
||||
access_token: String,
|
||||
token_type: &'static str,
|
||||
refresh_token: String,
|
||||
expires_in: u64,
|
||||
id_token: String,
|
||||
}
|
||||
|
||||
pub async fn token(req: HttpRequest,
|
||||
query: web::Form<TokenQuery>,
|
||||
clients: web::Data<ClientManager>,
|
||||
sessions: web::Data<Addr<OpenIDSessionsActor>>) -> actix_web::Result<HttpResponse> {
|
||||
|
||||
// Extraction authentication information
|
||||
let authorization_header = req.headers().get("authorization");
|
||||
let (client_id, client_secret) = match (&query.client_id, &query.client_secret, authorization_header) {
|
||||
// post authentication
|
||||
(Some(client_id), Some(client_secret), None) => {
|
||||
(client_id.clone(), client_secret.to_string())
|
||||
}
|
||||
|
||||
// Basic authentication
|
||||
(None, None, Some(v)) => {
|
||||
let token = match v.to_str().unwrap_or_default().strip_prefix("Basic ") {
|
||||
None => {
|
||||
log::warn!("Token request failed: Authorization header does not start with 'Basic '! => got '{:#?}'", v);
|
||||
return Ok(HttpResponse::Unauthorized().body("Authorization header does not start with 'Basic '"));
|
||||
}
|
||||
Some(v) => v
|
||||
};
|
||||
|
||||
let decode = String::from_utf8_lossy(&match base64::decode(token) {
|
||||
Ok(d) => d,
|
||||
Err(e) => {
|
||||
log::warn!("Failed to decode authorization header! {:?}", e);
|
||||
return Ok(HttpResponse::InternalServerError().body("Failed to decode authorization header!"));
|
||||
}
|
||||
}).to_string();
|
||||
|
||||
match decode.split_once(':') {
|
||||
None => (ClientID(decode), "".to_string()),
|
||||
Some((id, secret)) => (ClientID(id.to_string()), secret.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
_ => {
|
||||
log::warn!("Token request failed: Unknown client authentication method! {:#?}", query.0);
|
||||
return Ok(HttpResponse::BadRequest().body("Authentication method unknown!"));
|
||||
}
|
||||
};
|
||||
|
||||
let client = clients
|
||||
.find_by_id(&client_id)
|
||||
.ok_or_else(|| ErrorUnauthorized("Client not found"))?;
|
||||
|
||||
if !client.secret.eq(&client_secret) {
|
||||
log::warn!("Token request failed: client secret is invalid! {:#?}", query.0);
|
||||
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 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!"));
|
||||
}
|
||||
Some(s) => s,
|
||||
};
|
||||
|
||||
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 != query.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();
|
||||
|
||||
|
||||
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: session.session_id.0,
|
||||
}))
|
||||
}
|
Reference in New Issue
Block a user