Skip to content

Commit c2258d0

Browse files
authored
Merge pull request #22 from cloudflare/glen/onerror-callback
Added onError callback
2 parents f69d056 + 5dde126 commit c2258d0

File tree

4 files changed

+216
-64
lines changed

4 files changed

+216
-64
lines changed

README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,35 @@ The `accessTokenTTL` override is particularly useful when the application is als
262262

263263
The `props` values are end-to-end encrypted, so they can safely contain sensitive information.
264264

265+
## Custom Error Responses
266+
267+
By using the `onError` option, you can emit notifications or take other actions when an error response was to be emitted:
268+
269+
```ts
270+
new OAuthProvider({
271+
// ... other options ...
272+
onError({ code, description, status, headers }) {
273+
Sentry.captureMessage(/* ... */)
274+
}
275+
})
276+
```
277+
278+
By returning a `Response` you can also override what the OAuthProvider returns to your users:
279+
280+
```ts
281+
new OAuthProvider({
282+
// ... other options ...
283+
onError({ code, description, status, headers }) {
284+
if (code === 'unsupported_grant_type') {
285+
return new Response('...', { status, headers })
286+
}
287+
// returning undefined (i.e. void) uses the default Response generation
288+
}
289+
})
290+
```
291+
292+
By default, the `onError` callback is set to ``({ status, code, description }) => console.warn(`OAuth error response: ${status} ${code} - ${description}`)``.
293+
265294
## Implementation Notes
266295

267296
### End-to-end encryption

__tests__/oauth-provider.test.ts

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1856,6 +1856,108 @@ describe('OAuthProvider', () => {
18561856
});
18571857
});
18581858

1859+
describe('Error Handling with onError Callback', () => {
1860+
it('should use the default onError callback that logs a warning', async () => {
1861+
// Spy on console.warn to check default behavior
1862+
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
1863+
1864+
// Create a request that will trigger an error
1865+
const invalidTokenRequest = createMockRequest('https://example.com/api/test', 'GET', {
1866+
Authorization: 'Bearer invalid-token',
1867+
});
1868+
1869+
const response = await oauthProvider.fetch(invalidTokenRequest, mockEnv, mockCtx);
1870+
1871+
// Verify the error response
1872+
expect(response.status).toBe(401);
1873+
const error = await response.json();
1874+
expect(error.error).toBe('invalid_token');
1875+
1876+
// Verify the default onError callback was triggered and logged a warning
1877+
expect(consoleWarnSpy).toHaveBeenCalledWith(expect.stringContaining('OAuth error response: 401 invalid_token'));
1878+
1879+
// Restore the spy
1880+
consoleWarnSpy.mockRestore();
1881+
});
1882+
1883+
it('should allow custom onError callback to modify the error response', async () => {
1884+
// Create a provider with custom onError callback
1885+
const customErrorProvider = new OAuthProvider({
1886+
apiRoute: ['/api/'],
1887+
apiHandler: TestApiHandler,
1888+
defaultHandler: testDefaultHandler,
1889+
authorizeEndpoint: '/authorize',
1890+
tokenEndpoint: '/oauth/token',
1891+
scopesSupported: ['read', 'write'],
1892+
onError: ({ code, description, status }) => {
1893+
// Return a completely different response
1894+
return new Response(
1895+
JSON.stringify({
1896+
custom_error: true,
1897+
original_code: code,
1898+
custom_message: `Custom error handler: ${description}`,
1899+
}),
1900+
{
1901+
status,
1902+
headers: {
1903+
'Content-Type': 'application/json',
1904+
'X-Custom-Error': 'true',
1905+
},
1906+
}
1907+
);
1908+
},
1909+
});
1910+
1911+
// Create a request that will trigger an error
1912+
const invalidTokenRequest = createMockRequest('https://example.com/api/test', 'GET', {
1913+
Authorization: 'Bearer invalid-token',
1914+
});
1915+
1916+
const response = await customErrorProvider.fetch(invalidTokenRequest, mockEnv, mockCtx);
1917+
1918+
// Verify the custom error response
1919+
expect(response.status).toBe(401); // Status should be preserved
1920+
expect(response.headers.get('X-Custom-Error')).toBe('true');
1921+
1922+
const error = await response.json();
1923+
expect(error.custom_error).toBe(true);
1924+
expect(error.original_code).toBe('invalid_token');
1925+
expect(error.custom_message).toContain('Custom error handler');
1926+
});
1927+
1928+
it('should use standard error response when onError returns void', async () => {
1929+
// Create a provider with a callback that performs a side effect but doesn't return a response
1930+
let callbackInvoked = false;
1931+
const sideEffectProvider = new OAuthProvider({
1932+
apiRoute: ['/api/'],
1933+
apiHandler: TestApiHandler,
1934+
defaultHandler: testDefaultHandler,
1935+
authorizeEndpoint: '/authorize',
1936+
tokenEndpoint: '/oauth/token',
1937+
scopesSupported: ['read', 'write'],
1938+
onError: () => {
1939+
callbackInvoked = true;
1940+
// No return - should use standard error response
1941+
},
1942+
});
1943+
1944+
// Create a request that will trigger an error
1945+
const invalidRequest = createMockRequest('https://example.com/oauth/token', 'POST', {
1946+
'Content-Type': 'application/x-www-form-urlencoded',
1947+
});
1948+
1949+
const response = await sideEffectProvider.fetch(invalidRequest, mockEnv, mockCtx);
1950+
1951+
// Verify the standard error response
1952+
expect(response.status).toBe(401);
1953+
const error = await response.json();
1954+
expect(error.error).toBe('invalid_client');
1955+
1956+
// Verify callback was invoked
1957+
expect(callbackInvoked).toBe(true);
1958+
});
1959+
});
1960+
18591961
describe('OAuthHelpers', () => {
18601962
it('should allow listing and revoking grants', async () => {
18611963
// Create a client

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@cloudflare/workers-oauth-provider",
3-
"version": "0.0.2",
3+
"version": "0.0.3",
44
"description": "OAuth provider for Cloudflare Workers",
55
"main": "dist/oauth-provider.js",
66
"types": "dist/oauth-provider.d.ts",
@@ -15,7 +15,9 @@
1515
},
1616
"scripts": {
1717
"build": "tsup",
18+
"build:watch": "tsup --watch",
1819
"test": "vitest run",
20+
"test:watch": "vitest",
1921
"prepublishOnly": "npm run build",
2022
"prettier": "prettier -w ."
2123
},

0 commit comments

Comments
 (0)