From fdafa6d17ea884cc75a0d616899e1db84ae4ba0d Mon Sep 17 00:00:00 2001 From: Kjetil Orbekk Date: Sun, 2 Oct 2022 11:11:36 -0400 Subject: Rocket setup --- server/Cargo.toml | 6 +- server/src/main.rs | 160 ++++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 134 insertions(+), 32 deletions(-) (limited to 'server') diff --git a/server/Cargo.toml b/server/Cargo.toml index 7c149ca..7d7011b 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -9,4 +9,8 @@ dotenv = "0.15.0" log = "0.4.17" env_logger = "0.8.4" openidconnect = "2.3.2" -rocket = "0.5.0-rc.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/src/main.rs b/server/src/main.rs index f751c4d..aa615ea 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -1,53 +1,151 @@ -#[macro_use] extern crate rocket; +#[macro_use] +extern crate rocket; -#[get("/")] -fn index() -> &'static str { - "Hello, World!" +use std::result::Result; +use chrono::{DateTime, Utc}; +use rocket::http::uri::Reference; +use rocket::http::{Cookie, CookieJar,Status}; +use rocket::response::Redirect; +use rocket::request::{self, FromRequest}; +use rocket::Request; +use serde::{Deserialize, Serialize}; +use rocket::outcome::Outcome; + +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, } -#[rocket::main] -async fn main() -> Result<(), anyhow::Error> { - dotenv::dotenv().ok(); - env_logger::init(); - log::debug!("hello"); +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); + Ok(User {}) + } +} - use openidconnect::core::{ - CoreAuthenticationFlow, CoreClient, CoreProviderMetadata, CoreResponseType, - CoreUserInfoClaims, - }; - use openidconnect::{ - AccessTokenHash, AuthenticationFlow, AuthorizationCode, ClientId, ClientSecret, CsrfToken, - IssuerUrl, Nonce, PkceCodeChallenge, RedirectUrl, Scope, - }; +#[rocket::async_trait] +impl<'r> FromRequest<'r> for User { + type Error = anyhow::Error; - use openidconnect::reqwest::async_http_client; - use openidconnect::url::Url; + 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(), + } +} +async fn keycloak_client() -> CoreClient { // // Use OpenID Connect Discovery to fetch the provider metadata. - use openidconnect::{OAuth2TokenResponse, TokenResponse}; let provider_metadata = CoreProviderMetadata::discover_async( - IssuerUrl::new("https://auth.orbekk.com/realms/test".to_string())?, + IssuerUrl::new("https://auth.orbekk.com/realms/test".to_string()).unwrap(), async_http_client, - ).await?; + ) + .await + .unwrap(); - let client = - CoreClient::from_provider_metadata( + let client = CoreClient::from_provider_metadata( provider_metadata, ClientId::new("test-client".to_string()), - Some(ClientSecret::new("EbIMIpGnYPrG1GBl6eZtVM5zIhiuu5p1".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())?); + .set_redirect_uri( + RedirectUrl::new("https://bridge.orbekk.com/keycloak-callback".to_string()).unwrap(), + ); + + client +} - let (auth_url, csrf_token, nonce) = client - .authorize_url(AuthenticationFlow::::AuthorizationCode, - CsrfToken::new_random, - Nonce::new_random) +#[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(); + log::debug!("hello"); - rocket::build().mount("/", routes![index]).launch().await; + rocket::build() + .mount("/", routes![index, login, keycloak_callback]) + .launch() + .await; Ok(()) } -- cgit v1.2.3