coerce 'expires_in' to be a number (#1111)
Co-authored-by: Konstantin Konstantinov <KKonstantinov@users.noreply.github.com> Co-authored-by: Konstantin Konstantinov <konstantin@mach5technology.com>
This commit is contained in:
parent
6b90e1ada8
commit
b4c6090bf2
2 changed files with 41 additions and 3 deletions
|
|
@ -15,7 +15,7 @@ import {
|
|||
isHttpsUrl
|
||||
} from './auth.js';
|
||||
import { InvalidClientMetadataError, ServerError } from '../server/auth/errors.js';
|
||||
import { AuthorizationServerMetadata } from '../shared/auth.js';
|
||||
import { AuthorizationServerMetadata, OAuthTokens } from '../shared/auth.js';
|
||||
import { expect, vi, type Mock } from 'vitest';
|
||||
|
||||
// Mock pkce-challenge
|
||||
|
|
@ -1093,7 +1093,7 @@ describe('OAuth Authorization', () => {
|
|||
});
|
||||
|
||||
describe('exchangeAuthorization', () => {
|
||||
const validTokens = {
|
||||
const validTokens: OAuthTokens = {
|
||||
access_token: 'access123',
|
||||
token_type: 'Bearer',
|
||||
expires_in: 3600,
|
||||
|
|
@ -1154,6 +1154,44 @@ describe('OAuth Authorization', () => {
|
|||
expect(body.get('resource')).toBe('https://api.example.com/mcp-server');
|
||||
});
|
||||
|
||||
it('allows for string "expires_in" values', async () => {
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => ({ ...validTokens, expires_in: '3600' })
|
||||
});
|
||||
|
||||
const tokens = await exchangeAuthorization('https://auth.example.com', {
|
||||
clientInformation: validClientInfo,
|
||||
authorizationCode: 'code123',
|
||||
codeVerifier: 'verifier123',
|
||||
redirectUri: 'http://localhost:3000/callback',
|
||||
resource: new URL('https://api.example.com/mcp-server')
|
||||
});
|
||||
|
||||
expect(tokens).toEqual(validTokens);
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
href: 'https://auth.example.com/token'
|
||||
}),
|
||||
expect.objectContaining({
|
||||
method: 'POST'
|
||||
})
|
||||
);
|
||||
|
||||
const options = mockFetch.mock.calls[0][1];
|
||||
expect(options.headers).toBeInstanceOf(Headers);
|
||||
expect(options.headers.get('Content-Type')).toBe('application/x-www-form-urlencoded');
|
||||
|
||||
const body = options.body as URLSearchParams;
|
||||
expect(body.get('grant_type')).toBe('authorization_code');
|
||||
expect(body.get('code')).toBe('code123');
|
||||
expect(body.get('code_verifier')).toBe('verifier123');
|
||||
expect(body.get('client_id')).toBe('client123');
|
||||
expect(body.get('client_secret')).toBe('secret123');
|
||||
expect(body.get('redirect_uri')).toBe('http://localhost:3000/callback');
|
||||
expect(body.get('resource')).toBe('https://api.example.com/mcp-server');
|
||||
});
|
||||
it('exchanges code for tokens with auth', async () => {
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ export const OAuthTokensSchema = z
|
|||
access_token: z.string(),
|
||||
id_token: z.string().optional(), // Optional for OAuth 2.1, but necessary in OpenID Connect
|
||||
token_type: z.string(),
|
||||
expires_in: z.number().optional(),
|
||||
expires_in: z.coerce.number().optional(),
|
||||
scope: z.string().optional(),
|
||||
refresh_token: z.string().optional()
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in a new issue