diff options
author | Kjetil Orbekk <kj@orbekk.com> | 2022-10-07 16:59:29 -0400 |
---|---|---|
committer | Kjetil Orbekk <kj@orbekk.com> | 2022-10-07 16:59:29 -0400 |
commit | c64a7a640ac8c59eb6339f0a06d2ad2efab3fd11 (patch) | |
tree | 229ccf88da26d5339aadab98013834b6c2af6cbc /server/src/auth.rs | |
parent | 01753ebd32e4e0fa8adb11fb02a77720773e3018 (diff) |
Start working on authentication
Diffstat (limited to 'server/src/auth.rs')
-rw-r--r-- | server/src/auth.rs | 97 |
1 files changed, 97 insertions, 0 deletions
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<Mutex<LruCache<EndUserId, LoginState>>>, +} + +#[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::<CoreResponseType>::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) + } +} |