diff options
author | Kjetil Orbekk <kj@orbekk.com> | 2022-10-08 10:30:15 -0400 |
---|---|---|
committer | Kjetil Orbekk <kj@orbekk.com> | 2022-10-08 10:30:15 -0400 |
commit | 1cbf881835fc33859a31645f886c5d3787ed48f8 (patch) | |
tree | eb7a8ac803e33283ea0efffa015c8bd96ca40c29 | |
parent | b727db0d64f4250742b0ebaac0149c1224a0d040 (diff) |
Add access token validation
-rw-r--r-- | Cargo.lock | 54 | ||||
-rw-r--r-- | server/Cargo.toml | 6 | ||||
-rw-r--r-- | server/src/auth.rs | 60 | ||||
-rw-r--r-- | server/src/error.rs | 28 | ||||
-rw-r--r-- | server/src/main.rs | 13 | ||||
-rw-r--r-- | webapp/src/bridge_engine.rs | 2 |
6 files changed, 142 insertions, 21 deletions
@@ -247,9 +247,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" dependencies = [ "iana-time-zone", + "js-sys", "num-integer", "num-traits", "serde", + "time 0.1.44", + "wasm-bindgen", "winapi", ] @@ -270,7 +273,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "344adc371239ef32293cb1c4fe519592fcf21206c79c02854320afcdf3ab4917" dependencies = [ "percent-encoding", - "time", + "time 0.3.15", "version_check", ] @@ -573,7 +576,7 @@ dependencies = [ "cfg-if 1.0.0", "js-sys", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "wasm-bindgen", ] @@ -882,6 +885,19 @@ dependencies = [ ] [[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] name = "iana-time-zone" version = "0.1.50" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1077,7 +1093,7 @@ checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" dependencies = [ "libc", "log", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys", ] @@ -1533,10 +1549,12 @@ dependencies = [ "http-body", "hyper", "hyper-rustls", + "hyper-tls", "ipnet", "js-sys", "log", "mime", + "native-tls", "once_cell", "percent-encoding", "pin-project-lite", @@ -1546,6 +1564,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "tokio", + "tokio-native-tls", "tokio-rustls", "tower-service", "url", @@ -1743,14 +1762,18 @@ dependencies = [ name = "server" version = "0.1.0" dependencies = [ + "anyhow", "axum", + "chrono", "dotenv", "lru", "openidconnect", "protocol", + "reqwest", "serde", "serde_json", "sqlx", + "thiserror", "tokio", "tower", "tower-cookies", @@ -2011,18 +2034,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.34" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c1b05ca9d106ba7d2e31a9dab4a64e7be2cce415321966ea3132c49a656e252" +checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.34" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8f2591983642de85c921015f3f070c665a197ed69e417af436115e3a1407487" +checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" dependencies = [ "proc-macro2", "quote", @@ -2040,6 +2063,17 @@ dependencies = [ [[package]] name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + +[[package]] +name = "time" version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d634a985c4d4238ec39cacaed2e7ae552fbd3c476b552c1deac3021b7d7eaf0c" @@ -2410,6 +2444,12 @@ dependencies = [ [[package]] name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" diff --git a/server/Cargo.toml b/server/Cargo.toml index e7824a2..0651fef 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -21,4 +21,8 @@ uuid = { version = "1.1.2", features = ["serde", "fast-rng", "v4"] } tower-cookies = "0.7.0" tower = { version = "0.4.13", features = ["full"] } urlencoding = "2.1.2" -sqlx = { version = "0.6", features = [ "runtime-tokio-native-tls" , "postgres" ] }
\ No newline at end of file +sqlx = { version = "0.6", features = [ "runtime-tokio-native-tls" , "postgres" ] } +anyhow = "1.0.65" +chrono = { version = "0.4.22", features = ["serde"] } +thiserror = "1.0.37" +reqwest = "0.11.12" diff --git a/server/src/auth.rs b/server/src/auth.rs index c5f9e64..01ee467 100644 --- a/server/src/auth.rs +++ b/server/src/auth.rs @@ -1,9 +1,12 @@ use std::{ + collections::HashMap, env, num::NonZeroUsize, - sync::{Arc, Mutex}, collections::HashMap, + sync::{Arc, Mutex}, }; +use crate::error::BridgeError; +use chrono::Utc; use lru::LruCache; use openidconnect::{ core::{CoreClient, CoreProviderMetadata, CoreResponseType}, @@ -12,9 +15,9 @@ use openidconnect::{ AccessTokenHash, AuthenticationFlow, AuthorizationCode, ClientId, ClientSecret, CsrfToken, IssuerUrl, Nonce, OAuth2TokenResponse, PkceCodeChallenge, RedirectUrl, Scope, TokenResponse, }; +use serde::{Deserialize, Serialize}; use tracing::info; use uuid::Uuid; -use serde::{Deserialize, Serialize}; pub struct LoginState { csrf_token: CsrfToken, @@ -91,15 +94,58 @@ impl Authenticator { .url(); let user_id = EndUserId::new(); self.login_cache - .lock().unwrap() + .lock() + .unwrap() .put(user_id.clone(), LoginState { csrf_token, nonce }); (user_id, auth_url) } - pub async fn authenticate(&self, user_id: EndUserId, auth_params: HashMap<String, String>) { - let state = self.login_cache.lock().unwrap().pop(&user_id).unwrap(); - info!("state: {:?}, {:?}", state.csrf_token.secret(), state.nonce.secret()); + pub async fn authenticate( + &self, + user_id: EndUserId, + auth_params: HashMap<String, String>, + ) -> Result<(), BridgeError> { + // TODO: If the token is missing from the cache, client should retry logging in. + let state = self + .login_cache + .lock() + .unwrap() + .pop(&user_id) + .ok_or(BridgeError::InvalidRequest("token missing".to_string()))?; + info!( + "state: {:?}, {:?}", + state.csrf_token.secret(), + state.nonce.secret() + ); + if Some(state.csrf_token.secret()) != auth_params.get("state") { + return Err(BridgeError::InvalidRequest( + "token validation failed".to_string(), + )); + } + let authorization_code = AuthorizationCode::new( + auth_params + .get("code") + .ok_or(BridgeError::InvalidRequest( + "missing 'code' param".to_string(), + ))? + .to_string(), + ); + + let token = self + .client + .exchange_code(authorization_code) + .request_async(async_http_client) + .await?; + info!("Got token {token:#?}"); + + let id_token = token + .id_token() + .ok_or(BridgeError::InvalidRequest("Server did not return an IdToken".to_string()))?; + let claims = id_token.claims(&self.client.id_token_verifier(), &state.nonce)?; + + info!("Got claims {claims:#?}"); - // params: {"session_state": "909b9959-041b-4a98-84d0-5f978bc8a679", "code": "2b4e95d1-0000-4b28-b49d-7a9de731e82b.909b9959-041b-4a98-84d0-5f978bc8a679.a382d869-4e34-42f1-a64d-24a224b9d338", "state": "a7Hff_hF_FOCqPCxmA1ZXg + // params: {"session_state": "909b9959-041b-4a98-84d0-5f978bc8a679", "code": "2b4e95d1-0000-4b28-b49d-7a9de731e82b.909b9959-041b-4a98-84d0-5f978bc8a679.a382d869-4e34-42f1-a64d-24a224b9d338", "state": "a7Hff_hF_FOCqPCxmA1ZXg + Err(BridgeError::Internal("todo".to_string())) } } diff --git a/server/src/error.rs b/server/src/error.rs new file mode 100644 index 0000000..439e81b --- /dev/null +++ b/server/src/error.rs @@ -0,0 +1,28 @@ +use axum::{http::StatusCode, response::IntoResponse}; +use openidconnect::{core::CoreErrorResponseType, StandardErrorResponse, ClaimsVerificationError}; + +type RequestTokenError = openidconnect::RequestTokenError< + openidconnect::reqwest::Error<reqwest::Error>, + StandardErrorResponse<CoreErrorResponseType>, +>; + +#[derive(thiserror::Error, Debug)] +pub enum BridgeError { + #[error("Invalid request: {0}")] + InvalidRequest(String), + + #[error("Backend request failed")] + Backend(#[from] RequestTokenError), + + #[error("Unexpected authorization error")] + UnexpectedInvalidAuthorization(#[from] ClaimsVerificationError), + + #[error("Internal server error: {0}")] + Internal(String), +} + +impl IntoResponse for BridgeError { + fn into_response(self) -> axum::response::Response { + (StatusCode::INTERNAL_SERVER_ERROR, format!("Error: {self}")).into_response() + } +} diff --git a/server/src/main.rs b/server/src/main.rs index e3a84d9..4183abb 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -2,9 +2,9 @@ use std::{collections::HashMap, env, sync::Arc}; use axum::{ extract::{Extension, Query}, - response::Redirect, + response::{Redirect, IntoResponse}, routing::get, - Json, Router, + Json, Router, http::StatusCode, }; use protocol::UserInfo; use tower_cookies::{Cookie, CookieManagerLayer, Cookies}; @@ -12,8 +12,10 @@ use tower_http::trace::TraceLayer; use tracing::info; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; mod auth; +mod error; use crate::auth::{Authenticator, EndUserId}; use sqlx::{postgres::PgPoolOptions, PgPool}; +use crate::error::BridgeError; pub struct ServerContext { pub app_url: String, @@ -75,14 +77,14 @@ async fn login_callback( cookies: Cookies, Query(params): Query<HashMap<String, String>>, extension: ContextExtension, -) -> () { +) -> Result<(), BridgeError> { let cookie = cookies.get("user-id").unwrap(); let user_id: EndUserId = serde_json::from_str(&urlencoding::decode(cookie.value()).unwrap()).unwrap(); info!("cookie: {cookie:?}"); info!("params: {params:?}"); - extension.authenticator.authenticate(user_id, params).await; - () + extension.authenticator.authenticate(user_id, params).await?; + Ok(()) } async fn login(cookies: Cookies, extension: ContextExtension) -> Redirect { @@ -95,3 +97,4 @@ async fn login(cookies: Cookies, extension: ContextExtension) -> Redirect { )); Redirect::temporary(auth_url.as_str()) } + diff --git a/webapp/src/bridge_engine.rs b/webapp/src/bridge_engine.rs index ab5afea..808045d 100644 --- a/webapp/src/bridge_engine.rs +++ b/webapp/src/bridge_engine.rs @@ -1,6 +1,6 @@ use crate::card::{Card, Deal, Suit}; use anyhow::{anyhow, bail}; -use log::{debug, error}; +use log::{debug, error, info}; use regex::Regex; use std::cmp::Ordering; use std::fmt; |