This commit is contained in:
parent
8c01e2d5fd
commit
d7c226b5a1
5 changed files with 164 additions and 57 deletions
|
|
@ -64,6 +64,7 @@ pub struct User {
|
|||
pub updated: NaiveDateTime,
|
||||
pub nex_password: String,
|
||||
pub verification_code: Option<i32>,
|
||||
pub eula_version: i32,
|
||||
}
|
||||
|
||||
#[derive(sqlx::FromRow)]
|
||||
|
|
@ -170,6 +171,59 @@ pub async fn read_basic_auth_token(
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn read_hashed_basic_auth_token(
|
||||
connection: &Pool,
|
||||
token: &str,
|
||||
) -> Option<(User, Option<[u8; 32]>)> {
|
||||
let data = match BASE64_STANDARD.decode(token) {
|
||||
Ok(d) => d,
|
||||
Err(e) => {
|
||||
println!("Failed to decode base64: {:?}", e);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
let decoded_token = match String::from_utf8(data) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
println!("Failed to convert decoded bytes to UTF-8 string: {:?}", e);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
let (login_username, login_hash) = match decoded_token.split_once(' ') {
|
||||
Some(parts) => parts,
|
||||
None => {
|
||||
println!("Failed to split hashed basic token into username and hash");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
let user_result = sqlx::query_as!(
|
||||
User,
|
||||
"SELECT * FROM users WHERE username = $1",
|
||||
login_username
|
||||
)
|
||||
.fetch_one(connection)
|
||||
.await;
|
||||
|
||||
let user = match user_result {
|
||||
Ok(u) => u,
|
||||
Err(e) => {
|
||||
println!("Failed to fetch user from database: {:?}", e);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
let password_valid = user.verify_hashed_password(login_hash);
|
||||
|
||||
if password_valid == Some(true) {
|
||||
Some((user, None))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn read_bearer_auth_token(
|
||||
connection: &Pool,
|
||||
token: &str,
|
||||
|
|
@ -307,6 +361,7 @@ impl<'r, const FORCE_BEARER_AUTH: bool, const USE_CERT: bool> FromRequest<'r>
|
|||
|
||||
let data = match auth_type {
|
||||
"Basic" if !FORCE_BEARER_AUTH => read_basic_auth_token(pool, token).await,
|
||||
"HashedBasic" if !FORCE_BEARER_AUTH => read_hashed_basic_auth_token(pool, token).await,
|
||||
"Bearer" => read_bearer_auth_token(pool, token).await,
|
||||
_ => return Outcome::Error((Status::BadRequest, INVALID_TOKEN_ERRORS)),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,37 +1,64 @@
|
|||
use std::{env, io};
|
||||
use std::collections::HashSet;
|
||||
use std::{env, io, fs};
|
||||
use serde::Deserialize;
|
||||
use once_cell::sync::Lazy;
|
||||
use rocket::fs::NamedFile;
|
||||
use rocket::{get, Request};
|
||||
use rocket::http::Status;
|
||||
use rocket::request::{FromRequest, Outcome};
|
||||
use rocket::get;
|
||||
use rocket::response::content::RawXml;
|
||||
use tokio::fs::try_exists;
|
||||
use tokio::sync::RwLock;
|
||||
use rocket::async_trait;
|
||||
use crate::dsresponse::Ds;
|
||||
|
||||
pub static EVIL_AGREEMENT_THING: Lazy<RwLock<HashSet<String>>> = Lazy::new(|| Default::default());
|
||||
// pub static EVIL_AGREEMENT_THING: Lazy<RwLock<HashSet<String>>> = Lazy::new(|| Default::default());
|
||||
|
||||
pub struct CFIP(pub String);
|
||||
// pub struct CFIP(pub String);
|
||||
|
||||
#[async_trait]
|
||||
impl<'r> FromRequest<'r> for CFIP{
|
||||
type Error = ();
|
||||
// #[async_trait]
|
||||
// impl<'r> FromRequest<'r> for CFIP{
|
||||
// type Error = ();
|
||||
|
||||
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
|
||||
match request.headers().get("CF-Connecting-IP").next(){
|
||||
Some(v) => Outcome::Success(Self(v.to_owned())),
|
||||
None => Outcome::Error((Status::ImATeapot, ()))
|
||||
}
|
||||
}
|
||||
// async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
|
||||
// match request.headers().get("CF-Connecting-IP").next(){
|
||||
// Some(v) => Outcome::Success(Self(v.to_owned())),
|
||||
// None => Outcome::Error((Status::ImATeapot, ()))
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Agreement {
|
||||
version: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Agreements {
|
||||
#[serde(rename = "agreement")]
|
||||
agreements: Vec<Agreement>,
|
||||
}
|
||||
|
||||
pub fn get_latest_eula_version() -> Result<String, Box<dyn std::error::Error>> {
|
||||
let mut path = std::env::current_dir()?;
|
||||
path.push("res");
|
||||
path.push("agreement");
|
||||
path.push("DEFAULT.xml");
|
||||
|
||||
let content = fs::read_to_string(path)?;
|
||||
let parsed: Agreements = quick_xml::de::from_str(&content)?;
|
||||
|
||||
parsed
|
||||
.agreements
|
||||
.first()
|
||||
.map(|a| a.version.clone())
|
||||
.ok_or_else(|| "No agreement found in XML".into())
|
||||
}
|
||||
|
||||
pub static LATEST_EULA_VERSION: Lazy<i32> = Lazy::new(|| {
|
||||
get_latest_eula_version()
|
||||
.ok()
|
||||
.and_then(|v| v.parse::<i32>().ok())
|
||||
.unwrap_or(300)
|
||||
});
|
||||
|
||||
#[get("/v1/api/content/agreements/Nintendo-Network-EULA/<lang>/@latest")]
|
||||
pub async fn get_agreement(lang: &str, ip: CFIP) -> io::Result<Ds<RawXml<NamedFile>>>{
|
||||
|
||||
|
||||
pub async fn get_agreement(lang: &str) -> io::Result<Ds<RawXml<NamedFile>>>{
|
||||
let base_path = {
|
||||
// if this crashes then something is wrong with the server setup so crashing here is fine imo
|
||||
let mut path = env::current_dir().unwrap();
|
||||
|
|
@ -42,38 +69,26 @@ pub async fn get_agreement(lang: &str, ip: CFIP) -> io::Result<Ds<RawXml<NamedFi
|
|||
path
|
||||
};
|
||||
|
||||
if EVIL_AGREEMENT_THING.read().await.contains(&ip.0) {
|
||||
let path = {
|
||||
let requested_file_path = {
|
||||
let mut path = base_path.clone();
|
||||
|
||||
path.push(format!("{}.xml", lang));
|
||||
|
||||
path
|
||||
};
|
||||
|
||||
|
||||
if try_exists(&requested_file_path).await.is_ok_and(|v| v == true) {
|
||||
Ok(Ds(RawXml(NamedFile::open(&requested_file_path).await?)))
|
||||
} else {
|
||||
let fallback_path = {
|
||||
let mut path = base_path;
|
||||
|
||||
path.push("EVIL.xml");
|
||||
path.push("DEFAULT.xml");
|
||||
|
||||
path
|
||||
};
|
||||
|
||||
Ok(Ds(RawXml(NamedFile::open(&path).await?)))
|
||||
} else {
|
||||
let requested_file_path = {
|
||||
let mut path = base_path.clone();
|
||||
|
||||
path.push(format!("{}.xml", lang));
|
||||
|
||||
path
|
||||
};
|
||||
|
||||
|
||||
if try_exists(&requested_file_path).await.is_ok_and(|v| v == true) {
|
||||
Ok(Ds(RawXml(NamedFile::open(&requested_file_path).await?)))
|
||||
} else {
|
||||
let fallback_path = {
|
||||
let mut path = base_path;
|
||||
|
||||
path.push("DEFAULT.xml");
|
||||
|
||||
path
|
||||
};
|
||||
|
||||
Ok(Ds(RawXml(NamedFile::open(&fallback_path).await?)))
|
||||
}
|
||||
Ok(Ds(RawXml(NamedFile::open(&fallback_path).await?)))
|
||||
}
|
||||
}
|
||||
|
|
@ -2,9 +2,9 @@
|
|||
use crate::Pool;
|
||||
use crate::account::account::{Auth, DeviceCert, User, link_certificate_to_pid};
|
||||
use crate::error::{Error, Errors};
|
||||
use crate::nnid::agreements::{CFIP, EVIL_AGREEMENT_THING};
|
||||
use crate::nnid::oauth::TokenData;
|
||||
use crate::nnid::oauth::generate_token::token_type::{AUTH_REFRESH_TOKEN, AUTH_TOKEN};
|
||||
use crate::nnid::agreements::LATEST_EULA_VERSION;
|
||||
use crate::xml::Xml;
|
||||
use rocket::form::Form;
|
||||
use rocket::{FromForm, State, post};
|
||||
|
|
@ -30,10 +30,17 @@ const ACCOUNT_BANNED_ERRORS: Errors = Errors {
|
|||
}],
|
||||
};
|
||||
|
||||
const REREAD_EULA_EXTRABANNED_ERRORS: Errors = Errors {
|
||||
const ACCOUNT_TEMPBANNED_ERRORS: Errors = Errors {
|
||||
error: &[Error {
|
||||
code: "0132",
|
||||
message: "Account temporarily banned from server",
|
||||
}],
|
||||
};
|
||||
|
||||
const REREAD_EULA_ERRORS: Errors = Errors {
|
||||
error: &[Error {
|
||||
code: "0109",
|
||||
message: "REREAD THE EULA LOL",
|
||||
message: "The EULA has been updated",
|
||||
}],
|
||||
};
|
||||
#[derive(FromForm)]
|
||||
|
|
@ -117,7 +124,6 @@ pub struct TokenRequestReturnData {
|
|||
pub async fn generate_token(
|
||||
pool: &State<Pool>,
|
||||
data: Form<TokenRequestData<'_>>,
|
||||
ip: CFIP,
|
||||
cert: DeviceCert,
|
||||
) -> Result<Xml<TokenRequestReturnData>, Option<Errors<'static>>> {
|
||||
let pool = pool.inner();
|
||||
|
|
@ -133,14 +139,13 @@ pub async fn generate_token(
|
|||
return Err(Some(ACCOUNT_ID_OR_PASSWORD_ERRORS));
|
||||
}
|
||||
|
||||
if user.eula_version != *LATEST_EULA_VERSION {
|
||||
return Err(Some(REREAD_EULA_ERRORS));
|
||||
}
|
||||
|
||||
if user.account_level < 0 {
|
||||
if user.account_level == -2 {
|
||||
return Err(Some(REREAD_EULA_EXTRABANNED_ERRORS));
|
||||
}
|
||||
if user.account_level == -3 {
|
||||
EVIL_AGREEMENT_THING.write().await.insert(ip.0);
|
||||
|
||||
return Err(Some(REREAD_EULA_EXTRABANNED_ERRORS));
|
||||
return Err(Some(ACCOUNT_TEMPBANNED_ERRORS));
|
||||
}
|
||||
return Err(Some(ACCOUNT_BANNED_ERRORS));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ use rocket::{State, get, post, put};
|
|||
|
||||
const DATABASE_ERROR: Errors = Errors {
|
||||
error: &[Error {
|
||||
code: "9999",
|
||||
code: "2001",
|
||||
message: "Internal server error",
|
||||
}],
|
||||
};
|
||||
|
|
@ -494,5 +494,37 @@ pub async fn change_mii(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[post("/v1/api/people/@me/agreements")]
|
||||
pub async fn thing() {}
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct AgreedEulaData {
|
||||
pub agreement_date: NaiveDateTime,
|
||||
pub country: Box<str>,
|
||||
pub location: Box<str>,
|
||||
pub version: i32
|
||||
}
|
||||
|
||||
#[post("/v1/api/people/@me/agreements", data = "<data>")]
|
||||
pub async fn thing(
|
||||
database: &State<Pool>,
|
||||
auth: Auth<false>,
|
||||
data: Xml<AgreedEulaData>,
|
||||
) -> Result<(), Option<Errors<'static>>> {
|
||||
let db = database.inner();
|
||||
let version = data.version;
|
||||
let pid = auth.pid;
|
||||
println!("eula data: {:?}", data.version);
|
||||
|
||||
let result = sqlx::query!(
|
||||
"UPDATE users SET eula_version = $1 WHERE pid = $2",
|
||||
version,
|
||||
pid
|
||||
)
|
||||
.execute(db)
|
||||
.await;
|
||||
|
||||
if let Err(e) = result {
|
||||
println!("Failed to update EULA version for PID {}: {:?}", pid, e);
|
||||
return Err(Some(DATABASE_ERROR));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue