infrasynth-backend-kit/infrasynth/security/models.py
2026-08-28 14:38:47 -05:00

120 lines
3.9 KiB
Python

from django.conf import settings
from django.db import models
class Role(models.Model):
name = models.CharField(max_length=100)
slug = models.SlugField(max_length=100, unique=True)
description = models.TextField(blank=True)
permissions = models.JSONField(default=list, help_text="List of permission codenames")
is_system = models.BooleanField(default=False, help_text="System roles cannot be deleted")
users = models.ManyToManyField(
settings.AUTH_USER_MODEL,
related_name="roles",
blank=True,
help_text="Users assigned this role",
)
class Meta:
db_table = "security_role"
def __str__(self):
return self.name
class Grant(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="direct_grants",
)
codename = models.CharField(max_length=200, db_index=True)
granted_by = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.SET_NULL,
null=True,
related_name="grants_given",
)
reason = models.TextField(blank=True)
expires_at = models.DateTimeField(null=True, blank=True)
class Meta:
db_table = "security_grant"
unique_together = [("user", "codename")]
class Revoke(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="direct_revokes",
)
codename = models.CharField(max_length=200, db_index=True)
revoked_by = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.SET_NULL,
null=True,
related_name="revokes_given",
)
reason = models.TextField(blank=True)
class Meta:
db_table = "security_revoke"
unique_together = [("user", "codename")]
class APIKey(models.Model):
name = models.CharField(max_length=200)
prefix = models.CharField(max_length=12, unique=True, help_text="First 8 characters visible in UI")
key_hash = models.CharField(max_length=255, help_text="PBKDF2 hash of the full secret")
scopes = models.JSONField(default=list, help_text='["read:users", "write:billing"]')
created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True)
is_active = models.BooleanField(default=True)
expires_at = models.DateTimeField(null=True, blank=True)
last_used_at = models.DateTimeField(null=True, blank=True)
rotated_from = models.ForeignKey("self", on_delete=models.SET_NULL, null=True, blank=True)
class Meta:
db_table = "security_api_key"
class TwoFactorConfig(models.Model):
METHOD_TOTP = "totp"
METHOD_EMAIL = "email"
METHOD_BOTH = "both"
user = models.OneToOneField(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="two_factor_config",
)
is_enabled = models.BooleanField(default=False)
is_configured = models.BooleanField(default=False)
method = models.CharField(
max_length=10,
choices=[
(METHOD_TOTP, "TOTP"),
(METHOD_EMAIL, "Email"),
(METHOD_BOTH, "Both"),
],
default=METHOD_TOTP,
)
secret_key_encrypted = models.CharField(max_length=500, null=True, blank=True)
recovery_codes_encrypted = models.TextField(null=True, blank=True)
email_verified = models.BooleanField(default=False)
email_code = models.CharField(max_length=6, null=True, blank=True)
email_code_expires_at = models.DateTimeField(null=True, blank=True)
class Meta:
db_table = "security_two_factor_config"
class ALTCHAChallenge(models.Model):
challenge_id = models.CharField(max_length=64, primary_key=True)
salt = models.CharField(max_length=32)
difficulty = models.IntegerField(default=10000)
expires_at = models.DateTimeField(db_index=True)
is_verified = models.BooleanField(default=False)
class Meta:
db_table = "security_altcha_challenge"