diff --git a/.gitignore b/.gitignore index 17c0b07..44e2dee 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ .idea/ .vscode/ result +/config +/database \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 0ffee9a..bfc69ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -55,6 +55,21 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anstream" version = "0.6.4" @@ -164,7 +179,7 @@ dependencies = [ "fuzzy-matcher", "indicatif", "interim", - "itertools", + "itertools 0.11.0", "log", "ratatui", "rpassword", @@ -199,7 +214,7 @@ dependencies = [ "generic-array", "hex", "interim", - "itertools", + "itertools 0.11.0", "lazy_static", "log", "memchr", @@ -256,6 +271,7 @@ dependencies = [ "eyre", "fs-err", "http", + "openidconnect", "rand 0.8.5", "reqwest", "semver", @@ -367,6 +383,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base64" version = "0.13.1" @@ -517,6 +539,21 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "chrono" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-targets 0.48.5", +] + [[package]] name = "cipher" version = "0.3.0" @@ -746,6 +783,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "crypto-bigint" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28f85c3514d2a6e64160359b45a3918c3b4178bcbf4ae5d03ab2d02e521c479a" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -823,6 +872,41 @@ dependencies = [ "syn 2.0.38", ] +[[package]] +name = "darling" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.38", +] + +[[package]] +name = "darling_macro" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.38", +] + [[package]] name = "der" version = "0.7.8" @@ -934,6 +1018,26 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" +[[package]] +name = "dyn-clone" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d" + +[[package]] +name = "ecdsa" +version = "0.16.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4b1e0c257a9e9f25f90ff76d7a68360ed497ee519c8e428d1825ef0000799d4" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "signature 2.1.0", + "spki", +] + [[package]] name = "ed25519" version = "1.5.3" @@ -989,6 +1093,27 @@ dependencies = [ "serde", ] +[[package]] +name = "elliptic-curve" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d97ca172ae9dc9f9b779a6e3a65d308f2af74e5b8c921299075bdb4a0370e914" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "hkdf", + "pem-rfc7468", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "encode_unicode" version = "0.3.6" @@ -1076,6 +1201,16 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "fiat-crypto" version = "0.2.1" @@ -1286,8 +1421,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -1296,6 +1433,17 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "h2" version = "0.3.21" @@ -1472,6 +1620,35 @@ dependencies = [ "tokio-rustls", ] +[[package]] +name = "iana-time-zone" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "0.4.0" @@ -1496,6 +1673,7 @@ checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown 0.12.3", + "serde", ] [[package]] @@ -1506,6 +1684,7 @@ checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" dependencies = [ "equivalent", "hashbrown 0.14.2", + "serde", ] [[package]] @@ -1581,6 +1760,15 @@ dependencies = [ "nom", ] +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.11.0" @@ -1940,6 +2128,26 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" +[[package]] +name = "oauth2" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c38841cdd844847e3e7c8d29cef9dcfed8877f8f56f9071f77843ecf3baf937f" +dependencies = [ + "base64 0.13.1", + "chrono", + "getrandom 0.2.10", + "http", + "rand 0.8.5", + "reqwest", + "serde", + "serde_json", + "serde_path_to_error", + "sha2 0.10.8", + "thiserror", + "url", +] + [[package]] name = "objc" version = "0.2.7" @@ -1990,6 +2198,38 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "openidconnect" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62d6050f6a84b81f23c569f5607ad883293e57491036e318fafe6fc4895fadb1" +dependencies = [ + "base64 0.13.1", + "chrono", + "dyn-clone", + "ed25519-dalek 2.0.0", + "hmac", + "http", + "itertools 0.10.5", + "log", + "oauth2", + "p256", + "p384", + "rand 0.8.5", + "rsa", + "serde", + "serde-value", + "serde_derive", + "serde_json", + "serde_path_to_error", + "serde_plain", + "serde_with", + "sha2 0.10.8", + "subtle", + "thiserror", + "url", +] + [[package]] name = "openssl-probe" version = "0.1.5" @@ -2002,6 +2242,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", +] + [[package]] name = "os_pipe" version = "1.1.4" @@ -2018,6 +2267,30 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2 0.10.8", +] + +[[package]] +name = "p384" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2 0.10.8", +] + [[package]] name = "parking_lot" version = "0.12.1" @@ -2207,6 +2480,15 @@ dependencies = [ "yansi", ] +[[package]] +name = "primeorder" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7dbe9ed3b56368bd99483eb32fe9c17fdd3730aebadc906918ce78d54c7eeb4" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro2" version = "1.0.69" @@ -2306,7 +2588,7 @@ dependencies = [ "cassowary", "crossterm", "indoc", - "itertools", + "itertools 0.11.0", "lru", "paste", "strum", @@ -2433,9 +2715,20 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", + "webpki-roots 0.25.2", "winreg", ] +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "ring" version = "0.16.20" @@ -2692,6 +2985,20 @@ dependencies = [ "untrusted 0.9.0", ] +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + [[package]] name = "security-framework" version = "2.9.2" @@ -2730,6 +3037,16 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float", + "serde", +] + [[package]] name = "serde_derive" version = "1.0.171" @@ -2762,6 +3079,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_plain" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1fc6db65a611022b23a0dec6975d63fb80a302cb3388835ff02c097258d50" +dependencies = [ + "serde", +] + [[package]] name = "serde_regex" version = "1.1.0" @@ -2784,6 +3110,35 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64cd236ccc1b7a29e7e2739f27c0b2dd199804abc4290e32f59f3b68d6405c23" +dependencies = [ + "base64 0.21.5", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.0.2", + "serde", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93634eb5f75a2323b16de4748022ac4297f9e76b6dced2be287a099f41b5e788" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.38", +] + [[package]] name = "sha1" version = "0.10.6" @@ -2959,7 +3314,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b7b278788e7be4d0d29c0f39497a0eef3fba6bbc8e70d8bf7fde46edeaa9e85" dependencies = [ - "itertools", + "itertools 0.11.0", "nom", "unicode_categories", ] @@ -3019,7 +3374,7 @@ dependencies = [ "tracing", "url", "uuid", - "webpki-roots", + "webpki-roots 0.24.0", ] [[package]] @@ -3695,6 +4050,7 @@ dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", ] [[package]] @@ -3902,6 +4258,12 @@ dependencies = [ "rustls-webpki", ] +[[package]] +name = "webpki-roots" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" + [[package]] name = "whoami" version = "1.4.1" @@ -3952,6 +4314,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-sys" version = "0.45.0" diff --git a/atuin-server/Cargo.toml b/atuin-server/Cargo.toml index 95bd3e0..45d6df0 100644 --- a/atuin-server/Cargo.toml +++ b/atuin-server/Cargo.toml @@ -33,3 +33,4 @@ tower-http = { version = "0.4", features = ["trace"] } reqwest = { workspace = true } argon2 = "0.5.0" semver = { workspace = true } +openidconnect = "3.4.0" diff --git a/atuin-server/src/handlers/mod.rs b/atuin-server/src/handlers/mod.rs index 18b1af8..3009676 100644 --- a/atuin-server/src/handlers/mod.rs +++ b/atuin-server/src/handlers/mod.rs @@ -8,6 +8,7 @@ pub mod history; pub mod record; pub mod status; pub mod user; +pub mod oidc; const VERSION: &str = env!("CARGO_PKG_VERSION"); diff --git a/atuin-server/src/handlers/oidc.rs b/atuin-server/src/handlers/oidc.rs new file mode 100644 index 0000000..5caa1e0 --- /dev/null +++ b/atuin-server/src/handlers/oidc.rs @@ -0,0 +1,214 @@ +use axum::{ + extract::{State, Query}, + response::Redirect, + Json, +}; +use http::StatusCode; +use openidconnect::{ + core::{CoreAuthenticationFlow, CoreClient, CoreProviderMetadata}, + reqwest::async_http_client, + AuthorizationCode, ClientId, ClientSecret, CsrfToken, IssuerUrl, Nonce, RedirectUrl, Scope, + TokenResponse, +}; +use serde::{Serialize, Deserialize}; +use tracing::error; +use uuid::Uuid; + +use super::{ErrorResponse, ErrorResponseStatus, RespExt}; +use crate::router::AppState; +use atuin_server_database::{Database, DbError, models::{NewUser, NewSession}}; + +#[derive(Debug, Serialize, Deserialize)] +pub struct OIDCGetResponse { + pub login_url: String +} + +pub async fn get(state: State>) -> Result, ErrorResponseStatus<'static>> { + if state.settings.oidc_authority.is_none() + || state.settings.oidc_client_id.is_none() + || state.settings.oidc_client_secret.is_none() + { + return Err(ErrorResponse::reply("OIDC not supported on this server").with_status(StatusCode::BAD_REQUEST)); + } + + let url = state + .settings + .public_url + .strip_suffix("/") + .unwrap_or(&state.settings.public_url) + .to_string() + + "/oidc/login"; + + Ok(Json(OIDCGetResponse { + // This is dynamic to allow for backwards compatibility later + // it also is relative to the client's configured server URL + login_url: url, + })) +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct LoginQuery { + pub callback: String, +} + +pub async fn login( + state: State>, + params: Query, +) -> Result> { + if state.settings.oidc_authority.is_none() + || state.settings.oidc_client_id.is_none() + || state.settings.oidc_client_secret.is_none() + { + return Err(ErrorResponse::reply("OIDC not supported on this server").with_status(StatusCode::BAD_REQUEST)); + } + + let provider_metadata = CoreProviderMetadata::discover_async( + IssuerUrl::new(state.settings.oidc_authority.clone().unwrap()).or(Err(ErrorResponse::reply("unable to parse configured oidc authority").with_status(StatusCode::INTERNAL_SERVER_ERROR)))?, + async_http_client, + ) + .await + .or(Err(ErrorResponse::reply("unable to fetch oidc provider metadata").with_status(StatusCode::INTERNAL_SERVER_ERROR)))?; + + let client = CoreClient::from_provider_metadata( + provider_metadata, + ClientId::new(state.settings.oidc_client_id.clone().unwrap()), + Some(ClientSecret::new( + state.settings.oidc_client_secret.clone().unwrap(), + )), + ) + // Set the URL the user will be redirected to after the authorization process. + .set_redirect_uri( + RedirectUrl::new( + state + .settings + .public_url + .strip_suffix("/") + .unwrap_or(&state.settings.public_url) + .to_string() + + "/oidc/callback", + ) + .or(Err(ErrorResponse::reply("unable to parse constructed redirect uri").with_status(StatusCode::INTERNAL_SERVER_ERROR)))?, + ); + + let url = client + .authorize_url( + CoreAuthenticationFlow::AuthorizationCode, + || CsrfToken::new(params.0.callback), + Nonce::new_random, + ) + .add_scopes(vec![Scope::new("profile".to_string()), Scope::new("email".to_string())]) + .url(); + + Ok(Redirect::to(url.0.as_str())) +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct CallbackQuery { + pub code: String, + pub state: String +} + +pub async fn callback( + state: State>, + params: Query, +) -> Result, ErrorResponseStatus<'static>> { + if state.settings.oidc_authority.is_none() + || state.settings.oidc_client_id.is_none() + || state.settings.oidc_client_secret.is_none() + { + return Err(ErrorResponse::reply("OIDC not supported on this server").with_status(StatusCode::BAD_REQUEST)); + } + + let provider_metadata = CoreProviderMetadata::discover_async( + IssuerUrl::new(state.settings.oidc_authority.clone().unwrap()).or(Err(ErrorResponse::reply("unable to parse configured oidc authority").with_status(StatusCode::INTERNAL_SERVER_ERROR)))?, + async_http_client, + ) + .await + .or(Err(ErrorResponse::reply("unable to fetch oidc provider metadata").with_status(StatusCode::INTERNAL_SERVER_ERROR)))?; + + let client = CoreClient::from_provider_metadata( + provider_metadata, + ClientId::new(state.settings.oidc_client_id.clone().unwrap()), + Some(ClientSecret::new( + state.settings.oidc_client_secret.clone().unwrap(), + )), + ).set_redirect_uri( + RedirectUrl::new( + state + .settings + .public_url + .strip_suffix("/") + .unwrap_or(&state.settings.public_url) + .to_string() + + "/oidc/callback", + ) + .or(Err(ErrorResponse::reply("unable to parse constructed redirect uri").with_status(StatusCode::INTERNAL_SERVER_ERROR)))?, + ); + + let token_response = client + .exchange_code(AuthorizationCode::new(params.0.code)) + .request_async(async_http_client) + .await + .or(Err(ErrorResponse::reply("unable to exchange oidc code for token").with_status(StatusCode::INTERNAL_SERVER_ERROR)))?; + + let id_token = token_response.id_token().unwrap(); + // TODO actual audience validation + let claims = dbg!(id_token + .claims(&client.id_token_verifier().require_audience_match(false), |_: Option<&Nonce>| Ok(()))) + .or(Err(ErrorResponse::reply("unable to validate oidc token").with_status(StatusCode::INTERNAL_SERVER_ERROR)))?; + + let db = &state.0.database; + let username = claims.preferred_username().ok_or(ErrorResponse::reply("oidc token did not contain preferred_username").with_status(StatusCode::INTERNAL_SERVER_ERROR))?; + let email = claims.email().ok_or(ErrorResponse::reply("oidc token did not contain email").with_status(StatusCode::INTERNAL_SERVER_ERROR))?; + // Fetches the existing user ID, or registers them on the spot. + let user_id = match db.get_user(username).await { + Ok(u) => u.id, + Err(DbError::NotFound) => { + let new_user = NewUser { + email: email.to_string(), + username: username.to_string(), + password: "".to_string(), + }; + + match db.add_user(&new_user).await { + Ok(id) => id, + Err(e) => { + error!("failed to add user via oidc: {}", e); + return Err( + ErrorResponse::reply("failed to add user via oidc").with_status(StatusCode::BAD_REQUEST) + ); + } + } + } + Err(DbError::Other(e)) => { + error!("failed to get user {}: {}", username.as_str(), e); + + return Err(ErrorResponse::reply("database error") + .with_status(StatusCode::INTERNAL_SERVER_ERROR)); + } + }; + + // Creates a new session for the user + let token = Uuid::new_v4().as_simple().to_string(); + let new_session = NewSession { + user_id, + token: (&token).into(), + }; + + match db.add_session(&new_session).await { + Ok(_) => (), + Err(e) => { + error!("failed to add session from oidc: {}", e); + return Err(ErrorResponse::reply("failed to register user via oidc") + .with_status(StatusCode::BAD_REQUEST)) + } + }; + + dbg!(claims); + dbg!(username); + dbg!(email); + + // TODO - redirect to localhost + client side aspect + clean stuff up + + Ok(Json("it works probably wtf".to_string())) +} diff --git a/atuin-server/src/router.rs b/atuin-server/src/router.rs index e1220e5..d86487d 100644 --- a/atuin-server/src/router.rs +++ b/atuin-server/src/router.rs @@ -111,6 +111,9 @@ pub fn router(database: DB, settings: Settings) -> R .route("/user/:username", get(handlers::user::get)) .route("/account", delete(handlers::user::delete)) .route("/register", post(handlers::user::register)) + .route("/oidc", get(handlers::oidc::get)) + .route("/oidc/login", get(handlers::oidc::login)) + .route("/oidc/callback", get(handlers::oidc::callback)) .route("/login", post(handlers::user::login)); let path = settings.path.as_str(); diff --git a/atuin-server/src/settings.rs b/atuin-server/src/settings.rs index 744f4ec..7cdcee8 100644 --- a/atuin-server/src/settings.rs +++ b/atuin-server/src/settings.rs @@ -18,6 +18,10 @@ pub struct Settings { pub page_size: i64, pub register_webhook_url: Option, pub register_webhook_username: String, + pub public_url: String, + pub oidc_authority: Option, + pub oidc_client_id: Option, + pub oidc_client_secret: Option, #[serde(flatten)] pub db_settings: DbSettings, diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..ebb7727 --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,37 @@ +version: '3.5' +services: + atuin: + restart: always + image: atuinlocal:latest + build: + context: . + dockerfile: Dockerfile + network: host + command: server start + volumes: + - "./config:/config" + links: + - postgresql:db + ports: + - 8888:8888 + environment: + ATUIN_HOST: "0.0.0.0" + ATUIN_OPEN_REGISTRATION: "true" + ATUIN_DB_URI: postgres://atuin:really-insecure@db/atuin + postgresql: + image: postgres:14 + restart: unless-stopped + volumes: # Don't remove permanent storage for index database files! + - "./database:/var/lib/postgresql/data/" + environment: + POSTGRES_USER: atuin + POSTGRES_PASSWORD: really-insecure + POSTGRES_DB: atuin + +networks: + default: + name: atuinnet + ipam: + driver: default + config: + - subnet: 172.20.0.0/16 \ No newline at end of file