Sources: accept login:password Basic credential for Gitea (API + git store)

This commit is contained in:
2026-11-01 20:46:07 +08:00
parent e8b91abb2c
commit 589b5a6533
3 changed files with 17 additions and 4 deletions
+3 -1
View File
@@ -31,11 +31,13 @@ async function writeGiteaCredentials(file: string, gitea: GiteaAccount,
env: Record<string, string | undefined>): Promise<void> {
const token = env[gitea.tokenEnv] ?? ''
if (!token) throw new SetupError('giteaTokenMissing', { name: gitea.tokenEnv })
// A Basic-style credential (`login:password`) stores only the password part for git; a token stores itself.
const secret = token.includes(':') ? token.slice(token.indexOf(':') + 1) : token
const origin = new URL(gitea.url).origin
const scheme = origin.slice(0, origin.indexOf('://'))
const host = origin.slice(origin.indexOf('://') + 3)
const temporary = `${file}.${process.pid}.tmp`
await writeFile(temporary, `${scheme}://${encodeURIComponent(gitea.login)}:${encodeURIComponent(token)}@${host}\n`, { mode: 0o600 })
await writeFile(temporary, `${scheme}://${encodeURIComponent(gitea.login)}:${encodeURIComponent(secret)}@${host}\n`, { mode: 0o600 })
await rename(temporary, file)
}
+5 -2
View File
@@ -11,7 +11,10 @@ export type GiteaFetch = (url: string, init: { method: string; headers: Record<s
export interface GiteaTarget { url: string; token: string }
/** Reads the token from `env[tokenEnv]`; the token is never stored in Settings, only its variable name is. */
/** The credential scheme: `login:password` (any value with a colon) authenticates as HTTP Basic, anything else as a token. */
export const giteaAuthorization = (secret: string): string =>
secret.includes(':') ? `Basic ${Buffer.from(secret, 'utf8').toString('base64')}` : `token ${secret}`
/** Reads the credential from `env[tokenEnv]`; only its variable name is stored in Settings, never the value. */
export function giteaClient(url: string, tokenEnv: string, env: Record<string, string | undefined> = process.env,
fetchImpl: GiteaFetch = fetch): GiteaApi {
if (!url) throw new Error('The Gitea Source names no URL')
@@ -27,7 +30,7 @@ export class GiteaApi {
async raw(path: string, init: { method?: string; body?: unknown; signal?: AbortSignal } = {}): Promise<GiteaResponse> {
return await this.fetchImpl(`${this.target.url}/api/v1/${path.replace(/^\//, '')}`, {
method: init.method ?? 'GET',
headers: { authorization: `token ${this.target.token}`,
headers: { authorization: giteaAuthorization(this.target.token),
...(init.body === undefined ? {} : { 'content-type': 'application/json' }) },
...(init.body === undefined ? {} : { body: JSON.stringify(init.body) }),
...(init.signal === undefined ? {} : { signal: init.signal }) })
+9 -1
View File
@@ -2,7 +2,7 @@ import { afterEach, describe, expect, it, vi } from 'vitest'
import { Engine, key, type Assessor, type Executor, type OutboundMessage, type Trigger } from '../src/core/index.ts'
import { KV } from '../src/kv.ts'
import { GiteaConfigSchema, apply, giteaKind, giteaThreadContext, pollRound } from '../src/sources/gitea.ts'
import { giteaClient, GiteaApiError } from '../src/sources/gitea-api.ts'
import { giteaAuthorization, giteaClient, GiteaApiError } from '../src/sources/gitea-api.ts'
import { SOURCE, issueTrigger } from '../src/sources/gitea-trigger.ts'
import { giteaLink, sendGitea } from '../src/sources/gitea-channel.ts'
import { SourceRegistry } from '../src/sources/registry.ts'
@@ -70,6 +70,14 @@ const issue = (number: number, over: Record<string, unknown> = {}): Record<strin
const triggerOf = (meta: Record<string, unknown>): Trigger => ({ id: 't', source: SOURCE, threads: [{ source: SOURCE, id: '12' }],
actor: { source: SOURCE, id: 'alice' }, human: true, at: '2026-09-08T00:00:00Z', text: '', meta })
describe('giteaAuthorization', () => {
it('sends a value with a colon as HTTP Basic and a token as token auth', () => {
expect(giteaAuthorization('bot:secret:1')).toBe(`Basic ${Buffer.from('bot:secret:1', 'utf8').toString('base64')}`)
expect(giteaAuthorization('0123456789abcdef0123456789abcdef01234567')).toBe('token 0123456789abcdef0123456789abcdef01234567')
expect(giteaAuthorization('glpat-abc_123')).toBe('token glpat-abc_123')
})
})
describe('GiteaConfigSchema', () => {
it('defaults a bare config to a safe baseline', () => {
expect(GiteaConfigSchema.parse({})).toEqual({