Send mail notifications for new devices and password resets

This commit is contained in:
Jonas Lochmann 2021-12-27 01:00:00 +01:00
parent 50c0982bd3
commit 03890f209a
No known key found for this signature in database
GPG key ID: 8B8C9AEE10FA5B36
15 changed files with 583 additions and 22 deletions

View file

@ -0,0 +1,190 @@
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
<head>
<title>
</title>
<!--[if !mso]><!-->
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!--<![endif]-->
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style type="text/css">
#outlook a {
padding: 0;
}
body {
margin: 0;
padding: 0;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
}
table,
td {
border-collapse: collapse;
mso-table-lspace: 0pt;
mso-table-rspace: 0pt;
}
img {
border: 0;
height: auto;
line-height: 100%;
outline: none;
text-decoration: none;
-ms-interpolation-mode: bicubic;
}
p {
display: block;
margin: 13px 0;
}
</style>
<!--[if mso]>
<noscript>
<xml>
<o:OfficeDocumentSettings>
<o:AllowPNG/>
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
</noscript>
<![endif]-->
<!--[if lte mso 11]>
<style type="text/css">
.mj-outlook-group-fix { width:100% !important; }
</style>
<![endif]-->
<style type="text/css">
@media only screen and (min-width:480px) {
.mj-column-per-100 {
width: 100% !important;
max-width: 100%;
}
}
</style>
<style media="screen and (min-width:480px)">
.moz-text-html .mj-column-per-100 {
width: 100% !important;
max-width: 100%;
}
</style>
<style type="text/css">
</style>
</head>
<body style="word-spacing:normal;">
<div style="">
<!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#009688" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
<div style="background:#009688;background-color:#009688;margin:0px auto;max-width:600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#009688;background-color:#009688;width:100%;">
<tbody>
<tr>
<td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;">
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
<tbody>
<tr>
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:20px;line-height:1;text-align:left;color:#ffffff;">TimeLimit</div>
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table><![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
<div style="margin:0px auto;max-width:600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
<tbody>
<tr>
<td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;">
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
<tbody>
<tr>
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#000000;">
<p><%= preText %> <%= deviceName %> <%= postText %></p>
<p><%= securityText %></p>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table><![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
<div style="margin:0px auto;max-width:600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
<tbody>
<tr>
<td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;">
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
<tbody>
<tr>
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<p style="border-top:dashed 1px lightgrey;font-size:1px;margin:0px auto;width:100%;">
</p>
<!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" style="border-top:dashed 1px lightgrey;font-size:1px;margin:0px auto;width:550px;" role="presentation" width="550px" ><tr><td style="height:0;line-height:0;"> &nbsp;
</td></tr></table><![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table><![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
<div style="margin:0px auto;max-width:600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
<tbody>
<tr>
<td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;">
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
<tbody>
<tr>
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#000000;">
<p> Sie erhalten diese Nachricht, da ein Gerät zu Ihrem Konto hinzugefügt wurde. Mit diesem Gerät können die TimeLimit-Einstellungen ohne ein Passwort geändert werden. Falls Sie Fragen haben können Sie einfach auf diese E-Mail antworten. </p>
<p> You got this message because a device was added to your account. This device can be used to change the TimeLimit configuration without any password. If you have got any questions, then you can reply to this messagge. </p>
<p> &copy; <%= mailimprint %> </p>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table><![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table><![endif]-->
</div>
</body>
</html>

View file

@ -0,0 +1,41 @@
<mjml>
<mj-body>
<mj-section background-color="#009688">
<mj-column>
<mj-text font-size="20px" color="#ffffff">TimeLimit</mj-text>
</mj-column>
</mj-section>
<mj-section>
<mj-column>
<mj-text>
<p><%= preText %> <%= deviceName %> <%= postText %></p>
<p><%= securityText %></p>
</mj-text>
</mj-column>
</mj-section>
<mj-section>
<mj-column>
<mj-divider border-width="1px" border-style="dashed" border-color="lightgrey" />
</mj-column>
</mj-section>
<mj-section>
<mj-column>
<mj-text>
<p>
Sie erhalten diese Nachricht, da ein Gerät zu Ihrem Konto hinzugefügt wurde.
Mit diesem Gerät können die TimeLimit-Einstellungen ohne ein Passwort geändert werden.
Falls Sie Fragen haben können Sie einfach auf diese E-Mail antworten.
</p>
<p>
You got this message because a device was added to your account.
This device can be used to change the TimeLimit configuration without any password.
If you have got any questions, then you can reply to this messagge.
</p>
<p>
&copy; <%= mailimprint %>
</p>
</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>

View file

@ -0,0 +1 @@
<%- subject %>

View file

@ -0,0 +1,15 @@
<%- preText %> <%- deviceName %> <%- postText %>
<%- securityText %>
----------------------
Sie erhalten diese Nachricht, da ein Gerät zu Ihrem Konto hinzugefügt wurde.
Mit diesem Gerät können die TimeLimit-Einstellungen ohne ein Passwort geändert werden.
Falls Sie Fragen haben können Sie einfach auf diese E-Mail antworten.
You got this message because a device was added to your account.
This device can be used to change the TimeLimit configuration without any password.
If you have got any questions, then you can reply to this messagge.
<C> <%- mailimprint %>

View file

@ -0,0 +1,190 @@
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
<head>
<title>
</title>
<!--[if !mso]><!-->
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!--<![endif]-->
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style type="text/css">
#outlook a {
padding: 0;
}
body {
margin: 0;
padding: 0;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
}
table,
td {
border-collapse: collapse;
mso-table-lspace: 0pt;
mso-table-rspace: 0pt;
}
img {
border: 0;
height: auto;
line-height: 100%;
outline: none;
text-decoration: none;
-ms-interpolation-mode: bicubic;
}
p {
display: block;
margin: 13px 0;
}
</style>
<!--[if mso]>
<noscript>
<xml>
<o:OfficeDocumentSettings>
<o:AllowPNG/>
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
</noscript>
<![endif]-->
<!--[if lte mso 11]>
<style type="text/css">
.mj-outlook-group-fix { width:100% !important; }
</style>
<![endif]-->
<style type="text/css">
@media only screen and (min-width:480px) {
.mj-column-per-100 {
width: 100% !important;
max-width: 100%;
}
}
</style>
<style media="screen and (min-width:480px)">
.moz-text-html .mj-column-per-100 {
width: 100% !important;
max-width: 100%;
}
</style>
<style type="text/css">
</style>
</head>
<body style="word-spacing:normal;">
<div style="">
<!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#009688" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
<div style="background:#009688;background-color:#009688;margin:0px auto;max-width:600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#009688;background-color:#009688;width:100%;">
<tbody>
<tr>
<td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;">
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
<tbody>
<tr>
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:20px;line-height:1;text-align:left;color:#ffffff;">TimeLimit</div>
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table><![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
<div style="margin:0px auto;max-width:600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
<tbody>
<tr>
<td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;">
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
<tbody>
<tr>
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#000000;">
<p><%= text %></p>
<p><%= securityText %></p>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table><![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
<div style="margin:0px auto;max-width:600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
<tbody>
<tr>
<td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;">
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
<tbody>
<tr>
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<p style="border-top:dashed 1px lightgrey;font-size:1px;margin:0px auto;width:100%;">
</p>
<!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" style="border-top:dashed 1px lightgrey;font-size:1px;margin:0px auto;width:550px;" role="presentation" width="550px" ><tr><td style="height:0;line-height:0;"> &nbsp;
</td></tr></table><![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table><![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
<div style="margin:0px auto;max-width:600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
<tbody>
<tr>
<td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;">
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
<tbody>
<tr>
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#000000;">
<p> Sie erhalten diese Nachricht, da Ihr TimeLimit-Passwort geändert wurde. Falls Sie Fragen haben können Sie einfach auf diese E-Mail antworten. </p>
<p> You got this message because the password reset feature was used for your account. If you have got any questions, then you can reply to this messagge. </p>
<p> &copy; <%= mailimprint %> </p>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table><![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table><![endif]-->
</div>
</body>
</html>

View file

@ -0,0 +1,39 @@
<mjml>
<mj-body>
<mj-section background-color="#009688">
<mj-column>
<mj-text font-size="20px" color="#ffffff">TimeLimit</mj-text>
</mj-column>
</mj-section>
<mj-section>
<mj-column>
<mj-text>
<p><%= text %></p>
<p><%= securityText %></p>
</mj-text>
</mj-column>
</mj-section>
<mj-section>
<mj-column>
<mj-divider border-width="1px" border-style="dashed" border-color="lightgrey" />
</mj-column>
</mj-section>
<mj-section>
<mj-column>
<mj-text>
<p>
Sie erhalten diese Nachricht, da Ihr TimeLimit-Passwort geändert wurde.
Falls Sie Fragen haben können Sie einfach auf diese E-Mail antworten.
</p>
<p>
You got this message because the password reset feature was used for your account.
If you have got any questions, then you can reply to this messagge.
</p>
<p>
&copy; <%= mailimprint %>
</p>
</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>

View file

@ -0,0 +1 @@
<%- subject %>

View file

@ -0,0 +1,13 @@
<%- text %>
<%- securityText %>
----------------------
Sie erhalten diese Nachricht, da Ihr TimeLimit-Passwort geändert wurde.
Falls Sie Fragen haben können Sie einfach auf diese E-Mail antworten.
You got this message because the password reset feature was used for your account.
If you have got any questions, then you can reply to this messagge.
<C> <%- mailimprint %>

View file

@ -36,7 +36,7 @@ export const createAuthTokenByMailAddress = async ({
return token return token
} }
export const getMailByAuthToken = async ({ export const getMailAndLocaleByAuthToken = async ({
mailAuthToken, database, transaction, invalidate mailAuthToken, database, transaction, invalidate
}: { }: {
mailAuthToken: string, database: Database, transaction: Transaction, invalidate: boolean mailAuthToken: string, database: Database, transaction: Transaction, invalidate: boolean
@ -62,22 +62,25 @@ export const getMailByAuthToken = async ({
} }
} }
return entry.mail return {
mail: entry.mail,
locale: entry.locale
}
} else { } else {
return null return null
} }
} }
export const requireMailByAuthToken = async ({ export const requireMailAndLocaleByAuthToken = async ({
mailAuthToken, database, transaction, invalidate mailAuthToken, database, transaction, invalidate
}: { }: {
mailAuthToken: string, database: Database, transaction: Transaction, invalidate: boolean mailAuthToken: string, database: Database, transaction: Transaction, invalidate: boolean
}) => { }) => {
const mail = await getMailByAuthToken({ mailAuthToken, database, transaction, invalidate }) const result = await getMailAndLocaleByAuthToken({ mailAuthToken, database, transaction, invalidate })
if (!mail) { if (!result) {
throw new Unauthorized() throw new Unauthorized()
} }
return mail return result
} }

View file

@ -22,7 +22,7 @@ import { maxMailNotificationFlags } from '../../database/user'
import { import {
generateAuthToken, generateFamilyId, generateIdWithinFamily, generateVersionId generateAuthToken, generateFamilyId, generateIdWithinFamily, generateVersionId
} from '../../util/token' } from '../../util/token'
import { requireMailByAuthToken } from '../authentication' import { requireMailAndLocaleByAuthToken } from '../authentication'
import { prepareDeviceEntry } from '../device/prepare-device-entry' import { prepareDeviceEntry } from '../device/prepare-device-entry'
export const createFamily = async ({ database, mailAuthToken, firstParentDevice, password, timeZone, parentName, deviceName }: { export const createFamily = async ({ database, mailAuthToken, firstParentDevice, password, timeZone, parentName, deviceName }: {
@ -37,12 +37,12 @@ export const createFamily = async ({ database, mailAuthToken, firstParentDevice,
}) => { }) => {
return database.transaction(async (transaction) => { return database.transaction(async (transaction) => {
const now = Date.now().toString(10) const now = Date.now().toString(10)
const mail = await requireMailByAuthToken({ database, mailAuthToken, transaction, invalidate: true }) const mailInfo = await requireMailAndLocaleByAuthToken({ database, mailAuthToken, transaction, invalidate: true })
// ensure that no family was created for this mail yet // ensure that no family was created for this mail yet
const exisitngUserEntry = await database.user.findOne({ const exisitngUserEntry = await database.user.findOne({
where: { where: {
mail mail: mailInfo.mail
}, },
transaction transaction
}) })
@ -77,7 +77,7 @@ export const createFamily = async ({ database, mailAuthToken, firstParentDevice,
secondPasswordHash: password.secondHash, secondPasswordHash: password.secondHash,
secondPasswordSalt: password.secondSalt, secondPasswordSalt: password.secondSalt,
type: 'parent', type: 'parent',
mail, mail: mailInfo.mail,
timeZone, timeZone,
disableTimelimitsUntil: '0', disableTimelimitsUntil: '0',
currentDevice: '', currentDevice: '',

View file

@ -17,7 +17,7 @@
import { Database, Transaction } from '../../database' import { Database, Transaction } from '../../database'
import { StaticMessageException } from '../../exception' import { StaticMessageException } from '../../exception'
import { requireMailByAuthToken } from '../authentication' import { requireMailAndLocaleByAuthToken } from '../authentication'
const getStatusByMailAddress = async ({ const getStatusByMailAddress = async ({
mail, database, transaction mail, database, transaction
@ -43,7 +43,9 @@ const getStatusByMailAddress = async ({
export const getStatusByMailToken = async ({ export const getStatusByMailToken = async ({
mailAuthToken, database, transaction mailAuthToken, database, transaction
}: { mailAuthToken: string, database: Database, transaction: Transaction }) => { }: { mailAuthToken: string, database: Database, transaction: Transaction }) => {
const mail = await requireMailByAuthToken({ mailAuthToken, database, transaction, invalidate: false }) const mailInfo = await requireMailAndLocaleByAuthToken({ mailAuthToken, database, transaction, invalidate: false })
const mail = mailInfo.mail
const status = await getStatusByMailAddress({ mail, database, transaction }) const status = await getStatusByMailAddress({ mail, database, transaction })
return { mail, status } return { mail, status }

View file

@ -19,7 +19,7 @@ import { Conflict, Unauthorized } from 'http-errors'
import { Database } from '../../database' import { Database } from '../../database'
import { generateVersionId } from '../../util/token' import { generateVersionId } from '../../util/token'
import { WebsocketApi } from '../../websocket' import { WebsocketApi } from '../../websocket'
import { requireMailByAuthToken } from '../authentication' import { requireMailAndLocaleByAuthToken } from '../authentication'
import { notifyClientsAboutChangesDelayed } from '../websocket' import { notifyClientsAboutChangesDelayed } from '../websocket'
export const linkMailAddress = async ({ mailAuthToken, deviceAuthToken, parentUserId, parentPasswordSecondHash, database, websocket }: { export const linkMailAddress = async ({ mailAuthToken, deviceAuthToken, parentUserId, parentPasswordSecondHash, database, websocket }: {
@ -45,11 +45,11 @@ export const linkMailAddress = async ({ mailAuthToken, deviceAuthToken, parentUs
const familyId = deviceEntry.familyId const familyId = deviceEntry.familyId
const mailAddress = await requireMailByAuthToken({ mailAuthToken, database, transaction, invalidate: true }) const mailInfo = await requireMailAndLocaleByAuthToken({ mailAuthToken, database, transaction, invalidate: true })
const exisitingUser = await database.user.findOne({ const exisitingUser = await database.user.findOne({
where: { where: {
mail: mailAddress mail: mailInfo.mail
}, },
transaction transaction
}) })
@ -83,7 +83,7 @@ export const linkMailAddress = async ({ mailAuthToken, deviceAuthToken, parentUs
throw new Conflict() throw new Conflict()
} }
parentEntry.mail = mailAddress parentEntry.mail = mailInfo.mail
await parentEntry.save({ transaction }) await parentEntry.save({ transaction })

View file

@ -18,9 +18,10 @@
import { Conflict } from 'http-errors' import { Conflict } from 'http-errors'
import { ParentPassword } from '../../api/schema' import { ParentPassword } from '../../api/schema'
import { Database } from '../../database' import { Database } from '../../database'
import { sendPasswordRecoveryUsedMail } from '../../util/mail'
import { generateVersionId } from '../../util/token' import { generateVersionId } from '../../util/token'
import { WebsocketApi } from '../../websocket' import { WebsocketApi } from '../../websocket'
import { requireMailByAuthToken } from '../authentication' import { requireMailAndLocaleByAuthToken } from '../authentication'
import { notifyClientsAboutChangesDelayed } from '../websocket' import { notifyClientsAboutChangesDelayed } from '../websocket'
export const recoverParentPassword = async ({ database, websocket, password, mailAuthToken }: { export const recoverParentPassword = async ({ database, websocket, password, mailAuthToken }: {
@ -31,12 +32,12 @@ export const recoverParentPassword = async ({ database, websocket, password, mai
// no transaction here because this is directly called from an API endpoint // no transaction here because this is directly called from an API endpoint
}) => { }) => {
await database.transaction(async (transaction) => { await database.transaction(async (transaction) => {
const mail = await requireMailByAuthToken({ mailAuthToken, database, transaction, invalidate: true }) const mailInfo = await requireMailAndLocaleByAuthToken({ mailAuthToken, database, transaction, invalidate: true })
// update the user entry // update the user entry
const userEntry = await database.user.findOne({ const userEntry = await database.user.findOne({
where: { where: {
mail mail: mailInfo.mail
}, },
transaction transaction
}) })
@ -69,5 +70,12 @@ export const recoverParentPassword = async ({ database, websocket, password, mai
sourceDeviceId: null, sourceDeviceId: null,
transaction transaction
}) })
transaction.afterCommit(async () => {
await sendPasswordRecoveryUsedMail({
receiver: mailInfo.mail,
locale: mailInfo.locale
})
})
}) })
} }

View file

@ -18,9 +18,10 @@
import { Conflict } from 'http-errors' import { Conflict } from 'http-errors'
import { NewDeviceInfo } from '../../api/schema' import { NewDeviceInfo } from '../../api/schema'
import { Database } from '../../database' import { Database } from '../../database'
import { sendDeviceLinkedMail } from '../../util/mail'
import { generateAuthToken, generateIdWithinFamily, generateVersionId } from '../../util/token' import { generateAuthToken, generateIdWithinFamily, generateVersionId } from '../../util/token'
import { WebsocketApi } from '../../websocket' import { WebsocketApi } from '../../websocket'
import { requireMailByAuthToken } from '../authentication' import { requireMailAndLocaleByAuthToken } from '../authentication'
import { prepareDeviceEntry } from '../device/prepare-device-entry' import { prepareDeviceEntry } from '../device/prepare-device-entry'
import { notifyClientsAboutChangesDelayed } from '../websocket' import { notifyClientsAboutChangesDelayed } from '../websocket'
@ -33,11 +34,11 @@ export const signInIntoFamily = async ({ database, mailAuthToken, newDeviceInfo,
// no transaction here because this is directly called from an API endpoint // no transaction here because this is directly called from an API endpoint
}): Promise<{ deviceId: string; deviceAuthToken: string }> => { }): Promise<{ deviceId: string; deviceAuthToken: string }> => {
return database.transaction(async (transaction) => { return database.transaction(async (transaction) => {
const mail = await requireMailByAuthToken({ database, mailAuthToken, transaction, invalidate: true }) const mailInfo = await requireMailAndLocaleByAuthToken({ database, mailAuthToken, transaction, invalidate: true })
const userEntryUnsafe = await database.user.findOne({ const userEntryUnsafe = await database.user.findOne({
where: { where: {
mail mail: mailInfo.mail
}, },
attributes: ['familyId', 'userId'], attributes: ['familyId', 'userId'],
transaction transaction
@ -84,6 +85,14 @@ export const signInIntoFamily = async ({ database, mailAuthToken, newDeviceInfo,
transaction transaction
}) })
transaction.afterCommit(async () => {
await sendDeviceLinkedMail({
receiver: mailInfo.mail,
locale: mailInfo.locale,
deviceName
})
})
return { return {
deviceId, deviceId,
deviceAuthToken deviceAuthToken

View file

@ -108,6 +108,55 @@ export const sendTaskDoneMail = async ({ receiver, child, task }: {
}) })
} }
export const sendDeviceLinkedMail = async ({ receiver, deviceName, locale }: {
receiver: string
deviceName: string
locale: string
}) => {
await email.send({
template: join(__dirname, '../../other/mail/device-linked-by-mail'),
message: {
to: receiver
},
locals: {
subject: locale === 'de' ? 'Gerät hinzugefügt' : 'Device added',
preText: locale === 'de' ? 'Soeben wurde das Gerät' : 'The device',
deviceName,
postText: locale === 'de' ? 'über Ihre E-Mail-Adresse hinzugefügt.' : 'was added using your mail address.',
securityText: getMailSecurityText(locale),
mailimprint
}
})
}
export const sendPasswordRecoveryUsedMail = async ({ receiver, locale }: {
receiver: string
locale: string
}) => {
await email.send({
template: join(__dirname, '../../other/mail/password-recovery-used'),
message: {
to: receiver
},
locals: {
subject: locale === 'de' ? 'Passwort-Vergessen-Funktion verwendet' : 'Password reset',
text: locale === 'de' ?
'Soeben wurde Ihr TimeLimit-Passwort mit der Passwort-Vergessen-Funktion geändert.' :
'Your password was changed using the password reset feature.',
securityText: getMailSecurityText(locale),
mailimprint
}
})
}
function getMailSecurityText (locale: string) {
if (locale === 'de') {
return 'Achten Sie darauf, dass Ihr Kind/Ihre Kinder keinen Zugang zu der E-Mail-Adresse hat/haben, die Sie bei TimeLimit angegeben haben.'
} else {
return 'Make sure that your child/children can not access the mail addresss that you use for TimeLimit.'
}
}
export function isMailServerBlacklisted (mail: string): boolean { export function isMailServerBlacklisted (mail: string): boolean {
const parts = mail.split('@') const parts = mail.split('@')
const domain = parts[parts.length - 1] const domain = parts[parts.length - 1]