Browse Source

Users can add passwords to their accounts. Fixes #6

pull/27/head
Pēteris Caune 9 years ago
parent
commit
1dacc8b797
32 changed files with 320 additions and 92 deletions
  1. +22
    -1
      hc/accounts/backends.py
  2. +6
    -1
      hc/accounts/forms.py
  3. +9
    -0
      hc/accounts/models.py
  4. +6
    -5
      hc/accounts/tests/test_check_token.py
  5. +6
    -0
      hc/accounts/urls.py
  6. +66
    -13
      hc/accounts/views.py
  7. +8
    -8
      hc/front/tests/test_add_channel.py
  8. +2
    -2
      hc/front/tests/test_add_check.py
  9. +5
    -5
      hc/front/tests/test_channel_checks.py
  10. +6
    -6
      hc/front/tests/test_log.py
  11. +2
    -2
      hc/front/tests/test_my_checks.py
  12. +6
    -6
      hc/front/tests/test_remove_channel.py
  13. +6
    -6
      hc/front/tests/test_remove_check.py
  14. +8
    -8
      hc/front/tests/test_update_channel.py
  15. +7
    -7
      hc/front/tests/test_update_name.py
  16. +6
    -6
      hc/front/tests/test_update_timeout.py
  17. +1
    -1
      hc/front/tests/test_verify_email.py
  18. +5
    -0
      hc/lib/emails.py
  19. +2
    -2
      hc/payments/tests/test_billing.py
  20. +2
    -2
      hc/payments/tests/test_cancel_plan.py
  21. +2
    -2
      hc/payments/tests/test_create_plan.py
  22. +2
    -2
      hc/payments/tests/test_get_client_token.py
  23. +3
    -3
      hc/payments/tests/test_invoice.py
  24. +2
    -2
      hc/payments/tests/test_pricing.py
  25. +1
    -1
      hc/settings.py
  26. +7
    -0
      static/js/login.js
  27. +37
    -1
      templates/accounts/login.html
  28. +17
    -0
      templates/accounts/profile.html
  29. +39
    -0
      templates/accounts/set_password.html
  30. +18
    -0
      templates/accounts/set_password_link_sent.html
  31. +10
    -0
      templates/emails/set-password-body-html.html
  32. +1
    -0
      templates/emails/set-password-subject.html

+ 22
- 1
hc/accounts/backends.py 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

+ 6
- 1
hc/accounts/forms.py 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()

+ 9
- 0
hc/accounts/models.py 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()


+ 6
- 5
hc/accounts/tests/test_check_token.py 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="[email protected]")
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="[email protected]", password="password")
# Login again, when already authenticated
r = self.client.get("/accounts/check_token/alice/secret-token/")


+ 6
- 0
hc/accounts/urls.py 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"),
]

+ 66
- 13
hc/accounts/views.py 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)
profile = Profile.objects.for_user(user)
profile.send_instant_login_link()
return redirect("hc-login-link-sent")
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")
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"))


+ 8
- 8
hc/front/tests/test_add_channel.py 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="[email protected]")
self.alice.set_password("password")
self.alice.save()
@ -18,7 +18,7 @@ class AddChannelTestCase(TestCase):
url = "/integrations/add/"
form = {"kind": "email", "value": "[email protected]"}
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": " [email protected] "}
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="[email protected]")
@ -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"


+ 2
- 2
hc/front/tests/test_add_check.py 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="[email protected]")
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

+ 5
- 5
hc/front/tests/test_channel_checks.py 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="[email protected]")
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, "[email protected]", status_code=200)
def test_it_checks_owner(self):
mallory = User(username="mallory")
mallory = User(username="mallory", email="[email protected]")
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

+ 6
- 6
hc/front/tests/test_log.py 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="[email protected]")
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="[email protected]")
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

+ 2
- 2
hc/front/tests/test_my_checks.py 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="[email protected]")
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)

+ 6
- 6
hc/front/tests/test_remove_channel.py 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="[email protected]")
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="[email protected]")
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

+ 6
- 6
hc/front/tests/test_remove_check.py 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="[email protected]")
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="[email protected]")
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

+ 8
- 8
hc/front/tests/test_update_channel.py 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="[email protected]")
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="[email protected]")
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="[email protected]")
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

+ 7
- 7
hc/front/tests/test_update_name.py 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="[email protected]")
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="[email protected]")
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)


+ 6
- 6
hc/front/tests/test_update_timeout.py 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="[email protected]")
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="[email protected]")
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

+ 1
- 1
hc/front/tests/test_verify_email.py 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="[email protected]")
self.alice.set_password("password")
self.alice.save()


+ 5
- 0
hc/lib/emails.py 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)


+ 2
- 2
hc/payments/tests/test_billing.py 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="[email protected]")
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")

+ 2
- 2
hc/payments/tests/test_cancel_plan.py 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="[email protected]")
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/")


+ 2
- 2
hc/payments/tests/test_create_plan.py 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="[email protected]")
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")


+ 2
- 2
hc/payments/tests/test_get_client_token.py 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="[email protected]")
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)


+ 3
- 3
hc/payments/tests/test_invoice.py 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="[email protected]")
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)

+ 2
- 2
hc/payments/tests/test_pricing.py 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="[email protected]")
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)


+ 1
- 1
hc/settings.py View File

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


+ 7
- 0
static/js/login.js View File

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

+ 37
- 1
templates/accounts/login.html 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 %}

+ 17
- 0
templates/accounts/profile.html 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 %}

+ 39
- 0
templates/accounts/set_password.html 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 %}

+ 18
- 0
templates/accounts/set_password_link_sent.html 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 %}

+ 10
- 0
templates/emails/set-password-body-html.html 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>

+ 1
- 0
templates/emails/set-password-subject.html View File

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

Loading…
Cancel
Save