Skip to content
This repository has been archived by the owner on Feb 8, 2018. It is now read-only.

Email - Part 1 #2752

Merged
merged 107 commits into from
Dec 9, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
107 commits
Select commit Hold shift + click to select a range
aebe84b
Add hash and ctime fields to email_address_with_confirmation
rohitpaulk Sep 8, 2014
3c589fa
Update hash and ctime when email is changed
rohitpaulk Sep 9, 2014
c12554c
Verification page
rohitpaulk Sep 9, 2014
301e94f
Sending emails
rohitpaulk Sep 9, 2014
cdd872f
Account page UI - email verification
rohitpaulk Sep 10, 2014
2db3f30
Cleanup, split into functions
rohitpaulk Sep 12, 2014
803f25b
Add Resend email button
rohitpaulk Sep 12, 2014
f61197c
Added tests, simplified verify_email
rohitpaulk Sep 12, 2014
fc0ffbe
Update default Mandrill key
chadwhitacre Sep 30, 2014
f406e37
Some tweaks to mails
chadwhitacre Sep 30, 2014
5ed0e38
refactor
Changaco Oct 2, 2014
06cec4e
add test fixtures
Changaco Oct 2, 2014
21a561c
put the email hash timeout in a constant
Changaco Oct 2, 2014
6e2acec
simplify by using `getattr()` instead of `hasattr()`
Changaco Oct 2, 2014
5d0088e
fix indentation
Changaco Oct 2, 2014
135e99c
add test for expired hash
Changaco Oct 2, 2014
1a4528c
check return code in tests
Changaco Oct 2, 2014
baea124
refactor
Changaco Oct 3, 2014
c9112f1
coding style
Changaco Oct 3, 2014
abdf353
only compare once and do it in constant time
Changaco Oct 3, 2014
bc8a77c
rename `hash` to `nonce`
Changaco Oct 3, 2014
e7cec73
simplify a couple of test helpers (DRY)
Changaco Oct 3, 2014
ee8af15
Spruce up the design of the verification email
chadwhitacre Oct 6, 2014
a07f027
Switch to CDN image
chadwhitacre Oct 6, 2014
81710eb
Add footer to emails with our address
chadwhitacre Oct 6, 2014
69bef23
Factor header out of email as well
chadwhitacre Oct 6, 2014
9288749
Add alt text to header image
chadwhitacre Oct 6, 2014
3af999f
Fix bug where we were mutating underlying message
chadwhitacre Oct 6, 2014
50fa80e
Move Something wrong? to footer
chadwhitacre Oct 6, 2014
1c6aad2
Homepage is unhelpful atm; route to profile
chadwhitacre Oct 6, 2014
195088e
Improve success message for email verification
chadwhitacre Oct 6, 2014
99b65e4
Changing to emails table
rohitpaulk Oct 21, 2014
bd93c02
Fix tests
rohitpaulk Oct 21, 2014
a34edaa
Failing test for adding already verified emails
rohitpaulk Oct 22, 2014
e4e4276
Added EmailAlreadyTaken exception
rohitpaulk Oct 22, 2014
904d31b
Updated JS callback behaviour
rohitpaulk Oct 22, 2014
0214e51
Fixed double nonce problem
rohitpaulk Oct 22, 2014
ee6b490
Fixed JS behaviour for resending verification email
rohitpaulk Oct 22, 2014
208c766
Send notice to old email address on email change
rohitpaulk Oct 22, 2014
a826986
rename 'confirm' to 'verify' everywhere.
rohitpaulk Oct 23, 2014
37300cc
forget the trigger function, add check for already verified
rohitpaulk Nov 13, 2014
d881efa
Cleanup
rohitpaulk Nov 13, 2014
890ecd8
allow having multiple verified and unverified email addresses
Changaco Nov 17, 2014
88a0787
implement UI for multiple email addresses
Changaco Nov 17, 2014
5fe231f
fix and regroup the email tests
Changaco Nov 17, 2014
03bfd20
add `branch.py` script for initial email burst
Changaco Nov 18, 2014
c8c71e2
limit the number of email addresses
Changaco Nov 18, 2014
12453f1
remove leftover test fixture
Changaco Nov 18, 2014
c36a918
remove email_address_with_confirmation
Changaco Nov 20, 2014
0b35719
notify users who don't have a verified email address
Changaco Nov 20, 2014
71be243
fix i18n in `verify-email.html.spt`
Changaco Nov 20, 2014
43448ca
make the `website` object available to the DB model classes
Changaco Nov 20, 2014
fad8bf2
split some i18n functions for reuse in `Participant.send_email()`
Changaco Nov 21, 2014
645f5ce
implement email i18n
Changaco Nov 21, 2014
cdbbc42
move email methods under "Emails" heading instead of "Random Junk"
Changaco Nov 21, 2014
75b9910
set `email_lang` when adding or verifying an address for the first time
Changaco Nov 21, 2014
0c8c43c
improve test coverage
Changaco Nov 21, 2014
74b573c
stop trying to attach the `website` object to model classes
Changaco Nov 26, 2014
30bf017
fix branch.py
Changaco Nov 26, 2014
21198b7
Fixed link in initial email
rohitpaulk Nov 27, 2014
4e454d9
show new email address in verification notice, not old one.
rohitpaulk Nov 27, 2014
ee3c871
remove i18n TODOs, see #2977
Changaco Dec 2, 2014
c4d91c8
don't use emails from elsewhere in `branch.py`
Changaco Dec 3, 2014
8538edc
fix the schema of the new `emails` table
Changaco Dec 3, 2014
8274597
list the email addresses in the order they were added
Changaco Dec 4, 2014
3aa0e36
fix email events
Changaco Dec 4, 2014
11ace50
allow to hit enter when entering an email address
Changaco Dec 4, 2014
d7fe2e0
raise `EmailAlreadyTaken` early if an email address is already verified
Changaco Dec 4, 2014
ed30791
Tweak "already linked" error message
chadwhitacre Dec 5, 2014
6290606
Standardize on "connect"
chadwhitacre Dec 5, 2014
eab4f81
failing test for email addresses deletion when closing account
Changaco Dec 5, 2014
f65199c
delete all email addresses when closing account
Changaco Dec 5, 2014
7ae4095
failing test for the merging of email addresses
Changaco Dec 5, 2014
f1610f3
merge email addresses in `take_over`
Changaco Dec 5, 2014
a8dec44
strip email messages
Changaco Dec 5, 2014
2bc84bd
fix condition
Changaco Dec 5, 2014
f07a1db
Revise content for verification messages
chadwhitacre Dec 5, 2014
468239f
Trim up whitespace in text/plain emails
chadwhitacre Dec 6, 2014
a969f77
don't change the primary email when verifying a secondary address
Changaco Dec 6, 2014
2e60c4c
Update branch.py to build link from env
chadwhitacre Dec 8, 2014
365e182
Revise initial verification message
chadwhitacre Dec 8, 2014
3110318
Link straight to emails, at least
chadwhitacre Dec 8, 2014
6793871
more explicit success/error messages
Changaco Dec 8, 2014
3afd352
Add a period to a notification
chadwhitacre Dec 8, 2014
5286d52
fix quoting issue
Changaco Dec 8, 2014
aea5b43
fix test
Changaco Dec 8, 2014
8a1a80a
Post to Mandrill before recording change locally
chadwhitacre Dec 8, 2014
7931824
wait before reloading the page so the user can read the notification
Changaco Dec 8, 2014
3a69204
Add "Resend" button to Verify page
chadwhitacre Dec 8, 2014
1beb434
Reformat branch.sql for 100 chars
chadwhitacre Dec 8, 2014
b72d4bb
Handle the case where the qs is empty/malformed
chadwhitacre Dec 8, 2014
f475cec
Base64-encode the `then` parameter during auth
chadwhitacre Dec 8, 2014
8328a18
Require authentication to verify emails
chadwhitacre Dec 8, 2014
7b14196
Fix up test suite
chadwhitacre Dec 8, 2014
df5a49b
Harmonize VERIFICATION_ constants
chadwhitacre Dec 9, 2014
14be73e
Tweak comment
chadwhitacre Dec 9, 2014
87ef00a
Give ourselves room for comments in branch.sql
chadwhitacre Dec 9, 2014
66f8f64
Add a constraint and comment to verified
chadwhitacre Dec 9, 2014
c8d0c16
Clean up check on number of emails
chadwhitacre Dec 9, 2014
114b39b
Make room for longer field names
chadwhitacre Dec 9, 2014
f01be44
Rename {c,m}time to verification_{start,end}
chadwhitacre Dec 9, 2014
a79ed2f
Flatten verify_email
chadwhitacre Dec 9, 2014
42bbdc0
Handle case where auth'd is on wrong verify.html
chadwhitacre Dec 9, 2014
dd61c5b
Use same pattern for "already taken" error
chadwhitacre Dec 9, 2014
ee65f9a
Note MANDRILL_KEY in README
chadwhitacre Dec 9, 2014
2ab4145
fix order of checks
Changaco Dec 9, 2014
835de34
fix use of obsolete column name in `branch.py`
Changaco Dec 9, 2014
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,16 +223,23 @@ The site works without this, except for the credit card page. Visit the
[Balanced Documentation](https://www.balancedpayments.com/docs) if you want to
know more about creating marketplaces.

The GITHUB_* keys are for a gratipay-dev application in the Gratipay organization
on Github. It points back to localhost:8537, which is where Gratipay will be
running if you start it locally with `make run`. Similarly with the TWITTER_*
keys, but there they required us to spell it `127.0.0.1`.
The `GITHUB_*` keys are for a gratipay-dev application in the Gratipay
organization on Github. It points back to localhost:8537, which is where
Gratipay will be running if you start it locally with `make run`. Similarly
with the `TWITTER_*` keys, but there they required us to spell it `127.0.0.1`.

If you wish to use a different username or database name for the database, you
should override the `DATABASE_URL` in `local.env` using the following format:

DATABASE_URL=postgres://<username>@localhost/<database name>

The `MANDRILL_KEY` value in `defaults.env` is for a test mail server, which
won't actually send email to you. If you need to receive email during
development then sign up for an account of your own at
[Mandrill](http://mandrill.com/) and override `MANDRILL_KEY` in your
`local.env`.


Vagrant
-------
If you have Vagrant installed, you can run Gratipay by running `vagrant up` from the project directory. Please note that if you ever switch between running Gratipay on your own machine to Vagrant or vice versa, you will need to run `make clean`.
Expand Down
63 changes: 63 additions & 0 deletions branch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"""
This is a one-off script to populate the new `emails` table using the addresses
we have in `participants`.
"""
from __future__ import division, print_function, unicode_literals

from urllib import quote
import uuid

import gratipay
from aspen.utils import utcnow
from postgres.orm import Model

import gratipay.wireup

env = gratipay.wireup.env()
tell_sentry = gratipay.wireup.make_sentry_teller(env)
db = gratipay.wireup.db(env)
gratipay.wireup.mail(env)
gratipay.wireup.load_i18n('.', tell_sentry)
gratipay.wireup.canonical(env)


class EmailAddressWithConfirmation(Model):
typname = "email_address_with_confirmation"

db.register_model(EmailAddressWithConfirmation)


def add_email(self, email):
nonce = str(uuid.uuid4())
verification_start = utcnow()

scheme = gratipay.canonical_scheme
host = gratipay.canonical_host
username = self.username_lower
quoted_email = quote(email)
link = "{scheme}://{host}/{username}/emails/verify.html?email={quoted_email}&nonce={nonce}"
self.send_email('initial',
email=email,
link=link.format(**locals()),
username=self.username,
include_unsubscribe=False)

db.run("""
INSERT INTO emails
(address, nonce, verification_start, participant)
VALUES (%s, %s, %s, %s)
""", (email, nonce, verification_start, self.username))


participants = db.all("""
SELECT p.*::participants
FROM participants p
WHERE email IS NOT NULL
AND NOT is_closed
AND is_suspicious IS NOT true
AND claimed_time IS NOT NULL;
""")
total = len(participants)
for i, p in enumerate(participants, 1):
print('sending email to %s (%i/%i)' % (p.username, i, total))
add_email(p, p.email.address)
46 changes: 46 additions & 0 deletions branch.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
BEGIN;
CREATE TABLE emails
( id serial PRIMARY KEY
, address text NOT NULL
, verified boolean DEFAULT NULL
CONSTRAINT verified_cant_be_false
-- Only use TRUE and NULL, so that the
-- unique constraint below functions
-- properly.
CHECK (verified IS NOT FALSE)
, nonce text
, verification_start timestamp with time zone NOT NULL
DEFAULT CURRENT_TIMESTAMP
, verification_end timestamp with time zone
, participant text NOT NULL
REFERENCES participants
ON UPDATE CASCADE
ON DELETE RESTRICT

, UNIQUE (address, verified) -- A verified email address can't be linked to multiple
-- participants. However, an *un*verified address *can*
-- be linked to multiple participants. We implement this
-- by using NULL instead of FALSE for the unverified
-- state, hence the check constraint on verified.
, UNIQUE (participant, address)
);

-- The participants table currently has an `email` attribute of type
-- email_address_with confirmation. This should be deleted in the future,
-- once the emails are migrated. The column we're going to replace it with
-- is named `email_address`. This is only for **verified** emails. All
-- unverified email stuff happens in the emails table and won't touch this
-- attribute.

ALTER TABLE participants ADD COLUMN email_address text UNIQUE,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why email_address instead of email?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because email already exists, it's the one we're replacing.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does that mean we can't use the same name?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because we want to migrate data between the two, is that it? So it'd be a little more complicated?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because we want to migrate data between the two, is that it? So it'd be a little more complicated?

Yes.

ADD COLUMN email_lang text;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

email_lang? Why not generalize to lang?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because for now we only use it for emails, and because it seems to me like a good idea to be able to choose the language of emails independently from the rest.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it seems to me like a good idea to be able to choose the language of emails independently from the rest

Really? I'm mono-lingual, so not really qualified to say, but I'm surprised that you'd want the website in one language but emails in another.


UPDATE events
SET payload = replace(replace( payload::text, '"set"', '"add"')
, '"current_email"'
, '"email"'
)::json
WHERE payload->>'action' = 'set'
AND (payload->'values'->'current_email') IS NOT NULL;

END;
10 changes: 6 additions & 4 deletions configure-aspen.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import division

from decimal import Decimal as D
import base64
import threading
import time
import traceback
Expand Down Expand Up @@ -55,19 +56,20 @@ def _set_cookie(response, *args, **kw):
'len': len,
'float': float,
'type': type,
'str': str
'str': str,
'b64encode': base64.b64encode
}


env = website.env = gratipay.wireup.env()
tell_sentry = website.tell_sentry = gratipay.wireup.make_sentry_teller(env)
gratipay.wireup.canonical(env)
website.db = gratipay.wireup.db(env)
website.mail = gratipay.wireup.mail(env)
website.mailer = gratipay.wireup.mail(env, website.project_root)
gratipay.wireup.billing(env)
gratipay.wireup.username_restrictions(website)
gratipay.wireup.nanswers(env)
gratipay.wireup.load_i18n(website)
gratipay.wireup.load_i18n(website.project_root, tell_sentry)
gratipay.wireup.other_stuff(website, env)
gratipay.wireup.accounts_elsewhere(website, env)

Expand Down Expand Up @@ -158,7 +160,7 @@ def add_stuff_to_context(request):
, authentication.get_auth_from_request
, csrf.get_csrf_token_from_request
, add_stuff_to_context
, i18n.add_helpers_to_context
, i18n.set_up_i18n

, algorithm['dispatch_request_to_filesystem']

Expand Down
2 changes: 1 addition & 1 deletion defaults.env
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,6 @@ ASPEN_WWW_ROOT=www/
# https://github.com/benoitc/gunicorn/issues/186
GUNICORN_OPTS="--workers=1 --timeout=99999999"

MANDRILL_KEY=
MANDRILL_KEY=Phh_Lm3RdPT5blqOPY4dVQ
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whose key is this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gratipay's test key.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should update the README to talk about this. Iinm you won't be able to use this to actually send email.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in ee65f9a.


RAISE_CARD_EXPIRATION=no
29 changes: 29 additions & 0 deletions emails/base.spt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
[---] text/html
<div style="text-align: center; padding: 20px 0; margin: 0;">
<img src="https://downloads.gratipay.com/email/gratipay.png" alt="Gratipay">
</div>

<div style="text-align: center; font: normal 14px/21px Arial, sans-serif; color: #333; padding: 0 20%;">
$body
</div>

<div style="text-align: center; color: #999; padding: 21px 0 0;">
<div style="font: normal 14px/21px Arial, sans-serif;">
{{ _("Something not right? Reply to this email for help.") }}
</div>
<div style="font: normal 10px/21px Arial, sans-serif;">
Sent by <a href="https://gratipay.com/" style="color: #999; text-decoration: underline;">Gratipay, LLC</a> | 716 Park Road, Ambridge, PA, 15003, USA
</div>
</div>

[---] text/plain
{{ _("Greetings, program!") }}

$body

{{ _("Something not right? Reply to this email for help.") }}

----

Sent by Gratipay, LLC, https://gratipay.com/
716 Park Road, Ambridge, PA, 15003, USA
21 changes: 21 additions & 0 deletions emails/initial.spt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{{ _("Connect to {0} on Gratipay?", username) }}

[---] text/html
A while ago we received a request to connect <b>{{ escape(email) }}</b> to the
{{ '<b><a href="https://gratipay.com/{0}/">{0}</a></b>'.format(username) }}
account on Gratipay (<a
href="https://medium.com/gratipay-blog/gratitude-gratipay-ef24ad5e41f9">formerly</a>
Gittip). Now we're finally sending a verification email! Ring a bell?
<br>
<br>
<a href="{{ link }}" style="color: #fff; text-decoration:none; display:inline-block; padding: 0 15px; background: #396; font: normal 14px/40px Arial, sans-serif; white-space: nowrap; border-radius: 3px">Yes, proceed!</a>

[---] text/plain

A while ago we received a request to connect `{{ email }}`
to the `{{ username }}` account on Gratipay (formerly Gittip).
Now we're finally sending a verification email!

Ring a bell? Follow this link to finish connecting your email:

{{ link }}
17 changes: 17 additions & 0 deletions emails/verification.spt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{{ _("Connect to {0} on Gratipay?", username) }}

[---] text/html
{{ _("We've received a request to connect {0} to the {1} account on Gratipay. Sound familiar?",
'<b>%s</b>' % escape(email),
'<b><a href="https://gratipay.com/{0}">{0}</a></b>'.format(username)) }}
<br>
<br>
<a href="{{ link }}" style="color: #fff; text-decoration:none; display:inline-block; padding: 0 15px; background: #396; font: normal 14px/40px Arial, sans-serif; white-space: nowrap; border-radius: 3px">{{ _("Yes, proceed!") }}</a>

[---] text/plain
{{ _("We've received a request to connect {0} to the {1} account on Gratipay. Sound familiar?",
email, username) }}

{{ _("Follow this link to finish connecting your email:") }}

{{ link }}
13 changes: 13 additions & 0 deletions emails/verification_notice.spt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{{ _("Connecting {0} to {1} on Gratipay.", new_email, username) }}

[---] text/html
{{ _("We are connecting {0} to the {1} account on Gratipay. This is a notification "
"sent to {2} because that is the primary email address we have on file.",
'<b>%s</b>' % escape(new_email),
'<b><a href="https://gratipay.com/{0}">{0}</a></b>'.format(username),
'<b>%s</b>' % escape(email)) }}

[---] text/plain
{{ _("We are connecting {0} to the {1} account on Gratipay. This is a notification "
"sent to {2} because that is the primary email address we have on file.",
new_email, username, email) }}
19 changes: 19 additions & 0 deletions gratipay/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

from __future__ import print_function, unicode_literals

from aspen import Response


class ProblemChangingUsername(Exception):
def __str__(self):
Expand All @@ -25,6 +27,23 @@ class UsernameAlreadyTaken(ProblemChangingUsername):
msg = "The username '{}' is already taken."


class ProblemChangingEmail(Response):
def __init__(self, *args):
Response.__init__(self, 400, self.msg.format(*args))

class EmailAlreadyTaken(ProblemChangingEmail):
msg = "{} is already connected to a different Gratipay account."

class CannotRemovePrimaryEmail(ProblemChangingEmail):
msg = "You cannot remove your primary email address."

class EmailNotVerified(ProblemChangingEmail):
msg = "The email address '{}' is not verified."

class TooManyEmailAddresses(ProblemChangingEmail):
msg = "You've reached the maximum number of email addresses we allow."


class ProblemChangingNumber(Exception):
def __str__(self):
return self.msg
Expand Down
6 changes: 0 additions & 6 deletions gratipay/models/email_address_with_confirmation.py

This file was deleted.

Loading