This commit is contained in:
parent
fa063d78fb
commit
124916e62c
6 changed files with 556 additions and 0 deletions
15
.sqlx/query-7d5d57a9c0065301382767c85de4f25f0636ec0e7c0beed4cdb627c419e407c4.json
generated
Normal file
15
.sqlx/query-7d5d57a9c0065301382767c85de4f25f0636ec0e7c0beed4cdb627c419e407c4.json
generated
Normal file
|
|
@ -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"
|
||||
}
|
||||
190
res/email/masterKeyTemplate.html
Normal file
190
res/email/masterKeyTemplate.html
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" lang="en">
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta name="color-scheme" content="light dark">
|
||||
<meta http-equiv="Content-Type" content="text/html charset=UTF-8" />
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;700&display=swap');
|
||||
|
||||
:root {
|
||||
color-scheme: light dark;
|
||||
supported-color-schemes:light dark;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
body.email-body,
|
||||
table.centerer,
|
||||
table.wrapper {
|
||||
background-color: #FFFFFF !important;
|
||||
color: #000000 !important;
|
||||
}
|
||||
table.card {
|
||||
background-color: #B60000 !important;
|
||||
}
|
||||
span.shoutout {
|
||||
color: #FFB3B3 !important;
|
||||
}
|
||||
td.confirm-link {
|
||||
background-color: #FF4D4D !important;
|
||||
}
|
||||
td.confirm-code {
|
||||
background-color: #FFB3B3 !important;
|
||||
color: #660000 !important;
|
||||
}
|
||||
td.notice {
|
||||
color: #FF4D4D !important;
|
||||
}
|
||||
td.notice a {
|
||||
color: #B60000 !important;
|
||||
}
|
||||
img.logo {
|
||||
content: url("https://spfn.spbr.net/res/img/spfn.png") !important;
|
||||
}
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body.email-body,
|
||||
table.centerer,
|
||||
table.wrapper {
|
||||
background-color: #3B1B1B !important;
|
||||
color: #FFFFFF !important;
|
||||
}
|
||||
table.card {
|
||||
background-color: #4A2323 !important;
|
||||
}
|
||||
span.shoutout {
|
||||
color: #FF9999 !important;
|
||||
}
|
||||
td.confirm-link {
|
||||
background-color: #B60000 !important;
|
||||
}
|
||||
td.confirm-code {
|
||||
background-color: #652323 !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
td.notice {
|
||||
color: #C18989 !important;
|
||||
}
|
||||
td.notice a {
|
||||
color: #F5C1C1 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="email-body" bgcolor="#1B1F3B" style="margin-left: 0; margin-right: 0; margin-top: 0; margin-bottom: 0; padding-left: 0; padding-right: 0; padding-top: 0; padding-bottom: 0; font-family: Poppins, Arial, Helvetica, sans-serif;">
|
||||
<div style="display:none;">Hello. You have requested a reset of parental controls on your console.</div>
|
||||
<table class="centerer" bgcolor="#1B1F3B" border="0" cellpadding="0" cellspacing="0" height="100%" width="100%">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<table class="wrapper" bgcolor="#1B1F3B" style="font-family: Poppins, Arial, Helvetica, sans-serif;" border="0" cellpadding="0" cellspacing="0" height="100%" width="420px">
|
||||
<tr>
|
||||
<td>
|
||||
<table border="0" cellpadding="0" cellspacing="0" height="100%" width="100%">
|
||||
<tr>
|
||||
<td width="32px"> </td>
|
||||
<td>
|
||||
<table border="0" cellpadding="0" cellspacing="0" height="100%" width="100%">
|
||||
<tr>
|
||||
<td height="36px" style="line-height: 36px;" width="100%"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<table border="0" cellpadding="0" cellspacing="0" height="100%" width="100%">
|
||||
<tr>
|
||||
<td>
|
||||
<a href="https://spfn.spbr.net">
|
||||
<img class="logo" width="auto" height="48px" src="https://spfn.spbr.net/res/img/spfn.png" alt="SPFN">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="100%" height="36px" style="line-height: 36px;"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<table class="card" bgcolor="#23274a" style="color: #ffffff; border-radius: 10px;" border="0" cellpadding="0" cellspacing="0" height="100%" width="100%">
|
||||
<tr>
|
||||
<td width="24px" height="100%"> </td>
|
||||
<td>
|
||||
<table border="0" cellpadding="0" cellspacing="0" height="100%" width="100%">
|
||||
<tr width="100%" height="48px" style="line-height: 48px;">
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr style="font-size: 24px; font-weight: 700;">
|
||||
<td>
|
||||
Hello.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="100%" height="24px" style="line-height: 24px;"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="color: #ffffff; ">
|
||||
Your email was linked to a Wii U console with serial number {{serial}} for parental controls.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="100%" height="24px" style="line-height: 24px;"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
You have requested a master key to reset your parental controls PIN. The master key you must enter on your Wii U is:
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="100%" height="24px" style="line-height: 24px;"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="confirm-code" bgcolor="#373c65" style="color: #ffffff; font-size: 14px; font-weight: 700; border-radius: 10px; padding: 12px" align="center">
|
||||
{{mkey}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="100%" height="36px" style="line-height: 36px;"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Please make sure to change your PIN as soon as possible.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
The SPFN team
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="100%" height="24px" style="line-height: 24px;"> </td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
<td width="24px" height="100%"> </td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="100%" height="18px" style="line-height: 18px;"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="notice" style="color: #8990c1; font-size: 12px;">
|
||||
Note: this email message was auto-generated, please do not respond. For further assistance, please join our <a href="https://discord.gg/grMSxZf" style="text-decoration: none; color: #ffffff; ">Discord server</a>.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="100%" height="48px" style="line-height: 48px;"> </td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
<td width="32px"> </td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
177
res/email/parentalControlsTemplate.html
Normal file
177
res/email/parentalControlsTemplate.html
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" lang="en">
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta name="color-scheme" content="light dark">
|
||||
<meta http-equiv="Content-Type" content="text/html charset=UTF-8" />
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;700&display=swap');
|
||||
|
||||
:root {
|
||||
color-scheme: light dark;
|
||||
supported-color-schemes:light dark;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
body.email-body,
|
||||
table.centerer,
|
||||
table.wrapper {
|
||||
background-color: #FFFFFF !important;
|
||||
color: #000000 !important;
|
||||
}
|
||||
table.card {
|
||||
background-color: #B60000 !important;
|
||||
}
|
||||
span.shoutout {
|
||||
color: #FFB3B3 !important;
|
||||
}
|
||||
td.confirm-link {
|
||||
background-color: #FF4D4D !important;
|
||||
}
|
||||
td.confirm-code {
|
||||
background-color: #FFB3B3 !important;
|
||||
color: #660000 !important;
|
||||
}
|
||||
td.notice {
|
||||
color: #FF4D4D !important;
|
||||
}
|
||||
td.notice a {
|
||||
color: #B60000 !important;
|
||||
}
|
||||
img.logo {
|
||||
content: url("https://spfn.spbr.net/res/img/spfn.png") !important;
|
||||
}
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body.email-body,
|
||||
table.centerer,
|
||||
table.wrapper {
|
||||
background-color: #3B1B1B !important;
|
||||
color: #FFFFFF !important;
|
||||
}
|
||||
table.card {
|
||||
background-color: #4A2323 !important;
|
||||
}
|
||||
span.shoutout {
|
||||
color: #FF9999 !important;
|
||||
}
|
||||
td.confirm-link {
|
||||
background-color: #B60000 !important;
|
||||
}
|
||||
td.confirm-code {
|
||||
background-color: #652323 !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
td.notice {
|
||||
color: #C18989 !important;
|
||||
}
|
||||
td.notice a {
|
||||
color: #F5C1C1 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="email-body" bgcolor="#1B1F3B" style="margin-left: 0; margin-right: 0; margin-top: 0; margin-bottom: 0; padding-left: 0; padding-right: 0; padding-top: 0; padding-bottom: 0; font-family: Poppins, Arial, Helvetica, sans-serif;">
|
||||
<div style="display:none;">Hello. The console with the serial number {{serial}} has been linked to your email for parental controls.</div>
|
||||
<table class="centerer" bgcolor="#1B1F3B" border="0" cellpadding="0" cellspacing="0" height="100%" width="100%">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<table class="wrapper" bgcolor="#1B1F3B" style="font-family: Poppins, Arial, Helvetica, sans-serif;" border="0" cellpadding="0" cellspacing="0" height="100%" width="420px">
|
||||
<tr>
|
||||
<td>
|
||||
<table border="0" cellpadding="0" cellspacing="0" height="100%" width="100%">
|
||||
<tr>
|
||||
<td width="32px"> </td>
|
||||
<td>
|
||||
<table border="0" cellpadding="0" cellspacing="0" height="100%" width="100%">
|
||||
<tr>
|
||||
<td height="36px" style="line-height: 36px;" width="100%"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<table border="0" cellpadding="0" cellspacing="0" height="100%" width="100%">
|
||||
<tr>
|
||||
<td>
|
||||
<a href="https://spfn.spbr.net">
|
||||
<img class="logo" width="auto" height="48px" src="https://spfn.spbr.net/res/img/spfn.png" alt="SPFN">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="100%" height="36px" style="line-height: 36px;"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<table class="card" bgcolor="#23274a" style="color: #ffffff; border-radius: 10px;" border="0" cellpadding="0" cellspacing="0" height="100%" width="100%">
|
||||
<tr>
|
||||
<td width="24px" height="100%"> </td>
|
||||
<td>
|
||||
<table border="0" cellpadding="0" cellspacing="0" height="100%" width="100%">
|
||||
<tr width="100%" height="48px" style="line-height: 48px;">
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr style="font-size: 24px; font-weight: 700;">
|
||||
<td>
|
||||
Hello.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="100%" height="24px" style="line-height: 24px;"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="color: #ffffff; ">
|
||||
Your email was linked to a Wii U console with serial number {{serial}} for parental controls.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="100%" height="24px" style="line-height: 24px;"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
If you did not authorize this, please contact our team at support@spbr.net.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="100%" height="36px" style="line-height: 36px;"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
The SPFN team
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="100%" height="24px" style="line-height: 24px;"> </td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
<td width="24px" height="100%"> </td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="100%" height="18px" style="line-height: 18px;"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="notice" style="color: #8990c1; font-size: 12px;">
|
||||
Note: this email message was auto-generated, please do not respond. For further assistance, please join our <a href="https://discord.gg/grMSxZf" style="text-decoration: none; color: #ffffff; ">Discord server</a>.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="100%" height="48px" style="line-height: 48px;"> </td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
<td width="32px"> </td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
67
src/email.rs
67
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(())
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -222,3 +222,108 @@ pub async fn forgotten_password(
|
|||
|
||||
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(())
|
||||
}
|
||||
Loading…
Reference in a new issue