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