64 lines
2 KiB
Python
64 lines
2 KiB
Python
from __future__ import annotations
|
|
|
|
import secrets
|
|
|
|
from django.db import models
|
|
from django.utils import timezone
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
|
|
def _generate_token() -> str:
|
|
return secrets.token_urlsafe(32)
|
|
|
|
|
|
class TokenKind(models.TextChoices):
|
|
READ = "read", _("Read (non-public visibility)")
|
|
INGEST = "ingest", _("Ingest (push)")
|
|
|
|
|
|
class ApiToken(models.Model):
|
|
"""Static API token for machine clients. Not tied to a user, no scopes.
|
|
|
|
Read tokens extend visibility to non-public records; ingest tokens may also
|
|
push data (ground-station frames in tdas, station logs in ops). A service
|
|
that has nothing to ingest simply never issues the second kind.
|
|
"""
|
|
|
|
name = models.CharField(
|
|
max_length=64,
|
|
unique=True,
|
|
help_text=_("Human-readable label, e.g. 'TMTC pipeline' or 'ground station 1'."),
|
|
)
|
|
token = models.CharField(
|
|
max_length=128,
|
|
unique=True,
|
|
default=_generate_token,
|
|
editable=False,
|
|
db_index=True,
|
|
help_text=_("Bearer credential. Treat as a secret."),
|
|
)
|
|
kind = models.CharField(
|
|
max_length=16,
|
|
choices=TokenKind.choices,
|
|
default=TokenKind.READ,
|
|
help_text=_("Read tokens extend visibility; ingest tokens may also push data."),
|
|
)
|
|
is_active = models.BooleanField(default=True, db_index=True)
|
|
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
last_used_at = models.DateTimeField(null=True, blank=True)
|
|
notes = models.TextField(blank=True)
|
|
|
|
class Meta:
|
|
ordering = ["-created_at"]
|
|
verbose_name = _("API token")
|
|
verbose_name_plural = _("API tokens")
|
|
|
|
def __str__(self) -> str:
|
|
return self.name
|
|
|
|
def mark_used(self) -> None:
|
|
# Written on every authenticated request, so it must not touch the rest
|
|
# of the row: a full save() here would clobber a concurrent admin edit.
|
|
self.last_used_at = timezone.now()
|
|
type(self).objects.filter(pk=self.pk).update(last_used_at=self.last_used_at)
|