Move Generate Token to Backend

This commit is contained in:
BloxerHD 2026-06-23 19:36:04 +01:00
commit 91882fe4ae
Signed by: bloxerhd018
SSH key fingerprint: SHA256:ItSfcfhpXlfkMK3uQSDYW5X3XKm0hbHWQqWcCK57px8
7 changed files with 113 additions and 7 deletions

8
.env.example Normal file
View file

@ -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

View file

@ -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"] }

8
src/api/mod.rs Normal file
View file

@ -0,0 +1,8 @@
use axum::Router;
pub mod v1;
pub fn router() -> Router {
Router::new()
.nest("/v1", v1::router())
}

78
src/api/v1/login.rs Normal file
View file

@ -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<GenerateTokenRequest>) -> Result<Json<TokenResponse>, 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::<TokenResponse>().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);
}
}
}
}

8
src/api/v1/mod.rs Normal file
View file

@ -0,0 +1,8 @@
use axum::Router;
pub mod login;
pub fn router() -> Router {
Router::new()
.merge(login::router())
}

View file

@ -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))

View file

@ -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}`)