diff --git a/.env.example b/.env.example index 3fb0605..a045f44 100644 --- a/.env.example +++ b/.env.example @@ -19,6 +19,3 @@ S3_BUCKET=account-rs # Make sure to put a secure AES key here as this encrypts all tokens. ACCOUNT_AES_KEY=abcdef0123456789abcdef0123456789 -# You'll only be using gRPC if you're using Pretendo code but it's still recommended to set something secure here. -GRPC_PASSWORD=123456 - diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 64cd88d..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "grpc-protobufs"] - path = grpc-protobufs - url = https://github.com/PretendoNetwork/grpc-protobufs.git diff --git a/build.rs b/build.rs deleted file mode 100644 index f7a79e0..0000000 --- a/build.rs +++ /dev/null @@ -1,11 +0,0 @@ - -fn main(){ - tonic_build::configure() - .build_server(true) - .build_client(false) - .compile_protos( - &["grpc-protobufs/account/account_service.proto"], - &["grpc-protobufs/account"] - ) - .unwrap(); -} \ No newline at end of file diff --git a/grpc-protobufs b/grpc-protobufs deleted file mode 160000 index 4101111..0000000 --- a/grpc-protobufs +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 410111190ec9f540d60108b70d55a437a6caf68e diff --git a/src/account/account.rs b/src/account/account.rs index 12c759a..8e81069 100644 --- a/src/account/account.rs +++ b/src/account/account.rs @@ -63,7 +63,7 @@ pub struct User { #[derive(sqlx::FromRow)] pub struct CertificateRecord { - pub _hash: Vec, + pub hash: Vec, pub banned: bool, } @@ -252,62 +252,24 @@ impl Into } } -pub async fn handle_certificate( +pub async fn link_certificate_to_pid( 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" + sqlx::query( + "INSERT INTO certificate_pids (cert_hash, pid) + VALUES ($1, $2) + ON CONFLICT DO NOTHING" ) .bind(&hash[..]) - .fetch_optional(pool) + .bind(pid) + .execute(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(()) } @@ -344,24 +306,6 @@ impl<'r, const FORCE_BEARER_AUTH: bool, const USE_CERT: bool> FromRequest<'r> return Outcome::Error((Status::BadRequest, INVALID_TOKEN_ERRORS)); } - if USE_CERT { - let cert_header = request_try!( - request - .headers() - .get("X-Nintendo-Device-Cert") - .next() - .ok_or(INVALID_TOKEN_ERRORS) - ); - - 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{ // nex_password: format!("{:a>16}", user.nex_password), // ..user @@ -371,6 +315,62 @@ impl<'r, const FORCE_BEARER_AUTH: bool, const USE_CERT: bool> FromRequest<'r> } } +pub struct DeviceCert(pub Certificate); + +#[async_trait] +impl<'r> FromRequest<'r> for DeviceCert { + type Error = Errors<'static>; + + async fn from_request(request: &'r Request<'_>) -> Outcome { + let pool: &sqlx::PgPool = request.rocket().state().unwrap(); + + let cert_header = match request.headers().get("X-Nintendo-Device-Cert").next() { + Some(h) => h, + None => return Outcome::Error((Status::BadRequest, INVALID_TOKEN_ERRORS)), + }; + + let cert = match Certificate::new(cert_header) { + Some(c) => c, + None => return Outcome::Error((Status::BadGateway, INVALID_TOKEN_ERRORS)), + }; + + 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); + + let existing = match existing { + Ok(v) => v, + Err(e) => { + println!("certificate query failed: {:?}", e); + return Outcome::Error((Status::InternalServerError, INVALID_TOKEN_ERRORS)); + } + }; + + if let Some(row) = existing { + if row.banned { + return Outcome::Error((Status::Forbidden, INVALID_TOKEN_ERRORS)); + } + } else { + sqlx::query( + "INSERT INTO certificates (hash, banned) + VALUES ($1, false)" + ) + .bind(&hash[..]) + .execute(pool) + .await + .map_err(|_| INVALID_TOKEN_ERRORS); + } + + Outcome::Success(DeviceCert(cert)) + } +} + #[binread] #[br(big)] #[derive(Debug)] diff --git a/src/grpc/mod.rs b/src/grpc/mod.rs deleted file mode 100644 index 992fcb9..0000000 --- a/src/grpc/mod.rs +++ /dev/null @@ -1,105 +0,0 @@ -use crate::Pool; -use crate::grpc::grpc::{ - ExchangeTokenForUserDataRequest, GetNexDataRequest, GetNexDataResponse, GetNexPasswordRequest, - GetNexPasswordResponse, GetUserDataRequest, GetUserDataResponse, UpdatePnidPermissionsRequest, -}; -use once_cell::sync::Lazy; -use std::env; -use tonic::metadata::MetadataMap; -use tonic::{Request, Response, Status, async_trait}; - -/// This module is a legacy module meant for interacting with existing pretendo -/// servers. This will inevitably be removed completely as this is only meant as -/// a stopgap until RNEX is in a fully functional state. - -pub mod grpc { - tonic::include_proto!("account"); -} - -static GRPC_PASSWORD: Lazy> = Lazy::new(|| { - env::var("GRPC_PASSWORD") - .expect("GRPC_PASSWORD not specified") - .into_boxed_str() -}); - -fn verify_grpc_key(meta: &MetadataMap) -> Result<(), Status> { - // req.metadata_mut().insert("x-api-key", API_KEY.clone()); - - let key = meta - .get("x-api-key") - .ok_or(Status::permission_denied("api key missing"))?; - - if key.as_bytes() != GRPC_PASSWORD.as_bytes() { - return Err(Status::permission_denied("GO AWAY")); - } - - Ok(()) -} - -pub struct AccountService(pub Pool); - -#[async_trait] -impl grpc::account_server::Account for AccountService { - async fn exchange_token_for_user_data( - &self, - request: Request, - ) -> Result, Status> { - verify_grpc_key(request.metadata())?; - - Err(Status::unimplemented( - "grpc tecnically isnt supported by account-rs as such no full support is guaranteed(you called a stubbed function)", - )) - } - async fn get_nex_data( - &self, - request: Request, - ) -> Result, Status> { - verify_grpc_key(request.metadata())?; - - Err(Status::unimplemented( - "grpc tecnically isnt supported by account-rs as such no full support is guaranteed(you called a stubbed function)", - )) - } - async fn get_nex_password( - &self, - request: Request, - ) -> Result, Status> { - verify_grpc_key(request.metadata())?; - - let data = request.get_ref(); - - let password = sqlx::query!( - "select nex_password from users where pid = $1", - data.pid as i32 - ) - .fetch_one(&self.0) - .await - .map_err(|_| Status::invalid_argument("No NEX account found"))? - .nex_password; - - // let password_padded = format!("{:a>16}", password); - - Ok(Response::new(GetNexPasswordResponse { password })) - } - async fn update_pnid_permissions( - &self, - request: Request, - ) -> Result, Status> { - verify_grpc_key(request.metadata())?; - - Err(Status::unimplemented( - "grpc tecnically isnt supported by account-rs as such no full support is guaranteed(you called a stubbed function)", - )) - } - - async fn get_user_data( - &self, - request: Request, - ) -> Result, Status> { - verify_grpc_key(request.metadata())?; - - Err(Status::unimplemented( - "grpc tecnically isnt supported by account-rs as such no full support is guaranteed(you called a stubbed function)", - )) - } -} diff --git a/src/main.rs b/src/main.rs index c8e5d99..e645798 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,8 +20,6 @@ mod account; mod error; mod dsresponse; mod data_wrapper; -// #[deprecated] -mod grpc; mod graphql; mod email; mod mii_util; @@ -29,37 +27,6 @@ mod json_api; type Pool = sqlx::Pool; -async fn start_grpc(){ - let act_database_url = env::var("DATABASE_URL").expect("account database url is not set"); - - let pool = PgPoolOptions::new() - .max_connections(5) - .connect(&act_database_url) - .await - .expect("unable to create pool"); - - let grpc_instance = grpc::AccountService(pool); - - let addr: SocketAddr = - SocketAddr::from(( - env::var("ROCKET_ADDRESS").ok() - .map(|v| v.parse().expect("unable to read address")) - .unwrap_or(IpAddr::V4(Ipv4Addr::LOCALHOST)), - 7071 - ) - ); - - - - tokio::spawn(async move{ - Server::builder() - .add_service(grpc::grpc::account_server::AccountServer::new(grpc_instance)) - .serve(addr) - .await - .expect("unable to start grpc server"); - }); - -} #[catch(404)] fn not_found(_req: &Request) -> (Status, (ContentType, RawXml<&'static str>)) { @@ -85,8 +52,6 @@ fn not_found(_req: &Request) -> (Status, (ContentType, RawXml<&'static str>)) { async fn launch() -> _ { dotenv().ok(); - start_grpc().await; - let act_database_url = env::var("DATABASE_URL").expect("account database url is not set"); let pool = PgPoolOptions::new() diff --git a/src/nnid/oauth/generate_token.rs b/src/nnid/oauth/generate_token.rs index f5de9af..26c8775 100644 --- a/src/nnid/oauth/generate_token.rs +++ b/src/nnid/oauth/generate_token.rs @@ -2,7 +2,7 @@ use rocket::{post, FromForm, State}; use rocket::form::Form; use serde::{Serialize}; -use crate::account::account::User; +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::generate_token::token_type::{AUTH_REFRESH_TOKEN, AUTH_TOKEN}; @@ -101,7 +101,7 @@ pub struct TokenRequestReturnData{ } #[post("/v1/api/oauth20/access_token/generate", data="")] -pub async fn generate_token(pool: &State, data: Form>, ip: CFIP) -> Result, Option>>{ +pub async fn generate_token(pool: &State, data: Form>, ip: CFIP, cert: DeviceCert) -> Result, Option>>{ let pool = pool.inner(); let user = User::get_by_username(data.user_id, pool).await @@ -123,6 +123,8 @@ pub async fn generate_token(pool: &State, data: Form> return Err(Some(ACCOUNT_BANNED_ERRORS)); } + link_certificate_to_pid(&pool, &cert.0, user.pid).await?; + let access_token = TokenReturnData::new(user.pid, pool).await; Ok(Xml(TokenRequestReturnData{