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="")] pub async fn auth( db: &State, user: User, info: Form, ) -> Result> { 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 })) }