diff options
| -rw-r--r-- | Dockerfile | 4 | ||||
| -rw-r--r-- | blog/urls.py | 1 | ||||
| -rw-r--r-- | blog/views.py | 43 | ||||
| -rw-r--r-- | requirements.txt | 1 | ||||
| -rw-r--r-- | static/css/main.css | 16 | ||||
| -rw-r--r-- | static/images/icons/button_refresh.jpeg | bin | 0 -> 26372 bytes | |||
| -rw-r--r-- | static/js/captcha.js | 15 | ||||
| -rw-r--r-- | templates/blog/register.html | 16 | ||||
| -rw-r--r-- | users/migrations/0003_captchastore.py | 22 | ||||
| -rw-r--r-- | users/migrations/0004_remove_captchastore_id_alter_captchastore_csrf_token.py | 22 | ||||
| -rw-r--r-- | users/models.py | 7 |
11 files changed, 145 insertions, 2 deletions
@@ -17,6 +17,10 @@ RUN pip install -r requirements.txt COPY . . +# Migrations +RUN python manage.py makemigrations +RUN python manage.py migrate + RUN python manage.py collectstatic --noinput diff --git a/blog/urls.py b/blog/urls.py index 568d0cb9..1884e810 100644 --- a/blog/urls.py +++ b/blog/urls.py @@ -8,5 +8,6 @@ urlpatterns = [ path('account/', RedirectView.as_view(pattern_name='account', permanent=False)), path('my/account', views.account, name='account'), path('register/', views.register, name='register'), + path('register/refresh_captcha/', name='refresh_captcha', view=views.refresh_captcha), # path('my/homepage', views.homepage, name='homepage'), ] diff --git a/blog/views.py b/blog/views.py index b5c54182..c656f020 100644 --- a/blog/views.py +++ b/blog/views.py @@ -1,7 +1,14 @@ +from http.client import HTTPResponse from django.shortcuts import render, redirect -from users.models import UserProfile +from django.http import HttpResponse +from users.models import UserProfile, CaptchaStore from urllib.parse import urlparse import hashlib +from captcha.image import ImageCaptcha +from random import choice +from string import ascii_letters, digits +import base64 +import json # Create your views here. @@ -32,5 +39,37 @@ def homepage(request): return render(request, 'blog/homepage.html', {'title': 'Homepage'}) +def get_base64_captcha(): + image = ImageCaptcha() + random_string = ''.join([choice(ascii_letters + digits) for n in range(6)]) + data = image.generate(random_string) + base64_data = "data:image/png;base64," + base64.b64encode(data.getvalue()).decode() + return base64_data, random_string + def register(request): - return render(request, 'blog/register.html', {'title': 'Register New User'}) + csrf_token = request.META.get('CSRF_COOKIE') + base64_data, random_string = get_base64_captcha() + try: + # Delete old captcha + CaptchaStore.objects.get(csrf_token=csrf_token).delete() + except CaptchaStore.DoesNotExist: + pass + # Create new captcha + CaptchaStore.objects.create(captcha_string=random_string, csrf_token=csrf_token) + return render(request, 'blog/register.html', {'title': 'Register New User', 'captcha': base64_data}) + + +def refresh_captcha(request): + csrf_token = request.META.get('CSRF_COOKIE') + if not csrf_token or not request.META.get('HTTP_REFERER') or request.META.get('HTTP_REFERER').split('/')[-2] != 'register': + response_data = {'status': 'error', 'message': 'Unauthorized!'} + return HttpResponse(json.dumps(response_data), content_type="application/json", status=401) + base64_data, random_string = get_base64_captcha() + try: + CaptchaStore.objects.get(csrf_token=csrf_token).delete() + except CaptchaStore.DoesNotExist: + pass + + CaptchaStore.objects.create(captcha_string=random_string, csrf_token=csrf_token) + response_data = {'captcha': base64_data} + return HttpResponse(json.dumps(response_data), content_type="application/json") diff --git a/requirements.txt b/requirements.txt index 58cda50f..35143047 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,3 +5,4 @@ gunicorn==20.1.0 whitenoise==6.2.0 six==1.16.0 cryptocode==0.1 +captcha==0.4 diff --git a/static/css/main.css b/static/css/main.css index a524db34..32afdeb8 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -153,6 +153,22 @@ nav > ul > li { display: none; } +#captcha { + display: flex; + flex-direction: row; +} + +#captcha > img:nth-child(1) { + height: 60px; + margin-right: 10px; +} + +#captcha > img:nth-child(2) { + height: 30px; + transform: translateY(15px); + cursor: pointer; +} + /* Optimize for phones */ @media only screen and (max-width: 480px) { body { diff --git a/static/images/icons/button_refresh.jpeg b/static/images/icons/button_refresh.jpeg Binary files differnew file mode 100644 index 00000000..1d6b5567 --- /dev/null +++ b/static/images/icons/button_refresh.jpeg diff --git a/static/js/captcha.js b/static/js/captcha.js new file mode 100644 index 00000000..93be88da --- /dev/null +++ b/static/js/captcha.js @@ -0,0 +1,15 @@ +const refreshCaptchaButton = document.getElementById('refresh_captcha'); + +refreshCaptchaButton.addEventListener('click', function() { + const refreshCaptchaURl = refreshCaptchaButton.getAttribute('data-refresh-captcha-url'); + + const xhr = new XMLHttpRequest(); + xhr.open('GET', refreshCaptchaURl, true); + xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); + xhr.onload = function() { + const data = JSON.parse(this.responseText); + const captchaImage = document.getElementById('captcha_image'); + captchaImage.src = data['captcha']; + } + xhr.send(); +}); diff --git a/templates/blog/register.html b/templates/blog/register.html index aa0600da..44fbc116 100644 --- a/templates/blog/register.html +++ b/templates/blog/register.html @@ -42,8 +42,24 @@ {% endfor %} </div> <div> + <label for="captcha" style="margin: 5px 0px; display: block;">Type Captcha</label> + <div id="captcha"> + {% load static %} + <img src="{{ captcha }}" alt="captcha" class="captcha" id="captcha_image" style="display: block;"> + <img src="{% static 'images/icons/button_refresh.jpeg' %}" alt="Refresh Captcha" id="refresh_captcha" data-refresh-captcha-url="{% url 'refresh_captcha' %}"> + </div> + <input type="text" name="captcha" id="captcha" class="form-control" placeholder="Type Captcha" required> + {% for message in messages %} + {% if 'captchaError' in message.tags %} + <small class="error" style="display:block;">{{ message.message }}</small> + {% endif %} + {% endfor %} + </div> + <div> <button type="submit" class="btn btn-primary" style="margin-top:10px">Register</button> </div> </section> </div> +<script src={% static 'js/captcha.js' %}></script> {% endblock %} + diff --git a/users/migrations/0003_captchastore.py b/users/migrations/0003_captchastore.py new file mode 100644 index 00000000..9aebc0aa --- /dev/null +++ b/users/migrations/0003_captchastore.py @@ -0,0 +1,22 @@ +# Generated by Django 4.0.6 on 2022-09-05 22:27 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0002_userprofile_email_verified'), + ] + + operations = [ + migrations.CreateModel( + name='CaptchaStore', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('captcha_string', models.CharField(max_length=6)), + ('csrf_token', models.CharField(max_length=100)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ], + ), + ] diff --git a/users/migrations/0004_remove_captchastore_id_alter_captchastore_csrf_token.py b/users/migrations/0004_remove_captchastore_id_alter_captchastore_csrf_token.py new file mode 100644 index 00000000..1dd16bcc --- /dev/null +++ b/users/migrations/0004_remove_captchastore_id_alter_captchastore_csrf_token.py @@ -0,0 +1,22 @@ +# Generated by Django 4.0.6 on 2022-09-05 22:30 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0003_captchastore'), + ] + + operations = [ + migrations.RemoveField( + model_name='captchastore', + name='id', + ), + migrations.AlterField( + model_name='captchastore', + name='csrf_token', + field=models.CharField(max_length=100, primary_key=True, serialize=False), + ), + ] diff --git a/users/models.py b/users/models.py index 808e197a..dab44f38 100644 --- a/users/models.py +++ b/users/models.py @@ -17,3 +17,10 @@ class UserProfile(models.Model): def __str__(self): return self.user.username +class CaptchaStore(models.Model): + captcha_string = models.CharField(max_length=6) + csrf_token = models.CharField(max_length=100, primary_key=True) + created_at = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return self.captcha_string |
