summaryrefslogtreecommitdiff
path: root/server/src/auth.rs
diff options
context:
space:
mode:
authorKjetil Orbekk <kj@orbekk.com>2022-10-07 16:59:29 -0400
committerKjetil Orbekk <kj@orbekk.com>2022-10-07 16:59:29 -0400
commitc64a7a640ac8c59eb6339f0a06d2ad2efab3fd11 (patch)
tree229ccf88da26d5339aadab98013834b6c2af6cbc /server/src/auth.rs
parent01753ebd32e4e0fa8adb11fb02a77720773e3018 (diff)
Start working on authentication
Diffstat (limited to 'server/src/auth.rs')
-rw-r--r--server/src/auth.rs97
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)
+ }
+}