diff options
Diffstat (limited to 'crates')
| -rw-r--r-- | crates/api-auth/Cargo.toml | 1 | ||||
| -rw-r--r-- | crates/api-auth/src/discord/mod.rs | 18 | ||||
| -rw-r--r-- | crates/api-auth/src/lib.rs | 11 | ||||
| -rw-r--r-- | crates/sellershut/src/main.rs | 2 | ||||
| -rw-r--r-- | crates/sellershut/src/server/api/error.rs | 27 | ||||
| -rw-r--r-- | crates/sellershut/src/server/api/mod.rs | 10 | ||||
| -rw-r--r-- | crates/sellershut/src/server/api/routes/auth/discord.rs | 64 | ||||
| -rw-r--r-- | crates/sellershut/src/server/api/routes/auth/mod.rs | 22 | ||||
| -rw-r--r-- | crates/sellershut/src/server/api/routes/mod.rs | 1 | ||||
| -rw-r--r-- | crates/sellershut/src/server/mod.rs | 5 |
10 files changed, 148 insertions, 13 deletions
diff --git a/crates/api-auth/Cargo.toml b/crates/api-auth/Cargo.toml index 7df9411..053bbb9 100644 --- a/crates/api-auth/Cargo.toml +++ b/crates/api-auth/Cargo.toml @@ -17,6 +17,7 @@ sqlx.workspace = true thiserror.workspace = true utoipa = { workspace = true, optional = true } url.workspace = true +async-session = "3.0.0" [features] discord = [] diff --git a/crates/api-auth/src/discord/mod.rs b/crates/api-auth/src/discord/mod.rs index a39722d..29b9bc2 100644 --- a/crates/api-auth/src/discord/mod.rs +++ b/crates/api-auth/src/discord/mod.rs @@ -1,8 +1,10 @@ use api_core::models::user::User; +use async_session::Session; use async_trait::async_trait; +use oauth2::{CsrfToken, Scope}; use sqlx::PgPool; -use crate::{BasicClient, OauthDriver, error::AuthError}; +use crate::{BasicClient, CSRF_TOKEN, OauthDriver, error::AuthError}; #[derive(Clone, Debug)] pub struct AuthServiceDiscord { @@ -24,7 +26,19 @@ impl OauthDriver for AuthServiceDiscord { async fn get_user(&self) -> Result<User, AuthError> { todo!() } - async fn create_session(&self, _user: &User) { + async fn create_oauth_session(&self)->Result<String,AuthError> { + let (auth_url, csrf_token) = self + .client + .authorize_url(CsrfToken::new_random) + .add_scope(Scope::new("identify".to_string())) + .url(); + + let mut session = Session::new(); + session.insert(CSRF_TOKEN, &csrf_token).unwrap(); + + Ok(String::default()) + } + async fn save_session(&self, user: &User)->Result<(), AuthError>{ todo!() } } diff --git a/crates/api-auth/src/lib.rs b/crates/api-auth/src/lib.rs index 284b772..95a04c4 100644 --- a/crates/api-auth/src/lib.rs +++ b/crates/api-auth/src/lib.rs @@ -3,7 +3,6 @@ pub mod discord; mod error; use api_core::auth::AuthClientConfig; -use api_core::auth::provider::OauthProvider; use api_core::models::user::User; pub use error::AuthClientError; @@ -24,20 +23,16 @@ pub struct BasicClient(C); pub trait OauthDriver: Send + Sync + std::fmt::Debug { async fn get_auth_token(&self) -> Result<String, AuthError>; async fn get_user(&self) -> Result<User, AuthError>; - async fn create_session(&self, user: &User); + async fn create_oauth_session(&self)->Result<String, AuthError>; + async fn save_session(&self, user: &User)->Result<(), AuthError>; } use oauth2::{AuthUrl, ClientId, ClientSecret, RedirectUrl, TokenUrl}; -use sqlx::PgPool; -use std::collections::HashMap; -use std::sync::Arc; use std::{convert::TryFrom, ops::Deref}; use crate::error::AuthError; -pub struct OauthService { - clients: HashMap<OauthProvider, Arc<dyn OauthDriver>>, -} +static CSRF_TOKEN: &str = "csrf_token"; impl Deref for BasicClient { type Target = C; diff --git a/crates/sellershut/src/main.rs b/crates/sellershut/src/main.rs index fca10e1..ebae4ed 100644 --- a/crates/sellershut/src/main.rs +++ b/crates/sellershut/src/main.rs @@ -16,7 +16,7 @@ use api_core::{ }; use clap::Parser; use sqlx::PgPool; -use tokio::{net::TcpListener}; +use tokio::net::TcpListener; use tracing::info; use crate::{ diff --git a/crates/sellershut/src/server/api/error.rs b/crates/sellershut/src/server/api/error.rs new file mode 100644 index 0000000..6d07f9f --- /dev/null +++ b/crates/sellershut/src/server/api/error.rs @@ -0,0 +1,27 @@ +use axum::{ + http::StatusCode, + response::{IntoResponse, Response}, +}; + +#[derive(Debug)] +pub struct AppError(anyhow::Error); + +// Tell axum how to convert `AppError` into a response. +impl IntoResponse for AppError { + fn into_response(self) -> Response { + tracing::error!("Application error: {:#}", self.0); + + (StatusCode::INTERNAL_SERVER_ERROR, "Something went wrong").into_response() + } +} + +// This enables using `?` on functions that return `Result<_, anyhow::Error>` to turn them into +// `Result<_, AppError>`. That way you don't need to do that manually. +impl<E> From<E> for AppError +where + E: Into<anyhow::Error>, +{ + fn from(err: E) -> Self { + Self(err.into()) + } +} diff --git a/crates/sellershut/src/server/api/mod.rs b/crates/sellershut/src/server/api/mod.rs index c227f59..ebe29f8 100644 --- a/crates/sellershut/src/server/api/mod.rs +++ b/crates/sellershut/src/server/api/mod.rs @@ -1,9 +1,15 @@ +pub mod error; + use api_core::health::ApiDocBase; use axum::Router; use utoipa::OpenApi; use utoipa_axum::router::OpenApiRouter; -use crate::{config::Config, server::api::routes::ServerConfigDoc, state::AppState}; +use crate::{ + config::Config, + server::api::routes::{ServerConfigDoc, auth::AuthDoc}, + state::AppState, +}; pub mod routes; @@ -20,10 +26,12 @@ pub async fn router(state: AppState, config: Config) -> Router<()> { doc.merge(ApiDocBase::openapi()); doc.merge(ServerConfigDoc::openapi()); + doc.merge(AuthDoc::openapi()); let stubs = OpenApiRouter::with_openapi(doc) .routes(utoipa_axum::routes!(routes::health)) .nest("/api", routes::router(state.clone())) + .nest("/api", routes::auth::router(state.clone())) .with_state(state); let (router, _api) = stubs.split_for_parts(); diff --git a/crates/sellershut/src/server/api/routes/auth/discord.rs b/crates/sellershut/src/server/api/routes/auth/discord.rs new file mode 100644 index 0000000..163619b --- /dev/null +++ b/crates/sellershut/src/server/api/routes/auth/discord.rs @@ -0,0 +1,64 @@ +use crate::server::api::error::AppError; +use anyhow::Context; +use api_core::auth::provider::OauthProvider; +use axum::{ + extract::State, + http::HeaderMap, + response::{IntoResponse, Redirect}, +}; + +use crate::state::AppState; + +/// Update log level +#[utoipa::path( + patch, + responses( + ( + status = 200, + description = "A redirect to discord", + headers( + ("x-request-id", description = "Request identifier") + ) + ), + ), + operation_id = "auth_discord", // https://github.com/juhaku/utoipa/issues/1170 + path = "/auth", + tag = super::AUTH, +)] +pub async fn discord_auth(State(state): State<AppState>) -> Result<impl IntoResponse, AppError> { + let client = state + .auth_clients + .get(&OauthProvider::Discord) + .context("missing discord driver")?; + + let headers = HeaderMap::new(); + Ok((headers, Redirect::to(redirect_url))) + + // let (auth_url, csrf_token) = client + // .authorize_url(CsrfToken::new_random) + // .add_scope(Scope::new("identify".to_string())) + // .url(); + // + // // Create session to store csrf_token + // let mut session = Session::new(); + // session + // .insert(CSRF_TOKEN, &csrf_token) + // .context("failed in inserting CSRF token into session")?; + // + // // Store the session in MemoryStore and retrieve the session cookie + // let cookie = store + // .store_session(session) + // .await + // .context("failed to store CSRF token session")? + // .context("unexpected error retrieving CSRF cookie value")?; + // + // // Attach the session cookie to the response header + // let cookie = format!("{COOKIE_NAME}={cookie}; SameSite=Lax; HttpOnly; Secure; Path=/"); + // let mut headers = HeaderMap::new(); + // headers.insert( + // SET_COOKIE, + // cookie.parse().context("failed to parse cookie")?, + // ); + // + // Ok((headers, Redirect::to(auth_url.as_ref()))) +} diff --git a/crates/sellershut/src/server/api/routes/auth/mod.rs b/crates/sellershut/src/server/api/routes/auth/mod.rs new file mode 100644 index 0000000..3e36eaa --- /dev/null +++ b/crates/sellershut/src/server/api/routes/auth/mod.rs @@ -0,0 +1,22 @@ +#[cfg(feature = "auth-discord")] +mod discord; + +use utoipa::OpenApi; +use utoipa_axum::router::OpenApiRouter; + +use crate::state::AppState; + +const AUTH: &str = "Authentication"; + +#[derive(OpenApi)] +#[openapi(tags((name = AUTH, description = "Transaction monitoring endpoints")))] +pub struct AuthDoc; + +pub fn router(store: AppState) -> OpenApiRouter<AppState> { + let router = OpenApiRouter::new(); + + #[cfg(feature = "auth-discord")] + let router = router.routes(utoipa_axum::routes!(discord::discord_auth)); + + router.with_state(store) +} diff --git a/crates/sellershut/src/server/api/routes/mod.rs b/crates/sellershut/src/server/api/routes/mod.rs index 1de8e80..822bfe7 100644 --- a/crates/sellershut/src/server/api/routes/mod.rs +++ b/crates/sellershut/src/server/api/routes/mod.rs @@ -1,3 +1,4 @@ +pub mod auth; mod logs; use axum::{extract::State, response::IntoResponse}; diff --git a/crates/sellershut/src/server/mod.rs b/crates/sellershut/src/server/mod.rs index a66eed5..f8ea2c5 100644 --- a/crates/sellershut/src/server/mod.rs +++ b/crates/sellershut/src/server/mod.rs @@ -3,7 +3,10 @@ pub mod logs; #[cfg(test)] mod boostrap { - use std::{collections::HashMap, sync::{Arc, OnceLock}}; + use std::{ + collections::HashMap, + sync::{Arc, OnceLock}, + }; use api_core::health::BaseService; use tracing_subscriber::{EnvFilter, Registry, layer::SubscriberExt, reload}; |
