feat: initial oauth2 implementation
This commit is contained in:
102
crates/delta/src/routes/oauth2/authorize_auth.rs
Normal file
102
crates/delta/src/routes/oauth2/authorize_auth.rs
Normal file
@@ -0,0 +1,102 @@
|
||||
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 }))
|
||||
}
|
||||
Reference in New Issue
Block a user