Files
formbricks/apps/web/modules/gateway-auth/lib/request.ts
T
Dhruwang Jariwala 939fedfca4 feat: Formbricks 5 (#8017)
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>
2026-05-15 16:43:27 +00:00

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;
};