Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New log-in page for donors #1849

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion liberapay/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,8 +186,9 @@ def __str__(self):

class EmailAlreadyTaken(EmailAddressError):
code = 409
html_template = 'templates/exceptions/EmailAlreadyTaken.html'
def msg(self, _):
return _("{0} is already connected to a different Liberapay account.", self.email_address)
return _("The email address {0} is already connected to a Liberapay account.", self.email_address)


class CannotRemovePrimaryEmail(LazyResponseXXX):
Expand Down
32 changes: 19 additions & 13 deletions liberapay/security/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,10 @@ def _get_body(request):

def sign_in_with_form_data(body, state):
p = None
_, website = state['_'], state['website']
_, response, website = state['_'], state['response'], state['website']
new_user = body.get('log-in.new_user')

if body.get('log-in.id'):
if body.get('log-in.id') and new_user != 'yes':
request = state['request']
src_addr, src_country = request.source, request.country
input_id = body['log-in.id'].strip()
Expand Down Expand Up @@ -89,9 +90,6 @@ def sign_in_with_form_data(body, state):
except Exception as e:
website.tell_sentry(e, state)
elif id_type == 'email':
website.db.hit_rate_limit('log-in.email.ip-addr', str(src_addr), TooManyLogInAttempts)
website.db.hit_rate_limit('log-in.email.ip-net', get_ip_net(src_addr), TooManyLogInAttempts)
website.db.hit_rate_limit('log-in.email.country', src_country, TooManyLogInAttempts)
email = input_id
p = Participant.from_email(email)
if p and p.kind == 'group':
Expand All @@ -100,6 +98,11 @@ def sign_in_with_form_data(body, state):
email
)
elif p:
if new_user == 'no' and body.get('log-in.via-email') != 'yes' and p.has_password:
raise response.render('simplates/log-in-to-donate.spt', state)
website.db.hit_rate_limit('log-in.email.ip-addr', str(src_addr), TooManyLogInAttempts)
website.db.hit_rate_limit('log-in.email.ip-net', get_ip_net(src_addr), TooManyLogInAttempts)
website.db.hit_rate_limit('log-in.email.country', src_country, TooManyLogInAttempts)
if not p.get_email(email).verified:
website.db.hit_rate_limit('log-in.email.not-verified', email, TooManyLoginEmails)
website.db.hit_rate_limit('log-in.email', p.id, TooManyLoginEmails)
Expand All @@ -117,13 +120,12 @@ def sign_in_with_form_data(body, state):
state['log-in.error'] = _("\"{0}\" is not a valid email address.", input_id)
return

elif 'sign-in.email' in body:
response = state['response']
elif 'sign-in.email' in body or new_user == 'yes':
# Check the submitted data
kind = body.pop('sign-in.kind', 'individual')
if kind not in ('individual', 'organization'):
raise response.invalid_input(kind, 'sign-in.kind', 'body')
email = body['sign-in.email']
email = body.get('sign-in.email') or body.get('log-in.id')
if not email:
raise response.error(400, 'email is required')
email = normalize_and_check_email_address(email, state)
Expand Down Expand Up @@ -208,12 +210,16 @@ def sign_in_with_form_data(body, state):
p.check_password(password, context='login')
p.authenticated = True
p.sign_in(response.headers.cookie, token=session_token, suffix='.in')

if p:
# We're done, we can clean up the body now
body.pop('sign-in.email')
body.pop('sign-in.currency', None)
body.pop('sign-in.password', None)
body.pop('sign-in.username', None)
body.pop('sign-in.token', None)
body.popall('sign-in.email', None)
body.popall('sign-in.currency', None)
body.popall('sign-in.password', None)
body.popall('sign-in.username', None)
body.popall('sign-in.token', None)
body.popall('log-in.id', None)
body.popall('log-in.new_user', None)

return p

Expand Down
112 changes: 112 additions & 0 deletions simplates/log-in-to-donate.spt
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
[---]
[---] text/html
% set no_navbar_sign_in = True

% extends "templates/layouts/base-thin.html"

% block thin_content
% if state.get('log-in.error')
<p class="alert alert-danger">{{ state['log-in.error'] }}</p>
% endif

% if state.get('log-in.email-sent-to')

% set email = state['log-in.email-sent-to']
<div class="alert alert-success">
<p>{{ _(
"We've sent you a single-use login link. Check your inbox, open the "
"provided link in a new tab, then come back to this page and click on "
"the button below to carry on with what you wanted to do."
) }}</p>

<br>
<form action="" method="POST">
% include "templates/form-repost.html"
<input type="hidden" name="log-in.carry-on" value="{{ email }}" />
<button class="btn btn-success">{{ _("Carry on") }}</button>
</form>
</div>

% elif state.get('log-in.carry-on')

% set email = state['log-in.carry-on']
<div class="alert alert-danger">
<p>{{ _("You're still not logged in as {0}.", email) }}</p>

<br>
<form action="" method="POST">
% include "templates/form-repost.html"
<input type="hidden" name="log-in.carry-on" value="{{ email }}" />
<button class="btn btn-primary">{{ _("Try again") }}</button>
</form>
</div>

% elif request.body.get('log-in.new_user') == 'no'

% set email = request.body.pop('log-in.id')

<form action="" method="POST">
% include "templates/form-repost.html"

<p>{{ _("Please fill in your password to log in directly:") }}</p>
<input name="log-in.id" value="{{ email }}"
aria-hidden="true" class="out-of-sight" tabindex="-1" />
<div class="form-group">
<input name="log-in.password" class="form-control"
type="password" autocomplete="current-password"
placeholder="{{ _('Password') }}" required />
</div>
<button class="btn btn-primary">{{ _("Log in") }}</button>
</form>

<hr>

<form action="" method="POST">
% include "templates/form-repost.html"
<input type="hidden" name="log-in.id" value="{{ email }}" />
<p>{{ _("Or log in via email if you've lost your password:") }}</p>
<button class="btn btn-primary" name="log-in.via-email" value="yes">{{ _(
"Log in via email"
) }}</button>
</form>

% else

<form action="" method="POST">
% include "templates/form-repost.html"

<p>{{ _("Please input your email address:") }}</p>
<div>
<input name="log-in.id" autocomplete="email" class="form-control"
required placeholder="{{ _('Email address') }}" />
</div>

<br>
<p>{{ _("and select the appropriate option:") }}</p>
<div class="line-height-150">
<label>
<input type="radio" name="log-in.new_user" value="yes" required />
<span>{{ _(
"I am a new user, and I accept the {link_start}terms of service{link_end}."
, link_start='<a href="/about/legal#terms" target="_blank">'|safe, link_end='</a>'|safe
) }}</span>
</label><br>
<label>
<input type="radio" name="log-in.new_user" value="no" required />
<span>{{ _("I have used Liberapay before.") }}</span>
</label>
</div>

<br>
<button class="btn btn-primary btn-lg">{{ _("Proceed") }}</button>

<br><br><br>
<h4>{{ _("Why do you ask for my email address?") }}</h4>
<p>{{ _(
"We need to be able to notify you in the future if there is a "
"problem with your donation or if the time has come to renew it."
) }}</p>
</form>

% endif
% endblock
4 changes: 4 additions & 0 deletions style/base/utils.scss
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@
margin: 10px 10px 10px 0;
}

.line-height-150 {
line-height: 150%;
}

.out-of-sight {
position: absolute;
left: -1000vw;
Expand Down
22 changes: 22 additions & 0 deletions templates/exceptions/EmailAlreadyTaken.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<div class="row">
<div class="col-md-10 col-md-offset-1 col-lg-8 col-lg-offset-2">
<p class="alert alert-danger">{{ response.body }}</p>
% set input_name = request.find_input_name(response.email_address)
% if input_name in ('log-in.id', 'sign-in.email')
<p>{{ _("Log in to the existing account:") }}</p>
<form action="" method="POST">
% include "templates/form-repost.html"
<button class="btn btn-primary" name="log-in.new_user" value="no">{{ _("Log in") }}</button>
</form>
<hr>
<p>{{ _("Or use another email address:") }}</p>
<form action="" method="POST">
% include "templates/form-repost.html"
<div class="form-group">
<input class="form-control" type="email" name="{{ input_name }}" placeholder="{{ _('Email address') }}" />
</div>
<button class="btn btn-primary">{{ _("Try again") }}</button>
</form>
% endif
</div>
</div>
2 changes: 1 addition & 1 deletion tests/py/test_sign_in.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def log_in_and_check(self, p, password, **kw):

def check_login(self, r, p):
# Basic checks
assert r.code == 302
assert r.code == 302, r.text
session = self.db.one("""
SELECT id, secret, mtime
FROM user_secrets
Expand Down
15 changes: 8 additions & 7 deletions www/%username/tip.spt
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
"""Get or change the authenticated user's tip to this person.
"""
from base64 import b64encode

from liberapay.exceptions import AuthRequired
from liberapay.models.participant import Participant
from liberapay.utils import b64encode_s, get_participant

Expand Down Expand Up @@ -30,8 +28,8 @@ out = {}

direction = request.qs.get('dir', 'to')
if direction == 'to':
if user.ANON:
raise AuthRequired
if user.ANON and request.method == 'POST':
raise response.render('simplates/log-in-to-donate.spt', state)
tipper = user
tippee = get_participant(state, restrict=False, redirect_stub=False)
elif direction == 'from':
Expand Down Expand Up @@ -98,8 +96,9 @@ else:
out = tipper.get_tip_to(tippee)

out["npatrons"] = tippee.npatrons
out["total_giving"] = tipper.giving
out["total_receiving"] = tipper.receiving
if tipper:
out["total_giving"] = tipper.giving
out["total_receiving"] = tipper.receiving

if not tippee.hide_receiving:
total_receiving_tippee = tippee.receiving
Expand All @@ -114,7 +113,9 @@ if 'ctime' not in out:
% extends "templates/layouts/base-thin.html"

% block thin_content
<p class="alert alert-danger">These aren't the droids you're looking for.</p>
<p class="alert alert-danger">{{ _(
"Something went wrong, please go back to the previous page and resubmit the form."
) }}</p>
% endblock

[---] application/json via json_dump
Expand Down
2 changes: 1 addition & 1 deletion www/migrate.spt
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,7 @@ title = _("Migrating from Gratipay")
<form action="" method="POST">
% include "templates/form-repost.html"
<div class="alert alert-danger">{{ _(
"{0} is already connected to a different Liberapay account.",
"The email address {0} is already connected to a Liberapay account.",
existing_account.email
) }}</div>
<p>{{ _("If this address belongs to you please log in before continuing:") }}</p>
Expand Down