@plosson/agentio 0.8.1 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
package/src/server/oauth.test.ts
CHANGED
|
@@ -809,6 +809,29 @@ describe('handleAuthorizePost — happy path', () => {
|
|
|
809
809
|
expect(url.searchParams.get('state')).toBe('STATE_XYZ');
|
|
810
810
|
});
|
|
811
811
|
|
|
812
|
+
test('api_key with trailing whitespace still authorizes (trim)', async () => {
|
|
813
|
+
// Regression: operators often paste the key with a trailing newline
|
|
814
|
+
// from terminals. Byte-for-byte compare would silently reject.
|
|
815
|
+
const ctx = makeCtx();
|
|
816
|
+
const clientId = await registerTestClient(ctx);
|
|
817
|
+
for (const suffix of [' ', '\n', '\r\n', ' \t']) {
|
|
818
|
+
const res = await handleAuthorizePost(
|
|
819
|
+
authorizePost({
|
|
820
|
+
client_id: clientId,
|
|
821
|
+
redirect_uri: 'http://localhost:53682/callback',
|
|
822
|
+
response_type: 'code',
|
|
823
|
+
code_challenge: 'CHAL',
|
|
824
|
+
code_challenge_method: 'S256',
|
|
825
|
+
state: '',
|
|
826
|
+
scope: 'gchat:default',
|
|
827
|
+
api_key: TEST_API_KEY + suffix,
|
|
828
|
+
}),
|
|
829
|
+
ctx
|
|
830
|
+
);
|
|
831
|
+
expect(res.status).toBe(302);
|
|
832
|
+
}
|
|
833
|
+
});
|
|
834
|
+
|
|
812
835
|
test('issued code is consumable from the store', async () => {
|
|
813
836
|
const ctx = makeCtx();
|
|
814
837
|
const clientId = await registerTestClient(ctx);
|
package/src/server/oauth.ts
CHANGED
|
@@ -539,7 +539,10 @@ export async function handleAuthorizePost(
|
|
|
539
539
|
|
|
540
540
|
const params = result.params;
|
|
541
541
|
const client = ctx.oauthStore.findClient(params.clientId);
|
|
542
|
-
|
|
542
|
+
// Trim pasted whitespace — same UX issue as setup-page.ts: a stray
|
|
543
|
+
// newline or trailing space from copy-paste would otherwise fail the
|
|
544
|
+
// byte-for-byte compare with a confusing error.
|
|
545
|
+
const apiKey = (form.get('api_key') ?? '').trim();
|
|
543
546
|
|
|
544
547
|
if (!apiKey || !constantTimeEquals(apiKey, ctx.apiKey)) {
|
|
545
548
|
// Re-render the form with an error. We deliberately do NOT redirect
|
|
@@ -160,6 +160,23 @@ describe('POST / — login', () => {
|
|
|
160
160
|
expect(res.headers.get('set-cookie')).toBeNull();
|
|
161
161
|
});
|
|
162
162
|
|
|
163
|
+
test('correct api_key with trailing whitespace (newline/space) → 302', async () => {
|
|
164
|
+
// Regression: operators routinely paste the key with a trailing
|
|
165
|
+
// newline from terminals. Byte-for-byte compare would silently
|
|
166
|
+
// reject an otherwise-valid key; trim makes it forgiving.
|
|
167
|
+
for (const suffix of [' ', '\n', '\r\n', ' \t']) {
|
|
168
|
+
const res = await handleRequest(
|
|
169
|
+
req('POST', '/', {
|
|
170
|
+
contentType: FORM,
|
|
171
|
+
body: formBody({ api_key: API_KEY + suffix }),
|
|
172
|
+
}),
|
|
173
|
+
ctx
|
|
174
|
+
);
|
|
175
|
+
expect(res.status).toBe(302);
|
|
176
|
+
expect(res.headers.get('set-cookie')).toContain(expectedCookieValue(API_KEY));
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
|
|
163
180
|
test('correct api_key → 302 to /, sets cookie with expected attributes', async () => {
|
|
164
181
|
const res = await handleRequest(
|
|
165
182
|
req('POST', '/', {
|
package/src/server/setup-page.ts
CHANGED
|
@@ -338,7 +338,11 @@ async function handleSetupPost(
|
|
|
338
338
|
});
|
|
339
339
|
}
|
|
340
340
|
|
|
341
|
-
|
|
341
|
+
// Trim: operators routinely paste the key with a trailing newline or
|
|
342
|
+
// space from terminals / password managers. constantTimeEquals is a
|
|
343
|
+
// byte-for-byte compare, so an extra whitespace character silently
|
|
344
|
+
// fails the login with a confusing "Invalid API key" error.
|
|
345
|
+
const apiKey = (form.get('api_key') ?? '').trim();
|
|
342
346
|
if (!apiKey || !constantTimeEquals(apiKey, ctx.apiKey)) {
|
|
343
347
|
return new Response(loginFormHtml('Invalid API key.'), {
|
|
344
348
|
status: 401,
|