feat(backend): signup email localized (#8459)

* localized registration email

* localized email verification email

* localized reset password email
This commit is contained in:
Moriz Wahl 2025-05-05 20:44:14 +02:00 committed by GitHub
parent 63dd215297
commit 65f764f6d9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
37 changed files with 1907 additions and 50 deletions

View File

@ -62,6 +62,10 @@ a.button {
border-radius: 4px;
}
span {
color: #17b53e;
}
.text-block {
margin-top: 20px;
color: #000000;
@ -185,6 +189,10 @@ a.button {
border-radius: 4px;
}
span {
color: #17b53e;
}
.text-block {
margin-top: 20px;
color: #000000;

View File

@ -0,0 +1,261 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`sendEmailVerification English renders correctly 1`] = `
{
"attachments": [],
"from": "ocelot.social",
"html": "<!DOCTYPE html>
<html lang="en">
<head>
<meta content="multipart/html; charset=UTF-8" http-equiv="content-type">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>.wf-force-outline-none[tabindex="-1"]:focus{outline:none;}</style>
<style>body{
display: block;
font-family: Lato, sans-serif;
font-size: 17px;
text-align: left;
text-align: -webkit-left;
justify-content: center;
padding: 15px;
margin: 0px;
}
h2 {
margin-top: 25px;
font-size: 25px;
font-weight: normal;
line-height: 22px;
color: #333333;
}
.container {
max-width: 680px;
margin: 0 auto;
display: block;
}
.head-logo {
width: 60%;
height: auto;
display: block;
margin-left: auto;
margin-right: auto;
}
a {
color: #17b53e;
}
a.button {
background: #17b53e;
font-family: Lato, sans-serif;
font-size: 16px;
line-height: 15px;
text-decoration: none;
text-align:center;
padding: 13px 17px;
color: #ffffff;
display: table;
margin-left: auto;
margin-right: auto;
border-radius: 4px;
}
span {
color: #17b53e;
}
.text-block {
margin-top: 20px;
color: #000000;
}
footer {
padding: 20px;
font-family: Lato, sans-serif;
font-size: 12px;
line-height: 15px;
text-align: center;
color: #888888;
}
</style>
</head>
<body>
<div class="container">
<header>
<div class="head"><img class="head-logo" alt="Welcome Image" loading="lazy" src="http://webapp:3000/img/custom/logo-squared.svg">
</div>
</header>
<h2>Hello User,</h2>
<div class="wrapper">
<div class="content"></div>
<p>So, you want to change your e-mail? No problem! Just click the button below to verify your new address:</p><a class="button" href="http://webapp:3000/settings/my-email-address/verify?email=user%40example.org&amp;nonce=123456">Verify e-mail address</a>
<p>If you don't want to change your e-mail address feel free to ignore this message. </p>
<p>If the above button doesn't work, you can also copy the following code into your browser window: <span>123456</span></p>
<div class="text-block">
<p>See you soon on <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p>
<p> The ocelot.social Team</p>
</div>
</div>
<footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
</footer>
</div>
</body>
</html>",
"subject": "New E-Mail Address ocelot.social",
"text": "HELLO USER,
So, you want to change your e-mail? No problem! Just click the button below to
verify your new address:
Verify e-mail address
[http://webapp:3000/settings/my-email-address/verify?email=user%40example.org&nonce=123456]
If you don't want to change your e-mail address feel free to ignore this
message.
If the above button doesn't work, you can also copy the following code into your
browser window: 123456
See you soon on ocelot.social [https://ocelot.social]!
The ocelot.social Team
ocelot.social Community [https://ocelot.social]",
"to": "user@example.org",
}
`;
exports[`sendEmailVerification German renders correctly 1`] = `
{
"attachments": [],
"from": "ocelot.social",
"html": "<!DOCTYPE html>
<html lang="de">
<head>
<meta content="multipart/html; charset=UTF-8" http-equiv="content-type">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>.wf-force-outline-none[tabindex="-1"]:focus{outline:none;}</style>
<style>body{
display: block;
font-family: Lato, sans-serif;
font-size: 17px;
text-align: left;
text-align: -webkit-left;
justify-content: center;
padding: 15px;
margin: 0px;
}
h2 {
margin-top: 25px;
font-size: 25px;
font-weight: normal;
line-height: 22px;
color: #333333;
}
.container {
max-width: 680px;
margin: 0 auto;
display: block;
}
.head-logo {
width: 60%;
height: auto;
display: block;
margin-left: auto;
margin-right: auto;
}
a {
color: #17b53e;
}
a.button {
background: #17b53e;
font-family: Lato, sans-serif;
font-size: 16px;
line-height: 15px;
text-decoration: none;
text-align:center;
padding: 13px 17px;
color: #ffffff;
display: table;
margin-left: auto;
margin-right: auto;
border-radius: 4px;
}
span {
color: #17b53e;
}
.text-block {
margin-top: 20px;
color: #000000;
}
footer {
padding: 20px;
font-family: Lato, sans-serif;
font-size: 12px;
line-height: 15px;
text-align: center;
color: #888888;
}
</style>
</head>
<body>
<div class="container">
<header>
<div class="head"><img class="head-logo" alt="Welcome Image" loading="lazy" src="http://webapp:3000/img/custom/logo-squared.svg">
</div>
</header>
<h2>Hallo User,</h2>
<div class="wrapper">
<div class="content"></div>
<p>Du möchtest also deine E-Mail ändern? Kein Problem! Mit Klick auf diesen Button kannst Du Deine neue E-Mail Adresse bestätigen:</p><a class="button" href="http://webapp:3000/settings/my-email-address/verify?email=user%40example.org&amp;nonce=123456">E-Mail Adresse bestätigen</a>
<p>Falls Du deine E-Mail Adresse doch nicht ändern möchtest, kannst du diese Nachricht einfach ignorieren. </p>
<p>Sollte der Button für Dich nicht funktionieren, kannst Du auch folgenden Code in Dein Browserfenster kopieren: <span>123456</span></p>
<div class="text-block">
<p>Bis bald bei <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p>
<p> Dein ocelot.social Team</p>
</div>
</div>
<footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
</footer>
</div>
</body>
</html>",
"subject": "Neue E-Mail Addresse ocelot.social",
"text": "HALLO USER,
Du möchtest also deine E-Mail ändern? Kein Problem! Mit Klick auf diesen Button
kannst Du Deine neue E-Mail Adresse bestätigen:
E-Mail Adresse bestätigen
[http://webapp:3000/settings/my-email-address/verify?email=user%40example.org&nonce=123456]
Falls Du deine E-Mail Adresse doch nicht ändern möchtest, kannst du diese
Nachricht einfach ignorieren.
Sollte der Button für Dich nicht funktionieren, kannst Du auch folgenden Code in
Dein Browserfenster kopieren: 123456
Bis bald bei ocelot.social [https://ocelot.social]!
Dein ocelot.social Team
ocelot.social Community [https://ocelot.social]",
"to": "user@example.org",
}
`;

View File

@ -62,6 +62,10 @@ a.button {
border-radius: 4px;
}
span {
color: #17b53e;
}
.text-block {
margin-top: 20px;
color: #000000;
@ -184,6 +188,10 @@ a.button {
border-radius: 4px;
}
span {
color: #17b53e;
}
.text-block {
margin-top: 20px;
color: #000000;
@ -308,6 +316,10 @@ a.button {
border-radius: 4px;
}
span {
color: #17b53e;
}
.text-block {
margin-top: 20px;
color: #000000;
@ -432,6 +444,10 @@ a.button {
border-radius: 4px;
}
span {
color: #17b53e;
}
.text-block {
margin-top: 20px;
color: #000000;
@ -556,6 +572,10 @@ a.button {
border-radius: 4px;
}
span {
color: #17b53e;
}
.text-block {
margin-top: 20px;
color: #000000;
@ -679,6 +699,10 @@ a.button {
border-radius: 4px;
}
span {
color: #17b53e;
}
.text-block {
margin-top: 20px;
color: #000000;
@ -801,6 +825,10 @@ a.button {
border-radius: 4px;
}
span {
color: #17b53e;
}
.text-block {
margin-top: 20px;
color: #000000;
@ -920,6 +948,10 @@ a.button {
border-radius: 4px;
}
span {
color: #17b53e;
}
.text-block {
margin-top: 20px;
color: #000000;
@ -1043,6 +1075,10 @@ a.button {
border-radius: 4px;
}
span {
color: #17b53e;
}
.text-block {
margin-top: 20px;
color: #000000;
@ -1166,6 +1202,10 @@ a.button {
border-radius: 4px;
}
span {
color: #17b53e;
}
.text-block {
margin-top: 20px;
color: #000000;
@ -1288,6 +1328,10 @@ a.button {
border-radius: 4px;
}
span {
color: #17b53e;
}
.text-block {
margin-top: 20px;
color: #000000;
@ -1412,6 +1456,10 @@ a.button {
border-radius: 4px;
}
span {
color: #17b53e;
}
.text-block {
margin-top: 20px;
color: #000000;
@ -1536,6 +1584,10 @@ a.button {
border-radius: 4px;
}
span {
color: #17b53e;
}
.text-block {
margin-top: 20px;
color: #000000;
@ -1660,6 +1712,10 @@ a.button {
border-radius: 4px;
}
span {
color: #17b53e;
}
.text-block {
margin-top: 20px;
color: #000000;
@ -1783,6 +1839,10 @@ a.button {
border-radius: 4px;
}
span {
color: #17b53e;
}
.text-block {
margin-top: 20px;
color: #000000;
@ -1905,6 +1965,10 @@ a.button {
border-radius: 4px;
}
span {
color: #17b53e;
}
.text-block {
margin-top: 20px;
color: #000000;
@ -2024,6 +2088,10 @@ a.button {
border-radius: 4px;
}
span {
color: #17b53e;
}
.text-block {
margin-top: 20px;
color: #000000;
@ -2147,6 +2215,10 @@ a.button {
border-radius: 4px;
}
span {
color: #17b53e;
}
.text-block {
margin-top: 20px;
color: #000000;

View File

@ -0,0 +1,559 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`sendRegistrationMail with invite code English renders correctly 1`] = `
{
"attachments": [],
"from": "ocelot.social",
"html": "<!DOCTYPE html>
<html lang="en">
<head>
<meta content="multipart/html; charset=UTF-8" http-equiv="content-type">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>.wf-force-outline-none[tabindex="-1"]:focus{outline:none;}</style>
<style>body{
display: block;
font-family: Lato, sans-serif;
font-size: 17px;
text-align: left;
text-align: -webkit-left;
justify-content: center;
padding: 15px;
margin: 0px;
}
h2 {
margin-top: 25px;
font-size: 25px;
font-weight: normal;
line-height: 22px;
color: #333333;
}
.container {
max-width: 680px;
margin: 0 auto;
display: block;
}
.head-logo {
width: 60%;
height: auto;
display: block;
margin-left: auto;
margin-right: auto;
}
a {
color: #17b53e;
}
a.button {
background: #17b53e;
font-family: Lato, sans-serif;
font-size: 16px;
line-height: 15px;
text-decoration: none;
text-align:center;
padding: 13px 17px;
color: #ffffff;
display: table;
margin-left: auto;
margin-right: auto;
border-radius: 4px;
}
span {
color: #17b53e;
}
.text-block {
margin-top: 20px;
color: #000000;
}
footer {
padding: 20px;
font-family: Lato, sans-serif;
font-size: 12px;
line-height: 15px;
text-align: center;
color: #888888;
}
</style>
</head>
<body>
<div class="container">
<header>
<div class="head"><img class="head-logo" alt="Welcome Image" loading="lazy" src="http://webapp:3000/img/custom/logo-squared.svg">
</div>
</header>
<h2>Welcome to ocelot.social!</h2>
<div class="wrapper">
<div class="content"></div>
<p>Thank you for joining our cause it's awesome to have you on board. There's just one tiny step missing before we can start shaping the world together … Please confirm your e-mail address by clicking the button below:</p><a class="button" href="http://webapp:3000/registration?email=user%40example.org&amp;nonce=123456&amp;inviteCode=welcome&amp;method=invite-code">Confirm your e-mail address</a>
<p>If the above button doesn't work, you can also copy the following code into your browser window: <span>123456</span></p>
<p>However, this only works if you have registered through our website.</p>
<p>If you didn't sign up for <a>ocelot.social</a> we recommend you to check it out! It's a social network from people for people who want to connect and change the world together.
</p>
<p>PS: If you ignore this e-mail we will not create an account for you. ;)</p>
<div class="text-block">
<p>See you soon on <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p>
<p> The ocelot.social Team</p>
</div>
</div>
<footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
</footer>
</div>
</body>
</html>",
"subject": "Welcome to ocelot.social",
"text": "WELCOME TO OCELOT.SOCIAL!
Thank you for joining our cause it's awesome to have you on board. There's
just one tiny step missing before we can start shaping the world together …
Please confirm your e-mail address by clicking the button below:
Confirm your e-mail address
[http://webapp:3000/registration?email=user%40example.org&nonce=123456&inviteCode=welcome&method=invite-code]
If the above button doesn't work, you can also copy the following code into your
browser window: 123456
However, this only works if you have registered through our website.
If you didn't sign up for ocelot.social we recommend you to check it out! It's a
social network from people for people who want to connect and change the world
together.
PS: If you ignore this e-mail we will not create an account for you. ;)
See you soon on ocelot.social [https://ocelot.social]!
The ocelot.social Team
ocelot.social Community [https://ocelot.social]",
"to": "user@example.org",
}
`;
exports[`sendRegistrationMail with invite code German renders correctly 1`] = `
{
"attachments": [],
"from": "ocelot.social",
"html": "<!DOCTYPE html>
<html lang="de">
<head>
<meta content="multipart/html; charset=UTF-8" http-equiv="content-type">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>.wf-force-outline-none[tabindex="-1"]:focus{outline:none;}</style>
<style>body{
display: block;
font-family: Lato, sans-serif;
font-size: 17px;
text-align: left;
text-align: -webkit-left;
justify-content: center;
padding: 15px;
margin: 0px;
}
h2 {
margin-top: 25px;
font-size: 25px;
font-weight: normal;
line-height: 22px;
color: #333333;
}
.container {
max-width: 680px;
margin: 0 auto;
display: block;
}
.head-logo {
width: 60%;
height: auto;
display: block;
margin-left: auto;
margin-right: auto;
}
a {
color: #17b53e;
}
a.button {
background: #17b53e;
font-family: Lato, sans-serif;
font-size: 16px;
line-height: 15px;
text-decoration: none;
text-align:center;
padding: 13px 17px;
color: #ffffff;
display: table;
margin-left: auto;
margin-right: auto;
border-radius: 4px;
}
span {
color: #17b53e;
}
.text-block {
margin-top: 20px;
color: #000000;
}
footer {
padding: 20px;
font-family: Lato, sans-serif;
font-size: 12px;
line-height: 15px;
text-align: center;
color: #888888;
}
</style>
</head>
<body>
<div class="container">
<header>
<div class="head"><img class="head-logo" alt="Welcome Image" loading="lazy" src="http://webapp:3000/img/custom/logo-squared.svg">
</div>
</header>
<h2>Willkommen bei ocelot.social!</h2>
<div class="wrapper">
<div class="content"></div>
<p>Danke, dass du dich angemeldet hast wir freuen uns, dich dabei zu haben. Jetzt fehlt nur noch eine Kleinigkeit, bevor wir gemeinsam die Welt verbessern können … Bitte bestätige Deine E-Mail Adresse:</p><a class="button" href="http://webapp:3000/registration?email=user%40example.org&amp;nonce=123456&amp;inviteCode=welcome&amp;method=invite-code">Bestätige Deine E-Mail Adresse</a>
<p>Sollte der Button für Dich nicht funktionieren, kannst Du auch folgenden Code in Dein Browserfenster kopieren: <span>123456</span></p>
<p>Das funktioniert allerdings nur, wenn du Dich über unsere Website registriert hast.</p>
<p>Falls Du Dich nicht selbst bei <a>ocelot.social</a> angemeldet hast, schau doch mal vorbei! Wir sind ein gemeinnütziges Aktionsnetzwerk von Menschen für Menschen.
</p>
<p>PS: Wenn Du keinen Account bei uns möchtest, kannst Du diese E-Mail einfach ignorieren. ;)</p>
<div class="text-block">
<p>Bis bald bei <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p>
<p> Dein ocelot.social Team</p>
</div>
</div>
<footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
</footer>
</div>
</body>
</html>",
"subject": "Willkommen bei ocelot.social",
"text": "WILLKOMMEN BEI OCELOT.SOCIAL!
Danke, dass du dich angemeldet hast wir freuen uns, dich dabei zu haben. Jetzt
fehlt nur noch eine Kleinigkeit, bevor wir gemeinsam die Welt verbessern können
… Bitte bestätige Deine E-Mail Adresse:
Bestätige Deine E-Mail Adresse
[http://webapp:3000/registration?email=user%40example.org&nonce=123456&inviteCode=welcome&method=invite-code]
Sollte der Button für Dich nicht funktionieren, kannst Du auch folgenden Code in
Dein Browserfenster kopieren: 123456
Das funktioniert allerdings nur, wenn du Dich über unsere Website registriert
hast.
Falls Du Dich nicht selbst bei ocelot.social angemeldet hast, schau doch mal
vorbei! Wir sind ein gemeinnütziges Aktionsnetzwerk von Menschen für Menschen.
PS: Wenn Du keinen Account bei uns möchtest, kannst Du diese E-Mail einfach
ignorieren. ;)
Bis bald bei ocelot.social [https://ocelot.social]!
Dein ocelot.social Team
ocelot.social Community [https://ocelot.social]",
"to": "user@example.org",
}
`;
exports[`sendRegistrationMail without invite code English renders correctly 1`] = `
{
"attachments": [],
"from": "ocelot.social",
"html": "<!DOCTYPE html>
<html lang="en">
<head>
<meta content="multipart/html; charset=UTF-8" http-equiv="content-type">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>.wf-force-outline-none[tabindex="-1"]:focus{outline:none;}</style>
<style>body{
display: block;
font-family: Lato, sans-serif;
font-size: 17px;
text-align: left;
text-align: -webkit-left;
justify-content: center;
padding: 15px;
margin: 0px;
}
h2 {
margin-top: 25px;
font-size: 25px;
font-weight: normal;
line-height: 22px;
color: #333333;
}
.container {
max-width: 680px;
margin: 0 auto;
display: block;
}
.head-logo {
width: 60%;
height: auto;
display: block;
margin-left: auto;
margin-right: auto;
}
a {
color: #17b53e;
}
a.button {
background: #17b53e;
font-family: Lato, sans-serif;
font-size: 16px;
line-height: 15px;
text-decoration: none;
text-align:center;
padding: 13px 17px;
color: #ffffff;
display: table;
margin-left: auto;
margin-right: auto;
border-radius: 4px;
}
span {
color: #17b53e;
}
.text-block {
margin-top: 20px;
color: #000000;
}
footer {
padding: 20px;
font-family: Lato, sans-serif;
font-size: 12px;
line-height: 15px;
text-align: center;
color: #888888;
}
</style>
</head>
<body>
<div class="container">
<header>
<div class="head"><img class="head-logo" alt="Welcome Image" loading="lazy" src="http://webapp:3000/img/custom/logo-squared.svg">
</div>
</header>
<h2>Welcome to ocelot.social!</h2>
<div class="wrapper">
<div class="content"></div>
<p>Thank you for joining our cause it's awesome to have you on board. There's just one tiny step missing before we can start shaping the world together … Please confirm your e-mail address by clicking the button below:</p><a class="button" href="http://webapp:3000/registration?email=user%40example.org&amp;nonce=123456&amp;method=invite-mail">Confirm your e-mail address</a>
<p>If the above button doesn't work, you can also copy the following code into your browser window: <span>123456</span></p>
<p>However, this only works if you have registered through our website.</p>
<p>If you didn't sign up for <a>ocelot.social</a> we recommend you to check it out! It's a social network from people for people who want to connect and change the world together.
</p>
<p>PS: If you ignore this e-mail we will not create an account for you. ;)</p>
<div class="text-block">
<p>See you soon on <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p>
<p> The ocelot.social Team</p>
</div>
</div>
<footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
</footer>
</div>
</body>
</html>",
"subject": "Welcome to ocelot.social",
"text": "WELCOME TO OCELOT.SOCIAL!
Thank you for joining our cause it's awesome to have you on board. There's
just one tiny step missing before we can start shaping the world together …
Please confirm your e-mail address by clicking the button below:
Confirm your e-mail address
[http://webapp:3000/registration?email=user%40example.org&nonce=123456&method=invite-mail]
If the above button doesn't work, you can also copy the following code into your
browser window: 123456
However, this only works if you have registered through our website.
If you didn't sign up for ocelot.social we recommend you to check it out! It's a
social network from people for people who want to connect and change the world
together.
PS: If you ignore this e-mail we will not create an account for you. ;)
See you soon on ocelot.social [https://ocelot.social]!
The ocelot.social Team
ocelot.social Community [https://ocelot.social]",
"to": "user@example.org",
}
`;
exports[`sendRegistrationMail without invite code German renders correctly 1`] = `
{
"attachments": [],
"from": "ocelot.social",
"html": "<!DOCTYPE html>
<html lang="de">
<head>
<meta content="multipart/html; charset=UTF-8" http-equiv="content-type">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>.wf-force-outline-none[tabindex="-1"]:focus{outline:none;}</style>
<style>body{
display: block;
font-family: Lato, sans-serif;
font-size: 17px;
text-align: left;
text-align: -webkit-left;
justify-content: center;
padding: 15px;
margin: 0px;
}
h2 {
margin-top: 25px;
font-size: 25px;
font-weight: normal;
line-height: 22px;
color: #333333;
}
.container {
max-width: 680px;
margin: 0 auto;
display: block;
}
.head-logo {
width: 60%;
height: auto;
display: block;
margin-left: auto;
margin-right: auto;
}
a {
color: #17b53e;
}
a.button {
background: #17b53e;
font-family: Lato, sans-serif;
font-size: 16px;
line-height: 15px;
text-decoration: none;
text-align:center;
padding: 13px 17px;
color: #ffffff;
display: table;
margin-left: auto;
margin-right: auto;
border-radius: 4px;
}
span {
color: #17b53e;
}
.text-block {
margin-top: 20px;
color: #000000;
}
footer {
padding: 20px;
font-family: Lato, sans-serif;
font-size: 12px;
line-height: 15px;
text-align: center;
color: #888888;
}
</style>
</head>
<body>
<div class="container">
<header>
<div class="head"><img class="head-logo" alt="Welcome Image" loading="lazy" src="http://webapp:3000/img/custom/logo-squared.svg">
</div>
</header>
<h2>Willkommen bei ocelot.social!</h2>
<div class="wrapper">
<div class="content"></div>
<p>Danke, dass du dich angemeldet hast wir freuen uns, dich dabei zu haben. Jetzt fehlt nur noch eine Kleinigkeit, bevor wir gemeinsam die Welt verbessern können … Bitte bestätige Deine E-Mail Adresse:</p><a class="button" href="http://webapp:3000/registration?email=user%40example.org&amp;nonce=123456&amp;method=invite-mail">Bestätige Deine E-Mail Adresse</a>
<p>Sollte der Button für Dich nicht funktionieren, kannst Du auch folgenden Code in Dein Browserfenster kopieren: <span>123456</span></p>
<p>Das funktioniert allerdings nur, wenn du Dich über unsere Website registriert hast.</p>
<p>Falls Du Dich nicht selbst bei <a>ocelot.social</a> angemeldet hast, schau doch mal vorbei! Wir sind ein gemeinnütziges Aktionsnetzwerk von Menschen für Menschen.
</p>
<p>PS: Wenn Du keinen Account bei uns möchtest, kannst Du diese E-Mail einfach ignorieren. ;)</p>
<div class="text-block">
<p>Bis bald bei <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p>
<p> Dein ocelot.social Team</p>
</div>
</div>
<footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
</footer>
</div>
</body>
</html>",
"subject": "Willkommen bei ocelot.social",
"text": "WILLKOMMEN BEI OCELOT.SOCIAL!
Danke, dass du dich angemeldet hast wir freuen uns, dich dabei zu haben. Jetzt
fehlt nur noch eine Kleinigkeit, bevor wir gemeinsam die Welt verbessern können
… Bitte bestätige Deine E-Mail Adresse:
Bestätige Deine E-Mail Adresse
[http://webapp:3000/registration?email=user%40example.org&nonce=123456&method=invite-mail]
Sollte der Button für Dich nicht funktionieren, kannst Du auch folgenden Code in
Dein Browserfenster kopieren: 123456
Das funktioniert allerdings nur, wenn du Dich über unsere Website registriert
hast.
Falls Du Dich nicht selbst bei ocelot.social angemeldet hast, schau doch mal
vorbei! Wir sind ein gemeinnütziges Aktionsnetzwerk von Menschen für Menschen.
PS: Wenn Du keinen Account bei uns möchtest, kannst Du diese E-Mail einfach
ignorieren. ;)
Bis bald bei ocelot.social [https://ocelot.social]!
Dein ocelot.social Team
ocelot.social Community [https://ocelot.social]",
"to": "user@example.org",
}
`;

View File

@ -0,0 +1,260 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`sendResetPasswordMail English renders correctly 1`] = `
{
"attachments": [],
"from": "ocelot.social",
"html": "<!DOCTYPE html>
<html lang="en">
<head>
<meta content="multipart/html; charset=UTF-8" http-equiv="content-type">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>.wf-force-outline-none[tabindex="-1"]:focus{outline:none;}</style>
<style>body{
display: block;
font-family: Lato, sans-serif;
font-size: 17px;
text-align: left;
text-align: -webkit-left;
justify-content: center;
padding: 15px;
margin: 0px;
}
h2 {
margin-top: 25px;
font-size: 25px;
font-weight: normal;
line-height: 22px;
color: #333333;
}
.container {
max-width: 680px;
margin: 0 auto;
display: block;
}
.head-logo {
width: 60%;
height: auto;
display: block;
margin-left: auto;
margin-right: auto;
}
a {
color: #17b53e;
}
a.button {
background: #17b53e;
font-family: Lato, sans-serif;
font-size: 16px;
line-height: 15px;
text-decoration: none;
text-align:center;
padding: 13px 17px;
color: #ffffff;
display: table;
margin-left: auto;
margin-right: auto;
border-radius: 4px;
}
span {
color: #17b53e;
}
.text-block {
margin-top: 20px;
color: #000000;
}
footer {
padding: 20px;
font-family: Lato, sans-serif;
font-size: 12px;
line-height: 15px;
text-align: center;
color: #888888;
}
</style>
</head>
<body>
<div class="container">
<header>
<div class="head"><img class="head-logo" alt="Welcome Image" loading="lazy" src="http://webapp:3000/img/custom/logo-squared.svg">
</div>
</header>
<h2>Hello Jenny Rostock,</h2>
<div class="wrapper">
<div class="content"></div>
<p>So, you forgot your password? No problem! Just click the button below to reset it within the next 24 hours:</p><a class="button" href="http://webapp:3000/password-reset/change-password?email=user%40example.org&amp;nonce=123456">Confirm your e-mail address</a>
<p>If you didn't request a new password feel free to ignore this e-mail.</p>
<p>If the above button doesn't work you can also copy the following code into your browser window: <span>123456</span></p>
<div class="text-block">
<p>See you soon on <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p>
<p> The ocelot.social Team</p>
</div>
</div>
<footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
</footer>
</div>
</body>
</html>",
"subject": "Reset Password ocelot.social",
"text": "HELLO JENNY ROSTOCK,
So, you forgot your password? No problem! Just click the button below to reset
it within the next 24 hours:
Confirm your e-mail address
[http://webapp:3000/password-reset/change-password?email=user%40example.org&nonce=123456]
If you didn't request a new password feel free to ignore this e-mail.
If the above button doesn't work you can also copy the following code into your
browser window: 123456
See you soon on ocelot.social [https://ocelot.social]!
The ocelot.social Team
ocelot.social Community [https://ocelot.social]",
"to": "user@example.org",
}
`;
exports[`sendResetPasswordMail German renders correctly 1`] = `
{
"attachments": [],
"from": "ocelot.social",
"html": "<!DOCTYPE html>
<html lang="de">
<head>
<meta content="multipart/html; charset=UTF-8" http-equiv="content-type">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>.wf-force-outline-none[tabindex="-1"]:focus{outline:none;}</style>
<style>body{
display: block;
font-family: Lato, sans-serif;
font-size: 17px;
text-align: left;
text-align: -webkit-left;
justify-content: center;
padding: 15px;
margin: 0px;
}
h2 {
margin-top: 25px;
font-size: 25px;
font-weight: normal;
line-height: 22px;
color: #333333;
}
.container {
max-width: 680px;
margin: 0 auto;
display: block;
}
.head-logo {
width: 60%;
height: auto;
display: block;
margin-left: auto;
margin-right: auto;
}
a {
color: #17b53e;
}
a.button {
background: #17b53e;
font-family: Lato, sans-serif;
font-size: 16px;
line-height: 15px;
text-decoration: none;
text-align:center;
padding: 13px 17px;
color: #ffffff;
display: table;
margin-left: auto;
margin-right: auto;
border-radius: 4px;
}
span {
color: #17b53e;
}
.text-block {
margin-top: 20px;
color: #000000;
}
footer {
padding: 20px;
font-family: Lato, sans-serif;
font-size: 12px;
line-height: 15px;
text-align: center;
color: #888888;
}
</style>
</head>
<body>
<div class="container">
<header>
<div class="head"><img class="head-logo" alt="Welcome Image" loading="lazy" src="http://webapp:3000/img/custom/logo-squared.svg">
</div>
</header>
<h2>Hallo Jenny Rostock,</h2>
<div class="wrapper">
<div class="content"></div>
<p>Du hast also dein Passwort vergessen? Kein Problem! Mit Klick auf diesen Button kannst du innerhalb der nächsten 24 Stunden dein Passwort zurücksetzen:</p><a class="button" href="http://webapp:3000/password-reset/change-password?email=user%40example.org&amp;nonce=123456">Bestätige Deine E-Mail Adresse</a>
<p>Falls du kein neues Passwort angefordert hast, kannst du diese E-Mail einfach ignorieren.</p>
<p>Sollte der Button für dich nicht funktionieren, kannst du auch folgenden Code in Dein Browserfenster kopieren: <span>123456</span></p>
<div class="text-block">
<p>Bis bald bei <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p>
<p> Dein ocelot.social Team</p>
</div>
</div>
<footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
</footer>
</div>
</body>
</html>",
"subject": "Neues Passwort ocelot.social",
"text": "HALLO JENNY ROSTOCK,
Du hast also dein Passwort vergessen? Kein Problem! Mit Klick auf diesen Button
kannst du innerhalb der nächsten 24 Stunden dein Passwort zurücksetzen:
Bestätige Deine E-Mail Adresse
[http://webapp:3000/password-reset/change-password?email=user%40example.org&nonce=123456]
Falls du kein neues Passwort angefordert hast, kannst du diese E-Mail einfach
ignorieren.
Sollte der Button für dich nicht funktionieren, kannst du auch folgenden Code in
Dein Browserfenster kopieren: 123456
Bis bald bei ocelot.social [https://ocelot.social]!
Dein ocelot.social Team
ocelot.social Community [https://ocelot.social]",
"to": "user@example.org",
}
`;

View File

@ -0,0 +1,255 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`sendWrongEmail English renders correctly 1`] = `
{
"attachments": [],
"from": "ocelot.social",
"html": "<!DOCTYPE html>
<html lang="en">
<head>
<meta content="multipart/html; charset=UTF-8" http-equiv="content-type">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>.wf-force-outline-none[tabindex="-1"]:focus{outline:none;}</style>
<style>body{
display: block;
font-family: Lato, sans-serif;
font-size: 17px;
text-align: left;
text-align: -webkit-left;
justify-content: center;
padding: 15px;
margin: 0px;
}
h2 {
margin-top: 25px;
font-size: 25px;
font-weight: normal;
line-height: 22px;
color: #333333;
}
.container {
max-width: 680px;
margin: 0 auto;
display: block;
}
.head-logo {
width: 60%;
height: auto;
display: block;
margin-left: auto;
margin-right: auto;
}
a {
color: #17b53e;
}
a.button {
background: #17b53e;
font-family: Lato, sans-serif;
font-size: 16px;
line-height: 15px;
text-decoration: none;
text-align:center;
padding: 13px 17px;
color: #ffffff;
display: table;
margin-left: auto;
margin-right: auto;
border-radius: 4px;
}
span {
color: #17b53e;
}
.text-block {
margin-top: 20px;
color: #000000;
}
footer {
padding: 20px;
font-family: Lato, sans-serif;
font-size: 12px;
line-height: 15px;
text-align: center;
color: #888888;
}
</style>
</head>
<body>
<div class="container">
<header>
<div class="head"><img class="head-logo" alt="Welcome Image" loading="lazy" src="http://webapp:3000/img/custom/logo-squared.svg">
</div>
</header>
<h2>Welcome to ocelot.social!</h2>
<div class="wrapper">
<div class="content"></div>
<p>You requested a password reset but unfortunately we couldn't find an account associated with your e-mail address. Did you maybe use another one when you signed up?</p><a class="button" href="http://webapp:3000/password-reset/request">Try a different e-mail</a>
<p>If you don't have an account at <a>ocelot.social</a> yet or if you didn't want to reset your password, please ignore this e-mail.
</p>
<div class="text-block">
<p>See you soon on <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p>
<p> The ocelot.social Team</p>
</div>
</div>
<footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
</footer>
</div>
</body>
</html>",
"subject": "Wrong E-mail? ocelot.social",
"text": "WELCOME TO OCELOT.SOCIAL!
You requested a password reset but unfortunately we couldn't find an account
associated with your e-mail address. Did you maybe use another one when you
signed up?
Try a different e-mail [http://webapp:3000/password-reset/request]
If you don't have an account at ocelot.social yet or if you didn't want to reset
your password, please ignore this e-mail.
See you soon on ocelot.social [https://ocelot.social]!
The ocelot.social Team
ocelot.social Community [https://ocelot.social]",
"to": "user@example.org",
}
`;
exports[`sendWrongEmail German renders correctly 1`] = `
{
"attachments": [],
"from": "ocelot.social",
"html": "<!DOCTYPE html>
<html lang="de">
<head>
<meta content="multipart/html; charset=UTF-8" http-equiv="content-type">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>.wf-force-outline-none[tabindex="-1"]:focus{outline:none;}</style>
<style>body{
display: block;
font-family: Lato, sans-serif;
font-size: 17px;
text-align: left;
text-align: -webkit-left;
justify-content: center;
padding: 15px;
margin: 0px;
}
h2 {
margin-top: 25px;
font-size: 25px;
font-weight: normal;
line-height: 22px;
color: #333333;
}
.container {
max-width: 680px;
margin: 0 auto;
display: block;
}
.head-logo {
width: 60%;
height: auto;
display: block;
margin-left: auto;
margin-right: auto;
}
a {
color: #17b53e;
}
a.button {
background: #17b53e;
font-family: Lato, sans-serif;
font-size: 16px;
line-height: 15px;
text-decoration: none;
text-align:center;
padding: 13px 17px;
color: #ffffff;
display: table;
margin-left: auto;
margin-right: auto;
border-radius: 4px;
}
span {
color: #17b53e;
}
.text-block {
margin-top: 20px;
color: #000000;
}
footer {
padding: 20px;
font-family: Lato, sans-serif;
font-size: 12px;
line-height: 15px;
text-align: center;
color: #888888;
}
</style>
</head>
<body>
<div class="container">
<header>
<div class="head"><img class="head-logo" alt="Welcome Image" loading="lazy" src="http://webapp:3000/img/custom/logo-squared.svg">
</div>
</header>
<h2>Willkommen bei ocelot.social!</h2>
<div class="wrapper">
<div class="content"></div>
<p>Du hast bei uns ein neues Passwort angefordert leider haben wir aber keinen Account mit deiner E-Mailadresse gefunden. Kann es sein, dass du mit einer anderen Adresse bei uns angemeldet bist?</p><a class="button" href="http://webapp:3000/password-reset/request">Versuch' es mit einer anderen E-Mail</a>
<p>Wenn du noch keinen Account bei <a>ocelot.social</a> hast oder dein Password gar nicht ändern willst, kannst du diese E-Mail einfach ignorieren!
</p>
<div class="text-block">
<p>Bis bald bei <a class="organization" href="https://ocelot.social">ocelot.social</a>!</p>
<p> Dein ocelot.social Team</p>
</div>
</div>
<footer>
<div class="footer"></div><a href="https://ocelot.social">ocelot.social Community</a>
</footer>
</div>
</body>
</html>",
"subject": "Falsche Mailaddresse? ocelot.social",
"text": "WILLKOMMEN BEI OCELOT.SOCIAL!
Du hast bei uns ein neues Passwort angefordert leider haben wir aber keinen
Account mit deiner E-Mailadresse gefunden. Kann es sein, dass du mit einer
anderen Adresse bei uns angemeldet bist?
Versuch' es mit einer anderen E-Mail [http://webapp:3000/password-reset/request]
Wenn du noch keinen Account bei ocelot.social hast oder dein Password gar nicht
ändern willst, kannst du diese E-Mail einfach ignorieren!
Bis bald bei ocelot.social [https://ocelot.social]!
Dein ocelot.social Team
ocelot.social Community [https://ocelot.social]",
"to": "user@example.org",
}
`;

View File

@ -7,12 +7,32 @@
"followedUserPosted": "Neuer Beitrag von gefolgtem Nutzer",
"mentionedInComment": "Erwähnung in Kommentar",
"mentionedInPost": "Erwähnung in Beitrag",
"newEmail": "Neue E-Mail Addresse",
"removedUserFromGroup": "Aus Gruppe entfernt",
"postInGroup": "Neuer Beitrag in Gruppe",
"resetPassword": "Neues Passwort",
"userJoinedGroup": "Nutzer tritt Gruppe bei",
"userLeftGroup": "Nutzer verlässt Gruppe"
"userLeftGroup": "Nutzer verlässt Gruppe",
"wrongEmail": "Falsche Mailaddresse?"
},
"registration": {
"introduction": "Danke, dass du dich angemeldet hast wir freuen uns, dich dabei zu haben. Jetzt fehlt nur noch eine Kleinigkeit, bevor wir gemeinsam die Welt verbessern können … Bitte bestätige Deine E-Mail Adresse:",
"codeHint": "Sollte der Button für Dich nicht funktionieren, kannst Du auch folgenden Code in Dein Browserfenster kopieren: ",
"codeHintException": "Das funktioniert allerdings nur, wenn du Dich über unsere Website registriert hast.",
"notYouStart": "Falls Du Dich nicht selbst bei ",
"notYouEnd": " angemeldet hast, schau doch mal vorbei! Wir sind ein gemeinnütziges Aktionsnetzwerk von Menschen für Menschen.",
"ps": "PS: Wenn Du keinen Account bei uns möchtest, kannst Du diese E-Mail einfach ignorieren. ;)"
},
"emailVerification": {
"codeHint": "Sollte der Button für Dich nicht funktionieren, kannst Du auch folgenden Code in Dein Browserfenster kopieren: ",
"introduction": "Du möchtest also deine E-Mail ändern? Kein Problem! Mit Klick auf diesen Button kannst Du Deine neue E-Mail Adresse bestätigen:",
"doNotChange": "Falls Du deine E-Mail Adresse doch nicht ändern möchtest, kannst du diese Nachricht einfach ignorieren. "
},
"buttons": {
"confirmEmail": "Bestätige Deine E-Mail Adresse",
"resetPassword": "Passwort zurücksetzen",
"tryAgain": "Versuch' es mit einer anderen E-Mail",
"verifyEmail": "E-Mail Adresse bestätigen",
"viewChat": "Chat anzeigen",
"viewComment": "Kommentar ansehen",
"viewGroup": "Gruppe ansehen",
@ -23,7 +43,19 @@
"seeYou": "Bis bald bei ",
"yourTeam": " Dein {team} Team",
"settingsHint": "PS: Möchtest du keine E-Mails mehr erhalten, dann ändere deine ",
"settingsName": "Benachrichtigungseinstellungen"
"settingsName": "Benachrichtigungseinstellungen",
"welcome": "Willkommen bei"
},
"resetPassword": {
"codeHint": "Sollte der Button für dich nicht funktionieren, kannst du auch folgenden Code in Dein Browserfenster kopieren: ",
"ignore": "Falls du kein neues Passwort angefordert hast, kannst du diese E-Mail einfach ignorieren.",
"introduction": "Du hast also dein Passwort vergessen? Kein Problem! Mit Klick auf diesen Button kannst du innerhalb der nächsten 24 Stunden dein Passwort zurücksetzen:"
},
"wrongEmail": {
"codeHint": "Sollte der Button für Dich nicht funktionieren, kannst Du auch folgenden Code in Dein Browserfenster kopieren: ",
"ignoreEnd": " hast oder dein Password gar nicht ändern willst, kannst du diese E-Mail einfach ignorieren!",
"ignoreStart": "Wenn du noch keinen Account bei ",
"introduction": "Du hast bei uns ein neues Passwort angefordert leider haben wir aber keinen Account mit deiner E-Mailadresse gefunden. Kann es sein, dass du mit einer anderen Adresse bei uns angemeldet bist?"
},
"changedGroupMemberRole": "deine Rolle in der Gruppe „{groupName}“ wurde geändert. Klicke auf den Knopf, um diese Gruppe zu sehen:",
"chatMessageStart": "du hast eine neue Chat-Nachricht von ",

View File

@ -7,12 +7,32 @@
"followedUserPosted": "New post by followd user",
"mentionedInComment": "Mentioned in comment",
"mentionedInPost": "Mentioned in post",
"newEmail": "New E-Mail Address",
"removedUserFromGroup": "Removed from group",
"postInGroup": "New post in group",
"resetPassword": "Reset Password",
"userJoinedGroup": "User joined group",
"userLeftGroup": "User left group"
"userLeftGroup": "User left group",
"wrongEmail": "Wrong E-mail?"
},
"registration": {
"introduction": "Thank you for joining our cause it's awesome to have you on board. There's just one tiny step missing before we can start shaping the world together … Please confirm your e-mail address by clicking the button below:",
"codeHint": "If the above button doesn't work, you can also copy the following code into your browser window: ",
"codeHintException": "However, this only works if you have registered through our website.",
"notYouStart": "If you didn't sign up for ",
"notYouEnd": " we recommend you to check it out! It's a social network from people for people who want to connect and change the world together.",
"ps": "PS: If you ignore this e-mail we will not create an account for you. ;)"
},
"emailVerification": {
"codeHint": "If the above button doesn't work, you can also copy the following code into your browser window: ",
"introduction": "So, you want to change your e-mail? No problem! Just click the button below to verify your new address:",
"doNotChange": "If you don't want to change your e-mail address feel free to ignore this message. "
},
"buttons": {
"confirmEmail": "Confirm your e-mail address",
"resetPassword": "Reset password",
"tryAgain": "Try a different e-mail",
"verifyEmail": "Verify e-mail address",
"viewChat": "Show Chat",
"viewComment": "View comment",
"viewGroup": "View group",
@ -23,7 +43,18 @@
"seeYou": "See you soon on ",
"yourTeam": " The {team} Team",
"settingsHint": "PS: If you don't want to receive e-mails anymore, change your ",
"settingsName": "notification settings"
"settingsName": "notification settings",
"welcome": "Welcome to"
},
"resetPassword": {
"codeHint": "If the above button doesn't work you can also copy the following code into your browser window: ",
"ignore": "If you didn't request a new password feel free to ignore this e-mail.",
"introduction": "So, you forgot your password? No problem! Just click the button below to reset it within the next 24 hours:"
},
"wrongEmail": {
"ignoreEnd": " yet or if you didn't want to reset your password, please ignore this e-mail.",
"ignoreStart": "If you don't have an account at ",
"introduction": "You requested a password reset but unfortunately we couldn't find an account associated with your e-mail address. Did you maybe use another one when you signed up?"
},
"changedGroupMemberRole": "your role in the group “{groupName}” has been changed. Click on the button to view this group:",
"chatMessageStart": "you have received a new chat message from ",

View File

@ -28,6 +28,7 @@ const defaultParams = {
ORGANIZATION_URL: CONFIG.ORGANIZATION_URL,
supportUrl: CONFIG.SUPPORT_URL,
settingsUrl,
renderSettingsUrl: true,
}
export const transport = createTransport({
@ -202,3 +203,137 @@ export const sendChatMessageMail = async (
throw new Error(error)
}
}
interface VerifyMailInput {
email: string
nonce: string
locale: string
}
interface RegistrationMailInput extends VerifyMailInput {
inviteCode?: string
}
export const sendRegistrationMail = async (
data: RegistrationMailInput,
): Promise<OriginalMessage> => {
const { nonce, locale, inviteCode } = data
const to = data.email
const actionUrl = new URL('/registration', CONFIG.CLIENT_URI)
actionUrl.searchParams.set('email', to)
actionUrl.searchParams.set('nonce', nonce)
if (inviteCode) {
actionUrl.searchParams.set('inviteCode', inviteCode)
actionUrl.searchParams.set('method', 'invite-code')
} else {
actionUrl.searchParams.set('method', 'invite-mail')
}
try {
const { originalMessage } = await email.send({
template: path.join(__dirname, 'templates', 'registration'),
message: {
to,
},
locals: {
...defaultParams,
locale,
actionUrl,
nonce,
renderSettingsUrl: false,
},
})
return originalMessage as OriginalMessage
} catch (error) {
throw new Error(error)
}
}
interface EmailVerificationInput extends VerifyMailInput {
name: string
}
export const sendEmailVerification = async (
data: EmailVerificationInput,
): Promise<OriginalMessage> => {
const { nonce, locale, name } = data
const to = data.email
const actionUrl = new URL('/settings/my-email-address/verify', CONFIG.CLIENT_URI)
actionUrl.searchParams.set('email', to)
actionUrl.searchParams.set('nonce', nonce)
try {
const { originalMessage } = await email.send({
template: path.join(__dirname, 'templates', 'emailVerification'),
message: {
to,
},
locals: {
...defaultParams,
locale,
actionUrl,
nonce,
name,
renderSettingsUrl: false,
},
})
return originalMessage as OriginalMessage
} catch (error) {
throw new Error(error)
}
}
export const sendResetPasswordMail = async (
data: EmailVerificationInput,
): Promise<OriginalMessage> => {
const { nonce, locale, name } = data
const to = data.email
const actionUrl = new URL('/password-reset/change-password', CONFIG.CLIENT_URI)
actionUrl.searchParams.set('email', to)
actionUrl.searchParams.set('nonce', nonce)
try {
const { originalMessage } = await email.send({
template: path.join(__dirname, 'templates', 'resetPassword'),
message: {
to,
},
locals: {
...defaultParams,
locale,
actionUrl,
nonce,
name,
renderSettingsUrl: false,
},
})
return originalMessage as OriginalMessage
} catch (error) {
throw new Error(error)
}
}
export const sendWrongEmail = async (data: {
locale: string
email: string
}): Promise<OriginalMessage> => {
const { locale } = data
const to = data.email
const actionUrl = new URL('/password-reset/request', CONFIG.CLIENT_URI)
try {
const { originalMessage } = await email.send({
template: path.join(__dirname, 'templates', 'wrongEmail'),
message: {
to,
},
locals: {
...defaultParams,
locale,
actionUrl,
renderSettingsUrl: false,
},
})
return originalMessage as OriginalMessage
} catch (error) {
throw new Error(error)
}
}

View File

@ -0,0 +1,35 @@
import { sendEmailVerification } from './sendEmail'
describe('sendEmailVerification', () => {
const data: {
email: string
nonce: string
locale: string
name: string
} = {
email: 'user@example.org',
nonce: '123456',
locale: 'en',
name: 'User',
}
describe('English', () => {
beforeEach(() => {
data.locale = 'en'
})
it('renders correctly', async () => {
await expect(sendEmailVerification(data)).resolves.toMatchSnapshot()
})
})
describe('German', () => {
beforeEach(() => {
data.locale = 'de'
})
it('renders correctly', async () => {
await expect(sendEmailVerification(data)).resolves.toMatchSnapshot()
})
})
})

View File

@ -0,0 +1,63 @@
import { sendRegistrationMail } from './sendEmail'
describe('sendRegistrationMail', () => {
const data: {
email: string
nonce: string
locale: string
inviteCode?: string
} = {
email: 'user@example.org',
nonce: '123456',
locale: 'en',
inviteCode: 'welcome',
}
describe('with invite code', () => {
describe('English', () => {
beforeEach(() => {
data.locale = 'en'
data.inviteCode = 'welcome'
})
it('renders correctly', async () => {
await expect(sendRegistrationMail(data)).resolves.toMatchSnapshot()
})
})
describe('German', () => {
beforeEach(() => {
data.locale = 'de'
data.inviteCode = 'welcome'
})
it('renders correctly', async () => {
await expect(sendRegistrationMail(data)).resolves.toMatchSnapshot()
})
})
})
describe('without invite code', () => {
describe('English', () => {
beforeEach(() => {
data.locale = 'en'
delete data.inviteCode
})
it('renders correctly', async () => {
await expect(sendRegistrationMail(data)).resolves.toMatchSnapshot()
})
})
describe('German', () => {
beforeEach(() => {
data.locale = 'de'
delete data.inviteCode
})
it('renders correctly', async () => {
await expect(sendRegistrationMail(data)).resolves.toMatchSnapshot()
})
})
})
})

View File

@ -0,0 +1,35 @@
import { sendResetPasswordMail } from './sendEmail'
describe('sendResetPasswordMail', () => {
const data: {
email: string
nonce: string
locale: string
name: string
} = {
email: 'user@example.org',
nonce: '123456',
locale: 'en',
name: 'Jenny Rostock',
}
describe('English', () => {
beforeEach(() => {
data.locale = 'en'
})
it('renders correctly', async () => {
await expect(sendResetPasswordMail(data)).resolves.toMatchSnapshot()
})
})
describe('German', () => {
beforeEach(() => {
data.locale = 'de'
})
it('renders correctly', async () => {
await expect(sendResetPasswordMail(data)).resolves.toMatchSnapshot()
})
})
})

View File

@ -0,0 +1,31 @@
import { sendWrongEmail } from './sendEmail'
describe('sendWrongEmail', () => {
const data: {
email: string
locale: string
} = {
email: 'user@example.org',
locale: 'en',
}
describe('English', () => {
beforeEach(() => {
data.locale = 'en'
})
it('renders correctly', async () => {
await expect(sendWrongEmail(data)).resolves.toMatchSnapshot()
})
})
describe('German', () => {
beforeEach(() => {
data.locale = 'de'
})
it('renders correctly', async () => {
await expect(sendWrongEmail(data)).resolves.toMatchSnapshot()
})
})
})

View File

@ -0,0 +1,10 @@
extend ../layout.pug
block content
.content
p= t('emailVerification.introduction')
a.button(href=actionUrl)= t('buttons.verifyEmail')
p= t('emailVerification.doNotChange')
p= t('emailVerification.codeHint')
span= nonce

View File

@ -0,0 +1 @@
= `${t('subjects.newEmail')} ${APPLICATION_NAME}`

View File

@ -3,12 +3,15 @@
- var organizationUrl = ORGANIZATION_URL
- var team = APPLICATION_NAME
- var settingsUrl = settingsUrl
- var renderSettingsUrl = renderSettingsUrl
p= t('general.seeYou')
a.organization(href=organizationUrl)= team
| !
p= t('general.yourTeam', { team })
br
p= t('general.settingsHint')
a.settings(href=settingsUrl)= t('general.settingsName')
| !
if renderSettingsUrl
br
p= t('general.settingsHint')
a.settings(href=settingsUrl)= t('general.settingsName')
| !

View File

@ -50,6 +50,10 @@ a.button {
border-radius: 4px;
}
span {
color: #17b53e;
}
.text-block {
margin-top: 20px;
color: #000000;

View File

@ -0,0 +1 @@
h2= `${t('general.welcome')} ${APPLICATION_NAME}!`

View File

@ -14,10 +14,14 @@ html(lang=locale)
style
include includes/webflow.css
- var name = name
body
div.container
include includes/header.pug
include includes/salutation.pug
if name
include includes/salutation.pug
else
include includes/welcome.pug
.wrapper
block content

View File

@ -0,0 +1,15 @@
extend ../layout.pug
block content
.content
p= t('registration.introduction')
a.button(href=actionUrl)= t('buttons.confirmEmail')
p= t('registration.codeHint')
span= nonce
p= t('registration.codeHintException')
p= t('registration.notYouStart')
a(href=ORGANIZATION_LINK)= APPLICATION_NAME
= t('registration.notYouEnd')
p= t('registration.ps')

View File

@ -0,0 +1 @@
= `${t('general.welcome')} ${APPLICATION_NAME}`

View File

@ -0,0 +1,9 @@
extend ../layout.pug
block content
.content
p= t('resetPassword.introduction')
a.button(href=actionUrl)= t('buttons.confirmEmail')
p= t('resetPassword.ignore')
p= t('resetPassword.codeHint')
span= nonce

View File

@ -0,0 +1 @@
= `${t('subjects.resetPassword')} ${APPLICATION_NAME}`

View File

@ -0,0 +1,10 @@
extend ../layout.pug
block content
.content
p= t('wrongEmail.introduction')
a.button(href=actionUrl)= t('buttons.tryAgain')
p= t('wrongEmail.ignoreStart')
a(href=ORGANIZATION_LINK)= APPLICATION_NAME
= t('wrongEmail.ignoreEnd')

View File

@ -0,0 +1 @@
= `${t('subjects.wrongEmail')} ${APPLICATION_NAME}`

View File

@ -69,6 +69,7 @@ export default {
)
return result.records.map((record) => ({
name: record.get('user').properties.name,
locale: record.get('user').properties.locale,
...record.get('email').properties,
}))
})

View File

@ -71,14 +71,14 @@ describe('passwordReset', () => {
describe('requestPasswordReset', () => {
const mutation = gql`
mutation ($email: String!) {
requestPasswordReset(email: $email)
mutation ($email: String!, $locale: String!) {
requestPasswordReset(email: $email, locale: $locale)
}
`
describe('with invalid email', () => {
beforeEach(() => {
variables = { ...variables, email: 'non-existent@example.org' }
variables = { ...variables, email: 'non-existent@example.org', locale: 'de' }
})
it('resolves anyways', async () => {
@ -96,7 +96,7 @@ describe('passwordReset', () => {
describe('with a valid email', () => {
beforeEach(() => {
variables = { ...variables, email: 'user@example.org' }
variables = { ...variables, email: 'user@example.org', locale: 'de' }
})
it('resolves', async () => {

View File

@ -50,14 +50,14 @@ afterEach(async () => {
describe('Signup', () => {
const mutation = gql`
mutation ($email: String!, $inviteCode: String) {
Signup(email: $email, inviteCode: $inviteCode) {
mutation ($email: String!, $locale: String!, $inviteCode: String) {
Signup(email: $email, locale: $locale, inviteCode: $inviteCode) {
email
}
}
`
beforeEach(() => {
variables = { ...variables, email: 'someuser@example.org' }
variables = { ...variables, email: 'someuser@example.org', locale: 'de' }
})
describe('unauthenticated', () => {

View File

@ -9,7 +9,11 @@ type Query {
}
type Mutation {
Signup(email: String!, inviteCode: String = null): EmailAddress
Signup(
email: String!
locale: String!
inviteCode: String = null
): EmailAddress
SignupVerification(
nonce: String!
email: String!

View File

@ -245,7 +245,7 @@ type Mutation {
updateOnlineStatus(status: OnlineStatus!): Boolean!
requestPasswordReset(email: String!): Boolean!
requestPasswordReset(email: String!, locale: String!): Boolean!
resetPassword(email: String!, nonce: String!, newPassword: String!): Boolean!
changePassword(oldPassword: String!, newPassword: String!): String!

View File

@ -2,43 +2,41 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { sendMail } from '@middleware/helpers/email/sendMail'
import {
signupTemplate,
resetPasswordTemplate,
wrongAccountTemplate,
emailVerificationTemplate,
} from '@middleware/helpers/email/templateBuilder'
sendRegistrationMail,
sendEmailVerification,
sendResetPasswordMail,
} from '@src/emails/sendEmail'
const sendSignupMail = async (resolve, root, args, context, resolveInfo) => {
const { inviteCode } = args
const { inviteCode, locale } = args
const response = await resolve(root, args, context, resolveInfo)
const { email, nonce } = response
if (nonce) {
// emails that already exist do not have a nonce
if (inviteCode) {
await sendMail(signupTemplate({ email, variables: { nonce, inviteCode } }))
} else {
await sendMail(signupTemplate({ email, variables: { nonce } }))
}
await sendRegistrationMail({ email, nonce, locale, inviteCode })
}
delete response.nonce
return response
}
const sendPasswordResetMail = async (resolve, root, args, context, resolveInfo) => {
const { email } = args
const { email, locale } = args
const { email: userFound, nonce, name } = await resolve(root, args, context, resolveInfo)
const template = userFound ? resetPasswordTemplate : wrongAccountTemplate
await sendMail(template({ email, variables: { nonce, name } }))
if (userFound) {
await sendResetPasswordMail({ email, nonce, name, locale })
} else {
// this is an antifeature allowing unauthenticated users to spam any email with wrong-email notifications
// await sendWrongEmail({ email, locale })
}
return true
}
const sendEmailVerificationMail = async (resolve, root, args, context, resolveInfo) => {
const response = await resolve(root, args, context, resolveInfo)
const { email, nonce, name } = response
const { email, nonce, name, locale } = response
if (nonce) {
await sendMail(emailVerificationTemplate({ email, variables: { nonce, name } }))
await sendEmailVerification({ email, nonce, name, locale })
}
delete response.nonce
return response

View File

@ -177,8 +177,8 @@ describe('authorization', () => {
describe('access Signup', () => {
const signupMutation = gql`
mutation ($email: String!, $inviteCode: String) {
Signup(email: $email, inviteCode: $inviteCode) {
mutation ($email: String!, $locale: String!, $inviteCode: String) {
Signup(email: $email, locale: $locale, inviteCode: $inviteCode) {
email
}
}
@ -189,6 +189,7 @@ describe('authorization', () => {
variables = {
email: 'some@email.org',
inviteCode: 'ABCDEF',
locale: 'de',
}
CONFIG.INVITE_REGISTRATION = false
CONFIG.PUBLIC_REGISTRATION = false
@ -231,6 +232,7 @@ describe('authorization', () => {
variables = {
email: 'some@email.org',
inviteCode: 'ABCDEF',
locale: 'de',
}
CONFIG.INVITE_REGISTRATION = false
CONFIG.PUBLIC_REGISTRATION = true
@ -269,6 +271,7 @@ describe('authorization', () => {
variables = {
email: 'some@email.org',
inviteCode: 'ABCDEF',
locale: 'de',
}
authenticatedUser = null
})
@ -288,6 +291,7 @@ describe('authorization', () => {
variables = {
email: 'some@email.org',
inviteCode: 'no valid invite code',
locale: 'de',
}
authenticatedUser = null
})

View File

@ -59,7 +59,12 @@ describe('Request', () => {
})
it('delivers email to backend', () => {
const expected = expect.objectContaining({ variables: { email: 'mail@example.org' } })
const expected = expect.objectContaining({
variables: {
email: 'mail@example.org',
locale: 'en',
},
})
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expected)
})
@ -92,7 +97,12 @@ describe('Request', () => {
})
it('normalizes email to lower case letters', () => {
const expected = expect.objectContaining({ variables: { email: 'mail@gmail.com' } })
const expected = expect.objectContaining({
variables: {
email: 'mail@gmail.com',
locale: 'en',
},
})
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expected)
})
})

View File

@ -85,13 +85,13 @@ export default {
},
async handleSubmit() {
const mutation = gql`
mutation ($email: String!) {
requestPasswordReset(email: $email)
mutation ($email: String!, $locale: String!) {
requestPasswordReset(email: $email, locale: $locale)
}
`
try {
const { email } = this
await this.$apollo.mutate({ mutation, variables: { email } })
await this.$apollo.mutate({ mutation, variables: { email, locale: this.$i18n.locale() } })
this.submitted = true
setTimeout(() => {

View File

@ -36,8 +36,8 @@ import normalizeEmail from '~/components/utils/NormalizeEmail'
import translateErrorMessage from '~/components/utils/TranslateErrorMessage'
export const SignupMutation = gql`
mutation ($email: String!, $inviteCode: String) {
Signup(email: $email, inviteCode: $inviteCode) {
mutation ($email: String!, $locale: String!, $inviteCode: String) {
Signup(email: $email, locale: $locale, inviteCode: $inviteCode) {
email
}
}
@ -140,7 +140,7 @@ export default {
async onNextClick() {
const { email } = this.formData
const { inviteCode = null } = this.sliderData.collectedInputData
const variables = { email, inviteCode }
const variables = { email, inviteCode, locale: this.$i18n.locale() }
if (this.sliderData.collectedInputData.emailSend && !this.sendEmailAgain) {
return true

View File

@ -25,6 +25,9 @@ describe('Signup', () => {
loading: false,
mutate: jest.fn().mockResolvedValue({ data: { Signup: { email: 'mail@example.org' } } }),
},
$i18n: {
locale: () => 'de',
},
}
propsData = {}
})
@ -64,7 +67,7 @@ describe('Signup', () => {
it('delivers email to backend', () => {
const expected = expect.objectContaining({
mutation: SignupMutation,
variables: { email: 'mAIL@exAMPLE.org', inviteCode: null },
variables: { email: 'mAIL@exAMPLE.org', locale: 'de', inviteCode: null },
})
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expected)
})

View File

@ -70,8 +70,8 @@ import { SweetalertIcon } from 'vue-sweetalert-icons'
import translateErrorMessage from '~/components/utils/TranslateErrorMessage'
export const SignupMutation = gql`
mutation ($email: String!, $inviteCode: String) {
Signup(email: $email, inviteCode: $inviteCode) {
mutation ($email: String!, $locale: String!, $inviteCode: String) {
Signup(email: $email, locale: $locale, inviteCode: $inviteCode) {
email
}
}
@ -121,7 +121,7 @@ export default {
try {
const response = await this.$apollo.mutate({
mutation: SignupMutation,
variables: { email, inviteCode: null },
variables: { email, locale: this.$i18n.locale(), inviteCode: null },
})
this.data = response.data
setTimeout(() => {