G5: generate an unusable password on Gitea user create

Gitea rejects a null password with a bare 400. These accounts are never
password-authenticated — humans arrive via OIDC, agents via passport-bound
scoped tokens, local password sign-in is disabled server-wide — so we generate
a credential that is never stored, returned or recoverable. An unusable
password is safer than a blank one or a shared default.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Grant Whitmer
2026-08-11 15:55:11 -04:00
parent f621e49770
commit 3a9259a0da

View File

@@ -13,6 +13,7 @@ a marriage.
from __future__ import annotations from __future__ import annotations
import secrets
from typing import Any from typing import Any
import httpx import httpx
@@ -53,17 +54,20 @@ class GiteaClient:
r = await self._request("GET", f"/users/{username}") r = await self._request("GET", f"/users/{username}")
if r.status_code == 200: if r.status_code == 200:
return r.json() return r.json()
# Gitea requires a password field on admin user-create and rejects null
# with a bare 400. Nobody ever uses this one: humans arrive through OIDC
# and agents through scoped passport-bound tokens, and local password
# sign-in is disabled server-wide. So we generate a credential that is
# never stored, never returned and never recoverable — an unusable
# password is safer than a blank one or a shared default.
r = await self._request( r = await self._request(
"POST", "POST",
"/admin/users", "/admin/users",
json={ json={
"username": username, "username": username,
"email": email, "email": email,
"password": None, "password": secrets.token_urlsafe(48),
"must_change_password": False, "must_change_password": False,
# Humans arrive through OIDC and agents through scoped tokens.
# Nobody gets a password on this system.
"login_name": username,
}, },
) )
if r.status_code not in (200, 201): if r.status_code not in (200, 201):