gh_UserManager/apps/api/apps/authentication/models.py
bermooda-company 54d5891edf user
2026-08-23 23:59:14 +03:30

157 lines
4.9 KiB
Python

from django.db import models
from django.utils import timezone
from apps.common.models import BaseModel
from apps.identity.models import User
class CredentialType(models.TextChoices):
PASSWORD = "password", "Password"
PHONE = "phone", "Phone"
EMAIL = "email", "Email"
PASSKEY = "passkey", "Passkey"
OAUTH = "oauth", "OAuth Provider"
class CredentialStatus(models.TextChoices):
ACTIVE = "active", "Active"
INACTIVE = "inactive", "Inactive"
REVOKED = "revoked", "Revoked"
class Credential(BaseModel):
user = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name="credentials",
)
credential_type = models.CharField(
max_length=16,
choices=CredentialType.choices,
)
provider = models.CharField(max_length=128, blank=True, default="")
value_hash = models.CharField(max_length=128, blank=True, default="")
is_active = models.BooleanField(default=True)
is_default = models.BooleanField(default=False)
last_used_at = models.DateTimeField(null=True, blank=True)
times_used = models.IntegerField(default=0)
class Meta:
ordering = ["-is_default", "-created_at"]
indexes = [
models.Index(fields=["user", "credential_type"]),
models.Index(fields=["user", "is_default"]),
models.Index(fields=["is_active"]),
]
def __str__(self):
return f"{self.user}{self.get_credential_type_display()}"
def mark_used(self):
self.last_used_at = timezone.now()
self.times_used += 1
self.save(update_fields=["last_used_at", "times_used", "updated_at"])
class Passkey(BaseModel):
"""WebAuthn Passkey for passwordless authentication."""
class PasskeyType(models.TextChoices):
PLATFORM = "platform", "Platform (device-bound, e.g. built-in Fingerprint/Face)"
CREDENTIAL = "credential", "Cross-Platform (roaming, e.g. YubiKey, Feitian)"
user = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name="passkeys",
)
name = models.CharField(max_length=255, blank=True, default="")
credential_id = models.CharField(max_length=255, unique=True)
public_key = models.TextField()
sign_count = models.IntegerField(default=0)
last_used_at = models.DateTimeField(null=True, blank=True)
is_active = models.BooleanField(default=True)
touch_enabled = models.BooleanField(default=True)
user_verification = models.CharField(
max_length=16,
choices=[("required", "Required"), ("preferred", "Preferred"), ("discouraged", "Discouraged")],
default="preferred",
)
class Meta:
ordering = ["-is_active", "-created_at"]
indexes = [
models.Index(fields=["user", "is_active"]),
models.Index(fields=["credential_id"]),
]
def __str__(self):
return f"{self.user}{self.name or 'Passkey'}"
def mark_used(self):
self.last_used_at = timezone.now()
self.sign_count += 1
self.save(update_fields=["last_used_at", "sign_count", "updated_at"])
class RecoveryCode(BaseModel):
"""One-time recovery code for MFA bypass."""
user = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name="recovery_codes",
)
code_hash = models.CharField(max_length=128)
used = models.BooleanField(default=False)
used_at = models.DateTimeField(null=True, blank=True)
expires_at = models.DateTimeField()
purpose = models.CharField(
max_length=32,
choices=[("mfa_bypass", "MFA Bypass"), ("account_recovery", "Account Recovery")],
default="mfa_bypass",
)
class Meta:
ordering = ["-created_at"]
indexes = [
models.Index(fields=["user", "used"]),
models.Index(fields=["expires_at"]),
]
def __str__(self):
return f"Recovery code for {self.user}"
def is_valid(self):
from django.utils import timezone
return not self.used and self.expires_at > timezone.now()
class TrustedDevice(BaseModel):
"""Device trusted for MFA bypass."""
user = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name="trusted_devices",
)
fingerprint = models.CharField(max_length=128)
expires_at = models.DateTimeField()
purpose = models.CharField(
max_length=32,
choices=[("mfa_bypass", "MFA Bypass"), ("session_remember", "Session Remember")],
default="mfa_bypass",
)
is_active = models.BooleanField(default=True)
class Meta:
ordering = ["-is_active", "-created_at"]
indexes = [
models.Index(fields=["user", "is_active"]),
]
def __str__(self):
return f"Trusted device for {self.user}"
def is_valid(self):
from django.utils import timezone
return self.is_active and self.expires_at > timezone.now()