@ -1,6 +1,7 @@ | |||
from django.conf import settings | |||
from django.core import mail | |||
from hc.api.models import Check | |||
from django.test.utils import override_settings | |||
from hc.api.models import Check, TokenBucket | |||
from hc.test import BaseTestCase | |||
@ -32,6 +33,36 @@ class LoginTestCase(BaseTestCase): | |||
body = mail.outbox[0].body | |||
self.assertTrue("/?next=/integrations/add_slack/" in body) | |||
@override_settings(SECRET_KEY="test-secret") | |||
def test_it_rate_limits_emails(self): | |||
# "d60d..." is sha1("[email protected]") | |||
obj = TokenBucket(value="em-d60db3b2343e713a4de3e92d4eb417e4f05f06ab") | |||
obj.tokens = 0 | |||
obj.save() | |||
form = {"identity": "[email protected]"} | |||
r = self.client.post("/accounts/login/", form) | |||
self.assertContains(r, "Too Many Requests") | |||
# No email should have been sent | |||
self.assertEqual(len(mail.outbox), 0) | |||
@override_settings(SECRET_KEY="test-secret") | |||
def test_it_rate_limits_ips(self): | |||
# 4b84.... is sha1("127.0.0.1test-secret") | |||
obj = TokenBucket(value="ip-4b84b15bff6ee5796152495a230e45e3d7e947d9") | |||
obj.tokens = 0 | |||
obj.save() | |||
form = {"identity": "[email protected]"} | |||
r = self.client.post("/accounts/login/", form) | |||
self.assertContains(r, "Too Many Requests") | |||
# No email should have been sent | |||
self.assertEqual(len(mail.outbox), 0) | |||
def test_it_pops_bad_link_from_session(self): | |||
self.client.session["bad_link"] = True | |||
self.client.get("/accounts/login/") | |||
@ -0,0 +1,22 @@ | |||
# Generated by Django 2.2 on 2019-04-25 12:46 | |||
from django.db import migrations, models | |||
class Migration(migrations.Migration): | |||
dependencies = [ | |||
('api', '0059_auto_20190314_1744'), | |||
] | |||
operations = [ | |||
migrations.CreateModel( | |||
name='TokenBucket', | |||
fields=[ | |||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | |||
('value', models.CharField(max_length=80, unique=True)), | |||
('tokens', models.FloatField(default=1.0)), | |||
('updated', models.DateTimeField(auto_now_add=True)), | |||
], | |||
), | |||
] |
@ -0,0 +1,47 @@ | |||
from datetime import timedelta as td | |||
from django.test.utils import override_settings | |||
from django.utils.timezone import now | |||
from hc.api.models import TokenBucket | |||
from hc.test import BaseTestCase | |||
# This is sha1("[email protected]" + "test-secred") | |||
ALICE_HASH = "d60db3b2343e713a4de3e92d4eb417e4f05f06ab" | |||
@override_settings(SECRET_KEY="test-secret") | |||
class TokenBucketTestCase(BaseTestCase): | |||
def test_it_works(self): | |||
r = TokenBucket.authorize_login_email("[email protected]") | |||
self.assertTrue(r) | |||
obj = TokenBucket.objects.get() | |||
self.assertEqual(obj.tokens, 0.95) | |||
self.assertEqual(obj.value, "em-" + ALICE_HASH) | |||
def test_it_handles_insufficient_tokens(self): | |||
TokenBucket.objects.create(value="em-" + ALICE_HASH, tokens=0.04) | |||
r = TokenBucket.authorize_login_email("[email protected]") | |||
self.assertFalse(r) | |||
def test_it_tops_up(self): | |||
obj = TokenBucket(value="em-" + ALICE_HASH) | |||
obj.tokens = 0 | |||
obj.updated = now() - td(minutes=30) | |||
obj.save() | |||
r = TokenBucket.authorize_login_email("[email protected]") | |||
self.assertTrue(r) | |||
obj.refresh_from_db() | |||
self.assertAlmostEqual(obj.tokens, 0.45, places=5) | |||
def test_it_normalizes_email(self): | |||
emails = ("[email protected]", "[email protected]") | |||
for email in emails: | |||
TokenBucket.authorize_login_email(email) | |||
self.assertEqual(TokenBucket.objects.count(), 1) |
@ -0,0 +1,14 @@ | |||
{% extends "base.html" %} | |||
{% block content %} | |||
<div class="row"> | |||
<div class="col-sm-6 col-sm-offset-3"> | |||
<div class="hc-dialog text-center"> | |||
<h1>Too Many Requests</h1> | |||
<div class="dialog-body"> | |||
<p>Please try again later.</p> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
{% endblock %} |