From c64a7a640ac8c59eb6339f0a06d2ad2efab3fd11 Mon Sep 17 00:00:00 2001 From: Kjetil Orbekk Date: Fri, 7 Oct 2022 16:59:29 -0400 Subject: Start working on authentication --- server/src/auth.rs | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 server/src/auth.rs (limited to 'server/src/auth.rs') diff --git a/server/src/auth.rs b/server/src/auth.rs new file mode 100644 index 0000000..ab1ba8b --- /dev/null +++ b/server/src/auth.rs @@ -0,0 +1,97 @@ +use std::{ + env, + num::NonZeroUsize, + sync::{Arc, Mutex}, +}; + +use lru::LruCache; +use openidconnect::{ + core::{CoreClient, CoreProviderMetadata, CoreResponseType}, + reqwest::async_http_client, + url::Url, + AccessTokenHash, AuthenticationFlow, AuthorizationCode, ClientId, ClientSecret, CsrfToken, + IssuerUrl, Nonce, OAuth2TokenResponse, PkceCodeChallenge, RedirectUrl, Scope, TokenResponse, +}; +use uuid::Uuid; +use serde::{Deserialize, Serialize}; + +pub struct LoginState { + csrf_token: CsrfToken, + nonce: Nonce, +} + +pub struct Authenticator { + pub client: CoreClient, + pub login_cache: Arc>>, +} + +#[derive(Eq, PartialEq, Hash, Debug, Clone, Serialize, Deserialize)] +pub struct EndUserId(Uuid); + +impl EndUserId { + pub fn new() -> Self { + Self(Uuid::new_v4()) + } +} + +const LOGIN_CACHE_SIZE: usize = 50; + +pub const LOGIN_CALLBACK: &'static str = "/api/login_callback"; +fn redirect_url(app_url: &str) -> RedirectUrl { + RedirectUrl::new(format!("{}{}", app_url, LOGIN_CALLBACK)).unwrap() +} + +impl Authenticator { + pub async fn new( + issuer_url: IssuerUrl, + client_id: ClientId, + client_secret: ClientSecret, + redirect_uri: RedirectUrl, + ) -> Self { + // Use OpenID Connect Discovery to fetch the provider metadata. + let provider_metadata = CoreProviderMetadata::discover_async(issuer_url, async_http_client) + .await + .unwrap(); + + let client = + CoreClient::from_provider_metadata(provider_metadata, client_id, Some(client_secret)) + // Set the URL the user will be redirected to after the authorization process. + .set_redirect_uri(redirect_uri); + + Self { + client, + login_cache: Arc::new(Mutex::new(LruCache::new( + NonZeroUsize::new(LOGIN_CACHE_SIZE).unwrap(), + ))), + } + } + + pub async fn from_env() -> Self { + let app_url = env::var("APP_URL").unwrap(); + Authenticator::new( + IssuerUrl::new(env::var("OPENID_ISSUER_URL").unwrap()).unwrap(), + ClientId::new(env::var("OPENID_CLIENT_ID").unwrap()), + ClientSecret::new(env::var("OPENID_CLIENT_SECRET").unwrap()), + redirect_url(&app_url), + ) + .await + } + + pub async fn get_login_url(&self) -> (EndUserId, Url) { + let (auth_url, csrf_token, nonce) = self + .client + .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(); + let user_id = EndUserId::new(); + self.login_cache + .lock().unwrap() + .put(user_id.clone(), LoginState { csrf_token, nonce }); + (user_id, auth_url) + } +} -- cgit v1.2.3