diff options
author | Kjetil Orbekk <kj@orbekk.com> | 2022-11-25 11:32:43 -0500 |
---|---|---|
committer | Kjetil Orbekk <kj@orbekk.com> | 2022-11-25 11:32:43 -0500 |
commit | f4713b1ccf508c0ec1192ce8d800f21111e655e1 (patch) | |
tree | 25d5641b006af4533c2a25e762526428dc397a85 | |
parent | 24c71b3ddbcd793e533ee518bc1f82b1b5fa3d9e (diff) |
Extract authenticator logic into a trait
This is in preparation for providing a fake authenticator backend
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | flake.nix | 4 | ||||
-rw-r--r-- | server/src/auth.rs | 98 | ||||
-rw-r--r-- | server/src/main.rs | 9 | ||||
-rw-r--r-- | server/src/server.rs | 2 |
5 files changed, 68 insertions, 48 deletions
@@ -8,4 +8,5 @@ tmp/ target/ **/*.rs.bk webapp/dist -result
\ No newline at end of file +result +.env
\ No newline at end of file @@ -4,6 +4,10 @@ nixpkgs.url = "nixpkgs"; }; + # TODO: Stuff to include in rust development environment: + # - trunk + # - cargo-watch + # - sqlx-cli outputs = { self, nixpkgs, flake-utils }: flake-utils.lib.eachDefaultSystem (system: let pkgs = nixpkgs.legacyPackages.${system}; diff --git a/server/src/auth.rs b/server/src/auth.rs index 445a0d2..7222199 100644 --- a/server/src/auth.rs +++ b/server/src/auth.rs @@ -42,7 +42,19 @@ pub struct AuthenticatedSession { refresh_token: RefreshToken, } -pub struct Authenticator { +#[async_trait] +pub trait Authenticator { + async fn user_info(&self, session: &mut AuthenticatedSession) -> Result<String, BridgeError>; + async fn authenticate( + &self, + pool: &PgPool, + session_id: SessionId, + auth_params: HashMap<String, String>, + ) -> Result<AuthenticatedSession, BridgeError>; + async fn get_login_url(&self) -> (SessionId, Url); +} + +pub struct OauthAuthenticator { pub client: CoreClient, pub login_cache: Arc<Mutex<LruCache<SessionId, LoginState>>>, pub db: PgPool, @@ -82,7 +94,7 @@ fn redirect_url(app_url: &str) -> RedirectUrl { RedirectUrl::new(format!("{}{}", app_url, LOGIN_CALLBACK)).unwrap() } -impl Authenticator { +impl OauthAuthenticator { pub async fn new( db: PgPool, issuer_url: IssuerUrl, @@ -111,7 +123,7 @@ impl Authenticator { pub async fn from_env(db: PgPool) -> Self { let app_url = env::var("APP_URL").unwrap(); - Authenticator::new( + OauthAuthenticator::new( db, IssuerUrl::new(env::var("OPENID_ISSUER_URL").unwrap()).unwrap(), ClientId::new(env::var("OPENID_CLIENT_ID").unwrap()), @@ -121,26 +133,7 @@ impl Authenticator { .await } - pub async fn get_login_url(&self) -> (SessionId, Url) { - let (auth_url, csrf_token, nonce) = self - .client - .authorize_url( - AuthenticationFlow::<CoreResponseType>::AuthorizationCode, - CsrfToken::new_random, - Nonce::new_random, - ) - .add_scope(Scope::new("email".to_string())) - .add_scope(Scope::new("profile".to_string())) - .url(); - let user_id = SessionId::new(); - self.login_cache - .lock() - .unwrap() - .put(user_id.clone(), LoginState { csrf_token, nonce }); - (user_id, auth_url) - } - - pub async fn maybe_refresh_token( + async fn maybe_refresh_token( &self, session: &mut AuthenticatedSession, ) -> Result<(), BridgeError> { @@ -168,8 +161,46 @@ impl Authenticator { store_authenticated_session(&self.db, session).await?; Ok(()) } +} - pub async fn authenticate( +#[async_trait] +impl Authenticator for OauthAuthenticator { + async fn get_login_url(&self) -> (SessionId, Url) { + let (auth_url, csrf_token, nonce) = self + .client + .authorize_url( + AuthenticationFlow::<CoreResponseType>::AuthorizationCode, + CsrfToken::new_random, + Nonce::new_random, + ) + .add_scope(Scope::new("email".to_string())) + .add_scope(Scope::new("profile".to_string())) + .url(); + let user_id = SessionId::new(); + self.login_cache + .lock() + .unwrap() + .put(user_id.clone(), LoginState { csrf_token, nonce }); + (user_id, auth_url) + } + + async fn user_info(&self, session: &mut AuthenticatedSession) -> Result<String, BridgeError> { + self.maybe_refresh_token(session).await?; + let user_info: CoreUserInfoClaims = self + .client + .user_info(session.access_token.clone(), None)? + .request_async(async_http_client) + .await?; + debug!("Resolved user info: {user_info:#?}"); + Ok(user_info + .preferred_username() + .ok_or(BridgeError::Internal( + "missing preferred username".to_string(), + ))? + .to_string()) + } + + async fn authenticate( &self, pool: &PgPool, session_id: SessionId, @@ -240,25 +271,6 @@ impl Authenticator { store_authenticated_session(pool, &mut session).await?; Ok(session) } - - pub async fn user_info( - &self, - session: &mut AuthenticatedSession, - ) -> Result<String, BridgeError> { - self.maybe_refresh_token(session).await?; - let user_info: CoreUserInfoClaims = self - .client - .user_info(session.access_token.clone(), None)? - .request_async(async_http_client) - .await?; - debug!("Resolved user info: {user_info:#?}"); - Ok(user_info - .preferred_username() - .ok_or(BridgeError::Internal( - "missing preferred username".to_string(), - ))? - .to_string()) - } } async fn store_authenticated_session( diff --git a/server/src/main.rs b/server/src/main.rs index 130506c..2363110 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -13,14 +13,14 @@ use protocol::{bridge_engine::{self, TableView, Player}, Table, UserInfo}; use server::ContextExtension; use tower_cookies::{Cookie, CookieManagerLayer, Cookies}; use tower_http::trace::TraceLayer; -use tracing::info; +use tracing::{info, log::warn}; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; mod auth; mod error; mod play; mod server; use crate::{ - auth::{Authenticator, SessionId}, + auth::{OauthAuthenticator, SessionId}, server::ServerContext, }; use crate::{ @@ -61,9 +61,12 @@ async fn main() { let app_url = env::var("APP_URL").unwrap(); + #[cfg(debug_assertions)] + warn!("Running a debug build."); + let state = Arc::new(ServerContext { app_url, - authenticator: Authenticator::from_env(db_pool.clone()).await, + authenticator: Box::new(OauthAuthenticator::from_env(db_pool.clone()).await), db: db_pool, }); diff --git a/server/src/server.rs b/server/src/server.rs index eddba94..647abf9 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -7,7 +7,7 @@ use crate::auth::Authenticator; pub struct ServerContext { pub app_url: String, - pub authenticator: Authenticator, + pub authenticator: Box<dyn Authenticator + Send + Sync>, pub db: PgPool, } pub type ContextExtension = Extension<Arc<ServerContext>>; |