From 91882fe4aeca426ab3aee57021346895446d92a9 Mon Sep 17 00:00:00 2001 From: BloxerHD018 Date: Tue, 23 Jun 2026 19:36:04 +0100 Subject: [PATCH] Move Generate Token to Backend --- .env.example | 8 +++++ Cargo.toml | 2 ++ src/api/mod.rs | 8 +++++ src/api/v1/login.rs | 78 +++++++++++++++++++++++++++++++++++++++++++++ src/api/v1/mod.rs | 8 +++++ src/main.rs | 3 ++ static/js/login.js | 13 ++++---- 7 files changed, 113 insertions(+), 7 deletions(-) create mode 100644 .env.example create mode 100644 src/api/mod.rs create mode 100644 src/api/v1/login.rs create mode 100644 src/api/v1/mod.rs diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..d717be3 --- /dev/null +++ b/.env.example @@ -0,0 +1,8 @@ +# Specify the host and port the website will be hosted on, defaults to these +APP_HOST=0.0.0.0 +APP_PORT=80 + +# Account Client and Secret +# Token generation will always fail if no secret is specified +ACCOUNT_CLIENT_ID=account +ACCOUNT_CLIENT_SECRET=secret diff --git a/Cargo.toml b/Cargo.toml index c678117..89d8b58 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,5 +6,7 @@ edition = "2024" [dependencies] axum = "0.8.9" dotenvy = "0.15.7" +reqwest = { version = "0.13.4", features = ["json", "form"] } +serde = { version = "1.0.228", features = ["derive"] } tokio = { version = "1.52.3", features = ["full"] } tower-http = { version = "0.7.0", features = ["fs"] } diff --git a/src/api/mod.rs b/src/api/mod.rs new file mode 100644 index 0000000..04756ed --- /dev/null +++ b/src/api/mod.rs @@ -0,0 +1,8 @@ +use axum::Router; + +pub mod v1; + +pub fn router() -> Router { + Router::new() + .nest("/v1", v1::router()) +} diff --git a/src/api/v1/login.rs b/src/api/v1/login.rs new file mode 100644 index 0000000..aa7ff4a --- /dev/null +++ b/src/api/v1/login.rs @@ -0,0 +1,78 @@ +use std::env; +use axum::{Router, Json}; +use axum::routing::post; +use reqwest::StatusCode; +use reqwest::Client; +use serde::{Serialize, Deserialize}; + +#[derive(Deserialize)] +struct GenerateTokenRequest { + username: String, + password: String, +} + +#[derive(Serialize)] +struct TokenRequest { + grant_type: String, + username: String, + password: String, + client_id: String, + client_secret: String, +} + +#[derive(Serialize, Deserialize)] +struct TokenResponse { + access_token: String, + token_type: String, + expires_in: i64, +} + +pub fn router() -> Router { + Router::new() + .route("/login/generate_token", post(gen_token)) +} + +async fn gen_token(Json(payload): Json) -> Result, StatusCode> { + let client = Client::new(); + + let client_id = env::var("ACCOUNT_CLIENT_ID").unwrap_or("account".into()); + let client_secret = match env::var("ACCOUNT_CLIENT_SECRET") { + Ok(secret) => secret, + Err(_) => { + println!("No account client secret specified - Token generation will always fail"); + return Err(StatusCode::INTERNAL_SERVER_ERROR) + }, + }; + + let body = TokenRequest { + grant_type: "password".into(), + username: payload.username, + password: payload.password, + client_id, + client_secret, + }; + + let response = client + .post(format!("{}/api/v2/oauth2/generate_token", "https://account.spfn.net")) + .form(&body) + .send() + .await; + + match response { + Ok(res) => { + let body = res.json::().await; + + match body { + Ok(b) => return Ok(Json(b)), + Err(_) => return Err(StatusCode::BAD_GATEWAY) + }; + }, + Err(e) => match e.status() { + Some(code) => return Err(code), + None => { + println!("[/api/v1/login/generate_token] No status error returned from upstream: {}", e); + return Err(StatusCode::BAD_GATEWAY); + } + } + } +} diff --git a/src/api/v1/mod.rs b/src/api/v1/mod.rs new file mode 100644 index 0000000..c7b2062 --- /dev/null +++ b/src/api/v1/mod.rs @@ -0,0 +1,8 @@ +use axum::Router; + +pub mod login; + +pub fn router() -> Router { + Router::new() + .merge(login::router()) +} diff --git a/src/main.rs b/src/main.rs index fe9c2ed..3f2dacb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,8 @@ use axum::Router; use tower_http::services::ServeDir; use dotenvy::dotenv; +mod api; + #[tokio::main] async fn main() { dotenv().ok(); @@ -11,6 +13,7 @@ async fn main() { let port = env::var("APP_PORT").unwrap_or("80".into()); let app = Router::new() + .nest("/api", api::router()) .fallback_service(ServeDir::new("static")); let listener = tokio::net::TcpListener::bind(format!("{}:{}", host, port)) diff --git a/static/js/login.js b/static/js/login.js index 4456f3c..c0a2979 100644 --- a/static/js/login.js +++ b/static/js/login.js @@ -12,18 +12,17 @@ function loginError(message, code = null) { } async function generateToken(username, password) { - const credentials = btoa(`${username} ${password}`); - - const body = `grant_type=password&username=${username}&password=${password}&client_id=website` - let response; try { - response = await fetch("https://account.spfn.net/api/v2/oauth2/generate_token", { + response = await fetch("/api/v1/login/generate_token", { method: "POST", headers: { - "Content-Type": "application/x-www-form-urlencoded" + "Content-Type": "application/json", }, - body, + body: JSON.stringify({ + username, + password, + }), }) } catch (err) { loginError(`Internal Server Error: ${err.message}`)