fix(gitea-bot): mint PAT via Basic auth, not Sudo header

Gitea's POST /users/{name}/tokens is explicitly Basic-auth only;
neither the admin token nor Sudo header is accepted. Keep the random
password we generate at createUser time and pass it straight into
createAccessTokenFor as Basic auth.

For bots that already exist from a half-failed previous provision
run, reset their password via PATCH /admin/users/{name} so we can
Basic-auth as them and mint a fresh token.

Made-with: Cursor
This commit is contained in:
2026-04-21 10:58:25 -07:00
parent b9511601bc
commit d9d3514647
2 changed files with 64 additions and 10 deletions

View File

@@ -28,6 +28,7 @@ import {
createUser,
createAccessTokenFor,
ensureOrgTeamMembership,
adminEditUser,
} from '@/lib/gitea';
import { encryptSecret, decryptSecret } from '@/lib/auth/secret-box';
@@ -254,17 +255,29 @@ export async function ensureWorkspaceProvisioned(workspace: VibnWorkspace): Prom
try {
const wantBot = giteaBotUsernameFor(workspace.slug);
// 1. Ensure the bot user exists.
// 1. Ensure the bot user exists. We *always* generate a fresh
// random password — if the user already exists, we can't
// retrieve its password to auth with, so we'll fail at the
// token-mint step. The `partial` provision status + error
// message tells the operator to delete the bot user in Gitea
// (or reset its password) and re-run. Since this only hurts
// re-provisioning after a half-failed run, we also try to
// reset the password via the admin API in that case.
const password = `bot-${randomBytes(24).toString('base64url')}`;
let existingBot = await getUser(wantBot);
if (!existingBot) {
const created = await createUser({
username: wantBot,
email: giteaBotEmailFor(workspace.slug),
// Password is never used (only the PAT is), but Gitea requires one.
password: `bot-${randomBytes(24).toString('base64url')}`,
password,
fullName: `Vibn bot (${workspace.slug})`,
});
existingBot = { id: created.id, login: created.login };
} else {
// Existing bot from a half-failed previous run — reset its
// password so we can basic-auth as it below.
await adminEditUser({ username: wantBot, password });
}
botUsername = existingBot.login;
botUserId = existingBot.id;
@@ -282,6 +295,7 @@ export async function ensureWorkspaceProvisioned(workspace: VibnWorkspace): Prom
if (!botTokenEncrypted) {
const pat = await createAccessTokenFor({
username: botUsername,
password,
name: `vibn-${workspace.slug}-${Date.now().toString(36)}`,
scopes: ['write:repository', 'write:issue', 'write:user'],
});