1
0
Files
AstralRinth/src/auth/oauth/uris.rs
Jackson Kruger 6cfd4637db OAuth 2.0 Authorization Server [MOD-559] (#733)
* WIP end-of-day push

* Authorize endpoint, accept endpoints, DB stuff for oauth clients, their redirects, and client authorizations

* OAuth Client create route

* Get user clients

* Client delete

* Edit oauth client

* Include redirects in edit client route

* Database stuff for tokens

* Reorg oauth stuff out of auth/flows and into its own module

* Impl OAuth get access token endpoint

* Accept oauth access tokens as auth and update through AuthQueue

* User OAuth authorization management routes

* Forgot to actually add the routes lol

* Bit o cleanup

* Happy path test for OAuth and minor fixes for things it found

* Add dummy data oauth client (and detect/handle dummy data version changes)

* More tests

* Another test

* More tests and reject endpoint

* Test oauth client and authorization management routes

* cargo sqlx prepare

* dead code warning

* Auto clippy fixes

* Uri refactoring

* minor name improvement

* Don't compile-time check the test sqlx queries

* Trying to fix db concurrency problem to get tests to pass

* Try fix from test PR

* Fixes for updated sqlx

* Prevent restricted scopes from being requested or issued

* Get OAuth client(s)

* Remove joined oauth client info from authorization returns

* Add default conversion to OAuthError::error so we can use ?

* Rework routes

* Consolidate scopes into SESSION_ACCESS

* Cargo sqlx prepare

* Parse to OAuthClientId automatically through serde and actix

* Cargo clippy

* Remove validation requiring 1 redirect URI on oauth client creation

* Use serde(flatten) on OAuthClientCreationResult
2023-10-30 09:14:38 -07:00

95 lines
3.0 KiB
Rust

use super::errors::OAuthError;
use crate::auth::oauth::OAuthErrorType;
use crate::database::models::OAuthClientId;
use serde::{Deserialize, Serialize};
#[derive(derive_new::new, Serialize, Deserialize)]
pub struct OAuthRedirectUris {
pub original: Option<String>,
pub validated: ValidatedRedirectUri,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ValidatedRedirectUri(pub String);
impl ValidatedRedirectUri {
pub fn validate<'a>(
to_validate: &Option<String>,
validate_against: impl IntoIterator<Item = &'a str> + Clone,
client_id: OAuthClientId,
) -> Result<Self, OAuthError> {
if let Some(first_client_redirect_uri) = validate_against.clone().into_iter().next() {
if let Some(to_validate) = to_validate {
if validate_against
.into_iter()
.any(|uri| same_uri_except_query_components(uri, to_validate))
{
Ok(ValidatedRedirectUri(to_validate.clone()))
} else {
Err(OAuthError::error(OAuthErrorType::RedirectUriNotConfigured(
to_validate.clone(),
)))
}
} else {
Ok(ValidatedRedirectUri(first_client_redirect_uri.to_string()))
}
} else {
Err(OAuthError::error(
OAuthErrorType::ClientMissingRedirectURI { client_id },
))
}
}
}
fn same_uri_except_query_components(a: &str, b: &str) -> bool {
let mut a_components = a.split('?');
let mut b_components = b.split('?');
a_components.next() == b_components.next()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn validate_for_none_returns_first_valid_uri() {
let validate_against = vec!["https://modrinth.com/a"];
let validated =
ValidatedRedirectUri::validate(&None, validate_against.clone(), OAuthClientId(0))
.unwrap();
assert_eq!(validate_against[0], validated.0);
}
#[test]
fn validate_for_valid_uri_returns_first_matching_uri_ignoring_query_params() {
let validate_against = vec![
"https://modrinth.com/a?q3=p3&q4=p4",
"https://modrinth.com/a/b/c?q1=p1&q2=p2",
];
let to_validate = "https://modrinth.com/a/b/c?query0=param0&query1=param1".to_string();
let validated = ValidatedRedirectUri::validate(
&Some(to_validate.clone()),
validate_against,
OAuthClientId(0),
)
.unwrap();
assert_eq!(to_validate, validated.0);
}
#[test]
fn validate_for_invalid_uri_returns_err() {
let validate_against = vec!["https://modrinth.com/a"];
let to_validate = "https://modrinth.com/a/b".to_string();
let validated =
ValidatedRedirectUri::validate(&Some(to_validate), validate_against, OAuthClientId(0));
assert!(validated
.is_err_and(|e| matches!(e.error_type, OAuthErrorType::RedirectUriNotConfigured(_))));
}
}