From 124916e62cff7e0a79ea7a022b49c2a1aaf962ee Mon Sep 17 00:00:00 2001 From: red binder Date: Tue, 11 Aug 2026 03:34:00 +0200 Subject: [PATCH] parental controls --- ...4f25f0636ec0e7c0beed4cdb627c419e407c4.json | 15 ++ res/email/masterKeyTemplate.html | 190 ++++++++++++++++++ res/email/parentalControlsTemplate.html | 177 ++++++++++++++++ src/email.rs | 67 ++++++ src/main.rs | 2 + src/nnid/support.rs | 105 ++++++++++ 6 files changed, 556 insertions(+) create mode 100644 .sqlx/query-7d5d57a9c0065301382767c85de4f25f0636ec0e7c0beed4cdb627c419e407c4.json create mode 100644 res/email/masterKeyTemplate.html create mode 100644 res/email/parentalControlsTemplate.html diff --git a/.sqlx/query-7d5d57a9c0065301382767c85de4f25f0636ec0e7c0beed4cdb627c419e407c4.json b/.sqlx/query-7d5d57a9c0065301382767c85de4f25f0636ec0e7c0beed4cdb627c419e407c4.json new file mode 100644 index 0000000..758b35a --- /dev/null +++ b/.sqlx/query-7d5d57a9c0065301382767c85de4f25f0636ec0e7c0beed4cdb627c419e407c4.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "\n INSERT INTO device_parental_controls (serial_number, email, updated_at)\n VALUES ($1, $2, NOW())\n ON CONFLICT (serial_number)\n DO UPDATE SET email = EXCLUDED.email, updated_at = NOW()\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Varchar" + ] + }, + "nullable": [] + }, + "hash": "7d5d57a9c0065301382767c85de4f25f0636ec0e7c0beed4cdb627c419e407c4" +} diff --git a/res/email/masterKeyTemplate.html b/res/email/masterKeyTemplate.html new file mode 100644 index 0000000..50f3f9c --- /dev/null +++ b/res/email/masterKeyTemplate.html @@ -0,0 +1,190 @@ + + + + + + + + +
Hello. You have requested a reset of parental controls on your console.
+ + + + +
+ + + + +
+ + + + + + +
  + + + + + + + +
 
+ + + + + + + + + + + + + + + + + + + +
+ + + +
 
+ + + + + + +
  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 
+ Hello. +
 
+ Your email was linked to a Wii U console with serial number {{serial}} for parental controls. +
 
+ You have requested a master key to reset your parental controls PIN. The master key you must enter on your Wii U is: +
 
+ {{mkey}} +
 
+ Please make sure to change your PIN as soon as possible. +
+ The SPFN team +
 
+
 
+
 
+ Note: this email message was auto-generated, please do not respond. For further assistance, please join our Discord server. +
 
+
+
 
+
+
+ + \ No newline at end of file diff --git a/res/email/parentalControlsTemplate.html b/res/email/parentalControlsTemplate.html new file mode 100644 index 0000000..92c1cdb --- /dev/null +++ b/res/email/parentalControlsTemplate.html @@ -0,0 +1,177 @@ + + + + + + + + +
Hello. The console with the serial number {{serial}} has been linked to your email for parental controls.
+ + + + +
+ + + + +
+ + + + + + +
  + + + + + + + +
 
+ + + + + + + + + + + + + + + + + + + +
+ + + +
 
+ + + + + + +
  + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 
+ Hello. +
 
+ Your email was linked to a Wii U console with serial number {{serial}} for parental controls. +
 
+ If you did not authorize this, please contact our team at support@spbr.net. +
 
+ The SPFN team +
 
+
 
+
 
+ Note: this email message was auto-generated, please do not respond. For further assistance, please join our Discord server. +
 
+
+
 
+
+
+ + \ No newline at end of file diff --git a/src/email.rs b/src/email.rs index 4008994..b0ebd9f 100644 --- a/src/email.rs +++ b/src/email.rs @@ -70,3 +70,70 @@ pub async fn send_reset_email(to: &str, pwd: &str, username: &str) -> Result<(), Ok(()) } + +pub async fn send_pc_email(to: &str, serial: &str) -> Result<(), String> { + let smtp_user = env::var("SMTP_USER").map_err(|_| "SMTP_USER not set".to_string())?; + let smtp_pass = env::var("SMTP_PASS").map_err(|_| "SMTP_PASS not set".to_string())?; + let smtp_server = env::var("SMTP_SERVER").map_err(|_| "SMTP_SERVER not set".to_string())?; + + // Load template + let template = fs::read_to_string("res/email/parentalControlsTemplate.html") + .map_err(|e| format!("Failed to read email template: {}", e))?; + + // Replace placeholders + let body = template + .replace("{{serial}}", serial); + + let email = Message::builder() + .from(smtp_user.parse().unwrap()) + .to(to.parse().unwrap()) + .subject("Parental controls confirmation for SPFN") + .header(lettre::message::header::ContentType::TEXT_HTML) + .body(body) + .map_err(|e| e.to_string())?; + + let creds = Credentials::new(smtp_user, smtp_pass); + + let mailer = SmtpTransport::relay(&smtp_server) + .map_err(|e| e.to_string())? + .credentials(creds) + .build(); + + mailer.send(&email).map_err(|e| e.to_string())?; + + Ok(()) +} + +pub async fn send_reset_pin_email(to: &str, serial: &str, master_key: &str) -> Result<(), String> { + let smtp_user = env::var("SMTP_USER").map_err(|_| "SMTP_USER not set".to_string())?; + let smtp_pass = env::var("SMTP_PASS").map_err(|_| "SMTP_PASS not set".to_string())?; + let smtp_server = env::var("SMTP_SERVER").map_err(|_| "SMTP_SERVER not set".to_string())?; + + // Load template + let template = fs::read_to_string("res/email/masterKeyTemplate.html") + .map_err(|e| format!("Failed to read email template: {}", e))?; + + // Replace placeholders + let body = template + .replace("{{serial}}", serial) + .replace("{{mkey}}", master_key); + + let email = Message::builder() + .from(smtp_user.parse().unwrap()) + .to(to.parse().unwrap()) + .subject("Parental controls PIN reset for SPFN") + .header(lettre::message::header::ContentType::TEXT_HTML) + .body(body) + .map_err(|e| e.to_string())?; + + let creds = Credentials::new(smtp_user, smtp_pass); + + let mailer = SmtpTransport::relay(&smtp_server) + .map_err(|e| e.to_string())? + .credentials(creds) + .build(); + + mailer.send(&email).map_err(|e| e.to_string())?; + + Ok(()) +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index cee7cc5..622d3ed 100644 --- a/src/main.rs +++ b/src/main.rs @@ -102,6 +102,8 @@ async fn launch() -> _ { nnid::support::verify_email, nnid::support::resend_email, nnid::support::forgotten_password, + nnid::support::send_parental_controls_pin, + nnid::support::send_forgotten_pin, nnid::people::create_account, nnid::people::get_own_profile, nnid::people::get_device_owner, diff --git a/src/nnid/support.rs b/src/nnid/support.rs index aac67fc..f119ef9 100644 --- a/src/nnid/support.rs +++ b/src/nnid/support.rs @@ -220,5 +220,110 @@ pub async fn forgotten_password( UNAUTHORIZED_DEVICE_ERROR })?; + Ok(()) +} + +pub struct DeviceSerial(pub String); + +#[rocket::async_trait] +impl<'r> FromRequest<'r> for DeviceSerial { + type Error = Errors<'static>; + + async fn from_request(req: &'r Request<'_>) -> request::Outcome { + match req.headers().get_one("x-nintendo-serial-number") { + Some(val) if !val.trim().is_empty() => { + request::Outcome::Success(DeviceSerial(val.to_string())) + } + _ => request::Outcome::Error(( + Status::BadRequest, + Errors { + error: &[Error { + code: "0130", + message: "Serial not found", + }], + }, + )), + } + } +} + +#[get("/v1/api/support/send_confirmation/pin/")] +pub async fn send_parental_controls_pin( + database: &State, + serial: DeviceSerial, + email: &str, +) -> Result<(), Errors<'static>> { + let db = database.inner(); + let serial_number = serial.0; + let email = email.trim().to_lowercase(); + + if email.is_empty() || !email.contains('@') { + return Err(Errors { + error: &[Error { + code: "0103", + message: "Email format is invalid", + }], + }); + } + + let db_result = sqlx::query!( + " + INSERT INTO device_parental_controls (serial_number, email, updated_at) + VALUES ($1, $2, NOW()) + ON CONFLICT (serial_number) + DO UPDATE SET email = EXCLUDED.email, updated_at = NOW() + ", + serial_number, + email + ) + .execute(db) + .await; + + if let Err(e) = db_result { + eprintln!("Failed to associate serial {serial_number} with email {email}: {e}"); + return Err(Errors { + error: &[Error { + code: "0130", + message: "PID has not been registered yet", + }], + }); + } + + crate::email::send_pc_email(&email, &serial_number) + .await + .map_err(|e| { + eprintln!("Failed to send parental controls email to {email}: {e}"); + Errors { + error: &[Error { + code: "0130", + message: "PID has not been registered yet", + }], + } + })?; + + Ok(()) +} + +#[get("/v1/api/support/send_forgotten/pin//")] +pub async fn send_forgotten_pin( + serial: DeviceSerial, + email: &str, + master_key: &str, +) -> Result<(), Errors<'static>> { + let serial_number = serial.0; + let email = email.trim().to_lowercase(); + + crate::email::send_reset_pin_email(&email, &serial_number, &master_key) + .await + .map_err(|e| { + eprintln!("Failed to send parental controls email to {email}: {e}"); + Errors { + error: &[Error { + code: "0130", + message: "PID has not been registered yet", + }], + } + })?; + Ok(()) } \ No newline at end of file