The src attribute of an <img> tag can reference local files, inline base64 data, or execute asynchronous HTTP GET requests when pointing to a backend route. Routing this endpoint to return binary image data allows seamless integration with template rendering.
To avoid filesystem overhead, generating verification images entirely in memory is preferred. The following implementation leverages the Pillow library alongside Python's io.BytesIO class to construct and transmit PNG data dynmaically.
# server/routes/auth.py
import random
import string
from io import BytesIO
from django.shortcuts import render
from django.http import HttpResponse
from PIL import Image, ImageDraw, ImageFont
def _generate_rgba_color():
return tuple(random.randint(50, 220) for _ in range(3))
def render_verification_image(request):
width, height = 300, 50
bg_color = _generate_rgba_color()
canvas = Image.new('RGB', (width, height), bg_color)
drawer = ImageDraw.Draw(canvas)
font_selector = ImageFont.truetype('static/fonts/arial.ttf', 28)
token_sequence = []
available_chars = string.ascii_letters + string.digits
for index in range(5):
char_pool = [c for c in available_chars]
selected_char = random.choice(char_pool)
x_position = (index * width // 6) + 30
y_position = random.randint(5, 15)
drawer.text((x_position, y_position), selected_char, fill=_generate_rgba_color(), font=font_selector)
token_sequence.append(selected_char)
request.session['verification_token'] = ''.join(token_sequence)
buffer = BytesIO()
canvas.save(buffer, format='PNG')
response = HttpResponse(buffer.getvalue(), content_type='image/png')
buffer.close()
return response
Cache invalidation ensures users see a fresh token without page reloads. Appending a dynamic query parameter forces the browser to bypass cached assets.
// client/templates/index.html
$(document).on('click', '#captcha-image', function () {
const currentSrc = $(this).attr('src');
const separator = currentSrc.includes('?') ? '&' : '?';
$(this).attr('src', `${currentSrc}${separator}v=${Date.now()}`);
});
Authentication endpoints must validate credentials securely while maintaining user experience through asynchronous submissions. The following handler compares submitted tokens case-insensitively and delegates identity verification to the framework's built-in backend.
# server/routes/auth.py
from django.contrib.auth import authenticate, login as auth_login
from django.views.decorators.http import require_POST
from django.http import JsonResponse
@require_POST
def initiate_session(request):
payload = {
'status': 'success',
'redirect_url': None,
'error_message': ''
}
provided_token = request.POST.get('token_field')
stored_token = request.session.get('verification_token', '')
if stored_token.upper() != provided_token.upper():
payload['status'] = 'failed'
payload['error_message'] = 'Security token mismatch.'
return JsonResponse(payload)
target_user = authenticate(request, username=request.POST.get('user_input'),
password=request.POST.get('pass_input'))
if target_user is not None:
auth_login(request, target_user)
payload['redirect_url'] = '/dashboard/'
else:
payload['status'] = 'failed'
payload['error_message'] = 'Invalid credentials.'
return JsonResponse(payload)
Corresponding frontend logic captures form submissions, transmits data via XHR, and manages UI feedback states. Failed attempts automatically trigger a token regeneration cycle.
// client/static/js/handlers.js
$('#submit-btn').on('click', function (event) {
event.preventDefault();
$.ajax({
url: '/api/auth/session/',
method: 'POST',
data: {
'csrfmiddlewaretoken': '{{ csrf_token }}',
'user_input': $('#username-input').val(),
'pass_input': $('#password-input').val(),
'token_field': $('#token-input').val()
},
dataType: 'json',
success: function (resp) {
if (resp.status === 'success' && resp.redirect_url) {
window.location.replace(resp.redirect_url);
} else {
$('#notification-area').text(resp.error_message);
const baseSrc = $('#captcha-image').attr('src').split('?')[0];
$('#captcha-image').attr('src', `${baseSrc}?refresh=true`);
}
}
});
});
Terminating active sessions and rotating access keys require explicit route definitions protected by middleware decorators. These operations verify ownership before committing state changes to the database.
# server/routes/auth.py
from django.contrib.auth import logout
from django.shortcuts import redirect
from django.urls import reverse
from django.contrib.auth.decorators import login_required
@login_required(login_url='/authenticate/')
def terminate_session(request):
logout(request)
return redirect(reverse('landing-page'))
@login_required
def rotate_access_key(request):
if request.method != 'POST':
return JsonResponse({'error': 'Method not allowed'}, status=405)
body = request.POST
existing_pass = body.get('current_pass')
new_pass = body.get('new_pass')
confirmation = body.get('confirm_pass')
if not request.user.check_password(existing_pass):
return JsonResponse({'status': 'error', 'detail': 'Current password incorrect.'})
if new_pass != confirmation:
return JsonResponse({'status': 'error', 'detail': 'New passwords do not match.'})
request.user.set_password(new_pass)
request.user.save()
return JsonResponse({'status': 'success', 'redirect_url': '/profile/settings/'})
Interface components bind these endpoints to standard HTML controls. Event listeners handle focus transitions to clear warning banners, ensuring a clean interaction pattern.
<!-- client/templates/layouts/navigation.html -->
<button id="update-key-btn" class="action-button">Update Credentials</button>
<div id="feedback-display"></div>
<script>
$('#update-key-btn').off('click').on('click', function () {
$.ajax({
url: '{% url "rotate_credentials" %}',
type: 'POST',
data: {
'csrfmiddlewaretoken': '{{ csrf_token }}',
'current_pass': $('#old-password').val(),
'new_pass': $('#new-password').val(),
'confirm_pass': $('#confirm-password').val()
},
success: function (response) {
if (response.status === 'success') {
window.location.href = response.redirect_url;
} else {
$('#feedback-display').text(response.detail);
}
}
});
});
$('input.password-field').on('focus', function () {
$('#feedback-display').empty();
});
</script>