mirror of
https://github.com/formbricks/formbricks.git
synced 2026-05-23 02:45:21 -05:00
939fedfca4
Signed-off-by: gulshank0 <gulshanbahadur002@gmail.com> Co-authored-by: Tiago Farto <tiago@formbricks.com> Co-authored-by: Johannes <72809645+jobenjada@users.noreply.github.com> Co-authored-by: pandeymangg <anshuman.pandey9999@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Tiago <1585571+xernobyl@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Theodór Tómas <theodortomas@gmail.com> Co-authored-by: Anshuman Pandey <54475686+pandeymangg@users.noreply.github.com> Co-authored-by: Bhagya Amarasinghe <b.sithumini@yahoo.com> Co-authored-by: Chowdhury Tafsir Ahmed Siddiki <ctafsiras@gmail.com> Co-authored-by: neila <40727091+neila@users.noreply.github.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: Harsh Bhat <90265455+harshsbhat@users.noreply.github.com> Co-authored-by: Harsh Bhat <harshbhat@Harshs-MacBook-Air.local> Co-authored-by: Johannes <johannes@formbricks.com> Co-authored-by: Balázs Úr <balazs@urbalazs.hu> Co-authored-by: Gulshan <gulshanbahadur002@gmail.com> Co-authored-by: Harsh Bhat <harsh121102@gmail.com> Co-authored-by: Javi Aguilar <122741+itsjavi@users.noreply.github.com> Co-authored-by: Johannes <jobenjada@users.noreply.github.com>
172 lines
4.8 KiB
TypeScript
172 lines
4.8 KiB
TypeScript
import "server-only";
|
|
import { NextRequest } from "next/server";
|
|
import { prisma } from "@formbricks/database";
|
|
import { logger } from "@formbricks/logger";
|
|
import { TAuthenticationApiKey } from "@formbricks/types/auth";
|
|
import { authenticateApiKeyFromHeaders, getApiKeyFromHeaders } from "@/modules/api/lib/api-key-auth";
|
|
import { getProxySession } from "@/modules/auth/lib/proxy-session";
|
|
|
|
export type TGatewayOriginalRequest = {
|
|
method: string;
|
|
url: URL;
|
|
};
|
|
|
|
export type TGatewayAuthenticatedPrincipal =
|
|
| {
|
|
type: "apiKey";
|
|
authentication: TAuthenticationApiKey;
|
|
}
|
|
| {
|
|
type: "user";
|
|
userId: string;
|
|
source: "session" | "jwt";
|
|
};
|
|
|
|
export type TGatewayTokenHandler = {
|
|
getTokenFromHeaders: (headers: Headers) => string | null;
|
|
verifyToken: (token: string) => { userId: string };
|
|
};
|
|
|
|
export type TGatewayAuthenticationResult =
|
|
| { status: "authenticated"; principal: TGatewayAuthenticatedPrincipal }
|
|
| { status: "invalid" }
|
|
| { status: "missing" };
|
|
|
|
export type TGatewayAuthorizationDecision = { status: "allow" } | { status: "deny"; response: Response };
|
|
|
|
export type TGatewayRequestAuthorizer = {
|
|
matches: (originalRequest: TGatewayOriginalRequest) => boolean;
|
|
gatewayToken?: TGatewayTokenHandler;
|
|
authorize: (params: {
|
|
request: NextRequest;
|
|
originalRequest: TGatewayOriginalRequest;
|
|
principal: TGatewayAuthenticatedPrincipal;
|
|
requestId: string;
|
|
}) => Promise<TGatewayAuthorizationDecision>;
|
|
};
|
|
|
|
export const buildGatewayStatusResponse = (status: number, message: string): Response =>
|
|
new Response(message, {
|
|
status,
|
|
headers: {
|
|
"content-type": "text/plain; charset=utf-8",
|
|
},
|
|
});
|
|
|
|
export const allowGatewayRequest = (): TGatewayAuthorizationDecision => ({ status: "allow" });
|
|
|
|
export const authenticateGatewayRequest = async (
|
|
request: NextRequest,
|
|
gatewayToken?: TGatewayTokenHandler
|
|
): Promise<TGatewayAuthenticationResult> => {
|
|
if (getApiKeyFromHeaders(request.headers)) {
|
|
const apiKeyAuthentication = await authenticateApiKeyFromHeaders(request.headers);
|
|
if (!apiKeyAuthentication) {
|
|
logger.warn({ hasApiKey: true, reason: "invalid_api_key" }, "Gateway authentication failed");
|
|
return { status: "invalid" };
|
|
}
|
|
|
|
return {
|
|
status: "authenticated",
|
|
principal: {
|
|
type: "apiKey",
|
|
authentication: apiKeyAuthentication,
|
|
},
|
|
};
|
|
}
|
|
|
|
if (gatewayToken) {
|
|
const token = gatewayToken.getTokenFromHeaders(request.headers);
|
|
if (token) {
|
|
let userId: string;
|
|
|
|
try {
|
|
({ userId } = gatewayToken.verifyToken(token));
|
|
} catch (error) {
|
|
logger.warn(
|
|
{ error, hasToken: true, reason: "token_verification_failed" },
|
|
"Gateway authentication failed"
|
|
);
|
|
return { status: "invalid" };
|
|
}
|
|
|
|
const user = await prisma.user.findUnique({
|
|
where: { id: userId },
|
|
select: { id: true, isActive: true },
|
|
});
|
|
|
|
if (!user || user.isActive === false) {
|
|
logger.warn(
|
|
{
|
|
hasToken: true,
|
|
reason: "user_missing_or_inactive",
|
|
userId,
|
|
userFound: Boolean(user),
|
|
isActive: user?.isActive ?? null,
|
|
},
|
|
"Gateway authentication failed"
|
|
);
|
|
return { status: "invalid" };
|
|
}
|
|
|
|
return {
|
|
status: "authenticated",
|
|
principal: {
|
|
type: "user",
|
|
userId: user.id,
|
|
source: "jwt",
|
|
},
|
|
};
|
|
}
|
|
}
|
|
|
|
const proxySession = await getProxySession(request);
|
|
if (!proxySession) {
|
|
return { status: "missing" };
|
|
}
|
|
|
|
return {
|
|
status: "authenticated",
|
|
principal: {
|
|
type: "user",
|
|
userId: proxySession.userId,
|
|
source: "session",
|
|
},
|
|
};
|
|
};
|
|
|
|
export const authorizeGatewayRequest = async ({
|
|
request,
|
|
originalRequest,
|
|
authorizers,
|
|
requestId,
|
|
buildAllowResponse,
|
|
unsupportedRouteMessage,
|
|
}: {
|
|
request: NextRequest;
|
|
originalRequest: TGatewayOriginalRequest;
|
|
authorizers: TGatewayRequestAuthorizer[];
|
|
requestId: string;
|
|
buildAllowResponse: () => Response;
|
|
unsupportedRouteMessage: string;
|
|
}): Promise<Response> => {
|
|
const authorizer = authorizers.find((candidate) => candidate.matches(originalRequest));
|
|
if (!authorizer) {
|
|
return buildGatewayStatusResponse(400, unsupportedRouteMessage);
|
|
}
|
|
|
|
const authenticationResult = await authenticateGatewayRequest(request, authorizer.gatewayToken);
|
|
if (authenticationResult.status === "missing" || authenticationResult.status === "invalid") {
|
|
return buildGatewayStatusResponse(401, "Unauthorized");
|
|
}
|
|
|
|
const authorizationDecision = await authorizer.authorize({
|
|
request,
|
|
originalRequest,
|
|
principal: authenticationResult.principal,
|
|
requestId,
|
|
});
|
|
|
|
return authorizationDecision.status === "allow" ? buildAllowResponse() : authorizationDecision.response;
|
|
};
|