authorize.rs
1 use crate::api::common::response::{ErrMsg, ResponseStatus}; 2 use crate::api::core::login::rocket_uri_macro_get_login_page; 3 use crate::models::oauth_scope::{OauthScope, OauthScopes}; 4 use crate::{ 5 api::auth::session_guard::Session, 6 db::DB, 7 models::oauth_scope::ScopeField, 8 services::oauth_authorization_service::{self, Oauth2Error}, 9 }; 10 use mobc_redis::RedisConnectionManager; 11 use rocket::{http::Status, response::Redirect, State}; 12 use rocket_dyn_templates::Template; 13 use serde::Serialize; 14 use sqlx::Pool; 15 16 const CONSENT_TEMPLATE_NAME: &str = "oauth_consent"; 17 const RESPONSE_TYPE_CODE: &str = "code"; 18 19 #[derive(Serialize)] 20 pub struct ConsentTemplateFields { 21 client_name: String, 22 scope_fields: Vec<ScopeField>, 23 scope: String, 24 client_id: String, 25 state: String, 26 response_type: String, 27 redirect_uri: String, 28 } 29 30 #[derive(Responder)] 31 pub enum GetAuthorizationResponse { 32 #[response(status = 200)] 33 Consent(Template), 34 LoginRequired(Redirect), 35 Failure(ResponseStatus<()>), 36 Success(Redirect), 37 } 38 39 // TODO: Perhaps support nonce. 40 /// First step in the oauth2 authorization flow. 41 #[get("/authorize?<response_type>&<client_id>&<redirect_uri>&<state>&<scope>")] 42 #[allow(clippy::too_many_arguments)] 43 pub async fn get_authorization( 44 db_pool: &State<Pool<DB>>, 45 redis_pool: &State<mobc::Pool<RedisConnectionManager>>, 46 response_type: String, 47 client_id: String, 48 redirect_uri: String, 49 state: String, 50 scope: Option<String>, 51 session: Option<Session>, 52 ) -> GetAuthorizationResponse { 53 // TODO: I think we already support more than the response type code, look into this more. 54 if response_type != RESPONSE_TYPE_CODE { 55 return GetAuthorizationResponse::Failure(ResponseStatus::err( 56 Status::UnprocessableEntity, 57 ErrMsg::InvalidResponseType, 58 )); 59 } 60 61 let requested_scopes = match OauthScopes::parse_or_default(&scope) { 62 Ok(s) => s.scopes, 63 Err(err) => { 64 log::warn!("Failed to parse requested scopes, err: {err:?}"); 65 return GetAuthorizationResponse::Failure(ResponseStatus::err( 66 Status::UnprocessableEntity, 67 ErrMsg::InvalidScope, 68 )); 69 } 70 }; 71 72 // If the user is not currently logged in, redirect them to the login page then returning to this endpoint. 73 let session = match session { 74 Some(s) => s, 75 None => { 76 let return_to = format!( 77 "/api/oauth/{}", 78 uri!(get_authorization( 79 response_type, 80 client_id, 81 redirect_uri, 82 state, 83 scope, 84 )) 85 .to_string() 86 ); 87 88 let login_uri = format!( 89 "/api/core/{}", 90 uri!(get_login_page(Some(return_to))).to_string() 91 ); 92 93 return GetAuthorizationResponse::LoginRequired(Redirect::found(login_uri)); 94 } 95 }; 96 97 let url = match oauth_authorization_service::get_auth_token( 98 db_pool, 99 redis_pool, 100 client_id.clone(), 101 &redirect_uri, 102 &state, 103 session.account_id, 104 &requested_scopes, 105 ) 106 .await 107 { 108 Ok(url) => url, 109 Err(Oauth2Error::ScopeNotRegistered) => { 110 error!("The client was not registered for one or more of the requested scopes"); 111 return GetAuthorizationResponse::Failure(ResponseStatus::err( 112 Status::BadRequest, 113 ErrMsg::InvalidScope, 114 )); 115 } 116 Err(Oauth2Error::NoClientWithId) => { 117 error!("No client with id '{}'", client_id); 118 return GetAuthorizationResponse::Failure(ResponseStatus::err( 119 Status::BadRequest, 120 ErrMsg::InvalidClientId, 121 )); 122 } 123 Err(Oauth2Error::InvalidRedirectUri) => { 124 return GetAuthorizationResponse::Failure(ResponseStatus::err( 125 Status::BadRequest, 126 ErrMsg::InvalidRedirectUri, 127 )) 128 } 129 Err(Oauth2Error::MissingClientConsent { client_name }) => { 130 let scope_str = requested_scopes 131 .iter() 132 .map(|s| s.to_string()) 133 .collect::<Vec<String>>() 134 .join(" "); 135 136 let mut sorted_scopes: Vec<OauthScope> = requested_scopes.into_iter().collect(); 137 sorted_scopes.sort(); 138 139 let template_fields = ConsentTemplateFields { 140 client_name, 141 scope_fields: sorted_scopes 142 .into_iter() 143 .map(|s| s.get_scope_field()) 144 .collect(), 145 scope: scope_str, 146 client_id, 147 state, 148 response_type, 149 redirect_uri, 150 }; 151 152 return GetAuthorizationResponse::Consent(Template::render( 153 CONSENT_TEMPLATE_NAME, 154 template_fields, 155 )); 156 } 157 Err(err) => { 158 log::error!("An unexpected oauth2 error occurred, err: {err:?}"); 159 return GetAuthorizationResponse::Failure(ResponseStatus::internal_err()); 160 } 161 }; 162 163 GetAuthorizationResponse::Success(Redirect::found(url)) 164 }