Backend: a GreetingToken model stores only a SHA-256 hash of a j621r_…
key (shown once at creation) plus label, prefix, created/last-used. A
dedicated GreetingTokenAuthentication understands the usual
'Authorization: Token …' header but is registered only on RandomItemView
(alongside the normal token auth), so a greeting token authenticates
/api/random/ and is rejected with 401 everywhere else — exactly the scope
shell greetings need. Endpoints: GET/POST /api/auth/greeting-tokens/ and
DELETE /api/auth/greeting-tokens/{id}/ (own tokens only; the list never
returns keys or hashes).
Frontend: /tokens page (Account → Shell tokens card, command palette entry)
lists tokens with label, prefix, created/last-used and revoke (shared
confirm dialog). Creating one shows the key with Copy and 'Copy for fish'
buttons plus a pointer to extras/fish_greeting.
Tests: apps/accounts/tests/test_greeting_tokens.py — 9 tests covering
create-once semantics and hashing, hidden keys in listings, the scope
guarantee (random 200 with a signed URL; 401 on files, storage, me, tags
cloud, delete and the token list itself), unknown/revoked keys, cross-user
revocation, last-used tracking and label limits.
Verified live: created a token, rolled /random (signed URL), got 401 from
four other endpoints, saw the list omit secrets, revoked it (204) and the
same key then 401'd on /random. Full suite: 39 tests green.
34 lines
926 B
Python
34 lines
926 B
Python
from django.urls import path
|
|
|
|
from .views import (
|
|
AvatarView,
|
|
E621CredentialsView,
|
|
GreetingTokenDetailView,
|
|
GreetingTokenListView,
|
|
LoginView,
|
|
LogoutView,
|
|
MeView,
|
|
PreferencesView,
|
|
RegisterView,
|
|
)
|
|
|
|
urlpatterns = [
|
|
path("register/", RegisterView.as_view(), name="register"),
|
|
path("token/", LoginView.as_view(), name="login"),
|
|
path("logout/", LogoutView.as_view(), name="logout"),
|
|
path("me/", MeView.as_view(), name="me"),
|
|
path("avatar/", AvatarView.as_view(), name="avatar"),
|
|
path("preferences/", PreferencesView.as_view(), name="preferences"),
|
|
path("e621/", E621CredentialsView.as_view(), name="e621_credentials"),
|
|
path(
|
|
"greeting-tokens/",
|
|
GreetingTokenListView.as_view(),
|
|
name="greeting_tokens",
|
|
),
|
|
path(
|
|
"greeting-tokens/<int:pk>/",
|
|
GreetingTokenDetailView.as_view(),
|
|
name="greeting_token",
|
|
),
|
|
]
|