From 91f2b5c32f6274f35d226584bd779c233ca583fb Mon Sep 17 00:00:00 2001 From: magnalite Date: Tue, 15 Oct 2024 19:57:29 +0100 Subject: [PATCH] Extra validation of oauth tokens --- Cargo.lock | 79 +++++++++++++------ Cargo.toml | 1 + README.md | 2 +- wally-registry-backend/Cargo.toml | 1 + wally-registry-backend/Dockerfile | 2 +- wally-registry-backend/src/auth.rs | 77 +++++++++++++++--- wally-registry-frontend/src/pages/Install.mdx | 2 +- 7 files changed, 124 insertions(+), 40 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 53a0137f..00167eab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -89,7 +89,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.43", ] [[package]] @@ -435,7 +435,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7efb37c3e1ccb1ff97164ad95ac1606e8ccd35b3fa0a7d99a304c7f4a428cc24" dependencies = [ "percent-encoding", - "time 0.3.21", + "time 0.3.35", "version_check", ] @@ -640,6 +640,15 @@ dependencies = [ "parking_lot_core 0.9.7", ] +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + [[package]] name = "devise" version = "0.4.1" @@ -670,7 +679,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.18", + "syn 2.0.43", ] [[package]] @@ -1730,6 +1739,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-integer" version = "0.1.44" @@ -1909,7 +1924,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.18", + "syn 2.0.43", ] [[package]] @@ -1962,6 +1977,12 @@ version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "767eb9f07d4a5ebcb39bbf2d452058a93c011373abf6832e24194a1c3f004794" +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.10" @@ -2006,9 +2027,9 @@ checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" [[package]] name = "proc-macro2" -version = "1.0.59" +version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b" +checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" dependencies = [ "unicode-ident", ] @@ -2021,7 +2042,7 @@ checksum = "606c4ba35817e2922a308af55ad51bab3645b59eae5c570d4a6cf07e36bd493b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.43", "version_check", "yansi", ] @@ -2363,7 +2384,7 @@ dependencies = [ "serde_json", "state", "tempfile", - "time 0.3.21", + "time 0.3.35", "tokio", "tokio-stream", "tokio-util", @@ -2383,7 +2404,7 @@ dependencies = [ "proc-macro2", "quote", "rocket_http", - "syn 2.0.18", + "syn 2.0.43", "unicode-xid", ] @@ -2408,7 +2429,7 @@ dependencies = [ "smallvec", "stable-pattern", "state", - "time 0.3.21", + "time 0.3.35", "tokio", "uncased", ] @@ -2696,22 +2717,22 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.163" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.163" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.43", ] [[package]] @@ -2804,7 +2825,7 @@ checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.43", ] [[package]] @@ -3016,9 +3037,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.18" +version = "2.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" +checksum = "ee659fb5f3d355364e1f3e5bc10fb82068efbf824a1e9d1c9504244a6469ad53" dependencies = [ "proc-macro2", "quote", @@ -3199,11 +3220,14 @@ dependencies = [ [[package]] name = "time" -version = "0.3.21" +version = "0.3.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3403384eaacbca9923fa06940178ac13e4edb725486d70e8e15881d0c836cc" +checksum = "ef89ece63debf11bc32d1ed8d078ac870cbeb44da02afb02a9ff135ae7ca0582" dependencies = [ + "deranged", "itoa 1.0.6", + "num-conv", + "powerfmt", "serde", "time-core", "time-macros", @@ -3211,16 +3235,17 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.9" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ + "num-conv", "time-core", ] @@ -3265,7 +3290,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.43", ] [[package]] @@ -3393,7 +3418,7 @@ checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.43", ] [[package]] @@ -3666,6 +3691,7 @@ dependencies = [ "serial_test", "structopt", "tempfile", + "time 0.3.35", "tokio", "toml 0.5.8", "toml_edit 0.2.0", @@ -3699,6 +3725,7 @@ dependencies = [ "serde_json", "tantivy", "tempfile", + "time 0.3.35", "tokio", "url", "walkdir", diff --git a/Cargo.toml b/Cargo.toml index b21b007a..f46275ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,6 +58,7 @@ ubyte = "0.10.3" indicatif = "0.17.4" tokio = "1.28.2" serial_test = "2.0.0" +time = "=0.3.35" [dev-dependencies] insta = { version = "1.1.0" } diff --git a/README.md b/README.md index 312d5545..b2af9628 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ Pre-built binaries are available for Windows, macOS, and Linux from the [GitHub [releases]: https://github.com/UpliftGames/wally/releases ### From Source -It's straightforward to compile Wally from source. Wally requires Rust 1.51.0 or newer. +It's straightforward to compile Wally from source. Wally requires Rust 1.80.0 or newer. Clone the repository and use: diff --git a/wally-registry-backend/Cargo.toml b/wally-registry-backend/Cargo.toml index 1c044d1a..4a11ae82 100644 --- a/wally-registry-backend/Cargo.toml +++ b/wally-registry-backend/Cargo.toml @@ -37,6 +37,7 @@ url = { version = "2.2.1", features = ["serde"] } walkdir = "2.3.1" zip = "0.5.11" moka = "0.11.1" +time = "=0.3.35" [dev-dependencies] tempfile = "3.1.0" diff --git a/wally-registry-backend/Dockerfile b/wally-registry-backend/Dockerfile index 6678fd93..1cd5cb93 100644 --- a/wally-registry-backend/Dockerfile +++ b/wally-registry-backend/Dockerfile @@ -1,4 +1,4 @@ -FROM rust:1-slim-buster AS build +FROM rust:1.81-slim-bookworm AS build WORKDIR /usr/app # Debian Slim doesn't install certificates by default, but we kinda want those. diff --git a/wally-registry-backend/src/auth.rs b/wally-registry-backend/src/auth.rs index dcc3dfd1..02055792 100644 --- a/wally-registry-backend/src/auth.rs +++ b/wally-registry-backend/src/auth.rs @@ -1,4 +1,4 @@ -use std::fmt; +use std::{collections::HashMap, fmt}; use anyhow::format_err; use constant_time_eq::constant_time_eq; @@ -18,8 +18,14 @@ use crate::{config::Config, error::ApiErrorStatus}; #[serde(tag = "type", content = "value", rename_all = "kebab-case")] pub enum AuthMode { ApiKey(String), - DoubleApiKey { read: Option, write: String }, - GithubOAuth, + DoubleApiKey { + read: Option, + write: String, + }, + GithubOAuth { + client_id: String, + client_secret: String, + }, Unauthenticated, } @@ -39,12 +45,25 @@ impl GithubInfo { } } +#[derive(Deserialize)] +#[allow(unused)] // Variables are (currently) not accessed but ensure they are present during json parsing +struct ValidatedGithubApp { + client_id: String, +} + +#[derive(Deserialize)] +#[allow(unused)] // Variables are (currently) not accessed but ensure they are present during json parsing +struct ValidatedGithubInfo { + id: u64, + app: ValidatedGithubApp, +} + impl fmt::Debug for AuthMode { fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { match self { AuthMode::ApiKey(_) => write!(formatter, "API key"), AuthMode::DoubleApiKey { .. } => write!(formatter, "double API key"), - AuthMode::GithubOAuth => write!(formatter, "Github OAuth"), + AuthMode::GithubOAuth { .. } => write!(formatter, "Github OAuth"), AuthMode::Unauthenticated => write!(formatter, "no authentication"), } } @@ -69,7 +88,11 @@ fn match_api_key(request: &Request<'_>, key: &str, result: T) -> Outcome) -> Outcome { +async fn verify_github_token( + request: &Request<'_>, + client_id: &str, + client_secret: &str, +) -> Outcome { let token: String = match request.headers().get_one("authorization") { Some(key) if key.starts_with("Bearer ") => (key[6..].trim()).to_owned(), _ => { @@ -84,7 +107,7 @@ async fn verify_github_token(request: &Request<'_>) -> Outcome) -> Outcome { return format_err!(err).status(Status::InternalServerError).into(); } - Ok(response) => response.json::().await, + Ok(response) => match response.json::().await { + Err(err) => { + return format_err!("Github auth failed: {}", err) + .status(Status::Unauthorized) + .into(); + } + Ok(github_info) => github_info, + }, + }; + + let mut body = HashMap::new(); + body.insert("access_token", &token); + + let response = client + .post(format!( + "https://api.github.com/applications/{}/token", + client_id + )) + .header("accept", "application/json") + .header("user-agent", "wally") + .basic_auth(client_id, Some(client_secret)) + .json(&body) + .send() + .await; + + let validated_github_info = match response { + Err(err) => { + return format_err!(err).status(Status::InternalServerError).into(); + } + Ok(response) => response.json::().await, }; - match github_info { + match validated_github_info { Err(err) => format_err!("Github auth failed: {}", err) .status(Status::Unauthorized) .into(), - Ok(github_info) => Outcome::Success(WriteAccess::Github(github_info)), + Ok(_) => Outcome::Success(WriteAccess::Github(github_info)), } } @@ -120,7 +172,7 @@ impl<'r> FromRequest<'r> for ReadAccess { match &config.auth { AuthMode::Unauthenticated => Outcome::Success(ReadAccess::Public), - AuthMode::GithubOAuth => Outcome::Success(ReadAccess::Public), + AuthMode::GithubOAuth { .. } => Outcome::Success(ReadAccess::Public), AuthMode::ApiKey(key) => match_api_key(request, key, ReadAccess::ApiKey), AuthMode::DoubleApiKey { read, .. } => match read { None => Outcome::Success(ReadAccess::Public), @@ -179,7 +231,10 @@ impl<'r> FromRequest<'r> for WriteAccess { AuthMode::DoubleApiKey { write, .. } => { match_api_key(request, write, WriteAccess::ApiKey) } - AuthMode::GithubOAuth => verify_github_token(request).await, + AuthMode::GithubOAuth { + client_id, + client_secret, + } => verify_github_token(request, client_id, client_secret).await, } } } diff --git a/wally-registry-frontend/src/pages/Install.mdx b/wally-registry-frontend/src/pages/Install.mdx index 67cbcfa0..953f85a8 100644 --- a/wally-registry-frontend/src/pages/Install.mdx +++ b/wally-registry-frontend/src/pages/Install.mdx @@ -32,7 +32,7 @@ Pre-built binaries are available for Windows, macOS, and Linux from the [GitHub [releases]: https://github.com/UpliftGames/wally/releases ## From Source -It's straightforward to compile Wally from source. Wally requires Rust 1.51.0 or newer. +It's straightforward to compile Wally from source. Wally requires Rust 1.80.0 or newer. Clone the repository and use: