ask for the URL first, and offer what tea already knows

The form asked for a URL and a token together, on a fresh instance, while the
machine in front of it often had both on disk already — somebody had run `tea
login` by hand.

Two changes.

One thing at a time: until the platform knows where Gitea is, a token cannot be
checked, because there is nothing to check it against. So the owner names the
instance first, and only then does the page ask for — or discover — a token.
That step now probes /api/v1/version unauthenticated, since demanding a token
to validate a URL refuses a good URL for the wrong reason and sends whoever
typed it looking for a credential problem.

And it reads tea's own config when the platform has no instance yet, offering
the URL it finds. A machine that already has a working login should not be
asked to retype what it can see.

Members skip the first step entirely — the instance is the owner's.
This commit is contained in:
officer
2026-08-19 10:25:22 +00:00
parent 862512d83a
commit d4efc52447
4 changed files with 102 additions and 23 deletions
+36 -10
View File
@@ -84,18 +84,20 @@ export async function osUserFor(userId: number): Promise<string | null> {
* The TOKEN ITSELF IS NOT RETURNED. Callers want to know whether one exists and who it belongs to; the
* value never needs to leave this file, and returning it would put it in a response body.
*/
export async function readTeaLogin(userId: number, instanceUrl: string): Promise<TeaLogin | null> {
async function rawConfig(userId: number): Promise<string | null> {
const osUser = await osUserFor(userId);
const raw = isOwnerUser(osUser)
? await (async () => {
try {
return readFileSync(configPathFor(homedir()), 'utf8');
} catch {
return null;
}
})()
: await asUser(osUser, ['cat', configPathFor(join('/home', osUser!))]);
if (isOwnerUser(osUser)) {
try {
return readFileSync(configPathFor(homedir()), 'utf8');
} catch {
return null;
}
}
return asUser(osUser, ['cat', configPathFor(join('/home', osUser!))]);
}
export async function readTeaLogin(userId: number, instanceUrl: string): Promise<TeaLogin | null> {
const raw = await rawConfig(userId);
if (!raw) return null;
// One block per login. Matched loosely on purpose — the file is hand-editable and its key order is not
@@ -115,6 +117,30 @@ export async function readTeaLogin(userId: number, instanceUrl: string): Promise
return null;
}
/**
* Whatever login `tea` already has, when the platform does not yet know the instance.
*
* A machine where somebody ran `tea login` by hand already knows the URL and holds a working token. Not
* offering that back means asking them to retype something the box in front of them can see.
*
* Returns the FIRST login, which for a config with one is the only one, and for a config with several is
* the one `tea` itself defaults to — the same answer the agent would get.
*/
export async function readAnyTeaLogin(userId: number): Promise<TeaLogin | null> {
const raw = await rawConfig(userId);
if (!raw) return null;
const block = raw.split(/^\s*-\s+name:/m)[1];
if (!block) return null;
const url = block.match(/^\s*url:\s*(\S+)/m)?.[1]?.replace(/\/$/, '') ?? '';
if (!url) return null;
return {
name: block.split('\n')[0]?.trim() ?? '',
url,
user: block.match(/^\s*user:\s*(\S+)/m)?.[1] ?? null,
hasToken: /^\s*token:\s*\S+/m.test(block),
};
}
/**
* Write a login for this instance, as that user.
*