Josiah Glosson 62de07e4e6
Version updates (#3626)
* Update some Labrinth dependencies

* Update some Labrinth dependencies

* Update some Labrinth dependencies

* Update zip in Labrinth

* Update itertools in Labrinth

* Update validator in labrinth

* Update thiserror in labrinth

* Update rust_decimal, redis, and deadpool-redis in labrinth

* Update totp-rs and spdx in labrinth

* Update maxminddb and tar in labrinth

* Update sentry and sentry-actix in labrinth

* Update image in labrinth

* Update lettre in labrinth

* Update derive-new and rust_iso3166 in labrinth

* Update async-stripe and json-patch in labrinth

* Update clap and iana-time-zone in labrinth

* Update labrinth to Rust 2024

* Cargo fmt

* Just do a full cargo update

* Update daedelus to Rust 2024

* Update daedelus_client to Rust 2024

* Set the formatting edition to 2024

* Fix formatting

IntelliJ messed up my formatting
2025-05-09 12:27:55 +00:00

194 lines
6.7 KiB
Rust

use super::ValidatedRedirectUri;
use crate::auth::AuthenticationError;
use crate::models::error::ApiError;
use actix_web::HttpResponse;
use actix_web::http::{StatusCode, header::LOCATION};
use ariadne::ids::DecodingError;
#[derive(thiserror::Error, Debug)]
#[error("{}", .error_type)]
pub struct OAuthError {
#[source]
pub error_type: OAuthErrorType,
pub state: Option<String>,
pub valid_redirect_uri: Option<ValidatedRedirectUri>,
}
impl<T> From<T> for OAuthError
where
T: Into<OAuthErrorType>,
{
fn from(value: T) -> Self {
OAuthError::error(value.into())
}
}
impl OAuthError {
/// The OAuth request failed either because of an invalid redirection URI
/// or before we could validate the one we were given, so return an error
/// directly to the caller
///
/// See: IETF RFC 6749 4.1.2.1 (https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1)
pub fn error(error_type: impl Into<OAuthErrorType>) -> Self {
Self {
error_type: error_type.into(),
valid_redirect_uri: None,
state: None,
}
}
/// The OAuth request failed for a reason other than an invalid redirection URI
/// So send the error in url-encoded form to the redirect URI
///
/// See: IETF RFC 6749 4.1.2.1 (https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1)
pub fn redirect(
err: impl Into<OAuthErrorType>,
state: &Option<String>,
valid_redirect_uri: &ValidatedRedirectUri,
) -> Self {
Self {
error_type: err.into(),
state: state.clone(),
valid_redirect_uri: Some(valid_redirect_uri.clone()),
}
}
}
impl actix_web::ResponseError for OAuthError {
fn status_code(&self) -> StatusCode {
match self.error_type {
OAuthErrorType::AuthenticationError(_)
| OAuthErrorType::FailedScopeParse(_)
| OAuthErrorType::ScopesTooBroad
| OAuthErrorType::AccessDenied => {
if self.valid_redirect_uri.is_some() {
StatusCode::OK
} else {
StatusCode::INTERNAL_SERVER_ERROR
}
}
OAuthErrorType::RedirectUriNotConfigured(_)
| OAuthErrorType::ClientMissingRedirectURI { client_id: _ }
| OAuthErrorType::InvalidAcceptFlowId
| OAuthErrorType::MalformedId(_)
| OAuthErrorType::InvalidClientId(_)
| OAuthErrorType::InvalidAuthCode
| OAuthErrorType::OnlySupportsAuthorizationCodeGrant(_)
| OAuthErrorType::RedirectUriChanged(_)
| OAuthErrorType::UnauthorizedClient => StatusCode::BAD_REQUEST,
OAuthErrorType::ClientAuthenticationFailed => {
StatusCode::UNAUTHORIZED
}
}
}
fn error_response(&self) -> HttpResponse {
if let Some(ValidatedRedirectUri(mut redirect_uri)) =
self.valid_redirect_uri.clone()
{
redirect_uri = format!(
"{}?error={}&error_description={}",
redirect_uri,
self.error_type.error_name(),
self.error_type,
);
if let Some(state) = self.state.as_ref() {
redirect_uri = format!("{redirect_uri}&state={state}");
}
HttpResponse::Ok()
.append_header((LOCATION, redirect_uri.clone()))
.body(redirect_uri)
} else {
HttpResponse::build(self.status_code()).json(ApiError {
error: &self.error_type.error_name(),
description: self.error_type.to_string(),
})
}
}
}
#[derive(thiserror::Error, Debug)]
pub enum OAuthErrorType {
#[error(transparent)]
AuthenticationError(#[from] AuthenticationError),
#[error("Client {} has no redirect URIs specified", .client_id.0)]
ClientMissingRedirectURI {
client_id: crate::database::models::OAuthClientId,
},
#[error(
"The provided redirect URI did not match any configured in the client"
)]
RedirectUriNotConfigured(String),
#[error(
"The provided scope was malformed or did not correspond to known scopes ({0})"
)]
FailedScopeParse(bitflags::parser::ParseError),
#[error(
"The provided scope requested scopes broader than the developer app is configured with"
)]
ScopesTooBroad,
#[error("The provided flow id was invalid")]
InvalidAcceptFlowId,
#[error("The provided client id was invalid")]
InvalidClientId(crate::database::models::OAuthClientId),
#[error("The provided ID could not be decoded: {0}")]
MalformedId(#[from] DecodingError),
#[error("Failed to authenticate client")]
ClientAuthenticationFailed,
#[error("The provided authorization grant code was invalid")]
InvalidAuthCode,
#[error(
"The provided client id did not match the id this authorization code was granted to"
)]
UnauthorizedClient,
#[error(
"The provided redirect URI did not exactly match the uri originally provided when this flow began"
)]
RedirectUriChanged(Option<String>),
#[error("The provided grant type ({0}) must be \"authorization_code\"")]
OnlySupportsAuthorizationCodeGrant(String),
#[error("The resource owner denied the request")]
AccessDenied,
}
impl From<crate::database::models::DatabaseError> for OAuthErrorType {
fn from(value: crate::database::models::DatabaseError) -> Self {
OAuthErrorType::AuthenticationError(value.into())
}
}
impl From<sqlx::Error> for OAuthErrorType {
fn from(value: sqlx::Error) -> Self {
OAuthErrorType::AuthenticationError(value.into())
}
}
impl OAuthErrorType {
pub fn error_name(&self) -> String {
// IETF RFC 6749 4.1.2.1 (https://datatracker.ietf.org/doc/html/rfc6749#autoid-38)
// And 5.2 (https://datatracker.ietf.org/doc/html/rfc6749#section-5.2)
match self {
Self::RedirectUriNotConfigured(_)
| Self::ClientMissingRedirectURI { client_id: _ } => "invalid_uri",
Self::AuthenticationError(_) | Self::InvalidAcceptFlowId => {
"server_error"
}
Self::RedirectUriChanged(_) | Self::MalformedId(_) => {
"invalid_request"
}
Self::FailedScopeParse(_) | Self::ScopesTooBroad => "invalid_scope",
Self::InvalidClientId(_) | Self::ClientAuthenticationFailed => {
"invalid_client"
}
Self::InvalidAuthCode
| Self::OnlySupportsAuthorizationCodeGrant(_) => "invalid_grant",
Self::UnauthorizedClient => "unauthorized_client",
Self::AccessDenied => "access_denied",
}
.to_string()
}
}