From aef3ba85ae3d5ac44d01e861a4cf0ac7f7fc5aa8 Mon Sep 17 00:00:00 2001 From: Andrea Toska Date: Mon, 12 May 2025 09:37:39 +0200 Subject: [PATCH 1/9] commiting before i cause any irreprable harm --- src/graphql/mod.rs | 94 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 92 insertions(+), 2 deletions(-) diff --git a/src/graphql/mod.rs b/src/graphql/mod.rs index 86655d5..ee4b8d6 100644 --- a/src/graphql/mod.rs +++ b/src/graphql/mod.rs @@ -2,11 +2,28 @@ use chrono::NaiveDateTime; use juniper::{graphql_object, EmptyMutation, EmptySubscription, GraphQLObject, RootNode}; use rocket::response::content::RawHtml; use rocket::State; +use rocket::request::{FromRequest, Outcome, Request}; +use rocket::http::Status; // use crate::account::account::{read_basic_auth_token, read_bearer_auth_token}; use crate::nnid::oauth::TokenData; use crate::Pool; +#[rocket::async_trait] +impl<'r> FromRequest<'r> for Context { + type Error = (); + async fn from_request(req: &'r Request<'_>) -> Outcome { + let pool = req.rocket().state::().cloned().unwrap(); // assume Pool is managed as state + + // Grab API key from header + let api_key = req.headers().get_one("X-API-Key").map(|s| s.to_string()); + + Outcome::Success(Context { + pool, + api_key, + }) + } +} pub type Schema = RootNode< 'static, @@ -16,8 +33,12 @@ pub type Schema = RootNode< >; -pub struct Context(pub Pool); -impl juniper::Context for Context{} +pub struct Context { + pub pool: Pool, + pub api_key: Option, +} +impl juniper::Context for Context {} + #[derive(GraphQLObject)] #[graphql(description = "Data inside of a token")] @@ -27,6 +48,16 @@ struct TokenInfo { title_id: Option } +#[derive(GraphQLObject)] +#[graphql(description = "User information from a token")] +struct UserInfo { + username: String, + account_level: i32, + nex_password: String, + mii_data: String, +} + + pub struct Query; #[graphql_object] @@ -56,6 +87,65 @@ impl Query { }) } + async fn user_from_token( + token_data: String, + context: &Context, + ) -> Option { + let data = match TokenData::decode(&token_data) { + Some(data) => data, + None => { + eprintln!("Failed to decode token"); + return None; + } + }; + + let user = match sqlx::query!( + "SELECT username, account_level, nex_password, mii_data FROM users WHERE pid = $1", + data.pid + ) + .fetch_one(&context.0) + .await + .ok() { + Some(user) => user, + None => { + eprintln!("No user found for PID {}", data.pid); + return None; + } + }; + + Some(UserInfo { + username: user.username, + account_level: user.account_level, + nex_password: user.nex_password, + mii_data: user.mii_data.replace('\n', "").replace('\r', ""), + }) + } + + async fn user_by_pid(pid: i32, context: &Context) -> Option { + // Expected API key (could also be in an env var) + const EXPECTED_KEY: &str = "your-secret-api-key"; + + if context.api_key.as_deref() != Some(EXPECTED_KEY) { + eprintln!("Rejected request due to missing or invalid API key"); + return None; + } + + let user = sqlx::query!( + "SELECT username, account_level, nex_password, mii_data FROM users WHERE pid = $1", + pid + ) + .fetch_one(&context.pool) + .await + .ok()?; + + Some(UserInfo { + username: user.username, + account_level: user.account_level, + nex_password: user.nex_password, + mii_data: user.mii_data, + }) + } + } From 0b1503660fa3f1a060b208cd682f1c60c5fe9133 Mon Sep 17 00:00:00 2001 From: Andrea Toska Date: Mon, 12 May 2025 10:11:21 +0200 Subject: [PATCH 2/9] no harm caused hopefully --- src/graphql/mod.rs | 57 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 45 insertions(+), 12 deletions(-) diff --git a/src/graphql/mod.rs b/src/graphql/mod.rs index ee4b8d6..63b22a2 100644 --- a/src/graphql/mod.rs +++ b/src/graphql/mod.rs @@ -4,10 +4,16 @@ use rocket::response::content::RawHtml; use rocket::State; use rocket::request::{FromRequest, Outcome, Request}; use rocket::http::Status; +use std::env; +use once_cell::sync::Lazy; // use crate::account::account::{read_basic_auth_token, read_bearer_auth_token}; use crate::nnid::oauth::TokenData; use crate::Pool; +pub static API_KEY: Lazy = Lazy::new(|| { + env::var("GRAPHQL_API_KEY").expect("GRAPHQL_API_KEY not set") +}); + #[rocket::async_trait] impl<'r> FromRequest<'r> for Context { type Error = (); @@ -57,6 +63,15 @@ struct UserInfo { mii_data: String, } +#[derive(GraphQLObject)] +#[graphql(description = "User information from a username")] +struct UserInfoWithPId { + username: String, + account_level: i32, + nex_password: String, + mii_data: String, + pid: i32, +} pub struct Query; @@ -78,7 +93,7 @@ impl Query { "select * from tokens where pid = $1 and token_id = $2 and random = $3", data.pid, data.token_id, data.random ). - fetch_one(&context.0).await.ok()?; + fetch_one(&context.pool).await.ok()?; Some(TokenInfo{ pid: data.pid, @@ -103,7 +118,7 @@ impl Query { "SELECT username, account_level, nex_password, mii_data FROM users WHERE pid = $1", data.pid ) - .fetch_one(&context.0) + .fetch_one(&context.pool) .await .ok() { Some(user) => user, @@ -122,11 +137,8 @@ impl Query { } async fn user_by_pid(pid: i32, context: &Context) -> Option { - // Expected API key (could also be in an env var) - const EXPECTED_KEY: &str = "your-secret-api-key"; - - if context.api_key.as_deref() != Some(EXPECTED_KEY) { - eprintln!("Rejected request due to missing or invalid API key"); + if context.api_key.as_deref() != Some(&*API_KEY) { + eprintln!("Rejected request: invalid API key"); return None; } @@ -146,7 +158,28 @@ impl Query { }) } + async fn user_by_username(username: String, context: &Context) -> Option { + if context.api_key.as_deref() != Some(&*API_KEY) { + eprintln!("Rejected request: invalid API key"); + return None; + } + let user = sqlx::query!( + "SELECT pid, username, account_level, nex_password, mii_data FROM users WHERE username = $1", + username, + ) + .fetch_one(&context.pool) + .await + .ok()?; + + Some(UserInfoWithPId { + username: user.username, + account_level: user.account_level, + nex_password: user.nex_password, + mii_data: user.mii_data, + pid: user.pid, + }) + } } @@ -179,18 +212,18 @@ pub fn playground() -> RawHtml { #[rocket::get("/graphql?")] pub async fn get_graphql( - db: &State, request: juniper_rocket::GraphQLRequest, schema: &State, + context: Context ) -> juniper_rocket::GraphQLResponse { - request.execute(schema, db).await + request.execute(schema, &context).await } #[rocket::post("/graphql", data = "")] pub async fn post_graphql( - db: &State, request: juniper_rocket::GraphQLRequest, schema: &State, + context: Context ) -> juniper_rocket::GraphQLResponse { - request.execute(schema, db).await -} \ No newline at end of file + request.execute(schema, &context).await +} From e28cf8a21710ff769708917616dc66784767c8a3 Mon Sep 17 00:00:00 2001 From: Andrea Toska Date: Mon, 12 May 2025 10:11:31 +0200 Subject: [PATCH 3/9] remove unused import --- src/graphql/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/graphql/mod.rs b/src/graphql/mod.rs index 63b22a2..9b5d593 100644 --- a/src/graphql/mod.rs +++ b/src/graphql/mod.rs @@ -3,7 +3,6 @@ use juniper::{graphql_object, EmptyMutation, EmptySubscription, GraphQLObject, R use rocket::response::content::RawHtml; use rocket::State; use rocket::request::{FromRequest, Outcome, Request}; -use rocket::http::Status; use std::env; use once_cell::sync::Lazy; // use crate::account::account::{read_basic_auth_token, read_bearer_auth_token}; From 130b7a6de7d52f5767da25cbbd2e301ac7e67228 Mon Sep 17 00:00:00 2001 From: Andrea Toska Date: Mon, 12 May 2025 10:31:17 +0200 Subject: [PATCH 4/9] fix GQL init --- src/main.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index b8737b0..273757d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -137,7 +137,10 @@ async fn launch() -> _ { .manage(S3ClientState { client: Arc::new(s3_client), }) - .manage(graphql::Context(graph_pool)) + .manage(graphql::Context { + pool: graph_pool, + api_key: None, // or Some(...) if you’re preloading a static API key + }) .manage(Schema::new( Query, EmptyMutation::new(), From 0dd4bfa7de7b8117d6954206dd3517b96f1faf91 Mon Sep 17 00:00:00 2001 From: Andrea Toska Date: Mon, 12 May 2025 10:35:17 +0200 Subject: [PATCH 5/9] run cargo fix --- src/nnid/people.rs | 4 ---- src/nnid/support.rs | 1 - src/papi/login.rs | 1 - 3 files changed, 6 deletions(-) diff --git a/src/nnid/people.rs b/src/nnid/people.rs index 0eaef7e..e0f1625 100644 --- a/src/nnid/people.rs +++ b/src/nnid/people.rs @@ -1,8 +1,5 @@ use std::env; -use std::fs; -use std::fs::File; use std::io::Write; -use std::path::Path; use chrono::{NaiveDate, NaiveDateTime}; use gxhash::{gxhash32, gxhash64}; use minio::s3::builders::{ObjectContent}; @@ -23,7 +20,6 @@ use crate::email::send_verification_email; use rand::Rng; use mii::{get_image_png, get_image_tga}; use minio::s3::client::Client; -use minio::s3::args::PutObjectArgs; use std::sync::Arc; const DATABASE_ERROR: Errors = Errors{ diff --git a/src/nnid/support.rs b/src/nnid/support.rs index d3e1a5d..28c749c 100644 --- a/src/nnid/support.rs +++ b/src/nnid/support.rs @@ -1,7 +1,6 @@ use rocket::{State, post, FromForm, put}; use crate::Pool; use rocket::form::Form; -use crate::email::send_verification_email; use crate::error::{Error, Errors}; use chrono::Utc; diff --git a/src/papi/login.rs b/src/papi/login.rs index 7e228e7..45a1df1 100644 --- a/src/papi/login.rs +++ b/src/papi/login.rs @@ -1,5 +1,4 @@ use rocket::{post, State}; -use rocket::form::Form; use serde::Deserialize; use serde::Serialize; use crate::Pool; From cc20eac02b8f4d1e08a0d3abba6a049881c80da8 Mon Sep 17 00:00:00 2001 From: Andrea Toska Date: Mon, 12 May 2025 11:26:14 +0200 Subject: [PATCH 6/9] make graphQL playground private and add CDN URL variable --- src/graphql/mod.rs | 20 ++++++++++---------- src/main.rs | 4 ++++ src/papi/user.rs | 8 +++++++- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/graphql/mod.rs b/src/graphql/mod.rs index 9b5d593..363d008 100644 --- a/src/graphql/mod.rs +++ b/src/graphql/mod.rs @@ -198,16 +198,16 @@ impl Mutation { } */ -#[rocket::get("/graphiql")] -pub fn graphiql() -> RawHtml { - juniper_rocket::graphiql_source("/graphql", None) -} - - -#[rocket::get("/playground")] -pub fn playground() -> RawHtml { - juniper_rocket::playground_source("/graphql", None) -} +// #[rocket::get("/graphiql")] +// pub fn graphiql() -> RawHtml { +// juniper_rocket::graphiql_source("/graphql", None) +// } +// +// +// #[rocket::get("/playground")] +// pub fn playground() -> RawHtml { +// juniper_rocket::playground_source("/graphql", None) +// } #[rocket::get("/graphql?")] pub async fn get_graphql( diff --git a/src/main.rs b/src/main.rs index 273757d..d895bdc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -119,6 +119,10 @@ async fn launch() -> _ { env::var("S3_PASSWD").expect("S3_PASSWD not specified").into_boxed_str() ); + pub static CDN_URL: Lazy> = Lazy::new(|| + env::var("CDN_URL").expect("CDN_URL not specified").into_boxed_str() + ); + let s3_client = ClientBuilder::new(S3_URL.clone()) .provider(Some(Box::new(StaticProvider::new(&S3_USER, &S3_PASSWD, None)))) .build() diff --git a/src/papi/user.rs b/src/papi/user.rs index 770f786..ccfda69 100644 --- a/src/papi/user.rs +++ b/src/papi/user.rs @@ -1,7 +1,13 @@ +use std::env; +use once_cell::sync::Lazy; use rocket::{get}; use crate::account::account::{Auth}; use rocket::serde::json::Json; +pub static CDN_URL: Lazy> = Lazy::new(|| + env::var("CDN_URL").expect("CDN_URL not specified").into_boxed_str() +); + #[derive(serde::Serialize)] struct EmailInfo { address: String, @@ -89,7 +95,7 @@ pub async fn get_user(auth: Auth) -> Json { .map(|v| v.name) .unwrap_or_else(|| "INVALID".to_string()) }, - image_url: format!("https://cdn.spfn.cc/mii/{}/normal_face.png", user.pid), + image_url: format!("https://{}/mii/{}/normal_face.png", &CDN_URL.to_string(), user.pid), }, flags: FlagsInfo { marketing: user.marketing_allowed, From 00c875cd6dbd8b2efe7bf484eaea9c8fd2c6c181 Mon Sep 17 00:00:00 2001 From: Andrea Toska Date: Mon, 12 May 2025 11:28:03 +0200 Subject: [PATCH 7/9] remove playground and graphiql from main.rs --- src/main.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index d895bdc..ec2a26c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -185,9 +185,8 @@ async fn launch() -> _ { nnid::mapped_ids::mapped_ids, papi::login::login, papi::user::get_user, - //graphql - graphql::graphiql, - graphql::playground, + // graphql::graphiql, + // graphql::playground, graphql::get_graphql, graphql::post_graphql, ]) From 46a9e17973a2cdbe06b9f521292f019a31ed51dc Mon Sep 17 00:00:00 2001 From: Andrea Toska Date: Thu, 15 May 2025 21:45:37 +0200 Subject: [PATCH 8/9] fix bucket name --- src/nnid/people.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nnid/people.rs b/src/nnid/people.rs index e0f1625..07ecf62 100644 --- a/src/nnid/people.rs +++ b/src/nnid/people.rs @@ -56,7 +56,7 @@ fn get_mii_img_url_path(pid: i32, format: &str) -> String{ } fn get_mii_img_url(pid: i32, format: &str) -> String{ - format!("{}/pn-boss/{}", &*S3_URL_STRING, get_mii_img_url_path(pid, format)) + format!("{}/{}/{}", &*S3_URL_STRING, &*S3_BUCKET, get_mii_img_url_path(pid, format)) } pub async fn generate_s3_images(pid: i32, mii_data: &str) { From 3a7dbde2dde92149063cf7822c133a1ca97a160a Mon Sep 17 00:00:00 2001 From: Andrea Toska Date: Thu, 15 May 2025 22:02:17 +0200 Subject: [PATCH 9/9] remove graphQL context from main.rs --- src/graphql/mod.rs | 12 ++++++------ src/main.rs | 4 ---- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/graphql/mod.rs b/src/graphql/mod.rs index 363d008..4e4a9a0 100644 --- a/src/graphql/mod.rs +++ b/src/graphql/mod.rs @@ -64,12 +64,12 @@ struct UserInfo { #[derive(GraphQLObject)] #[graphql(description = "User information from a username")] -struct UserInfoWithPId { - username: String, - account_level: i32, - nex_password: String, - mii_data: String, - pid: i32, +pub struct UserInfoWithPId { + pub username: String, + pub account_level: i32, + pub nex_password: String, + pub mii_data: String, + pub pid: i32, } pub struct Query; diff --git a/src/main.rs b/src/main.rs index ec2a26c..25882b9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -141,10 +141,6 @@ async fn launch() -> _ { .manage(S3ClientState { client: Arc::new(s3_client), }) - .manage(graphql::Context { - pool: graph_pool, - api_key: None, // or Some(...) if you’re preloading a static API key - }) .manage(Schema::new( Query, EmptyMutation::new(),