Users can add passwords to their accounts. Fixes #6

This commit is contained in:
Pēteris Caune 2016-01-05 00:25:08 +02:00
parent b92b0db087
commit 1dacc8b797
32 changed files with 319 additions and 91 deletions

View File

@ -3,8 +3,17 @@ from django.contrib.auth.models import User
from hc.accounts.models import Profile
class BasicBackend:
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
# Authenticate against the token in user's profile.
class ProfileBackend(object):
class ProfileBackend(BasicBackend):
def authenticate(self, username=None, token=None):
try:
@ -22,3 +31,15 @@ class ProfileBackend(object):
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
class EmailBackend(BasicBackend):
def authenticate(self, username=None, password=None):
try:
user = User.objects.get(email=username)
except User.DoesNotExist:
return None
if user.check_password(password):
return user

View File

@ -8,9 +8,14 @@ class LowercaseEmailField(forms.EmailField):
return value.lower()
class EmailForm(forms.Form):
class EmailPasswordForm(forms.Form):
email = LowercaseEmailField()
password = forms.CharField(required=False)
class ReportSettingsForm(forms.Form):
reports_allowed = forms.BooleanField(required=False)
class SetPasswordForm(forms.Form):
password = forms.CharField()

View File

@ -40,6 +40,15 @@ class Profile(models.Model):
ctx = {"login_link": settings.SITE_ROOT + path}
emails.login(self.user.email, ctx)
def send_set_password_link(self):
token = str(uuid.uuid4())
self.token = make_password(token)
self.save()
path = reverse("hc-set-password", args=[token])
ctx = {"set_password_link": settings.SITE_ROOT + path}
emails.set_password(self.user.email, ctx)
def send_report(self):
# reset next report date first:
now = timezone.now()

View File

@ -10,7 +10,8 @@ class CheckTokenTestCase(TestCase):
def setUp(self):
super(CheckTokenTestCase, self).setUp()
self.alice = User(username="alice")
self.alice = User(username="alice", email="alice@example.org")
self.alice.set_password("password")
self.alice.save()
self.profile = Profile(user=self.alice)
@ -21,13 +22,13 @@ class CheckTokenTestCase(TestCase):
r = self.client.get("/accounts/check_token/alice/secret-token/")
self.assertRedirects(r, "/checks/")
# After login, password should be unusable
self.alice.refresh_from_db()
assert not self.alice.has_usable_password()
# After login, token should be blank
self.profile.refresh_from_db()
self.assertEqual(self.profile.token, "")
def test_it_redirects_already_logged_in(self):
# Login
self.client.get("/accounts/check_token/alice/secret-token/")
self.client.login(username="alice@example.org", password="password")
# Login again, when already authenticated
r = self.client.get("/accounts/check_token/alice/secret-token/")

View File

@ -7,6 +7,9 @@ urlpatterns = [
url(r'^login_link_sent/$',
views.login_link_sent, name="hc-login-link-sent"),
url(r'^set_password_link_sent/$',
views.set_password_link_sent, name="hc-set-password-link-sent"),
url(r'^check_token/([\w-]+)/([\w-]+)/$',
views.check_token, name="hc-check-token"),
@ -15,4 +18,7 @@ urlpatterns = [
url(r'^unsubscribe_reports/([\w-]+)/$',
views.unsubscribe_reports, name="hc-unsubscribe-reports"),
url(r'^set_password/([\w-]+)/$',
views.set_password, name="hc-set-password"),
]

View File

@ -5,11 +5,13 @@ from django.contrib.auth import login as auth_login
from django.contrib.auth import logout as auth_logout
from django.contrib.auth import authenticate
from django.contrib.auth.decorators import login_required
from django.contrib.auth.hashers import check_password
from django.contrib.auth.models import User
from django.core import signing
from django.http import HttpResponseBadRequest
from django.shortcuts import redirect, render
from hc.accounts.forms import EmailForm, ReportSettingsForm
from hc.accounts.forms import (EmailPasswordForm, ReportSettingsForm,
SetPasswordForm)
from hc.accounts.models import Profile
from hc.api.models import Channel, Check
@ -45,25 +47,38 @@ def _associate_demo_check(request, user):
def login(request):
bad_credentials = False
if request.method == 'POST':
form = EmailForm(request.POST)
form = EmailPasswordForm(request.POST)
if form.is_valid():
email = form.cleaned_data["email"]
try:
user = User.objects.get(email=email)
except User.DoesNotExist:
user = _make_user(email)
_associate_demo_check(request, user)
password = form.cleaned_data["password"]
if len(password):
user = authenticate(username=email, password=password)
if user is not None and user.is_active:
auth_login(request, user)
return redirect("hc-checks")
bad_credentials = True
else:
try:
user = User.objects.get(email=email)
except User.DoesNotExist:
user = _make_user(email)
_associate_demo_check(request, user)
profile = Profile.objects.for_user(user)
profile.send_instant_login_link()
return redirect("hc-login-link-sent")
profile = Profile.objects.for_user(user)
profile.send_instant_login_link()
return redirect("hc-login-link-sent")
else:
form = EmailForm()
form = EmailPasswordForm()
bad_link = request.session.pop("bad_link", None)
ctx = {"form": form, "bad_link": bad_link}
ctx = {
"form": form,
"bad_credentials": bad_credentials,
"bad_link": bad_link
}
return render(request, "accounts/login.html", ctx)
@ -76,6 +91,10 @@ def login_link_sent(request):
return render(request, "accounts/login_link_sent.html")
def set_password_link_sent(request):
return render(request, "accounts/set_password_link_sent.html")
def check_token(request, username, token):
if request.user.is_authenticated() and request.user.username == username:
# User is already logged in
@ -102,6 +121,10 @@ def profile(request):
profile = Profile.objects.for_user(request.user)
if request.method == "POST":
if "set_password" in request.POST:
profile.send_set_password_link()
return redirect("hc-set-password-link-sent")
form = ReportSettingsForm(request.POST)
if form.is_valid():
profile.reports_allowed = form.cleaned_data["reports_allowed"]
@ -115,6 +138,36 @@ def profile(request):
return render(request, "accounts/profile.html", ctx)
@login_required
def set_password(request, token):
profile = Profile.objects.for_user(request.user)
if not check_password(token, profile.token):
return HttpResponseBadRequest()
if request.method == "POST":
form = SetPasswordForm(request.POST)
if form.is_valid():
password = form.cleaned_data["password"]
request.user.set_password(password)
request.user.save()
profile.token = ""
profile.save()
# Setting a password logs the user out, so here we
# log them back in.
u = authenticate(username=request.user.email, password=password)
auth_login(request, u)
messages.info(request, "Your password has been set!")
return redirect("hc-profile")
ctx = {
}
return render(request, "accounts/set_password.html", ctx)
def unsubscribe_reports(request, username):
try:
signing.Signer().unsign(request.GET.get("token"))

View File

@ -7,7 +7,7 @@ from hc.api.models import Channel
class AddChannelTestCase(TestCase):
def setUp(self):
self.alice = User(username="alice")
self.alice = User(username="alice", email="alice@example.org")
self.alice.set_password("password")
self.alice.save()
@ -18,7 +18,7 @@ class AddChannelTestCase(TestCase):
url = "/integrations/add/"
form = {"kind": "email", "value": "alice@example.org"}
self.client.login(username="alice", password="password")
self.client.login(username="alice@example.org", password="password")
r = self.client.post(url, form)
self.assertRedirects(r, "/integrations/")
@ -30,7 +30,7 @@ class AddChannelTestCase(TestCase):
url = "/integrations/add/"
form = {"kind": "email", "value": " alice@example.org "}
self.client.login(username="alice", password="password")
self.client.login(username="alice@example.org", password="password")
self.client.post(url, form)
q = Channel.objects.filter(value="alice@example.org")
@ -40,20 +40,20 @@ class AddChannelTestCase(TestCase):
url = "/integrations/add/"
form = {"kind": "dog", "value": "Lassie"}
self.client.login(username="alice", password="password")
self.client.login(username="alice@example.org", password="password")
r = self.client.post(url, form)
assert r.status_code == 400, r.status_code
def test_instructions_work(self):
self.client.login(username="alice", password="password")
self.client.login(username="alice@example.org", password="password")
for frag in ("email", "webhook", "pd", "pushover", "slack", "hipchat"):
url = "/integrations/add_%s/" % frag
r = self.client.get(url)
self.assertContains(r, "Integration Settings", status_code=200)
def test_it_adds_pushover_channel(self):
self.client.login(username="alice", password="password")
self.client.login(username="alice@example.org", password="password")
session = self.client.session
session["po_nonce"] = "n"
@ -68,7 +68,7 @@ class AddChannelTestCase(TestCase):
assert channels[0].value == "a|0"
def test_it_validates_pushover_priority(self):
self.client.login(username="alice", password="password")
self.client.login(username="alice@example.org", password="password")
session = self.client.session
session["po_nonce"] = "n"
@ -79,7 +79,7 @@ class AddChannelTestCase(TestCase):
assert r.status_code == 400
def test_it_validates_pushover_nonce(self):
self.client.login(username="alice", password="password")
self.client.login(username="alice@example.org", password="password")
session = self.client.session
session["po_nonce"] = "n"

View File

@ -6,13 +6,13 @@ from hc.api.models import Check
class AddCheckTestCase(TestCase):
def setUp(self):
self.alice = User(username="alice")
self.alice = User(username="alice", email="alice@example.org")
self.alice.set_password("password")
self.alice.save()
def test_it_works(self):
url = "/checks/add/"
self.client.login(username="alice", password="password")
self.client.login(username="alice@example.org", password="password")
r = self.client.post(url)
self.assertRedirects(r, "/checks/")
assert Check.objects.count() == 1

View File

@ -6,7 +6,7 @@ from hc.api.models import Channel
class ChannelChecksTestCase(TestCase):
def setUp(self):
self.alice = User(username="alice")
self.alice = User(username="alice", email="alice@example.org")
self.alice.set_password("password")
self.alice.save()
@ -17,19 +17,19 @@ class ChannelChecksTestCase(TestCase):
def test_it_works(self):
url = "/integrations/%s/checks/" % self.channel.code
self.client.login(username="alice", password="password")
self.client.login(username="alice@example.org", password="password")
r = self.client.get(url)
self.assertContains(r, "alice@example.org", status_code=200)
def test_it_checks_owner(self):
mallory = User(username="mallory")
mallory = User(username="mallory", email="mallory@example.org")
mallory.set_password("password")
mallory.save()
# channel does not belong to mallory so this should come back
# with 403 Forbidden:
url = "/integrations/%s/checks/" % self.channel.code
self.client.login(username="mallory", password="password")
self.client.login(username="mallory@example.org", password="password")
r = self.client.get(url)
assert r.status_code == 403
@ -37,6 +37,6 @@ class ChannelChecksTestCase(TestCase):
# Valid UUID but there is no channel for it:
url = "/integrations/6837d6ec-fc08-4da5-a67f-08a9ed1ccf62/checks/"
self.client.login(username="alice", password="password")
self.client.login(username="alice@example.org", password="password")
r = self.client.get(url)
assert r.status_code == 404

View File

@ -6,7 +6,7 @@ from hc.api.models import Check, Ping
class LogTestCase(TestCase):
def setUp(self):
self.alice = User(username="alice")
self.alice = User(username="alice", email="alice@example.org")
self.alice.set_password("password")
self.alice.save()
@ -19,14 +19,14 @@ class LogTestCase(TestCase):
def test_it_works(self):
url = "/checks/%s/log/" % self.check.code
self.client.login(username="alice", password="password")
self.client.login(username="alice@example.org", password="password")
r = self.client.get(url)
self.assertContains(r, "Dates and times are", status_code=200)
def test_it_handles_bad_uuid(self):
url = "/checks/not-uuid/log/"
self.client.login(username="alice", password="password")
self.client.login(username="alice@example.org", password="password")
r = self.client.get(url)
assert r.status_code == 400
@ -34,16 +34,16 @@ class LogTestCase(TestCase):
# Valid UUID but there is no check for it:
url = "/checks/6837d6ec-fc08-4da5-a67f-08a9ed1ccf62/log/"
self.client.login(username="alice", password="password")
self.client.login(username="alice@example.org", password="password")
r = self.client.get(url)
assert r.status_code == 404
def test_it_checks_ownership(self):
charlie = User(username="charlie")
charlie = User(username="charlie", email="charlie@example.org")
charlie.set_password("password")
charlie.save()
url = "/checks/%s/log/" % self.check.code
self.client.login(username="charlie", password="password")
self.client.login(username="charlie@example.org", password="password")
r = self.client.get(url)
assert r.status_code == 403

View File

@ -6,7 +6,7 @@ from hc.api.models import Check
class MyChecksTestCase(TestCase):
def setUp(self):
self.alice = User(username="alice")
self.alice = User(username="alice", email="alice@example.org")
self.alice.set_password("password")
self.alice.save()
@ -14,6 +14,6 @@ class MyChecksTestCase(TestCase):
self.check.save()
def test_it_works(self):
self.client.login(username="alice", password="password")
self.client.login(username="alice@example.org", password="password")
r = self.client.get("/checks/")
self.assertContains(r, "Alice Was Here", status_code=200)

View File

@ -6,7 +6,7 @@ from hc.api.models import Channel
class RemoveChannelTestCase(TestCase):
def setUp(self):
self.alice = User(username="alice")
self.alice = User(username="alice", email="alice@example.org")
self.alice.set_password("password")
self.alice.save()
@ -17,7 +17,7 @@ class RemoveChannelTestCase(TestCase):
def test_it_works(self):
url = "/integrations/%s/remove/" % self.channel.code
self.client.login(username="alice", password="password")
self.client.login(username="alice@example.org", password="password")
r = self.client.post(url)
self.assertRedirects(r, "/integrations/")
@ -26,18 +26,18 @@ class RemoveChannelTestCase(TestCase):
def test_it_handles_bad_uuid(self):
url = "/integrations/not-uuid/remove/"
self.client.login(username="alice", password="password")
self.client.login(username="alice@example.org", password="password")
r = self.client.post(url)
assert r.status_code == 400
def test_it_checks_owner(self):
url = "/integrations/%s/remove/" % self.channel.code
mallory = User(username="mallory")
mallory = User(username="mallory", email="mallory@example.org")
mallory.set_password("password")
mallory.save()
self.client.login(username="mallory", password="password")
self.client.login(username="mallory@example.org", password="password")
r = self.client.post(url)
assert r.status_code == 403
@ -45,6 +45,6 @@ class RemoveChannelTestCase(TestCase):
# Valid UUID but there is no channel for it:
url = "/integrations/6837d6ec-fc08-4da5-a67f-08a9ed1ccf62/remove/"
self.client.login(username="alice", password="password")
self.client.login(username="alice@example.org", password="password")
r = self.client.post(url)
assert r.status_code == 404

View File

@ -6,7 +6,7 @@ from hc.api.models import Check
class RemoveCheckTestCase(TestCase):
def setUp(self):
self.alice = User(username="alice")
self.alice = User(username="alice", email="alice@example.org")
self.alice.set_password("password")
self.alice.save()
@ -16,7 +16,7 @@ class RemoveCheckTestCase(TestCase):
def test_it_works(self):
url = "/checks/%s/remove/" % self.check.code
self.client.login(username="alice", password="password")
self.client.login(username="alice@example.org", password="password")
r = self.client.post(url)
self.assertRedirects(r, "/checks/")
@ -25,18 +25,18 @@ class RemoveCheckTestCase(TestCase):
def test_it_handles_bad_uuid(self):
url = "/checks/not-uuid/remove/"
self.client.login(username="alice", password="password")
self.client.login(username="alice@example.org", password="password")
r = self.client.post(url)
assert r.status_code == 400
def test_it_checks_owner(self):
url = "/checks/%s/remove/" % self.check.code
mallory = User(username="mallory")
mallory = User(username="mallory", email="mallory@example.org")
mallory.set_password("password")
mallory.save()
self.client.login(username="mallory", password="password")
self.client.login(username="mallory@example.org", password="password")
r = self.client.post(url)
assert r.status_code == 403
@ -44,6 +44,6 @@ class RemoveCheckTestCase(TestCase):
# Valid UUID but there is no check for it:
url = "/checks/6837d6ec-fc08-4da5-a67f-08a9ed1ccf62/remove/"
self.client.login(username="alice", password="password")
self.client.login(username="alice@example.org", password="password")
r = self.client.post(url)
assert r.status_code == 404

View File

@ -6,7 +6,7 @@ from hc.api.models import Channel, Check
class UpdateChannelTestCase(TestCase):
def setUp(self):
self.alice = User(username="alice")
self.alice = User(username="alice", email="alice@example.org")
self.alice.set_password("password")
self.alice.save()
@ -23,7 +23,7 @@ class UpdateChannelTestCase(TestCase):
"check-%s" % self.check.code: True
}
self.client.login(username="alice", password="password")
self.client.login(username="alice@example.org", password="password")
r = self.client.post("/integrations/", data=payload)
self.assertRedirects(r, "/integrations/")
@ -33,20 +33,20 @@ class UpdateChannelTestCase(TestCase):
assert checks[0].code == self.check.code
def test_it_checks_channel_user(self):
mallory = User(username="mallory")
mallory = User(username="mallory", email="mallory@example.org")
mallory.set_password("password")
mallory.save()
payload = {"channel": self.channel.code}
self.client.login(username="mallory", password="password")
self.client.login(username="mallory@example.org", password="password")
r = self.client.post("/integrations/", data=payload)
# self.channel does not belong to mallory, this should fail--
assert r.status_code == 403
def test_it_checks_check_user(self):
mallory = User(username="mallory")
mallory = User(username="mallory", email="mallory@example.org")
mallory.set_password("password")
mallory.save()
@ -58,7 +58,7 @@ class UpdateChannelTestCase(TestCase):
"channel": mc.code,
"check-%s" % self.check.code: True
}
self.client.login(username="mallory", password="password")
self.client.login(username="mallory@example.org", password="password")
r = self.client.post("/integrations/", data=payload)
# mc belongs to mallorym but self.check does not--
@ -68,7 +68,7 @@ class UpdateChannelTestCase(TestCase):
# Correct UUID but there is no channel for it:
payload = {"channel": "6837d6ec-fc08-4da5-a67f-08a9ed1ccf62"}
self.client.login(username="alice", password="password")
self.client.login(username="alice@example.org", password="password")
r = self.client.post("/integrations/", data=payload)
assert r.status_code == 400
@ -79,6 +79,6 @@ class UpdateChannelTestCase(TestCase):
"check-6837d6ec-fc08-4da5-a67f-08a9ed1ccf62": True
}
self.client.login(username="alice", password="password")
self.client.login(username="alice@example.org", password="password")
r = self.client.post("/integrations/", data=payload)
assert r.status_code == 400

View File

@ -6,7 +6,7 @@ from hc.api.models import Check
class UpdateNameTestCase(TestCase):
def setUp(self):
self.alice = User(username="alice")
self.alice = User(username="alice", email="alice@example.org")
self.alice.set_password("password")
self.alice.save()
@ -17,7 +17,7 @@ class UpdateNameTestCase(TestCase):
url = "/checks/%s/name/" % self.check.code
payload = {"name": "Alice Was Here"}
self.client.login(username="alice", password="password")
self.client.login(username="alice@example.org", password="password")
r = self.client.post(url, data=payload)
self.assertRedirects(r, "/checks/")
@ -26,14 +26,14 @@ class UpdateNameTestCase(TestCase):
def test_it_checks_ownership(self):
charlie = User(username="charlie")
charlie = User(username="charlie", email="charlie@example.org")
charlie.set_password("password")
charlie.save()
url = "/checks/%s/name/" % self.check.code
payload = {"name": "Charlie Sent This"}
self.client.login(username="charlie", password="password")
self.client.login(username="charlie@example.org", password="password")
r = self.client.post(url, data=payload)
assert r.status_code == 403
@ -41,7 +41,7 @@ class UpdateNameTestCase(TestCase):
url = "/checks/not-uuid/name/"
payload = {"name": "Alice Was Here"}
self.client.login(username="alice", password="password")
self.client.login(username="alice@example.org", password="password")
r = self.client.post(url, data=payload)
assert r.status_code == 400
@ -50,7 +50,7 @@ class UpdateNameTestCase(TestCase):
url = "/checks/6837d6ec-fc08-4da5-a67f-08a9ed1ccf62/name/"
payload = {"name": "Alice Was Here"}
self.client.login(username="alice", password="password")
self.client.login(username="alice@example.org", password="password")
r = self.client.post(url, data=payload)
assert r.status_code == 404
@ -58,7 +58,7 @@ class UpdateNameTestCase(TestCase):
url = "/checks/%s/name/" % self.check.code
payload = {"tags": " foo bar\r\t \n baz \n"}
self.client.login(username="alice", password="password")
self.client.login(username="alice@example.org", password="password")
self.client.post(url, data=payload)
check = Check.objects.get(id=self.check.id)

View File

@ -6,7 +6,7 @@ from hc.api.models import Check
class UpdateTimeoutTestCase(TestCase):
def setUp(self):
self.alice = User(username="alice")
self.alice = User(username="alice", email="alice@example.org")
self.alice.set_password("password")
self.alice.save()
@ -17,7 +17,7 @@ class UpdateTimeoutTestCase(TestCase):
url = "/checks/%s/timeout/" % self.check.code
payload = {"timeout": 3600, "grace": 60}
self.client.login(username="alice", password="password")
self.client.login(username="alice@example.org", password="password")
r = self.client.post(url, data=payload)
self.assertRedirects(r, "/checks/")
@ -29,7 +29,7 @@ class UpdateTimeoutTestCase(TestCase):
url = "/checks/not-uuid/timeout/"
payload = {"timeout": 3600, "grace": 60}
self.client.login(username="alice", password="password")
self.client.login(username="alice@example.org", password="password")
r = self.client.post(url, data=payload)
assert r.status_code == 400
@ -38,18 +38,18 @@ class UpdateTimeoutTestCase(TestCase):
url = "/checks/6837d6ec-fc08-4da5-a67f-08a9ed1ccf62/timeout/"
payload = {"timeout": 3600, "grace": 60}
self.client.login(username="alice", password="password")
self.client.login(username="alice@example.org", password="password")
r = self.client.post(url, data=payload)
assert r.status_code == 404
def test_it_checks_ownership(self):
charlie = User(username="charlie")
charlie = User(username="charlie", email="charlie@example.org")
charlie.set_password("password")
charlie.save()
url = "/checks/%s/timeout/" % self.check.code
payload = {"timeout": 3600, "grace": 60}
self.client.login(username="charlie", password="password")
self.client.login(username="charlie@example.org", password="password")
r = self.client.post(url, data=payload)
assert r.status_code == 403

View File

@ -6,7 +6,7 @@ from hc.api.models import Channel
class VerifyEmailTestCase(TestCase):
def setUp(self):
self.alice = User(username="alice")
self.alice = User(username="alice", email="alice@example.org")
self.alice.set_password("password")
self.alice.save()

View File

@ -6,6 +6,11 @@ def login(to, ctx):
o.send(to, ctx)
def set_password(to, ctx):
o = InlineCSSTemplateMail("set-password")
o.send(to, ctx)
def alert(to, ctx):
o = InlineCSSTemplateMail("alert")
o.send(to, ctx)

View File

@ -7,7 +7,7 @@ from mock import Mock, patch
class BillingTestCase(TestCase):
def setUp(self):
self.alice = User(username="alice")
self.alice = User(username="alice", email="alice@example.org")
self.alice.set_password("password")
self.alice.save()
@ -23,7 +23,7 @@ class BillingTestCase(TestCase):
m2 = Mock(id="def456", amount=456)
mock_braintree.Transaction.search.return_value = [m1, m2]
self.client.login(username="alice", password="password")
self.client.login(username="alice@example.org", password="password")
r = self.client.get("/billing/")
self.assertContains(r, "123")
self.assertContains(r, "def456")

View File

@ -7,7 +7,7 @@ from mock import patch
class CancelPlanTestCase(TestCase):
def setUp(self):
self.alice = User(username="alice")
self.alice = User(username="alice", email="alice@example.org")
self.alice.set_password("password")
self.alice.save()
@ -19,7 +19,7 @@ class CancelPlanTestCase(TestCase):
@patch("hc.payments.views.braintree")
def test_it_works(self, mock_braintree):
self.client.login(username="alice", password="password")
self.client.login(username="alice@example.org", password="password")
r = self.client.post("/pricing/cancel_plan/")
self.assertRedirects(r, "/pricing/")

View File

@ -8,7 +8,7 @@ from mock import patch
class CreatePlanTestCase(TestCase):
def setUp(self):
self.alice = User(username="alice")
self.alice = User(username="alice", email="alice@example.org")
self.alice.set_password("password")
self.alice.save()
@ -26,7 +26,7 @@ class CreatePlanTestCase(TestCase):
def run_create_plan(self, plan_id="P5"):
form = {"plan_id": plan_id, "payment_method_nonce": "test-nonce"}
self.client.login(username="alice", password="password")
self.client.login(username="alice@example.org", password="password")
return self.client.post("/pricing/create_plan/", form, follow=True)
@patch("hc.payments.views.braintree")

View File

@ -7,14 +7,14 @@ from mock import patch
class GetClientTokenTestCase(TestCase):
def setUp(self):
self.alice = User(username="alice")
self.alice = User(username="alice", email="alice@example.org")
self.alice.set_password("password")
self.alice.save()
@patch("hc.payments.views.braintree")
def test_it_works(self, mock_braintree):
mock_braintree.ClientToken.generate.return_value = "test-token"
self.client.login(username="alice", password="password")
self.client.login(username="alice@example.org", password="password")
r = self.client.get("/pricing/get_client_token/")
self.assertContains(r, "test-token", status_code=200)

View File

@ -7,7 +7,7 @@ from mock import Mock, patch
class InvoiceTestCase(TestCase):
def setUp(self):
self.alice = User(username="alice")
self.alice = User(username="alice", email="alice@example.org")
self.alice.set_password("password")
self.alice.save()
@ -25,7 +25,7 @@ class InvoiceTestCase(TestCase):
tx.created_at = None
mock_braintree.Transaction.find.return_value = tx
self.client.login(username="alice", password="password")
self.client.login(username="alice@example.org", password="password")
r = self.client.get("/invoice/abc123/")
self.assertContains(r, "ABC123") # tx.id in uppercase
@ -38,6 +38,6 @@ class InvoiceTestCase(TestCase):
tx.created_at = None
mock_braintree.Transaction.find.return_value = tx
self.client.login(username="alice", password="password")
self.client.login(username="alice@example.org", password="password")
r = self.client.get("/invoice/abc123/")
self.assertEqual(r.status_code, 403)

View File

@ -6,7 +6,7 @@ from hc.payments.models import Subscription
class PricingTestCase(TestCase):
def setUp(self):
self.alice = User(username="alice")
self.alice = User(username="alice", email="alice@example.org")
self.alice.set_password("password")
self.alice.save()
@ -18,7 +18,7 @@ class PricingTestCase(TestCase):
assert Subscription.objects.count() == 0
def test_authenticated(self):
self.client.login(username="alice", password="password")
self.client.login(username="alice@example.org", password="password")
r = self.client.get("/pricing/")
self.assertContains(r, "Unlimited Checks", status_code=200)

View File

@ -52,7 +52,7 @@ MIDDLEWARE_CLASSES = (
)
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
'hc.accounts.backends.EmailBackend',
'hc.accounts.backends.ProfileBackend'
)

7
static/js/login.js Normal file
View File

@ -0,0 +1,7 @@
$(function () {
$("#password-toggle").click(function() {
$("#password-toggle").hide();
$("#password-block").removeClass("hide");
});
});

View File

@ -1,4 +1,5 @@
{% extends "base.html" %}
{% load compress staticfiles %}
{% block content %}
<div class="row">
@ -20,22 +21,50 @@
</div>
{% endif %}
{% if bad_credentials %}
<p class="alert alert-danger">Incorrect email or password.</p>
{% endif %}
<form method="post">
{% csrf_token %}
<div class="form-group">
<div class="input-group input-group-lg">
<div class="input-group-addon">@</div>
<div class="input-group-addon">
<span class="glyphicon glyphicon-user"></span>
</div>
<input
type="text"
class="form-control"
id="id_email"
name="email"
value="{{ form.email.value|default:"" }}"
placeholder="Email">
</div>
</div>
{% if not bad_credentials %}
<div class="checkbox" id="password-toggle">
<label>
<input type="checkbox"> I want to use a password
</label>
</div>
{% endif %}
<div id="password-block" class="form-group {% if not bad_credentials %} hide {% endif %}">
<div class="input-group input-group-lg">
<div class="input-group-addon">
<span class="glyphicon glyphicon-lock"></span>
</div>
<input
type="password"
class="form-control"
name="password"
placeholder="password">
</div>
</div>
<div class="clearfix">
<button type="submit" class="btn btn-lg btn-primary pull-right">
Log In
@ -45,4 +74,11 @@
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
{% compress js %}
<script src="{% static 'js/jquery-2.1.4.min.js' %}"></script>
<script src="{% static 'js/login.js' %}"></script>
{% endcompress %}
{% endblock %}

View File

@ -39,6 +39,23 @@
</div>
</div>
</div>
<div class="col-sm-6">
<div class="panel panel-default">
<div class="panel-body settings-block">
<form method="post">
{% csrf_token %}
<h2>Set Password</h2>
Attach a password to your healthchecks.io account
<input type="hidden" name="set_password" value="1" />
<button
type="submit"
class="btn btn-default pull-right">Set Password</button>
</form>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,39 @@
{% extends "base.html" %}
{% block content %}
<div class="row">
<div class="col-sm-6 col-sm-offset-3">
<div class="hc-dialog">
<h1>Set a Password</h1>
<div class="dialog-body">
<p>
Please pick a password for your healthchecks.io account.
</p>
</div>
<form method="post">
{% csrf_token %}
<div class="form-group">
<div class="input-group input-group-lg">
<div class="input-group-addon">
<span class="glyphicon glyphicon-user"></span>
</div>
<input
type="password"
class="form-control"
name="password"
placeholder="pick a password">
</div>
</div>
<div class="clearfix">
<button type="submit" class="btn btn-lg btn-primary pull-right">
Set Password
</button>
</div>
</form>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,18 @@
{% extends "base.html" %}
{% block content %}
<div class="row">
<div class="col-sm-6 col-sm-offset-3">
<div class="hc-dialog">
<h1>Email with Instructions Sent!</h1>
<br />
<p>
We've sent you an email with instructions to set
a password for your account. Please check your inbox!
</p>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,10 @@
<p>Hello,</p>
<p>Here's a link to set a password for your account on healthchecks.io:</p>
<p><a href="{{ set_password_link }}">{{ set_password_link }}</a></p>
<p>
--<br />
Regards,<br />
healthchecks.io
</p>

View File

@ -0,0 +1 @@
Set password on healthchecks.io