123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185 |
- 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<schema::Realm>,
- client: µrm::Stored<schema::Client>,
- user: µrm::Stored<schema::User>,
- scopes: impl Iterator<Item = &'l str>,
- redirect_uri: String,
- state: Option<&'s str>,
- ) -> Result<tide::Response, OIDCError<'s>> {
- 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::<Vec<_>>()
- .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<tide::Response, OIDCError> {
- 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,
- ))
- }
- }
|