diff options
author | Kjetil Orbekk <kj@orbekk.com> | 2022-10-13 08:12:59 -0400 |
---|---|---|
committer | Kjetil Orbekk <kj@orbekk.com> | 2022-10-13 08:12:59 -0400 |
commit | accb9032b9abe595020a27dd2f7b666cb7028f67 (patch) | |
tree | 4c92937ad368e93e9bb9ddf9a0ebb31e8288c04b | |
parent | 8b5d16152ffb7d55811a7a558f67620a94e4cbf0 (diff) |
Add AuthenticatedSession request extractor
-rw-r--r-- | server/src/auth.rs | 51 | ||||
-rw-r--r-- | server/src/error.rs | 13 | ||||
-rw-r--r-- | server/src/main.rs | 46 | ||||
-rw-r--r-- | server/src/server.rs | 13 |
4 files changed, 75 insertions, 48 deletions
diff --git a/server/src/auth.rs b/server/src/auth.rs index 0be1b85..98a0000 100644 --- a/server/src/auth.rs +++ b/server/src/auth.rs @@ -6,9 +6,13 @@ use std::{ sync::{Arc, Mutex}, }; -use crate::error::BridgeError; +use crate::{error::BridgeError, server::ContextExtension}; use async_trait::async_trait; -use axum::{extract::FromRequest, http}; +use axum::{ + extract::FromRequest, + response::{IntoResponse, Response}, + Json, +}; use chrono::{DateTime, Utc}; use lru::LruCache; use openidconnect::{ @@ -22,7 +26,7 @@ use openidconnect::{ use protocol::UserInfo; use serde::{Deserialize, Serialize}; use sqlx::PgPool; -use tower_cookies::Cookies; +use tower_cookies::{Cookie, Cookies}; use tracing::{debug, error, info}; use uuid::Uuid; @@ -335,25 +339,42 @@ pub async fn fetch_authenticated_session( } } -#[derive(Clone, Debug, Default)] -pub struct LoggedInUser { - _priv: (), -} - #[async_trait] -impl<B> FromRequest<B> for LoggedInUser +impl<B> FromRequest<B> for AuthenticatedSession where B: Send, { - type Rejection = (http::StatusCode, &'static str); + type Rejection = Response; async fn from_request( req: &mut axum::extract::RequestParts<B>, ) -> Result<Self, Self::Rejection> { - info!( - "Creating LoggedInUser; found cookies: {:?}", - req.extensions().get::<Cookies>().cloned() - ); - Ok(LoggedInUser { _priv: () }) + let cookies = Cookies::from_request(req) + .await + .map_err(|e| e.into_response())?; + let extension = ContextExtension::from_request(req) + .await + .map_err(|e| e.into_response())?; + let cookie = match cookies.get("user-id") { + None => return Err(BridgeError::NotLoggedIn.into_response()), + Some(v) => v, + }; + + let session_id: SessionId = match SessionId::from_str(cookie.value()) { + Err(e) => { + info!("Clearing cookie that failed to parse {cookie:?}: {e}"); + cookies.remove(cookie.into_owned()); + return Err(BridgeError::NotLoggedIn.into_response()); + } + Ok(s) => s, + }; + let session = match crate::auth::fetch_authenticated_session(&extension.db, &session_id) + .await + .map_err(|e| e.into_response())? + { + None => return Err(BridgeError::NotLoggedIn.into_response()), + Some(v) => v, + }; + Ok(session) } } diff --git a/server/src/error.rs b/server/src/error.rs index cea23e7..aef2687 100644 --- a/server/src/error.rs +++ b/server/src/error.rs @@ -1,4 +1,4 @@ -use axum::{http::StatusCode, response::IntoResponse}; +use axum::{http::{StatusCode, self}, response::IntoResponse}; use openidconnect::{core::CoreErrorResponseType, ClaimsVerificationError, StandardErrorResponse}; use tracing::error; @@ -26,6 +26,9 @@ pub enum BridgeError { #[error("Unexpected authorization error")] UnexpectedInvalidAuthorization(#[from] ClaimsVerificationError), + #[error("User is not logged in")] + NotLoggedIn, + #[error("Authentication error")] SigningFailed(#[from] openidconnect::SigningError), @@ -42,9 +45,15 @@ pub enum BridgeError { DurationOutOfRange(#[from] time::OutOfRangeError), } +impl BridgeError { + pub fn as_rejection(&self) -> (http::StatusCode, String) { + (StatusCode::INTERNAL_SERVER_ERROR, format!("Error: {self}")) + } +} + impl IntoResponse for BridgeError { fn into_response(self) -> axum::response::Response { error!("Error occurred: {self:?}"); - (StatusCode::INTERNAL_SERVER_ERROR, format!("Error: {self}")).into_response() + self.as_rejection().into_response() } } diff --git a/server/src/main.rs b/server/src/main.rs index fd28789..b961c17 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -1,6 +1,6 @@ use std::{collections::HashMap, env, str::FromStr, sync::Arc}; -use auth::{AuthenticatedSession, LoggedInUser}; +use auth::AuthenticatedSession; use axum::{ extract::{Extension, Query}, response::Redirect, @@ -8,23 +8,21 @@ use axum::{ Json, Router, }; use protocol::{Table, UserInfo}; +use server::ContextExtension; use tower_cookies::{Cookie, CookieManagerLayer, Cookies}; use tower_http::trace::TraceLayer; use tracing::info; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; mod auth; mod error; -use crate::auth::{Authenticator, SessionId}; +mod server; use crate::error::BridgeError; +use crate::{ + auth::{Authenticator, SessionId}, + server::ServerContext, +}; use sqlx::{postgres::PgPoolOptions, PgPool}; -pub struct ServerContext { - pub app_url: String, - pub authenticator: Authenticator, - pub db: PgPool, -} -type ContextExtension = Extension<Arc<ServerContext>>; - #[tokio::main] async fn main() { dotenv::dotenv().ok(); @@ -60,6 +58,7 @@ async fn main() { let app = Router::new() .route("/api/user/info", get(user_info)) + // .route("/api/user/table", get(user_table)) .route("/api/login", get(login)) .route(auth::LOGIN_CALLBACK, get(login_callback)) .layer(CookieManagerLayer::new()) @@ -73,36 +72,21 @@ async fn main() { } async fn user_info( - _user: LoggedInUser, - cookies: Cookies, + session: Option<AuthenticatedSession>, extension: ContextExtension, ) -> Result<Json<Option<UserInfo>>, BridgeError> { - let cookie = match cookies.get("user-id") { + let mut session = match session { None => return Ok(Json(None)), - Some(v) => v, + Some(s) => s, }; - - let session_id: SessionId = match SessionId::from_str(cookie.value()) { - Err(e) => { - info!("Clearing cookie that failed to parse {cookie:?}: {e}"); - cookies.remove(cookie.into_owned()); - return Ok(Json(None)); - } - Ok(s) => s, - }; - let mut session = - match crate::auth::fetch_authenticated_session(&extension.db, &session_id).await? { - None => return Ok(Json(None)), - Some(v) => v, - }; Ok(Json(Some(UserInfo { username: extension.authenticator.user_info(&mut session).await?, - table: get_table(&extension.db, &session).await?, + table: user_table(extension, &session).await?, }))) } -async fn get_table( - db: &PgPool, +async fn user_table( + extension: ContextExtension, session: &AuthenticatedSession, ) -> Result<Option<Table>, BridgeError> { Ok(sqlx::query_as!( @@ -115,7 +99,7 @@ async fn get_table( "#, session.player_id ) - .fetch_optional(db) + .fetch_optional(&extension.db) .await?) } diff --git a/server/src/server.rs b/server/src/server.rs new file mode 100644 index 0000000..eddba94 --- /dev/null +++ b/server/src/server.rs @@ -0,0 +1,13 @@ +use sqlx::PgPool; +use std::sync::Arc; + +use axum::Extension; + +use crate::auth::Authenticator; + +pub struct ServerContext { + pub app_url: String, + pub authenticator: Authenticator, + pub db: PgPool, +} +pub type ContextExtension = Extension<Arc<ServerContext>>; |