Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions mentirinha.env
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,7 @@ DJANGO_ALLOWED_HOSTS=localhost
DJANGO_DEBUG=1
DJANGO_SECRET_KEY=secret_key
DJANGO_LOG_FILE=/dkdata/mentirinha.log

# API Authentication
# Set this to a secure random token for the /api/shorten endpoint
API_TOKEN=your-secret-api-token-here
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why exactly do we need authentication for this internal tool? A possible option (with less code) is to block external requests for this specific endpoint from within the firewall.

However, doing that separates a specific business logic (if it is) from the code itself.

I trust your judgement on this, though.

42 changes: 42 additions & 0 deletions mentirinha/auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import os
from functools import wraps
from django.http import JsonResponse


def require_bearer_token(view_func):
"""
Decorator to require Bearer token authentication for API views.

Checks the Authorization header for a Bearer token and validates it
against the API_TOKEN environment variable.

Returns 401 if authentication fails.
"""
@wraps(view_func)
def wrapper(request, *args, **kwargs):
auth_header = request.headers.get('Authorization', '')

if not auth_header.startswith('Bearer '):
return JsonResponse(
{'error': 'Missing or invalid Authorization header'},
status=401
)

token = auth_header[7:] # Remove 'Bearer ' prefix
expected_token = os.getenv('API_TOKEN', '')

if not expected_token:
return JsonResponse(
{'error': 'API authentication not configured'},
status=500
)

if token != expected_token:
return JsonResponse(
{'error': 'Invalid authentication token'},
status=401
)

return view_func(request, *args, **kwargs)

return wrapper
1 change: 1 addition & 0 deletions mentirinha/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@
urlpatterns = [
path('', views.redirect_to),
path('__ping', views.ping),
path('api/shorten', views.shorten_url),
path('<short_code>', views.redirect_to),
]
25 changes: 25 additions & 0 deletions mentirinha/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import random
import string
from mentirinha.models import ShortenedUrl


def generate_short_code(length=6):
"""
Generate a random short code that doesn't already exist in the database.

Args:
length: Length of the short code (default: 6)

Returns:
A unique short code string
"""
characters = string.ascii_letters + string.digits
max_attempts = 100

for _ in range(max_attempts):
short_code = ''.join(random.choice(characters) for _ in range(length))
if not ShortenedUrl.objects.filter(short_code=short_code).exists():
return short_code

# If we couldn't find a unique code in max_attempts, try with longer length
return generate_short_code(length + 1)
80 changes: 80 additions & 0 deletions mentirinha/views.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import json
from django.http import JsonResponse
from django.db.models import F
from django.shortcuts import get_object_or_404, redirect
from django.views.decorators.csrf import csrf_exempt
from django.core.exceptions import ValidationError
from mentirinha.models import ShortenedUrl
from mentirinha import counter
from mentirinha.auth import require_bearer_token
from mentirinha.utils import generate_short_code
from sample_project.settings import BASE_URL


def list_all(request):
Expand All @@ -21,3 +27,77 @@ def redirect_to(request, short_code=None):

def ping(request):
return JsonResponse({"pong": True})


@csrf_exempt
@require_bearer_token
def shorten_url(request):
"""
API endpoint to create shortened URLs.

Accepts POST requests with JSON body:
{
"url": "https://example.com/long-url",
"short_code": "optional-custom-code" # optional
}

Returns:
{
"short_url": "http://localhost:8000/abc123",
"short_code": "abc123"
}

Requires Bearer token authentication via Authorization header.
"""
if request.method != 'POST':
return JsonResponse(
{'error': 'Only POST method is allowed'},
status=405
)

try:
data = json.loads(request.body)
except json.JSONDecodeError:
return JsonResponse(
{'error': 'Invalid JSON in request body'},
status=400
)

original_url = data.get('url')
if not original_url:
return JsonResponse(
{'error': 'Missing required field: url'},
status=400
)

# Get custom short_code or generate one
short_code = data.get('short_code')
if short_code:
# Validate that custom short_code is not already in use
if ShortenedUrl.objects.filter(short_code=short_code).exists():
return JsonResponse(
{'error': f'Short code "{short_code}" is already in use'},
status=409
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Random number. Use HTTPStatus.

)
else:
short_code = generate_short_code()

# Create the shortened URL
shortened_url = ShortenedUrl(
short_code=short_code,
original_url=original_url
)

try:
shortened_url.full_clean()
shortened_url.save()
except ValidationError as e:
return JsonResponse(
{'error': str(e.message_dict)},
status=400
)
Comment on lines +91 to +98
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Unhandled IntegrityError due to race condition when saving ShortenedUrl with duplicate short_code causes server crash.
Severity: CRITICAL | Confidence: 1.00

🔍 Detailed Analysis

The ShortenedUrl model has a unique=True constraint on short_code. The try...except block at mentirinha/views.py:91~98 only catches ValidationError, not IntegrityError. A race condition exists where two concurrent requests could pass the uniqueness check (either for custom or auto-generated codes) and then attempt to save the same short_code. The second save() operation will raise an IntegrityError, which is not caught, leading to an unhandled exception and server crash.

💡 Suggested Fix

Add except IntegrityError as e: to the try...except block to catch database unique constraint violations and return an appropriate error response, similar to how ValidationError is handled.

🤖 Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: mentirinha/views.py#L91-L98

Potential issue: The `ShortenedUrl` model has a `unique=True` constraint on
`short_code`. The `try...except` block at `mentirinha/views.py:91~98` only catches
`ValidationError`, not `IntegrityError`. A race condition exists where two concurrent
requests could pass the uniqueness check (either for custom or auto-generated codes) and
then attempt to save the same `short_code`. The second `save()` operation will raise an
`IntegrityError`, which is not caught, leading to an unhandled exception and server
crash.

Did we get this right? 👍 / 👎 to inform future reviews.

Reference_id: 2782303


return JsonResponse({
'short_url': f'{BASE_URL}/{short_code}',
'short_code': short_code
}, status=201)