summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKjetil Orbekk <kj@orbekk.com>2022-10-08 10:30:15 -0400
committerKjetil Orbekk <kj@orbekk.com>2022-10-08 10:30:15 -0400
commit1cbf881835fc33859a31645f886c5d3787ed48f8 (patch)
treeeb7a8ac803e33283ea0efffa015c8bd96ca40c29
parentb727db0d64f4250742b0ebaac0149c1224a0d040 (diff)
Add access token validation
-rw-r--r--Cargo.lock54
-rw-r--r--server/Cargo.toml6
-rw-r--r--server/src/auth.rs60
-rw-r--r--server/src/error.rs28
-rw-r--r--server/src/main.rs13
-rw-r--r--webapp/src/bridge_engine.rs2
6 files changed, 142 insertions, 21 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 88fcba6..823c09f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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;