mirror of
https://github.com/suitenumerique/meet.git
synced 2026-10-12 16:07:49 +00:00
0450c74f53
Application token authentication currently uses Django's default password hasher. On our production hardware, token requests take at least ~500 ms. Authentication happens per user of each application, so this cost accumulates across frequently used integrations. Password hashers deliberately make guessing expensive to protect human-chosen passwords after a database leak. Our application secrets are generated server-side using a cryptographically secure random generator, with a default length of 128 alphanumeric characters. Guessing these secrets is already computationally infeasible, making password stretching an unnecessary CPU cost. Use salted SHA-256 for application secrets while retaining constant-time comparison, and keep user password hashing unchanged. Existing secrets migrate after successful verification without requiring key rotation. Conditional updates prevent migration from overwriting a concurrent rotation. Store the new hash in `client_secret_sha256` while preserving the original Django hash in `client_secret`. New applications populate both fields, allowing the previous release to authenticate both existing and newly created applications if we need to roll back. Once every application has migrated and the rollback window has closed, complete the migration by removing the legacy verification code and `client_secret` field. This assumes securely generated, high-entropy secrets. Deployments that reduce `APPLICATION_CLIENT_SECRET_LENGTH` or supply predictable secrets lose the offline guessing protection that the previous slow hasher provided.
54 lines
1.6 KiB
Python
54 lines
1.6 KiB
Python
"""
|
|
Core application fields
|
|
"""
|
|
|
|
from logging import getLogger
|
|
|
|
from django.contrib.auth.hashers import identify_hasher, make_password
|
|
from django.db import models
|
|
|
|
from .hashers import CLIENT_SECRET_HASH_PATTERN
|
|
|
|
logger = getLogger(__name__)
|
|
|
|
|
|
class SecretField(models.CharField):
|
|
"""CharField that automatically hashes secrets before saving.
|
|
|
|
Use for API keys, client secrets, or tokens that should never be stored
|
|
in plain text. Already-hashed values are preserved to prevent double-hashing.
|
|
|
|
Inspired by: https://github.com/django-oauth-toolkit/django-oauth-toolkit
|
|
"""
|
|
|
|
def pre_save(self, model_instance, add):
|
|
"""Hash the secret if not already hashed, otherwise preserve it."""
|
|
|
|
secret = getattr(model_instance, self.attname)
|
|
|
|
if CLIENT_SECRET_HASH_PATTERN.fullmatch(secret):
|
|
logger.debug(
|
|
"%s: %s is already hashed with sha256.",
|
|
model_instance,
|
|
self.attname,
|
|
)
|
|
return secret
|
|
|
|
try:
|
|
hasher = identify_hasher(secret)
|
|
logger.debug(
|
|
"%s: %s is already hashed with %s.",
|
|
model_instance,
|
|
self.attname,
|
|
hasher,
|
|
)
|
|
except ValueError:
|
|
logger.debug(
|
|
"%s: %s is not hashed; hashing it now.", model_instance, self.attname
|
|
)
|
|
hashed_secret = make_password(secret)
|
|
setattr(model_instance, self.attname, hashed_secret)
|
|
return hashed_secret
|
|
|
|
return super().pre_save(model_instance, add)
|