Skip to content

Commit 8bab97e

Browse files
committed
Ajan kataloğunda site kimliğini doğru sütunla ara
Katalog ucu canlıda boş dönüyordu. Sebep: `SiteGrantView.clientId` yabancı anahtar, yani `oauth_clients.id`; ben onu metin `client_id` arayan `getClientByClientId`'ye veriyordum. Sorgu hiçbir şey bulmuyor, null dönüyor ve döngüdeki `continue` bunu "bu site eylem sunmuyor" diye yutuyordu — hata hiçbir yerde görünmedi. Asıl kusur yanlış çağrı değil, sessizliği. `getClientById` eklendi ve iki çağrı düzeltildi. Test ayrımı tutuyor: grant satır kimliğini taşımalı, satır kimliği metin client_id olarak ARANAMAMALI. `getClientById`'yi yanlış sütuna bakacak şekilde bozan mutasyon testi düşürüyor (24→23). Bu hatayı birim testleri yakalayamazdı; yalnız canlıda uçtan uca deneyince göründü ve o da ancak "200 döndü ama liste boş" diye bakınca.
1 parent 1ad8ac6 commit 8bab97e

5 files changed

Lines changed: 71 additions & 2 deletions

File tree

‎scripts/orbit-d1-test-worker.ts‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,12 @@ async function handleAction(body: ActionRequest, env: Environment): Promise<Resp
748748
return json({ ok: true });
749749
}
750750

751+
case 'getSiteClientById': {
752+
return json({
753+
client: await siteRepository.getClientById(stringValue(data, 'id')),
754+
});
755+
}
756+
751757
case 'getSiteClient': {
752758
return json({ client: await siteRepository.getClientByClientId(stringValue(data, 'clientId')) });
753759
}

‎scripts/orbit-site-authorization-tests.ts‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,37 @@ describe('Orbit as a sign-in door for other sites', { concurrency: false }, () =
600600
assert.equal(afterReplay.tokens.filter((row) => row.revoked_at === null).length, 0);
601601
});
602602

603+
test('a grant points at the client ROW id, not the public client_id', async () => {
604+
const now = 1_760_000_950_000;
605+
const accountId = 'door-rowid';
606+
await seedClientAndAccount(accountId, now);
607+
const grant = await consent({ prefix: 'rowid', accountId, now });
608+
609+
/* Bu test canlıda ödenen bir bedelden doğdu. `SiteGrantView.clientId`
610+
yabancı anahtar, yani `oauth_clients.id`. Onu metin `client_id` arayan
611+
`getClientByClientId`'ye verince sorgu hiçbir şey bulmuyor, null dönüyor
612+
ve çağıran tarafta "bu site eylem sunmuyor" gibi görünüyor. Ajan
613+
kataloğu boş döndü ve hiçbir yerde hata görünmedi — çünkü çağıran
614+
taraftaki `continue` onu yutuyordu.
615+
616+
İki isim birbirine o kadar benziyor ki karıştırmak kaçınılmaz; bu yüzden
617+
ayrımı test tutuyor. */
618+
assert.equal(grant.clientId, CLIENT.id, 'grant satır kimliğini taşımalı');
619+
assert.notEqual(grant.clientId, CLIENT.clientId, 'metin client_id ile aynı olmamalı');
620+
621+
const byRow = await callAction<{ client: { clientId: string } | null }>(
622+
'getSiteClientById', { id: grant.clientId },
623+
);
624+
assert.equal(byRow.client?.clientId, CLIENT.clientId, 'satır kimliğiyle bulunmalı');
625+
626+
/* Ters yön: satır kimliğini metin arayan fonksiyona vermek BULMAMALI.
627+
Bulsaydı hata görünür olurdu; bulmadığı için sessizdi. */
628+
const byPublic = await callAction<{ client: unknown | null }>(
629+
'getSiteClient', { clientId: grant.clientId },
630+
);
631+
assert.equal(byPublic.client, null, 'satır kimliği metin client_id olarak aranamaz');
632+
});
633+
603634
test('agent access is off until the person turns it on, and dies with the grant', async () => {
604635
const now = 1_760_000_900_000;
605636
const accountId = 'door-agent';

‎src/server/http/api.ts‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7516,7 +7516,10 @@ export async function handleApiRequest(
75167516
const sites = [];
75177517
for (const grant of grants) {
75187518
if (grant.revokedAt !== null || grant.agentAccessAt === null) continue;
7519-
const client = await siteRepository.getClientByClientId(grant.clientId);
7519+
/* `grant.clientId` SATIR kimliği (yabancı anahtar), metin client_id
7520+
* değil — `getClientByClientId` ile aramak hiçbir şey bulmuyordu ve
7521+
* site sessizce listeden düşüyordu. */
7522+
const client = await siteRepository.getClientById(grant.clientId);
75207523
if (!client || client.status !== 'active' || !client.actionsUrl) continue;
75217524
try {
75227525
const { catalog, fetchedAt } = await siteActionCatalog(client, now);
@@ -7570,7 +7573,7 @@ export async function handleApiRequest(
75707573
throw new ApiError(403, 'agent_access_closed', 'The person has not opened this site to agents.');
75717574
}
75727575

7573-
const client = await siteRepository.getClientByClientId(grant.clientId);
7576+
const client = await siteRepository.getClientById(grant.clientId);
75747577
if (!client || client.status !== 'active' || !client.actionsUrl || !client.actionsEndpoint) {
75757578
throw new ApiError(503, 'site_catalog_unavailable', 'This site does not accept agent actions.');
75767579
}

‎src/server/repositories/d1/d1-site-authorization-repository.ts‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,25 @@ export class D1SiteAuthorizationRepository implements SiteAuthorizationRepositor
209209
return clientFromSql(row, uris.results.map((entry) => entry.redirect_uri));
210210
}
211211

212+
async getClientById(id: string): Promise<SiteClientView | null> {
213+
const row = await this.#db.prepare(`
214+
SELECT id, client_id, secret_digest, hash_version, label, site_url,
215+
allowed_scopes, environment, status, created_at, revoked_at,
216+
actions_url, actions_endpoint
217+
FROM oauth_clients
218+
WHERE id = ?
219+
`).bind(id).first<ClientSqlRow>();
220+
if (!row) return null;
221+
222+
const uris = await this.#db.prepare(`
223+
SELECT redirect_uri FROM oauth_client_redirect_uris
224+
WHERE client_id = ?
225+
ORDER BY created_at, redirect_uri
226+
`).bind(row.id).all<{ redirect_uri: string }>();
227+
228+
return clientFromSql(row, uris.results.map((entry) => entry.redirect_uri));
229+
}
230+
212231
async createClient(
213232
input: Parameters<SiteAuthorizationRepository['createClient']>[0],
214233
): Promise<SiteClientView | null> {

‎src/server/repositories/site-authorization-repository.ts‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,16 @@ export interface SiteTokenResolution {
9191
export interface SiteAuthorizationRepository {
9292
getClientByClientId(clientId: string): Promise<SiteClientView | null>;
9393

94+
/* SATIR KİMLİĞİYLE istemci. `getClientByClientId` metin `client_id`'yi arar
95+
* (örn. "orbit-equinox-rota"); bu ise `oauth_clients.id`'yi.
96+
*
97+
* İkisi ayrı olmak zorunda ve karıştırmak sessiz: `SiteGrantView.clientId`
98+
* yabancı anahtar, yani SATIR kimliği. Onu metin arayan fonksiyona verince
99+
* sorgu hiçbir şey bulmuyor, `null` dönüyor ve çağıran tarafta "bu site
100+
* eylem sunmuyor" gibi görünüyor. Canlıda tam olarak bu oldu: katalog boş
101+
* döndü, hata da vermedi. */
102+
getClientById(id: string): Promise<SiteClientView | null>;
103+
94104
/* Yeni alt site istemcisi. Sır BURAYA GİRMİYOR, yalnız özeti: peper
95105
* Worker'ın içinde kalıyor ve özet çağıran tarafta hesaplanıyor.
96106
*

0 commit comments

Comments
 (0)