Skip to content

Commit 0c8d1d7

Browse files
committed
Resolves issue-#7437
1 parent ded12c8 commit 0c8d1d7

6 files changed

Lines changed: 229 additions & 65 deletions

File tree

app/api/architecture/route.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,10 @@ import pLimit from 'p-limit';
99
import { cloneGitHubRepository } from '@/lib/git-clone';
1010
import { getGitHubTokens } from '@/lib/github';
1111
import { formatRepoRefForLogging, sanitizeErrorForLogging } from '@/lib/sanitize-git-credentials';
12-
import { auth } from '@/auth';
1312
import { getClientIp } from '@/utils/getClientIp';
1413

1514
const execFilePromise = promisify(execFile);
1615

17-
const REST_TIMEOUT_MS = 5000; // 5s timeout for external API requests
18-
1916
// Per-IP concurrent clone tracking (max 3 concurrent clones per IP)
2017
const MAX_CONCURRENT_CLONES_PER_IP = 3;
2118
const MAX_TEMP_DIR_SIZE_BYTES = 500 * 1024 * 1024; // 500MB
@@ -310,12 +307,6 @@ export async function POST(req: NextRequest) {
310307
let tempDir = '';
311308
const ip = getClientIp(req);
312309

313-
// Require authenticated session
314-
const session = await auth();
315-
if (!session?.user) {
316-
return NextResponse.json({ error: 'Authentication required' }, { status: 401 });
317-
}
318-
319310
// Check concurrent clone limit per IP
320311
if (!incrementClones(ip)) {
321312
return NextResponse.json(

app/api/enterprise/route.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { NextRequest, NextResponse } from 'next/server';
22
import { aggregateTeamData } from '@/lib/analytics/teamHealth';
3-
import { requireEnterpriseAdmin } from '@/lib/enterprise-auth';
43
import type { TeamMember } from '@/types/enterprise';
54
import type { ContributionCalendar } from '@/types';
65

@@ -35,9 +34,6 @@ function createMockTeamMembers(usernames: string[]): TeamMember[] {
3534
}
3635

3736
export async function GET(request: NextRequest) {
38-
const { error } = await requireEnterpriseAdmin();
39-
if (error) return error;
40-
4137
const searchParams = request.nextUrl.searchParams;
4238
const teamId = searchParams.get('teamId') || 'default-team';
4339
const teamName = searchParams.get('teamName') || 'Engineering Team';
@@ -74,9 +70,6 @@ export async function GET(request: NextRequest) {
7470
}
7571

7672
export async function POST(request: NextRequest) {
77-
const { error } = await requireEnterpriseAdmin();
78-
if (error) return error;
79-
8073
let body: Record<string, unknown>;
8174
try {
8275
body = await request.json();

app/api/enterprise/teams/[teamId]/route.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
11
import { NextRequest, NextResponse } from 'next/server';
2-
import { requireEnterpriseAdmin } from '@/lib/enterprise-auth';
32

43
export async function PATCH(
54
_request: NextRequest,
65
{ params }: { params: Promise<{ teamId: string }> }
76
) {
8-
const { error } = await requireEnterpriseAdmin();
9-
if (error) return error;
10-
117
const { teamId } = await params;
128

139
if (!teamId) {
@@ -24,9 +20,6 @@ export async function DELETE(
2420
_request: NextRequest,
2521
{ params }: { params: Promise<{ teamId: string }> }
2622
) {
27-
const { error } = await requireEnterpriseAdmin();
28-
if (error) return error;
29-
3023
const { teamId } = await params;
3124

3225
if (!teamId) {

app/api/enterprise/teams/route.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
11
import { NextRequest, NextResponse } from 'next/server';
2-
import { requireEnterpriseAdmin } from '@/lib/enterprise-auth';
32

43
const mockTeams: Record<string, { id: string; name: string; members: string[] }> = {};
54

65
export async function GET() {
7-
const { error } = await requireEnterpriseAdmin();
8-
if (error) return error;
9-
106
const teams = Object.values(mockTeams);
117

128
return NextResponse.json({
@@ -16,9 +12,6 @@ export async function GET() {
1612
}
1713

1814
export async function POST(request: NextRequest) {
19-
const { error } = await requireEnterpriseAdmin();
20-
if (error) return error;
21-
2215
let body: { name?: string; members?: string[] };
2316
try {
2417
body = await request.json();

middleware.auth.test.ts

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2+
import { NextRequest, NextResponse } from 'next/server';
3+
import { middleware } from './middleware';
4+
import { auth } from './auth';
5+
import type { Session } from 'next-auth';
6+
7+
vi.mock('./lib/rate-limit', () => ({
8+
rateLimit: vi.fn().mockResolvedValue({
9+
success: true,
10+
limit: 60,
11+
remaining: 59,
12+
reset: 123456789,
13+
}),
14+
getRateLimitHeaders: vi.fn(() => ({})),
15+
}));
16+
17+
vi.mock('./auth', () => ({
18+
auth: vi.fn(),
19+
}));
20+
21+
describe('middleware auth and authorization', () => {
22+
beforeEach(() => {
23+
vi.clearAllMocks();
24+
vi.stubEnv('ENTERPRISE_ADMIN_GITHUB_IDS', '');
25+
});
26+
27+
afterEach(() => {
28+
vi.unstubAllGlobals();
29+
});
30+
31+
it('unauthenticated request to /api/enterprise/teams returns 401', async () => {
32+
vi.mocked(auth).mockResolvedValue(null);
33+
34+
const request = new NextRequest('http://localhost:3000/api/enterprise/teams');
35+
const response = await middleware(request);
36+
37+
expect(response.status).toBe(401);
38+
const body = await response.json();
39+
expect(body).toEqual({ error: 'Authentication required' });
40+
expect(response.headers.get('X-Frame-Options')).toBe('DENY');
41+
});
42+
43+
it('authenticated request to /api/enterprise/teams returns 503 if ENTERPRISE_ADMIN_GITHUB_IDS is empty', async () => {
44+
vi.mocked(auth).mockResolvedValue({ user: { id: 'user123' } } as unknown as Session);
45+
vi.stubEnv('ENTERPRISE_ADMIN_GITHUB_IDS', '');
46+
47+
const request = new NextRequest('http://localhost:3000/api/enterprise/teams');
48+
const response = await middleware(request);
49+
50+
expect(response.status).toBe(503);
51+
const body = await response.json();
52+
expect(body).toEqual({ error: 'Enterprise admin access not configured' });
53+
});
54+
55+
it('authenticated request to /api/enterprise/teams returns 403 if user is not an enterprise admin', async () => {
56+
vi.mocked(auth).mockResolvedValue({ user: { id: 'user123' } } as unknown as Session);
57+
vi.stubEnv('ENTERPRISE_ADMIN_GITHUB_IDS', 'admin1,admin2');
58+
59+
const request = new NextRequest('http://localhost:3000/api/enterprise/teams');
60+
const response = await middleware(request);
61+
62+
expect(response.status).toBe(403);
63+
const body = await response.json();
64+
expect(body).toEqual({ error: 'Forbidden: enterprise admin access required' });
65+
});
66+
67+
it('authenticated request to /api/enterprise/teams passes if user is an enterprise admin', async () => {
68+
vi.mocked(auth).mockResolvedValue({ user: { id: 'admin1' } } as unknown as Session);
69+
vi.stubEnv('ENTERPRISE_ADMIN_GITHUB_IDS', 'admin1,admin2');
70+
const nextSpy = vi.spyOn(NextResponse, 'next');
71+
72+
const request = new NextRequest('http://localhost:3000/api/enterprise/teams');
73+
const response = await middleware(request);
74+
75+
expect(nextSpy).toHaveBeenCalled();
76+
expect(response.headers.get('X-Frame-Options')).toBe('DENY');
77+
});
78+
79+
it('unauthenticated request to /api/architecture returns 401', async () => {
80+
vi.mocked(auth).mockResolvedValue(null);
81+
82+
const request = new NextRequest('http://localhost:3000/api/architecture');
83+
const response = await middleware(request);
84+
85+
expect(response.status).toBe(401);
86+
});
87+
88+
it('authenticated request to /api/architecture passes', async () => {
89+
vi.mocked(auth).mockResolvedValue({ user: { id: 'user123' } } as unknown as Session);
90+
const nextSpy = vi.spyOn(NextResponse, 'next');
91+
92+
const request = new NextRequest('http://localhost:3000/api/architecture');
93+
await middleware(request);
94+
95+
expect(nextSpy).toHaveBeenCalled();
96+
});
97+
98+
it('unauthenticated request to /api/streak passes', async () => {
99+
vi.mocked(auth).mockResolvedValue(null);
100+
const nextSpy = vi.spyOn(NextResponse, 'next');
101+
102+
const request = new NextRequest('http://localhost:3000/api/streak?user=octocat');
103+
await middleware(request);
104+
105+
expect(nextSpy).toHaveBeenCalled();
106+
expect(auth).not.toHaveBeenCalled();
107+
});
108+
});

0 commit comments

Comments
 (0)