Merge branch 'master' of github.com:Human-Connection/Human-Connection into 1707-reporting-with-specific-information

This commit is contained in:
Wolfgang Huß 2019-10-04 09:08:15 +02:00
commit 5458d6640e
51 changed files with 3071 additions and 1945 deletions

View File

@ -6,16 +6,21 @@
**Fixed bugs:**
- 🐛 \[Bug\] Update maintenance page email [\#1731](https://github.com/Human-Connection/Human-Connection/issues/1731)
- 🐛 \[Bug\] Editing comments is not reactive again [\#1718](https://github.com/Human-Connection/Human-Connection/issues/1718)
- 🐛 \[Bug\] Comments with mentions in the end not displayed [\#1665](https://github.com/Human-Connection/Human-Connection/issues/1665)
- 🐛 \[Bug\] Delete the Sleep Icon [\#1659](https://github.com/Human-Connection/Human-Connection/issues/1659)
- 🐛 \[Bug\] Far to less Characters per Contribution \(2000\) [\#1639](https://github.com/Human-Connection/Human-Connection/issues/1639)
- 🐛 \[Bug\] Notifications do just update with page reload [\#1637](https://github.com/Human-Connection/Human-Connection/issues/1637)
- 🐛 \[Bug\] Can no users used all hashtags be correct? [\#1632](https://github.com/Human-Connection/Human-Connection/issues/1632)
- 🐛 \[Bug\] Create account has no info about email, no localisation, no HC logo [\#1631](https://github.com/Human-Connection/Human-Connection/issues/1631)
- 🐛 \[Bug\] Embeds are displayed when creating comments but get removed [\#1547](https://github.com/Human-Connection/Human-Connection/issues/1547)
- 🐛 \[Bug\] One cypress test fails but it does not fail the build [\#1312](https://github.com/Human-Connection/Human-Connection/issues/1312)
- 🐛 \[Bug\] TypeError: Cannot read property 'offsetTop' of null [\#1273](https://github.com/Human-Connection/Human-Connection/issues/1273)
**Closed issues:**
- 🚀 \[Feature\] Extend Emoticons [\#1745](https://github.com/Human-Connection/Human-Connection/issues/1745)
- 🚀 \[Feature\] Change slug [\#1650](https://github.com/Human-Connection/Human-Connection/issues/1650)
- 🚀 \[Feature\] Make the slug more visible and usable [\#1486](https://github.com/Human-Connection/Human-Connection/issues/1486)
- 🚀 \[Feature\] Report with reason [\#1469](https://github.com/Human-Connection/Human-Connection/issues/1469)
@ -24,12 +29,57 @@
**Merged pull requests:**
- Bump metascraper-logo from 5.7.5 to 5.7.6 in /backend [\#1783](https://github.com/Human-Connection/Human-Connection/pull/1783) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump metascraper-url from 5.7.5 to 5.7.6 in /backend [\#1782](https://github.com/Human-Connection/Human-Connection/pull/1782) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump graphql-middleware from 3.0.5 to 4.0.1 in /backend [\#1781](https://github.com/Human-Connection/Human-Connection/pull/1781) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump eslint from 6.4.0 to 6.5.1 in /backend [\#1780](https://github.com/Human-Connection/Human-Connection/pull/1780) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump metascraper-audio from 5.7.5 to 5.7.6 in /backend [\#1779](https://github.com/Human-Connection/Human-Connection/pull/1779) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump metascraper-publisher from 5.7.4 to 5.7.6 in /backend [\#1778](https://github.com/Human-Connection/Human-Connection/pull/1778) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump metascraper-youtube from 5.7.5 to 5.7.6 in /backend [\#1777](https://github.com/Human-Connection/Human-Connection/pull/1777) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump @babel/preset-env from 7.6.0 to 7.6.2 in /backend [\#1776](https://github.com/Human-Connection/Human-Connection/pull/1776) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump metascraper-soundcloud from 5.7.4 to 5.7.6 in /backend [\#1775](https://github.com/Human-Connection/Human-Connection/pull/1775) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump metascraper-author from 5.7.4 to 5.7.6 in /backend [\#1774](https://github.com/Human-Connection/Human-Connection/pull/1774) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Fix failing test [\#1772](https://github.com/Human-Connection/Human-Connection/pull/1772) ([aonomike](https://github.com/aonomike))
- Bump metascraper-date from 5.7.4 to 5.7.6 in /backend [\#1771](https://github.com/Human-Connection/Human-Connection/pull/1771) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump tiptap-extensions from 1.27.0 to 1.28.0 in /webapp [\#1770](https://github.com/Human-Connection/Human-Connection/pull/1770) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump date-fns from 2.3.0 to 2.4.1 in /backend [\#1769](https://github.com/Human-Connection/Human-Connection/pull/1769) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump date-fns from 2.4.0 to 2.4.1 in /webapp [\#1768](https://github.com/Human-Connection/Human-Connection/pull/1768) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump graphql-middleware-sentry from 3.2.0 to 3.2.1 in /backend [\#1767](https://github.com/Human-Connection/Human-Connection/pull/1767) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump metascraper-lang from 5.7.4 to 5.7.6 in /backend [\#1766](https://github.com/Human-Connection/Human-Connection/pull/1766) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump tiptap from 1.25.0 to 1.26.0 in /webapp [\#1765](https://github.com/Human-Connection/Human-Connection/pull/1765) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump metascraper-video from 5.7.5 to 5.7.6 in /backend [\#1764](https://github.com/Human-Connection/Human-Connection/pull/1764) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump apollo-server from 2.9.3 to 2.9.4 in /backend [\#1762](https://github.com/Human-Connection/Human-Connection/pull/1762) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump metascraper-image from 5.7.5 to 5.7.6 in /backend [\#1761](https://github.com/Human-Connection/Human-Connection/pull/1761) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump eslint-loader from 3.0.1 to 3.0.2 in /webapp [\#1760](https://github.com/Human-Connection/Human-Connection/pull/1760) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump metascraper-title from 5.7.5 to 5.7.6 in /backend [\#1759](https://github.com/Human-Connection/Human-Connection/pull/1759) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump nodemon from 1.19.2 to 1.19.3 in /backend [\#1758](https://github.com/Human-Connection/Human-Connection/pull/1758) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- fix email middleware transport config [\#1757](https://github.com/Human-Connection/Human-Connection/pull/1757) ([vbelolapotkov](https://github.com/vbelolapotkov))
- 1273 fix post page nav suggestions [\#1756](https://github.com/Human-Connection/Human-Connection/pull/1756) ([roschaefer](https://github.com/roschaefer))
- docs: moves storybook into webapp/README.md [\#1755](https://github.com/Human-Connection/Human-Connection/pull/1755) ([roschaefer](https://github.com/roschaefer))
- Bump date-fns from 2.2.1 to 2.4.0 in /webapp [\#1752](https://github.com/Human-Connection/Human-Connection/pull/1752) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- fix: Github's security vulnerability warning [\#1751](https://github.com/Human-Connection/Human-Connection/pull/1751) ([roschaefer](https://github.com/roschaefer))
- update neo4j docker-compose config [\#1750](https://github.com/Human-Connection/Human-Connection/pull/1750) ([vbelolapotkov](https://github.com/vbelolapotkov))
- 🍰 Try to fix VSCode format works against ESLint [\#1749](https://github.com/Human-Connection/Human-Connection/pull/1749) ([Tirokk](https://github.com/Tirokk))
- Bump neo4j from 3.5.9 to 3.5.11 in /neo4j [\#1739](https://github.com/Human-Connection/Human-Connection/pull/1739) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump graphql from 14.5.7 to 14.5.8 in /backend [\#1738](https://github.com/Human-Connection/Human-Connection/pull/1738) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump metascraper-youtube from 5.7.4 to 5.7.5 in /backend [\#1737](https://github.com/Human-Connection/Human-Connection/pull/1737) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Update to 0.1.1 [\#1734](https://github.com/Human-Connection/Human-Connection/pull/1734) ([roschaefer](https://github.com/roschaefer))
- Update maintenance page email to support@... [\#1732](https://github.com/Human-Connection/Human-Connection/pull/1732) ([mattwr18](https://github.com/mattwr18))
- Bump @babel/register from 7.6.0 to 7.6.2 in /backend [\#1730](https://github.com/Human-Connection/Human-Connection/pull/1730) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump graphql from 14.5.7 to 14.5.8 in /webapp [\#1729](https://github.com/Human-Connection/Human-Connection/pull/1729) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump @babel/core from 7.6.0 to 7.6.2 in /backend [\#1728](https://github.com/Human-Connection/Human-Connection/pull/1728) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump eslint-loader from 3.0.0 to 3.0.1 in /webapp [\#1727](https://github.com/Human-Connection/Human-Connection/pull/1727) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump apollo-server-express from 2.9.3 to 2.9.4 in /backend [\#1726](https://github.com/Human-Connection/Human-Connection/pull/1726) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump @babel/node from 7.6.1 to 7.6.2 in /backend [\#1725](https://github.com/Human-Connection/Human-Connection/pull/1725) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Fix bug UpdateComment, Fix styling on Comment [\#1719](https://github.com/Human-Connection/Human-Connection/pull/1719) ([mattwr18](https://github.com/mattwr18))
- Bump apollo-server-testing from 2.9.3 to 2.9.4 in /backend [\#1717](https://github.com/Human-Connection/Human-Connection/pull/1717) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump metascraper-title from 5.7.4 to 5.7.5 in /backend [\#1715](https://github.com/Human-Connection/Human-Connection/pull/1715) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump date-fns from 2.2.1 to 2.3.0 in /backend [\#1714](https://github.com/Human-Connection/Human-Connection/pull/1714) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump @babel/cli from 7.6.0 to 7.6.2 in /backend [\#1713](https://github.com/Human-Connection/Human-Connection/pull/1713) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- \[WIP\]1706 refactor shout spec [\#1712](https://github.com/Human-Connection/Human-Connection/pull/1712) ([aonomike](https://github.com/aonomike))
- Remove repetitive labels from emote button [\#1702](https://github.com/Human-Connection/Human-Connection/pull/1702) ([roschaefer](https://github.com/roschaefer))
- fix the bug with scrolling post comments into view [\#1701](https://github.com/Human-Connection/Human-Connection/pull/1701) ([vbelolapotkov](https://github.com/vbelolapotkov))
- Bump metascraper-description from 5.7.4 to 5.7.5 in /backend [\#1700](https://github.com/Human-Connection/Human-Connection/pull/1700) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump metascraper-logo from 5.7.4 to 5.7.5 in /backend [\#1698](https://github.com/Human-Connection/Human-Connection/pull/1698) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump metascraper-video from 5.7.4 to 5.7.5 in /backend [\#1697](https://github.com/Human-Connection/Human-Connection/pull/1697) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump @babel/core from 7.6.0 to 7.6.2 in /webapp [\#1696](https://github.com/Human-Connection/Human-Connection/pull/1696) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
@ -39,6 +89,7 @@
- Bump metascraper-url from 5.7.4 to 5.7.5 in /backend [\#1692](https://github.com/Human-Connection/Human-Connection/pull/1692) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bugfix create user page - missing submit buttons [\#1690](https://github.com/Human-Connection/Human-Connection/pull/1690) ([roschaefer](https://github.com/roschaefer))
- Remove sleep icon from comments list [\#1689](https://github.com/Human-Connection/Human-Connection/pull/1689) ([alina-beck](https://github.com/alina-beck))
- Configure docker to work with storybook [\#1688](https://github.com/Human-Connection/Human-Connection/pull/1688) ([mattwr18](https://github.com/mattwr18))
- Add Comment story, add spacing above user info [\#1685](https://github.com/Human-Connection/Human-Connection/pull/1685) ([mattwr18](https://github.com/mattwr18))
- Fix create account page has no logo, localisation [\#1681](https://github.com/Human-Connection/Human-Connection/pull/1681) ([roschaefer](https://github.com/roschaefer))
- Fix intermittent backend specs [\#1679](https://github.com/Human-Connection/Human-Connection/pull/1679) ([roschaefer](https://github.com/roschaefer))
@ -47,11 +98,15 @@
- Bump graphql from 14.5.6 to 14.5.7 in /webapp [\#1676](https://github.com/Human-Connection/Human-Connection/pull/1676) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump graphql from 14.5.6 to 14.5.7 in /backend [\#1675](https://github.com/Human-Connection/Human-Connection/pull/1675) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump cookie-universal-nuxt from 2.0.17 to 2.0.18 in /webapp [\#1674](https://github.com/Human-Connection/Human-Connection/pull/1674) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump @hapi/joi from 16.1.2 to 16.1.4 in /backend [\#1673](https://github.com/Human-Connection/Human-Connection/pull/1673) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump helmet from 3.21.0 to 3.21.1 in /backend [\#1672](https://github.com/Human-Connection/Human-Connection/pull/1672) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump cypress-file-upload from 3.3.3 to 3.3.4 [\#1671](https://github.com/Human-Connection/Human-Connection/pull/1671) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump codecov from 3.6.0 to 3.6.1 [\#1670](https://github.com/Human-Connection/Human-Connection/pull/1670) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Make Human Connection a Progressive Web App [\#1668](https://github.com/Human-Connection/Human-Connection/pull/1668) ([roschaefer](https://github.com/roschaefer))
- Remove contentExcerpt from comments [\#1667](https://github.com/Human-Connection/Human-Connection/pull/1667) ([mattwr18](https://github.com/mattwr18))
- Remove follow type enum [\#1660](https://github.com/Human-Connection/Human-Connection/pull/1660) ([vbelolapotkov](https://github.com/vbelolapotkov))
- 🍰 Notifications self update and refactoring [\#1658](https://github.com/Human-Connection/Human-Connection/pull/1658) ([Tirokk](https://github.com/Tirokk))
- Bump mustache from 3.0.3 to 3.1.0 in /backend [\#1655](https://github.com/Human-Connection/Human-Connection/pull/1655) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump @hapi/joi from 16.1.1 to 16.1.2 in /backend [\#1654](https://github.com/Human-Connection/Human-Connection/pull/1654) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump @nuxtjs/apollo from 4.0.0-rc13 to 4.0.0-rc13.1 in /webapp [\#1653](https://github.com/Human-Connection/Human-Connection/pull/1653) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump codecov from 3.5.0 to 3.6.0 [\#1652](https://github.com/Human-Connection/Human-Connection/pull/1652) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))

View File

@ -1 +1 @@
0.1.1
0.1.2

View File

@ -41,7 +41,7 @@
]
},
"dependencies": {
"@hapi/joi": "^16.1.4",
"@hapi/joi": "^16.1.5",
"@sentry/node": "^5.6.2",
"apollo-cache-inmemory": "~1.6.3",
"apollo-client": "~2.6.4",
@ -53,7 +53,7 @@
"bcryptjs": "~2.4.3",
"cheerio": "~1.0.0-rc.3",
"cors": "~2.8.5",
"cross-env": "~6.0.0",
"cross-env": "~6.0.3",
"date-fns": "2.4.1",
"debug": "~4.1.1",
"dotenv": "~8.1.0",
@ -76,7 +76,7 @@
"metascraper-author": "^5.7.6",
"metascraper-clearbit-logo": "^5.3.0",
"metascraper-date": "^5.7.6",
"metascraper-description": "^5.7.5",
"metascraper-description": "^5.7.6",
"metascraper-image": "^5.7.6",
"metascraper-lang": "^5.7.6",
"metascraper-lang-detector": "^4.8.5",

View File

@ -5,7 +5,8 @@ import {
signupTemplate,
resetPasswordTemplate,
wrongAccountTemplate,
} from './templates/templateBuilder'
emailVerificationTemplate,
} from './templateBuilder'
const hasEmailConfig = CONFIG.SMTP_HOST && CONFIG.SMTP_PORT
const hasAuthData = CONFIG.SMTP_USERNAME && CONFIG.SMTP_PASSWORD
@ -57,8 +58,17 @@ const sendPasswordResetMail = async (resolve, root, args, context, resolveInfo)
return true
}
const sendEmailVerificationMail = async (resolve, root, args, context, resolveInfo) => {
const response = await resolve(root, args, context, resolveInfo)
const { email, nonce, name } = response
await sendMail(emailVerificationTemplate({ email, nonce, name }))
delete response.nonce
return response
}
export default {
Mutation: {
AddEmailAddress: sendEmailVerificationMail,
requestPasswordReset: sendPasswordResetMail,
Signup: sendSignupMail,
SignupByInvitation: sendSignupMail,

View File

@ -0,0 +1,77 @@
import mustache from 'mustache'
import CONFIG from '../../config'
import * as templates from './templates'
const from = '"Human Connection" <info@human-connection.org>'
const supportUrl = 'https://human-connection.org/en/contact'
export const signupTemplate = ({ email, nonce }) => {
const subject = 'Willkommen, Bienvenue, Welcome to Human Connection!'
const actionUrl = new URL('/registration/create-user-account', CONFIG.CLIENT_URI)
actionUrl.searchParams.set('nonce', nonce)
actionUrl.searchParams.set('email', email)
return {
from,
to: email,
subject,
html: mustache.render(
templates.layout,
{ actionUrl, supportUrl, subject },
{ content: templates.signup },
),
}
}
export const emailVerificationTemplate = ({ email, nonce, name }) => {
const subject = 'Neue E-Mail Adresse | New E-Mail Address'
const actionUrl = new URL('/settings/my-email-address/verify', CONFIG.CLIENT_URI)
actionUrl.searchParams.set('nonce', nonce)
actionUrl.searchParams.set('email', email)
return {
from,
to: email,
subject,
html: mustache.render(
templates.layout,
{ actionUrl, name, nonce, supportUrl, subject },
{ content: templates.emailVerification },
),
}
}
export const resetPasswordTemplate = ({ email, nonce, name }) => {
const subject = 'Neues Passwort | Reset Password'
const actionUrl = new URL('/password-reset/change-password', CONFIG.CLIENT_URI)
actionUrl.searchParams.set('nonce', nonce)
actionUrl.searchParams.set('email', email)
return {
from,
to: email,
subject,
html: mustache.render(
templates.layout,
{ actionUrl, name, nonce, supportUrl, subject },
{ content: templates.passwordReset },
),
}
}
export const wrongAccountTemplate = ({ email }) => {
const subject = 'Falsche Mailadresse? | Wrong E-mail?'
const actionUrl = new URL('/password-reset/request', CONFIG.CLIENT_URI)
return {
from,
to: email,
subject,
html: mustache.render(
templates.layout,
{ actionUrl, supportUrl },
{ content: templates.wrongAccount },
),
}
}

View File

@ -0,0 +1,186 @@
<!-- Email Body German : BEGIN -->
<table class="email-german" align="center" role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%"
style="margin: auto;">
<!-- Hero Image, Flush : BEGIN -->
<tr>
<td style="background-color: #ffffff;">
<img
src="https://firebasestorage.googleapis.com/v0/b/gitbook-28427.appspot.com/o/assets%2F-LcGvGRsW6DrZn7FWRzF%2F-LcGv6EiVcsjYLfQ_2YE%2F-LcGv8UtmAWc61fxGveg%2Flets_get_together.png?generation=1555078880410873&alt=media"
width="600" height="" alt="Human Connection community logo" border="0"
style="width: 100%; max-width: 600px; height: auto; background: #ffffff; font-family: Lato, sans-serif; font-size: 16px; line-height: 15px; color: #555555; margin: auto; display: block;"
class="g-img">
</td>
</tr>
<!-- Hero Image, Flush : END -->
<!-- 1 Column Text + Button : BEGIN -->
<tr>
<td style="background-color: #ffffff; padding: 0 20px;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td
style="padding: 20px; padding-top: 0; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">
<h1
style="margin: 0 0 10px 0; font-family: Lato, sans-serif; font-size: 25px; line-height: 30px; color: #333333; font-weight: normal;">
Hallo {{ name }}!</h1>
<p style="margin: 0;">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>
</td>
</tr>
<tr>
<td style="padding: 0 20px;">
<!-- Button : BEGIN -->
<table align="center" role="presentation" cellspacing="0" cellpadding="0" border="0" style="margin: auto;">
<tr>
<td class="button-td button-td-primary" style="border-radius: 4px; background: #17b53e;">
<a class="button-a button-a-primary" href="{{{ actionUrl }}}"
style="background: #17b53e; font-family: Lato, sans-serif; font-size: 16px; line-height: 15px; text-decoration: none; padding: 13px 17px; color: #ffffff; display: block; border-radius: 4px;">E-Mail
Adresse
bestätigen</a>
</td>
</tr>
</table>
<!-- Button : END -->
</td>
</tr>
</table>
</td>
</tr>
<!-- 1 Column Text + Button : END -->
<!-- 1 Column Text : BEGIN -->
<tr>
<td style="background-color: #ffffff; padding: 0 20px;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td
style="padding: 20px; padding-bottom: 0; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">
<p style="margin: 0;">Falls Du deine E-Mail Adresse doch nicht ändern möchtest, kannst du diese Nachricht
einfach ignorieren. Mlde Dich gerne <a href="{{{ supportUrl }}}" style="color: #17b53e;">bei
unserem Support Team</a>, wenn du noch Fragen hast!</p>
</td>
</tr>
</table>
</td>
</tr>
<!-- 1 Column Text : END -->
<!-- 1 Column Text : BEGIN -->
<tr>
<td style="background-color: #ffffff; padding: 0 20px;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td style="padding: 20px; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">
<p style="margin: 0;">Sollte der Button für Dich nicht funktionieren, kannst Du auch folgenden Code in
Dein Browserfenster kopieren: <span style="color: #17b53e;">{{{ nonce }}}</span></p>
<p style="margin: 0; margin-top: 10px;">Bis bald bei <a href="https://human-connection.org"
style="color: #17b53e;">Human Connection</a>!</p>
<p style="margin: 0; margin-bottom: 10px;"> Dein Human Connection Team</p>
</td>
</tr>
<tr>
<td style="display: none;">
<p></p>
</td>
</tr>
</table>
</td>
</tr>
<!-- 1 Column Text : END -->
</table>
<!-- Email Body German : END -->
<!-- Email Body English : BEGIN -->
<table class="email-english" align="center" role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%"
style="margin: auto;">
<tr>
<td style="padding: 20px 0; text-align: center">
</td>
</tr>
<!-- Hero Image, Flush : BEGIN -->
<tr>
<td style="background-color: #ffffff;">
<img
src="https://firebasestorage.googleapis.com/v0/b/gitbook-28427.appspot.com/o/assets%2F-LcGvGRsW6DrZn7FWRzF%2F-LcGv6EiVcsjYLfQ_2YE%2F-LcGv8UtmAWc61fxGveg%2Flets_get_together.png?generation=1555078880410873&alt=media"
width="600" height="" alt="Human Connection community logo" border="0"
style="width: 100%; max-width: 600px; height: auto; background: #ffffff; font-family: Lato, sans-serif; font-size: 16px; line-height: 15px; color: #555555; margin: auto; display: block;"
class="g-img">
</td>
</tr>
<!-- Hero Image, Flush : END -->
<!-- 1 Column Text + Button : BEGIN -->
<tr>
<td style="background-color: #ffffff; padding: 0 20px;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td
style="padding: 20px; padding-top: 0; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">
<h1
style="margin: 0 0 10px 0; font-family: Lato, sans-serif; font-size: 25px; line-height: 30px; color: #333333; font-weight: normal;">
Hello {{ name }}!</h1>
<p style="margin: 0;">So, you want to change your e-mail? No problem! Just click the button below to verify
your new address:</p>
</td>
</tr>
<tr>
<td style="padding: 0 20px;">
<!-- Button : BEGIN -->
<table align="center" role="presentation" cellspacing="0" cellpadding="0" border="0" style="margin: auto;">
<tr>
<td class="button-td button-td-primary" style="border-radius: 4px; background: #17b53e;">
<a class="button-a button-a-primary" href="{{{ actionUrl }}}"
style="background: #17b53e; font-family: Lato, sans-serif; font-size: 16px; line-height: 15px; text-decoration: none; padding: 13px 17px; color: #ffffff; display: block; border-radius: 4px;">Verify
e-mail address</a>
</td>
</tr>
</table>
<!-- Button : END -->
</td>
</tr>
</table>
</td>
</tr>
<!-- 1 Column Text + Button : END -->
<!-- 1 Column Text : BEGIN -->
<tr>
<td style="background-color: #ffffff; padding: 0 20px;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td
style="padding: 20px; padding-bottom: 0; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">
<p style="margin: 0;">If you don't want to change your e-mail address feel free to ignore this message. You
can
also <a href="{{{ supportUrl }}}" style="color: #17b53e;">contact our
support team</a> if you have any questions!</p>
</td>
</tr>
</table>
</td>
</tr>
<!-- 1 Column Text : END -->
<!-- 1 Column Text : BEGIN -->
<tr>
<td style="background-color: #ffffff; padding: 0 20px;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td style="padding: 20px; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">
<p style="margin: 0;">If the above button doesn't work you can also copy the following code into your
browser window: <span style="color: #17b53e;">{{{ nonce }}}</span></p>
<p style="margin: 0; margin-top: 10px;">See you soon on <a href="https://human-connection.org"
style="color: #17b53e;">Human Connection</a>!</p>
<p style="margin: 0; margin-bottom: 10px;"> The Human Connection Team</p>
</td>
</tr>
</table>
</td>
</tr>
<!-- 1 Column Text : END -->
</table>
<!-- Email Body English : END -->

View File

@ -0,0 +1,11 @@
import fs from 'fs'
import path from 'path'
const readFile = fileName => fs.readFileSync(path.join(__dirname, fileName), 'utf-8')
export const signup = readFile('./signup.html')
export const passwordReset = readFile('./resetPassword.html')
export const wrongAccount = readFile('./wrongAccount.html')
export const emailVerification = readFile('./emailVerification.html')
export const layout = readFile('./layout.html')

View File

@ -0,0 +1,196 @@
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml"
xmlns:o="urn:schemas-microsoft-com:office:office">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="x-apple-disable-message-reformatting">
<meta name="format-detection" content="telephone=no,address=no,email=no,date=no,url=no">
<title>{{ subject }}</title>
<!--[if mso]>
<style>
* {
font-family: sans-serif !important;
}
</style>
<![endif]-->
<!--[if !mso]><!-->
<link href='https://fonts.googleapis.com/css?family=Lato:400,700' rel='stylesheet' type='text/css'>
<!--<![endif]-->
<!-- CSS RESETS -->
<style>
html,
body {
margin: 0 !important;
padding: 0 !important;
height: 100% !important;
width: 100% !important;
}
* {
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
}
div[style*="margin: 16px 0"] {
margin: 0 !important;
}
table,
td {
mso-table-lspace: 0pt !important;
mso-table-rspace: 0pt !important;
}
table {
border-spacing: 0 !important;
border-collapse: collapse !important;
table-layout: fixed !important;
margin: 0 auto !important;
}
img {
-ms-interpolation-mode: bicubic;
}
a {
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
a[x-apple-data-detectors],
.unstyle-auto-detected-links a,
.aBn {
border-bottom: 0 !important;
cursor: default !important;
color: inherit !important;
text-decoration: none !important;
font-size: inherit !important;
font-family: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
}
.a6S {
display: none !important;
opacity: 0.01 !important;
}
.im {
color: inherit !important;
}
img.g-img+div {
display: none !important;
}
/* iPhone 4, 4S, 5, 5S, 5C, and 5SE */
@media only screen and (min-device-width: 320px) and (max-device-width: 374px) {
u~div .email-container {
min-width: 320px !important;
}
}
/* iPhone 6, 6S, 7, 8, and X */
@media only screen and (min-device-width: 375px) and (max-device-width: 413px) {
u~div .email-container {
min-width: 375px !important;
}
}
/* iPhone 6+, 7+, and 8+ */
@media only screen and (min-device-width: 414px) {
u~div .email-container {
min-width: 414px !important;
}
}
</style>
<!--[if gte mso 9]>
<xml>
<o:OfficeDocumentSettings>
<o:AllowPNG/>
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
<![endif]-->
<!-- PROGRESSIVE ENHANCEMENTS -->
<style>
.button-td,
.button-a {
transition: all 100ms ease-in;
}
.button-td-primary:hover,
.button-a-primary:hover {
background: #19c243 !important;
border-color: #555555 !important;
}
@media screen and (max-width: 600px) {
.email-container p {
font-size: 17px !important;
}
}
</style>
</head>
<body width="100%" style="margin: 0; padding: 0 !important; mso-line-height-rule: exactly; background-color: #f5f4f6;">
<center style="width: 100%; background-color: #f5f4f6;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0" width="100%" style="background-color: #f5f4f6;">
<tr>
<td>
<![endif]-->
<div style="max-width: 600px; margin: 0 auto;" class="email-container">
<!--[if mso]>
<table align="center" role="presentation" cellspacing="0" cellpadding="0" border="0" width="600">
<tr>
<td>
<![endif]-->
<p style="color:#19c243; font-style: italic; font-family: Lato, sans-serif; font-size: 16px; padding-top: 20px;">English version below!</p>
{{> content}}
<!-- Email Footer : BEGIN -->
<table align="center" role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%"
style="margin: auto;">
<tr>
<td
style="padding: 20px; font-family: Lato, sans-serif; font-size: 12px; line-height: 15px; text-align: center; color: #888888;">
<br><br>
Human Connection gGmbH<br><span class="unstyle-auto-detected-links">Bahnhofstraße 11, 73235 Weilheim /
Teck<br>Germany</span>
<br><br>
</td>
</tr>
</table>
<!-- Email Footer : END -->
<!--[if mso]>
</td>
</tr>
</table>
<![endif]-->
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</center>
</body>
</html>

View File

@ -1,448 +1,185 @@
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml"
xmlns:o="urn:schemas-microsoft-com:office:office">
<!-- Email Body German : BEGIN -->
<table class="email-german" align="center" role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%"
style="margin: auto;">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="x-apple-disable-message-reformatting">
<meta name="format-detection" content="telephone=no,address=no,email=no,date=no,url=no">
<title>Neues Passwort | Reset Password</title>
<!-- Hero Image, Flush : BEGIN -->
<tr>
<td style="background-color: #ffffff;">
<img
src="https://firebasestorage.googleapis.com/v0/b/gitbook-28427.appspot.com/o/assets%2F-LcGvGRsW6DrZn7FWRzF%2F-LcGv6EiVcsjYLfQ_2YE%2F-LcGv8UtmAWc61fxGveg%2Flets_get_together.png?generation=1555078880410873&alt=media"
width="600" height="" alt="Human Connection community logo" border="0"
style="width: 100%; max-width: 600px; height: auto; background: #ffffff; font-family: Lato, sans-serif; font-size: 16px; line-height: 15px; color: #555555; margin: auto; display: block;"
class="g-img">
</td>
</tr>
<!-- Hero Image, Flush : END -->
<!--[if mso]>
<style>
* {
font-family: sans-serif !important;
}
</style>
<![endif]-->
<!--[if !mso]><!-->
<link href='https://fonts.googleapis.com/css?family=Lato:400,700' rel='stylesheet' type='text/css'>
<!--<![endif]-->
<!-- CSS RESETS -->
<style>
html,
body {
margin: 0 !important;
padding: 0 !important;
height: 100% !important;
width: 100% !important;
}
* {
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
}
div[style*="margin: 16px 0"] {
margin: 0 !important;
}
table,
td {
mso-table-lspace: 0pt !important;
mso-table-rspace: 0pt !important;
}
table {
border-spacing: 0 !important;
border-collapse: collapse !important;
table-layout: fixed !important;
margin: 0 auto !important;
}
img {
-ms-interpolation-mode: bicubic;
}
a {
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
a[x-apple-data-detectors],
.unstyle-auto-detected-links a,
.aBn {
border-bottom: 0 !important;
cursor: default !important;
color: inherit !important;
text-decoration: none !important;
font-size: inherit !important;
font-family: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
}
.a6S {
display: none !important;
opacity: 0.01 !important;
}
.im {
color: inherit !important;
}
img.g-img+div {
display: none !important;
}
/* iPhone 4, 4S, 5, 5S, 5C, and 5SE */
@media only screen and (min-device-width: 320px) and (max-device-width: 374px) {
u~div .email-container {
min-width: 320px !important;
}
}
/* iPhone 6, 6S, 7, 8, and X */
@media only screen and (min-device-width: 375px) and (max-device-width: 413px) {
u~div .email-container {
min-width: 375px !important;
}
}
/* iPhone 6+, 7+, and 8+ */
@media only screen and (min-device-width: 414px) {
u~div .email-container {
min-width: 414px !important;
}
}
</style>
<!--[if gte mso 9]>
<xml>
<o:OfficeDocumentSettings>
<o:AllowPNG/>
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
<![endif]-->
<!-- PROGRESSIVE ENHANCEMENTS -->
<style>
.button-td,
.button-a {
transition: all 100ms ease-in;
}
.button-td-primary:hover,
.button-a-primary:hover {
background: #19c243 !important;
border-color: #555555 !important;
}
@media screen and (max-width: 600px) {
.email-container p {
font-size: 17px !important;
}
}
</style>
<!-- LANGUAGE TOGGLE -->
<style>
.toggle+label {
display: inline-block;
height: 40px;
padding: 0 12px;
margin-top: 40px;
line-height: 38px;
font-family: Lato, sans-serif;
font-size: 16px;
border: 1px solid #cbc7d1;
cursor: pointer;
}
.toggle+label:hover {
background-color: #bee876;
}
.toggle:checked+label {
background-color: #19c243;
color: white;
}
.toggle-english+label {
border-bottom-right-radius: 50px;
border-top-right-radius: 50px;
border-left: none;
margin-left: -2px;
}
.toggle-german+label {
border-bottom-left-radius: 50px;
border-top-left-radius: 50px;
border-right: none;
margin-right: -2px;
}
.toggle-german:checked~table.email-german {
display: block;
}
.toggle-german:checked~table.email-english {
display: none;
}
.toggle-english:checked~table.email-english {
display: block;
}
.toggle-english:checked~table.email-german {
display: none;
}
</style>
</head>
<body width="100%" style="margin: 0; padding: 0 !important; mso-line-height-rule: exactly; background-color: #f5f4f6;">
<center style="width: 100%; background-color: #f5f4f6;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0" width="100%" style="background-color: #f5f4f6;">
<tr>
<td>
<![endif]-->
<div style="max-width: 600px; margin: 0 auto;" class="email-container">
<!--[if mso]>
<table align="center" role="presentation" cellspacing="0" cellpadding="0" border="0" width="600">
<tr>
<td>
<![endif]-->
<!-- LANGUAGE TOGGLE -->
<input type="radio" name="language" class="toggle toggle-german" style="display: none;" id="toggle-german"
checked="checked">
<label for="toggle-german">Deutsch</label>
<input type="radio" name="language" class="toggle toggle-english" style="display:none;" id="toggle-english">
<label for="toggle-english">English</label>
<p style="margin: 0;"></p>
<!-- Email Body German : BEGIN -->
<table class="email-german" align="center" role="presentation" cellspacing="0" cellpadding="0" border="0"
width="100%" style="margin: auto;">
<tr>
<td style="padding: 20px 0; text-align: center">
</td>
</tr>
<!-- Hero Image, Flush : BEGIN -->
<tr>
<td style="background-color: #ffffff;">
<img
src="https://firebasestorage.googleapis.com/v0/b/gitbook-28427.appspot.com/o/assets%2F-LcGvGRsW6DrZn7FWRzF%2F-LcGv6EiVcsjYLfQ_2YE%2F-LcGv8UtmAWc61fxGveg%2Flets_get_together.png?generation=1555078880410873&alt=media"
width="600" height="" alt="Human Connection community logo" border="0"
style="width: 100%; max-width: 600px; height: auto; background: #ffffff; font-family: Lato, sans-serif; font-size: 16px; line-height: 15px; color: #555555; margin: auto; display: block;"
class="g-img">
</td>
</tr>
<!-- Hero Image, Flush : END -->
<!-- 1 Column Text + Button : BEGIN -->
<tr>
<td style="background-color: #ffffff; padding: 0 20px;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td
style="padding: 20px; padding-top: 0; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">
<h1
style="margin: 0 0 10px 0; font-family: Lato, sans-serif; font-size: 25px; line-height: 30px; color: #333333; font-weight: normal;">
Hallo {{ name }}!</h1>
<p style="margin: 0;">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>
</td>
</tr>
<tr>
<td style="padding: 0 20px;">
<!-- Button : BEGIN -->
<table align="center" role="presentation" cellspacing="0" cellpadding="0" border="0"
style="margin: auto;">
<tr>
<td class="button-td button-td-primary" style="border-radius: 4px; background: #17b53e;">
<a class="button-a button-a-primary" href="{{{ actionUrl }}}"
style="background: #17b53e; font-family: Lato, sans-serif; font-size: 16px; line-height: 15px; text-decoration: none; padding: 13px 17px; color: #ffffff; display: block; border-radius: 4px;">Passwort
zurücksetzen</a>
</td>
</tr>
</table>
<!-- Button : END -->
</td>
</tr>
</table>
</td>
</tr>
<!-- 1 Column Text + Button : END -->
<!-- 1 Column Text : BEGIN -->
<tr>
<td style="background-color: #ffffff; padding: 0 20px;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td
style="padding: 20px; padding-bottom: 0; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">
<p style="margin: 0;">Falls Du kein neues Passwort angefordert hast, kannst Du diese E-Mail einfach
ignorieren. Wenn Du noch Fragen hast, melde Dich gerne <a href="{{{ supportUrl }}}"
style="color: #17b53e;">bei
unserem Support Team</a>!</p>
</td>
</tr>
</table>
</td>
</tr>
<!-- 1 Column Text : END -->
<!-- 1 Column Text : BEGIN -->
<tr>
<td style="background-color: #ffffff; padding: 0 20px;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td
style="padding: 20px; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">
<p style="margin: 0;">Sollte der Button für Dich nicht funktionieren, kannst Du auch folgenden Code in
Dein Browserfenster kopieren: <span style="color: #17b53e;">{{{ nonce }}}</span></p>
<p style="margin: 0; margin-top: 10px;">Bis bald bei <a href="https://human-connection.org"
style="color: #17b53e;">Human Connection</a>!</p>
<p style="margin: 0; margin-bottom: 10px;"> Dein Human Connection Team</p>
</td>
</tr>
<tr>
<td style="display: none;">
<p></p>
</td>
</tr>
</table>
</td>
</tr>
<!-- 1 Column Text : END -->
</table>
<!-- Email Body German : END -->
<!-- Email Body English : BEGIN -->
<table class="email-english" align="center" role="presentation" cellspacing="0" cellpadding="0" border="0"
width="100%" style="margin: auto;">
<tr>
<td style="padding: 20px 0; text-align: center">
</td>
</tr>
<!-- Hero Image, Flush : BEGIN -->
<tr>
<td style="background-color: #ffffff;">
<img
src="https://firebasestorage.googleapis.com/v0/b/gitbook-28427.appspot.com/o/assets%2F-LcGvGRsW6DrZn7FWRzF%2F-LcGv6EiVcsjYLfQ_2YE%2F-LcGv8UtmAWc61fxGveg%2Flets_get_together.png?generation=1555078880410873&alt=media"
width="600" height="" alt="Human Connection community logo" border="0"
style="width: 100%; max-width: 600px; height: auto; background: #ffffff; font-family: Lato, sans-serif; font-size: 16px; line-height: 15px; color: #555555; margin: auto; display: block;"
class="g-img">
</td>
</tr>
<!-- Hero Image, Flush : END -->
<!-- 1 Column Text + Button : BEGIN -->
<tr>
<td style="background-color: #ffffff; padding: 0 20px;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td
style="padding: 20px; padding-top: 0; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">
<h1
style="margin: 0 0 10px 0; font-family: Lato, sans-serif; font-size: 25px; line-height: 30px; color: #333333; font-weight: normal;">
Hello {{ name }}!</h1>
<p style="margin: 0;">So, you forgot your password? No problem! Just click the button below to reset
it within the next 24 hours:</p>
</td>
</tr>
<tr>
<td style="padding: 0 20px;">
<!-- Button : BEGIN -->
<table align="center" role="presentation" cellspacing="0" cellpadding="0" border="0"
style="margin: auto;">
<tr>
<td class="button-td button-td-primary" style="border-radius: 4px; background: #17b53e;">
<a class="button-a button-a-primary" href="{{{ actionUrl }}}"
style="background: #17b53e; font-family: Lato, sans-serif; font-size: 16px; line-height: 15px; text-decoration: none; padding: 13px 17px; color: #ffffff; display: block; border-radius: 4px;">Reset
password</a>
</td>
</tr>
</table>
<!-- Button : END -->
</td>
</tr>
</table>
</td>
</tr>
<!-- 1 Column Text + Button : END -->
<!-- 1 Column Text : BEGIN -->
<tr>
<td style="background-color: #ffffff; padding: 0 20px;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td
style="padding: 20px; padding-bottom: 0; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">
<p style="margin: 0;">If you didn't request a new password feel free to ignore this e-mail. You can
also <a href="{{{ supportUrl }}}" style="color: #17b53e;">contact our
support team</a> if you have any questions!</p>
</td>
</tr>
</table>
</td>
</tr>
<!-- 1 Column Text : END -->
<!-- 1 Column Text : BEGIN -->
<tr>
<td style="background-color: #ffffff; padding: 0 20px;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td
style="padding: 20px; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">
<p style="margin: 0;">If the above button doesn't work you can also copy the following code into your
browser window: <span style="color: #17b53e;">{{{ nonce }}}</span></p>
<p style="margin: 0; margin-top: 10px;">See you soon on <a href="https://human-connection.org"
style="color: #17b53e;">Human Connection</a>!</p>
<p style="margin: 0; margin-bottom: 10px;"> The Human Connection Team</p>
</td>
</tr>
</table>
</td>
</tr>
<!-- 1 Column Text : END -->
</table>
<!-- Email Body English : END -->
<!-- Email Footer : BEGIN -->
<table align="center" role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%"
style="margin: auto;">
<!-- 1 Column Text + Button : BEGIN -->
<tr>
<td style="background-color: #ffffff; padding: 0 20px;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td
style="padding: 20px; font-family: Lato, sans-serif; font-size: 12px; line-height: 15px; text-align: center; color: #888888;">
<br><br>
Human Connection gGmbH<br><span class="unstyle-auto-detected-links">Bahnhofstraße 11, 73235 Weilheim /
Teck<br>Germany</span>
<br><br>
style="padding: 20px; padding-top: 0; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">
<h1
style="margin: 0 0 10px 0; font-family: Lato, sans-serif; font-size: 25px; line-height: 30px; color: #333333; font-weight: normal;">
Hallo {{ name }}!</h1>
<p style="margin: 0;">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>
</td>
</tr>
<tr>
<td style="padding: 0 20px;">
<!-- Button : BEGIN -->
<table align="center" role="presentation" cellspacing="0" cellpadding="0" border="0" style="margin: auto;">
<tr>
<td class="button-td button-td-primary" style="border-radius: 4px; background: #17b53e;">
<a class="button-a button-a-primary" href="{{{ actionUrl }}}"
style="background: #17b53e; font-family: Lato, sans-serif; font-size: 16px; line-height: 15px; text-decoration: none; padding: 13px 17px; color: #ffffff; display: block; border-radius: 4px;">Passwort
zurücksetzen</a>
</td>
</tr>
</table>
<!-- Button : END -->
</td>
</tr>
</table>
<!-- Email Footer : END -->
</td>
</tr>
<!-- 1 Column Text + Button : END -->
<!--[if mso]>
</td>
</tr>
</table>
<![endif]-->
</div>
<!--[if mso | IE]>
<!-- 1 Column Text : BEGIN -->
<tr>
<td style="background-color: #ffffff; padding: 0 20px;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td
style="padding: 20px; padding-bottom: 0; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">
<p style="margin: 0;">Falls Du kein neues Passwort angefordert hast, kannst Du diese E-Mail einfach
ignorieren. Wenn Du noch Fragen hast, melde Dich gerne <a href="{{{ supportUrl }}}"
style="color: #17b53e;">bei
unserem Support Team</a>!</p>
</td>
</tr>
</table>
<![endif]-->
</center>
</body>
</td>
</tr>
<!-- 1 Column Text : END -->
</html>
<!-- 1 Column Text : BEGIN -->
<tr>
<td style="background-color: #ffffff; padding: 0 20px;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td style="padding: 20px; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">
<p style="margin: 0;">Sollte der Button für Dich nicht funktionieren, kannst Du auch folgenden Code in
Dein Browserfenster kopieren: <span style="color: #17b53e;">{{{ nonce }}}</span></p>
<p style="margin: 0; margin-top: 10px;">Bis bald bei <a href="https://human-connection.org"
style="color: #17b53e;">Human Connection</a>!</p>
<p style="margin: 0; margin-bottom: 10px;"> Dein Human Connection Team</p>
</td>
</tr>
<tr>
<td style="display: none;">
<p></p>
</td>
</tr>
</table>
</td>
</tr>
<!-- 1 Column Text : END -->
</table>
<!-- Email Body German : END -->
<!-- Email Body English : BEGIN -->
<table class="email-english" align="center" role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%"
style="margin: auto;">
<tr>
<td style="padding: 20px 0; text-align: center">
</td>
</tr>
<!-- Hero Image, Flush : BEGIN -->
<tr>
<td style="background-color: #ffffff;">
<img
src="https://firebasestorage.googleapis.com/v0/b/gitbook-28427.appspot.com/o/assets%2F-LcGvGRsW6DrZn7FWRzF%2F-LcGv6EiVcsjYLfQ_2YE%2F-LcGv8UtmAWc61fxGveg%2Flets_get_together.png?generation=1555078880410873&alt=media"
width="600" height="" alt="Human Connection community logo" border="0"
style="width: 100%; max-width: 600px; height: auto; background: #ffffff; font-family: Lato, sans-serif; font-size: 16px; line-height: 15px; color: #555555; margin: auto; display: block;"
class="g-img">
</td>
</tr>
<!-- Hero Image, Flush : END -->
<!-- 1 Column Text + Button : BEGIN -->
<tr>
<td style="background-color: #ffffff; padding: 0 20px;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td
style="padding: 20px; padding-top: 0; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">
<h1
style="margin: 0 0 10px 0; font-family: Lato, sans-serif; font-size: 25px; line-height: 30px; color: #333333; font-weight: normal;">
Hello {{ name }}!</h1>
<p style="margin: 0;">So, you forgot your password? No problem! Just click the button below to reset
it within the next 24 hours:</p>
</td>
</tr>
<tr>
<td style="padding: 0 20px;">
<!-- Button : BEGIN -->
<table align="center" role="presentation" cellspacing="0" cellpadding="0" border="0" style="margin: auto;">
<tr>
<td class="button-td button-td-primary" style="border-radius: 4px; background: #17b53e;">
<a class="button-a button-a-primary" href="{{{ actionUrl }}}"
style="background: #17b53e; font-family: Lato, sans-serif; font-size: 16px; line-height: 15px; text-decoration: none; padding: 13px 17px; color: #ffffff; display: block; border-radius: 4px;">Reset
password</a>
</td>
</tr>
</table>
<!-- Button : END -->
</td>
</tr>
</table>
</td>
</tr>
<!-- 1 Column Text + Button : END -->
<!-- 1 Column Text : BEGIN -->
<tr>
<td style="background-color: #ffffff; padding: 0 20px;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td
style="padding: 20px; padding-bottom: 0; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">
<p style="margin: 0;">If you didn't request a new password feel free to ignore this e-mail. You can
also <a href="{{{ supportUrl }}}" style="color: #17b53e;">contact our
support team</a> if you have any questions!</p>
</td>
</tr>
</table>
</td>
</tr>
<!-- 1 Column Text : END -->
<!-- 1 Column Text : BEGIN -->
<tr>
<td style="background-color: #ffffff; padding: 0 20px;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td style="padding: 20px; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">
<p style="margin: 0;">If the above button doesn't work you can also copy the following code into your
browser window: <span style="color: #17b53e;">{{{ nonce }}}</span></p>
<p style="margin: 0; margin-top: 10px;">See you soon on <a href="https://human-connection.org"
style="color: #17b53e;">Human Connection</a>!</p>
<p style="margin: 0; margin-bottom: 10px;"> The Human Connection Team</p>
</td>
</tr>
</table>
</td>
</tr>
<!-- 1 Column Text : END -->
</table>
<!-- Email Body English : END -->

View File

@ -1,485 +1,210 @@
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml"
xmlns:o="urn:schemas-microsoft-com:office:office">
<!-- Email Body German : BEGIN -->
<table class="email-german" align="center" role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%"
style="margin: auto;">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="x-apple-disable-message-reformatting">
<meta name="format-detection" content="telephone=no,address=no,email=no,date=no,url=no">
<title>Willkommen, Bienvenue, Welcome to Human Connection</title>
<!-- Hero Image, Flush : BEGIN -->
<tr>
<td style="background-color: #ffffff;">
<img
src="https://firebasestorage.googleapis.com/v0/b/gitbook-28427.appspot.com/o/assets%2F-LcGvGRsW6DrZn7FWRzF%2F-LcGv6EiVcsjYLfQ_2YE%2F-LcGv8UtmAWc61fxGveg%2Flets_get_together.png?generation=1555078880410873&alt=media"
width="600" height="" alt="Human Connection community logo" border="0"
style="width: 100%; max-width: 600px; height: auto; background: #ffffff; font-family: Lato, sans-serif; font-size: 16px; line-height: 15px; color: #555555; margin: auto; display: block;"
class="g-img">
</td>
</tr>
<!-- Hero Image, Flush : END -->
<!--[if mso]>
<style>
* {
font-family: sans-serif !important;
}
</style>
<![endif]-->
<!--[if !mso]><!-->
<link href='https://fonts.googleapis.com/css?family=Lato:400,700' rel='stylesheet' type='text/css'>
<!--<![endif]-->
<!-- CSS RESETS -->
<style>
html,
body {
margin: 0 !important;
padding: 0 !important;
height: 100% !important;
width: 100% !important;
}
* {
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
}
div[style*="margin: 16px 0"] {
margin: 0 !important;
}
table,
td {
mso-table-lspace: 0pt !important;
mso-table-rspace: 0pt !important;
}
table {
border-spacing: 0 !important;
border-collapse: collapse !important;
table-layout: fixed !important;
margin: 0 auto !important;
}
img {
-ms-interpolation-mode: bicubic;
}
a {
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
a[x-apple-data-detectors],
.unstyle-auto-detected-links a,
.aBn {
border-bottom: 0 !important;
cursor: default !important;
color: inherit !important;
text-decoration: none !important;
font-size: inherit !important;
font-family: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
}
.a6S {
display: none !important;
opacity: 0.01 !important;
}
.im {
color: inherit !important;
}
img.g-img+div {
display: none !important;
}
/* iPhone 4, 4S, 5, 5S, 5C, and 5SE */
@media only screen and (min-device-width: 320px) and (max-device-width: 374px) {
u~div .email-container {
min-width: 320px !important;
}
}
/* iPhone 6, 6S, 7, 8, and X */
@media only screen and (min-device-width: 375px) and (max-device-width: 413px) {
u~div .email-container {
min-width: 375px !important;
}
}
/* iPhone 6+, 7+, and 8+ */
@media only screen and (min-device-width: 414px) {
u~div .email-container {
min-width: 414px !important;
}
}
</style>
<!--[if gte mso 9]>
<xml>
<o:OfficeDocumentSettings>
<o:AllowPNG/>
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
<![endif]-->
<!-- PROGRESSIVE ENHANCEMENTS -->
<style>
.button-td,
.button-a {
transition: all 100ms ease-in;
}
.button-td-primary:hover,
.button-a-primary:hover {
background: #19c243 !important;
border-color: #555555 !important;
}
@media screen and (max-width: 600px) {
.email-container p {
font-size: 17px !important;
}
}
</style>
<!-- LANGUAGE TOGGLE -->
<style>
.toggle+label {
display: inline-block;
height: 40px;
padding: 0 12px;
margin-top: 40px;
line-height: 38px;
font-family: Lato, sans-serif;
font-size: 16px;
border: 1px solid #cbc7d1;
cursor: pointer;
}
.toggle+label:hover {
background-color: #bee876;
}
.toggle:checked+label {
background-color: #19c243;
color: white;
}
.toggle-english+label {
border-bottom-right-radius: 50px;
border-top-right-radius: 50px;
border-left: none;
margin-left: -2px;
}
.toggle-german+label {
border-bottom-left-radius: 50px;
border-top-left-radius: 50px;
border-right: none;
margin-right: -2px;
}
.toggle-german:checked~table.email-german {
display: block;
}
.toggle-german:checked~table.email-english {
display: none;
}
.toggle-english:checked~table.email-english {
display: block;
}
.toggle-english:checked~table.email-german {
display: none;
}
</style>
</head>
<body width="100%" style="margin: 0; padding: 0 !important; mso-line-height-rule: exactly; background-color: #f5f4f6;">
<center style="width: 100%; background-color: #f5f4f6;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0" width="100%" style="background-color: #f5f4f6;">
<tr>
<td>
<![endif]-->
<!-- VISUALLY HIDDEN PRE-HEADER TEXT -->
<div
style="display: none; font-size: 1px; line-height: 1px; max-height: 0px; max-width: 0px; opacity: 0; overflow: hidden; mso-hide: all; font-family: sans-serif;">
Dein Anmeldelink. | Here is your signup link.
</div>
<div
style="display: none; font-size: 1px; line-height: 1px; max-height: 0px; max-width: 0px; opacity: 0; overflow: hidden; mso-hide: all; font-family: sans-serif;">
&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;<br>
</div>
<div style="max-width: 600px; margin: 0 auto;" class="email-container">
<!--[if mso]>
<table align="center" role="presentation" cellspacing="0" cellpadding="0" border="0" width="600">
<tr>
<td>
<![endif]-->
<!-- LANGUAGE TOGGLE -->
<input type="radio" name="language" class="toggle toggle-german" style="display: none;" id="toggle-german"
checked="checked">
<label for="toggle-german">Deutsch</label>
<input type="radio" name="language" class="toggle toggle-english" style="display:none;" id="toggle-english">
<label for="toggle-english">English</label>
<p style="margin: 0;"></p>
<!-- Email Body German : BEGIN -->
<table class="email-german" align="center" role="presentation" cellspacing="0" cellpadding="0" border="0"
width="100%" style="margin: auto;">
<tr>
<td style="padding: 20px 0; text-align: center">
</td>
</tr>
<!-- Hero Image, Flush : BEGIN -->
<tr>
<td style="background-color: #ffffff;">
<img
src="https://firebasestorage.googleapis.com/v0/b/gitbook-28427.appspot.com/o/assets%2F-LcGvGRsW6DrZn7FWRzF%2F-LcGv6EiVcsjYLfQ_2YE%2F-LcGv8UtmAWc61fxGveg%2Flets_get_together.png?generation=1555078880410873&alt=media"
width="600" height="" alt="Human Connection community logo" border="0"
style="width: 100%; max-width: 600px; height: auto; background: #ffffff; font-family: Lato, sans-serif; font-size: 16px; line-height: 15px; color: #555555; margin: auto; display: block;"
class="g-img">
</td>
</tr>
<!-- Hero Image, Flush : END -->
<!-- 1 Column Text + Button : BEGIN -->
<tr>
<td style="background-color: #ffffff; padding: 0 20px;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td
style="padding: 20px; padding-top: 0; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">
<h1
style="margin: 0 0 10px 0; font-family: Lato, sans-serif; font-size: 25px; line-height: 30px; color: #333333; font-weight: normal;">
Willkommen bei Human Connection!</h1>
<p style="margin: 0;">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>
</td>
</tr>
<tr>
<td style="padding: 0 20px;">
<!-- Button : BEGIN -->
<table align="center" role="presentation" cellspacing="0" cellpadding="0" border="0"
style="margin: auto;">
<tr>
<td class="button-td button-td-primary" style="border-radius: 4px; background: #17b53e;">
<a class="button-a button-a-primary" href="{{{ actionUrl }}}"
style="background: #17b53e; font-family: Lato, sans-serif; font-size: 16px; line-height: 15px; text-decoration: none; padding: 13px 17px; color: #ffffff; display: block; border-radius: 4px;">Bestätige
Deine E-Mail Adresse</a>
</td>
</tr>
</table>
<!-- Button : END -->
</td>
</tr>
<tr>
<td style="text-align: center; color :#17b53e">
<p></p>
</td>
</tr>
</table>
</td>
</tr>
<!-- 1 Column Text + Button : END -->
<!-- 1 Column Text : BEGIN -->
<tr>
<td style="background-color: #ffffff; padding: 0 20px;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td
style="padding: 20px; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">
<p style="margin: 0;">Falls Du Dich nicht selbst bei <a href="https://human-connection.org"
style="color: #17b53e;">Human Connection</a> angemeldet hast, schau doch mal vorbei!
Wir sind ein gemeinnütziges Aktionsnetzwerk von Menschen für Menschen.</p>
<p style="margin: 0; margin-top: 10px;">PS: Wenn Du keinen Account bei uns möchtest, kannst Du diese
E-Mail einfach ignorieren. ;)</p>
</td>
</tr>
<tr>
<td style="text-align: center; color :#17b53e">
<p></p>
</td>
</tr>
</table>
</td>
</tr>
<!-- 1 Column Text : END -->
<!-- 1 Column Text : BEGIN -->
<tr>
<td style="background-color: #ffffff; padding: 0 20px;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td
style="padding: 20px; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">
<p style="margin: 0;">Melde Dich gerne <a href="{{{ supportUrl }}}" style="color: #17b53e;">bei
unserem Support Team</a>, wenn Du Fragen hast.</p>
<p style="margin: 0; margin-top: 10px;">Bis bald bei <a href="https://human-connection.org"
style="color: #17b53e;">Human Connection</a>!</p>
<p style="margin: 0; margin-bottom: 10px;"> Dein Human Connection Team</p>
</td>
</tr>
<tr>
<td style="display: none;">
<p></p>
</td>
</tr>
</table>
</td>
</tr>
<!-- 1 Column Text : END -->
</table>
<!-- Email Body German : END -->
<!-- Email Body English : BEGIN -->
<table class="email-english" align="center" role="presentation" cellspacing="0" cellpadding="0" border="0"
width="100%" style="margin: auto;">
<tr>
<td style="padding: 20px 0; text-align: center">
</td>
</tr>
<!-- Hero Image, Flush : BEGIN -->
<tr>
<td style="background-color: #ffffff;">
<img
src="https://firebasestorage.googleapis.com/v0/b/gitbook-28427.appspot.com/o/assets%2F-LcGvGRsW6DrZn7FWRzF%2F-LcGv6EiVcsjYLfQ_2YE%2F-LcGv8UtmAWc61fxGveg%2Flets_get_together.png?generation=1555078880410873&alt=media"
width="600" height="" alt="Human Connection community logo" border="0"
style="width: 100%; max-width: 600px; height: auto; background: #ffffff; font-family: Lato, sans-serif; font-size: 16px; line-height: 15px; color: #555555; margin: auto; display: block;"
class="g-img">
</td>
</tr>
<!-- Hero Image, Flush : END -->
<!-- 1 Column Text + Button : BEGIN -->
<tr>
<td style="background-color: #ffffff; padding: 0 20px;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td
style="padding: 20px; padding-top: 0; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">
<h1
style="margin: 0 0 10px 0; font-family: Lato, sans-serif; font-size: 25px; line-height: 30px; color: #333333; font-weight: normal;">
Welcome to Human Connection!</h1>
<p style="margin: 0;">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>
</td>
</tr>
<tr>
<td style="padding: 0 20px;">
<!-- Button : BEGIN -->
<table align="center" role="presentation" cellspacing="0" cellpadding="0" border="0"
style="margin: auto;">
<tr>
<td class="button-td button-td-primary" style="border-radius: 4px; background: #17b53e;">
<a class="button-a button-a-primary" href="{{{ actionUrl }}}"
style="background: #17b53e; font-family: Lato, sans-serif; font-size: 16px; line-height: 15px; text-decoration: none; padding: 13px 17px; color: #ffffff; display: block; border-radius: 4px;">Confirm
your e-mail address</a>
</td>
</tr>
</table>
<!-- Button : END -->
</td>
</tr>
<tr>
<td style="text-align: center; color :#17b53e">
<p></p>
</td>
</tr>
</table>
</td>
</tr>
<!-- 1 Column Text + Button : END -->
<!-- 1 Column Text : BEGIN -->
<tr>
<td style="background-color: #ffffff; padding: 0 20px;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td
style="padding: 20px; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">
<p style="margin: 0;">If you didn't sign up for <a href="https://human-connection.org"
style="color: #17b53e;">Human Connection</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 style="margin: 0; margin-top: 10px;">PS: If you ignore this e-mail we will not create an account
for
you. ;)</p>
</td>
</tr>
<tr>
<td style="text-align: center; color :#17b53e">
<p></p>
</td>
</tr>
</table>
</td>
</tr>
<!-- 1 Column Text : END -->
<!-- 1 Column Text : BEGIN -->
<tr>
<td style="background-color: #ffffff; padding: 0 20px;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td
style="padding: 20px; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">
<p style="margin: 0;">Feel free to <a href="{{{ supportUrl }}}" style="color: #17b53e;">contact our
support team</a> with any
questions you have.</p>
<p style="margin: 0; margin-top: 10px;">See you soon on <a href="https://human-connection.org"
style="color: #17b53e;">Human Connection</a>!</p>
<p style="margin: 0; margin-bottom: 10px;"> The Human Connection Team</p>
</td>
</tr>
</table>
</td>
</tr>
<!-- 1 Column Text : END -->
</table>
<!-- Email Body English : END -->
<!-- Email Footer : BEGIN -->
<table align="center" role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%"
style="margin: auto;">
<!-- 1 Column Text + Button : BEGIN -->
<tr>
<td style="background-color: #ffffff; padding: 0 20px;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td
style="padding: 20px; font-family: Lato, sans-serif; font-size: 12px; line-height: 15px; text-align: center; color: #888888;">
<br><br>
Human Connection gGmbH<br><span class="unstyle-auto-detected-links">Bahnhofstraße 11, 73235 Weilheim /
Teck<br>Germany</span>
<br><br>
style="padding: 20px; padding-top: 0; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">
<h1
style="margin: 0 0 10px 0; font-family: Lato, sans-serif; font-size: 25px; line-height: 30px; color: #333333; font-weight: normal;">
Willkommen bei Human Connection!</h1>
<p style="margin: 0;">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>
</td>
</tr>
<tr>
<td style="padding: 0 20px;">
<!-- Button : BEGIN -->
<table align="center" role="presentation" cellspacing="0" cellpadding="0" border="0" style="margin: auto;">
<tr>
<td class="button-td button-td-primary" style="border-radius: 4px; background: #17b53e;">
<a class="button-a button-a-primary" href="{{{ actionUrl }}}"
style="background: #17b53e; font-family: Lato, sans-serif; font-size: 16px; line-height: 15px; text-decoration: none; padding: 13px 17px; color: #ffffff; display: block; border-radius: 4px;">Bestätige
Deine E-Mail Adresse</a>
</td>
</tr>
</table>
<!-- Button : END -->
</td>
</tr>
<tr>
<td style="text-align: center; color :#17b53e">
<p></p>
</td>
</tr>
</table>
<!-- Email Footer : END -->
</td>
</tr>
<!-- 1 Column Text + Button : END -->
<!--[if mso]>
</td>
</tr>
</table>
<![endif]-->
</div>
<!--[if mso | IE]>
<!-- 1 Column Text : BEGIN -->
<tr>
<td style="background-color: #ffffff; padding: 0 20px;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td style="padding: 20px; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">
<p style="margin: 0;">Falls Du Dich nicht selbst bei <a href="https://human-connection.org"
style="color: #17b53e;">Human Connection</a> angemeldet hast, schau doch mal vorbei!
Wir sind ein gemeinnütziges Aktionsnetzwerk von Menschen für Menschen.</p>
<p style="margin: 0; margin-top: 10px;">PS: Wenn Du keinen Account bei uns möchtest, kannst Du diese
E-Mail einfach ignorieren. ;)</p>
</td>
</tr>
<tr>
<td style="text-align: center; color :#17b53e">
<p></p>
</td>
</tr>
</table>
<![endif]-->
</center>
</body>
</td>
</tr>
<!-- 1 Column Text : END -->
</html>
<!-- 1 Column Text : BEGIN -->
<tr>
<td style="background-color: #ffffff; padding: 0 20px;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td style="padding: 20px; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">
<p style="margin: 0;">Melde Dich gerne <a href="{{{ supportUrl }}}" style="color: #17b53e;">bei
unserem Support Team</a>, wenn Du Fragen hast.</p>
<p style="margin: 0; margin-top: 10px;">Bis bald bei <a href="https://human-connection.org"
style="color: #17b53e;">Human Connection</a>!</p>
<p style="margin: 0; margin-bottom: 10px;"> Dein Human Connection Team</p>
</td>
</tr>
<tr>
<td style="display: none;">
<p></p>
</td>
</tr>
</table>
</td>
</tr>
<!-- 1 Column Text : END -->
</table>
<!-- Email Body German : END -->
<!-- Email Body English : BEGIN -->
<table class="email-english" align="center" role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%"
style="margin: auto;">
<tr>
<td style="padding: 20px 0; text-align: center">
</td>
</tr>
<!-- Hero Image, Flush : BEGIN -->
<tr>
<td style="background-color: #ffffff;">
<img
src="https://firebasestorage.googleapis.com/v0/b/gitbook-28427.appspot.com/o/assets%2F-LcGvGRsW6DrZn7FWRzF%2F-LcGv6EiVcsjYLfQ_2YE%2F-LcGv8UtmAWc61fxGveg%2Flets_get_together.png?generation=1555078880410873&alt=media"
width="600" height="" alt="Human Connection community logo" border="0"
style="width: 100%; max-width: 600px; height: auto; background: #ffffff; font-family: Lato, sans-serif; font-size: 16px; line-height: 15px; color: #555555; margin: auto; display: block;"
class="g-img">
</td>
</tr>
<!-- Hero Image, Flush : END -->
<!-- 1 Column Text + Button : BEGIN -->
<tr>
<td style="background-color: #ffffff; padding: 0 20px;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td
style="padding: 20px; padding-top: 0; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">
<h1
style="margin: 0 0 10px 0; font-family: Lato, sans-serif; font-size: 25px; line-height: 30px; color: #333333; font-weight: normal;">
Welcome to Human Connection!</h1>
<p style="margin: 0;">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>
</td>
</tr>
<tr>
<td style="padding: 0 20px;">
<!-- Button : BEGIN -->
<table align="center" role="presentation" cellspacing="0" cellpadding="0" border="0" style="margin: auto;">
<tr>
<td class="button-td button-td-primary" style="border-radius: 4px; background: #17b53e;">
<a class="button-a button-a-primary" href="{{{ actionUrl }}}"
style="background: #17b53e; font-family: Lato, sans-serif; font-size: 16px; line-height: 15px; text-decoration: none; padding: 13px 17px; color: #ffffff; display: block; border-radius: 4px;">Confirm
your e-mail address</a>
</td>
</tr>
</table>
<!-- Button : END -->
</td>
</tr>
<tr>
<td style="text-align: center; color :#17b53e">
<p></p>
</td>
</tr>
</table>
</td>
</tr>
<!-- 1 Column Text + Button : END -->
<!-- 1 Column Text : BEGIN -->
<tr>
<td style="background-color: #ffffff; padding: 0 20px;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td style="padding: 20px; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">
<p style="margin: 0;">If you didn't sign up for <a href="https://human-connection.org"
style="color: #17b53e;">Human Connection</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 style="margin: 0; margin-top: 10px;">PS: If you ignore this e-mail we will not create an account
for
you. ;)</p>
</td>
</tr>
<tr>
<td style="text-align: center; color :#17b53e">
<p></p>
</td>
</tr>
</table>
</td>
</tr>
<!-- 1 Column Text : END -->
<!-- 1 Column Text : BEGIN -->
<tr>
<td style="background-color: #ffffff; padding: 0 20px;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td style="padding: 20px; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">
<p style="margin: 0;">Feel free to <a href="{{{ supportUrl }}}" style="color: #17b53e;">contact our
support team</a> with any
questions you have.</p>
<p style="margin: 0; margin-top: 10px;">See you soon on <a href="https://human-connection.org"
style="color: #17b53e;">Human Connection</a>!</p>
<p style="margin: 0; margin-bottom: 10px;"> The Human Connection Team</p>
</td>
</tr>
</table>
</td>
</tr>
<!-- 1 Column Text : END -->
</table>
<!-- Email Body English : END -->

View File

@ -1,48 +0,0 @@
import fs from 'fs'
import path from 'path'
import mustache from 'mustache'
import CONFIG from '../../../config'
const from = '"Human Connection" <info@human-connection.org>'
const supportUrl = 'https://human-connection.org/en/contact'
const signupHtml = fs.readFileSync(path.join(__dirname, './signup.html'), 'utf-8')
const passwordResetHtml = fs.readFileSync(path.join(__dirname, './resetPassword.html'), 'utf-8')
const wrongAccountHtml = fs.readFileSync(path.join(__dirname, './wrongAccount.html'), 'utf-8')
export const signupTemplate = ({ email, nonce }) => {
const actionUrl = new URL('/registration/create-user-account', CONFIG.CLIENT_URI)
actionUrl.searchParams.set('nonce', nonce)
actionUrl.searchParams.set('email', email)
return {
from,
to: email,
subject: 'Willkommen, Bienvenue, Welcome to Human Connection!',
html: mustache.render(signupHtml, { actionUrl, supportUrl }),
}
}
export const resetPasswordTemplate = ({ email, nonce, name }) => {
const actionUrl = new URL('/password-reset/change-password', CONFIG.CLIENT_URI)
actionUrl.searchParams.set('nonce', nonce)
actionUrl.searchParams.set('email', email)
return {
from,
to: email,
subject: 'Neues Passwort | Reset Password',
html: mustache.render(passwordResetHtml, { actionUrl, name, nonce, supportUrl }),
}
}
export const wrongAccountTemplate = ({ email }) => {
const actionUrl = new URL('/password-reset/request', CONFIG.CLIENT_URI)
return {
from,
to: email,
subject: 'Falsche Mailadresse? | Wrong E-mail?',
html: mustache.render(wrongAccountHtml, { actionUrl, supportUrl }),
}
}

View File

@ -1,448 +1,185 @@
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml"
xmlns:o="urn:schemas-microsoft-com:office:office">
<!-- Email Body German : BEGIN -->
<table class="email-german" align="center" role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%"
style="margin: auto;">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="x-apple-disable-message-reformatting">
<meta name="format-detection" content="telephone=no,address=no,email=no,date=no,url=no">
<title>Falsche Mailadresse? | Wrong E-mail?</title>
<!-- Hero Image, Flush : BEGIN -->
<tr>
<td style="background-color: #ffffff;">
<img
src="https://firebasestorage.googleapis.com/v0/b/gitbook-28427.appspot.com/o/assets%2F-LcGvGRsW6DrZn7FWRzF%2F-LcGv6EiVcsjYLfQ_2YE%2F-LcGv8UtmAWc61fxGveg%2Flets_get_together.png?generation=1555078880410873&alt=media"
width="600" height="" alt="Human Connection community logo" border="0"
style="width: 100%; max-width: 600px; height: auto; background: #ffffff; font-family: Lato, sans-serif; font-size: 16px; line-height: 15px; color: #555555; margin: auto; display: block;"
class="g-img">
</td>
</tr>
<!-- Hero Image, Flush : END -->
<!--[if mso]>
<style>
* {
font-family: sans-serif !important;
}
</style>
<![endif]-->
<!--[if !mso]><!-->
<link href='https://fonts.googleapis.com/css?family=Lato:400,700' rel='stylesheet' type='text/css'>
<!--<![endif]-->
<!-- CSS RESETS -->
<style>
html,
body {
margin: 0 !important;
padding: 0 !important;
height: 100% !important;
width: 100% !important;
}
* {
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
}
div[style*="margin: 16px 0"] {
margin: 0 !important;
}
table,
td {
mso-table-lspace: 0pt !important;
mso-table-rspace: 0pt !important;
}
table {
border-spacing: 0 !important;
border-collapse: collapse !important;
table-layout: fixed !important;
margin: 0 auto !important;
}
img {
-ms-interpolation-mode: bicubic;
}
a {
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
a[x-apple-data-detectors],
.unstyle-auto-detected-links a,
.aBn {
border-bottom: 0 !important;
cursor: default !important;
color: inherit !important;
text-decoration: none !important;
font-size: inherit !important;
font-family: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
}
.a6S {
display: none !important;
opacity: 0.01 !important;
}
.im {
color: inherit !important;
}
img.g-img+div {
display: none !important;
}
/* iPhone 4, 4S, 5, 5S, 5C, and 5SE */
@media only screen and (min-device-width: 320px) and (max-device-width: 374px) {
u~div .email-container {
min-width: 320px !important;
}
}
/* iPhone 6, 6S, 7, 8, and X */
@media only screen and (min-device-width: 375px) and (max-device-width: 413px) {
u~div .email-container {
min-width: 375px !important;
}
}
/* iPhone 6+, 7+, and 8+ */
@media only screen and (min-device-width: 414px) {
u~div .email-container {
min-width: 414px !important;
}
}
</style>
<!--[if gte mso 9]>
<xml>
<o:OfficeDocumentSettings>
<o:AllowPNG/>
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
<![endif]-->
<!-- PROGRESSIVE ENHANCEMENTS -->
<style>
.button-td,
.button-a {
transition: all 100ms ease-in;
}
.button-td-primary:hover,
.button-a-primary:hover {
background: #19c243 !important;
border-color: #555555 !important;
}
@media screen and (max-width: 600px) {
.email-container p {
font-size: 17px !important;
}
}
</style>
<!-- LANGUAGE TOGGLE -->
<style>
.toggle+label {
display: inline-block;
height: 40px;
padding: 0 12px;
margin-top: 40px;
line-height: 38px;
font-family: Lato, sans-serif;
font-size: 16px;
border: 1px solid #cbc7d1;
cursor: pointer;
}
.toggle+label:hover {
background-color: #bee876;
}
.toggle:checked+label {
background-color: #19c243;
color: white;
}
.toggle-english+label {
border-bottom-right-radius: 50px;
border-top-right-radius: 50px;
border-left: none;
margin-left: -2px;
}
.toggle-german+label {
border-bottom-left-radius: 50px;
border-top-left-radius: 50px;
border-right: none;
margin-right: -2px;
}
.toggle-german:checked~table.email-german {
display: block;
}
.toggle-german:checked~table.email-english {
display: none;
}
.toggle-english:checked~table.email-english {
display: block;
}
.toggle-english:checked~table.email-german {
display: none;
}
</style>
</head>
<body width="100%" style="margin: 0; padding: 0 !important; mso-line-height-rule: exactly; background-color: #f5f4f6;">
<center style="width: 100%; background-color: #f5f4f6;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0" width="100%" style="background-color: #f5f4f6;">
<tr>
<td>
<![endif]-->
<div style="max-width: 600px; margin: 0 auto;" class="email-container">
<!--[if mso]>
<table align="center" role="presentation" cellspacing="0" cellpadding="0" border="0" width="600">
<tr>
<td>
<![endif]-->
<!-- LANGUAGE TOGGLE -->
<input type="radio" name="language" class="toggle toggle-german" style="display: none;" id="toggle-german"
checked="checked">
<label for="toggle-german">Deutsch</label>
<input type="radio" name="language" class="toggle toggle-english" style="display:none;" id="toggle-english">
<label for="toggle-english">English</label>
<p style="margin: 0;"></p>
<!-- Email Body German : BEGIN -->
<table class="email-german" align="center" role="presentation" cellspacing="0" cellpadding="0" border="0"
width="100%" style="margin: auto;">
<tr>
<td style="padding: 20px 0; text-align: center">
</td>
</tr>
<!-- Hero Image, Flush : BEGIN -->
<tr>
<td style="background-color: #ffffff;">
<img
src="https://firebasestorage.googleapis.com/v0/b/gitbook-28427.appspot.com/o/assets%2F-LcGvGRsW6DrZn7FWRzF%2F-LcGv6EiVcsjYLfQ_2YE%2F-LcGv8UtmAWc61fxGveg%2Flets_get_together.png?generation=1555078880410873&alt=media"
width="600" height="" alt="Human Connection community logo" border="0"
style="width: 100%; max-width: 600px; height: auto; background: #ffffff; font-family: Lato, sans-serif; font-size: 16px; line-height: 15px; color: #555555; margin: auto; display: block;"
class="g-img">
</td>
</tr>
<!-- Hero Image, Flush : END -->
<!-- 1 Column Text + Button : BEGIN -->
<tr>
<td style="background-color: #ffffff; padding: 0 20px;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td
style="padding: 20px; padding-top: 0; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">
<h1
style="margin: 0 0 10px 0; font-family: Lato, sans-serif; font-size: 25px; line-height: 30px; color: #333333; font-weight: normal;">
Hallo!</h1>
<p style="margin: 0;">Du hast bei uns ein neues Password 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>
</td>
</tr>
<tr>
<td style="padding: 0 20px;">
<!-- Button : BEGIN -->
<table align="center" role="presentation" cellspacing="0" cellpadding="0" border="0"
style="margin: auto;">
<tr>
<td class="button-td button-td-primary" style="border-radius: 4px; background: #17b53e;">
<a class="button-a button-a-primary" href="{{{ actionUrl }}}"
style="background: #17b53e; font-family: Lato, sans-serif; font-size: 16px; line-height: 15px; text-decoration: none; padding: 13px 17px; color: #ffffff; display: block; border-radius: 4px;">Versuch'
es mit einer anderen E-Mail</a>
</td>
</tr>
</table>
<!-- Button : END -->
</td>
</tr>
</table>
</td>
</tr>
<!-- 1 Column Text + Button : END -->
<!-- 1 Column Text : BEGIN -->
<tr>
<td style="background-color: #ffffff; padding: 0 20px;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td
style="padding: 20px; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">
<p style="margin: 0;">Wenn Du noch keinen Account bei <a href="https://human-connection.org"
style="color: #17b53e;">Human Connection</a> hast oder Dein Password gar nicht ändern willst,
kannst Du diese E-Mail einfach ignorieren!</p>
</td>
</tr>
</table>
</td>
</tr>
<!-- 1 Column Text : END -->
<!-- 1 Column Text : BEGIN -->
<tr>
<td style="background-color: #ffffff; padding: 0 20px;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td
style="padding: 20px; padding-top: 0; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">
<p style="margin: 0;">Ansonsten hilft Dir <a href="{{{ supportUrl }}}" style="color: #17b53e;">unser
Support Team</a> gerne weiter.</p>
<p style="margin: 0; margin-top: 10px;">Bis bald bei <a href="https://human-connection.org"
style="color: #17b53e;">Human Connection</a>!</p>
<p style="margin: 0; margin-bottom: 10px;"> Dein Human Connection Team</p>
</td>
</tr>
<tr>
<td style="display: none;">
<p></p>
</td>
</tr>
</table>
</td>
</tr>
<!-- 1 Column Text : END -->
</table>
<!-- Email Body German : END -->
<!-- Email Body English : BEGIN -->
<table class="email-english" align="center" role="presentation" cellspacing="0" cellpadding="0" border="0"
width="100%" style="margin: auto;">
<tr>
<td style="padding: 20px 0; text-align: center">
</td>
</tr>
<!-- Hero Image, Flush : BEGIN -->
<tr>
<td style="background-color: #ffffff;">
<img
src="https://firebasestorage.googleapis.com/v0/b/gitbook-28427.appspot.com/o/assets%2F-LcGvGRsW6DrZn7FWRzF%2F-LcGv6EiVcsjYLfQ_2YE%2F-LcGv8UtmAWc61fxGveg%2Flets_get_together.png?generation=1555078880410873&alt=media"
width="600" height="" alt="Human Connection community logo" border="0"
style="width: 100%; max-width: 600px; height: auto; background: #ffffff; font-family: Lato, sans-serif; font-size: 16px; line-height: 15px; color: #555555; margin: auto; display: block;"
class="g-img">
</td>
</tr>
<!-- Hero Image, Flush : END -->
<!-- 1 Column Text + Button : BEGIN -->
<tr>
<td style="background-color: #ffffff; padding: 0 20px;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td
style="padding: 20px; padding-top: 0; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">
<h1
style="margin: 0 0 10px 0; font-family: Lato, sans-serif; font-size: 25px; line-height: 30px; color: #333333; font-weight: normal;">
Hello!</h1>
<p style="margin: 0;">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>
</td>
</tr>
<tr>
<td style="padding: 0 20px;">
<!-- Button : BEGIN -->
<table align="center" role="presentation" cellspacing="0" cellpadding="0" border="0"
style="margin: auto;">
<tr>
<td class="button-td button-td-primary" style="border-radius: 4px; background: #17b53e;">
<a class="button-a button-a-primary" href="{{{ actionUrl }}}"
style="background: #17b53e; font-family: Lato, sans-serif; font-size: 16px; line-height: 15px; text-decoration: none; padding: 13px 17px; color: #ffffff; display: block; border-radius: 4px;">Try
a different e-mail</a>
</td>
</tr>
</table>
<!-- Button : END -->
</td>
</tr>
</table>
</td>
</tr>
<!-- 1 Column Text + Button : END -->
<!-- 1 Column Text : BEGIN -->
<tr>
<td style="background-color: #ffffff; padding: 0 20px;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td
style="padding: 20px; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">
<p style="margin: 0;">If you don't have an account at <a href="https://human-connection.org"
style="color: #17b53e;">Human Connection</a> yet or if you didn't want to reset your password,
please ignore this e-mail.</p>
</td>
</tr>
</table>
</td>
</tr>
<!-- 1 Column Text : END -->
<!-- 1 Column Text : BEGIN -->
<tr>
<td style="background-color: #ffffff; padding: 0 20px;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td
style="padding: 20px; padding-top: 0; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">
<p style="margin: 0;">Otherwise <a href="{{{ supportUrl }}}" style="color: #17b53e;">our
support team</a> will be happy to help you out.</p>
<p style="margin: 0; margin-top: 10px;">See you soon on <a href="https://human-connection.org"
style="color: #17b53e;">Human Connection</a>!</p>
<p style="margin: 0; margin-bottom: 10px;"> The Human Connection Team</p>
</td>
</tr>
</table>
</td>
</tr>
<!-- 1 Column Text : END -->
</table>
<!-- Email Body English : END -->
<!-- Email Footer : BEGIN -->
<table align="center" role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%"
style="margin: auto;">
<!-- 1 Column Text + Button : BEGIN -->
<tr>
<td style="background-color: #ffffff; padding: 0 20px;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td
style="padding: 20px; font-family: Lato, sans-serif; font-size: 12px; line-height: 15px; text-align: center; color: #888888;">
<br><br>
Human Connection gGmbH<br><span class="unstyle-auto-detected-links">Bahnhofstraße 11, 73235 Weilheim /
Teck<br>Germany</span>
<br><br>
style="padding: 20px; padding-top: 0; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">
<h1
style="margin: 0 0 10px 0; font-family: Lato, sans-serif; font-size: 25px; line-height: 30px; color: #333333; font-weight: normal;">
Hallo!</h1>
<p style="margin: 0;">Du hast bei uns ein neues Password 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>
</td>
</tr>
<tr>
<td style="padding: 0 20px;">
<!-- Button : BEGIN -->
<table align="center" role="presentation" cellspacing="0" cellpadding="0" border="0" style="margin: auto;">
<tr>
<td class="button-td button-td-primary" style="border-radius: 4px; background: #17b53e;">
<a class="button-a button-a-primary" href="{{{ actionUrl }}}"
style="background: #17b53e; font-family: Lato, sans-serif; font-size: 16px; line-height: 15px; text-decoration: none; padding: 13px 17px; color: #ffffff; display: block; border-radius: 4px;">Versuch'
es mit einer anderen E-Mail</a>
</td>
</tr>
</table>
<!-- Button : END -->
</td>
</tr>
</table>
<!-- Email Footer : END -->
</td>
</tr>
<!-- 1 Column Text + Button : END -->
<!--[if mso]>
</td>
</tr>
</table>
<![endif]-->
</div>
<!--[if mso | IE]>
<!-- 1 Column Text : BEGIN -->
<tr>
<td style="background-color: #ffffff; padding: 0 20px;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td style="padding: 20px; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">
<p style="margin: 0;">Wenn Du noch keinen Account bei <a href="https://human-connection.org"
style="color: #17b53e;">Human Connection</a> hast oder Dein Password gar nicht ändern willst,
kannst Du diese E-Mail einfach ignorieren!</p>
</td>
</tr>
</table>
<![endif]-->
</center>
</body>
</td>
</tr>
<!-- 1 Column Text : END -->
</html>
<!-- 1 Column Text : BEGIN -->
<tr>
<td style="background-color: #ffffff; padding: 0 20px;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td
style="padding: 20px; padding-top: 0; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">
<p style="margin: 0;">Ansonsten hilft Dir <a href="{{{ supportUrl }}}" style="color: #17b53e;">unser
Support Team</a> gerne weiter.</p>
<p style="margin: 0; margin-top: 10px;">Bis bald bei <a href="https://human-connection.org"
style="color: #17b53e;">Human Connection</a>!</p>
<p style="margin: 0; margin-bottom: 10px;"> Dein Human Connection Team</p>
</td>
</tr>
<tr>
<td style="display: none;">
<p></p>
</td>
</tr>
</table>
</td>
</tr>
<!-- 1 Column Text : END -->
</table>
<!-- Email Body German : END -->
<!-- Email Body English : BEGIN -->
<table class="email-english" align="center" role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%"
style="margin: auto;">
<tr>
<td style="padding: 20px 0; text-align: center">
</td>
</tr>
<!-- Hero Image, Flush : BEGIN -->
<tr>
<td style="background-color: #ffffff;">
<img
src="https://firebasestorage.googleapis.com/v0/b/gitbook-28427.appspot.com/o/assets%2F-LcGvGRsW6DrZn7FWRzF%2F-LcGv6EiVcsjYLfQ_2YE%2F-LcGv8UtmAWc61fxGveg%2Flets_get_together.png?generation=1555078880410873&alt=media"
width="600" height="" alt="Human Connection community logo" border="0"
style="width: 100%; max-width: 600px; height: auto; background: #ffffff; font-family: Lato, sans-serif; font-size: 16px; line-height: 15px; color: #555555; margin: auto; display: block;"
class="g-img">
</td>
</tr>
<!-- Hero Image, Flush : END -->
<!-- 1 Column Text + Button : BEGIN -->
<tr>
<td style="background-color: #ffffff; padding: 0 20px;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td
style="padding: 20px; padding-top: 0; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">
<h1
style="margin: 0 0 10px 0; font-family: Lato, sans-serif; font-size: 25px; line-height: 30px; color: #333333; font-weight: normal;">
Hello!</h1>
<p style="margin: 0;">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>
</td>
</tr>
<tr>
<td style="padding: 0 20px;">
<!-- Button : BEGIN -->
<table align="center" role="presentation" cellspacing="0" cellpadding="0" border="0" style="margin: auto;">
<tr>
<td class="button-td button-td-primary" style="border-radius: 4px; background: #17b53e;">
<a class="button-a button-a-primary" href="{{{ actionUrl }}}"
style="background: #17b53e; font-family: Lato, sans-serif; font-size: 16px; line-height: 15px; text-decoration: none; padding: 13px 17px; color: #ffffff; display: block; border-radius: 4px;">Try
a different e-mail</a>
</td>
</tr>
</table>
<!-- Button : END -->
</td>
</tr>
</table>
</td>
</tr>
<!-- 1 Column Text + Button : END -->
<!-- 1 Column Text : BEGIN -->
<tr>
<td style="background-color: #ffffff; padding: 0 20px;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td style="padding: 20px; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">
<p style="margin: 0;">If you don't have an account at <a href="https://human-connection.org"
style="color: #17b53e;">Human Connection</a> yet or if you didn't want to reset your password,
please ignore this e-mail.</p>
</td>
</tr>
</table>
</td>
</tr>
<!-- 1 Column Text : END -->
<!-- 1 Column Text : BEGIN -->
<tr>
<td style="background-color: #ffffff; padding: 0 20px;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td
style="padding: 20px; padding-top: 0; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">
<p style="margin: 0;">Otherwise <a href="{{{ supportUrl }}}" style="color: #17b53e;">our
support team</a> will be happy to help you out.</p>
<p style="margin: 0; margin-top: 10px;">See you soon on <a href="https://human-connection.org"
style="color: #17b53e;">Human Connection</a>!</p>
<p style="margin: 0; margin-bottom: 10px;"> The Human Connection Team</p>
</td>
</tr>
</table>
</td>
</tr>
<!-- 1 Column Text : END -->
</table>
<!-- Email Body English : END -->

View File

@ -170,6 +170,8 @@ const permissions = shield(
block: isAuthenticated,
unblock: isAuthenticated,
markAsRead: isAuthenticated,
AddEmailAddress: isAuthenticated,
VerifyEmailAddress: isAuthenticated,
},
User: {
email: isMyOwn,

View File

@ -0,0 +1,12 @@
module.exports = {
email: { type: 'string', primary: true, lowercase: true, email: true },
createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() },
nonce: { type: 'string', token: true },
belongsTo: {
type: 'relationship',
relationship: 'BELONGS_TO',
target: 'User',
direction: 'out',
eager: true,
},
}

View File

@ -5,6 +5,7 @@ export default {
User: require('./User.js'),
InvitationCode: require('./InvitationCode.js'),
EmailAddress: require('./EmailAddress.js'),
UnverifiedEmailAddress: require('./UnverifiedEmailAddress.js'),
SocialMedia: require('./SocialMedia.js'),
Post: require('./Post.js'),
Comment: require('./Comment.js'),

View File

@ -0,0 +1,92 @@
import generateNonce from './helpers/generateNonce'
import Resolver from './helpers/Resolver'
import existingEmailAddress from './helpers/existingEmailAddress'
import { UserInputError } from 'apollo-server'
import Validator from 'neode/build/Services/Validator.js'
export default {
Mutation: {
AddEmailAddress: async (_parent, args, context, _resolveInfo) => {
let response
try {
const { neode } = context
await new Validator(neode, neode.model('UnverifiedEmailAddress'), args)
} catch (e) {
throw new UserInputError('must be a valid email')
}
// check email does not belong to anybody
await existingEmailAddress(_parent, args, context)
const nonce = generateNonce()
const {
user: { id: userId },
} = context
const { email } = args
const session = context.driver.session()
const writeTxResultPromise = session.writeTransaction(async txc => {
const result = await txc.run(
`
MATCH (user:User {id: $userId})
MERGE (user)<-[:BELONGS_TO]-(email:UnverifiedEmailAddress {email: $email, nonce: $nonce})
SET email.createdAt = toString(datetime())
RETURN email, user
`,
{ userId, email, nonce },
)
return result.records.map(record => ({
name: record.get('user').properties.name,
...record.get('email').properties,
}))
})
try {
const txResult = await writeTxResultPromise
response = txResult[0]
} finally {
session.close()
}
return response
},
VerifyEmailAddress: async (_parent, args, context, _resolveInfo) => {
let response
const {
user: { id: userId },
} = context
const { nonce, email } = args
const session = context.driver.session()
const writeTxResultPromise = session.writeTransaction(async txc => {
const result = await txc.run(
`
MATCH (user:User {id: $userId})-[:PRIMARY_EMAIL]->(previous:EmailAddress)
MATCH (user)<-[:BELONGS_TO]-(email:UnverifiedEmailAddress {email: $email, nonce: $nonce})
MERGE (user)-[:PRIMARY_EMAIL]->(email)
SET email:EmailAddress
SET email.verifiedAt = toString(datetime())
REMOVE email:UnverifiedEmailAddress
DETACH DELETE previous
RETURN email
`,
{ userId, email, nonce },
)
return result.records.map(record => record.get('email').properties)
})
try {
const txResult = await writeTxResultPromise
response = txResult[0]
} catch (e) {
if (e.code === 'Neo.ClientError.Schema.ConstraintValidationFailed')
throw new UserInputError('A user account with this email already exists.')
throw new Error(e)
} finally {
session.close()
}
if (!response) throw new UserInputError('Invalid nonce or no email address found.')
return response
},
},
EmailAddress: {
...Resolver('EmailAddress', {
undefinedToNull: ['verifiedAt'],
}),
},
}

View File

@ -0,0 +1,298 @@
import Factory from '../../seed/factories'
import { gql } from '../../jest/helpers'
import { getDriver, neode as getNeode } from '../../bootstrap/neo4j'
import createServer from '../../server'
import { createTestClient } from 'apollo-server-testing'
const factory = Factory()
const neode = getNeode()
let mutate
let authenticatedUser
let user
let variables
const driver = getDriver()
beforeEach(async () => {
variables = {}
})
beforeAll(() => {
const { server } = createServer({
context: () => {
return {
driver,
neode,
user: authenticatedUser,
}
},
})
mutate = createTestClient(server).mutate
})
afterEach(async () => {
await factory.cleanDatabase()
})
describe('AddEmailAddress', () => {
const mutation = gql`
mutation($email: String!) {
AddEmailAddress(email: $email) {
email
verifiedAt
createdAt
}
}
`
beforeEach(() => {
variables = { ...variables, email: 'new-email@example.org' }
})
describe('unauthenticated', () => {
beforeEach(() => {
authenticatedUser = null
})
it('throws AuthorizationError', async () => {
await expect(mutate({ mutation, variables })).resolves.toMatchObject({
data: { AddEmailAddress: null },
errors: [{ message: 'Not Authorised!' }],
})
})
})
describe('authenticated', () => {
beforeEach(async () => {
user = await factory.create('User', { id: '567', email: 'user@example.org' })
authenticatedUser = await user.toJson()
})
describe('email attribute is not a valid email', () => {
beforeEach(() => {
variables = { ...variables, email: 'foobar' }
})
it('throws UserInputError', async () => {
await expect(mutate({ mutation, variables })).resolves.toMatchObject({
data: { AddEmailAddress: null },
errors: [{ message: 'must be a valid email' }],
})
})
})
describe('email attribute is a valid email', () => {
it('creates a new unverified `EmailAddress` node', async () => {
await expect(mutate({ mutation, variables })).resolves.toMatchObject({
data: {
AddEmailAddress: {
email: 'new-email@example.org',
verifiedAt: null,
createdAt: expect.any(String),
},
},
errors: undefined,
})
})
it('connects `UnverifiedEmailAddress` to the authenticated user', async () => {
await mutate({ mutation, variables })
const result = await neode.cypher(`
MATCH(u:User)-[:PRIMARY_EMAIL]->(:EmailAddress {email: "user@example.org"})
MATCH(u:User)<-[:BELONGS_TO]-(e:UnverifiedEmailAddress {email: "new-email@example.org"})
RETURN e
`)
const email = neode.hydrateFirst(result, 'e', neode.model('UnverifiedEmailAddress'))
await expect(email.toJson()).resolves.toMatchObject({
email: 'new-email@example.org',
nonce: expect.any(String),
})
})
describe('if another `UnverifiedEmailAddress` node already exists with that email', () => {
it('throws no unique constraint violation error', async () => {
await factory.create('UnverifiedEmailAddress', {
createdAt: '2019-09-24T14:00:01.565Z',
email: 'new-email@example.org',
})
await expect(mutate({ mutation, variables })).resolves.toMatchObject({
data: {
AddEmailAddress: {
email: 'new-email@example.org',
verifiedAt: null,
},
},
errors: undefined,
})
})
})
describe('but if another user owns an `EmailAddress` already with that email', () => {
it('throws UserInputError because of unique constraints', async () => {
await factory.create('User', { email: 'new-email@example.org' })
await expect(mutate({ mutation, variables })).resolves.toMatchObject({
data: { AddEmailAddress: null },
errors: [{ message: 'A user account with this email already exists.' }],
})
})
})
})
})
})
describe('VerifyEmailAddress', () => {
const mutation = gql`
mutation($email: String!, $nonce: String!) {
VerifyEmailAddress(email: $email, nonce: $nonce) {
email
createdAt
verifiedAt
}
}
`
beforeEach(() => {
variables = { ...variables, email: 'to-be-verified@example.org', nonce: '123456' }
})
describe('unauthenticated', () => {
beforeEach(() => {
authenticatedUser = null
})
it('throws AuthorizationError', async () => {
await expect(mutate({ mutation, variables })).resolves.toMatchObject({
data: { VerifyEmailAddress: null },
errors: [{ message: 'Not Authorised!' }],
})
})
})
describe('authenticated', () => {
beforeEach(async () => {
user = await factory.create('User', { id: '567', email: 'user@example.org' })
authenticatedUser = await user.toJson()
})
describe('if no unverified `EmailAddress` node exists', () => {
it('throws UserInputError', async () => {
await expect(mutate({ mutation, variables })).resolves.toMatchObject({
data: { VerifyEmailAddress: null },
errors: [{ message: 'Invalid nonce or no email address found.' }],
})
})
})
describe('given a `UnverifiedEmailAddress`', () => {
let emailAddress
beforeEach(async () => {
emailAddress = await factory.create('UnverifiedEmailAddress', {
nonce: 'abcdef',
verifiedAt: null,
createdAt: new Date().toISOString(),
email: 'to-be-verified@example.org',
})
})
describe('given invalid nonce', () => {
it('throws UserInputError', async () => {
variables.nonce = 'asdfgh'
await expect(mutate({ mutation, variables })).resolves.toMatchObject({
data: { VerifyEmailAddress: null },
errors: [{ message: 'Invalid nonce or no email address found.' }],
})
})
})
describe('given valid nonce for `UnverifiedEmailAddress` node', () => {
beforeEach(() => {
variables = { ...variables, nonce: 'abcdef' }
})
describe('but the address does not belong to the authenticated user', () => {
it('throws UserInputError', async () => {
await expect(mutate({ mutation, variables })).resolves.toMatchObject({
data: { VerifyEmailAddress: null },
errors: [{ message: 'Invalid nonce or no email address found.' }],
})
})
})
describe('and the `UnverifiedEmailAddress` belongs to the authenticated user', () => {
beforeEach(async () => {
await emailAddress.relateTo(user, 'belongsTo')
})
it('adds `verifiedAt`', async () => {
await expect(mutate({ mutation, variables })).resolves.toMatchObject({
data: {
VerifyEmailAddress: {
email: 'to-be-verified@example.org',
verifiedAt: expect.any(String),
createdAt: expect.any(String),
},
},
errors: undefined,
})
})
it('connects the new `EmailAddress` as PRIMARY', async () => {
await mutate({ mutation, variables })
const result = await neode.cypher(`
MATCH(u:User {id: "567"})-[:PRIMARY_EMAIL]->(e:EmailAddress {email: "to-be-verified@example.org"})
RETURN e
`)
const email = neode.hydrateFirst(result, 'e', neode.model('EmailAddress'))
await expect(email.toJson()).resolves.toMatchObject({
email: 'to-be-verified@example.org',
})
})
it('removes previous PRIMARY relationship', async () => {
const cypherStatement = `
MATCH(u:User {id: "567"})-[:PRIMARY_EMAIL]->(e:EmailAddress {email: "user@example.org"})
RETURN e
`
let result = await neode.cypher(cypherStatement)
let email = neode.hydrateFirst(result, 'e', neode.model('EmailAddress'))
await expect(email.toJson()).resolves.toMatchObject({
email: 'user@example.org',
})
await mutate({ mutation, variables })
result = await neode.cypher(cypherStatement)
email = neode.hydrateFirst(result, 'e', neode.model('EmailAddress'))
await expect(email).toBe(false)
})
it('removes previous `EmailAddress` node', async () => {
const cypherStatement = `
MATCH(u:User {id: "567"})<-[:BELONGS_TO]-(e:EmailAddress {email: "user@example.org"})
RETURN e
`
let result = await neode.cypher(cypherStatement)
let email = neode.hydrateFirst(result, 'e', neode.model('EmailAddress'))
await expect(email.toJson()).resolves.toMatchObject({
email: 'user@example.org',
})
await mutate({ mutation, variables })
result = await neode.cypher(cypherStatement)
email = neode.hydrateFirst(result, 'e', neode.model('EmailAddress'))
await expect(email).toBe(false)
})
describe('Edge case: In the meantime someone created an `EmailAddress` node with the given email', () => {
beforeEach(async () => {
await factory.create('EmailAddress', { email: 'to-be-verified@example.org' })
})
it('throws UserInputError because of unique constraints', async () => {
await expect(mutate({ mutation, variables })).resolves.toMatchObject({
data: { VerifyEmailAddress: null },
errors: [{ message: 'A user account with this email already exists.' }],
})
})
})
})
})
})
})
})

View File

@ -0,0 +1,26 @@
import { UserInputError } from 'apollo-server'
export default async function alreadyExistingMail(_parent, args, context) {
let { email } = args
email = email.toLowerCase()
const cypher = `
MATCH (email:EmailAddress {email: $email})
OPTIONAL MATCH (email)-[:BELONGS_TO]-(user)
RETURN email, user
`
let transactionRes
const session = context.driver.session()
try {
transactionRes = await session.run(cypher, { email })
} finally {
session.close()
}
const [result] = transactionRes.records.map(record => {
return {
alreadyExistingEmail: record.get('email').properties,
user: record.get('user') && record.get('user').properties,
}
})
const { alreadyExistingEmail, user } = result || {}
if (user) throw new UserInputError('A user account with this email already exists.')
return alreadyExistingEmail
}

View File

@ -0,0 +1,4 @@
import uuid from 'uuid/v4'
export default function generateNonce() {
return uuid().substring(0, 6)
}

View File

@ -1,41 +1,16 @@
import { UserInputError } from 'apollo-server'
import uuid from 'uuid/v4'
import { neode } from '../../bootstrap/neo4j'
import fileUpload from './fileUpload'
import encryptPassword from '../../helpers/encryptPassword'
import generateNonce from './helpers/generateNonce'
import existingEmailAddress from './helpers/existingEmailAddress'
const instance = neode()
const alreadyExistingMail = async (_parent, args, context) => {
let { email } = args
email = email.toLowerCase()
const cypher = `
MATCH (email:EmailAddress {email: $email})
OPTIONAL MATCH (email)-[:PRIMARY_EMAIL]-(user)
RETURN email, user
`
let transactionRes
const session = context.driver.session()
try {
transactionRes = await session.run(cypher, { email })
} finally {
session.close()
}
const [result] = transactionRes.records.map(record => {
return {
alreadyExistingEmail: record.get('email').properties,
user: record.get('user') && record.get('user').properties,
}
})
const { alreadyExistingEmail, user } = result || {}
if (user) throw new UserInputError('User account with this email already exists.')
return alreadyExistingEmail
}
export default {
Mutation: {
CreateInvitationCode: async (_parent, args, context, _resolveInfo) => {
args.token = uuid().substring(0, 6)
args.token = generateNonce()
const {
user: { id: userId },
} = context
@ -54,9 +29,9 @@ export default {
return response
},
Signup: async (_parent, args, context) => {
const nonce = uuid().substring(0, 6)
const nonce = generateNonce()
args.nonce = nonce
let emailAddress = await alreadyExistingMail(_parent, args, context)
let emailAddress = await existingEmailAddress(_parent, args, context)
if (emailAddress) return emailAddress
try {
emailAddress = await instance.create('EmailAddress', args)
@ -67,9 +42,9 @@ export default {
},
SignupByInvitation: async (_parent, args, context) => {
const { token } = args
const nonce = uuid().substring(0, 6)
const nonce = generateNonce()
args.nonce = nonce
let emailAddress = await alreadyExistingMail(_parent, args, context)
let emailAddress = await existingEmailAddress(_parent, args, context)
if (emailAddress) return emailAddress
try {
const result = await instance.cypher(

View File

@ -257,7 +257,7 @@ describe('SignupByInvitation', () => {
it('throws unique violation error', async () => {
await expect(mutate({ mutation, variables })).resolves.toMatchObject({
errors: [{ message: 'User account with this email already exists.' }],
errors: [{ message: 'A user account with this email already exists.' }],
})
})
})
@ -307,6 +307,7 @@ describe('Signup', () => {
it('is allowed to signup users by email', async () => {
await expect(mutate({ mutation, variables })).resolves.toMatchObject({
data: { Signup: { email: 'someuser@example.org' } },
errors: undefined,
})
})
@ -342,7 +343,7 @@ describe('Signup', () => {
it('throws UserInputError error because of unique constraint violation', async () => {
await expect(mutate({ mutation, variables })).resolves.toMatchObject({
errors: [{ message: 'User account with this email already exists.' }],
errors: [{ message: 'A user account with this email already exists.' }],
})
})
})
@ -351,6 +352,7 @@ describe('Signup', () => {
it('resolves with the already existing email', async () => {
await expect(mutate({ mutation, variables })).resolves.toMatchObject({
data: { Signup: { email: 'someuser@example.org' } },
errors: undefined,
})
})
@ -359,6 +361,7 @@ describe('Signup', () => {
await expect(neode.all('EmailAddress')).resolves.toHaveLength(2)
await expect(mutate({ mutation, variables })).resolves.toMatchObject({
data: { Signup: { email: 'someuser@example.org' } },
errors: undefined,
})
await expect(neode.all('EmailAddress')).resolves.toHaveLength(2)
})

View File

@ -9,7 +9,7 @@ export default {
countPosts: 'Post',
countComments: 'Comment',
countNotifications: 'NOTIFIED',
countInvites: 'InvitationCode',
countEmails: 'EmailAddress',
countFollows: 'FOLLOWS',
countShouts: 'SHOUTED',
}
@ -28,6 +28,11 @@ export default {
const stat = statistics[mapping[key]]
response[key] = stat ? stat.toNumber() : 0
})
/*
* Note: invites count is calculated this way because invitation codes are not in use yet
*/
response.countInvites = response.countEmails - response.countUsers
} finally {
session.close()
}

View File

@ -22,6 +22,7 @@ export const getBlockedUsers = async context => {
}
export const getBlockedByUsers = async context => {
if (context.user.role === 'moderator' || context.user.role === 'admin') return []
const { neode } = context
const userModel = neode.model('User')
let blockedByUsers = neode

View File

@ -20,4 +20,9 @@ type Mutation {
about: String
termsAndConditionsAgreedVersion: String!
): User
AddEmailAddress(email: String!): EmailAddress
VerifyEmailAddress(
nonce: String!
email: String!
): EmailAddress
}

View File

@ -1,16 +1,21 @@
import faker from 'faker'
export function defaults({ args }) {
const defaults = {
email: faker.internet.email(),
verifiedAt: new Date().toISOString(),
}
args = {
...defaults,
...args,
}
return args
}
export default function create() {
return {
factory: async ({ args, neodeInstance }) => {
const defaults = {
email: faker.internet.email(),
verifiedAt: new Date().toISOString(),
}
args = {
...defaults,
...args,
}
args = defaults({ args })
return neodeInstance.create('EmailAddress', args)
},
}

View File

@ -9,6 +9,7 @@ import createTag from './tags.js'
import createSocialMedia from './socialMedia.js'
import createLocation from './locations.js'
import createEmailAddress from './emailAddresses.js'
import createUnverifiedEmailAddresss from './unverifiedEmailAddresses.js'
export const seedServerHost = 'http://127.0.0.1:4001'
@ -32,6 +33,7 @@ const factories = {
SocialMedia: createSocialMedia,
Location: createLocation,
EmailAddress: createEmailAddress,
UnverifiedEmailAddress: createUnverifiedEmailAddresss,
}
export const cleanDatabase = async (options = {}) => {

View File

@ -0,0 +1,10 @@
import { defaults } from './emailAddresses.js'
export default function create() {
return {
factory: async ({ args, neodeInstance }) => {
args = defaults({ args })
return neodeInstance.create('UnverifiedEmailAddress', args)
},
}
}

View File

@ -689,9 +689,9 @@
regenerator-runtime "^0.13.2"
"@babel/runtime@^7.0.0", "@babel/runtime@^7.5.5":
version "7.5.5"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.5.5.tgz#74fba56d35efbeca444091c7850ccd494fd2f132"
integrity sha512-28QvEGyQyNkB0/m2B4FU7IEZGK2NUrcMtT6BZEFALTguLk+AUT6ofsHtPk5QyjAdUkpMJ+/Em+quwz4HOt30AQ==
version "7.6.2"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.6.2.tgz#c3d6e41b304ef10dcf13777a33e7694ec4a9a6dd"
integrity sha512-EXxN64agfUqqIGeEjI5dL5z0Sw0ZwWo1mLTi4mQowCZ42O59b7DRpZAnTC6OqdF28wMBMFKNb/4uFGrVaigSpg==
dependencies:
regenerator-runtime "^0.13.2"
@ -766,10 +766,10 @@
"@hapi/hoek" "8.x.x"
"@hapi/topo" "3.x.x"
"@hapi/joi@^16.1.4":
version "16.1.4"
resolved "https://registry.yarnpkg.com/@hapi/joi/-/joi-16.1.4.tgz#b039fe474a0ab838c1a90620c53a208fcef75d99"
integrity sha512-m7ctezhxjob+dSpXnCNlgAj6rrEpdSsaWu3GWL3g1AybQCU36mlAo9IwGFJwIxD+oHgdO6mYyviYlaejX+qN6g==
"@hapi/joi@^16.1.5":
version "16.1.5"
resolved "https://registry.yarnpkg.com/@hapi/joi/-/joi-16.1.5.tgz#06bdfec1084f46bff6be8f6b4076d8314c81cea2"
integrity sha512-FnVe0t1YQbF0H4fhFM6qBL7lIP4wdVHdFFBkloxgCkpKKdaCB6Z2UaNI0UalaDRHbRM9BvfTfyUKJg7tGtBSXg==
dependencies:
"@hapi/address" "^2.1.2"
"@hapi/formula" "^1.2.0"
@ -971,7 +971,7 @@
url-regex "~4.1.1"
video-extensions "~1.1.0"
"@metascraper/helpers@^5.7.5", "@metascraper/helpers@^5.7.6":
"@metascraper/helpers@^5.7.6":
version "5.7.6"
resolved "https://registry.yarnpkg.com/@metascraper/helpers/-/helpers-5.7.6.tgz#84007215d3b31525995fd85cf0d28bf6a12bf7bb"
integrity sha512-AD2VTQmMWl/KCUXl9h0fP84VacoiTI/8y8CBgErmYZnm+sliKGedQrDZO3JmzNg73Z5z08GQTjME1WHIDiIQDw==
@ -2626,10 +2626,10 @@ create-error-class@^3.0.0:
dependencies:
capture-stack-trace "^1.0.0"
cross-env@~6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-6.0.0.tgz#3c8e71440ea20aa6faaf5aec541235efc565dac6"
integrity sha512-G/B6gtkjgthT8AP/xN1wdj5Xe18fVyk58JepK8GxpUbqcz3hyWxegocMbvnZK+KoTslwd0ACZ3woi/DVUdVjyQ==
cross-env@~6.0.3:
version "6.0.3"
resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-6.0.3.tgz#4256b71e49b3a40637a0ce70768a6ef5c72ae941"
integrity sha512-+KqxF6LCvfhWvADcDPqo64yVIB31gv/jQulX2NGzKS/g3GEVz6/pt4wjHFtFWsHMddebWD/sDthJemzM4MaAag==
dependencies:
cross-spawn "^7.0.0"
@ -5754,12 +5754,12 @@ metascraper-date@^5.7.6:
dependencies:
"@metascraper/helpers" "^5.7.6"
metascraper-description@^5.7.5:
version "5.7.5"
resolved "https://registry.yarnpkg.com/metascraper-description/-/metascraper-description-5.7.5.tgz#b19be853f7f712d2c388cb0c4086b3b38f6e603b"
integrity sha512-9WHt9XRIj7OCS1wkFbvFI4dOkdgc6I74dC78UcVlwJYJKpRRonm83i5opev4chG4psOwbUhZUhKS3n+Rb8khCg==
metascraper-description@^5.7.6:
version "5.7.6"
resolved "https://registry.yarnpkg.com/metascraper-description/-/metascraper-description-5.7.6.tgz#3a26b8bb8f325b1e959864f2c983ad4ef6050c24"
integrity sha512-DtcIRTwI2RFdy2NBSYCUpekNSqMH4BVNjAJNpWWYsDJZbk5rw6w2gOgzJH4HUM4eYKDkd0/tVd+HZYz57xxYBQ==
dependencies:
"@metascraper/helpers" "^5.7.5"
"@metascraper/helpers" "^5.7.6"
metascraper-image@^5.7.6:
version "5.7.6"

View File

@ -5,7 +5,7 @@ import { Given, When, Then } from 'cypress-cucumber-preprocessor/steps'
let lastReportTitle
let davidIrvingPostTitle = 'The Truth about the Holocaust'
let davidIrvingPostSlug = 'the-truth-about-the-holocaust'
let davidIrvingName = 'David Irving'
let annoyingUserWhoBlockedModeratorTitle = 'Fake news'
const savePostTitle = $post => {
return $post
@ -116,7 +116,7 @@ When(/^I confirm the reporting dialog .*:$/, message => {
Given('somebody reported the following posts:', table => {
table.hashes().forEach(({ id }) => {
const submitter = {
email: `submitter${id}@example.org`,
email: `submitter${id}@example.org`,
password: '1234'
}
cy.factory()
@ -139,7 +139,28 @@ Then('I see all the reported posts including the one from above', () => {
})
})
Then('I see all the reported posts including from the user who blocked me', () => {
cy.get('table tbody').within(() => {
cy.contains('tr', annoyingUserWhoBlockedModeratorTitle)
})
})
Then('each list item links to the post page', () => {
cy.contains(davidIrvingPostTitle).click()
cy.location('pathname').should('contain', '/post')
})
Then('I can visit the post page', () => {
cy.contains(annoyingUserWhoBlockedModeratorTitle).click()
cy.location('pathname').should('contain', '/post')
.get('h3').should('contain', annoyingUserWhoBlockedModeratorTitle)
})
When("they have a post someone has reported", () => {
cy.factory()
.create("Post", {
authorId: 'annnoying-user',
title,
});
})

View File

@ -410,6 +410,20 @@ Given("there is an annoying user called {string}", name => {
});
});
Given("there is an annoying user who has blocked me", () => {
cy.neode()
.first("User", {
role: 'moderator'
})
.then(blocked => {
cy.neode()
.first("User", {
id: 'annoying-user'
})
.relateTo(blocked, "blocked");
});
});
Given("I am on the profile page of the annoying user", name => {
cy.openPage("/profile/annoying-user/spammy-spammer");
});

View File

@ -8,13 +8,15 @@ Feature: Report and Moderate
So I can look into it and decide what to do
Background:
Given we have this user in our database:
| id | name |
| u67 | David Irving|
Given we have the following user accounts:
| id | name |
| u67 | David Irving |
| annoying-user | I'm gonna block Moderators and Admins HA HA HA |
Given we have the following posts in our database:
| authorId | id | title | content |
| u67 | p1 | The Truth about the Holocaust | It never existed! |
| authorId | id | title | content |
| u67 | p1 | The Truth about the Holocaust | It never existed! |
| annoying-user | p2 | Fake news | This content is demonstratably infactual in some way |
Scenario Outline: Report a post from various pages
Given I am logged in with a "user" role
When I see David Irving's post on the <Page>
@ -56,6 +58,18 @@ Feature: Report and Moderate
Then I see all the reported posts including the one from above
And each list item links to the post page
Scenario: Review reported posts of a user who's blocked a moderator
Given somebody reported the following posts:
| id |
| p2 |
And my user account has the role "moderator"
And there is an annoying user who has blocked me
And I am logged in
When I click on the avatar menu in the top right corner
And I click on "Moderation"
Then I see all the reported posts including from the user who blocked me
And I can visit the post page
Scenario: Normal user can't see the moderation page
Given I am logged in with a "user" role
When I click on the avatar menu in the top right corner

View File

@ -0,0 +1,57 @@
# Backup (online)
## Online backups are only avaible with a Neo4j Enterprise and a license, see https://neo4j.com/licensing/ for the different licenses available
This tutorial explains how to carry out an online backup of your Neo4J
database in a kubernetes cluster.
One of the benefits of doing an online backup is that the Neo4j database does not need to be stopped, so there is no downtime. Read [the docs](https://neo4j.com/docs/operations-manual/current/backup/performing/)
To use Neo4j Enterprise you must add this line to your configmap, if using, or your deployment `nitro-neo4j` env.
```
NEO4J_ACCEPT_LICENSE_AGREEMENT: "yes"
```
## Create a Backup in Kubernetes
```sh
# Backup the database with one command, this will get the nitro-neo4j pod, ssh into it, and run the backup command
kubectl -n=human-connection exec -it $(kubectl -n=human-connection get pods | grep nitro-neo4j | awk '{ print $1 }') -- neo4j-admin backup --backup-dir=/var/lib/neo4j --name=neo4j-backup
# Download the file from the pod to your computer.
kubectl cp human-connection/$(kubectl -n=human-connection get pods | grep nitro-neo4j | awk '{ print $1 }'):/var/lib/neo4j/neo4j-backup ./neo4j-backup/
```
You should now have a backup of the database locally. If you want, you can simulate disaster recovery by sshing into the nitro-neo4j pod, deleting all data and restoring from backup
## Disaster where database data is gone somehow
```sh
kubectl -n=human-connection exec -it $(kubectl -n=human-connection get pods | grep nitro-neo4j |awk '{ print $1 }') bash
# Enter cypher-shell
cypher-shell
# Delete all data
> MATCH (n) DETACH DELETE (n);
exit
```
## Restore a backup in Kubernetes
Restoration must be done while the database is not running, see [our docs](https://docs.human-connection.org/human-connection/deployment/volumes/neo4j-offline-backup#stop-and-restart-neo-4-j-database-in-kubernetes) for how to stop the database, but keep the container running
After, you have stopped the database, and have the pod running, you can restore the database by running these commands:
```sh
kubectl --namespace=human-connection get pods
# Copy the ID of the pod running Neo4J.
# Then upload your local backup to the pod. Note that once the pod gets deleted
# e.g. if you change the deployment, the backup file is gone with it.
kubectl cp ./neo4j-backup/ human-connection/<POD-ID>:/root/
kubectl --namespace=human-connection exec -it <POD-ID> bash
# Once you're in the pod restore the backup and overwrite the default database
# called `graph.db` with `--force`.
# This will delete all existing data in database `graph.db`!
neo4j-admin restore --from=/root/neo4j-backup --force
exit
```
Revert your changes to deployment `nitro-neo4j` which will restart the database.

View File

@ -57,6 +57,7 @@ services:
environment:
- NEO4J_AUTH=none
- NEO4J_dbms_security_procedures_unrestricted=algo.*,apoc.*
- NEO4J_ACCEPT_LICENSE_AGREEMENT=yes
ports:
- 7687:7687
- 7474:7474

View File

@ -1,4 +1,4 @@
FROM neo4j:3.5.11
FROM neo4j:3.5.11-enterprise
LABEL Description="Neo4J database of the Social Network Human-Connection.org with preinstalled database constraints and indices" Vendor="Human Connection gGmbH" Version="0.0.1" Maintainer="Human Connection gGmbH (developer@human-connection.org)"
ARG BUILD_COMMIT

View File

@ -21,7 +21,7 @@
"devDependencies": {
"bcryptjs": "^2.4.3",
"codecov": "^3.6.1",
"cross-env": "^6.0.0",
"cross-env": "^6.0.3",
"cypress": "^3.4.1",
"cypress-cucumber-preprocessor": "^1.16.0",
"cypress-file-upload": "^3.3.4",

View File

@ -115,7 +115,7 @@ describe('Signup', () => {
mocks.$apollo.mutate = jest
.fn()
.mockRejectedValue(
new Error('UserInputError: User account with this email already exists.'),
new Error('UserInputError: A user account with this email already exists.'),
)
})

View File

@ -128,7 +128,7 @@ export default {
} catch (err) {
const { message } = err
const mapping = {
'User account with this email already exists': 'email-exists',
'A user account with this email already exists': 'email-exists',
'Invitation code already used or does not exist': 'invalid-invitation-token',
}
for (const [pattern, key] of Object.entries(mapping)) {

View File

@ -0,0 +1,20 @@
import gql from 'graphql-tag'
export const AddEmailAddressMutation = gql`
mutation($email: String!) {
AddEmailAddress(email: $email) {
email
createdAt
}
}
`
export const VerifyEmailAddressMutation = gql`
mutation($email: String!, $nonce: String!) {
VerifyEmailAddress(email: $email, nonce: $nonce) {
email
verifiedAt
createdAt
}
}
`

View File

@ -158,6 +158,27 @@
"labelBio": "Über dich",
"success": "Deine Daten wurden erfolgreich aktualisiert!"
},
"email": {
"validation": {
"same-email": "Das ist deine aktuelle E-Mail Addresse"
},
"name": "Deine E-Mail",
"labelEmail": "E-Mail Adresse ändern",
"labelNewEmail": "Neue E-Mail Adresse",
"labelNonce": "Bestätigungscode eingeben",
"success": "Eine neue E-Mail Addresse wurde registriert.",
"submitted": "Eine E-Mail zur Bestätigung deiner Adresse wurde an <b>{email}</b> gesendet.",
"change-successful": "Deine E-Mail Adresse wurde erfolgreich geändert.",
"verification-error": {
"message": "Deine E-Mail Adresse konnte nicht verifiziert werden.",
"support": "Wenn das Problem weiterhin besteht, kontaktiere uns gerne per E-Mail an",
"explanation": "Das kann verschiedene Ursachen haben:",
"reason": {
"invalid-nonce": "Ist der Bestätigungscode falsch?",
"no-email-request": "Bist du dir sicher, dass du eine Änderung deiner E-Mail Adresse angefragt hattest?"
}
}
},
"validation": {
"slug": {
"regex": "Es sind nur Kleinbuchstaben, Zahlen, Unterstriche oder Bindestriche erlaubt.",

View File

@ -159,6 +159,27 @@
"labelBio": "About You",
"success": "Your data was successfully updated!"
},
"email": {
"validation": {
"same-email": "This is your current email address"
},
"name": "Your email",
"labelEmail": "Change your email address",
"labelNewEmail": "New email Address",
"labelNonce": "Enter your code",
"success": "A new email address has been registered.",
"submitted": "An email to verify your address has been sent to <b>{email}</b>.",
"change-successful": "Your email address has been changed successfully.",
"verification-error": {
"message": "Your email could not be changed.",
"explanation": "This can have different causes:",
"reason": {
"invalid-nonce": "Is the confirmation code invalid?",
"no-email-request": "Are you certain that you requested a change of your email address?"
},
"support": "If the problem persists, please contact us by email at"
}
},
"validation": {
"slug": {
"regex": "Allowed characters are only lowercase letters, numbers, underscores and hyphens.",
@ -254,7 +275,7 @@
"users": {
"name": "Users",
"form": {
"placeholder": "E-Mail, name or description"
"placeholder": "email, name or description"
},
"table": {
"columns": {

View File

@ -52,7 +52,7 @@
},
"dependencies": {
"@human-connection/styleguide": "0.5.21",
"@nuxtjs/apollo": "^4.0.0-rc13.1",
"@nuxtjs/apollo": "^4.0.0-rc14",
"@nuxtjs/axios": "~5.6.0",
"@nuxtjs/dotenv": "~1.4.1",
"@nuxtjs/pwa": "^3.0.0-beta.19",
@ -62,7 +62,7 @@
"apollo-cache-inmemory": "~1.6.3",
"apollo-client": "~2.6.4",
"cookie-universal-nuxt": "~2.0.18",
"cross-env": "~6.0.0",
"cross-env": "~6.0.3",
"date-fns": "2.4.1",
"express": "~4.17.1",
"graphql": "~14.5.8",
@ -70,7 +70,7 @@
"jsonwebtoken": "~8.5.1",
"linkify-it": "~2.2.0",
"node-fetch": "^2.6.0",
"nuxt": "~2.9.2",
"nuxt": "~2.10.0",
"nuxt-dropzone": "^1.0.4",
"nuxt-env": "~0.1.0",
"stack-utils": "^1.0.2",

View File

@ -23,6 +23,10 @@ export default {
name: this.$t('settings.data.name'),
path: `/settings`,
},
{
name: this.$t('settings.email.name'),
path: `/settings/my-email-address`,
},
{
name: this.$t('settings.security.name'),
path: `/settings/security`,

View File

@ -31,14 +31,7 @@
:placeholder="$t('settings.data.labelBio')"
/>
<template slot="footer">
<ds-button
style="float: right;"
icon="check"
:disabled="errors"
type="submit"
:loading="loadingData"
primary
>
<ds-button icon="check" :disabled="errors" type="submit" :loading="loadingData" primary>
{{ $t('actions.save') }}
</ds-button>
</template>

View File

@ -0,0 +1,53 @@
import { mount, createLocalVue } from '@vue/test-utils'
import EnterNoncePage from './enter-nonce.vue'
import Styleguide from '@human-connection/styleguide'
const localVue = createLocalVue()
localVue.use(Styleguide)
describe('EnterNoncePage', () => {
let mocks
let wrapper
beforeEach(() => {
wrapper = null
mocks = {
$t: jest.fn(t => t),
$route: {
query: {},
},
$router: {
replace: jest.fn(),
},
}
})
describe('mount', () => {
const Wrapper = () => {
return mount(EnterNoncePage, {
mocks,
localVue,
})
}
describe('form', () => {
describe('submit', () => {
it('renders form errors', () => {
wrapper = Wrapper()
wrapper.find('form').trigger('submit')
expect(mocks.$router.replace).not.toHaveBeenCalled()
})
describe('entering a nonce', () => {
it('redirects to my-email-address/verify', () => {
wrapper = Wrapper()
wrapper.find('#nonce').setValue('foobar')
wrapper.find('form').trigger('submit')
expect(mocks.$router.replace).toHaveBeenCalled()
})
})
})
})
})
})

View File

@ -0,0 +1,59 @@
<template>
<ds-form v-model="form" :schema="formSchema" @submit="submit">
<template slot-scope="{ errors }">
<ds-card :header="$t('settings.email.name')">
<ds-input
id="email"
model="email"
icon="envelope"
disabled
:label="$t('settings.email.labelNewEmail')"
/>
<ds-input
id="nonce"
model="nonce"
icon="question-circle"
:label="$t('settings.email.labelNonce')"
/>
<template slot="footer">
<ds-button class="submit-button" icon="check" :disabled="errors" type="submit" primary>
{{ $t('actions.save') }}
</ds-button>
</template>
</ds-card>
</template>
</ds-form>
</template>
<script>
export default {
data() {
return {
formSchema: {
nonce: { type: 'string', required: true },
},
}
},
computed: {
form: {
get: function() {
const { email = '', nonce = '' } = this.$route.query
return { email, nonce }
},
set: function(formData) {
this.formData = formData
},
},
},
methods: {
async submit() {
const { email, nonce } = this.formData
this.$router.replace({
path: 'verify',
query: { email, nonce },
})
},
},
}
</script>

View File

@ -0,0 +1,116 @@
import { config, mount, createLocalVue } from '@vue/test-utils'
import EmailSettingsIndexPage from './index.vue'
import Vuex from 'vuex'
import Styleguide from '@human-connection/styleguide'
const localVue = createLocalVue()
localVue.use(Vuex)
localVue.use(Styleguide)
config.stubs['sweetalert-icon'] = '<span><slot /></span>'
describe('EmailSettingsIndexPage', () => {
let store
let mocks
let wrapper
beforeEach(() => {
wrapper = null
store = new Vuex.Store({
getters: {
'auth/user': () => {
return { id: 'u23', email: 'some-mail@example.org' }
},
},
})
mocks = {
$t: jest.fn(t => t),
$toast: {
success: jest.fn(),
error: jest.fn(),
},
$apollo: {
mutate: jest.fn().mockResolvedValue(),
},
$router: {
push: jest.fn(),
},
}
})
describe('mount', () => {
const Wrapper = () => {
return mount(EmailSettingsIndexPage, {
store,
mocks,
localVue,
})
}
describe('form', () => {
describe('submit', () => {
beforeEach(jest.useFakeTimers)
describe('email unchanged', () => {
beforeEach(() => {
wrapper = Wrapper()
wrapper.find('form').trigger('submit')
})
it('displays form errors', () => {
expect(wrapper.text()).not.toContain('settings.email.submitted')
expect(wrapper.text()).toContain('settings.email.validation.same-email')
})
it('does not call $apollo.mutate', () => {
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
})
})
describe('enter another email', () => {
beforeEach(() => {
wrapper = Wrapper()
wrapper.find('#email').setValue('yet-another-email@example.org')
wrapper.find('form').trigger('submit')
})
it('calls $apollo.mutate', () => {
expect(mocks.$apollo.mutate).toHaveBeenCalled()
})
it('no form errors', () => {
expect(wrapper.text()).not.toContain('settings.email.validation.same-email')
expect(wrapper.text()).toContain('settings.email.submitted')
})
describe('after timeout', () => {
beforeEach(jest.runAllTimers)
it('redirects to `my-email-address/enter-nonce`', () => {
expect(mocks.$router.push).toHaveBeenCalledWith({
path: 'my-email-address/enter-nonce',
query: { email: 'yet-another-email@example.org' },
})
})
})
})
describe('if backend responds with unique constraint violation', () => {
beforeEach(() => {
mocks.$apollo.mutate = jest.fn().mockRejectedValue({
message: 'User account already exists',
})
wrapper = Wrapper()
wrapper.find('#email').setValue('already-taken@example.org')
wrapper.find('form').trigger('submit')
})
it('translates error message', () => {
expect(wrapper.text()).toContain('registration.signup.form.errors.email-exists')
})
})
})
})
})
})

View File

@ -0,0 +1,113 @@
<template>
<ds-card centered v-if="success">
<transition name="ds-transition-fade">
<sweetalert-icon icon="info" />
</transition>
<ds-text v-html="submitMessage" />
</ds-card>
<ds-form v-else v-model="form" :schema="formSchema" @submit="submit">
<template slot-scope="{ errors }">
<ds-card :header="$t('settings.email.name')">
<ds-input
id="email"
model="email"
icon="envelope"
:label="$t('settings.email.labelEmail')"
/>
<template slot="footer">
<ds-space class="backendErrors" v-if="backendErrors">
<ds-text align="center" bold color="danger">{{ backendErrors.message }}</ds-text>
</ds-space>
<ds-button icon="check" :disabled="errors" type="submit" primary>
{{ $t('actions.save') }}
</ds-button>
</template>
</ds-card>
</template>
</ds-form>
</template>
<script>
import { mapGetters } from 'vuex'
import { AddEmailAddressMutation } from '~/graphql/EmailAddress.js'
import { SweetalertIcon } from 'vue-sweetalert-icons'
export default {
components: {
SweetalertIcon,
},
data() {
return {
backendErrors: null,
success: false,
}
},
computed: {
submitMessage() {
const { email } = this.formData
return this.$t('settings.email.submitted', { email })
},
...mapGetters({
currentUser: 'auth/user',
}),
form: {
get: function() {
const { email } = this.currentUser
return { email }
},
set: function(formData) {
this.formData = formData
},
},
formSchema() {
const { email } = this.currentUser
const sameEmailValidationError = this.$t('settings.email.validation.same-email')
return {
email: [
{ type: 'email', required: true },
{
validator(rule, value, callback, source, options) {
const errors = []
if (email === value) {
errors.push(sameEmailValidationError)
}
return errors
},
},
],
}
},
},
methods: {
async submit() {
const { email } = this.formData
try {
await this.$apollo.mutate({
mutation: AddEmailAddressMutation,
variables: { email },
})
this.$toast.success(this.$t('settings.email.success'))
this.success = true
setTimeout(() => {
this.$router.push({
path: 'my-email-address/enter-nonce',
query: { email },
})
}, 3000)
} catch (err) {
if (err.message.includes('exists')) {
// We cannot use form validation errors here, the backend does not
// have a query to filter for email addresses. This is a privacy
// consideration. We could implement a dedicated query to check that
// but I think it's too much effort for this feature.
this.backendErrors = { message: this.$t('registration.signup.form.errors.email-exists') }
return
}
this.$toast.error(err.message)
}
},
},
}
</script>

View File

@ -0,0 +1,164 @@
import { config, mount, createLocalVue } from '@vue/test-utils'
import EmailVerifyPage from './verify.vue'
import Vuex from 'vuex'
import Styleguide from '@human-connection/styleguide'
const localVue = createLocalVue()
localVue.use(Vuex)
localVue.use(Styleguide)
config.stubs['client-only'] = '<span><slot /></span>'
config.stubs['sweetalert-icon'] = '<span><slot /></span>'
describe('EmailVerifyPage', () => {
let store
let mocks
let wrapper
let setUser
beforeEach(() => {
setUser = jest.fn()
wrapper = null
store = new Vuex.Store({
getters: {
'auth/user': () => {
return { id: 'u23', email: 'some-mail@example.org' }
},
},
mutations: {
'auth/SET_USER': setUser,
},
})
mocks = {
$t: jest.fn(t => t),
$toast: {
success: jest.fn(),
error: jest.fn(),
},
$router: {
replace: jest.fn(),
},
store,
}
})
describe('asyncData', () => {
const asyncDataAction = () => {
const context = {
store: mocks.store,
query: {},
app: {
apolloProvider: {
defaultClient: mocks.$apollo,
},
},
}
return EmailVerifyPage.asyncData(context)
}
describe('backend sends successful response', () => {
beforeEach(() => {
mocks = {
...mocks,
$apollo: {
mutate: jest.fn().mockResolvedValue({
data: {
VerifyEmailAddress: {
email: 'verified-email@example.org',
},
},
}),
},
}
})
it('sets `success` to true', async () => {
await expect(asyncDataAction()).resolves.toEqual({
success: true,
})
})
it("updates current user's email", async () => {
await asyncDataAction()
expect(setUser).toHaveBeenCalledWith({}, { id: 'u23', email: 'verified-email@example.org' })
})
})
describe('backend sends unsuccessful response', () => {
beforeEach(() => {
mocks = {
...mocks,
$apollo: {
mutate: jest.fn().mockRejectedValue({
data: { VerifyEmailAddress: null },
errors: [{ message: 'User account already exists with that email' }],
}),
},
}
})
it('sets `success` to false', async () => {
await expect(asyncDataAction()).resolves.toEqual({
success: false,
})
})
it('does not updates current user', async () => {
await asyncDataAction()
expect(setUser).not.toHaveBeenCalled()
})
})
})
describe('mount', () => {
beforeEach(jest.useFakeTimers)
const Wrapper = () => {
return mount(EmailVerifyPage, {
store,
mocks,
localVue,
})
}
describe('given successful verification', () => {
beforeEach(() => {
mocks = { ...mocks, success: true }
wrapper = Wrapper()
})
it('shows success message', () => {
expect(wrapper.text()).toContain('settings.email.change-successful')
})
describe('after timeout', () => {
beforeEach(jest.runAllTimers)
it('redirects to email settings page', () => {
expect(mocks.$router.replace).toHaveBeenCalledWith({
name: 'settings-my-email-address',
})
})
})
})
describe('given unsuccessful verification', () => {
beforeEach(() => {
mocks = { ...mocks, success: false }
wrapper = Wrapper()
})
it('shows success message', () => {
expect(wrapper.text()).toContain('settings.email.verification-error')
})
describe('after timeout', () => {
beforeEach(jest.runAllTimers)
it('does not redirect', () => {
expect(mocks.$router.replace).not.toHaveBeenCalledWith()
})
})
})
})
})

View File

@ -0,0 +1,99 @@
<template>
<ds-card>
<transition name="ds-transition-fade">
<client-only>
<sweetalert-icon :icon="sweetAlertIcon" />
</client-only>
</transition>
<ds-space v-if="success">
<ds-text bold align="center">
{{ $t(`settings.email.change-successful`) }}
</ds-text>
</ds-space>
<template v-else>
<ds-text bold align="center">
{{ $t(`settings.email.verification-error.message`) }}
</ds-text>
<ds-space class="message">
<client-only>
<ds-text>
<ds-space margin-top="large" margin-bottom="small">
{{ $t(`settings.email.verification-error.explanation`) }}
</ds-space>
<ds-list>
<ds-list-item>
{{ $t(`settings.email.verification-error.reason.invalid-nonce`) }}
</ds-list-item>
<ds-list-item>
{{ $t(`settings.email.verification-error.reason.no-email-request`) }}
</ds-list-item>
</ds-list>
{{ $t('settings.email.verification-error.support') }}
<a href="mailto:support@human-connection.org">support@human-connection.org</a>
</ds-text>
</client-only>
</ds-space>
</template>
</ds-card>
</template>
<script>
import { VerifyEmailAddressMutation } from '~/graphql/EmailAddress.js'
import { SweetalertIcon } from 'vue-sweetalert-icons'
export default {
components: {
SweetalertIcon,
},
computed: {
sweetAlertIcon() {
return this.success ? 'success' : 'error'
},
},
created() {
if (this.success) {
setTimeout(() => {
this.$router.replace({ name: 'settings-my-email-address' })
}, 3000)
}
},
async asyncData(context) {
const {
store,
query,
app: { apolloProvider },
} = context
const client = apolloProvider.defaultClient
let success
const { email = '', nonce = '' } = query
const currentUser = store.getters['auth/user']
try {
const response = await client.mutate({
mutation: VerifyEmailAddressMutation,
variables: { email, nonce },
})
const {
data: { VerifyEmailAddress },
} = response
success = true
store.commit(
'auth/SET_USER',
{ ...currentUser, email: VerifyEmailAddress.email },
{ root: true },
)
} catch (error) {
success = false
}
return { success }
},
}
</script>
<style lang="scss" scoped>
.message {
display: flex;
justify-content: space-around;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -741,9 +741,9 @@
regenerator-runtime "^0.12.0"
"@babel/runtime@^7.5.5":
version "7.5.5"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.5.5.tgz#74fba56d35efbeca444091c7850ccd494fd2f132"
integrity sha512-28QvEGyQyNkB0/m2B4FU7IEZGK2NUrcMtT6BZEFALTguLk+AUT6ofsHtPk5QyjAdUkpMJ+/Em+quwz4HOt30AQ==
version "7.6.2"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.6.2.tgz#c3d6e41b304ef10dcf13777a33e7694ec4a9a6dd"
integrity sha512-EXxN64agfUqqIGeEjI5dL5z0Sw0ZwWo1mLTi4mQowCZ42O59b7DRpZAnTC6OqdF28wMBMFKNb/4uFGrVaigSpg==
dependencies:
regenerator-runtime "^0.13.2"
@ -1772,10 +1772,10 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4:
safe-buffer "^5.0.1"
sha.js "^2.4.8"
cross-env@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-6.0.0.tgz#3c8e71440ea20aa6faaf5aec541235efc565dac6"
integrity sha512-G/B6gtkjgthT8AP/xN1wdj5Xe18fVyk58JepK8GxpUbqcz3hyWxegocMbvnZK+KoTslwd0ACZ3woi/DVUdVjyQ==
cross-env@^6.0.3:
version "6.0.3"
resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-6.0.3.tgz#4256b71e49b3a40637a0ce70768a6ef5c72ae941"
integrity sha512-+KqxF6LCvfhWvADcDPqo64yVIB31gv/jQulX2NGzKS/g3GEVz6/pt4wjHFtFWsHMddebWD/sDthJemzM4MaAag==
dependencies:
cross-spawn "^7.0.0"