Skip to content
Merged
Show file tree
Hide file tree
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
2 changes: 0 additions & 2 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,6 @@
"@tanstack/react-query": "^5.90.21",
"@tanstack/react-table": "^8.21.3",
"@tldraw/sync": "^4.4.1",
"@upstash/qstash": "^2.9.0",
"@upstash/ratelimit": "^2.0.8",
"@vercel/analytics": "^1.6.1",
"@vercel/edge": "^1.2.2",
"@vercel/kv": "^3.0.0",
Expand Down
15 changes: 12 additions & 3 deletions apps/web/src/proxy.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { match } from '@formatjs/intl-localematcher';
import { updateSession } from '@ncthub/supabase/next/proxy';
import type { SupabaseUser } from '@ncthub/supabase/next/user';
import { guardApiProxyRequest } from '@ncthub/utils/api-proxy-guard';
import Negotiator from 'negotiator';
import type { NextRequest } from 'next/server';
import { NextResponse } from 'next/server';
Expand All @@ -9,12 +10,20 @@ import { LOCALE_COOKIE_NAME, PUBLIC_PATHS } from './constants/common';
import { defaultLocale, type Locale, supportedLocales } from './i18n/routing';

export async function proxy(req: NextRequest): Promise<NextResponse> {
if (req.nextUrl.pathname.startsWith('/api')) {
const guardResponse = await guardApiProxyRequest(req, {
prefixBase: 'proxy:web:api',
});
if (guardResponse) {
return guardResponse;
}

return NextResponse.next();
}

// Make sure user session is always refreshed
const { res, user } = await updateSession(req);

// If current path starts with /api, return without redirecting
if (req.nextUrl.pathname.startsWith('/api')) return res;

// Handle special cases for public paths
const { res: nextRes, redirect } = handleRedirect({ req, res, user });
if (redirect) return nextRes;
Expand Down
20 changes: 8 additions & 12 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions packages/utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
"packageManager": "[email protected]",
"dependencies": {
"@ncthub/supabase": "workspace:*",
"@upstash/ratelimit": "^2.0.8",
"@upstash/redis": "^1.37.0",
"clsx": "^2.1.1",
"next": "16.1.6",
"react": "^19.2.4",
Expand Down
29 changes: 29 additions & 0 deletions packages/utils/src/abuse-protection/__tests__/edge.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { describe, expect, it } from 'vitest';
import { extractIPFromRequest } from '../edge';

describe('abuse-protection edge', () => {
describe('extractIPFromRequest', () => {
it('prefers cf-connecting-ip over x-forwarded-for', () => {
const headers = new Headers();
headers.set('cf-connecting-ip', '203.0.113.10');
headers.set('x-forwarded-for', '198.51.100.10, 10.0.0.1');

expect(extractIPFromRequest(headers)).toBe('203.0.113.10');
});

it('falls back to true-client-ip before generic proxy headers', () => {
const headers = new Headers();
headers.set('true-client-ip', '203.0.113.20');
headers.set('x-forwarded-for', '198.51.100.20, 10.0.0.1');

expect(extractIPFromRequest(headers)).toBe('203.0.113.20');
});

it('falls back to x-forwarded-for when cloud proxy headers are absent', () => {
const headers = new Headers();
headers.set('x-forwarded-for', '198.51.100.30, 10.0.0.1');

expect(extractIPFromRequest(headers)).toBe('198.51.100.30');
});
});
});
Loading
Loading