From d0b9d3970de02fbb7d6fb498fe93b3a28827ad02 Mon Sep 17 00:00:00 2001 From: Alexander Korolev Date: Sun, 12 Jan 2025 00:26:43 +0100 Subject: [PATCH] Release v0.16.0 --- .github/workflows/release.yml | 2 +- Cargo.toml | 2 +- README.md | 319 +--------------------------------- release.sh | 22 ++- 4 files changed, 28 insertions(+), 317 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f2f1e40..bfe2587 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,5 +27,5 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - title: ${{ github.ref }} + title: Release ${{ github.ref }} tag: ${{ github.ref }} \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 8363f8c..48d2e6e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "openid" -version = "0.15.0" +version = "0.16.0" authors = ["Alexander Korolev "] edition = "2021" categories = ["authentication"] diff --git a/README.md b/README.md index 39ef0fb..26a656e 100644 --- a/README.md +++ b/README.md @@ -42,14 +42,14 @@ Add dependency to Cargo.toml: ```toml [dependencies] -openid = "0.15" +openid = "0.16" ``` By default we use native tls, if you want to use `rustls`: ```toml [dependencies] -openid = { version = "0.15", default-features = false, features = ["rustls"] } +openid = { version = "0.16", default-features = false, features = ["rustls"] } ``` ### Use case: [Warp](https://crates.io/crates/warp) web server with [JHipster](https://www.jhipster.tech/) generated frontend and [Google OpenID Connect](https://developers.google.com/identity/protocols/OpenIDConnect) @@ -59,322 +59,13 @@ This example provides only Rust part, assuming just default JHipster frontend se in Cargo.toml: ```toml -[dependencies] -anyhow = "1.0" -cookie = "0.18" -dotenv = "0.15" -log = "0.4" -openid = "0.15" -pretty_env_logger = "0.5" -reqwest = "0.12" -serde = { version = "1", default-features = false, features = [ "derive" ] } -serde_json = "1" -tokio = { version = "1", default-features = false, features = [ "rt-multi-thread", "macros" ] } -uuid = { version = "1.0", default-features = false, features = [ "v4" ] } -warp = { version = "0.3", default-features = false } +404: Not Found ``` in src/main.rs: ```rust, compile_fail -use std::{convert::Infallible, env, net::SocketAddr, sync::Arc}; - -use cookie::time::Duration; -use log::{error, info}; -use openid::{Client, Discovered, DiscoveredClient, Options, StandardClaims, Token, Userinfo}; -use openid_examples::{ - entity::{LoginQuery, Sessions, User}, - INDEX_HTML, -}; -use tokio::sync::RwLock; -use warp::{ - http::{Response, StatusCode}, - reject, Filter, Rejection, Reply, -}; - -type OpenIDClient = Client; - -const EXAMPLE_COOKIE: &str = "openid_warp_example"; - -#[tokio::main] -async fn main() -> anyhow::Result<()> { - dotenv::dotenv().ok(); - - pretty_env_logger::init(); - - let client_id = env::var("CLIENT_ID").expect(" for your provider"); - let client_secret = env::var("CLIENT_SECRET").ok(); - let issuer_url = - env::var("ISSUER").unwrap_or_else(|_| "https://accounts.google.com".to_string()); - let redirect = Some(host("/login/oauth2/code/oidc")); - let issuer = reqwest::Url::parse(&issuer_url)?; - let listen: SocketAddr = env::var("LISTEN") - .unwrap_or_else(|_| "127.0.0.1:8080".to_string()) - .parse()?; - - info!("redirect: {:?}", redirect); - info!("issuer: {}", issuer); - - let client = Arc::new( - DiscoveredClient::discover( - client_id, - client_secret.unwrap_or_default(), - redirect, - issuer, - ) - .await?, - ); - - info!("discovered config: {:?}", client.config()); - - let with_client = |client: Arc>| warp::any().map(move || client.clone()); - - let sessions = Arc::new(RwLock::new(Sessions::default())); - - let with_sessions = |sessions: Arc>| warp::any().map(move || sessions.clone()); - - let index = warp::path::end() - .and(warp::get()) - .map(|| warp::reply::html(INDEX_HTML)); - - let authorize = warp::path!("oauth2" / "authorization" / "oidc") - .and(warp::get()) - .and(with_client(client.clone())) - .and_then(reply_authorize); - - let login = warp::path!("login" / "oauth2" / "code" / "oidc") - .and(warp::get()) - .and(with_client(client.clone())) - .and(warp::query::()) - .and(with_sessions(sessions.clone())) - .and_then(reply_login); - - let logout = warp::path!("logout") - .and(warp::get()) - .and(with_client(client.clone())) - .and(warp::cookie::optional(EXAMPLE_COOKIE)) - .and(with_sessions(sessions.clone())) - .and_then(reply_logout); - - let api_account = warp::path!("api" / "account") - .and(warp::get()) - .and(with_user(sessions)) - .map(|user: User| warp::reply::json(&user)); - - let routes = index - .or(authorize) - .or(login) - .or(logout) - .or(api_account) - .recover(handle_rejections); - - let logged_routes = routes.with(warp::log("openid_warp_example")); - - warp::serve(logged_routes).run(listen).await; - - Ok(()) -} - -async fn request_token( - oidc_client: &OpenIDClient, - login_query: &LoginQuery, -) -> anyhow::Result> { - let mut token: Token = oidc_client.request_token(&login_query.code).await?.into(); - - if let Some(id_token) = token.id_token.as_mut() { - oidc_client.decode_token(id_token)?; - oidc_client.validate_token(id_token, None, None)?; - info!("token: {:?}", id_token); - } else { - return Ok(None); - } - - let userinfo = oidc_client.request_userinfo(&token).await?; - - info!("user info: {:?}", userinfo); - - Ok(Some((token, userinfo))) -} - -async fn reply_login( - oidc_client: Arc, - login_query: LoginQuery, - sessions: Arc>, -) -> Result { - let request_token = request_token(&oidc_client, &login_query).await; - match request_token { - Ok(Some((token, user_info))) => { - let id = uuid::Uuid::new_v4().to_string(); - - let login = user_info.preferred_username.clone(); - let email = user_info.email.clone(); - - let user = User { - id: user_info.sub.clone().unwrap_or_default(), - login, - last_name: user_info.family_name.clone(), - first_name: user_info.name.clone(), - email, - activated: user_info.email_verified, - image_url: user_info.picture.clone().map(|x| x.to_string()), - lang_key: Some("en".to_string()), - authorities: vec!["ROLE_USER".to_string()], - }; - - let authorization_cookie = ::cookie::Cookie::build((EXAMPLE_COOKIE, &id)) - .path("/") - .http_only(true) - .build() - .to_string(); - - sessions - .write() - .await - .map - .insert(id, (user, token, user_info)); - - let redirect_url = login_query.state.clone().unwrap_or_else(|| host("/")); - - Ok(Response::builder() - .status(StatusCode::MOVED_PERMANENTLY) - .header(warp::http::header::LOCATION, redirect_url) - .header(warp::http::header::SET_COOKIE, authorization_cookie) - .body("") - .unwrap()) - } - Ok(None) => { - error!("login error in call: no id_token found"); - - response_unauthorized() - } - Err(err) => { - error!("login error in call: {:?}", err); - - response_unauthorized() - } - } -} - -fn response_unauthorized() -> Result, Infallible> { - Ok(Response::builder() - .status(StatusCode::UNAUTHORIZED) - .body("") - .unwrap()) -} - -async fn reply_logout( - oidc_client: Arc, - session_id: Option, - sessions: Arc>, -) -> Result { - let Some(id) = session_id else { - return response_unauthorized(); - }; - - let session_removed = sessions.write().await.map.remove(&id); - - if let Some(id_token) = session_removed.and_then(|(_, token, _)| token.bearer.id_token) { - let authorization_cookie = ::cookie::Cookie::build((EXAMPLE_COOKIE, &id)) - .path("/") - .http_only(true) - .max_age(Duration::seconds(-1)) - .build() - .to_string(); - - let return_redirect_url = host("/"); - - let redirect_url = oidc_client - .config() - .end_session_endpoint - .clone() - .map(|mut logout_provider_endpoint| { - logout_provider_endpoint - .query_pairs_mut() - .append_pair("id_token_hint", &id_token) - .append_pair("post_logout_redirect_uri", &return_redirect_url); - logout_provider_endpoint.to_string() - }) - .unwrap_or_else(|| return_redirect_url); - - info!("logout redirect url: {redirect_url}"); - - Ok(Response::builder() - .status(StatusCode::FOUND) - .header(warp::http::header::LOCATION, redirect_url) - .header(warp::http::header::SET_COOKIE, authorization_cookie) - .body("") - .unwrap()) - } else { - response_unauthorized() - } -} - -async fn reply_authorize(oidc_client: Arc) -> Result { - let origin_url = env::var("ORIGIN").unwrap_or_else(|_| host("")); - - let auth_url = oidc_client.auth_url(&Options { - scope: Some("openid email profile".into()), - state: Some(origin_url), - ..Default::default() - }); - - info!("authorize: {}", auth_url); - - let url: String = auth_url.into(); - - Ok(warp::reply::with_header( - StatusCode::FOUND, - warp::http::header::LOCATION, - url, - )) -} - -#[derive(Debug)] -struct Unauthorized; - -impl reject::Reject for Unauthorized {} - -async fn extract_user( - session_id: Option, - sessions: Arc>, -) -> Result { - if let Some(session_id) = session_id { - if let Some((user, _, _)) = sessions.read().await.map.get(&session_id) { - Ok(user.clone()) - } else { - Err(warp::reject::custom(Unauthorized)) - } - } else { - Err(warp::reject::custom(Unauthorized)) - } -} - -fn with_user( - sessions: Arc>, -) -> impl Filter + Clone { - warp::cookie::optional(EXAMPLE_COOKIE) - .and(warp::any().map(move || sessions.clone())) - .and_then(extract_user) -} - -async fn handle_rejections(err: Rejection) -> Result { - let code = if err.is_not_found() { - StatusCode::NOT_FOUND - } else if let Some(Unauthorized) = err.find() { - StatusCode::UNAUTHORIZED - } else { - StatusCode::INTERNAL_SERVER_ERROR - }; - - Ok(warp::reply::with_status(warp::reply(), code)) -} - -/// This host is the address, where user would be redirected after initial authorization. -/// For DEV environment with WebPack this is usually something like `http://localhost:9000`. -/// We are using `http://localhost:8080` in all-in-one example. -pub fn host(path: &str) -> String { - env::var("REDIRECT_URL").unwrap_or_else(|_| "http://localhost:8080".to_string()) + path -} +404: Not Found ``` -See full example: [openid-examples: warp](https://github.com/kilork/openid-examples/blob/v0.15/examples/warp.rs) +See full example: [openid-examples: warp](https://github.com/kilork/openid-examples/blob/v0.16/examples/warp.rs) diff --git a/release.sh b/release.sh index 599cbe0..26448c4 100755 --- a/release.sh +++ b/release.sh @@ -1,3 +1,23 @@ #!/bin/sh -echo "release" \ No newline at end of file +set -e + +RELEASE_TYPE=${RELEASE_TYPE:-minor} +cargo set-version --bump ${RELEASE_TYPE} +VERSION=`cargo pkgid | cut -d"#" -f2` +export OPENID_RUST_MAJOR_VERSION=`echo ${VERSION} | cut -d"." -f1,2` +pushd ../openid-examples +git checkout main +git pull +cargo upgrade -p openid@${OPENID_RUST_MAJOR_VERSION} +cargo update +git add . +git commit -m"openid version ${OPENID_RUST_MAJOR_VERSION}" +git branch v${OPENID_RUST_MAJOR_VERSION} +git push +popd +handlebars-magic templates . +git add . +git commit "Release v${VERSION}" +git tag v${VERSION} +#git push && git push --tag \ No newline at end of file