Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/examples/server/demoInMemoryOAuthProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import express, { Request, Response } from "express";
import { AuthInfo } from '../../server/auth/types.js';
import { createOAuthMetadata, mcpAuthRouter } from '../../server/auth/router.js';
import { resourceUrlFromServerUrl } from '../../shared/auth-utils.js';
import { InvalidRequestError } from 'src/server/auth/errors.js';


export class DemoInMemoryClientsStore implements OAuthRegisteredClientsStore {
Expand Down Expand Up @@ -57,7 +58,10 @@ export class DemoInMemoryAuthProvider implements OAuthServerProvider {
params
});

const targetUrl = new URL(client.redirect_uris[0]);
if (!client.redirect_uris.includes(params.redirectUri)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a test for this?

Something like:

describe('DemoInMemoryAuthProvider', () => {
  let provider: DemoInMemoryAuthProvider;
  let mockResponse: Partial<Response>;
  let redirectUrl: string | undefined;

  beforeEach(() => {
    provider = new DemoInMemoryAuthProvider();
    redirectUrl = undefined;
    mockResponse = {
      redirect: jest.fn((url: string | number, arg?: string) => {
        if (typeof url === 'string') {
          redirectUrl = url;
        } else if (typeof arg === 'string') {
          redirectUrl = arg;
        }
      }) as any
    };
  });

  describe('authorize', () => {
    const validClient: OAuthClientInformationFull = {
      client_id: 'test-client',
      client_secret: 'test-secret',
      redirect_uris: [
        'https://example.com/callback',
        'https://example.com/callback2'
      ],
      scope: 'test-scope'
    };

    it('should redirect to the requested redirect_uri when valid', async () => {
      const params: AuthorizationParams = {
        redirectUri: 'https://example.com/callback',
        state: 'test-state',
        codeChallenge: 'test-challenge',
        scopes: ['test-scope']
      };

      await provider.authorize(validClient, params, mockResponse as Response);

      expect(mockResponse.redirect).toHaveBeenCalled();
      expect(redirectUrl).toBeDefined();
      
      const url = new URL(redirectUrl!);
      expect(url.origin + url.pathname).toBe('https://example.com/callback');
      expect(url.searchParams.get('state')).toBe('test-state');
      expect(url.searchParams.has('code')).toBe(true);
    });

    it('should redirect to second valid redirect_uri', async () => {
      const params: AuthorizationParams = {
        redirectUri: 'https://example.com/callback2',
        state: 'test-state',
        codeChallenge: 'test-challenge',
        scopes: ['test-scope']
      };

      await provider.authorize(validClient, params, mockResponse as Response);

      expect(mockResponse.redirect).toHaveBeenCalled();
      expect(redirectUrl).toBeDefined();
      
      const url = new URL(redirectUrl!);
      expect(url.origin + url.pathname).toBe('https://example.com/callback2');
      expect(url.searchParams.get('state')).toBe('test-state');
      expect(url.searchParams.has('code')).toBe(true);
    });

    it('should throw InvalidRequestError for unregistered redirect_uri', async () => {
      const params: AuthorizationParams = {
        redirectUri: 'https://evil.com/callback',
        state: 'test-state',
        codeChallenge: 'test-challenge',
        scopes: ['test-scope']
      };

      await expect(
        provider.authorize(validClient, params, mockResponse as Response)
      ).rejects.toThrow(InvalidRequestError);

      await expect(
        provider.authorize(validClient, params, mockResponse as Response)
      ).rejects.toThrow('Unregistered redirect_uri');

      expect(mockResponse.redirect).not.toHaveBeenCalled();
    });
...

Copy link
Contributor Author

@TylerLeonhardt TylerLeonhardt Sep 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok added a bunch of test for this sample oauth server. There were none to begin with, feel free to modify them as you wish.

throw new InvalidRequestError("Unregistered redirect_uri");
}
const targetUrl = new URL(params.redirectUri);
targetUrl.search = searchParams.toString();
res.redirect(targetUrl.toString());
}
Expand Down