fix: cors
This commit is contained in:
@@ -1,12 +1,25 @@
|
|||||||
import { NextResponse } from 'next/server';
|
import cors from '@/lib/cors';
|
||||||
|
|
||||||
import { requestHandler } from '@/app/request-handler';
|
export async function GET(request: Request) {
|
||||||
|
|
||||||
export const GET = requestHandler(async () => {
|
|
||||||
const res = await fetch('https://api.github.com/repos/documenso/documenso');
|
const res = await fetch('https://api.github.com/repos/documenso/documenso');
|
||||||
const { forks_count } = await res.json();
|
const { forks_count } = await res.json();
|
||||||
|
|
||||||
return NextResponse.json({
|
return cors(
|
||||||
data: forks_count,
|
request,
|
||||||
});
|
new Response(JSON.stringify({ data: forks_count }), {
|
||||||
});
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'content-type': 'application/json',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function OPTIONS(request: Request) {
|
||||||
|
return cors(
|
||||||
|
request,
|
||||||
|
new Response(null, {
|
||||||
|
status: 204,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,14 +1,27 @@
|
|||||||
import { NextResponse } from 'next/server';
|
import cors from '@/lib/cors';
|
||||||
|
|
||||||
import { requestHandler } from '@/app/request-handler';
|
export async function GET(request: Request) {
|
||||||
|
|
||||||
export const GET = requestHandler(async () => {
|
|
||||||
const res = await fetch(
|
const res = await fetch(
|
||||||
'https://api.github.com/search/issues?q=repo:documenso/documenso+type:issue+state:open&page=0&per_page=1',
|
'https://api.github.com/search/issues?q=repo:documenso/documenso+type:issue+state:open&page=0&per_page=1',
|
||||||
);
|
);
|
||||||
const { total_count } = await res.json();
|
const { total_count } = await res.json();
|
||||||
|
|
||||||
return NextResponse.json({
|
return cors(
|
||||||
data: total_count,
|
request,
|
||||||
});
|
new Response(JSON.stringify({ data: total_count }), {
|
||||||
});
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'content-type': 'application/json',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function OPTIONS(request: Request) {
|
||||||
|
return cors(
|
||||||
|
request,
|
||||||
|
new Response(null, {
|
||||||
|
status: 204,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,14 +1,27 @@
|
|||||||
import { NextResponse } from 'next/server';
|
import cors from '@/lib/cors';
|
||||||
|
|
||||||
import { requestHandler } from '@/app/request-handler';
|
export async function GET(request: Request) {
|
||||||
|
|
||||||
export const GET = requestHandler(async () => {
|
|
||||||
const res = await fetch(
|
const res = await fetch(
|
||||||
'https://api.github.com/search/issues?q=repo:documenso/documenso/+is:pr+merged:>=2010-01-01&page=0&per_page=1',
|
'https://api.github.com/search/issues?q=repo:documenso/documenso/+is:pr+merged:>=2010-01-01&page=0&per_page=1',
|
||||||
);
|
);
|
||||||
const { total_count } = await res.json();
|
const { total_count } = await res.json();
|
||||||
|
|
||||||
return NextResponse.json({
|
return cors(
|
||||||
data: total_count,
|
request,
|
||||||
});
|
new Response(JSON.stringify({ data: total_count }), {
|
||||||
});
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'content-type': 'application/json',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function OPTIONS(request: Request) {
|
||||||
|
return cors(
|
||||||
|
request,
|
||||||
|
new Response(null, {
|
||||||
|
status: 204,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import { type NextRequest, NextResponse } from 'next/server';
|
import type { NextRequest } from 'next/server';
|
||||||
|
|
||||||
|
import cors from '@/lib/cors';
|
||||||
|
|
||||||
const paths = [
|
const paths = [
|
||||||
{ path: '/forks', description: 'GitHub Forks' },
|
{ path: '/forks', description: 'GitHub Forks' },
|
||||||
@@ -13,5 +15,22 @@ export function GET(request: NextRequest) {
|
|||||||
return { path: url + path, description };
|
return { path: url + path, description };
|
||||||
});
|
});
|
||||||
|
|
||||||
return NextResponse.json(apis);
|
return cors(
|
||||||
|
request,
|
||||||
|
new Response(JSON.stringify(apis), {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'content-type': 'application/json',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function OPTIONS(request: Request) {
|
||||||
|
return cors(
|
||||||
|
request,
|
||||||
|
new Response(null, {
|
||||||
|
status: 204,
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,25 @@
|
|||||||
import { NextResponse } from 'next/server';
|
import cors from '@/lib/cors';
|
||||||
|
|
||||||
import { requestHandler } from '@/app/request-handler';
|
export async function GET(request: Request) {
|
||||||
|
|
||||||
export const GET = requestHandler(async () => {
|
|
||||||
const res = await fetch('https://api.github.com/repos/documenso/documenso');
|
const res = await fetch('https://api.github.com/repos/documenso/documenso');
|
||||||
const { stargazers_count } = await res.json();
|
const { stargazers_count } = await res.json();
|
||||||
|
|
||||||
return NextResponse.json({
|
return cors(
|
||||||
data: stargazers_count,
|
request,
|
||||||
});
|
new Response(JSON.stringify({ data: stargazers_count }), {
|
||||||
});
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'content-type': 'application/json',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function OPTIONS(request: Request) {
|
||||||
|
return cors(
|
||||||
|
request,
|
||||||
|
new Response(null, {
|
||||||
|
status: 204,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,76 +0,0 @@
|
|||||||
import type { NextRequest } from 'next/server';
|
|
||||||
import { NextResponse } from 'next/server';
|
|
||||||
|
|
||||||
type RouteHandler<T = Record<string, string | string[]>> = (
|
|
||||||
req: NextRequest,
|
|
||||||
ctx: { params: T },
|
|
||||||
) => Promise<Response> | Response;
|
|
||||||
|
|
||||||
const ALLOWED_ORIGINS = new Set(['documenso.com', 'prd-openpage-api.vercel.app']);
|
|
||||||
|
|
||||||
const CORS_HEADERS = {
|
|
||||||
'Access-Control-Allow-Origin': '*',
|
|
||||||
'Access-Control-Allow-Methods': 'GET, PUT, DELETE, OPTIONS',
|
|
||||||
'Access-Control-Allow-Headers': 'Content-Type',
|
|
||||||
};
|
|
||||||
|
|
||||||
function isAllowedOrigin(req: NextRequest): boolean {
|
|
||||||
const referer = req.headers.get('referer');
|
|
||||||
const host = req.headers.get('host');
|
|
||||||
|
|
||||||
if (host?.includes('localhost')) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!referer || !host) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const refererUrl = new URL(referer);
|
|
||||||
const hostUrl = new URL(`http://${host}`);
|
|
||||||
|
|
||||||
const isRefererAllowed = ALLOWED_ORIGINS.has(refererUrl.host);
|
|
||||||
const isHostAllowed = ALLOWED_ORIGINS.has(hostUrl.host);
|
|
||||||
|
|
||||||
return isRefererAllowed || isHostAllowed;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error parsing URLs:', error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function requestHandler<T = Record<string, string | string[]>>(
|
|
||||||
handler: RouteHandler<T>,
|
|
||||||
): RouteHandler<T> {
|
|
||||||
return async (req: NextRequest, ctx: { params: T }) => {
|
|
||||||
try {
|
|
||||||
// if (!isAllowedOrigin(req)) {
|
|
||||||
// return NextResponse.json(
|
|
||||||
// { error: 'Forbidden' },
|
|
||||||
// {
|
|
||||||
// status: 403,
|
|
||||||
// headers: CORS_HEADERS,
|
|
||||||
// },
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
const response = await handler(req, ctx);
|
|
||||||
|
|
||||||
Object.entries(CORS_HEADERS).forEach(([key, value]) => {
|
|
||||||
response.headers.set(key, value);
|
|
||||||
});
|
|
||||||
|
|
||||||
return response;
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: 'Internal Server Error' },
|
|
||||||
{
|
|
||||||
status: 500,
|
|
||||||
headers: CORS_HEADERS,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
import { type NextRequest, NextResponse } from 'next/server';
|
import type { NextRequest } from 'next/server';
|
||||||
|
|
||||||
|
import cors from '@/lib/cors';
|
||||||
|
|
||||||
const paths = [{ path: 'github', description: 'GitHub Data' }];
|
const paths = [{ path: 'github', description: 'GitHub Data' }];
|
||||||
|
|
||||||
@@ -8,12 +10,22 @@ export function GET(request: NextRequest) {
|
|||||||
return { path: url + path, description };
|
return { path: url + path, description };
|
||||||
});
|
});
|
||||||
|
|
||||||
return NextResponse.json(apis, {
|
return cors(
|
||||||
headers: {
|
request,
|
||||||
// TODO: Update for marketing page
|
new Response(JSON.stringify(apis), {
|
||||||
'Access-Control-Allow-Origin': '*',
|
status: 200,
|
||||||
'Access-Control-Allow-Methods': 'GET, PUT, DELETE, OPTIONS',
|
headers: {
|
||||||
'Access-Control-Allow-Headers': 'Content-Type',
|
'content-type': 'application/json',
|
||||||
},
|
},
|
||||||
});
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function OPTIONS(request: Request) {
|
||||||
|
return cors(
|
||||||
|
request,
|
||||||
|
new Response(null, {
|
||||||
|
status: 204,
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
138
apps/openpage-api/lib/cors.ts
Normal file
138
apps/openpage-api/lib/cors.ts
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
/**
|
||||||
|
* Multi purpose CORS lib.
|
||||||
|
* Note: Based on the `cors` package in npm but using only web APIs.
|
||||||
|
* Taken from: https://github.com/vercel/examples/blob/main/edge-functions/cors/lib/cors.ts
|
||||||
|
*/
|
||||||
|
|
||||||
|
type StaticOrigin = boolean | string | RegExp | (boolean | string | RegExp)[];
|
||||||
|
|
||||||
|
type OriginFn = (origin: string | undefined, req: Request) => StaticOrigin | Promise<StaticOrigin>;
|
||||||
|
|
||||||
|
interface CorsOptions {
|
||||||
|
origin?: StaticOrigin | OriginFn;
|
||||||
|
methods?: string | string[];
|
||||||
|
allowedHeaders?: string | string[];
|
||||||
|
exposedHeaders?: string | string[];
|
||||||
|
credentials?: boolean;
|
||||||
|
maxAge?: number;
|
||||||
|
preflightContinue?: boolean;
|
||||||
|
optionsSuccessStatus?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultOptions: CorsOptions = {
|
||||||
|
origin: '*',
|
||||||
|
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
|
||||||
|
preflightContinue: false,
|
||||||
|
optionsSuccessStatus: 204,
|
||||||
|
};
|
||||||
|
|
||||||
|
function isOriginAllowed(origin: string, allowed: StaticOrigin): boolean {
|
||||||
|
return Array.isArray(allowed)
|
||||||
|
? allowed.some((o) => isOriginAllowed(origin, o))
|
||||||
|
: typeof allowed === 'string'
|
||||||
|
? origin === allowed
|
||||||
|
: allowed instanceof RegExp
|
||||||
|
? allowed.test(origin)
|
||||||
|
: !!allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOriginHeaders(reqOrigin: string | undefined, origin: StaticOrigin) {
|
||||||
|
const headers = new Headers();
|
||||||
|
|
||||||
|
if (origin === '*') {
|
||||||
|
// Allow any origin
|
||||||
|
headers.set('Access-Control-Allow-Origin', '*');
|
||||||
|
} else if (typeof origin === 'string') {
|
||||||
|
// Fixed origin
|
||||||
|
headers.set('Access-Control-Allow-Origin', origin);
|
||||||
|
headers.append('Vary', 'Origin');
|
||||||
|
} else {
|
||||||
|
const allowed = isOriginAllowed(reqOrigin ?? '', origin);
|
||||||
|
|
||||||
|
if (allowed && reqOrigin) {
|
||||||
|
headers.set('Access-Control-Allow-Origin', reqOrigin);
|
||||||
|
}
|
||||||
|
headers.append('Vary', 'Origin');
|
||||||
|
}
|
||||||
|
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function originHeadersFromReq(req: Request, origin: StaticOrigin | OriginFn) {
|
||||||
|
const reqOrigin = req.headers.get('Origin') || undefined;
|
||||||
|
const value = typeof origin === 'function' ? await origin(reqOrigin, req) : origin;
|
||||||
|
|
||||||
|
if (!value) return;
|
||||||
|
return getOriginHeaders(reqOrigin, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAllowedHeaders(req: Request, allowed?: string | string[]) {
|
||||||
|
const headers = new Headers();
|
||||||
|
|
||||||
|
if (!allowed) {
|
||||||
|
allowed = req.headers.get('Access-Control-Request-Headers')!;
|
||||||
|
headers.append('Vary', 'Access-Control-Request-Headers');
|
||||||
|
} else if (Array.isArray(allowed)) {
|
||||||
|
// If the allowed headers is an array, turn it into a string
|
||||||
|
allowed = allowed.join(',');
|
||||||
|
}
|
||||||
|
if (allowed) {
|
||||||
|
headers.set('Access-Control-Allow-Headers', allowed);
|
||||||
|
}
|
||||||
|
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function cors(req: Request, res: Response, options?: CorsOptions) {
|
||||||
|
const opts = { ...defaultOptions, ...options };
|
||||||
|
const { headers } = res;
|
||||||
|
const originHeaders = await originHeadersFromReq(req, opts.origin ?? false);
|
||||||
|
const mergeHeaders = (v: string, k: string) => {
|
||||||
|
if (k === 'Vary') headers.append(k, v);
|
||||||
|
else headers.set(k, v);
|
||||||
|
};
|
||||||
|
|
||||||
|
// If there's no origin we won't touch the response
|
||||||
|
if (!originHeaders) return res;
|
||||||
|
|
||||||
|
originHeaders.forEach(mergeHeaders);
|
||||||
|
|
||||||
|
if (opts.credentials) {
|
||||||
|
headers.set('Access-Control-Allow-Credentials', 'true');
|
||||||
|
}
|
||||||
|
|
||||||
|
const exposed = Array.isArray(opts.exposedHeaders)
|
||||||
|
? opts.exposedHeaders.join(',')
|
||||||
|
: opts.exposedHeaders;
|
||||||
|
|
||||||
|
if (exposed) {
|
||||||
|
headers.set('Access-Control-Expose-Headers', exposed);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle the preflight request
|
||||||
|
if (req.method === 'OPTIONS') {
|
||||||
|
if (opts.methods) {
|
||||||
|
const methods = Array.isArray(opts.methods) ? opts.methods.join(',') : opts.methods;
|
||||||
|
|
||||||
|
headers.set('Access-Control-Allow-Methods', methods);
|
||||||
|
}
|
||||||
|
|
||||||
|
getAllowedHeaders(req, opts.allowedHeaders).forEach(mergeHeaders);
|
||||||
|
|
||||||
|
if (typeof opts.maxAge === 'number') {
|
||||||
|
headers.set('Access-Control-Max-Age', String(opts.maxAge));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.preflightContinue) return res;
|
||||||
|
|
||||||
|
headers.set('Content-Length', '0');
|
||||||
|
return new Response(null, { status: opts.optionsSuccessStatus, headers });
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we got here, it's a normal request
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function initCors(options?: CorsOptions) {
|
||||||
|
return async (req: Request, res: Response) => cors(req, res, options);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user