Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2024"

[dependencies]
axum = { version = "0.7", features = ["ws"] }
axum = { version = "0.8.8", features = ["ws"] }
tokio = { version = "1", features = ["full"] }
bollard = "0.17" # The Docker Client
futures = "0.3"
Expand All @@ -15,11 +15,11 @@ tracing = "0.1"
tracing-subscriber = "0.3"
sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "postgres", "uuid", "time"] }
uuid = { version = "1", features = ["serde", "v4"] }
oauth2 = "4.4"
tower-sessions = "0.10"
axum-extra = { version = "0.9", features = ["cookie"] }
oauth2 = "5.0"
tower-sessions = "0.15"
axum-extra = { version = "0.12", features = ["cookie"] }
dotenvy = "0.15" # To load client secrets
reqwest = { version = "0.11", features = ["json", "rustls-tls"] }
reqwest = { version = "0.12", features = ["json", "rustls-tls"] }
time = "0.3.46"
openssl = { version = "0.10", features = ["vendored"] }
openssl-sys = { version = "0.9", features = ["vendored"] }
Expand Down
118 changes: 0 additions & 118 deletions server/src/auth.rs

This file was deleted.

4 changes: 2 additions & 2 deletions server/src/handlers/admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ pub fn routes() -> Router<AppState> {
Router::new()
.route("/api/admin/stats", get(get_system_stats))
.route("/api/admin/projects", get(get_all_projects))
.route("/api/admin/container/:id", delete(kill_container))
.route("/api/admin/project/:slug", delete(delete_project_admin))
.route("/api/admin/container/{id}", delete(kill_container))
.route("/api/admin/project/{slug}", delete(delete_project_admin))
}

// Updated middleware to check the list
Expand Down
28 changes: 17 additions & 11 deletions server/src/handlers/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use axum::{
};
use oauth2::{
basic::BasicClient, AuthUrl, ClientId, ClientSecret, CsrfToken,
RedirectUrl, Scope, TokenUrl, TokenResponse,
RedirectUrl, Scope, TokenUrl, TokenResponse, EndpointSet, EndpointNotSet,
};
use serde::Deserialize;
use tower_sessions::Session;
Expand All @@ -17,6 +17,9 @@ use crate::models::User;
pub const AUTH_URL: &str = "https://github.com/login/oauth/authorize";
pub const TOKEN_URL: &str = "https://github.com/login/oauth/access_token";

// Type alias for a fully configured OAuth client with both auth and token endpoints set
type ConfiguredClient = BasicClient<EndpointSet, EndpointNotSet, EndpointNotSet, EndpointNotSet, EndpointSet>;

// Routes for Auth
pub fn routes() -> Router<AppState> {
Router::new()
Expand Down Expand Up @@ -50,17 +53,22 @@ async fn github_callback(
// FIX: Handle config errors gracefully
let client = make_client(&state).map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e))?;

// Create a stateful HTTP client with no redirects (for SSRF protection)
let http_client = reqwest::Client::builder()
.redirect(reqwest::redirect::Policy::none())
.build()
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("HTTP Client Error: {}", e)))?;

// 1. Exchange Code
let token = client
.exchange_code(oauth2::AuthorizationCode::new(query.code))
.request_async(oauth2::reqwest::async_http_client)
.request_async(&http_client)
.await
.map_err(|e| {
(StatusCode::INTERNAL_SERVER_ERROR, format!("Token Error: {}", e))
})?;

// 2. Fetch Profile
let http_client = reqwest::Client::new();
let user_data: User = http_client
.get("https://api.github.com/user")
.header("User-Agent", "TryCli Studio")
Expand Down Expand Up @@ -99,7 +107,7 @@ async fn get_me(session: Session) -> Result<impl IntoResponse, (StatusCode, Stri
}

// 4. Helper to create OAuth client (Now returns Result)
fn make_client(state: &AppState) -> Result<BasicClient, String> {
fn make_client(state: &AppState) -> Result<ConfiguredClient, String> {
let auth_url = AuthUrl::new(AUTH_URL.to_string())
.map_err(|e| format!("Invalid Auth URL: {}", e))?;

Expand All @@ -112,13 +120,11 @@ fn make_client(state: &AppState) -> Result<BasicClient, String> {
let redirect_url = RedirectUrl::new(format!("{}/auth/callback", api_url))
.map_err(|e| format!("Invalid Redirect URL: {}", e))?;

Ok(BasicClient::new(
ClientId::new(state.github_id.clone()),
Some(ClientSecret::new(state.github_secret.clone())),
auth_url,
Some(token_url),
)
.set_redirect_uri(redirect_url))
Ok(BasicClient::new(ClientId::new(state.github_id.clone()))
.set_client_secret(ClientSecret::new(state.github_secret.clone()))
.set_auth_uri(auth_url)
.set_token_uri(token_url)
.set_redirect_uri(redirect_url))
}

async fn logout(session: Session) -> Result<impl IntoResponse, (StatusCode, String)> {
Expand Down
26 changes: 13 additions & 13 deletions server/src/handlers/oembed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,24 +27,23 @@ pub async fn oembed_handler(
if parts.len() >= 2 && parts[0] == "e" {
let token = parts[1];

let project = sqlx::query!(
"SELECT slug, owner_username FROM projects WHERE embed_token = $1",
token
let project: Option<(String, String)> = sqlx::query_as(
"SELECT slug, owner_username FROM projects WHERE embed_token = $1",
)
.bind(token)
.fetch_optional(&state.db)
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;

if let Some(p) = project {
if let Some((slug, owner_username)) = project {
let origin = std::env::var("FRONTEND_URL").unwrap_or("https://trycli.com".to_string());
let embed_src = format!("{}/embed/{}/{}", origin, p.owner_username, p.slug);
let embed_src = format!("{}/embed/{}/{}", origin, owner_username, slug);

return Ok(Json(OEmbedResponse::Rich {
version: "1.0".to_string(),
title: format!("Interactive Demo: {}", p.slug),
// FIX: Clone the username here so it doesn't get moved
author_name: p.owner_username.clone(),
author_url: format!("{}/{}", origin, p.owner_username),
title: format!("Interactive Demo: {}", slug),
author_name: owner_username.clone(),
author_url: format!("{}/{}", origin, owner_username),
provider_name: "TryCLI Studio".to_string(),
provider_url: origin.clone(),
html: format!(
Expand All @@ -62,13 +61,14 @@ pub async fn oembed_handler(
let username = parts[0];
let slug = parts[1];

let exists = sqlx::query!(
"SELECT 1 as exists FROM projects WHERE owner_username = $1 AND slug = $2",
username, slug
let exists: Option<(i32,)> = sqlx::query_as(
"SELECT 1 FROM projects WHERE owner_username = $1 AND slug = $2",
)
.bind(username)
.bind(slug)
.fetch_optional(&state.db)
.await
.unwrap_or(None);
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;

if exists.is_some() {
let origin = std::env::var("FRONTEND_URL").unwrap_or("https://trycli.com".to_string());
Expand Down
Loading