Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
51 changes: 36 additions & 15 deletions __tests__/server/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,9 @@ import {
ConfigurationError,
InsufficientScopeError
} from '../../src/errors/index.js';
import { getInstance } from '../../src/server/utils.js';
import { ApiClient } from '@auth0/auth0-api-js';
import type { JWTClaims } from '../../src/types/index.js';

vi.mock('../../src/server/utils.js', () => ({
getInstance: vi.fn(() => ({
config: { domain: 'test.auth0.com', audience: 'https://api.example.com' }
}))
}));

const mockVerifyAccessToken = vi.hoisted(() => vi.fn());

vi.mock('@auth0/auth0-api-js', () => {
Expand Down Expand Up @@ -189,19 +182,43 @@ describe('requireClaims — scope check', () => {
});
});

// ─── audience guard (no _setVerifyJwt stub) ───────────────────────────────────
// ─── config guards (no _setVerifyJwt stub) ────────────────────────────────────

describe('config guard — missing AUTH0_DOMAIN', () => {
beforeEach(() => {
delete process.env['AUTH0_DOMAIN'];
process.env['AUTH0_AUDIENCE'] = 'https://api.example.com';
});

afterEach(() => {
delete process.env['AUTH0_DOMAIN'];
delete process.env['AUTH0_AUDIENCE'];
_resetApiClient();
});

it('getClaims throws ConfigurationError when AUTH0_DOMAIN is not set', async () => {
await expect(getClaims(makeRequest('Bearer some-token'))).rejects.toThrow(
ConfigurationError
);
});

it('requireClaims throws ConfigurationError when AUTH0_DOMAIN is not set', async () => {
await expect(
requireClaims(makeRequest('Bearer some-token'))
).rejects.toThrow(ConfigurationError);
});
});

describe('audience guard — missing AUTH0_AUDIENCE', () => {
describe('config guard — missing AUTH0_AUDIENCE', () => {
beforeEach(() => {
vi.mocked(getInstance).mockReturnValue({
config: { domain: 'test.auth0.com', audience: undefined }
} as ReturnType<typeof getInstance>);
process.env['AUTH0_DOMAIN'] = 'test.auth0.com';
delete process.env['AUTH0_AUDIENCE'];
});

afterEach(() => {
vi.mocked(getInstance).mockReturnValue({
config: { domain: 'test.auth0.com', audience: 'https://api.example.com' }
} as ReturnType<typeof getInstance>);
delete process.env['AUTH0_DOMAIN'];
delete process.env['AUTH0_AUDIENCE'];
_resetApiClient();
});

it('getClaims throws ConfigurationError when AUTH0_AUDIENCE is not set', async () => {
Expand All @@ -223,10 +240,14 @@ describe('audience guard — missing AUTH0_AUDIENCE', () => {

describe('ApiClient integration — real verifyJwt path', () => {
beforeEach(() => {
process.env['AUTH0_DOMAIN'] = 'test.auth0.com';
process.env['AUTH0_AUDIENCE'] = 'https://api.example.com';
mockVerifyAccessToken.mockResolvedValue(CLAIMS);
});

afterEach(() => {
delete process.env['AUTH0_DOMAIN'];
delete process.env['AUTH0_AUDIENCE'];
_resetApiClient();
vi.mocked(ApiClient).mockClear();
mockVerifyAccessToken.mockReset();
Expand Down
20 changes: 12 additions & 8 deletions src/server/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {
ConfigurationError,
InsufficientScopeError
} from '../errors/index.js';
import { getInstance } from './utils.js';
import type { JWTClaims } from '../types/index.js';

// ─── Test injection ───────────────────────────────────────────────────────────
Expand Down Expand Up @@ -34,18 +33,23 @@ function extractBearerToken(request: Request): string | null {
async function verifyJwt(token: string): Promise<JWTClaims> {
if (_verifyJwtFn) return _verifyJwtFn(token);

const auth0 = getInstance();
if (!auth0.config.audience) {
const domain = process.env['AUTH0_DOMAIN'];
const audience = process.env['AUTH0_AUDIENCE'];

if (!domain) {
throw new ConfigurationError(
'AUTH0_DOMAIN is required for Bearer token verification. ' +
'Set it as an environment variable.'
);
}
if (!audience) {
throw new ConfigurationError(
'AUTH0_AUDIENCE is required for Bearer token verification. ' +
'Set it as an environment variable or pass it to new Auth0Server({ audience: "..." }).'
'Set it as an environment variable.'
);
}
if (!_apiClient) {
_apiClient = new ApiClient({
domain: auth0.config.domain,
audience: auth0.config.audience
});
_apiClient = new ApiClient({ domain, audience });
}
const claims = await _apiClient.verifyAccessToken({ accessToken: token });
return claims as unknown as JWTClaims;
Expand Down