use super::{api, OIDCError, OIDCErrorType, Request}; use crate::{client::ClientExt, config, schema, server::session::SessionHelper}; use microrm::prelude::*; fn do_code_authorize<'l, 's>( config: &config::Config, realm: µrm::Stored, client: µrm::Stored, user: µrm::Stored, scopes: impl Iterator, redirect_uri: String, state: Option<&'s str>, ) -> Result> { let expiry = std::time::SystemTime::now() + std::time::Duration::from_secs(config.auth_code_expiry); let rng = ring::rand::SystemRandom::new(); let raw_auth_code: [u8; 32] = ring::rand::generate(&rng) .map_err(|_| { OIDCError( OIDCErrorType::ServerError, "failed to generate auth code".into(), state, ) })? .expose(); let encoded_auth_code = base64::encode_config(raw_auth_code, base64::URL_SAFE_NO_PAD); realm.auth_codes.insert(schema::AuthCode { realm: realm.id(), client: client.id(), user: user.id(), scopes: scopes .map(|x| x.to_owned()) .collect::>() .into_serialized(), expiry: expiry.into(), redirect_uri: redirect_uri.clone(), code: encoded_auth_code.clone(), })?; let new_params = [ ("response_type", "code"), ("code", encoded_auth_code.as_str()), ] .into_iter() .chain(state.map(|v| ("state", v))); Ok(tide::Redirect::temporary( tide::http::Url::parse_with_params(redirect_uri.as_str(), new_params).map_err(|e| { OIDCError( OIDCErrorType::InvalidRequest, format!("could not parse redirect_uri as a URL: {e}").into(), None, ) })?, ) .into()) } pub(super) fn do_authorize( request: Request, state: Option<&str>, ) -> Result { let shelper = SessionHelper::new(&request); let realm = shelper.get_realm().map_err(|_| { OIDCError( OIDCErrorType::InvalidRequest, "No such realm!".into(), state, ) })?; let make_redirect = || { let mut login_url = request.url().join("../v1/session/login").unwrap(); login_url .query_pairs_mut() .clear() .append_pair("redirect", request.url().as_str()); Ok(tide::Redirect::new(login_url).into()) }; let qp: api::AuthorizationRequestQuery = request .query() .map_err(|x| OIDCError(OIDCErrorType::InvalidRequest, x.to_string().into(), state))?; // collect session authentication info let potential_sauth = shelper .get_session(&request) .and_then(|session| shelper.get_auth_for_session(realm.id(), &session)); let Some(user_id) = potential_sauth.and_then(|v| v.user) else { // if we don't have any relevant auth info, redirect to login return make_redirect(); }; let Ok(Some(user)) = realm.users.with_id(user_id).first().get() else { return Err(OIDCError( OIDCErrorType::ServerError, "Internal state error!".into(), state, )); }; // verify the client_id refers to an extant client let client = realm .clients .with(schema::Client::Shortname, &qp.client_id) .first() .get() .ok() .flatten() .ok_or_else(|| { OIDCError( OIDCErrorType::UnauthorizedClient, "client does not exist".into(), state, ) })?; let scopes = qp.scope.as_deref().unwrap_or("").split_whitespace(); // check that redirect URI matches match client.check_redirect(qp.redirect_uri.as_str()) { Ok(true) => (), Ok(false) => { return Err(OIDCError( OIDCErrorType::InvalidRequest, "invalid redirect URI".into(), state, )) } Err(_) => { return Err(OIDCError( OIDCErrorType::ServerError, "invalid stored redirect uri".into(), state, )) } } if qp.response_type == "code" { do_code_authorize( &request.state().core.config, &realm, &client, &user, scopes, qp.redirect_uri, state, ) } else if qp.response_type == "token" { let rhelper = request.state().core.realms.get_helper(realm.id()).unwrap(); let token = rhelper .generate_access_token(&client, &user, scopes) .map_err(|e| { OIDCError( OIDCErrorType::ServerError, format!("could not generate token: {e}").into(), state, ) })?; Ok(tide::Response::builder(200) .content_type(tide::http::mime::JSON) .body( serde_json::to_vec(&api::TokenResponse { token_type: "bearer", access_token: token.as_str(), refresh_token: None, scope: None, }) .unwrap(), ) .build()) } else { Err(OIDCError( OIDCErrorType::UnsupportedResponseType, "only 'code' and 'token' are understood".into(), state, )) } }