feat(pat): add personal access token storage and settings

A personal_access_tokens table (migration 0032) holds scoped user-level API
credentials. Only the SHA-256 of the secret is stored, like device session
tokens. Lookups exclude revoked and expired tokens and the tokens of
deactivated users. PAT_* settings cover the feature switch, default and
maximum lifetime, the operator opt-in for non-expiring tokens and the
per-user cap.
This commit is contained in:
arc53-machine committed 2026-09-19 23:39:58 +01:00
1 parent 0952a05aab
commit 22ecc0aee3
7 files changed
+494

No files matched your search

+24
View File
@@ -85,6 +85,28 @@ class AuthSettings(SettingsGroup):
default=None, description="Bearer token for IdP SCIM clients (required when SCIM is enabled)."
)
# Personal access tokens: scoped user-level API credentials for CLI and CI/CD use.
PAT_ENABLED: bool = Field(
default=True,
description=(
"Allow users to create personal access tokens. Tokens are only issued under AUTH_TYPE=oidc or "
"unset (None); simple_jwt and session_jwt have no stable user identity to bind a token to."
),
)
PAT_DEFAULT_LIFETIME_DAYS: int = Field(
default=90, gt=0, description="Lifetime of a personal access token created without an explicit expiry."
)
PAT_MAX_LIFETIME_DAYS: int = Field(
default=365, gt=0, description="Longest lifetime a user may request for a personal access token."
)
PAT_ALLOW_NON_EXPIRING: bool = Field(
default=False,
description="Let users create personal access tokens that never expire. Off by default.",
)
PAT_MAX_PER_USER: int = Field(
default=25, gt=0, description="Maximum number of live personal access tokens per user."
)
@field_validator("AUTH_TYPE", mode="before")
@classmethod
def _normalize_auth_type(cls, v):
@@ -99,4 +121,6 @@ class AuthSettings(SettingsGroup):
raise ValueError(f"AUTH_TYPE=oidc requires settings: {', '.join(missing)}")
if self.SCIM_ENABLED and not self.SCIM_TOKEN:
raise ValueError("SCIM_ENABLED requires settings: SCIM_TOKEN")
if self.PAT_DEFAULT_LIFETIME_DAYS > self.PAT_MAX_LIFETIME_DAYS:
raise ValueError("PAT_DEFAULT_LIFETIME_DAYS must not exceed PAT_MAX_LIFETIME_DAYS")
return self