From 1d32d2666146bc60f9ebed56f094fbe4cb86edaa Mon Sep 17 00:00:00 2001 From: Kjetil Orbekk Date: Fri, 7 Oct 2022 07:30:20 -0400 Subject: Move rocket based server out of the way --- server-old/.env | 3 + server-old/Cargo.toml | 16 +++++ server-old/Rocket.toml | 4 ++ server-old/src/main.rs | 168 +++++++++++++++++++++++++++++++++++++++++++++++++ server/.env | 3 - server/Cargo.toml | 16 ----- server/Rocket.toml | 4 -- server/src/main.rs | 168 ------------------------------------------------- 8 files changed, 191 insertions(+), 191 deletions(-) create mode 100644 server-old/.env create mode 100644 server-old/Cargo.toml create mode 100644 server-old/Rocket.toml create mode 100644 server-old/src/main.rs delete mode 100644 server/.env delete mode 100644 server/Cargo.toml delete mode 100644 server/Rocket.toml delete mode 100644 server/src/main.rs diff --git a/server-old/.env b/server-old/.env new file mode 100644 index 0000000..14d8663 --- /dev/null +++ b/server-old/.env @@ -0,0 +1,3 @@ +RUST_LOG=info +ROCKET_PROFILE=development +WEBAPP_PATH=../webapp/dist/ \ No newline at end of file diff --git a/server-old/Cargo.toml b/server-old/Cargo.toml new file mode 100644 index 0000000..7d7011b --- /dev/null +++ b/server-old/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "server" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = "1.0.65" +dotenv = "0.15.0" +log = "0.4.17" +env_logger = "0.8.4" +openidconnect = "2.3.2" +rocket = { version = "0.5.0-rc.2", features = ["secrets", "json"] } +jwt-simple = "0.11.0" +serde = { version = "1.0.145", features = ["derive"] } +chrono = { version = "0.4.22", features = ["serde"] } +serde_json = "1.0.85" diff --git a/server-old/Rocket.toml b/server-old/Rocket.toml new file mode 100644 index 0000000..c7c73fa --- /dev/null +++ b/server-old/Rocket.toml @@ -0,0 +1,4 @@ +[development] +address = "::" +port = 11120 +secret_key = "1SmYoERqnfzf8dhTNEMp1VYrK7UCYhCbijQtSaF5LOI=" diff --git a/server-old/src/main.rs b/server-old/src/main.rs new file mode 100644 index 0000000..411ea47 --- /dev/null +++ b/server-old/src/main.rs @@ -0,0 +1,168 @@ +#[macro_use] +extern crate rocket; + +use chrono::{DateTime, Utc}; +use rocket::fs::FileServer; +use rocket::http::uri::Reference; +use rocket::http::{Cookie, CookieJar, Status}; +use rocket::outcome::Outcome; +use rocket::request::{self, FromRequest}; +use rocket::response::{Redirect, content}; +use rocket::Request; +use serde::{Deserialize, Serialize}; +use std::result::Result; +use rocket::serde::json::Json; + +use openidconnect::core::{ + CoreAuthenticationFlow, CoreClient, CoreProviderMetadata, CoreResponseType, CoreUserInfoClaims, +}; +use openidconnect::reqwest::async_http_client; +use openidconnect::url::Url; +use openidconnect::{ + AccessTokenHash, AuthenticationFlow, AuthorizationCode, ClientId, ClientSecret, CsrfToken, + IssuerUrl, Nonce, OAuth2TokenResponse, PkceCodeChallenge, RedirectUrl, Scope, TokenResponse, +}; + +const USER_COOKIE: &'static str = "user"; + +#[derive(Serialize, Deserialize)] +struct UserCookie { + access_token: openidconnect::AccessToken, + expiration: DateTime, + refresh_token: openidconnect::RefreshToken, +} + +struct User {} + +impl User { + async fn from_request_helper(req: &Request<'_>) -> Result { + let cookie = req + .cookies() + .get_private(USER_COOKIE) + .ok_or(anyhow::anyhow!("no cookie"))?; + let user_cookie: UserCookie = serde_json::from_str(cookie.value())?; + let client = keycloak_client().await; + + let token = client + .exchange_refresh_token(&user_cookie.refresh_token) + .request_async(async_http_client) + .await?; + let user_info: CoreUserInfoClaims = client + .user_info(token.access_token().clone(), None)? + .request_async(async_http_client) + .await?; + log::info!("Got user_info: {:?}", user_info); + Ok(User {}) + } +} + +#[rocket::async_trait] +impl<'r> FromRequest<'r> for User { + type Error = anyhow::Error; + + async fn from_request(req: &'r Request<'_>) -> request::Outcome { + match User::from_request_helper(req).await { + Ok(user) => Outcome::Success(User {}), + Err(error) => Outcome::Failure((Status::Forbidden, error)), + } + } +} + +#[get("/")] +fn index(user: Option) -> String { + match user { + None => "Not logged in".to_string(), + Some(user) => "Logged in".to_string(), + } +} + +#[get("/test")] +fn test() -> Json { + Json(String::from("test")) +} + +async fn keycloak_client() -> CoreClient { + // // Use OpenID Connect Discovery to fetch the provider metadata. + let provider_metadata = CoreProviderMetadata::discover_async( + IssuerUrl::new("https://auth.orbekk.com/realms/test".to_string()).unwrap(), + async_http_client, + ) + .await + .unwrap(); + + let client = CoreClient::from_provider_metadata( + provider_metadata, + ClientId::new("test-client".to_string()), + Some(ClientSecret::new( + "EbIMIpGnYPrG1GBl6eZtVM5zIhiuu5p1".to_string(), + )), + ) + // Set the URL the user will be redirected to after the authorization process. + .set_redirect_uri( + RedirectUrl::new("https://bridge.orbekk.com/keycloak-callback".to_string()).unwrap(), + ); + + client +} + +#[get("/login")] +async fn login() -> Redirect { + let (auth_url, csrf_token, nonce) = keycloak_client() + .await + .authorize_url( + AuthenticationFlow::::AuthorizationCode, + CsrfToken::new_random, + Nonce::new_random, + ) + .add_scope(Scope::new("email".to_string())) + .add_scope(Scope::new("profile".to_string())) + .url(); + + log::info!("{:?}", auth_url); + Redirect::to(Reference::parse_owned(auth_url.into()).unwrap()) +} + +#[get("/keycloak-callback?&")] +async fn keycloak_callback(jar: &CookieJar<'_>, code: &str, state: &str) -> Redirect { + // TODO: Validate state + let request_time = Utc::now(); + let token = keycloak_client() + .await + .exchange_code(AuthorizationCode::new(code.to_string())) + .request_async(async_http_client) + .await + .unwrap(); + + log::info!("token: {:?}", token); + log::info!("access token {:?}", token.access_token().secret()); + log::info!( + "refresh token {:?}", + token.refresh_token().unwrap().secret() + ); + let expiration = + request_time + chrono::Duration::from_std(token.expires_in().unwrap()).unwrap(); + jar.add_private(Cookie::new( + USER_COOKIE, + serde_json::to_string(&UserCookie { + access_token: token.access_token().clone(), + expiration, + refresh_token: token.refresh_token().unwrap().clone(), + }) + .unwrap(), + )); + + Redirect::to(uri!(index)) +} + +#[rocket::main] +async fn main() -> Result<(), anyhow::Error> { + dotenv::dotenv().ok(); + env_logger::init(); + + rocket::build() + .mount("/api", routes![index, test, login, keycloak_callback]) + .mount("/", FileServer::from(std::env::var("WEBAPP_PATH").unwrap())) + .launch() + .await?; + Ok(()) +} diff --git a/server/.env b/server/.env deleted file mode 100644 index 14d8663..0000000 --- a/server/.env +++ /dev/null @@ -1,3 +0,0 @@ -RUST_LOG=info -ROCKET_PROFILE=development -WEBAPP_PATH=../webapp/dist/ \ No newline at end of file diff --git a/server/Cargo.toml b/server/Cargo.toml deleted file mode 100644 index 7d7011b..0000000 --- a/server/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "server" -version = "0.1.0" -edition = "2021" - -[dependencies] -anyhow = "1.0.65" -dotenv = "0.15.0" -log = "0.4.17" -env_logger = "0.8.4" -openidconnect = "2.3.2" -rocket = { version = "0.5.0-rc.2", features = ["secrets", "json"] } -jwt-simple = "0.11.0" -serde = { version = "1.0.145", features = ["derive"] } -chrono = { version = "0.4.22", features = ["serde"] } -serde_json = "1.0.85" diff --git a/server/Rocket.toml b/server/Rocket.toml deleted file mode 100644 index c7c73fa..0000000 --- a/server/Rocket.toml +++ /dev/null @@ -1,4 +0,0 @@ -[development] -address = "::" -port = 11120 -secret_key = "1SmYoERqnfzf8dhTNEMp1VYrK7UCYhCbijQtSaF5LOI=" diff --git a/server/src/main.rs b/server/src/main.rs deleted file mode 100644 index 411ea47..0000000 --- a/server/src/main.rs +++ /dev/null @@ -1,168 +0,0 @@ -#[macro_use] -extern crate rocket; - -use chrono::{DateTime, Utc}; -use rocket::fs::FileServer; -use rocket::http::uri::Reference; -use rocket::http::{Cookie, CookieJar, Status}; -use rocket::outcome::Outcome; -use rocket::request::{self, FromRequest}; -use rocket::response::{Redirect, content}; -use rocket::Request; -use serde::{Deserialize, Serialize}; -use std::result::Result; -use rocket::serde::json::Json; - -use openidconnect::core::{ - CoreAuthenticationFlow, CoreClient, CoreProviderMetadata, CoreResponseType, CoreUserInfoClaims, -}; -use openidconnect::reqwest::async_http_client; -use openidconnect::url::Url; -use openidconnect::{ - AccessTokenHash, AuthenticationFlow, AuthorizationCode, ClientId, ClientSecret, CsrfToken, - IssuerUrl, Nonce, OAuth2TokenResponse, PkceCodeChallenge, RedirectUrl, Scope, TokenResponse, -}; - -const USER_COOKIE: &'static str = "user"; - -#[derive(Serialize, Deserialize)] -struct UserCookie { - access_token: openidconnect::AccessToken, - expiration: DateTime, - refresh_token: openidconnect::RefreshToken, -} - -struct User {} - -impl User { - async fn from_request_helper(req: &Request<'_>) -> Result { - let cookie = req - .cookies() - .get_private(USER_COOKIE) - .ok_or(anyhow::anyhow!("no cookie"))?; - let user_cookie: UserCookie = serde_json::from_str(cookie.value())?; - let client = keycloak_client().await; - - let token = client - .exchange_refresh_token(&user_cookie.refresh_token) - .request_async(async_http_client) - .await?; - let user_info: CoreUserInfoClaims = client - .user_info(token.access_token().clone(), None)? - .request_async(async_http_client) - .await?; - log::info!("Got user_info: {:?}", user_info); - Ok(User {}) - } -} - -#[rocket::async_trait] -impl<'r> FromRequest<'r> for User { - type Error = anyhow::Error; - - async fn from_request(req: &'r Request<'_>) -> request::Outcome { - match User::from_request_helper(req).await { - Ok(user) => Outcome::Success(User {}), - Err(error) => Outcome::Failure((Status::Forbidden, error)), - } - } -} - -#[get("/")] -fn index(user: Option) -> String { - match user { - None => "Not logged in".to_string(), - Some(user) => "Logged in".to_string(), - } -} - -#[get("/test")] -fn test() -> Json { - Json(String::from("test")) -} - -async fn keycloak_client() -> CoreClient { - // // Use OpenID Connect Discovery to fetch the provider metadata. - let provider_metadata = CoreProviderMetadata::discover_async( - IssuerUrl::new("https://auth.orbekk.com/realms/test".to_string()).unwrap(), - async_http_client, - ) - .await - .unwrap(); - - let client = CoreClient::from_provider_metadata( - provider_metadata, - ClientId::new("test-client".to_string()), - Some(ClientSecret::new( - "EbIMIpGnYPrG1GBl6eZtVM5zIhiuu5p1".to_string(), - )), - ) - // Set the URL the user will be redirected to after the authorization process. - .set_redirect_uri( - RedirectUrl::new("https://bridge.orbekk.com/keycloak-callback".to_string()).unwrap(), - ); - - client -} - -#[get("/login")] -async fn login() -> Redirect { - let (auth_url, csrf_token, nonce) = keycloak_client() - .await - .authorize_url( - AuthenticationFlow::::AuthorizationCode, - CsrfToken::new_random, - Nonce::new_random, - ) - .add_scope(Scope::new("email".to_string())) - .add_scope(Scope::new("profile".to_string())) - .url(); - - log::info!("{:?}", auth_url); - Redirect::to(Reference::parse_owned(auth_url.into()).unwrap()) -} - -#[get("/keycloak-callback?&")] -async fn keycloak_callback(jar: &CookieJar<'_>, code: &str, state: &str) -> Redirect { - // TODO: Validate state - let request_time = Utc::now(); - let token = keycloak_client() - .await - .exchange_code(AuthorizationCode::new(code.to_string())) - .request_async(async_http_client) - .await - .unwrap(); - - log::info!("token: {:?}", token); - log::info!("access token {:?}", token.access_token().secret()); - log::info!( - "refresh token {:?}", - token.refresh_token().unwrap().secret() - ); - let expiration = - request_time + chrono::Duration::from_std(token.expires_in().unwrap()).unwrap(); - jar.add_private(Cookie::new( - USER_COOKIE, - serde_json::to_string(&UserCookie { - access_token: token.access_token().clone(), - expiration, - refresh_token: token.refresh_token().unwrap().clone(), - }) - .unwrap(), - )); - - Redirect::to(uri!(index)) -} - -#[rocket::main] -async fn main() -> Result<(), anyhow::Error> { - dotenv::dotenv().ok(); - env_logger::init(); - - rocket::build() - .mount("/api", routes![index, test, login, keycloak_callback]) - .mount("/", FileServer::from(std::env::var("WEBAPP_PATH").unwrap())) - .launch() - .await?; - Ok(()) -} -- cgit v1.2.3