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
This commit is contained in:
Jackson Kruger
2023-10-30 11:14:38 -05:00
committed by GitHub
parent 8803e11945
commit 6cfd4637db
54 changed files with 3658 additions and 135 deletions

View File

@@ -91,12 +91,7 @@ where
let token = if let Some(token) = token {
token
} else {
let headers = req.headers();
let token_val: Option<&HeaderValue> = headers.get(AUTHORIZATION);
token_val
.ok_or_else(|| AuthenticationError::InvalidAuthMethod)?
.to_str()
.map_err(|_| AuthenticationError::InvalidCredentials)?
extract_authorization_header(req)?
};
let possible_user = match token.split_once('_') {
@@ -142,6 +137,25 @@ where
user.map(|x| (Scopes::all(), x))
}
Some(("mro", _)) => {
use crate::database::models::oauth_token_item::OAuthAccessToken;
let hash = OAuthAccessToken::hash_token(token);
let access_token =
crate::database::models::oauth_token_item::OAuthAccessToken::get(hash, executor)
.await?
.ok_or(AuthenticationError::InvalidCredentials)?;
if access_token.expires < Utc::now() {
return Err(AuthenticationError::InvalidCredentials);
}
let user = user_item::User::get_id(access_token.user_id, executor, redis).await?;
session_queue.add_oauth_access_token(access_token.id).await;
user.map(|u| (access_token.scopes, u))
}
Some(("github", _)) | Some(("gho", _)) | Some(("ghp", _)) => {
let user = AuthProvider::GitHub.get_user(token).await?;
let id = AuthProvider::GitHub.get_user_id(&user.id, executor).await?;
@@ -160,6 +174,15 @@ where
Ok(possible_user)
}
pub fn extract_authorization_header(req: &HttpRequest) -> Result<&str, AuthenticationError> {
let headers = req.headers();
let token_val: Option<&HeaderValue> = headers.get(AUTHORIZATION);
token_val
.ok_or_else(|| AuthenticationError::InvalidAuthMethod)?
.to_str()
.map_err(|_| AuthenticationError::InvalidCredentials)
}
pub async fn check_is_moderator_from_headers<'a, 'b, E>(
req: &HttpRequest,
executor: E,