1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-11 12:46:49 +00:00

feat(accounts): API tokens for scripts and tools

The REST API took only the login session, so a script could not use it
without a browser. A user now makes API tokens on the profile page (API
Tokens tab): a name, and an expiry of 30, 90 or 365 days or none. The
token is shown once, on a page that is not cached, and then kept only as
a SHA-512 digest; the list shows its name, first characters, creation
and expiry, and a token is deleted there. Tools send it as
"Authorization: Token wvc_...", and act as its user, with that user's
permissions. Making and deleting a token is logged, without the token.

A password alone (HTTP Basic) still does not reach the API, as it would
skip the OTP second factor: a token is made in a session that passed the
login and its OTP. The token class comes first, so a refused request gets
401 with its challenge instead of 403.

django-rest-knox 5.1.0 provides the tokens (accounts.ApiToken adds the
name); settings.py takes "knox", KNOX_TOKEN_MODEL and REST_KNOX from the
template.
This commit is contained in:
catborise 2026-10-24 23:05:27 +03:00
parent ba51b3e4d6
commit 1c60696ea1
14 changed files with 290 additions and 13 deletions

View file

@ -2,7 +2,7 @@ from appsettings.settings import app_settings
from django.contrib.auth import get_user_model
from django.contrib.auth.forms import AuthenticationForm
from django.core.validators import MaxLengthValidator
from django.forms import EmailField, Form, ModelForm, ValidationError
from django.forms import CharField, EmailField, Form, ModelForm, TypedChoiceField, ValidationError
from django.utils.translation import gettext_lazy as _
from .models import UserInstance, UserSSHKey
@ -74,6 +74,14 @@ class UserSSHKeyForm(ModelForm):
fields = ("keyname", "keypublic")
class ApiTokenForm(Form):
name = CharField(label=_("Name"), max_length=64, help_text=_("What uses it, such as a script or a host"))
days = TypedChoiceField(
label=_("Expires"), coerce=int, empty_value=None, required=False, initial=90,
choices=[(30, _("in 30 days")), (90, _("in 90 days")), (365, _("in a year")), ("", _("never"))],
)
class EmailOTPForm(Form):
email = EmailField(label=_("Email"))