Browse Source

For security tell people the IP and browser that attempted the request.

master
Zed A. Shaw 1 week ago
parent
commit
e28679d640
  1. 7
      api/password_reset.js
  2. 2
      emails/change_email.txt
  3. 120
      emails/reset_email.html
  4. 4
      emails/reset_email.txt
  5. 2
      emails/reset_finished.html
  6. 4
      emails/reset_finished.txt
  7. 8
      lib/queues.js
  8. 21
      queues/mail.js
  9. 2
      services/api.js

7
api/password_reset.js

@ -10,6 +10,8 @@ const RESET_MAX = 10;
export const post = async (req, res) => {
const api = new API(req, res);
const { email, code, password, password_repeat, finalize} = req.body;
assert(email, "email is required.");
@ -31,12 +33,13 @@ export const post = async (req, res) => {
} else {
// SECURITY: encrypt the password here so that it's not left in the queue unencrypted
user.password = User.encrypt_password(password);
send_reset_finished(user);
send_reset_finished(user, req.ip, req.headers['user-agent']);
return api.reply(200, {message: "OK"});
}
} else {
// new request, send out a code
send_reset({ email, id: user.id});
const user_req = { email, id: user.id};
send_reset(user_req, req.ip, req.headers['user-agent']);
return api.reply(200, {message: "OK"});
}
} catch(error) {

2
emails/change_email.txt

@ -1,7 +1,7 @@
Your Account Has Changed
------------------------
Your account at <%- company.product %> was recently changed by a computer at IP Address <%- ip_addr %>.
Your account at <%- company.product %> was recently changed by a computer at IP Address <%- ip_address %>.
The change made was:

120
emails/reset_email.html

@ -9,26 +9,26 @@
<title><%- company.name %> - Email Reset</title>
<style type="text/css" rel="stylesheet" media="all">
/* Base ------------------------------ */
body {
width: 100% !important;
height: 100%;
margin: 0;
-webkit-text-size-adjust: none;
}
a {
color: #3869D4;
}
a img {
border: none;
}
td {
word-break: break-word;
}
.preheader {
display: none !important;
visibility: hidden;
@ -41,13 +41,13 @@
overflow: hidden;
}
/* Type ------------------------------ */
body,
td,
th {
font-family: Helvetica, Arial, sans-serif;
}
h1 {
margin-top: 0;
color: #333333;
@ -55,7 +55,7 @@
font-weight: bold;
text-align: left;
}
h2 {
margin-top: 0;
color: #333333;
@ -63,7 +63,7 @@
font-weight: bold;
text-align: left;
}
h3 {
margin-top: 0;
color: #333333;
@ -71,12 +71,12 @@
font-weight: bold;
text-align: left;
}
td,
th {
font-size: 16px;
}
p,
ul,
ol,
@ -85,25 +85,25 @@
font-size: 16px;
line-height: 1.625;
}
p.sub {
font-size: 13px;
}
/* Utilities ------------------------------ */
.align-right {
text-align: right;
}
.align-left {
text-align: left;
}
.align-center {
text-align: center;
}
/* Buttons ------------------------------ */
.button {
background-color: #3869D4;
border-top: 10px solid #3869D4;
@ -118,7 +118,7 @@
-webkit-text-size-adjust: none;
box-sizing: border-box;
}
.button--green {
background-color: #22BC66;
border-top: 10px solid #22BC66;
@ -126,7 +126,7 @@
border-bottom: 10px solid #22BC66;
border-left: 18px solid #22BC66;
}
.button--red {
background-color: #FF6136;
border-top: 10px solid #FF6136;
@ -134,7 +134,7 @@
border-bottom: 10px solid #FF6136;
border-left: 18px solid #FF6136;
}
@media only screen and (max-width: 500px) {
.button {
width: 100% !important;
@ -142,21 +142,21 @@
}
}
/* Attribute list ------------------------------ */
.attributes {
margin: 0 0 21px;
}
.attributes_content {
background-color: #F4F4F7;
padding: 16px;
}
.attributes_item {
padding: 0;
}
/* Related Items ------------------------------ */
.related {
width: 100%;
margin: 0;
@ -165,31 +165,31 @@
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
}
.related_item {
padding: 10px 0;
color: #CBCCCF;
font-size: 15px;
line-height: 18px;
}
.related_item-title {
display: block;
margin: .5em 0 0;
}
.related_item-thumb {
display: block;
padding-bottom: 10px;
}
.related_heading {
border-top: 1px solid #CBCCCF;
text-align: center;
padding: 25px 0 10px;
}
/* Discount Code ------------------------------ */
.discount {
width: 100%;
margin: 0;
@ -200,33 +200,33 @@
background-color: #F4F4F7;
border: 2px dashed #CBCCCF;
}
.discount_heading {
text-align: center;
}
.discount_body {
text-align: center;
font-size: 15px;
}
/* Social Icons ------------------------------ */
.social {
width: auto;
}
.social td {
padding: 0;
width: auto;
}
.social_icon {
height: 20px;
margin: 0 8px 10px 8px;
padding: 0;
}
/* Data table ------------------------------ */
.purchase {
width: 100%;
margin: 0;
@ -235,7 +235,7 @@
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
}
.purchase_content {
width: 100%;
margin: 0;
@ -244,54 +244,54 @@
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
}
.purchase_item {
padding: 10px 0;
color: #51545E;
font-size: 15px;
line-height: 18px;
}
.purchase_heading {
padding-bottom: 8px;
border-bottom: 1px solid #EAEAEC;
}
.purchase_heading p {
margin: 0;
color: #85878E;
font-size: 12px;
}
.purchase_footer {
padding-top: 15px;
border-top: 1px solid #EAEAEC;
}
.purchase_total {
margin: 0;
text-align: right;
font-weight: bold;
color: #333333;
}
.purchase_total--label {
padding: 0 15px 0 0;
}
body {
background-color: #F4F4F7;
color: #51545E;
}
p {
color: #51545E;
}
p.sub {
color: #6B6E76;
}
.email-wrapper {
width: 100%;
margin: 0;
@ -301,7 +301,7 @@
-premailer-cellspacing: 0;
background-color: #F4F4F7;
}
.email-content {
width: 100%;
margin: 0;
@ -311,16 +311,16 @@
-premailer-cellspacing: 0;
}
/* Masthead ----------------------- */
.email-masthead {
padding: 25px 0;
text-align: center;
}
.email-masthead_logo {
width: 94px;
}
.email-masthead_name {
font-size: 16px;
font-weight: bold;
@ -329,7 +329,7 @@
text-shadow: 0 1px 0 white;
}
/* Body ------------------------------ */
.email-body {
width: 100%;
margin: 0;
@ -339,7 +339,7 @@
-premailer-cellspacing: 0;
background-color: #FFFFFF;
}
.email-body_inner {
width: 570px;
margin: 0 auto;
@ -349,7 +349,7 @@
-premailer-cellspacing: 0;
background-color: #FFFFFF;
}
.email-footer {
width: 570px;
margin: 0 auto;
@ -359,11 +359,11 @@
-premailer-cellspacing: 0;
text-align: center;
}
.email-footer p {
color: #6B6E76;
}
.body-action {
width: 100%;
margin: 30px auto;
@ -373,25 +373,25 @@
-premailer-cellspacing: 0;
text-align: center;
}
.body-sub {
margin-top: 25px;
padding-top: 25px;
border-top: 1px solid #EAEAEC;
}
.content-cell {
padding: 35px;
}
/*Media Queries ------------------------------ */
@media only screen and (max-width: 600px) {
.email-body_inner,
.email-footer {
width: 100% !important;
}
}
@media (prefers-color-scheme: dark) {
body,
.email-body,
@ -420,7 +420,7 @@
text-shadow: none !important;
}
}
:root {
color-scheme: light dark;
supported-color-schemes: light dark;
@ -471,7 +471,7 @@
</td>
</tr>
</table>
<p>For security, this request was received from a browser identified as <b><%- browser %></b> from the IP address <b><%- ip_addr %></b>. If you did not request a password reset, please ignore this email or email <a href="mailto:help@<%- company.website %>">help@<%- company.website %></a> if you have questions.</p>
<p>For security, this request was received from a browser identified as <b><%- browser %></b> from the IP address <b><%- ip_address %></b>. If you did not request a password reset, please forward this email to <a href="mailto:help@<%- company.website %>">help@<%- company.website %></a> so we can look into your account.</p>
<p>Thanks,
<br>The <em><%- company.product %></em> Team</p>
</div>

4
emails/reset_email.txt

@ -17,9 +17,9 @@ For security, this request was received from a browser identified as:
<%- browser || 'Unknown' %>
From the IP address <%- ip_addr || 'Unknown' %>.
From the IP address <%- ip_address || 'Unknown' %>.
If you did not request a password reset, please ignore this email or contact support at help@<%- company.website %> so we can look into your account.
If you did not request a password reset, please forward this email to support at help@<%- company.website %> so we can look into your account.
Thanks,
The <%- company.product %> Team

2
emails/reset_finished.html

@ -470,7 +470,7 @@
</td>
</tr>
</table>
<p>For security, this request was received from a browser identified as <b><%- browser %></b> from the IP address <b><%- ip_addr %></b>. If you did not request a password reset, please ignore this email or email <a href="mailto:help@<%- company.website %>">help@<%- company.website %></a> if you have questions.</p>
<p>For security, this request was received from a browser identified as <b><%- browser %></b> from the IP address <b><%- ip_address %></b>. If you did not request a password reset, please forward this email to <a href="mailto:help@<%- company.website %>">help@<%- company.website %></a> so we can look into your account.</p>
<p>Thanks,
<br>The <em><%- company.product %></em> Team</p>
</div>

4
emails/reset_finished.txt

@ -10,9 +10,9 @@ For security, this request was received from a browser identified as:
<%- browser || 'Unknown' %>
From the IP address <%- ip_addr || 'Unknown' %>.
From the IP address <%- ip_address || 'Unknown' %>.
If you did not request a password reset, please ignore this email or contact support at help@<%- company.website %> so we can look into your account.
If you did not request a password reset, please forward this email to support at help@<%- company.website %> so we can look into your account.
Thanks,
The <%- company.product %> Team

8
lib/queues.js

@ -12,12 +12,12 @@ export const send_welcome = (user) => {
mail.add({user});
}
export const send_reset = (user) => {
export const send_reset = (user, ip_address, browser) => {
const mail = create("mail/reset");
mail.add({user});
mail.add({user, ip_address, browser});
}
export const send_reset_finished = (user) => {
export const send_reset_finished = (user, ip_address, browser) => {
const mail = create("mail/reset_finished");
mail.add({user});
mail.add({user, ip_address, browser});
}

21
queues/mail.js

@ -43,6 +43,8 @@ export const reset = async (job) => {
const email = job.data.user.email;
const user_id = job.data.user.id;
const browser = job.data.browser || "None";
const ip_address = job.data.ip_address || "None";
assert(email, "Email is required");
assert(user_id !== undefined, "User user_id is undefined.");
@ -63,13 +65,7 @@ export const reset = async (job) => {
user.reset_code = User.random_hex(RESET_CODE_SIZE);
await User.update({id: user.id}, {reset_code: user.reset_code, reset_sent_at: Date.now()});
const reset_data = {
company,
user,
browser: "Mozilla",
ip_addr: "127.0.0.1",
reset_code: user.reset_code,
};
const reset_data = { company, user, browser, ip_address, reset_code: user.reset_code };
const text = reset_form.txt(reset_data);
const html = reset_form.html(reset_data);
@ -94,6 +90,8 @@ const reset_finished_form = await load_templates("reset_finished");
export const reset_finished = async (job) => {
try {
const user = job.data.user;
const browser = job.data.browser || "None";
const ip_address = job.data.ip_address || "None";
// update the user's password now and then send the email
await User.update({id: user.id}, {
@ -103,13 +101,8 @@ export const reset_finished = async (job) => {
reset_count: 0
});
const reset_finished_data = {
company,
user,
browser: "Mozilla",
ip_addr: "127.0.0.1",
reset_finished_code: "CODE",
};
const reset_finished_data = { company, user, browser, ip_address };
const text = reset_finished_form.txt(reset_finished_data);
const html = reset_finished_form.html(reset_finished_data);

2
services/api.js

@ -23,6 +23,8 @@ const API_BASE = path.resolve("./api");
const SOCKET_BASE = path.resolve("./socket");
const socket_routes = {};
app.set('trust proxy', true)
// app.use(morgan('combined'));
// parse application/x-www-form-urlencoded

Loading…
Cancel
Save