A LOT of stuff
All checks were successful
Build and Test / account (push) Successful in 7m48s

Basically I removed all the warnings, removed some old APIs no longer in use, added cert verification to DB, im just cool like that.
This commit is contained in:
red binder 2026-04-27 16:37:54 +02:00
commit 5a8e61c255
18 changed files with 363 additions and 254 deletions

224
Cargo.lock generated
View file

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

View file

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

14
hooks/pre-commit Executable file
View file

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

3
setup-hook.sh Executable file
View file

@ -0,0 +1,3 @@
#!/bin/sh
cp hooks/pre-commit .git/hooks/

View file

@ -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<i32>,
}
#[derive(sqlx::FromRow)]
pub struct CertificateRecord {
pub _hash: Vec<u8>,
pub banned: bool,
}
#[derive(sqlx::FromRow)]
pub struct _CertificatePid {
pub cert_hash: Vec<u8>,
pub pid: i32,
}
fn generate_nintendo_hash(pid: i32, text_password: &str) -> String {
let mut sha = Sha256::new();
@ -239,6 +252,65 @@ impl<const FORCE_BEARER_AUTH: bool, const USE_CERT: bool> Into<User>
}
}
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<FORCE_BEARER_AUTH, USE_CERT>
@ -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],
}

View file

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

View file

@ -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<Pool>, auth: Auth<false>) -> Json<TokenData>{
let pool = pool.inner();
Json(
TokenData{
expiry: Utc::now().naive_utc() + Duration::hours(1),

View file

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

View file

@ -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<Pool>, auth: Auth<true>) -> Json<GetOwnProfileData> {
pub async fn get_own_profile(_pool: &State<Pool>, auth: Auth<true>) -> Json<GetOwnProfileData> {
Json(build_profile(auth.into()))
}

View file

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

View file

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

View file

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

View file

@ -1,3 +1,4 @@
#![allow(unused)]
use rocket::{post, FromForm, State};
use rocket::form::Form;
use serde::{Serialize};

View file

@ -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<str>,
primary: crate::xml::YesNoVal,
_name: Box<str>,
_primary: crate::xml::YesNoVal,
data: Box<str>,
}
@ -109,7 +110,7 @@ pub struct AccountCreationResponseData{
}
#[post("/v1/api/people", data="<data>")]
pub async fn create_account(database: &State<Pool>, data: Xml<AccountCreationData>) -> Result<Xml<AccountCreationResponseData>, Option<Errors>>{
pub async fn create_account(database: &State<Pool>, data: Xml<AccountCreationData>) -> Result<Xml<AccountCreationResponseData>, Option<Errors<'_>>>{
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

View file

@ -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 = "<data>")]
pub async fn validate(data: Form<ValidateEmailInput>) {}
pub async fn validate(
data: Form<ValidateEmailInput>,
) -> 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/<pid>/<code>")]
pub async fn verify_email(

View file

@ -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<String>,
password: Option<String>,
refresh_token: Option<String>,
}
#[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 = "<form_data>")]
pub async fn login(pool: &State<Pool>, form_data: Json<LoginRequest>) -> Result<Json<LoginResponse>, Option<Errors<'static>>> {
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,
}))
}

View file

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

View file

@ -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<Box<str>> = 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<String>,
}
#[derive(serde::Serialize)]
struct StripeInfo {
tier_name: Option<String>,
tier_level: Option<i32>,
}
#[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<false>) -> Json<UserInfoResponse> {
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,
},
},
})
}