forked from GithubBackups/healthchecks
Users can add passwords to their accounts. Fixes #6
This commit is contained in:
parent
b92b0db087
commit
1dacc8b797
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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/")
|
||||
|
@ -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"),
|
||||
|
||||
]
|
||||
|
@ -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"))
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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")
|
||||
|
@ -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/")
|
||||
|
||||
|
@ -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")
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
7
static/js/login.js
Normal file
@ -0,0 +1,7 @@
|
||||
$(function () {
|
||||
$("#password-toggle").click(function() {
|
||||
$("#password-toggle").hide();
|
||||
$("#password-block").removeClass("hide");
|
||||
});
|
||||
|
||||
});
|
@ -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 %}
|
@ -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
templates/accounts/set_password.html
Normal file
39
templates/accounts/set_password.html
Normal 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
templates/accounts/set_password_link_sent.html
Normal file
18
templates/accounts/set_password_link_sent.html
Normal 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
templates/emails/set-password-body-html.html
Normal file
10
templates/emails/set-password-body-html.html
Normal 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
templates/emails/set-password-subject.html
Normal file
1
templates/emails/set-password-subject.html
Normal file
@ -0,0 +1 @@
|
||||
Set password on healthchecks.io
|
Loading…
x
Reference in New Issue
Block a user