From 5a8e61c2559d8b125325eeffdffa3dc70f836100 Mon Sep 17 00:00:00 2001 From: red binder Date: Mon, 27 Apr 2026 16:37:54 +0200 Subject: [PATCH] A LOT of stuff Basically I removed all the warnings, removed some old APIs no longer in use, added cert verification to DB, im just cool like that. --- Cargo.lock | 224 ++++++++++++++++++++++++--- Cargo.toml | 1 + hooks/pre-commit | 14 ++ setup-hook.sh | 3 + src/account/account.rs | 82 +++++++++- src/graphql/mod.rs | 1 - src/json_api/oauth/generate_token.rs | 6 +- src/json_api/users/mii.rs | 6 +- src/json_api/users/profile.rs | 2 +- src/main.rs | 7 +- src/nnid/agreements.rs | 1 - src/nnid/mapped_ids.rs | 2 +- src/nnid/oauth/generate_token.rs | 1 + src/nnid/people.rs | 7 +- src/nnid/support.rs | 53 ++++++- src/papi/login.rs | 90 ----------- src/papi/mod.rs | 4 - src/papi/user.rs | 113 -------------- 18 files changed, 363 insertions(+), 254 deletions(-) create mode 100755 hooks/pre-commit create mode 100755 setup-hook.sh delete mode 100644 src/papi/login.rs delete mode 100644 src/papi/mod.rs delete mode 100644 src/papi/user.rs diff --git a/Cargo.lock b/Cargo.lock index e636f74..b2857b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,6 +20,7 @@ dependencies = [ "ecdsa", "gxhash", "hex", + "hickory-resolver", "hmac", "juniper", "juniper_rocket", @@ -43,7 +44,7 @@ dependencies = [ "sha2", "sha256", "sqlx", - "thiserror", + "thiserror 2.0.12", "time", "tokio", "tonic", @@ -138,7 +139,7 @@ dependencies = [ "futures-core", "futures-util", "mio", - "socket2", + "socket2 0.5.8", "tokio", "tracing", ] @@ -196,7 +197,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "smallvec", - "socket2", + "socket2 0.5.8", "time", "tracing", "url", @@ -627,7 +628,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-link", + "windows-link 0.1.1", ] [[package]] @@ -762,6 +763,12 @@ dependencies = [ "typenum", ] +[[package]] +name = "data-encoding" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" + [[package]] name = "debugid" version = "0.8.0" @@ -970,6 +977,18 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "enum-as-inner" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -1380,6 +1399,51 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hickory-proto" +version = "0.24.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92652067c9ce6f66ce53cc38d1169daa36e6e7eb7dd3b63b5103bd9d97117248" +dependencies = [ + "async-trait", + "cfg-if", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "idna", + "ipnet", + "once_cell", + "rand 0.8.5", + "thiserror 1.0.69", + "tinyvec", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "hickory-resolver" +version = "0.24.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbb117a1ca520e111743ab2f6688eddee69db4e0ea242545a604dce8a66fd22e" +dependencies = [ + "cfg-if", + "futures-util", + "hickory-proto", + "ipconfig", + "lru-cache", + "once_cell", + "parking_lot", + "rand 0.8.5", + "resolv-conf", + "smallvec", + "thiserror 1.0.69", + "tokio", + "tracing", +] + [[package]] name = "hkdf" version = "0.12.4" @@ -1415,7 +1479,7 @@ checksum = "a56f203cd1c76362b69e3863fd987520ac36cf70a8c92627449b2f64a8cf7d65" dependencies = [ "cfg-if", "libc", - "windows-link", + "windows-link 0.1.1", ] [[package]] @@ -1503,7 +1567,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2", + "socket2 0.5.8", "tokio", "tower-service", "tracing", @@ -1590,7 +1654,7 @@ dependencies = [ "http-body 1.0.1", "hyper 1.6.0", "pin-project-lite", - "socket2", + "socket2 0.5.8", "tokio", "tower-service", "tracing", @@ -1801,6 +1865,19 @@ dependencies = [ "generic-array", ] +[[package]] +name = "ipconfig" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d40460c0ce33d6ce4b0630ad68ff63d6661961c48b6dba35e5a4d81cfb48222" +dependencies = [ + "socket2 0.6.3", + "widestring", + "windows-registry 0.6.1", + "windows-result 0.4.1", + "windows-sys 0.61.2", +] + [[package]] name = "ipnet" version = "2.11.0" @@ -1947,16 +2024,16 @@ dependencies = [ "nom", "percent-encoding", "quoted_printable", - "socket2", + "socket2 0.5.8", "tokio", "url", ] [[package]] name = "libc" -version = "0.2.170" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libm" @@ -1974,6 +2051,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "linux-raw-sys" version = "0.4.15" @@ -2034,6 +2117,15 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "lru-cache" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" +dependencies = [ + "linked-hash-map", +] + [[package]] name = "matchers" version = "0.1.0" @@ -2789,9 +2881,15 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "windows-registry", + "windows-registry 0.4.0", ] +[[package]] +name = "resolv-conf" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" + [[package]] name = "rfc6979" version = "0.4.0" @@ -3191,7 +3289,7 @@ dependencies = [ "rand 0.9.1", "serde", "serde_json", - "thiserror", + "thiserror 2.0.12", "time", "url", "uuid", @@ -3368,6 +3466,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "socket2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + [[package]] name = "spin" version = "0.9.8" @@ -3430,7 +3538,7 @@ dependencies = [ "serde_json", "sha2", "smallvec", - "thiserror", + "thiserror 2.0.12", "tokio", "tokio-stream", "tracing", @@ -3513,7 +3621,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror", + "thiserror 2.0.12", "tracing", "whoami", ] @@ -3552,7 +3660,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror", + "thiserror 2.0.12", "tracing", "whoami", ] @@ -3577,7 +3685,7 @@ dependencies = [ "serde", "serde_urlencoded", "sqlx-core", - "thiserror", + "thiserror 2.0.12", "tracing", "url", ] @@ -3708,13 +3816,33 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + [[package]] name = "thiserror" version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.12", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -3807,7 +3935,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.5.8", "tokio-macros", "windows-sys 0.52.0", ] @@ -3922,7 +4050,7 @@ dependencies = [ "percent-encoding", "pin-project", "prost", - "socket2", + "socket2 0.5.8", "tokio", "tokio-stream", "tower 0.4.13", @@ -4334,6 +4462,12 @@ dependencies = [ "wasite", ] +[[package]] +name = "widestring" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + [[package]] name = "winapi" version = "0.3.9" @@ -4380,24 +4514,50 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + [[package]] name = "windows-registry" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" dependencies = [ - "windows-result", - "windows-strings", + "windows-result 0.3.4", + "windows-strings 0.3.1", "windows-targets 0.53.0", ] +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + [[package]] name = "windows-result" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ - "windows-link", + "windows-link 0.1.1", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link 0.2.1", ] [[package]] @@ -4406,7 +4566,16 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" dependencies = [ - "windows-link", + "windows-link 0.1.1", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link 0.2.1", ] [[package]] @@ -4436,6 +4605,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + [[package]] name = "windows-targets" version = "0.48.5" diff --git a/Cargo.toml b/Cargo.toml index 8d5123e..1e99d34 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,6 +56,7 @@ k256 = "0.13.4" dsa = "0.6.3" openssl = "0.10.78" time = "0.3.47" +hickory-resolver = { version = "0.24", features = ["tokio-runtime"] } diff --git a/hooks/pre-commit b/hooks/pre-commit new file mode 100755 index 0000000..e9db422 --- /dev/null +++ b/hooks/pre-commit @@ -0,0 +1,14 @@ +#!/bin/sh + +echo "running cargo check..." + +cargo check +STATUS=$? + +if [ $STATUS -ne 0 ]; then + echo "cargo check failed, aborting" + exit 1 +fi + +echo "cargo check passed" +exit 0 diff --git a/setup-hook.sh b/setup-hook.sh new file mode 100755 index 0000000..03a61b5 --- /dev/null +++ b/setup-hook.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +cp hooks/pre-commit .git/hooks/ diff --git a/src/account/account.rs b/src/account/account.rs index df198bc..12c759a 100644 --- a/src/account/account.rs +++ b/src/account/account.rs @@ -1,3 +1,4 @@ +#![allow(unused)] use std::io::{Cursor, Write}; use std::ops::{Deref, DerefMut}; // Don't import until required. @@ -60,6 +61,18 @@ pub struct User { pub verification_code: Option, } +#[derive(sqlx::FromRow)] +pub struct CertificateRecord { + pub _hash: Vec, + pub banned: bool, +} + +#[derive(sqlx::FromRow)] +pub struct _CertificatePid { + pub cert_hash: Vec, + pub pid: i32, +} + fn generate_nintendo_hash(pid: i32, text_password: &str) -> String { let mut sha = Sha256::new(); @@ -239,6 +252,65 @@ impl Into } } +pub async fn handle_certificate( + pool: &sqlx::PgPool, + cert: &Certificate, + pid: i32, +) -> Result<(), Errors<'static>> { + let hash = cert.hash(); + + let existing = sqlx::query_as::<_, CertificateRecord>( + "SELECT hash, banned FROM certificates WHERE hash = $1" + ) + .bind(&hash[..]) + .fetch_optional(pool) + .await + .map_err(|_| INVALID_TOKEN_ERRORS)?; + + if let Some(cert_row) = existing { + if cert_row.banned { + return Err(INVALID_TOKEN_ERRORS); + } + + sqlx::query( + "INSERT INTO certificate_pids (cert_hash, pid) + VALUES ($1, $2) + ON CONFLICT DO NOTHING" + ) + .bind(&hash[..]) + .bind(pid) + .execute(pool) + .await + .map_err(|_| INVALID_TOKEN_ERRORS)?; + + } else { + let mut tx = pool.begin().await.map_err(|_| INVALID_TOKEN_ERRORS)?; + + sqlx::query( + "INSERT INTO certificates (hash, banned) + VALUES ($1, false)" + ) + .bind(&hash[..]) + .execute(&mut *tx) + .await + .map_err(|_| INVALID_TOKEN_ERRORS)?; + + sqlx::query( + "INSERT INTO certificate_pids (cert_hash, pid) + VALUES ($1, $2)" + ) + .bind(&hash[..]) + .bind(pid) + .execute(&mut *tx) + .await + .map_err(|_| INVALID_TOKEN_ERRORS)?; + + tx.commit().await.map_err(|_| INVALID_TOKEN_ERRORS)?; + } + + Ok(()) +} + #[async_trait] impl<'r, const FORCE_BEARER_AUTH: bool, const USE_CERT: bool> FromRequest<'r> for Auth @@ -273,7 +345,7 @@ impl<'r, const FORCE_BEARER_AUTH: bool, const USE_CERT: bool> FromRequest<'r> } if USE_CERT { - let cert = request_try!( + let cert_header = request_try!( request .headers() .get("X-Nintendo-Device-Cert") @@ -281,9 +353,13 @@ impl<'r, const FORCE_BEARER_AUTH: bool, const USE_CERT: bool> FromRequest<'r> .ok_or(INVALID_TOKEN_ERRORS) ); - let Some(cert) = Certificate::new(&cert) else { + let Some(cert) = Certificate::new(&cert_header) else { return Outcome::Error((Status::BadGateway, INVALID_TOKEN_ERRORS)); }; + + if let Err(_) = handle_certificate(pool, &cert, user.pid).await { + return Outcome::Error((Status::BadRequest, INVALID_TOKEN_ERRORS)); + } } // let user = User{ @@ -311,7 +387,7 @@ pub struct Certificate { #[br(magic(0x10005u32))] struct OuterCertificate { signature: [u8; 0x3C], - padding: [u8; 0x40], + _padding: [u8; 0x40], data: [u8; 0x100], } diff --git a/src/graphql/mod.rs b/src/graphql/mod.rs index ae5db30..1c916f1 100644 --- a/src/graphql/mod.rs +++ b/src/graphql/mod.rs @@ -1,6 +1,5 @@ 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 std::env; diff --git a/src/json_api/oauth/generate_token.rs b/src/json_api/oauth/generate_token.rs index 0aa7e27..fb14912 100644 --- a/src/json_api/oauth/generate_token.rs +++ b/src/json_api/oauth/generate_token.rs @@ -1,4 +1,4 @@ -use chrono::{DateTime, Duration, NaiveDateTime, Utc}; +use chrono::{Duration, NaiveDateTime, Utc}; use rocket::{get, State}; use rocket::serde::json::Json; use serde::Serialize; @@ -8,7 +8,7 @@ use crate::nnid::oauth::generate_token::token_type::AUTH_TOKEN; use crate::Pool; #[derive(Serialize)] -struct TokenData{ +pub struct TokenData{ token: String, expiry: NaiveDateTime } @@ -17,8 +17,6 @@ struct TokenData{ pub async fn generate_token(pool: &State, auth: Auth) -> Json{ let pool = pool.inner(); - - Json( TokenData{ expiry: Utc::now().naive_utc() + Duration::hours(1), diff --git a/src/json_api/users/mii.rs b/src/json_api/users/mii.rs index 0ba60f0..fa716d3 100644 --- a/src/json_api/users/mii.rs +++ b/src/json_api/users/mii.rs @@ -1,9 +1,9 @@ use rocket::{get, State}; use rocket::serde::json::Json; -use serde::de::IntoDeserializer; +// use serde::de::IntoDeserializer; use sqlx::query; -use crate::account::account::Auth; -use crate::nnid::people::{build_profile, GetOwnProfileData}; +// use crate::account::account::Auth; +// use crate::nnid::people::{build_profile, GetOwnProfileData}; use crate::Pool; diff --git a/src/json_api/users/profile.rs b/src/json_api/users/profile.rs index cd54b13..81f11fb 100644 --- a/src/json_api/users/profile.rs +++ b/src/json_api/users/profile.rs @@ -5,6 +5,6 @@ use crate::nnid::people::{build_profile, GetOwnProfileData}; use crate::Pool; #[get("/api/v2/users/@me/profile")] -pub async fn get_own_profile(pool: &State, auth: Auth) -> Json { +pub async fn get_own_profile(_pool: &State, auth: Auth) -> Json { Json(build_profile(auth.into())) } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 5cf8712..c8e5d99 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,8 @@ use std::env; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; -use std::sync::Arc; use std::time::{SystemTime, UNIX_EPOCH}; use dotenvy::dotenv; use juniper::{EmptyMutation, EmptySubscription}; -use once_cell::sync::Lazy; use rocket::fairing::AdHoc; use rocket::http::{ContentType, Header, Method, Status}; use rocket::{catch, catchers, routes, Request}; @@ -26,7 +24,6 @@ mod data_wrapper; mod grpc; mod graphql; mod email; -mod papi; mod mii_util; mod json_api; @@ -116,7 +113,7 @@ async fn launch() -> _ { EmptySubscription::new()) ) .attach(AdHoc::on_response("org", |_, response| Box::pin(async move { - response.adjoin_header(Header::new("x-organization", "Nintendo")); + response.adjoin_header(Header::new("X-Organization", "Nintendo")); response.adjoin_header(Header::new("X-Nintendo-Date", SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() @@ -155,8 +152,6 @@ async fn launch() -> _ { json_api::users::profile::get_own_profile, json_api::users::mii::get_mii_data_by_pid, json_api::users::delete::delete_account, - papi::login::login, - papi::user::get_user, nnid::people::thing, // graphql::graphiql, // graphql::playground, diff --git a/src/nnid/agreements.rs b/src/nnid/agreements.rs index e4ad256..16d610f 100644 --- a/src/nnid/agreements.rs +++ b/src/nnid/agreements.rs @@ -1,6 +1,5 @@ use std::{env, io}; use std::collections::HashSet; -use gxhash::HashMap; use once_cell::sync::Lazy; use rocket::fs::NamedFile; use rocket::{get, Request}; diff --git a/src/nnid/mapped_ids.rs b/src/nnid/mapped_ids.rs index 1b989a1..652efaa 100644 --- a/src/nnid/mapped_ids.rs +++ b/src/nnid/mapped_ids.rs @@ -6,7 +6,7 @@ use std::io::Cursor; use rocket::{Request, response::{Responder, Response}}; use rocket::http::Header; -use time::{OffsetDateTime, Time}; +use time::OffsetDateTime; use time::format_description::well_known::Rfc2822; #[derive(Serialize)] diff --git a/src/nnid/oauth/generate_token.rs b/src/nnid/oauth/generate_token.rs index 2baf374..f5de9af 100644 --- a/src/nnid/oauth/generate_token.rs +++ b/src/nnid/oauth/generate_token.rs @@ -1,3 +1,4 @@ +#![allow(unused)] use rocket::{post, FromForm, State}; use rocket::form::Form; use serde::{Serialize}; diff --git a/src/nnid/people.rs b/src/nnid/people.rs index ab2c4c6..7d5f721 100644 --- a/src/nnid/people.rs +++ b/src/nnid/people.rs @@ -1,3 +1,4 @@ +#![allow(unused)] use chrono::{NaiveDate, NaiveDateTime}; use gxhash::{gxhash32, gxhash64}; use rocket::{get, post, put, State}; @@ -73,8 +74,8 @@ pub struct Email{ #[derive(Deserialize)] pub struct UpdateMiiData { - name: Box, - primary: crate::xml::YesNoVal, + _name: Box, + _primary: crate::xml::YesNoVal, data: Box, } @@ -109,7 +110,7 @@ pub struct AccountCreationResponseData{ } #[post("/v1/api/people", data="")] -pub async fn create_account(database: &State, data: Xml) -> Result, Option>{ +pub async fn create_account(database: &State, data: Xml) -> Result, Option>>{ let database = database.inner(); // its fine to crash here if we cant get the next pid as that is in my opinion a dead state diff --git a/src/nnid/support.rs b/src/nnid/support.rs index 20c4b6d..cd81a49 100644 --- a/src/nnid/support.rs +++ b/src/nnid/support.rs @@ -1,6 +1,7 @@ use crate::Pool; use crate::error::{Error, Errors}; use chrono::Utc; +use hickory_resolver::TokioAsyncResolver; use rocket::form::Form; use rocket::{FromForm, State, post, put}; @@ -15,8 +16,58 @@ const BAD_CODE_ERROR: Errors = Errors { pub struct ValidateEmailInput { email: String, } + #[post("/v1/api/support/validate/email", data = "")] -pub async fn validate(data: Form) {} +pub async fn validate( + data: Form, +) -> Result<(), Errors<'static>> { + + let email = data.email.trim(); + + // 1. Validate presence + basic format + if email.is_empty() || !email.contains('@') { + return Err(Errors { + error: &[Error { + code: "0103", + message: "Email format is invalid", + }], + }); + } + + // 2. Extract domain safely + let domain = match email.split('@').nth(1) { + Some(d) if !d.is_empty() => d, + _ => { + return Err(Errors { + error: &[Error { + code: "0103", + message: "Email format is invalid", + }], + }); + } + }; + + // 3. DNS resolver + let resolver = TokioAsyncResolver::tokio_from_system_conf() + .map_err(|_| Errors { + error: &[Error { + code: "1126", + message: "DNS resolver initialization failed", + }], + })?; + + // 4. MX lookup + match resolver.mx_lookup(domain).await { + Ok(mx) if mx.iter().next().is_some() => Ok(()), + + _ => Err(Errors { + error: &[Error { + code: "1126", + message: "The domain is not accessible", + }], + }), + } +} #[put("/v1/api/support/email_confirmation//")] pub async fn verify_email( diff --git a/src/papi/login.rs b/src/papi/login.rs deleted file mode 100644 index 45a1df1..0000000 --- a/src/papi/login.rs +++ /dev/null @@ -1,90 +0,0 @@ -use rocket::{post, State}; -use serde::Deserialize; -use serde::Serialize; -use crate::Pool; -use crate::account::account::{User, read_bearer_auth_token}; -use crate::nnid::oauth::generate_token::{create_token, token_type::AUTH_TOKEN, token_type::AUTH_REFRESH_TOKEN}; -use crate::error::{Error, Errors}; -use rocket::serde::json::Json; - -#[derive(Deserialize)] -pub struct LoginRequest { - grant_type: String, - username: Option, - password: Option, - refresh_token: Option, -} - -#[derive(Serialize)] -pub struct LoginResponse { - access_token: String, - token_type: String, - expires_in: i32, - refresh_token: String, -} - -const INVALID_GRANT_TYPE_ERROR: Errors<'static> = Errors { - error: &[Error { - code: "0100", - message: "Invalid grant type", - }] -}; - -const ACCOUNT_ID_OR_PASSWORD_ERRORS: Errors<'static> = Errors { - error: &[Error { - code: "0106", - message: "Invalid account ID or password", - }] -}; - -const INVALID_REFRESH_TOKEN_ERRORS: Errors<'static> = Errors { - error: &[Error { - code: "0107", - message: "Invalid or missing refresh token", - }] -}; - -#[post("/v1/login", data = "")] -pub async fn login(pool: &State, form_data: Json) -> Result, Option>> { - let pool = pool.inner(); - let grant_type = form_data.grant_type.as_str(); - - if grant_type != "password" && grant_type != "refresh_token" { - return Err(Some(INVALID_GRANT_TYPE_ERROR)); - } - - let user: User; - - if grant_type == "password" { - let username = form_data.username.as_ref().ok_or(Some(ACCOUNT_ID_OR_PASSWORD_ERRORS))?; - let password = form_data.password.as_ref().ok_or(Some(ACCOUNT_ID_OR_PASSWORD_ERRORS))?; - - user = User::get_by_username(username, pool) - .await - .ok_or(Some(ACCOUNT_ID_OR_PASSWORD_ERRORS))?; - - if !user.verify_cleartext_password(password).is_some_and(|v| v) { - return Err(Some(ACCOUNT_ID_OR_PASSWORD_ERRORS)); - } - } else { - let refresh_token = form_data.refresh_token.as_ref().ok_or(Some(INVALID_REFRESH_TOKEN_ERRORS))?; - - user = read_bearer_auth_token(pool, refresh_token) - .await - .ok_or(Some(INVALID_REFRESH_TOKEN_ERRORS))?; - } - - if user.account_level < 0 { - return Err(Some(ACCOUNT_ID_OR_PASSWORD_ERRORS)); - } - - let access_token = create_token(pool, user.pid, AUTH_TOKEN, None).await; - let refresh_token = create_token(pool, user.pid, AUTH_REFRESH_TOKEN, None).await; - - Ok(Json(LoginResponse { - access_token, - token_type: "Bearer".to_string(), - expires_in: 3600, - refresh_token, - })) -} diff --git a/src/papi/mod.rs b/src/papi/mod.rs deleted file mode 100644 index 4bc9288..0000000 --- a/src/papi/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -#[deprecated(note="please use the upcoming api instead of this")] -pub mod user; -#[deprecated(note="please use the upcoming api instead of this")] -pub mod login; diff --git a/src/papi/user.rs b/src/papi/user.rs deleted file mode 100644 index ccfda69..0000000 --- a/src/papi/user.rs +++ /dev/null @@ -1,113 +0,0 @@ -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, -} - -#[derive(serde::Serialize)] -struct TimezoneInfo { - name: String, -} - -#[derive(serde::Serialize)] -struct MiiInfo { - data: String, - name: String, - image_url: String, -} - -#[derive(serde::Serialize)] -struct FlagsInfo { - marketing: bool, -} - -#[derive(serde::Serialize)] -struct ConnectionsInfo { - discord: DiscordInfo, - stripe: StripeInfo, -} - -#[derive(serde::Serialize)] -struct DiscordInfo { - id: Option, -} - -#[derive(serde::Serialize)] -struct StripeInfo { - tier_name: Option, - tier_level: Option, -} - -#[derive(serde::Serialize)] -struct UserInfoResponse { - deleted: bool, - access_level: i32, - server_access_level: String, - pid: i32, - creation_date: chrono::NaiveDateTime, - updated: chrono::NaiveDateTime, - username: String, - birthdate: chrono::NaiveDate, - gender: String, - country: String, - email: EmailInfo, - timezone: TimezoneInfo, - mii: MiiInfo, - flags: FlagsInfo, - connections: ConnectionsInfo, -} - -#[get("/v1/user")] -pub async fn get_user(auth: Auth) -> Json { - let user = auth.0; - - Json(UserInfoResponse { - deleted: false, - access_level: user.account_level, - server_access_level: "test".to_string(), - pid: user.pid, - creation_date: user.creation_date, - updated: user.updated, - username: user.username.clone(), - birthdate: user.birthdate, - gender: user.gender.clone(), - country: user.country.clone(), - email: EmailInfo { - address: user.email.clone(), - }, - timezone: TimezoneInfo { - name: user.timezone.clone(), - }, - mii: MiiInfo { - data: user.mii_data.clone(), - name: { - let cleaned = user.mii_data.replace('\n', "").replace('\r', ""); - mii::MiiData::read(&cleaned) - .map(|v| v.name) - .unwrap_or_else(|| "INVALID".to_string()) - }, - image_url: format!("https://{}/mii/{}/normal_face.png", &CDN_URL.to_string(), user.pid), - }, - flags: FlagsInfo { - marketing: user.marketing_allowed, - }, - connections: ConnectionsInfo { - discord: DiscordInfo { - id: None, - }, - stripe: StripeInfo { - tier_name: None, - tier_level: None, - }, - }, - }) -}