diff --git a/src/auth.rs b/src/auth.rs deleted file mode 100644 index 536bc67..0000000 --- a/src/auth.rs +++ /dev/null @@ -1,152 +0,0 @@ -// use crate::{error::ApiError, server::routes::auth::SignupParams, storage::db::SurrealDbClient}; -// use axum::async_trait; -// use axum_session_auth::Authentication; -// use serde::{Deserialize, Serialize}; -// use surrealdb::{ -// engine::any::Any, -// opt::auth::{Database, Namespace, Record}, -// Object, Surreal, -// }; -// use tracing::info; -// use uuid::Uuid; - -// #[derive(Deserialize, Serialize)] -// pub struct AuthParams { -// email: String, -// password: String, -// user_id: String, -// } - -// #[derive(Debug, Clone, Serialize, Deserialize)] -// pub struct User { -// pub user_id: String, -// pub email: String, -// #[serde(default)] -// pub anonymous: bool, -// } - -// impl Default for User { -// fn default() -> Self { -// Self { -// user_id: "user:guest".into(), -// email: "guest@example.com".into(), -// anonymous: true, -// } -// } -// } - -// #[async_trait] -// impl Authentication> for User { -// async fn load_user(userid: i64, pool: Option<&Surreal>) -> Result { -// let pool = pool.unwrap(); -// User::get_user(userid, pool) -// .await -// .ok_or_else(|| anyhow::anyhow!("Could not load user")) -// } - -// fn is_authenticated(&self) -> bool { -// !self.anonymous -// } - -// fn is_active(&self) -> bool { -// !self.anonymous -// } - -// fn is_anonymous(&self) -> bool { -// self.anonymous -// } -// } - -// impl User { -// // pub async fn get_user_by_email( -// // email: &str, -// // db: &SurrealDbClient, -// // ) -> Result, ApiError> { -// // info!("First, let's see what records exist"); -// // let debug_query: Vec = db.select("users").await?; -// // // let debug_query: Vec = db.client.query("SELECT * FROM user").await?.take(0)?; -// // info!("All users in database: {:?}", debug_query); - -// // // let tables: Vec = db.client.query("INFO FOR DB").await?.take(0)?; -// // // info!("Available tables: {:?}", tables); - -// // // Modified query to match exactly how the record is stored -// // let user: Option = db -// // .client -// // .query("SELECT * FROM user WHERE email = $email LIMIT 1") -// // .bind(("email", email.to_string())) -// // .await? -// // .take(0)?; - -// // info!("Found user: {:?}", user); - -// // Ok(user) -// // } - -// pub async fn get_user(id: i64, pool: &Surreal) -> Option { -// let user: Option = pool -// .query("SELECT * FROM user WHERE user_id = $user_id") -// .bind(("user_id", format!("user:{}", id))) -// .await -// .ok()? -// .take(0) -// .ok()?; - -// user -// } - -// pub async fn signin(params: SignupParams, db: &SurrealDbClient) -> Result<(), ApiError> { -// info!("Trying to sign in"); -// let result = db -// .client -// .signin(Record { -// access: "account", -// namespace: "test", - -// database: "test", -// params: SignupParams { -// email: params.email, -// password: params.password, -// }, -// }) -// .await?; - -// info!("{:?}", result.into_insecure_token()); -// Ok(()) -// } - -// pub async fn signup(params: SignupParams, db: &SurrealDbClient) -> Result { -// // First check if user already exists -// if let Some(_) = Self::get_user_by_email(¶ms.email, db).await? { -// return Err(ApiError::UserAlreadyExists); -// } - -// // Use SurrealDB's built-in signup -// let signup_response = db -// .client -// .signup(Record { -// access: "account", -// namespace: "test", -// database: "test", -// params: AuthParams { -// email: params.email.clone(), -// password: params.password.clone(), -// user_id: Uuid::new_v4().to_string(), -// }, -// }) -// .await?; - -// info!("Signup response: {:?}", signup_response); - -// // Wait a moment to ensure the record is created -// tokio::time::sleep(tokio::time::Duration::from_millis(300)).await; - -// Self::signin(params, db).await?; -// // Fetch the created user -// // let user = Self::get_user_by_email(¶ms.email, db) -// // .await? -// // .ok_or(ApiError::UserNotFound)?; - -// Ok(User::default()) -// } -// } diff --git a/src/bin/server.rs b/src/bin/server.rs index ee6cb0b..1465b59 100644 --- a/src/bin/server.rs +++ b/src/bin/server.rs @@ -1,11 +1,11 @@ use axum::{ extract::DefaultBodyLimit, - http::Method, + middleware::from_fn_with_state, routing::{get, post}, Router, }; use axum_session::{SessionConfig, SessionLayer, SessionStore}; -use axum_session_auth::{Auth, AuthConfig, AuthSession, AuthSessionLayer, Rights}; +use axum_session_auth::{AuthConfig, AuthSessionLayer}; use axum_session_surreal::SessionSurrealPool; use std::sync::Arc; use surrealdb::{engine::any::Any, Surreal}; @@ -16,6 +16,7 @@ use tracing_subscriber::{fmt, prelude::*, EnvFilter}; use zettle_db::{ rabbitmq::{consumer::RabbitMQConsumer, publisher::RabbitMQProducer, RabbitMQConfig}, server::{ + middleware_api_auth::api_auth, routes::{ auth::{show_signup_form, signup_handler}, file::upload_handler, @@ -54,7 +55,6 @@ async fn main() -> Result<(), Box> { tera: Arc::new(Tera::new("src/server/templates/**/*.html").unwrap()), openai_client: Arc::new(async_openai::Client::new()), }; - // app_state.surreal_db_client.query("DELETE user").await?; // setup_auth(&app_state.surreal_db_client).await?; @@ -71,7 +71,7 @@ async fn main() -> Result<(), Box> { // Create Axum router let app = Router::new() - .nest("/api/v1", api_routes_v1()) + .nest("/api/v1", api_routes_v1(&app_state)) .nest( "/", html_routes( @@ -82,7 +82,7 @@ async fn main() -> Result<(), Box> { ) .with_state(app_state); - tracing::info!("Listening on 0.0.0.0:3000"); + info!("Listening on 0.0.0.0:3000"); let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?; axum::serve(listener, app).await?; @@ -90,7 +90,7 @@ async fn main() -> Result<(), Box> { } /// Router for API functionality, version 1 -fn api_routes_v1() -> Router { +fn api_routes_v1(app_state: &AppState) -> Router { Router::new() // Ingress routes .route("/ingress", post(ingress_handler)) @@ -100,6 +100,7 @@ fn api_routes_v1() -> Router { .layer(DefaultBodyLimit::max(1024 * 1024 * 1024)) // Query routes .route("/query", post(query_handler)) + .route_layer(from_fn_with_state(app_state.clone(), api_auth)) } /// Router for HTML endpoints diff --git a/src/lib.rs b/src/lib.rs index c679302..8fc7ad7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,3 @@ -pub mod auth; pub mod error; pub mod ingress; pub mod rabbitmq; diff --git a/src/server/assets/style.css b/src/server/assets/style.css index 7aae006..97d4dc0 100644 --- a/src/server/assets/style.css +++ b/src/server/assets/style.css @@ -691,24 +691,6 @@ html { } } -.avatar { - position: relative; - display: inline-flex; -} - -.avatar > div { - display: block; - aspect-ratio: 1 / 1; - overflow: hidden; -} - -.avatar img { - height: 100%; - width: 100%; - -o-object-fit: cover; - object-fit: cover; -} - .avatar.placeholder > div { display: flex; align-items: center; @@ -861,11 +843,6 @@ html { padding-bottom: 0.25rem; } -.chat-image { - grid-row: span 2 / span 2; - align-self: flex-end; -} - .chat-bubble { position: relative; display: block; @@ -933,10 +910,6 @@ html { mask-image: url("data:image/svg+xml,%3csvg width='3' height='3' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='m 0 3 L 1 3 L 3 3 C 2 3 0 1 0 0'/%3e%3c/svg%3e"); } -.chat-end .chat-image { - grid-column-start: 2; -} - .chat-end .chat-bubble { grid-column-start: 1; border-end-end-radius: 0px; @@ -1128,69 +1101,6 @@ html { margin-inline-end: -1rem; } -.join { - display: inline-flex; - align-items: stretch; - border-radius: var(--rounded-btn, 0.5rem); -} - -.join :where(.join-item) { - border-start-end-radius: 0; - border-end-end-radius: 0; - border-end-start-radius: 0; - border-start-start-radius: 0; -} - -.join .join-item:not(:first-child):not(:last-child), - .join *:not(:first-child):not(:last-child) .join-item { - border-start-end-radius: 0; - border-end-end-radius: 0; - border-end-start-radius: 0; - border-start-start-radius: 0; -} - -.join .join-item:first-child:not(:last-child), - .join *:first-child:not(:last-child) .join-item { - border-start-end-radius: 0; - border-end-end-radius: 0; -} - -.join .dropdown .join-item:first-child:not(:last-child), - .join *:first-child:not(:last-child) .dropdown .join-item { - border-start-end-radius: inherit; - border-end-end-radius: inherit; -} - -.join :where(.join-item:first-child:not(:last-child)), - .join :where(*:first-child:not(:last-child) .join-item) { - border-end-start-radius: inherit; - border-start-start-radius: inherit; -} - -.join .join-item:last-child:not(:first-child), - .join *:last-child:not(:first-child) .join-item { - border-end-start-radius: 0; - border-start-start-radius: 0; -} - -.join :where(.join-item:last-child:not(:first-child)), - .join :where(*:last-child:not(:first-child) .join-item) { - border-start-end-radius: inherit; - border-end-end-radius: inherit; -} - -@supports not selector(:has(*)) { - :where(.join *) { - border-radius: inherit; - } -} - -@supports selector(:has(*)) { - :where(.join *:has(.join-item)) { - border-radius: inherit; - } -} - .link { cursor: pointer; text-decoration-line: underline; @@ -1262,14 +1172,6 @@ html { align-items: center; } -.avatar-group :where(.avatar) { - overflow: hidden; - border-radius: 9999px; - border-width: 4px; - --tw-border-opacity: 1; - border-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-border-opacity))); -} - .btm-nav > *.disabled, .btm-nav > *[disabled] { pointer-events: none; @@ -1474,12 +1376,6 @@ html { gap: 1rem; } -.divider-primary:before, - .divider-primary:after { - --tw-bg-opacity: 1; - background-color: var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity))); -} - .label-text { font-size: 0.875rem; line-height: 1.25rem; @@ -1549,12 +1445,6 @@ html { text-align: inherit; } -.join > :where(*:not(:first-child)) { - margin-top: 0px; - margin-bottom: 0px; - margin-inline-start: -1px; -} - .join > :where(*:not(:first-child)):is(.btn) { margin-inline-start: calc(var(--border-btn) * -1); } @@ -1868,46 +1758,6 @@ html { transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); } -.join.join-vertical { - flex-direction: column; -} - -.join.join-vertical .join-item:first-child:not(:last-child), - .join.join-vertical *:first-child:not(:last-child) .join-item { - border-end-start-radius: 0; - border-end-end-radius: 0; - border-start-start-radius: inherit; - border-start-end-radius: inherit; -} - -.join.join-vertical .join-item:last-child:not(:first-child), - .join.join-vertical *:last-child:not(:first-child) .join-item { - border-start-start-radius: 0; - border-start-end-radius: 0; - border-end-start-radius: inherit; - border-end-end-radius: inherit; -} - -.join.join-horizontal { - flex-direction: row; -} - -.join.join-horizontal .join-item:first-child:not(:last-child), - .join.join-horizontal *:first-child:not(:last-child) .join-item { - border-end-end-radius: 0; - border-start-end-radius: 0; - border-end-start-radius: inherit; - border-start-start-radius: inherit; -} - -.join.join-horizontal .join-item:last-child:not(:first-child), - .join.join-horizontal *:last-child:not(:first-child) .join-item { - border-end-start-radius: 0; - border-start-start-radius: 0; - border-end-end-radius: inherit; - border-start-end-radius: inherit; -} - .menu-horizontal { display: inline-flex; flex-direction: row; @@ -1917,40 +1767,6 @@ html { position: absolute; } -.avatar.online:before { - content: ""; - position: absolute; - z-index: 10; - display: block; - border-radius: 9999px; - --tw-bg-opacity: 1; - background-color: var(--fallback-su,oklch(var(--su)/var(--tw-bg-opacity))); - outline-style: solid; - outline-width: 2px; - outline-color: var(--fallback-b1,oklch(var(--b1)/1)); - width: 15%; - height: 15%; - top: 7%; - right: 7%; -} - -.avatar.offline:before { - content: ""; - position: absolute; - z-index: 10; - display: block; - border-radius: 9999px; - --tw-bg-opacity: 1; - background-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity))); - outline-style: solid; - outline-width: 2px; - outline-color: var(--fallback-b1,oklch(var(--b1)/1)); - width: 15%; - height: 15%; - top: 7%; - right: 7%; -} - .card-compact .card-body { padding: 1rem; font-size: 0.875rem; @@ -1971,22 +1787,10 @@ html { margin-bottom: 0.75rem; } -.join.join-vertical > :where(*:not(:first-child)) { - margin-left: 0px; - margin-right: 0px; - margin-top: -1px; -} - .join.join-vertical > :where(*:not(:first-child)):is(.btn) { margin-top: calc(var(--border-btn) * -1); } -.join.join-horizontal > :where(*:not(:first-child)) { - margin-top: 0px; - margin-bottom: 0px; - margin-inline-start: -1px; -} - .join.join-horizontal > :where(*:not(:first-child)):is(.btn) { margin-inline-start: calc(var(--border-btn) * -1); margin-top: 0px; @@ -2013,15 +1817,15 @@ html { box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); } -.relative { - position: relative; -} - .mx-auto { margin-left: auto; margin-right: auto; } +.mb-4 { + margin-bottom: 1rem; +} + .mb-8 { margin-bottom: 2rem; } @@ -2030,14 +1834,6 @@ html { margin-top: 1rem; } -.mt-8 { - margin-top: 2rem; -} - -.mb-4 { - margin-bottom: 1rem; -} - .mt-6 { margin-top: 1.5rem; } @@ -2054,22 +1850,6 @@ html { display: none; } -.h-16 { - height: 4rem; -} - -.h-auto { - height: auto; -} - -.h-full { - height: 100%; -} - -.min-h-36 { - min-height: 9rem; -} - .min-h-\[80vh\] { min-height: 80vh; } @@ -2078,42 +1858,18 @@ html { min-height: 100vh; } -.min-h-\[6rem\] { - min-height: 6rem; +.w-96 { + width: 24rem; } .w-full { width: 100%; } -.w-10 { - width: 2.5rem; -} - -.w-96 { - width: 24rem; -} - -.min-w-\[18rem\] { - min-width: 18rem; -} - .max-w-2xl { max-width: 42rem; } -.max-w-7xl { - max-width: 80rem; -} - -.max-w-xs { - max-width: 20rem; -} - -.max-w-4xl { - max-width: 56rem; -} - .flex-1 { flex: 1 1 0%; } @@ -2126,10 +1882,6 @@ html { flex-direction: column; } -.flex-wrap { - flex-wrap: wrap; -} - .items-center { align-items: center; } @@ -2138,30 +1890,6 @@ html { justify-content: center; } -.justify-between { - justify-content: space-between; -} - -.gap-4 { - gap: 1rem; -} - -.gap-2 { - gap: 0.5rem; -} - -.space-x-4 > :not([hidden]) ~ :not([hidden]) { - --tw-space-x-reverse: 0; - margin-right: calc(1rem * var(--tw-space-x-reverse)); - margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse))); -} - -.space-y-3 > :not([hidden]) ~ :not([hidden]) { - --tw-space-y-reverse: 0; - margin-top: calc(0.75rem * calc(1 - var(--tw-space-y-reverse))); - margin-bottom: calc(0.75rem * var(--tw-space-y-reverse)); -} - .space-y-4 > :not([hidden]) ~ :not([hidden]) { --tw-space-y-reverse: 0; margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse))); @@ -2174,95 +1902,11 @@ html { margin-bottom: calc(2rem * var(--tw-space-y-reverse)); } -.overflow-x-auto { - overflow-x: auto; -} - -.overflow-x-hidden { - overflow-x: hidden; -} - -.break-words { - overflow-wrap: break-word; -} - -.rounded-lg { - border-radius: 0.5rem; -} - -.rounded-md { - border-radius: 0.375rem; -} - -.rounded-xl { - border-radius: 0.75rem; -} - -.rounded-full { - border-radius: 9999px; -} - .rounded-t-none { border-top-left-radius: 0px; border-top-right-radius: 0px; } -.rounded-b-box { - border-bottom-right-radius: var(--rounded-box, 1rem); - border-bottom-left-radius: var(--rounded-box, 1rem); -} - -.rounded-se-box { - border-start-end-radius: var(--rounded-box, 1rem); -} - -.border { - border-width: 1px; -} - -.border-b { - border-bottom-width: 1px; -} - -.border-blue-500\/30 { - border-color: rgb(59 130 246 / 0.3); -} - -.border-gray-600 { - --tw-border-opacity: 1; - border-color: rgb(75 85 99 / var(--tw-border-opacity, 1)); -} - -.border-purple-500\/30 { - border-color: rgb(168 85 247 / 0.3); -} - -.border-white\/10 { - border-color: rgb(255 255 255 / 0.1); -} - -.border-base-300 { - --tw-border-opacity: 1; - border-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-border-opacity, 1))); -} - -.bg-black\/30 { - background-color: rgb(0 0 0 / 0.3); -} - -.bg-blue-600\/20 { - background-color: rgb(37 99 235 / 0.2); -} - -.bg-gray-800 { - --tw-bg-opacity: 1; - background-color: rgb(31 41 55 / var(--tw-bg-opacity, 1)); -} - -.bg-purple-600\/20 { - background-color: rgb(147 51 234 / 0.2); -} - .bg-base-100 { --tw-bg-opacity: 1; background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity, 1))); @@ -2273,15 +1917,6 @@ html { background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity, 1))); } -.bg-base-300 { - --tw-bg-opacity: 1; - background-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity, 1))); -} - -.bg-gradient-to-br { - background-image: linear-gradient(to bottom right, var(--tw-gradient-stops)); -} - .bg-gradient-to-r { background-image: linear-gradient(to right, var(--tw-gradient-stops)); } @@ -2292,68 +1927,27 @@ html { --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); } -.from-pink-500 { - --tw-gradient-from: #ec4899 var(--tw-gradient-from-position); - --tw-gradient-to: rgb(236 72 153 / 0) var(--tw-gradient-to-position); - --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); -} - -.from-slate-50 { - --tw-gradient-from: #f8fafc var(--tw-gradient-from-position); - --tw-gradient-to: rgb(248 250 252 / 0) var(--tw-gradient-to-position); - --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); -} - .via-purple-500 { --tw-gradient-to: rgb(168 85 247 / 0) var(--tw-gradient-to-position); --tw-gradient-stops: var(--tw-gradient-from), #a855f7 var(--tw-gradient-via-position), var(--tw-gradient-to); } -.via-red-500 { - --tw-gradient-to: rgb(239 68 68 / 0) var(--tw-gradient-to-position); - --tw-gradient-stops: var(--tw-gradient-from), #ef4444 var(--tw-gradient-via-position), var(--tw-gradient-to); -} - .to-pink-500 { --tw-gradient-to: #ec4899 var(--tw-gradient-to-position); } -.to-purple-200 { - --tw-gradient-to: #e9d5ff var(--tw-gradient-to-position); -} - -.to-yellow-500 { - --tw-gradient-to: #eab308 var(--tw-gradient-to-position); -} - -.bg-cover { - background-size: cover; -} - .bg-clip-text { -webkit-background-clip: text; background-clip: text; } -.bg-top { - background-position: top; -} - -.p-0\.5 { - padding: 0.125rem; -} - -.p-4 { - padding: 1rem; -} - .p-2 { padding: 0.5rem; } -.px-3 { - padding-left: 0.75rem; - padding-right: 0.75rem; +.px-1 { + padding-left: 0.25rem; + padding-right: 0.25rem; } .px-4 { @@ -2361,36 +1955,11 @@ html { padding-right: 1rem; } -.px-6 { - padding-left: 1.5rem; - padding-right: 1.5rem; -} - -.py-2 { - padding-top: 0.5rem; - padding-bottom: 0.5rem; -} - -.py-3 { - padding-top: 0.75rem; - padding-bottom: 0.75rem; -} - -.py-4 { - padding-top: 1rem; - padding-bottom: 1rem; -} - .py-8 { padding-top: 2rem; padding-bottom: 2rem; } -.px-1 { - padding-left: 0.25rem; - padding-right: 0.25rem; -} - .text-center { text-align: center; } @@ -2420,125 +1989,21 @@ html { line-height: 1.75rem; } -.font-black { - font-weight: 900; -} - .font-bold { font-weight: 700; } -.font-medium { - font-weight: 500; -} - -.text-blue-400 { - --tw-text-opacity: 1; - color: rgb(96 165 250 / var(--tw-text-opacity, 1)); -} - -.text-gray-300 { - --tw-text-opacity: 1; - color: rgb(209 213 219 / var(--tw-text-opacity, 1)); -} - .text-gray-400 { --tw-text-opacity: 1; color: rgb(156 163 175 / var(--tw-text-opacity, 1)); } -.text-purple-400 { - --tw-text-opacity: 1; - color: rgb(192 132 252 / var(--tw-text-opacity, 1)); -} - .text-transparent { color: transparent; } -.text-white { - --tw-text-opacity: 1; - color: rgb(255 255 255 / var(--tw-text-opacity, 1)); -} - -.placeholder-gray-400::-moz-placeholder { - --tw-placeholder-opacity: 1; - color: rgb(156 163 175 / var(--tw-placeholder-opacity, 1)); -} - -.placeholder-gray-400::placeholder { - --tw-placeholder-opacity: 1; - color: rgb(156 163 175 / var(--tw-placeholder-opacity, 1)); -} - .shadow-xl { --tw-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); --tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color); box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); -} - -.outline-none { - outline: 2px solid transparent; - outline-offset: 2px; -} - -.backdrop-blur-md { - --tw-backdrop-blur: blur(12px); - -webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia); - backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia); -} - -.backdrop-blur-sm { - --tw-backdrop-blur: blur(4px); - -webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia); - backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia); -} - -.transition-all { - transition-property: all; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 150ms; -} - -.transition-colors { - transition-property: color, background-color, border-color, text-decoration-color, fill, stroke; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 150ms; -} - -.\[border-width\:var\(--tab-border\)\] { - border-width: var(--tab-border); -} - -.hover\:scale-105:hover { - --tw-scale-x: 1.05; - --tw-scale-y: 1.05; - transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); -} - -.hover\:bg-blue-600\/30:hover { - background-color: rgb(37 99 235 / 0.3); -} - -.hover\:bg-purple-600\/30:hover { - background-color: rgb(147 51 234 / 0.3); -} - -.hover\:text-white:hover { - --tw-text-opacity: 1; - color: rgb(255 255 255 / var(--tw-text-opacity, 1)); -} - -@media (min-width: 640px) { - .sm\:px-6 { - padding-left: 1.5rem; - padding-right: 1.5rem; - } -} - -@media (min-width: 1024px) { - .lg\:px-8 { - padding-left: 2rem; - padding-right: 2rem; - } } \ No newline at end of file diff --git a/src/server/middleware_api_auth.rs b/src/server/middleware_api_auth.rs new file mode 100644 index 0000000..995a904 --- /dev/null +++ b/src/server/middleware_api_auth.rs @@ -0,0 +1,39 @@ +use axum::{ + extract::{Request, State}, + middleware::Next, + response::Response, +}; + +use crate::{error::ApiError, storage::types::user::User}; + +use super::AppState; + +pub async fn api_auth( + State(state): State, + mut request: Request, + next: Next, +) -> Result { + let api_key = extract_api_key(&request).ok_or(ApiError::UserNotFound)?; + + let user = User::find_by_api_key(&api_key, &state.surreal_db_client).await?; + let user = user.ok_or(ApiError::UserNotFound)?; + + request.extensions_mut().insert(user); + + Ok(next.run(request).await) +} + +fn extract_api_key(request: &Request) -> Option { + request + .headers() + .get("X-API-Key") + .and_then(|v| v.to_str().ok()) + .or_else(|| { + request + .headers() + .get("Authorization") + .and_then(|v| v.to_str().ok()) + .and_then(|auth| auth.strip_prefix("Bearer ").map(|s| s.trim())) + }) + .map(String::from) +} diff --git a/src/server/mod.rs b/src/server/mod.rs index a6f8748..c5070ee 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -4,6 +4,7 @@ use crate::storage::db::SurrealDbClient; use std::sync::Arc; use tera::Tera; +pub mod middleware_api_auth; pub mod routes; #[derive(Clone)] diff --git a/src/server/routes/query.rs b/src/server/routes/query.rs index 282cfc0..574941c 100644 --- a/src/server/routes/query.rs +++ b/src/server/routes/query.rs @@ -1,8 +1,8 @@ pub mod helper; pub mod prompt; -use crate::{error::ApiError, server::AppState}; -use axum::{extract::State, response::IntoResponse, Json}; +use crate::{error::ApiError, server::AppState, storage::types::user::User}; +use axum::{extract::State, response::IntoResponse, Extension, Json}; use helper::get_answer_with_references; use serde::Deserialize; use tracing::info; @@ -27,9 +27,11 @@ pub struct LLMResponseFormat { pub async fn query_handler( State(state): State, + Extension(user): Extension, Json(query): Json, ) -> Result { info!("Received input: {:?}", query); + info!("{:?}", user); let answer = get_answer_with_references(&state.surreal_db_client, &state.openai_client, &query.query) diff --git a/src/storage/db.rs b/src/storage/db.rs index 55e5812..7f8f205 100644 --- a/src/storage/db.rs +++ b/src/storage/db.rs @@ -113,3 +113,18 @@ where { db_client.select((T::table_name(), id)).await } + +/// Operation to delete a single object by its ID, requires the struct to implement StoredObject +/// +/// # Arguments +/// * `db_client` - An initialized database client +/// * `id` - The ID of the item to delete +/// +/// # Returns +/// * `Result, Error>` - The deleted item or Error +pub async fn delete_item(db_client: &Surreal, id: &str) -> Result, Error> +where + T: for<'de> StoredObject, +{ + db_client.delete((T::table_name(), id)).await +} diff --git a/src/storage/types/user.rs b/src/storage/types/user.rs index 9bdafaf..94a10c4 100644 --- a/src/storage/types/user.rs +++ b/src/storage/types/user.rs @@ -10,17 +10,15 @@ use uuid::Uuid; stored_object!(User, "user", { email: String, password: String, - anonymous: bool + anonymous: bool, + api_key: Option }); #[async_trait] impl Authentication> for User { - async fn load_user(userid: String, pool: Option<&Surreal>) -> Result { - let pool = pool.unwrap(); - Ok(get_item::(&pool, userid.as_str()).await?.unwrap()) - // User::get_user(userid, pool) - // .await - // .ok_or_else(|| anyhow::anyhow!("Could not load user")) + async fn load_user(userid: String, db: Option<&Surreal>) -> Result { + let db = db.unwrap(); + Ok(get_item::(db, userid.as_str()).await?.unwrap()) } fn is_authenticated(&self) -> bool { @@ -43,7 +41,7 @@ impl User { db: &SurrealDbClient, ) -> Result { // Check if user exists - if let Some(_) = Self::find_by_email(&email, db).await? { + if (Self::find_by_email(&email, db).await?).is_some() { return Err(ApiError::UserAlreadyExists); } @@ -97,4 +95,66 @@ impl User { Ok(user) } + + pub async fn find_by_api_key( + api_key: &str, + db: &SurrealDbClient, + ) -> Result, ApiError> { + let user: Option = db + .client + .query("SELECT * FROM user WHERE api_key = $api_key LIMIT 1") + .bind(("api_key", api_key.to_string())) + .await? + .take(0)?; + + Ok(user) + } + + pub async fn set_api_key(id: &str, db: &SurrealDbClient) -> Result { + // Generate a secure random API key + let api_key = format!("sk_{}", Uuid::new_v4().to_string().replace("-", "")); + + // Update the user record with the new API key + let user: Option = db + .client + .query( + "UPDATE type::thing('user', $id) + SET api_key = $api_key + RETURN AFTER", + ) + .bind(("id", id.to_owned())) + .bind(("api_key", api_key.clone())) + .await? + .take(0)?; + + // If the user was found and updated, return the API key + if user.is_some() { + Ok(api_key) + } else { + Err(ApiError::UserNotFound) + } + } + pub async fn reset_api_key(id: &str, db: &SurrealDbClient) -> Result { + // Simply call set_api_key to generate and set a new key + Self::set_api_key(id, db).await + } + + pub async fn revoke_api_key(id: &str, db: &SurrealDbClient) -> Result<(), ApiError> { + let user: Option = db + .client + .query( + "UPDATE type::thing('user', $id) + SET api_key = NULL + RETURN AFTER", + ) + .bind(("id", id.to_owned())) + .await? + .take(0)?; + + if user.is_some() { + Ok(()) + } else { + Err(ApiError::UserNotFound) + } + } }