Files
stoatchat/crates/delta/src/routes/oauth2/authorize_auth.rs
2025-08-18 01:58:14 +01:00

103 lines
3.6 KiB
Rust

use revolt_config::config;
use revolt_database::{
util::{oauth2, reference::Reference},
Database, User,
};
use revolt_models::v0;
use revolt_result::{create_error, Result};
use rocket::{form::Form, serde::json::Json, State};
use redis_kiss::AsyncCommands;
/// # OAuth2 Authorization Auth
///
/// Generates an OAuth2 code to be passed to the redirect URI.
#[openapi(tag = "OAuth2")]
#[post("/authorize", data="<info>")]
pub async fn auth(
db: &State<Database>,
user: User,
info: Form<v0::OAuth2AuthorizationForm>,
) -> Result<Json<v0::OAuth2AuthorizeAuthResponse>> {
let bot = Reference::from_unchecked(info.client_id.clone())
.as_bot(db)
.await?;
let Some(oauth2) = &bot.oauth2 else {
return Err(create_error!(InvalidOperation));
};
if !oauth2.redirects.contains(&info.redirect_uri) || v0::OAuth2Scope::scopes_from_str(&info.scope).is_none() {
return Err(create_error!(InvalidOperation));
};
let config = config().await;
let code = match info.response_type {
// implicit
v0::OAuth2ResponseType::Code => {
if info.state.is_some() || info.code_challenge.is_some() || info.code_challenge_method.is_some() {
return Err(create_error!(InvalidOperation));
};
oauth2::encode_token(
&config.api.security.token_secret,
oauth2::TokenType::Auth,
user.id.clone(),
info.client_id.clone(),
info.redirect_uri.clone(),
info.scope.clone(),
info.code_challenge_method,
)
.map_err(|_| create_error!(InternalError))?
},
// authorization code
v0::OAuth2ResponseType::Token => {
if let Some(((state, code_challange), code_challange_method)) = info.state.as_ref().zip(info.code_challenge.as_ref()).zip(info.code_challenge_method) {
let is_valid = match code_challange_method {
v0::OAuth2CodeChallangeMethod::Plain => state == code_challange,
v0::OAuth2CodeChallangeMethod::S256 => &pkce::code_challenge(state.as_bytes()) == code_challange,
};
if !is_valid || state.len() <= 43 || state.len() >= 128 {
return Err(create_error!(InvalidOperation))
}
} else if !oauth2.public {
return Err(create_error!(InvalidOperation))
};
let code = oauth2::encode_token(
&config.api.security.token_secret,
oauth2::TokenType::Access,
user.id.clone(),
info.client_id.clone(),
info.redirect_uri.clone(),
info.scope.clone(),
info.code_challenge_method,
)
.map_err(|_| create_error!(InternalError))?;
if let Some(code_challenge) = info.code_challenge.as_ref() {
let mut conn = redis_kiss::get_connection()
.await
.map_err(|_| create_error!(InternalError))?;
conn.pset_ex::<_, _, ()>(
format!("oauth2:{code}:code_challenge"),
code_challenge,
oauth2::TokenType::Access.lifetime().num_milliseconds() as usize
)
.await
.map_err(|_| create_error!(InternalError))?;
};
code
},
};
let redirect_uri = format!("{}/?code={code}", &info.redirect_uri);
Ok(Json(v0::OAuth2AuthorizeAuthResponse { redirect_uri }))
}