Skip to content

Commit 3671c99

Browse files
committed
cleanup
1 parent d881fd2 commit 3671c99

File tree

1 file changed

+184
-0
lines changed

1 file changed

+184
-0
lines changed

__tests__/oauth-provider.test.ts

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,23 @@ describe('OAuthProvider', () => {
365365
expect(grants.keys.length).toBe(1);
366366
});
367367

368+
it('should reject authorization request with invalid redirect URI', async () => {
369+
// Create an authorization request with an invalid redirect URI
370+
const invalidRedirectUri = 'https://attacker.example.com/callback';
371+
const authRequest = createMockRequest(
372+
`https://example.com/authorize?response_type=code&client_id=${clientId}` +
373+
`&redirect_uri=${encodeURIComponent(invalidRedirectUri)}` +
374+
`&scope=read%20write&state=xyz123`
375+
);
376+
377+
// Expect the request to be rejected
378+
await expect(oauthProvider.fetch(authRequest, mockEnv, mockCtx)).rejects.toThrow('Invalid redirect URI');
379+
380+
// Verify no grant was created
381+
const grants = await mockEnv.OAUTH_KV.list({ prefix: 'grant:' });
382+
expect(grants.keys.length).toBe(0);
383+
});
384+
368385
// Add more tests for auth code flow...
369386
});
370387

@@ -694,6 +711,44 @@ describe('OAuthProvider', () => {
694711
expect(tokens.expires_in).toBe(3600);
695712
});
696713

714+
it('should reject token exchange with code_verifier when PKCE was not used in authorization', async () => {
715+
// First get an auth code WITHOUT using PKCE
716+
const authRequest = createMockRequest(
717+
`https://example.com/authorize?response_type=code&client_id=${clientId}` +
718+
`&redirect_uri=${encodeURIComponent(redirectUri)}` +
719+
`&scope=read%20write&state=xyz123`
720+
);
721+
722+
const authResponse = await oauthProvider.fetch(authRequest, mockEnv, mockCtx);
723+
const location = authResponse.headers.get('Location')!;
724+
const url = new URL(location);
725+
const code = url.searchParams.get('code')!;
726+
727+
// Now exchange the code and incorrectly provide a code_verifier
728+
const params = new URLSearchParams();
729+
params.append('grant_type', 'authorization_code');
730+
params.append('code', code);
731+
params.append('redirect_uri', redirectUri);
732+
params.append('client_id', clientId);
733+
params.append('client_secret', clientSecret);
734+
params.append('code_verifier', 'some_random_verifier_that_wasnt_used_in_auth');
735+
736+
const tokenRequest = createMockRequest(
737+
'https://example.com/oauth/token',
738+
'POST',
739+
{ 'Content-Type': 'application/x-www-form-urlencoded' },
740+
params.toString()
741+
);
742+
743+
const tokenResponse = await oauthProvider.fetch(tokenRequest, mockEnv, mockCtx);
744+
745+
// Should fail because code_verifier is provided but PKCE wasn't used in authorization
746+
expect(tokenResponse.status).toBe(400);
747+
const error = await tokenResponse.json();
748+
expect(error.error).toBe('invalid_request');
749+
expect(error.error_description).toBe('code_verifier provided for a flow that did not use PKCE');
750+
});
751+
697752
it('should accept the access token for API requests', async () => {
698753
// Get an auth code
699754
const authRequest = createMockRequest(
@@ -2047,6 +2102,135 @@ describe('OAuthProvider', () => {
20472102
});
20482103
});
20492104

2105+
describe('API Route Configuration', () => {
2106+
it('should support multi-handler configuration with apiHandlers', async () => {
2107+
// Create handler classes for different API routes
2108+
class UsersApiHandler extends WorkerEntrypoint {
2109+
fetch(request: Request) {
2110+
return new Response('Users API response', { status: 200 });
2111+
}
2112+
}
2113+
2114+
class DocumentsApiHandler extends WorkerEntrypoint {
2115+
fetch(request: Request) {
2116+
return new Response('Documents API response', { status: 200 });
2117+
}
2118+
}
2119+
2120+
// Create provider with multi-handler configuration
2121+
const providerWithMultiHandler = new OAuthProvider({
2122+
apiHandlers: {
2123+
'/api/users/': UsersApiHandler,
2124+
'/api/documents/': DocumentsApiHandler,
2125+
},
2126+
defaultHandler: testDefaultHandler,
2127+
authorizeEndpoint: '/authorize',
2128+
tokenEndpoint: '/oauth/token',
2129+
clientRegistrationEndpoint: '/oauth/register', // Important for registering clients in the test
2130+
scopesSupported: ['read', 'write'],
2131+
});
2132+
2133+
// Create a client and get an access token
2134+
const clientData = {
2135+
redirect_uris: ['https://client.example.com/callback'],
2136+
client_name: 'Test Client',
2137+
token_endpoint_auth_method: 'client_secret_basic',
2138+
};
2139+
2140+
const registerRequest = createMockRequest(
2141+
'https://example.com/oauth/register',
2142+
'POST',
2143+
{ 'Content-Type': 'application/json' },
2144+
JSON.stringify(clientData)
2145+
);
2146+
2147+
const registerResponse = await providerWithMultiHandler.fetch(registerRequest, mockEnv, mockCtx);
2148+
const client = await registerResponse.json();
2149+
const clientId = client.client_id;
2150+
const clientSecret = client.client_secret;
2151+
const redirectUri = 'https://client.example.com/callback';
2152+
2153+
// Get an auth code
2154+
const authRequest = createMockRequest(
2155+
`https://example.com/authorize?response_type=code&client_id=${clientId}` +
2156+
`&redirect_uri=${encodeURIComponent(redirectUri)}` +
2157+
`&scope=read%20write&state=xyz123`
2158+
);
2159+
2160+
const authResponse = await providerWithMultiHandler.fetch(authRequest, mockEnv, mockCtx);
2161+
const location = authResponse.headers.get('Location')!;
2162+
const code = new URL(location).searchParams.get('code')!;
2163+
2164+
// Exchange for tokens
2165+
const params = new URLSearchParams();
2166+
params.append('grant_type', 'authorization_code');
2167+
params.append('code', code);
2168+
params.append('redirect_uri', redirectUri);
2169+
params.append('client_id', clientId);
2170+
params.append('client_secret', clientSecret);
2171+
2172+
const tokenRequest = createMockRequest(
2173+
'https://example.com/oauth/token',
2174+
'POST',
2175+
{ 'Content-Type': 'application/x-www-form-urlencoded' },
2176+
params.toString()
2177+
);
2178+
2179+
const tokenResponse = await providerWithMultiHandler.fetch(tokenRequest, mockEnv, mockCtx);
2180+
const tokens = await tokenResponse.json();
2181+
const accessToken = tokens.access_token;
2182+
2183+
// Make requests to different API routes
2184+
const usersApiRequest = createMockRequest('https://example.com/api/users/profile', 'GET', {
2185+
Authorization: `Bearer ${accessToken}`,
2186+
});
2187+
2188+
const documentsApiRequest = createMockRequest('https://example.com/api/documents/list', 'GET', {
2189+
Authorization: `Bearer ${accessToken}`,
2190+
});
2191+
2192+
// Request to Users API should be handled by UsersApiHandler
2193+
const usersResponse = await providerWithMultiHandler.fetch(usersApiRequest, mockEnv, mockCtx);
2194+
expect(usersResponse.status).toBe(200);
2195+
expect(await usersResponse.text()).toBe('Users API response');
2196+
2197+
// Request to Documents API should be handled by DocumentsApiHandler
2198+
const documentsResponse = await providerWithMultiHandler.fetch(documentsApiRequest, mockEnv, mockCtx);
2199+
expect(documentsResponse.status).toBe(200);
2200+
expect(await documentsResponse.text()).toBe('Documents API response');
2201+
});
2202+
2203+
it('should throw an error when both single-handler and multi-handler configs are provided', () => {
2204+
expect(() => {
2205+
new OAuthProvider({
2206+
apiRoute: '/api/',
2207+
apiHandler: {
2208+
fetch: () => Promise.resolve(new Response()),
2209+
},
2210+
apiHandlers: {
2211+
'/api/users/': {
2212+
fetch: () => Promise.resolve(new Response()),
2213+
},
2214+
},
2215+
defaultHandler: testDefaultHandler,
2216+
authorizeEndpoint: '/authorize',
2217+
tokenEndpoint: '/oauth/token',
2218+
});
2219+
}).toThrow('Cannot use both apiRoute/apiHandler and apiHandlers');
2220+
});
2221+
2222+
it('should throw an error when neither single-handler nor multi-handler config is provided', () => {
2223+
expect(() => {
2224+
new OAuthProvider({
2225+
// Intentionally omitting apiRoute and apiHandler and apiHandlers
2226+
defaultHandler: testDefaultHandler,
2227+
authorizeEndpoint: '/authorize',
2228+
tokenEndpoint: '/oauth/token',
2229+
});
2230+
}).toThrow('Must provide either apiRoute + apiHandler OR apiHandlers');
2231+
});
2232+
});
2233+
20502234
describe('Token Revocation', () => {
20512235
let clientId: string;
20522236
let clientSecret: string;

0 commit comments

Comments
 (0)