feat: changed backend-pg to backend

This commit is contained in:
Akhil Mohan
2024-01-27 19:14:33 +05:30
parent d13eafcef7
commit 66d258f02b
792 changed files with 40298 additions and 40607 deletions
+71
View File
@@ -0,0 +1,71 @@
import fp from "fastify-plugin";
import { UserAgentType } from "@app/ee/services/audit-log/audit-log-types";
import { BadRequestError } from "@app/lib/errors";
import { ActorType } from "@app/services/auth/auth-type";
export const getUserAgentType = (userAgent: string | undefined) => {
if (userAgent === undefined) {
return UserAgentType.OTHER;
}
if (userAgent === UserAgentType.CLI) {
return UserAgentType.CLI;
}
if (userAgent === UserAgentType.K8_OPERATOR) {
return UserAgentType.K8_OPERATOR;
}
if (userAgent === UserAgentType.TERRAFORM) {
return UserAgentType.TERRAFORM;
}
if (userAgent.toLowerCase().includes("mozilla")) {
return UserAgentType.WEB;
}
if (userAgent.includes(UserAgentType.NODE_SDK)) {
return UserAgentType.NODE_SDK;
}
if (userAgent.includes(UserAgentType.PYTHON_SDK)) {
return UserAgentType.PYTHON_SDK;
}
return UserAgentType.OTHER;
};
export const injectAuditLogInfo = fp(async (server: FastifyZodProvider) => {
server.decorateRequest("auditLogInfo", null);
server.addHook("onRequest", async (req) => {
if (!req.auth) return;
const userAgent = req.headers["user-agent"] ?? "";
const payload = {
ipAddress: req.realIp,
userAgent,
userAgentType: getUserAgentType(userAgent)
} as typeof req.auditLogInfo;
if (req.auth.actor === ActorType.USER) {
payload.actor = {
type: ActorType.USER,
metadata: {
email: req.auth.user.email,
userId: req.permission.id
}
};
} else if (req.auth.actor === ActorType.SERVICE) {
payload.actor = {
type: ActorType.SERVICE,
metadata: {
name: req.auth.serviceToken.name,
serviceId: req.auth.serviceTokenId
}
};
} else if (req.auth.actor === ActorType.IDENTITY) {
payload.actor = {
type: ActorType.IDENTITY,
metadata: {
name: req.auth.identityName,
identityId: req.auth.identityId
}
};
} else {
throw new BadRequestError({ message: "Missing logic for other actor" });
}
req.auditLogInfo = payload;
});
});
@@ -0,0 +1,129 @@
import { FastifyRequest } from "fastify";
import fp from "fastify-plugin";
import jwt, { JwtPayload } from "jsonwebtoken";
import { TServiceTokens, TUsers } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env";
import { UnauthorizedError } from "@app/lib/errors";
import {
ActorType,
AuthMode,
AuthModeJwtTokenPayload,
AuthTokenType
} from "@app/services/auth/auth-type";
import { TIdentityAccessTokenJwtPayload } from "@app/services/identity-access-token/identity-access-token-types";
export type TAuthMode =
| {
authMode: AuthMode.JWT;
actor: ActorType.USER;
userId: string;
tokenVersionId: string; // the session id of token used
user: TUsers;
}
| {
authMode: AuthMode.API_KEY;
actor: ActorType.USER;
userId: string;
user: TUsers;
}
| {
authMode: AuthMode.SERVICE_TOKEN;
serviceToken: TServiceTokens;
actor: ActorType.SERVICE;
serviceTokenId: string;
}
| {
authMode: AuthMode.IDENTITY_ACCESS_TOKEN;
actor: ActorType.IDENTITY;
identityId: string;
identityName: string;
};
const extractAuth = async (req: FastifyRequest, jwtSecret: string) => {
const apiKey = req.headers?.["x-api-key"];
if (apiKey) {
return { authMode: AuthMode.API_KEY, token: apiKey, actor: ActorType.USER } as const;
}
const authHeader = req.headers?.authorization;
if (!authHeader) return { authMode: null, token: null };
const authTokenValue = authHeader.slice(7); // slice of after Bearer
if (authTokenValue.startsWith("st.")) {
return {
authMode: AuthMode.SERVICE_TOKEN,
token: authTokenValue,
actor: ActorType.SERVICE
} as const;
}
const decodedToken = jwt.verify(authTokenValue, jwtSecret) as JwtPayload;
switch (decodedToken.authTokenType) {
case AuthTokenType.ACCESS_TOKEN:
return {
authMode: AuthMode.JWT,
token: decodedToken as AuthModeJwtTokenPayload,
actor: ActorType.USER
} as const;
case AuthTokenType.API_KEY:
return { authMode: AuthMode.API_KEY, token: decodedToken, actor: ActorType.USER } as const;
case AuthTokenType.IDENTITY_ACCESS_TOKEN:
return {
authMode: AuthMode.IDENTITY_ACCESS_TOKEN,
token: decodedToken as TIdentityAccessTokenJwtPayload,
actor: ActorType.IDENTITY
} as const;
default:
return { authMode: null, token: null } as const;
}
};
export const injectIdentity = fp(async (server: FastifyZodProvider) => {
server.decorateRequest("auth", null);
server.addHook("onRequest", async (req) => {
const appCfg = getConfig();
const { authMode, token, actor } = await extractAuth(req, appCfg.AUTH_SECRET);
if (!authMode) return;
switch (authMode) {
case AuthMode.JWT: {
const { user, tokenVersionId } =
await server.services.authToken.fnValidateJwtIdentity(token);
req.auth = { authMode: AuthMode.JWT, user, userId: user.id, tokenVersionId, actor };
break;
}
case AuthMode.IDENTITY_ACCESS_TOKEN: {
const identity = await server.services.identityAccessToken.fnValidateIdentityAccessToken(
token,
req.realIp
);
req.auth = {
authMode: AuthMode.IDENTITY_ACCESS_TOKEN,
actor,
identityId: identity.identityId,
identityName: identity.name
};
break;
}
case AuthMode.SERVICE_TOKEN: {
const serviceToken = await server.services.serviceToken.fnValidateServiceToken(
token as string
);
req.auth = {
authMode: AuthMode.SERVICE_TOKEN as const,
serviceToken,
serviceTokenId: serviceToken.id,
actor
};
break;
}
case AuthMode.API_KEY: {
const user = await server.services.apiKey.fnValidateApiKey(token as string);
req.auth = { authMode: AuthMode.API_KEY as const, userId: user.id, actor, user };
break;
}
default:
throw new UnauthorizedError({ name: "Unknown token strategy" });
}
});
});
@@ -0,0 +1,19 @@
import fp from "fastify-plugin";
import { ActorType } from "@app/services/auth/auth-type";
// inject permission type needed based on auth extracted
export const injectPermission = fp(async (server) => {
server.decorateRequest("permission", null);
server.addHook("onRequest", async (req) => {
if (!req.auth) return;
if (req.auth.actor === ActorType.USER) {
req.permission = { type: ActorType.USER, id: req.auth.userId };
} else if (req.auth.actor === ActorType.IDENTITY) {
req.permission = { type: ActorType.IDENTITY, id: req.auth.identityId };
} else if (req.auth.actor === ActorType.SERVICE) {
req.permission = { type: ActorType.SERVICE, id: req.auth.serviceTokenId };
}
});
});
@@ -0,0 +1,12 @@
import { FastifyRequest } from "fastify";
import { UnauthorizedError } from "@app/lib/errors";
import { ActorType } from "@app/services/auth/auth-type";
export const verifySuperAdmin = async <T extends FastifyRequest>(req: T) => {
if (req.auth.actor !== ActorType.USER || !req.auth.user.superAdmin)
throw new UnauthorizedError({
name: "Unauthorized access",
message: "Requires superadmin access"
});
};
@@ -0,0 +1,17 @@
import { FastifyRequest } from "fastify";
import { UnauthorizedError } from "@app/lib/errors";
import { AuthMode } from "@app/services/auth/auth-type";
export const verifyAuth =
<T extends FastifyRequest>(authStrats: AuthMode[]) =>
async (req: T) => {
if (!Array.isArray(authStrats)) throw new Error("Auth strategy must be array");
if (!req.auth)
throw new UnauthorizedError({ name: "Unauthorized access", message: "Token missing" });
const isAccessAllowed = authStrats.some((strat) => strat === req.auth.authMode);
if (!isAccessAllowed) {
throw new UnauthorizedError({ name: `${req.url} Unauthorized Access` });
}
};
@@ -0,0 +1,34 @@
import { ForbiddenError } from "@casl/ability";
import fastifyPlugin from "fastify-plugin";
import { ZodError } from "zod";
import {
BadRequestError,
DatabaseError,
ForbiddenRequestError,
InternalServerError,
UnauthorizedError
} from "@app/lib/errors";
export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider) => {
server.setErrorHandler((error, req, res) => {
req.log.error(error);
if (error instanceof BadRequestError) {
res.status(400).send({ statusCode: 400, message: error.message, error: error.name });
} else if (error instanceof UnauthorizedError || error instanceof ForbiddenRequestError) {
res.status(403).send({ statusCode: 403, message: error.message, error: error.name });
} else if (error instanceof DatabaseError || error instanceof InternalServerError) {
res.status(500).send({ statusCode: 500, message: "Something went wrong", error: error.name });
} else if (error instanceof ZodError) {
res.status(403).send({ statusCode: 403, error: "ValidationFailure", message: error.issues });
} else if (error instanceof ForbiddenError) {
res.status(403).send({
statusCode: 403,
error: "PermissionDenied",
message: `You are not allowed to ${error.action} on ${error.subjectType}`
});
} else {
res.send(error);
}
});
});
@@ -0,0 +1,56 @@
// this plugins allows to run infisical in standalone mode
// standalone mode = infisical backend and nextjs frontend in one server
// this way users don't need to deploy two things
import path from "node:path";
// to enabled this u need to set standalone mode to true
export const registerExternalNextjs = async (
server: FastifyZodProvider,
{
standaloneMode,
dir,
port
}: {
standaloneMode?: boolean;
dir: string;
port: number;
}
) => {
if (standaloneMode) {
const nextJsBuildPath = path.join(dir, "frontend-build");
const { default: conf } = await import(
path.join(dir, "frontend-build/.next/required-server-files.json"),
// @ts-expect-error type
{
assert: { type: "json" }
}
);
const { default: NextServer } = (
await import(path.join(dir, "frontend-build/node_modules/next/dist/server/next-server.js"))
).default;
const nextApp = new NextServer({
dev: false,
dir: nextJsBuildPath,
port,
conf: conf.config,
hostname: "local",
customServer: false
});
server.route({
method: ["GET", "PUT", "PATCH", "POST", "DELETE"],
url: "/*",
handler: (req, res) =>
nextApp
.getRequestHandler()(req.raw, res.raw)
.then(() => {
res.hijack();
})
});
server.addHook("onClose", () => nextApp.close());
await nextApp.prepare();
}
};
+142
View File
@@ -0,0 +1,142 @@
/* eslint-disable */
// Code taken from https://www.npmjs.com/package/fastify-type-provider-zod
// Full credits goes to https://github.com/turkerdev
// Code taken to keep in in house
import type { FastifySchema, FastifySchemaCompiler, FastifyTypeProvider } from "fastify";
import type { FastifySerializerCompiler } from "fastify/types/schema";
import type { z, ZodAny, ZodTypeAny } from "zod";
import { zodToJsonSchema } from "zod-to-json-schema";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type FreeformRecord = Record<string, any>;
const defaultSkipList = [
"/documentation/",
"/documentation/initOAuth",
"/documentation/json",
"/documentation/uiConfig",
"/documentation/yaml",
"/documentation/*",
"/documentation/static/*"
];
export interface ZodTypeProvider extends FastifyTypeProvider {
output: this["input"] extends ZodTypeAny ? z.infer<this["input"]> : never;
}
interface Schema extends FastifySchema {
hide?: boolean;
}
const zodToJsonSchemaOptions = {
target: "openApi3",
$refStrategy: "none"
} as const;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function hasOwnProperty<T, K extends PropertyKey>(obj: T, prop: K): obj is T & Record<K, any> {
return Object.prototype.hasOwnProperty.call(obj, prop);
}
function resolveSchema(maybeSchema: ZodAny | { properties: ZodAny }): Pick<ZodAny, "safeParse"> {
if (hasOwnProperty(maybeSchema, "safeParse")) {
return maybeSchema;
}
if (hasOwnProperty(maybeSchema, "properties")) {
return maybeSchema.properties;
}
throw new Error(`Invalid schema passed: ${JSON.stringify(maybeSchema)}`);
}
export const createJsonSchemaTransform = ({ skipList }: { skipList: readonly string[] }) => {
return ({ schema, url }: { schema: Schema; url: string }) => {
if (!schema) {
return {
schema,
url
};
}
const { response, headers, querystring, body, params, hide, ...rest } = schema;
const transformed: FreeformRecord = {};
if (skipList.includes(url) || hide) {
transformed.hide = true;
return { schema: transformed, url };
}
const zodSchemas: FreeformRecord = { headers, querystring, body, params };
for (const prop in zodSchemas) {
const zodSchema = zodSchemas[prop];
if (zodSchema) {
transformed[prop] = zodToJsonSchema(zodSchema, zodToJsonSchemaOptions);
}
}
if (response) {
transformed.response = {};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
for (const prop in response as any) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const schema = resolveSchema((response as any)[prop]);
const transformedResponse = zodToJsonSchema(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
schema as any,
zodToJsonSchemaOptions
);
transformed.response[prop] = transformedResponse;
}
}
for (const prop in rest) {
const meta = rest[prop as keyof typeof rest];
if (meta) {
transformed[prop] = meta;
}
}
return { schema: transformed, url };
};
};
export const jsonSchemaTransform = createJsonSchemaTransform({
skipList: defaultSkipList
});
export const validatorCompiler: FastifySchemaCompiler<ZodAny> =
({ schema }) =>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(data): any => {
try {
return { value: schema.parse(data) };
} catch (error) {
return { error };
}
};
export class ResponseValidationError extends Error {
public details: FreeformRecord;
constructor(validationResult: FreeformRecord) {
super("Response doesn't match the schema");
this.name = "ResponseValidationError";
this.details = validationResult.error;
}
}
export const serializerCompiler: FastifySerializerCompiler<ZodAny | { properties: ZodAny }> =
({ schema: maybeSchema }) =>
(data) => {
const schema: Pick<ZodAny, "safeParse"> = resolveSchema(maybeSchema);
const result = schema.safeParse(data);
if (result.success) {
return JSON.stringify(result.data);
}
throw new ResponseValidationError(result);
};
+31
View File
@@ -0,0 +1,31 @@
import fp from "fastify-plugin";
/*! https://github.com/pbojinov/request-ip/blob/9501cdf6e73059cc70fc6890adb086348d7cca46/src/index.js.
MIT License. 2022 Petar Bojinov - [email protected] */
const headersOrder = [
"x-client-ip", // Most common
"x-forwarded-for", // Mostly used by proxies
"cf-connecting-ip", // Cloudflare
"Cf-Pseudo-IPv4", // Cloudflare
"fastly-client-ip",
"true-client-ip", // Akamai and Cloudflare
"x-real-ip", // Nginx
"x-cluser-client-ip", // Rackspace LB
"forwarded-for",
"x-forwarded",
"forwarded",
"x-appengine-user-ip" // GCP App Engine
];
export const fastifyIp = fp(async (fastify) => {
fastify.decorateRequest("realIp", null);
fastify.addHook("onRequest", async (req) => {
const forwardedIpHeader = headersOrder.find((header) => Boolean(req.headers[header]));
const forwardedIp = forwardedIpHeader ? req.headers[forwardedIpHeader] : undefined;
if (forwardedIp) {
req.realIp = Array.isArray(forwardedIp) ? forwardedIp[0] : forwardedIp;
} else {
req.realIp = req.ip;
}
});
});
@@ -0,0 +1,65 @@
import { Probot } from "probot";
import SmeeClient from "smee-client";
import { getConfig } from "@app/lib/config/env";
import { logger } from "@app/lib/logger";
export const registerSecretScannerGhApp = async (server: FastifyZodProvider) => {
const probotApp = (app: Probot) => {
app.on("installation.deleted", async (context) => {
const { payload } = context;
const { installation, repositories } = payload;
await server.services.secretScanning.handleRepoDeleteEvent(
String(installation.id),
(repositories || [])?.map(({ id }) => String(id))
);
});
app.on("installation", async (context) => {
const { payload } = context;
logger.info("Installed secret scanner to:", { repositories: payload.repositories });
});
app.on("push", async (context) => {
const { payload } = context;
await server.services.secretScanning.handleRepoPushEvent(payload as any);
});
};
const appCfg = getConfig();
if (appCfg.isSecretScanningConfigured) {
const probot = new Probot({
appId: appCfg.SECRET_SCANNING_GIT_APP_ID as string,
privateKey: appCfg.SECRET_SCANNING_PRIVATE_KEY as string,
secret: appCfg.SECRET_SCANNING_WEBHOOK_SECRET as string
});
if (appCfg.NODE_ENV === "development") {
const smee = new SmeeClient({
source: appCfg.SECRET_SCANNING_WEBHOOK_PROXY as string,
target: "http://backend:4000/ss-webhook",
logger: console
});
smee.start();
}
await probot.load(probotApp);
server.route({
method: "POST",
url: "/",
handler: async (req, res) => {
const eventName = req.headers["x-github-event"] as any;
const signatureSHA256 = req.headers["x-hub-signature-256"] as string;
const id = req.headers["x-github-delivery"] as string;
await probot.webhooks.verifyAndReceive({
id,
name: eventName,
payload: req.body as string,
signature: signatureSHA256
});
res.send("ok");
}
});
}
};
+48
View File
@@ -0,0 +1,48 @@
import swagger from "@fastify/swagger";
import swaggerUI from "@fastify/swagger-ui";
import fp from "fastify-plugin";
import { jsonSchemaTransform } from "./fastify-zod";
export const fastifySwagger = fp(async (fastify) => {
await fastify.register(swagger, {
transform: jsonSchemaTransform,
openapi: {
info: {
title: "Infisical API",
description: "List of all available APIs that can be consumed",
version: "0.0.1"
},
servers: [
{
url: "http://localhost:8080",
description: "Local server"
},
{
url: "https://app.infisical.com",
description: "Production server"
}
],
components: {
securitySchemes: {
bearer: {
type: "http",
scheme: "bearer",
bearerFormat: "JWT",
description: "A service token in Infisical"
},
apiKey: {
type: "apiKey",
in: "header",
name: "X-API-Key",
description: "An API Key in Infisical"
}
}
}
}
});
await fastify.register(swaggerUI, {
routePrefix: "/docs"
});
});