mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 08:27:36 +00:00
feat(infisical-pg): added all previous simple server middlewares, swagger support
This commit is contained in:
@@ -0,0 +1,42 @@
|
|||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
es2021: true
|
||||||
|
},
|
||||||
|
extends: ["airbnb-base", "airbnb-typescript/base", "prettier"],
|
||||||
|
plugins: ["prettier", "simple-import-sort", "import"],
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: "latest",
|
||||||
|
sourceType: "module",
|
||||||
|
project: "./tsconfig.json",
|
||||||
|
tsconfigRootDir: __dirname
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
"import/prefer-default-export": "off",
|
||||||
|
"simple-import-sort/exports": "error",
|
||||||
|
"import/first": "error",
|
||||||
|
"import/newline-after-import": "error",
|
||||||
|
"import/no-duplicates": "error",
|
||||||
|
"simple-import-sort/imports": [
|
||||||
|
"warn",
|
||||||
|
{
|
||||||
|
groups: [
|
||||||
|
["^node:", "^[a-z]", "@fastify"],
|
||||||
|
["^@app"],
|
||||||
|
["@lib"],
|
||||||
|
["@server"],
|
||||||
|
["^~(/.*|$)"],
|
||||||
|
["^\\.\\.(?!/?$)", "^\\.\\./?$", "^\\./(?=.*/)(?!/?$)", "^\\.(?!/?$)", "^\\./?$"]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
"import/resolver": {
|
||||||
|
typescript: {
|
||||||
|
project: ["./tsconfig.json"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"singleQuote": false,
|
||||||
|
"printWidth": 100,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"tabWidth": 2,
|
||||||
|
"semi": true
|
||||||
|
}
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
// final prod build
|
|
||||||
const { build } = require("esbuild");
|
|
||||||
|
|
||||||
build({
|
|
||||||
entryPoints: ["./src/app.ts"],
|
|
||||||
minify: true,
|
|
||||||
format: "cjs",
|
|
||||||
platform: "node",
|
|
||||||
target: "node20",
|
|
||||||
bundle: true,
|
|
||||||
outfile: "dist/index.js",
|
|
||||||
plugins: [],
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.error(err);
|
|
||||||
process.exit(1);
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
console.log("Finished bundling server..");
|
|
||||||
});
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"watch": "./src",
|
|
||||||
"verbose": true,
|
|
||||||
"ignore": [".git", "node_modules"],
|
|
||||||
"exec": "node esbuild.config.js && node dist/index.js",
|
|
||||||
"ext": "ts json"
|
|
||||||
}
|
|
||||||
Generated
+3998
-262
File diff suppressed because it is too large
Load Diff
+31
-7
@@ -5,20 +5,44 @@
|
|||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
"build": "node esbuild.config.js",
|
"dev": "tsx watch --clear-screen=false ./src/server/app.ts | pino-pretty --colorize --colorizeObjects --singleLine",
|
||||||
"dev": "nodemon"
|
"type:check": "tsc --noEmit",
|
||||||
|
"lint:fix": "eslint --fix 'src/**/*.ts'",
|
||||||
|
"lint": "eslint 'src/**/*.ts'"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.9.1",
|
"@types/node": "^20.9.5",
|
||||||
"esbuild": "^0.19.5",
|
"@typescript-eslint/eslint-plugin": "^6.12.0",
|
||||||
"nodemon": "^3.0.1",
|
"@typescript-eslint/parser": "^6.12.0",
|
||||||
"typescript": "^5.2.2"
|
"eslint": "^8.54.0",
|
||||||
|
"eslint-config-airbnb-base": "^15.0.0",
|
||||||
|
"eslint-config-prettier": "^9.0.0",
|
||||||
|
"eslint-import-resolver-typescript": "^3.6.1",
|
||||||
|
"eslint-plugin-import": "^2.29.0",
|
||||||
|
"eslint-plugin-prettier": "^5.0.1",
|
||||||
|
"eslint-plugin-simple-import-sort": "^10.0.0",
|
||||||
|
"pino-pretty": "^10.2.3",
|
||||||
|
"ts-node": "^10.9.1",
|
||||||
|
"tsx": "^4.4.0",
|
||||||
|
"typescript": "^5.3.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@fastify/cookie": "^9.2.0",
|
||||||
|
"@fastify/cors": "^8.4.1",
|
||||||
|
"@fastify/helmet": "^11.1.1",
|
||||||
|
"@fastify/rate-limit": "^9.0.0",
|
||||||
|
"@fastify/swagger": "^8.12.0",
|
||||||
|
"@fastify/swagger-ui": "^1.10.1",
|
||||||
|
"dotenv": "^16.3.1",
|
||||||
|
"eslint-config-airbnb-typescript": "^17.1.0",
|
||||||
"fastify": "^4.24.3",
|
"fastify": "^4.24.3",
|
||||||
"ora": "^7.0.1"
|
"fastify-plugin": "^4.5.1",
|
||||||
|
"ora": "^7.0.1",
|
||||||
|
"pino": "^8.16.2",
|
||||||
|
"zod": "^3.22.4",
|
||||||
|
"zod-to-json-schema": "^3.22.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+19
@@ -0,0 +1,19 @@
|
|||||||
|
import { ZodTypeProvider } from "@app/server/plugins/fastify-zod";
|
||||||
|
|
||||||
|
import "fastify";
|
||||||
|
|
||||||
|
declare module "fastify" {
|
||||||
|
interface FastifyRequest {
|
||||||
|
realIp: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
type FastifyZodProvider = FastifyInstance<
|
||||||
|
RawServerDefault,
|
||||||
|
RawRequestDefaultExpression<RawServerDefault>,
|
||||||
|
RawReplyDefaultExpression<RawServerDefault>,
|
||||||
|
FastifyBaseLogger,
|
||||||
|
ZodTypeProvider
|
||||||
|
>;
|
||||||
|
}
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import Fasitfy from "fastify";
|
|
||||||
|
|
||||||
const fastify = Fasitfy({
|
|
||||||
logger: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Declare a route
|
|
||||||
fastify.get("/", async function handler(_request, _reply) {
|
|
||||||
return { hello: "world changed" };
|
|
||||||
});
|
|
||||||
|
|
||||||
// Run the server!
|
|
||||||
const main = async () => {
|
|
||||||
try {
|
|
||||||
await fastify.listen({ port: 8000 });
|
|
||||||
} catch (err) {
|
|
||||||
fastify.log.error(err);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
main();
|
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { Logger } from "pino";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
const envSchema = z.object({
|
||||||
|
NODE_ENV: z.enum(["development", "test", "production"]).default("development"),
|
||||||
|
SALT_ROUNDS: z.coerce.number().default(10),
|
||||||
|
// TODO(akhilmhdh): will be changed to one
|
||||||
|
ENCRYPTION_KEY: z.string().optional(),
|
||||||
|
ROOT_ENCRYPTION_KEY: z.string().optional(),
|
||||||
|
HTTPS_ENABLED: z
|
||||||
|
.enum(["true", "false"])
|
||||||
|
.optional()
|
||||||
|
.transform((val) => val === "true")
|
||||||
|
});
|
||||||
|
|
||||||
|
let envCfg: Readonly<z.infer<typeof envSchema>>;
|
||||||
|
|
||||||
|
export const getConfig = () => envCfg;
|
||||||
|
// cannot import singleton logger directly as it needs config to load various transport
|
||||||
|
export const initEnvConfig = (logger: Logger) => {
|
||||||
|
const parsedEnv = envSchema.safeParse(process.env);
|
||||||
|
if (!parsedEnv.success) {
|
||||||
|
logger.error("Invalid environment variables. Check the error below");
|
||||||
|
logger.error(parsedEnv.error);
|
||||||
|
process.exit(-1);
|
||||||
|
}
|
||||||
|
envCfg = Object.freeze(parsedEnv.data);
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { initLogger,logger } from "./logger";
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
// logger follows a singleton pattern
|
||||||
|
// easier to use it that's all.
|
||||||
|
import pino, { Logger } from "pino";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
const logLevelToSeverityLookup: Record<string, string> = {
|
||||||
|
"10": "TRACE",
|
||||||
|
"20": "DEBUG",
|
||||||
|
"30": "INFO",
|
||||||
|
"40": "WARNING",
|
||||||
|
"50": "ERROR",
|
||||||
|
"60": "CRITICAL"
|
||||||
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-mutable-exports
|
||||||
|
export let logger: Readonly<Logger>;
|
||||||
|
// akhilmhdh: why this instead of putting it in config right
|
||||||
|
// reason is to avoid a cyclical condition
|
||||||
|
// config needs logger to output error when invalid environment is provided
|
||||||
|
// logger needs config to get aws or other transport cred
|
||||||
|
// this would make logger independent package
|
||||||
|
const loggerConfig = z.object({
|
||||||
|
AWS_CLOUDWATCH_LOG_GROUP_NAME: z.string().default("infisical-log-stream"),
|
||||||
|
AWS_CLOUDWATCH_LOG_REGION: z.string().default("us-east-1"),
|
||||||
|
AWS_CLOUDWATCH_LOG_ACCESS_KEY_ID: z.string().min(1).optional(),
|
||||||
|
AWS_CLOUDWATCH_LOG_ACCESS_KEY_SECRET: z.string().min(1).optional(),
|
||||||
|
AWS_CLOUDWATCH_LOG_INTERVAL: z.coerce.number().default(1000)
|
||||||
|
});
|
||||||
|
|
||||||
|
export const initLogger = async () => {
|
||||||
|
const targets: pino.TransportMultiOptions["targets"][number][] = [
|
||||||
|
{ level: "info", target: "pino/file", options: {} }
|
||||||
|
];
|
||||||
|
const cfg = loggerConfig.parse(process.env);
|
||||||
|
if (cfg.AWS_CLOUDWATCH_LOG_ACCESS_KEY_ID && cfg.AWS_CLOUDWATCH_LOG_ACCESS_KEY_SECRET) {
|
||||||
|
targets.push({
|
||||||
|
target: "@serdnam/pino-cloudwatch-transport",
|
||||||
|
level: "info",
|
||||||
|
options: {
|
||||||
|
logGroupName: cfg.AWS_CLOUDWATCH_LOG_GROUP_NAME,
|
||||||
|
logStreamName: cfg.AWS_CLOUDWATCH_LOG_GROUP_NAME,
|
||||||
|
awsRegion: cfg.AWS_CLOUDWATCH_LOG_REGION,
|
||||||
|
awsAccessKeyId: cfg.AWS_CLOUDWATCH_LOG_ACCESS_KEY_ID,
|
||||||
|
awsSecretAccessKey: cfg.AWS_CLOUDWATCH_LOG_ACCESS_KEY_SECRET,
|
||||||
|
interval: cfg.AWS_CLOUDWATCH_LOG_INTERVAL
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const transport = pino.transport({
|
||||||
|
targets
|
||||||
|
});
|
||||||
|
|
||||||
|
logger = pino(
|
||||||
|
{
|
||||||
|
mixin(_context, level) {
|
||||||
|
return { severity: logLevelToSeverityLookup[level] || logLevelToSeverityLookup["30"] };
|
||||||
|
},
|
||||||
|
level: process.env.PINO_LOG_LEVEL || "info",
|
||||||
|
formatters: {
|
||||||
|
bindings: (bindings) => ({
|
||||||
|
pid: bindings.pid,
|
||||||
|
hostname: bindings.hostname
|
||||||
|
// node_version: process.version
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
transport
|
||||||
|
);
|
||||||
|
return logger;
|
||||||
|
};
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import dotenv from "dotenv";
|
||||||
|
import fasitfy from "fastify";
|
||||||
|
import { z } from "zod";
|
||||||
|
import type { FastifyCookieOptions } from "@fastify/cookie";
|
||||||
|
import cookie from "@fastify/cookie";
|
||||||
|
import type { FastifyCorsOptions } from "@fastify/cors";
|
||||||
|
import cors from "@fastify/cors";
|
||||||
|
import helmet from "@fastify/helmet";
|
||||||
|
import type { FastifyRateLimitOptions } from "@fastify/rate-limit";
|
||||||
|
import ratelimiter from "@fastify/rate-limit";
|
||||||
|
|
||||||
|
import { initEnvConfig } from "@lib/config/env";
|
||||||
|
import { initLogger } from "@lib/logger";
|
||||||
|
|
||||||
|
import { globalRateLimiterCfg } from "./config/rateLimiter";
|
||||||
|
import { serializerCompiler, validatorCompiler, ZodTypeProvider } from "./plugins/fastify-zod";
|
||||||
|
import { fastifyIp } from "./plugins/ip";
|
||||||
|
import { fastifySwagger } from "./plugins/swagger";
|
||||||
|
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
// Run the server!
|
||||||
|
const main = async () => {
|
||||||
|
const logger = await initLogger();
|
||||||
|
initEnvConfig(logger);
|
||||||
|
|
||||||
|
const server = fasitfy({
|
||||||
|
logger,
|
||||||
|
trustProxy: true
|
||||||
|
}).withTypeProvider<ZodTypeProvider>();
|
||||||
|
|
||||||
|
server.setValidatorCompiler(validatorCompiler);
|
||||||
|
server.setSerializerCompiler(serializerCompiler);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// TODO(akhilmhdh:pg): change this to environment variable with default
|
||||||
|
await server.register<FastifyCookieOptions>(cookie, {
|
||||||
|
secret: "infisical-cookie-secret"
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.register<FastifyCorsOptions>(cors, {
|
||||||
|
credentials: true,
|
||||||
|
origin: "http://localhost:3000"
|
||||||
|
});
|
||||||
|
// pull ip based on various proxy headers
|
||||||
|
await server.register(fastifyIp);
|
||||||
|
|
||||||
|
// Rate limiters and security headers
|
||||||
|
await server.register<FastifyRateLimitOptions>(ratelimiter, globalRateLimiterCfg);
|
||||||
|
await server.register(helmet);
|
||||||
|
|
||||||
|
await server.register(fastifySwagger);
|
||||||
|
|
||||||
|
// Declare a route
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/",
|
||||||
|
schema: {
|
||||||
|
response: {
|
||||||
|
200: z.object({ hello: z.string() })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: () => ({
|
||||||
|
hello: "world"
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.ready();
|
||||||
|
server.swagger();
|
||||||
|
await server.listen({ port: 8000 });
|
||||||
|
} catch (err) {
|
||||||
|
server.log.error(err);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
main();
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import type { RateLimitOptions } from "@fastify/rate-limit";
|
||||||
|
|
||||||
|
export const globalRateLimiterCfg: RateLimitOptions = {
|
||||||
|
timeWindow: 60 * 1000,
|
||||||
|
max: 100,
|
||||||
|
keyGenerator: (req) => req.realIp
|
||||||
|
};
|
||||||
@@ -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);
|
||||||
|
};
|
||||||
@@ -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,48 @@
|
|||||||
|
import fp from "fastify-plugin";
|
||||||
|
import swagger from "@fastify/swagger";
|
||||||
|
import swaggerUI from "@fastify/swagger-ui";
|
||||||
|
|
||||||
|
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: "https://app.infisical.com",
|
||||||
|
description: "Production server"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: "http://localhost:8000",
|
||||||
|
description: "Local 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"
|
||||||
|
});
|
||||||
|
});
|
||||||
+12
-16
@@ -1,31 +1,27 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "esnext",
|
"target": "esnext",
|
||||||
"module": "esnext",
|
"module": "commonjs",
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"removeComments": true,
|
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"typeRoots": [
|
"typeRoots": ["./node_modules/@types", "./src/@types"],
|
||||||
"./node_modules/@types"
|
|
||||||
],
|
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"lib": [
|
"lib": ["esnext"],
|
||||||
"esnext"
|
|
||||||
],
|
|
||||||
"baseUrl": ".",
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
"moduleResolution": "Node",
|
"moduleResolution": "Node",
|
||||||
"skipLibCheck": true
|
"skipLibCheck": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@app/*": ["./src/*"],
|
||||||
|
"@lib/*": ["./src/lib/*"],
|
||||||
|
"@server/*": ["./src/server/*"]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"include": [
|
"include": ["src/**/*"],
|
||||||
"src/**/*"
|
"exclude": ["node_modules"]
|
||||||
],
|
|
||||||
"exclude": [
|
|
||||||
"node_modules"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user