summaryrefslogtreecommitdiff
path: root/server/src
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
parent01753ebd32e4e0fa8adb11fb02a77720773e3018 (diff)
Start working on authentication
Diffstat (limited to 'server/src')
-rw-r--r--server/src/auth.rs97
-rw-r--r--server/src/main.rs79
2 files changed, 165 insertions, 11 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)
+ }
+}
diff --git a/server/src/main.rs b/server/src/main.rs
index aaa5798..994c291 100644
--- a/server/src/main.rs
+++ b/server/src/main.rs
@@ -1,10 +1,46 @@
-use std::env;
+use std::{env, sync::Arc};
-use axum::{routing::get, Json, Router};
-use data::MyMessage;
-use tracing::info;
+use axum::{extract::{FromRequest, Extension}, routing::get, Json, Router, http::{Request, request::Parts}, body::Body};
+use openidconnect::{
+ core::{CoreClient, CoreProviderMetadata, CoreResponseType},
+ reqwest::async_http_client,
+ AccessTokenHash, AuthenticationFlow, AuthorizationCode, ClientId, ClientSecret, CsrfToken,
+ IssuerUrl, Nonce, OAuth2TokenResponse, PkceCodeChallenge, RedirectUrl, Scope, TokenResponse, url::Url,
+};
+use protocol::UserInfo;
+use tower_http::trace::TraceLayer;
+use tower_cookies::{Cookie, CookieManagerLayer, Cookies};
+use tracing::{info, trace};
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
-use tower_http::{trace::TraceLayer};
+mod auth;
+use crate::auth::Authenticator;
+
+struct ServerContext {
+ pub app_url: String,
+ pub authenticator: Authenticator,
+}
+type ContextExtension = Extension<Arc<ServerContext>>;
+
+async fn keycloak_client(
+ issuer_url: IssuerUrl,
+ client_id: ClientId,
+ client_secret: ClientSecret,
+ redirect_uri: RedirectUrl,
+) -> CoreClient {
+ // // 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
+ );
+
+ client
+}
#[tokio::main]
async fn main() {
@@ -16,12 +52,23 @@ async fn main() {
))
.with(tracing_subscriber::fmt::layer())
.init();
-
+
let bind_address = env::var("BIND_ADDRESS").unwrap();
info!("Starting server on {}", bind_address);
+ let app_url = env::var("APP_URL").unwrap();
+
+ let state = Arc::new(ServerContext {
+ app_url: app_url,
+ authenticator: Authenticator::from_env().await,
+ });
+
let app = Router::new()
- .route("/api/test", get(test))
+ .route("/api/user/info", get(user_info))
+ .route("/api/get_login_url", get(get_login_url))
+ .route(auth::LOGIN_CALLBACK, get(login_callback))
+ .layer(CookieManagerLayer::new())
+ .layer(Extension(state))
.layer(TraceLayer::new_for_http());
axum::Server::bind(&bind_address.parse().unwrap())
@@ -30,8 +77,18 @@ async fn main() {
.unwrap();
}
-async fn test() -> Json<MyMessage> {
- Json(MyMessage {
- message: "Hello, ,World!".to_string(),
- })
+async fn user_info() -> Json<Option<UserInfo>> {
+ Json(None)
+}
+
+async fn login_callback(mut req: Parts) -> &'static str {
+ info!("{req:?}");
+ "hello"
+}
+
+async fn get_login_url(extension: ContextExtension) -> Json<Url> {
+ let (user_id, auth_url) = extension.authenticator.get_login_url().await;
+ trace!("Creating auth url for {user_id:?}");
+ // cookies.add(Cookie::new("user-id", serde_json::to_string(user_id)));
+ Json(auth_url)
}