summaryrefslogtreecommitdiff
path: root/server
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
parent01753ebd32e4e0fa8adb11fb02a77720773e3018 (diff)
Start working on authentication
Diffstat (limited to 'server')
-rw-r--r--server/.env7
-rw-r--r--server/Cargo.toml7
-rw-r--r--server/src/auth.rs97
-rw-r--r--server/src/main.rs79
4 files changed, 176 insertions, 14 deletions
diff --git a/server/.env b/server/.env
index 720d4c7..a639376 100644
--- a/server/.env
+++ b/server/.env
@@ -1,4 +1,7 @@
RUST_LOG=info,tower_http=trace
BIND_ADDRESS=[::]:11121
-WEBAPP_PATH=../webapp/dist/
-RUST_BACKTRACE=1 \ No newline at end of file
+RUST_BACKTRACE=1
+OPENID_ISSUER_URL=https://auth.orbekk.com/realms/test
+OPENID_CLIENT_ID=test-client
+OPENID_CLIENT_SECRET=EbIMIpGnYPrG1GBl6eZtVM5zIhiuu5p1
+APP_URL=https://bridge.orbekk.com \ No newline at end of file
diff --git a/server/Cargo.toml b/server/Cargo.toml
index 88dee76..f25aa4c 100644
--- a/server/Cargo.toml
+++ b/server/Cargo.toml
@@ -14,4 +14,9 @@ tokio = { version = "1.21.2", features = ["full"] }
tower-http = { version = "0.3.4", features = ["full"] }
tracing = "0.1.37"
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
-data = { path = "../data" }
+protocol = { path = "../protocol" }
+openidconnect = "2.3.2"
+lru = "0.8.1"
+uuid = { version = "1.1.2", features = ["serde", "fast-rng", "v4"] }
+tower-cookies = "0.7.0"
+tower = { version = "0.4.13", features = ["full"] }
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)
}