Compare commits

..

1 Commits

Author SHA1 Message Date
lebaudantoine deb1ef9ed6 ⚡️(backend) reduce domain queries on the application token endpoint
Load allowed domains once and compare the lowercased email domain
in Python. For restricted applications with an existing user,
this reduces successful token requests from four queries to three.

Keep domain loading after credential validation so rejected
credentials require only the application lookup.

Add endpoint query-count tests for restricted and unrestricted
successes and invalid credentials.
2026-10-07 19:06:05 +02:00
4 changed files with 58 additions and 32 deletions
+1 -4
View File
@@ -8,13 +8,10 @@ and this project adheres to
## [Unreleased]
### Added
- ⚡️(backend) add UPPER(email) index for case-insensitive user lookups
### Changed
- ⚡️(backend) hash application secrets with SHA-256
- ⚡️(backend) reduce domain queries on the application token endpoint
### Fixed
@@ -1,23 +0,0 @@
# Generated by Django 5.2.14 on 2026-10-07 18:45
from django.contrib.postgres.operations import AddIndexConcurrently
from django.db import migrations, models
from django.db.models.functions import Upper
class Migration(migrations.Migration):
atomic = False
dependencies = [
('core', '0025_application_client_secret_sha256'),
]
operations = [
AddIndexConcurrently(
model_name="user",
index=models.Index(
Upper("email"),
name="user_email_upper_idx",
),
),
]
+3 -5
View File
@@ -246,9 +246,6 @@ class User(AbstractBaseUser, BaseModel, auth_models.PermissionsMixin):
name="unique_email_when_sub_is_null",
)
]
indexes = [
models.Index(models.functions.Upper("email"), name="user_email_upper_idx"),
]
def __str__(self):
return self.email or self.admin_email or str(self.id)
@@ -906,11 +903,12 @@ class Application(BaseModel):
def can_delegate_email(self, email):
"""Check if this application can delegate the given email."""
if not self.allowed_domains.exists():
allowed_domains = {d.domain for d in self.allowed_domains.all()}
if not allowed_domains:
return True # No domain restrictions
domain = get_domain_from_email(email)
return self.allowed_domains.filter(domain__iexact=domain).exists()
return bool(domain) and domain.lower() in allowed_domains
class ApplicationDomain(BaseModel):
@@ -87,6 +87,60 @@ def test_api_applications_generate_token_success(settings):
}
@pytest.mark.parametrize(
"restricted", [True, False], ids=["restricted", "unrestricted"]
)
def test_api_applications_generate_token_success_query_count(
restricted, django_assert_num_queries
):
"""An existing user needs one query each for application, domains, and user."""
user = UserFactory(email="user@example.com")
plain_secret = "test-secret-123"
application = ApplicationFactory(client_secret=plain_secret)
if restricted:
ApplicationDomainFactory(application=application, domain="example.com")
client = APIClient()
with django_assert_num_queries(3):
response = client.post(
"/external-api/v1.0/application/token/",
{
"client_id": application.client_id,
"client_secret": plain_secret,
"grant_type": "client_credentials",
"scope": user.email,
},
format="json",
)
assert response.status_code == 200
assert "access_token" in response.data
def test_api_applications_generate_token_invalid_credentials_query_count(
django_assert_num_queries,
):
"""An invalid secret must be rejected before querying domains or users."""
application = ApplicationFactory(client_secret="test-secret-123")
ApplicationDomainFactory(application=application, domain="example.com")
client = APIClient()
with django_assert_num_queries(1):
response = client.post(
"/external-api/v1.0/application/token/",
{
"client_id": application.client_id,
"client_secret": "wrong-secret",
"grant_type": "client_credentials",
"scope": "user@example.com",
},
format="json",
)
assert response.status_code == 401
assert "Invalid credentials" in str(response.data)
def test_api_applications_generate_token_form_urlencoded(settings):
"""The token endpoint should accept "application/x-www-form-urlencoded"
requests, as mandated by RFC 6749 (sections 3.2 and 4.4.2) for OAuth 2.0