parental controls

This commit is contained in:
red binder 2026-08-11 03:34:00 +02:00
commit 124916e62c
6 changed files with 556 additions and 0 deletions

View file

@ -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(())
}

View file

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

View file

@ -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<Self, Self::Error> {
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/<email>")]
pub async fn send_parental_controls_pin(
database: &State<Pool>,
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/<email>/<master_key>")]
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(())
}