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
+88
View File
@@ -0,0 +1,88 @@
/* eslint-disable import/extensions */
import path from "node:path";
import type { FastifyCookieOptions } from "@fastify/cookie";
import cookie from "@fastify/cookie";
import type { FastifyCorsOptions } from "@fastify/cors";
import cors from "@fastify/cors";
import fastifyFormBody from "@fastify/formbody";
import helmet from "@fastify/helmet";
import type { FastifyRateLimitOptions } from "@fastify/rate-limit";
import ratelimiter from "@fastify/rate-limit";
import fasitfy from "fastify";
import { Knex } from "knex";
import { Logger } from "pino";
import { TQueueServiceFactory } from "@app/queue";
import { TSmtpService } from "@app/services/smtp/smtp-service";
import { getConfig } from "@lib/config/env";
import { globalRateLimiterCfg } from "./config/rateLimiter";
import { fastifyErrHandler } from "./plugins/error-handler";
import { registerExternalNextjs } from "./plugins/external-nextjs";
import { serializerCompiler, validatorCompiler, ZodTypeProvider } from "./plugins/fastify-zod";
import { fastifyIp } from "./plugins/ip";
import { fastifySwagger } from "./plugins/swagger";
import { registerRoutes } from "./routes";
type TMain = {
db: Knex;
smtp: TSmtpService;
logger?: Logger;
queue: TQueueServiceFactory;
};
// Run the server!
export const main = async ({ db, smtp, logger, queue }: TMain) => {
const appCfg = getConfig();
const server = fasitfy({
logger,
trustProxy: true,
ignoreTrailingSlash: true
}).withTypeProvider<ZodTypeProvider>();
server.setValidatorCompiler(validatorCompiler);
server.setSerializerCompiler(serializerCompiler);
try {
await server.register<FastifyCookieOptions>(cookie, {
secret: appCfg.COOKIE_SECRET_SIGN_KEY
});
await server.register<FastifyCorsOptions>(cors, {
credentials: true,
origin: appCfg.SITE_URL || true
});
// pull ip based on various proxy headers
await server.register(fastifyIp);
await server.register(fastifySwagger);
await server.register(fastifyFormBody);
await server.register(fastifyErrHandler);
// Rate limiters and security headers
if (appCfg.isProductionMode) {
await server.register<FastifyRateLimitOptions>(ratelimiter, globalRateLimiterCfg());
}
await server.register(helmet, { contentSecurityPolicy: false });
await server.register(registerRoutes, { smtp, queue, db });
if (appCfg.isProductionMode) {
await server.register(registerExternalNextjs, {
standaloneMode: appCfg.STANDALONE_MODE,
dir: path.join(__dirname, "../"),
port: appCfg.PORT
});
}
await server.ready();
server.swagger();
return server;
} catch (err) {
server.log.error(err);
await queue.shutdown();
process.exit(1);
}
};
+79
View File
@@ -0,0 +1,79 @@
/* eslint-disable no-console */
import { Redis } from "ioredis";
import { Knex } from "knex";
import { createTransport } from "nodemailer";
import { formatSmtpConfig, getConfig } from "@app/lib/config/env";
import { logger } from "@app/lib/logger";
import { getTlsOption } from "@app/services/smtp/smtp-service";
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
type BootstrapOpt = {
db: Knex;
};
const bootstrapCb = () => {
const appCfg = getConfig();
const serverCfg = getServerCfg();
if (!serverCfg.initialized) {
console.info(`Welcome to Infisical
Create your Infisical administrator account at:
http://localhost:${appCfg.PORT}/admin/signup
`);
} else {
console.info(`Welcome back!
To access Infisical Administrator Panel open
http://localhost:${appCfg.PORT}/admin
To access Infisical server
http://localhost:${appCfg.PORT}
`);
}
};
export const bootstrapCheck = async ({ db }: BootstrapOpt) => {
const appCfg = getConfig();
if (appCfg.isDevelopmentMode) {
console.log("Development mode. Skipping initial check");
return bootstrapCb;
}
console.info("Checking configurations...");
console.info("Testing smtp connection");
const smtpCfg = formatSmtpConfig();
await createTransport({ ...smtpCfg, ...getTlsOption(smtpCfg.host, smtpCfg.secure) })
.verify()
.then(async () => {
console.info("SMTP successfully connected");
})
.catch((err) => {
console.error(`SMTP - Failed to connect to ${appCfg.SMTP_HOST}:${appCfg.SMTP_PORT}`);
logger.error(err);
});
console.log("Testing Postgres connection");
await db
.raw("SELECT NOW()")
.then(() => {
console.log("PostgreSQL - Connected successfully");
})
.catch((err) => {
console.error("Failed to connect to PostgreSQL");
logger.error(err);
});
console.log("Testing redis connection");
const redis = new Redis(appCfg.REDIS_URL);
const redisPing = await redis?.ping();
if (!redisPing) {
console.error("Redis - Failed to connect");
} else {
console.error("Redis successfully connected");
redis.disconnect();
}
return bootstrapCb;
};
+31
View File
@@ -0,0 +1,31 @@
import type { RateLimitOptions, RateLimitPluginOptions } from "@fastify/rate-limit";
import { Redis } from "ioredis";
import { getConfig } from "@app/lib/config/env";
export const globalRateLimiterCfg = (): RateLimitPluginOptions => {
const appCfg = getConfig();
const redis = appCfg.isRedisConfigured
? new Redis(appCfg.REDIS_URL as string, { connectTimeout: 500, maxRetriesPerRequest: 1 })
: null;
return {
timeWindow: 60 * 1000,
max: 400,
redis,
allowList: (req) => req.url === "/healthcheck" || req.url === "/api/status",
keyGenerator: (req) => req.realIp
};
};
export const authRateLimit: RateLimitOptions = {
timeWindow: 60 * 1000,
max: 300,
keyGenerator: (req) => req.realIp
};
export const passwordRateLimit: RateLimitOptions = {
timeWindow: 60 * 1000,
max: 300,
keyGenerator: (req) => req.realIp
};
+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"
});
});
+541
View File
@@ -0,0 +1,541 @@
import { Knex } from "knex";
import { z } from "zod";
import { registerV1EERoutes } from "@app/ee/routes/v1";
import { auditLogDALFactory } from "@app/ee/services/audit-log/audit-log-dal";
import { auditLogQueueServiceFactory } from "@app/ee/services/audit-log/audit-log-queue";
import { auditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service";
import { licenseDALFactory } from "@app/ee/services/license/license-dal";
import { licenseServiceFactory } from "@app/ee/services/license/license-service";
import { permissionDALFactory } from "@app/ee/services/permission/permission-dal";
import { permissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { samlConfigDALFactory } from "@app/ee/services/saml-config/saml-config-dal";
import { samlConfigServiceFactory } from "@app/ee/services/saml-config/saml-config-service";
import { secretApprovalPolicyApproverDALFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-approver-dal";
import { secretApprovalPolicyDALFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-dal";
import { secretApprovalPolicyServiceFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-service";
import { secretApprovalRequestDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-dal";
import { secretApprovalRequestReviewerDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-reviewer-dal";
import { secretApprovalRequestSecretDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-secret-dal";
import { secretApprovalRequestServiceFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-service";
import { secretRotationDALFactory } from "@app/ee/services/secret-rotation/secret-rotation-dal";
import { secretRotationQueueFactory } from "@app/ee/services/secret-rotation/secret-rotation-queue";
import { secretRotationServiceFactory } from "@app/ee/services/secret-rotation/secret-rotation-service";
import { gitAppDALFactory } from "@app/ee/services/secret-scanning/git-app-dal";
import { gitAppInstallSessionDALFactory } from "@app/ee/services/secret-scanning/git-app-install-session-dal";
import { secretScanningDALFactory } from "@app/ee/services/secret-scanning/secret-scanning-dal";
import { secretScanningQueueFactory } from "@app/ee/services/secret-scanning/secret-scanning-queue";
import { secretScanningServiceFactory } from "@app/ee/services/secret-scanning/secret-scanning-service";
import { secretSnapshotServiceFactory } from "@app/ee/services/secret-snapshot/secret-snapshot-service";
import { snapshotDALFactory } from "@app/ee/services/secret-snapshot/snapshot-dal";
import { snapshotFolderDALFactory } from "@app/ee/services/secret-snapshot/snapshot-folder-dal";
import { snapshotSecretDALFactory } from "@app/ee/services/secret-snapshot/snapshot-secret-dal";
import { trustedIpDALFactory } from "@app/ee/services/trusted-ip/trusted-ip-dal";
import { trustedIpServiceFactory } from "@app/ee/services/trusted-ip/trusted-ip-service";
import { getConfig } from "@app/lib/config/env";
import { TQueueServiceFactory } from "@app/queue";
import { apiKeyDALFactory } from "@app/services/api-key/api-key-dal";
import { apiKeyServiceFactory } from "@app/services/api-key/api-key-service";
import { authDALFactory } from "@app/services/auth/auth-dal";
import { authLoginServiceFactory } from "@app/services/auth/auth-login-service";
import { authPaswordServiceFactory } from "@app/services/auth/auth-password-service";
import { authSignupServiceFactory } from "@app/services/auth/auth-signup-service";
import { tokenDALFactory } from "@app/services/auth-token/auth-token-dal";
import { tokenServiceFactory } from "@app/services/auth-token/auth-token-service";
import { identityDALFactory } from "@app/services/identity/identity-dal";
import { identityOrgDALFactory } from "@app/services/identity/identity-org-dal";
import { identityServiceFactory } from "@app/services/identity/identity-service";
import { identityAccessTokenDALFactory } from "@app/services/identity-access-token/identity-access-token-dal";
import { identityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service";
import { identityProjectDALFactory } from "@app/services/identity-project/identity-project-dal";
import { identityProjectServiceFactory } from "@app/services/identity-project/identity-project-service";
import { identityUaClientSecretDALFactory } from "@app/services/identity-ua/identity-ua-client-secret-dal";
import { identityUaDALFactory } from "@app/services/identity-ua/identity-ua-dal";
import { identityUaServiceFactory } from "@app/services/identity-ua/identity-ua-service";
import { integrationDALFactory } from "@app/services/integration/integration-dal";
import { integrationServiceFactory } from "@app/services/integration/integration-service";
import { integrationAuthDALFactory } from "@app/services/integration-auth/integration-auth-dal";
import { integrationAuthServiceFactory } from "@app/services/integration-auth/integration-auth-service";
import { incidentContactDALFactory } from "@app/services/org/incident-contacts-dal";
import { orgBotDALFactory } from "@app/services/org/org-bot-dal";
import { orgDALFactory } from "@app/services/org/org-dal";
import { orgRoleDALFactory } from "@app/services/org/org-role-dal";
import { orgRoleServiceFactory } from "@app/services/org/org-role-service";
import { orgServiceFactory } from "@app/services/org/org-service";
import { projectDALFactory } from "@app/services/project/project-dal";
import { projectServiceFactory } from "@app/services/project/project-service";
import { projectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
import { projectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
import { projectEnvDALFactory } from "@app/services/project-env/project-env-dal";
import { projectEnvServiceFactory } from "@app/services/project-env/project-env-service";
import { projectKeyDALFactory } from "@app/services/project-key/project-key-dal";
import { projectKeyServiceFactory } from "@app/services/project-key/project-key-service";
import { projectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
import { projectMembershipServiceFactory } from "@app/services/project-membership/project-membership-service";
import { projectRoleDALFactory } from "@app/services/project-role/project-role-dal";
import { projectRoleServiceFactory } from "@app/services/project-role/project-role-service";
import { secretDALFactory } from "@app/services/secret/secret-dal";
import { secretQueueFactory } from "@app/services/secret/secret-queue";
import { secretServiceFactory } from "@app/services/secret/secret-service";
import { secretVersionDALFactory } from "@app/services/secret/secret-version-dal";
import { secretVersionTagDALFactory } from "@app/services/secret/secret-version-tag-dal";
import { secretBlindIndexDALFactory } from "@app/services/secret-blind-index/secret-blind-index-dal";
import { secretBlindIndexServiceFactory } from "@app/services/secret-blind-index/secret-blind-index-service";
import { secretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal";
import { secretFolderServiceFactory } from "@app/services/secret-folder/secret-folder-service";
import { secretFolderVersionDALFactory } from "@app/services/secret-folder/secret-folder-version-dal";
import { secretImportDALFactory } from "@app/services/secret-import/secret-import-dal";
import { secretImportServiceFactory } from "@app/services/secret-import/secret-import-service";
import { secretTagDALFactory } from "@app/services/secret-tag/secret-tag-dal";
import { secretTagServiceFactory } from "@app/services/secret-tag/secret-tag-service";
import { serviceTokenDALFactory } from "@app/services/service-token/service-token-dal";
import { serviceTokenServiceFactory } from "@app/services/service-token/service-token-service";
import { TSmtpService } from "@app/services/smtp/smtp-service";
import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal";
import { getServerCfg, superAdminServiceFactory } from "@app/services/super-admin/super-admin-service";
import { telemetryServiceFactory } from "@app/services/telemetry/telemetry-service";
import { userDALFactory } from "@app/services/user/user-dal";
import { userServiceFactory } from "@app/services/user/user-service";
import { webhookDALFactory } from "@app/services/webhook/webhook-dal";
import { webhookServiceFactory } from "@app/services/webhook/webhook-service";
import { injectAuditLogInfo } from "../plugins/audit-log";
import { injectIdentity } from "../plugins/auth/inject-identity";
import { injectPermission } from "../plugins/auth/inject-permission";
import { registerSecretScannerGhApp } from "../plugins/secret-scanner";
import { registerV1Routes } from "./v1";
import { registerV2Routes } from "./v2";
import { registerV3Routes } from "./v3";
export const registerRoutes = async (
server: FastifyZodProvider,
{
db,
smtp: smtpService,
queue: queueService
}: { db: Knex; smtp: TSmtpService; queue: TQueueServiceFactory }
) => {
server.register(registerSecretScannerGhApp, { prefix: "/ss-webhook" });
// db layers
const userDAL = userDALFactory(db);
const authDAL = authDALFactory(db);
const authTokenDAL = tokenDALFactory(db);
const orgDAL = orgDALFactory(db);
const orgBotDAL = orgBotDALFactory(db);
const incidentContactDAL = incidentContactDALFactory(db);
const orgRoleDAL = orgRoleDALFactory(db);
const superAdminDAL = superAdminDALFactory(db);
const apiKeyDAL = apiKeyDALFactory(db);
const projectDAL = projectDALFactory(db);
const projectMembershipDAL = projectMembershipDALFactory(db);
const projectRoleDAL = projectRoleDALFactory(db);
const projectEnvDAL = projectEnvDALFactory(db);
const projectKeyDAL = projectKeyDALFactory(db);
const projectBotDAL = projectBotDALFactory(db);
const secretDAL = secretDALFactory(db);
const secretTagDAL = secretTagDALFactory(db);
const folderDAL = secretFolderDALFactory(db);
const folderVersionDAL = secretFolderVersionDALFactory(db);
const secretImportDAL = secretImportDALFactory(db);
const secretVersionDAL = secretVersionDALFactory(db);
const secretVersionTagDAL = secretVersionTagDALFactory(db);
const secretBlindIndexDAL = secretBlindIndexDALFactory(db);
const integrationDAL = integrationDALFactory(db);
const integrationAuthDAL = integrationAuthDALFactory(db);
const webhookDAL = webhookDALFactory(db);
const serviceTokenDAL = serviceTokenDALFactory(db);
const identityDAL = identityDALFactory(db);
const identityAccessTokenDAL = identityAccessTokenDALFactory(db);
const identityOrgMembershipDAL = identityOrgDALFactory(db);
const identityProjectDAL = identityProjectDALFactory(db);
const identityUaDAL = identityUaDALFactory(db);
const identityUaClientSecretDAL = identityUaClientSecretDALFactory(db);
const auditLogDAL = auditLogDALFactory(db);
const trustedIpDAL = trustedIpDALFactory(db);
// ee db layer ops
const permissionDAL = permissionDALFactory(db);
const samlConfigDAL = samlConfigDALFactory(db);
const sapApproverDAL = secretApprovalPolicyApproverDALFactory(db);
const secretApprovalPolicyDAL = secretApprovalPolicyDALFactory(db);
const secretApprovalRequestDAL = secretApprovalRequestDALFactory(db);
const sarReviewerDAL = secretApprovalRequestReviewerDALFactory(db);
const sarSecretDAL = secretApprovalRequestSecretDALFactory(db);
const secretRotationDAL = secretRotationDALFactory(db);
const snapshotDAL = snapshotDALFactory(db);
const snapshotSecretDAL = snapshotSecretDALFactory(db);
const snapshotFolderDAL = snapshotFolderDALFactory(db);
const gitAppInstallSessionDAL = gitAppInstallSessionDALFactory(db);
const gitAppOrgDAL = gitAppDALFactory(db);
const secretScanningDAL = secretScanningDALFactory(db);
const licenseDAL = licenseDALFactory(db);
const permissionService = permissionServiceFactory({
permissionDAL,
orgRoleDAL,
projectRoleDAL,
serviceTokenDAL
});
const licenseService = licenseServiceFactory({ permissionService, orgDAL, licenseDAL });
const trustedIpService = trustedIpServiceFactory({
licenseService,
projectDAL,
trustedIpDAL,
permissionService
});
const auditLogQueue = auditLogQueueServiceFactory({
auditLogDAL,
queueService,
projectDAL,
licenseService
});
const auditLogService = auditLogServiceFactory({ auditLogDAL, permissionService, auditLogQueue });
const sapService = secretApprovalPolicyServiceFactory({
projectMembershipDAL,
projectEnvDAL,
secretApprovalPolicyApproverDAL: sapApproverDAL,
permissionService,
secretApprovalPolicyDAL
});
const samlService = samlConfigServiceFactory({
permissionService,
orgBotDAL,
orgDAL,
userDAL,
samlConfigDAL,
licenseService
});
const telemetryService = telemetryServiceFactory();
const tokenService = tokenServiceFactory({ tokenDAL: authTokenDAL, userDAL });
const userService = userServiceFactory({ userDAL });
const loginService = authLoginServiceFactory({ userDAL, smtpService, tokenService });
const passwordService = authPaswordServiceFactory({
tokenService,
smtpService,
authDAL,
userDAL
});
const orgService = orgServiceFactory({
licenseService,
samlConfigDAL,
orgRoleDAL,
permissionService,
orgDAL,
incidentContactDAL,
tokenService,
smtpService,
userDAL,
orgBotDAL
});
const signupService = authSignupServiceFactory({
tokenService,
smtpService,
authDAL,
userDAL,
orgDAL,
orgService,
licenseService
});
const orgRoleService = orgRoleServiceFactory({ permissionService, orgRoleDAL });
const superAdminService = superAdminServiceFactory({
userDAL,
authService: loginService,
serverCfgDAL: superAdminDAL,
orgService
});
const apiKeyService = apiKeyServiceFactory({ apiKeyDAL, userDAL });
const secretScanningQueue = secretScanningQueueFactory({
telemetryService,
smtpService,
secretScanningDAL,
queueService,
orgMembershipDAL: orgDAL
});
const secretScanningService = secretScanningServiceFactory({
permissionService,
gitAppOrgDAL,
gitAppInstallSessionDAL,
secretScanningDAL,
secretScanningQueue
});
const projectService = projectServiceFactory({
permissionService,
projectDAL,
secretBlindIndexDAL,
projectEnvDAL,
projectMembershipDAL,
folderDAL,
licenseService
});
const projectMembershipService = projectMembershipServiceFactory({
projectMembershipDAL,
projectDAL,
permissionService,
orgDAL,
userDAL,
smtpService,
projectKeyDAL,
projectRoleDAL,
licenseService
});
const projectEnvService = projectEnvServiceFactory({
permissionService,
projectEnvDAL,
licenseService,
projectDAL,
folderDAL
});
const projectKeyService = projectKeyServiceFactory({
permissionService,
projectKeyDAL,
projectMembershipDAL
});
const projectRoleService = projectRoleServiceFactory({ permissionService, projectRoleDAL });
const snapshotService = secretSnapshotServiceFactory({
permissionService,
licenseService,
folderDAL,
secretDAL,
snapshotDAL,
snapshotFolderDAL,
snapshotSecretDAL,
secretVersionDAL,
folderVersionDAL,
secretTagDAL,
secretVersionTagDAL
});
const webhookService = webhookServiceFactory({
permissionService,
webhookDAL,
projectEnvDAL
});
const secretTagService = secretTagServiceFactory({ secretTagDAL, permissionService });
const folderService = secretFolderServiceFactory({
permissionService,
folderDAL,
folderVersionDAL,
projectEnvDAL,
snapshotService
});
const secretImportService = secretImportServiceFactory({
projectEnvDAL,
folderDAL,
permissionService,
secretImportDAL,
secretDAL
});
const projectBotService = projectBotServiceFactory({ permissionService, projectBotDAL });
const integrationAuthService = integrationAuthServiceFactory({
integrationAuthDAL,
integrationDAL,
permissionService,
projectBotDAL,
projectBotService
});
const secretQueueService = secretQueueFactory({
queueService,
secretDAL,
folderDAL,
integrationAuthService,
projectBotService,
integrationDAL,
secretImportDAL,
projectEnvDAL,
webhookDAL,
orgDAL,
projectMembershipDAL,
smtpService,
projectDAL
});
const secretBlindIndexService = secretBlindIndexServiceFactory({
permissionService,
secretDAL,
secretBlindIndexDAL
});
const secretService = secretServiceFactory({
folderDAL,
secretVersionDAL,
secretVersionTagDAL,
secretBlindIndexDAL,
permissionService,
secretDAL,
secretTagDAL,
snapshotService,
secretQueueService,
secretImportDAL,
projectBotService
});
const sarService = secretApprovalRequestServiceFactory({
permissionService,
folderDAL,
secretTagDAL,
secretApprovalRequestSecretDAL: sarSecretDAL,
secretApprovalRequestReviewerDAL: sarReviewerDAL,
secretVersionDAL,
secretBlindIndexDAL,
secretApprovalRequestDAL,
secretService,
snapshotService,
secretQueueService
});
const secretRotationQueue = secretRotationQueueFactory({
telemetryService,
secretRotationDAL,
queue: queueService,
secretDAL,
secretVersionDAL,
projectBotService
});
const secretRotationService = secretRotationServiceFactory({
permissionService,
secretRotationDAL,
secretRotationQueue,
projectDAL,
licenseService,
secretDAL,
folderDAL
});
const integrationService = integrationServiceFactory({
permissionService,
folderDAL,
integrationDAL,
integrationAuthDAL,
secretQueueService
});
const serviceTokenService = serviceTokenServiceFactory({
projectEnvDAL,
serviceTokenDAL,
permissionService
});
const identityService = identityServiceFactory({
permissionService,
identityDAL,
identityOrgMembershipDAL
});
const identityAccessTokenService = identityAccessTokenServiceFactory({ identityAccessTokenDAL });
const identityProjectService = identityProjectServiceFactory({
permissionService,
projectDAL,
identityProjectDAL,
identityOrgMembershipDAL
});
const identityUaService = identityUaServiceFactory({
identityOrgMembershipDAL,
permissionService,
identityDAL,
identityAccessTokenDAL,
identityUaClientSecretDAL,
identityUaDAL,
licenseService
});
await superAdminService.initServerCfg();
// setup the communication with license key server
await licenseService.init();
// inject all services
server.decorate<FastifyZodProvider["services"]>("services", {
login: loginService,
password: passwordService,
signup: signupService,
user: userService,
permission: permissionService,
org: orgService,
orgRole: orgRoleService,
apiKey: apiKeyService,
authToken: tokenService,
superAdmin: superAdminService,
project: projectService,
projectMembership: projectMembershipService,
projectKey: projectKeyService,
projectEnv: projectEnvService,
projectRole: projectRoleService,
secret: secretService,
secretTag: secretTagService,
folder: folderService,
secretImport: secretImportService,
projectBot: projectBotService,
integration: integrationService,
integrationAuth: integrationAuthService,
webhook: webhookService,
serviceToken: serviceTokenService,
identity: identityService,
identityAccessToken: identityAccessTokenService,
identityProject: identityProjectService,
identityUa: identityUaService,
secretApprovalPolicy: sapService,
secretApprovalRequest: sarService,
secretRotation: secretRotationService,
snapshot: snapshotService,
saml: samlService,
auditLog: auditLogService,
secretScanning: secretScanningService,
license: licenseService,
trustedIp: trustedIpService,
secretBlindIndex: secretBlindIndexService,
telemetry: telemetryService
});
server.decorate<FastifyZodProvider["store"]>("store", {
user: userDAL
});
await server.register(injectIdentity, { userDAL, serviceTokenDAL });
await server.register(injectPermission);
await server.register(injectAuditLogInfo);
server.route({
url: "/api/status",
method: "GET",
schema: {
response: {
200: z.object({
date: z.date(),
message: z.literal("Ok"),
emailConfigured: z.boolean().optional(),
inviteOnlySignup: z.boolean().optional(),
redisConfigured: z.boolean().optional(),
secretScanningConfigured: z.boolean().optional()
})
}
},
handler: () => {
const cfg = getConfig();
const serverCfg = getServerCfg()
return {
date: new Date(),
message: "Ok" as const,
emailConfigured: cfg.isSmtpConfigured,
inviteOnlySignup: Boolean(serverCfg.allowSignUp),
redisConfigured: cfg.isRedisConfigured,
secretScanningConfigured: cfg.isSecretScanningConfigured,
};
}
});
// register routes for v1
await server.register(
async (v1Server) => {
await v1Server.register(registerV1EERoutes);
await v1Server.register(registerV1Routes);
},
{ prefix: "/api/v1" }
);
await server.register(registerV2Routes, { prefix: "/api/v2" });
await server.register(registerV3Routes, { prefix: "/api/v3" });
};
@@ -0,0 +1,41 @@
import { z } from "zod";
import { IntegrationAuthsSchema, SecretApprovalPoliciesSchema } from "@app/db/schemas";
// sometimes the return data must be santizied to avoid leaking important values
// always prefer pick over omit in zod
export const integrationAuthPubSchema = IntegrationAuthsSchema.pick({
id: true,
projectId: true,
integration: true,
teamId: true,
url: true,
namespace: true,
accountId: true,
metadata: true,
createdAt: true,
updatedAt: true
});
export const sapPubSchema = SecretApprovalPoliciesSchema.merge(
z.object({
environment: z.object({
id: z.string(),
name: z.string(),
slug: z.string()
}),
projectId: z.string()
})
);
export const secretRawSchema = z.object({
id: z.string(),
_id: z.string(),
workspace: z.string(),
environment: z.string(),
version: z.number(),
type: z.string(),
secretKey: z.string(),
secretValue: z.string(),
secretComment: z.string().optional()
});
@@ -0,0 +1,114 @@
import { z } from "zod";
import { SuperAdminSchema, UsersSchema } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env";
import { UnauthorizedError } from "@app/lib/errors";
import { verifySuperAdmin } from "@app/server/plugins/auth/superAdmin";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
export const registerAdminRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/config",
method: "GET",
schema: {
response: {
200: z.object({
config: SuperAdminSchema
})
}
},
handler: () => {
const config = getServerCfg();
return { config };
}
});
server.route({
url: "/config",
method: "PATCH",
schema: {
body: z.object({
allowSignUp: z.boolean().optional()
}),
response: {
200: z.object({
config: SuperAdminSchema
})
}
},
onRequest: (req, _, done) => {
verifyAuth([AuthMode.JWT, AuthMode.API_KEY])(req);
verifySuperAdmin(req);
done();
},
handler: async (req) => {
const config = await server.services.superAdmin.updateServerCfg(req.body);
return { config };
}
});
server.route({
url: "/signup",
method: "POST",
schema: {
body: z.object({
email: z.string().email().trim(),
firstName: z.string().trim(),
lastName: z.string().trim().optional(),
protectedKey: z.string().trim(),
protectedKeyIV: z.string().trim(),
protectedKeyTag: z.string().trim(),
publicKey: z.string().trim(),
encryptedPrivateKey: z.string().trim(),
encryptedPrivateKeyIV: z.string().trim(),
encryptedPrivateKeyTag: z.string().trim(),
salt: z.string().trim(),
verifier: z.string().trim()
}),
response: {
200: z.object({
message: z.string(),
user: UsersSchema,
token: z.string()
})
}
},
handler: async (req, res) => {
const appCfg = getConfig();
const serverCfg = getServerCfg();
if (serverCfg.initialized)
throw new UnauthorizedError({ name: "Admin sign up", message: "Admin has been created" });
const { user, token } = await server.services.superAdmin.adminSignUp({
...req.body,
ip: req.realIp,
userAgent: req.headers["user-agent"] || ""
});
server.services.telemetry.sendPostHogEvents({
event: PostHogEventTypes.AdminInit,
distinctId: user.user.email,
properties: {
email: user.user.email,
lastName: user.user.lastName || "",
firstName: user.user.firstName || ""
}
});
res.setCookie("jid", token.refresh, {
httpOnly: true,
path: "/",
sameSite: "strict",
secure: appCfg.HTTPS_ENABLED
});
return {
message: "Successfully set up admin account",
user: user.user,
token: token.access
};
}
});
};
+108
View File
@@ -0,0 +1,108 @@
import jwt from "jsonwebtoken";
import { z } from "zod";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
import { authRateLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import {
AuthMode,
AuthModeRefreshJwtTokenPayload,
AuthTokenType
} from "@app/services/auth/auth-type";
export const registerAuthRoutes = async (server: FastifyZodProvider) => {
server.route({
url: "/logout",
method: "POST",
config:{
rateLimit:authRateLimit
},
schema: {
response: {
200: z.object({
message: z.string()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req, res) => {
const appCfg = getConfig();
if (req.auth.authMode === AuthMode.JWT) {
await server.services.login.logout(req.permission.id, req.auth.tokenVersionId);
}
res.cookie("jid", "", {
httpOnly: true,
path: "/",
sameSite: "strict",
secure: appCfg.HTTPS_ENABLED
});
return { message: "Successfully logged out" };
}
});
server.route({
url: "/checkAuth",
method: "POST",
schema: {
response: {
200: z.object({
message: z.literal("Authenticated")
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: () => ({ message: "Authenticated" as const })
});
server.route({
url: "/token",
method: "POST",
schema: {
response: {
200: z.object({
token: z.string()
})
}
},
handler: async (req) => {
const refreshToken = req.cookies.jid;
const appCfg = getConfig();
if (!refreshToken)
throw new BadRequestError({
name: "Auth token route",
message: "Failed to find refresh token"
});
const decodedToken = jwt.verify(
refreshToken,
appCfg.AUTH_SECRET
) as AuthModeRefreshJwtTokenPayload;
if (decodedToken.authTokenType !== AuthTokenType.REFRESH_TOKEN)
throw new UnauthorizedError({ message: "Invalid token", name: "Auth token route" });
const tokenVersion = await server.services.authToken.getUserTokenSessionById(
decodedToken.tokenVersionId,
decodedToken.userId
);
if (!tokenVersion)
throw new UnauthorizedError({ message: "Invalid token", name: "Auth token route" });
if (decodedToken.refreshVersion !== tokenVersion.refreshVersion)
throw new UnauthorizedError({ message: "Invalid token", name: "Auth token route" });
const token = jwt.sign(
{
authTokenType: AuthTokenType.ACCESS_TOKEN,
userId: decodedToken.userId,
tokenVersionId: tokenVersion.id,
accessVersion: tokenVersion.accessVersion
},
appCfg.AUTH_SECRET,
{ expiresIn: appCfg.JWT_AUTH_LIFETIME }
);
return { token };
}
});
};
@@ -0,0 +1,78 @@
import { z } from "zod";
import { ProjectBotsSchema } from "@app/db/schemas";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
export const registerProjectBotRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/:projectId",
method: "GET",
schema: {
params: z.object({
projectId: z.string().trim()
}),
response: {
200: z.object({
bot: ProjectBotsSchema.omit({
iv: true,
encryptedPrivateKey: true,
tag: true,
algorithm: true,
keyEncoding: true
})
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const bot = await server.services.projectBot.findBotByProjectId({
actor: req.permission.type,
actorId: req.permission.id,
projectId: req.params.projectId
});
return { bot };
}
});
server.route({
url: "/:botId/active",
method: "PATCH",
schema: {
body: z.object({
isActive: z.boolean(),
botKey: z
.object({
nonce: z.string().trim().optional(),
encryptedKey: z.string().trim().optional()
})
.optional()
}),
params: z.object({
botId: z.string().trim()
}),
response: {
200: z.object({
bot: ProjectBotsSchema.omit({
iv: true,
encryptedPrivateKey: true,
tag: true,
algorithm: true,
keyEncoding: true
})
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const bot = await server.services.projectBot.setBotActiveState({
actor: req.permission.type,
actorId: req.permission.id,
botId: req.params.botId,
botKey: req.body.botKey,
isActive: req.body.isActive
});
return { bot };
}
});
};
@@ -0,0 +1,33 @@
import { z } from "zod";
export const registerIdentityAccessTokenRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/token/renew",
method: "POST",
schema: {
body: z.object({
accessToken: z.string().trim()
}),
response: {
200: z.object({
accessToken: z.string(),
expiresIn: z.coerce.number(),
accessTokenMaxTTL: z.coerce.number(),
tokenType: z.literal("Bearer")
})
}
},
handler: async (req) => {
const { accessToken, identityAccessToken } =
await server.services.identityAccessToken.renewAccessToken({
accessToken: req.body.accessToken
});
return {
accessToken,
tokenType: "Bearer" as const,
expiresIn: identityAccessToken.accessTokenTTL,
accessTokenMaxTTL: identityAccessToken.accessTokenMaxTTL
};
}
});
};
@@ -0,0 +1,125 @@
import { z } from "zod";
import { IdentitiesSchema, OrgMembershipRole } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
export const registerIdentityRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
body: z.object({
name: z.string().trim(),
organizationId: z.string().trim(),
role: z.string().trim().min(1).default(OrgMembershipRole.NoAccess)
}),
response: {
200: z.object({
identity: IdentitiesSchema
})
}
},
handler: async (req) => {
const identity = await server.services.identity.createIdentity({
actor: req.permission.type,
actorId: req.permission.id,
...req.body,
orgId: req.body.organizationId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.body.organizationId,
event: {
type: EventType.CREATE_IDENTITY,
metadata: {
name: identity.name,
identityId: identity.id
}
}
});
return { identity };
}
});
server.route({
method: "PATCH",
url: "/:identityId",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
identityId: z.string()
}),
body: z.object({
name: z.string().trim().optional(),
role: z.string().trim().min(1).optional()
}),
response: {
200: z.object({
identity: IdentitiesSchema
})
}
},
handler: async (req) => {
const identity = await server.services.identity.updateIdentity({
actor: req.permission.type,
actorId: req.permission.id,
id: req.params.identityId,
...req.body
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identity.orgId,
event: {
type: EventType.UPDATE_IDENTITY,
metadata: {
name: identity.name,
identityId: identity.id
}
}
});
return { identity };
}
});
server.route({
method: "DELETE",
url: "/:identityId",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
identityId: z.string()
}),
response: {
200: z.object({
identity: IdentitiesSchema
})
}
},
handler: async (req) => {
const identity = await server.services.identity.deleteIdentity({
actor: req.permission.type,
actorId: req.permission.id,
id: req.params.identityId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identity.orgId,
event: {
type: EventType.DELETE_IDENTITY,
metadata: {
identityId: identity.id
}
}
});
return { identity };
}
});
};
+368
View File
@@ -0,0 +1,368 @@
import { z } from "zod";
import { IdentityUaClientSecretsSchema, IdentityUniversalAuthsSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { TIdentityTrustedIp } from "@app/services/identity/identity-types";
export const sanitizedClientSecretSchema = IdentityUaClientSecretsSchema.pick({
id: true,
createdAt: true,
updatedAt: true,
description: true,
clientSecretPrefix: true,
clientSecretNumUses: true,
clientSecretNumUsesLimit: true,
clientSecretTTL: true,
identityUAId: true,
isClientSecretRevoked: true
});
export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/universal-auth/login",
method: "POST",
schema: {
body: z.object({
clientId: z.string().trim(),
clientSecret: z.string().trim()
}),
response: {
200: z.object({
accessToken: z.string(),
expiresIn: z.coerce.number(),
accessTokenMaxTTL: z.coerce.number(),
tokenType: z.literal("Bearer")
})
}
},
handler: async (req) => {
const { identityUa, accessToken, identityAccessToken, validClientSecretInfo } =
await server.services.identityUa.login(
req.body.clientId,
req.body.clientSecret,
req.realIp
);
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
event: {
type: EventType.LOGIN_IDENTITY_UNIVERSAL_AUTH,
metadata: {
clientSecretId: validClientSecretInfo.id,
identityId: identityUa.identityId,
identityAccessTokenId: identityAccessToken.id,
identityUniversalAuthId: identityUa.id
}
}
});
return {
accessToken,
tokenType: "Bearer" as const,
expiresIn: identityUa.accessTokenTTL,
accessTokenMaxTTL: identityUa.accessTokenMaxTTL
};
}
});
server.route({
url: "/universal-auth/identities/:identityId",
method: "POST",
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
params: z.object({
identityId: z.string().trim()
}),
body: z.object({
clientSecretTrustedIps: z
.object({
ipAddress: z.string().trim()
})
.array()
.min(1)
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]),
accessTokenTrustedIps: z
.object({
ipAddress: z.string().trim()
})
.array()
.min(1)
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]),
accessTokenTTL: z
.number()
.int()
.min(1)
.refine((value) => value !== 0, {
message: "accessTokenTTL must have a non zero number"
})
.default(2592000),
accessTokenMaxTTL: z
.number()
.int()
.refine((value) => value !== 0, {
message: "accessTokenMaxTTL must have a non zero number"
})
.default(2592000), // 30 days
accessTokenNumUsesLimit: z.number().int().min(0).default(0)
}),
response: {
200: z.object({
identityUniversalAuth: IdentityUniversalAuthsSchema
})
}
},
handler: async (req) => {
const identityUniversalAuth = await server.services.identityUa.attachUa({
actor: req.permission.type,
actorId: req.permission.id,
...req.body,
identityId: req.params.identityId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityUniversalAuth.orgId,
event: {
type: EventType.ADD_IDENTITY_UNIVERSAL_AUTH,
metadata: {
identityId: identityUniversalAuth.identityId,
accessTokenTTL: identityUniversalAuth.accessTokenTTL,
accessTokenMaxTTL: identityUniversalAuth.accessTokenMaxTTL,
accessTokenTrustedIps:
identityUniversalAuth.accessTokenTrustedIps as TIdentityTrustedIp[],
clientSecretTrustedIps:
identityUniversalAuth.clientSecretTrustedIps as TIdentityTrustedIp[],
accessTokenNumUsesLimit: identityUniversalAuth.accessTokenNumUsesLimit
}
}
});
return { identityUniversalAuth };
}
});
server.route({
url: "/universal-auth/identities/:identityId",
method: "PATCH",
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
params: z.object({
identityId: z.string()
}),
body: z.object({
clientSecretTrustedIps: z
.object({
ipAddress: z.string().trim()
})
.array()
.min(1)
.optional(),
accessTokenTrustedIps: z
.object({
ipAddress: z.string().trim()
})
.array()
.min(1)
.optional(),
accessTokenTTL: z.number().int().min(0).optional(),
accessTokenNumUsesLimit: z.number().int().min(0).optional(),
accessTokenMaxTTL: z
.number()
.int()
.refine((value) => value !== 0, {
message: "accessTokenMaxTTL must have a non zero number"
})
.optional()
}),
response: {
200: z.object({
identityUniversalAuth: IdentityUniversalAuthsSchema
})
}
},
handler: async (req) => {
const identityUniversalAuth = await server.services.identityUa.updateUa({
actor: req.permission.type,
actorId: req.permission.id,
...req.body,
identityId: req.params.identityId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityUniversalAuth.orgId,
event: {
type: EventType.UPDATE_IDENTITY_UNIVERSAL_AUTH,
metadata: {
identityId: identityUniversalAuth.identityId,
accessTokenTTL: identityUniversalAuth.accessTokenTTL,
accessTokenMaxTTL: identityUniversalAuth.accessTokenMaxTTL,
accessTokenTrustedIps:
identityUniversalAuth.accessTokenTrustedIps as TIdentityTrustedIp[],
clientSecretTrustedIps:
identityUniversalAuth.clientSecretTrustedIps as TIdentityTrustedIp[],
accessTokenNumUsesLimit: identityUniversalAuth.accessTokenNumUsesLimit
}
}
});
return { identityUniversalAuth };
}
});
server.route({
url: "/universal-auth/identities/:identityId",
method: "GET",
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
params: z.object({
identityId: z.string()
}),
response: {
200: z.object({
identityUniversalAuth: IdentityUniversalAuthsSchema
})
}
},
handler: async (req) => {
const identityUniversalAuth = await server.services.identityUa.getIdentityUa({
actor: req.permission.type,
actorId: req.permission.id,
identityId: req.params.identityId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityUniversalAuth.orgId,
event: {
type: EventType.GET_IDENTITY_UNIVERSAL_AUTH,
metadata: {
identityId: identityUniversalAuth.identityId
}
}
});
return { identityUniversalAuth };
}
});
server.route({
url: "/universal-auth/identities/:identityId/client-secrets",
method: "POST",
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
params: z.object({
identityId: z.string()
}),
body: z.object({
description: z.string().trim().default(""),
numUsesLimit: z.number().min(0).default(0),
ttl: z.number().min(0).default(0)
}),
response: {
200: z.object({
clientSecret: z.string(),
clientSecretData: sanitizedClientSecretSchema
})
}
},
handler: async (req) => {
const { clientSecret, clientSecretData, orgId } =
await server.services.identityUa.createUaClientSecret({
actor: req.permission.type,
actorId: req.permission.id,
identityId: req.params.identityId,
...req.body
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId,
event: {
type: EventType.CREATE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET,
metadata: {
identityId: req.params.identityId,
clientSecretId: clientSecretData.id
}
}
});
return { clientSecret, clientSecretData };
}
});
server.route({
url: "/universal-auth/identities/:identityId/client-secrets",
method: "GET",
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
params: z.object({
identityId: z.string()
}),
response: {
200: z.object({
clientSecretData: sanitizedClientSecretSchema.array()
})
}
},
handler: async (req) => {
const { clientSecrets: clientSecretData, orgId } =
await server.services.identityUa.getUaClientSecrets({
actor: req.permission.type,
actorId: req.permission.id,
identityId: req.params.identityId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId,
event: {
type: EventType.GET_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRETS,
metadata: {
identityId: req.params.identityId
}
}
});
return { clientSecretData };
}
});
server.route({
url: "/universal-auth/identities/:identityId/client-secrets/:clientSecretId/revoke",
method: "POST",
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
params: z.object({
identityId: z.string(),
clientSecretId: z.string()
}),
response: {
200: z.object({
clientSecretData: sanitizedClientSecretSchema
})
}
},
handler: async (req) => {
const clientSecretData = await server.services.identityUa.revokeUaClientSecret({
actor: req.permission.type,
actorId: req.permission.id,
identityId: req.params.identityId,
clientSecretId: req.params.clientSecretId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: clientSecretData.orgId,
event: {
type: EventType.REVOKE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET,
metadata: {
identityId: clientSecretData.identityId,
clientSecretId: clientSecretData.id
}
}
});
return { clientSecretData };
}
});
};
+59
View File
@@ -0,0 +1,59 @@
import { registerAdminRouter } from "./admin-router";
import { registerAuthRoutes } from "./auth-router";
import { registerProjectBotRouter } from "./bot-router";
import { registerIdentityAccessTokenRouter } from "./identity-access-token-router";
import { registerIdentityRouter } from "./identity-router";
import { registerIdentityUaRouter } from "./identity-ua";
import { registerIntegrationAuthRouter } from "./integration-auth-router";
import { registerIntegrationRouter } from "./integration-router";
import { registerInviteOrgRouter } from "./invite-org-router";
import { registerOrgRouter } from "./organization-router";
import { registerPasswordRouter } from "./password-router";
import { registerProjectEnvRouter } from "./project-env-router";
import { registerProjectKeyRouter } from "./project-key-router";
import { registerProjectMembershipRouter } from "./project-membership-router";
import { registerProjectRouter } from "./project-router";
import { registerSecretFolderRouter } from "./secret-folder-router";
import { registerSecretImportRouter } from "./secret-import-router";
import { registerSecretTagRouter } from "./secret-tag-router";
import { registerSsoRouter } from "./sso-router";
import { registerUserActionRouter } from "./user-action-router";
import { registerUserRouter } from "./user-router";
import { registerWebhookRouter } from "./webhook-router";
export const registerV1Routes = async (server: FastifyZodProvider) => {
await server.register(registerSsoRouter, { prefix: "/sso" });
await server.register(
async (authRouter) => {
await authRouter.register(registerAuthRoutes);
await authRouter.register(registerIdentityUaRouter);
await authRouter.register(registerIdentityAccessTokenRouter);
},
{ prefix: "/auth" }
);
await server.register(registerPasswordRouter, { prefix: "/password" });
await server.register(registerOrgRouter, { prefix: "/organization" });
await server.register(registerAdminRouter, { prefix: "/admin" });
await server.register(registerUserRouter, { prefix: "/user" });
await server.register(registerInviteOrgRouter, { prefix: "/invite-org" });
await server.register(registerUserActionRouter, { prefix: "/user-action" });
await server.register(registerSecretImportRouter, { prefix: "/secret-imports" });
await server.register(registerSecretFolderRouter, { prefix: "/folders" });
await server.register(
async (projectRouter) => {
await projectRouter.register(registerProjectRouter);
await projectRouter.register(registerProjectEnvRouter);
await projectRouter.register(registerProjectKeyRouter);
await projectRouter.register(registerProjectMembershipRouter);
await projectRouter.register(registerSecretTagRouter);
},
{ prefix: "/workspace" }
);
await server.register(registerProjectBotRouter, { prefix: "/bot" });
await server.register(registerIntegrationRouter, { prefix: "/integration" });
await server.register(registerIntegrationAuthRouter, { prefix: "/integration-auth" });
await server.register(registerWebhookRouter, { prefix: "/webhooks" });
await server.register(registerIdentityRouter, { prefix: "/identities" });
};
@@ -0,0 +1,656 @@
import { z } from "zod";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { integrationAuthPubSchema } from "../sanitizedSchemas";
export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/integration-options",
method: "GET",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
response: {
200: z.object({
integrationOptions: z
.object({
name: z.string(),
slug: z.string(),
clientSlug: z.string().optional(),
image: z.string(),
isAvailable: z.boolean().optional(),
type: z.string(),
clientId: z.string().optional(),
docsLink: z.string().optional()
})
.array()
})
}
},
handler: async () => {
const integrationOptions = await server.services.integrationAuth.getIntegrationOptions();
return { integrationOptions };
}
});
server.route({
url: "/:integrationAuthId",
method: "GET",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
integrationAuthId: z.string().trim()
}),
response: {
200: z.object({
integrationAuth: integrationAuthPubSchema
})
}
},
handler: async (req) => {
const integrationAuth = await server.services.integrationAuth.getIntegrationAuth({
actorId: req.permission.id,
actor: req.permission.type,
id: req.params.integrationAuthId
});
return { integrationAuth };
}
});
server.route({
url: "/",
method: "DELETE",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
querystring: z.object({
integration: z.string().trim(),
projectId: z.string().trim()
}),
response: {
200: z.object({
integrationAuth: integrationAuthPubSchema.array()
})
}
},
handler: async (req) => {
const integrationAuth = await server.services.integrationAuth.deleteIntegrationAuths({
actorId: req.permission.id,
actor: req.permission.type,
integration: req.query.integration,
projectId: req.query.projectId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.query.projectId,
event: {
type: EventType.UNAUTHORIZE_INTEGRATION,
metadata: {
integration: req.query.integration
}
}
});
return { integrationAuth };
}
});
server.route({
url: "/:integrationAuthId",
method: "DELETE",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
integrationAuthId: z.string().trim()
}),
response: {
200: z.object({
integrationAuth: integrationAuthPubSchema
})
}
},
handler: async (req) => {
const integrationAuth = await server.services.integrationAuth.deleteIntegrationAuthById({
actorId: req.permission.id,
actor: req.permission.type,
id: req.params.integrationAuthId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: integrationAuth.projectId,
event: {
type: EventType.UNAUTHORIZE_INTEGRATION,
metadata: {
integration: integrationAuth.integration
}
}
});
return { integrationAuth };
}
});
server.route({
url: "/oauth-token",
method: "POST",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
body: z.object({
workspaceId: z.string().trim(),
code: z.string().trim(),
integration: z.string().trim(),
url: z.string().trim().url().optional()
}),
response: {
200: z.object({
integrationAuth: integrationAuthPubSchema
})
}
},
handler: async (req) => {
const integrationAuth = await server.services.integrationAuth.oauthExchange({
actorId: req.permission.id,
actor: req.permission.type,
projectId: req.body.workspaceId,
...req.body
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.body.workspaceId,
event: {
type: EventType.AUTHORIZE_INTEGRATION,
metadata: {
integration: integrationAuth.integration
}
}
});
return { integrationAuth };
}
});
server.route({
url: "/access-token",
method: "POST",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
body: z.object({
workspaceId: z.string().trim(),
integration: z.string().trim(),
accessId: z.string().trim().optional(),
accessToken: z.string().trim().optional(),
url: z.string().url().trim().optional(),
namespace: z.string().trim().optional(),
refreshToken: z.string().trim().optional()
}),
response: {
200: z.object({
integrationAuth: integrationAuthPubSchema
})
}
},
handler: async (req) => {
const integrationAuth = await server.services.integrationAuth.saveIntegrationToken({
actorId: req.permission.id,
actor: req.permission.type,
projectId: req.body.workspaceId,
...req.body
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.body.workspaceId,
event: {
type: EventType.AUTHORIZE_INTEGRATION,
metadata: {
integration: integrationAuth.integration
}
}
});
return { integrationAuth };
}
});
server.route({
url: "/:integrationAuthId/apps",
method: "GET",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
integrationAuthId: z.string().trim()
}),
querystring: z.object({
teamId: z.string().trim().optional(),
workspaceSlug: z.string().trim().optional()
}),
response: {
200: z.object({
apps: z
.object({
name: z.string(),
appId: z.coerce.string().optional(),
owner: z.string().optional()
})
.array()
})
}
},
handler: async (req) => {
const apps = await server.services.integrationAuth.getIntegrationApps({
actorId: req.permission.id,
actor: req.permission.type,
id: req.params.integrationAuthId,
...req.query
});
return { apps };
}
});
server.route({
url: "/:integrationAuthId/teams",
method: "GET",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
integrationAuthId: z.string().trim()
}),
response: {
200: z.object({
teams: z
.object({
name: z.string(),
id: z.string().optional()
})
.array()
})
}
},
handler: async (req) => {
const teams = await server.services.integrationAuth.getIntegrationAuthTeams({
actorId: req.permission.id,
actor: req.permission.type,
id: req.params.integrationAuthId
});
return { teams };
}
});
server.route({
url: "/:integrationAuthId/vercel/branches",
method: "GET",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
integrationAuthId: z.string().trim()
}),
querystring: z.object({
appId: z.string().trim()
}),
response: {
200: z.object({
branches: z.string().array()
})
}
},
handler: async (req) => {
const branches = await server.services.integrationAuth.getVercelBranches({
actorId: req.permission.id,
actor: req.permission.type,
id: req.params.integrationAuthId,
appId: req.query.appId
});
return { branches };
}
});
server.route({
url: "/:integrationAuthId/checkly/groups",
method: "GET",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
integrationAuthId: z.string().trim()
}),
querystring: z.object({
accountId: z.string().trim()
}),
response: {
200: z.object({
groups: z.object({ name: z.string(), groupId: z.number() }).array()
})
}
},
handler: async (req) => {
const groups = await server.services.integrationAuth.getChecklyGroups({
actorId: req.permission.id,
actor: req.permission.type,
id: req.params.integrationAuthId,
accountId: req.query.accountId
});
return { groups };
}
});
server.route({
url: "/:integrationAuthId/qovery/orgs",
method: "GET",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
integrationAuthId: z.string().trim()
}),
response: {
200: z.object({
orgs: z.object({ name: z.string(), orgId: z.string() }).array()
})
}
},
handler: async (req) => {
const orgs = await server.services.integrationAuth.getQoveryOrgs({
actorId: req.permission.id,
actor: req.permission.type,
id: req.params.integrationAuthId
});
return { orgs };
}
});
server.route({
url: "/:integrationAuthId/qovery/projects",
method: "GET",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
integrationAuthId: z.string().trim()
}),
querystring: z.object({
orgId: z.string().trim()
}),
response: {
200: z.object({
projects: z.object({ name: z.string(), projectId: z.string() }).array()
})
}
},
handler: async (req) => {
const projects = await server.services.integrationAuth.getQoveryProjects({
actorId: req.permission.id,
actor: req.permission.type,
id: req.params.integrationAuthId,
orgId: req.query.orgId
});
return { projects };
}
});
server.route({
url: "/:integrationAuthId/qovery/environments",
method: "GET",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
integrationAuthId: z.string().trim()
}),
querystring: z.object({
projectId: z.string().trim()
}),
response: {
200: z.object({
environments: z.object({ name: z.string(), environmentId: z.string() }).array()
})
}
},
handler: async (req) => {
const environments = await server.services.integrationAuth.getQoveryEnvs({
actorId: req.permission.id,
actor: req.permission.type,
id: req.params.integrationAuthId,
projectId: req.query.projectId
});
return { environments };
}
});
server.route({
url: "/:integrationAuthId/qovery/apps",
method: "GET",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
integrationAuthId: z.string().trim()
}),
querystring: z.object({
environmentId: z.string().trim()
}),
response: {
200: z.object({
apps: z.object({ name: z.string(), appId: z.string() }).array()
})
}
},
handler: async (req) => {
const apps = await server.services.integrationAuth.getQoveryApps({
actorId: req.permission.id,
actor: req.permission.type,
id: req.params.integrationAuthId,
environmentId: req.query.environmentId
});
return { apps };
}
});
server.route({
url: "/:integrationAuthId/qovery/containers",
method: "GET",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
integrationAuthId: z.string().trim()
}),
querystring: z.object({
environmentId: z.string().trim()
}),
response: {
200: z.object({
containers: z.object({ name: z.string(), appId: z.string() }).array()
})
}
},
handler: async (req) => {
const containers = await server.services.integrationAuth.getQoveryContainers({
actorId: req.permission.id,
actor: req.permission.type,
id: req.params.integrationAuthId,
environmentId: req.query.environmentId
});
return { containers };
}
});
server.route({
url: "/:integrationAuthId/qovery/jobs",
method: "GET",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
integrationAuthId: z.string().trim()
}),
querystring: z.object({
environmentId: z.string().trim()
}),
response: {
200: z.object({
jobs: z.object({ name: z.string(), appId: z.string() }).array()
})
}
},
handler: async (req) => {
const jobs = await server.services.integrationAuth.getQoveryJobs({
actorId: req.permission.id,
actor: req.permission.type,
id: req.params.integrationAuthId,
environmentId: req.query.environmentId
});
return { jobs };
}
});
server.route({
url: "/:integrationAuthId/railway/environments",
method: "GET",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
integrationAuthId: z.string().trim()
}),
querystring: z.object({
appId: z.string().trim()
}),
response: {
200: z.object({
environments: z.object({ name: z.string(), environmentId: z.string() }).array()
})
}
},
handler: async (req) => {
const environments = await server.services.integrationAuth.getRailwayEnvironments({
actorId: req.permission.id,
actor: req.permission.type,
id: req.params.integrationAuthId,
appId: req.query.appId
});
return { environments };
}
});
server.route({
url: "/:integrationAuthId/railway/services",
method: "GET",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
integrationAuthId: z.string().trim()
}),
querystring: z.object({
appId: z.string().trim()
}),
response: {
200: z.object({
services: z.object({ name: z.string(), serviceId: z.string() }).array()
})
}
},
handler: async (req) => {
const services = await server.services.integrationAuth.getRailwayServices({
actorId: req.permission.id,
actor: req.permission.type,
id: req.params.integrationAuthId,
appId: req.query.appId
});
return { services };
}
});
server.route({
url: "/:integrationAuthId/bitbucket/workspaces",
method: "GET",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
integrationAuthId: z.string().trim()
}),
response: {
200: z.object({
workspaces: z
.object({
name: z.string(),
slug: z.string(),
uuid: z.string(),
type: z.string(),
is_private: z.boolean(),
created_on: z.string(),
updated_on: z.string().optional()
})
.array()
})
}
},
handler: async (req) => {
const workspaces = await server.services.integrationAuth.getBitbucketWorkspaces({
actorId: req.permission.id,
actor: req.permission.type,
id: req.params.integrationAuthId
});
return { workspaces };
}
});
server.route({
url: "/:integrationAuthId/northflank/secret-groups",
method: "GET",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
integrationAuthId: z.string().trim()
}),
querystring: z.object({
appId: z.string().trim()
}),
response: {
200: z.object({
secretGroups: z
.object({
name: z.string(),
groupId: z.string()
})
.array()
})
}
},
handler: async (req) => {
const secretGroups = await server.services.integrationAuth.getNorthFlankSecretGroups({
actorId: req.permission.id,
actor: req.permission.type,
id: req.params.integrationAuthId,
appId: req.query.appId
});
return { secretGroups };
}
});
server.route({
url: "/:integrationAuthId/teamcity/build-configs",
method: "GET",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
integrationAuthId: z.string().trim()
}),
querystring: z.object({
appId: z.string().trim()
}),
response: {
200: z.object({
buildConfigs: z
.object({
name: z.string(),
buildConfigId: z.string()
})
.array()
})
}
},
handler: async (req) => {
const buildConfigs = await server.services.integrationAuth.getTeamcityBuildConfigs({
actorId: req.permission.id,
actor: req.permission.type,
id: req.params.integrationAuthId,
appId: req.query.appId
});
return { buildConfigs };
}
});
};
@@ -0,0 +1,163 @@
import { z } from "zod";
import { IntegrationsSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { shake } from "@app/lib/fn";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/",
method: "POST",
schema: {
body: z.object({
integrationAuthId: z.string().trim(),
app: z.string().trim().optional(),
isActive: z.boolean(),
appId: z.string().trim().optional(),
secretPath: z.string().trim().default("/"),
sourceEnvironment: z.string().trim(),
targetEnvironment: z.string().trim().optional(),
targetEnvironmentId: z.string().trim().optional(),
targetService: z.string().trim().optional(),
targetServiceId: z.string().trim().optional(),
owner: z.string().trim().optional(),
path: z.string().trim().optional(),
region: z.string().trim().optional(),
scope: z.string().trim().optional(),
metadata: z
.object({
secretPrefix: z.string().optional(),
secretSuffix: z.string().optional(),
secretGCPLabel: z
.object({
labelName: z.string(),
labelValue: z.string()
})
.optional()
})
.optional()
}),
response: {
200: z.object({
integration: IntegrationsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { integration, integrationAuth } = await server.services.integration.createIntegration({
actorId: req.permission.id,
actor: req.permission.type,
...req.body
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: integrationAuth.projectId,
event: {
type: EventType.CREATE_INTEGRATION,
metadata: shake({
integrationId: integration.id.toString(),
integration: integration.integration,
environment: req.body.sourceEnvironment,
secretPath: req.body.secretPath,
url: integration.url,
app: integration.app,
appId: integration.appId,
targetEnvironment: integration.targetEnvironment,
targetEnvironmentId: integration.targetEnvironmentId,
targetService: integration.targetService,
targetServiceId: integration.targetServiceId,
path: integration.path,
region: integration.region
}) as any
}
});
return { integration };
}
});
server.route({
url: "/:integrationId",
method: "PATCH",
schema: {
params: z.object({
integrationId: z.string().trim()
}),
body: z.object({
app: z.string().trim(),
appId: z.string().trim(),
isActive: z.boolean(),
secretPath: z.string().trim().default("/"),
targetEnvironment: z.string().trim(),
owner: z.string().trim(),
environment: z.string().trim()
}),
response: {
200: z.object({
integration: IntegrationsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const integration = await server.services.integration.updateIntegration({
actorId: req.permission.id,
actor: req.permission.type,
id: req.params.integrationId,
...req.body
});
return { integration };
}
});
server.route({
url: "/:integrationId",
method: "DELETE",
schema: {
params: z.object({
integrationId: z.string().trim()
}),
response: {
200: z.object({
integration: IntegrationsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const integration = await server.services.integration.deleteIntegration({
actorId: req.permission.id,
actor: req.permission.type,
id: req.params.integrationId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: integration.projectId,
event: {
type: EventType.DELETE_INTEGRATION,
metadata: shake({
integrationId: integration.id,
integration: integration.integration,
environment: integration.environment.slug,
secretPath: integration.secretPath,
url: integration.url,
app: integration.app,
appId: integration.appId,
targetEnvironment: integration.targetEnvironment,
targetEnvironmentId: integration.targetEnvironmentId,
targetService: integration.targetService,
targetServiceId: integration.targetServiceId,
path: integration.path,
region: integration.region
}) as any
}
});
return { integration };
}
});
// TODO(akhilmhdh-pg): manual sync
};
@@ -0,0 +1,70 @@
import { z } from "zod";
import { UsersSchema } from "@app/db/schemas";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { ActorType, AuthMode } from "@app/services/auth/auth-type";
export const registerInviteOrgRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/signup",
method: "POST",
schema: {
body: z.object({
inviteeEmail: z.string().trim().email(),
organizationId: z.string().trim()
}),
response: {
200: z.object({
message: z.string(),
completeInviteLink: z.string().optional()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
if (req.auth.actor !== ActorType.USER) return;
const completeInviteLink = await server.services.org.inviteUserToOrganization({
orgId: req.body.organizationId,
userId: req.permission.id,
inviteeEmail: req.body.inviteeEmail
});
return {
completeInviteLink,
message: `Send an invite link to ${req.body.inviteeEmail}`
};
}
});
server.route({
url: "/verify",
method: "POST",
schema: {
body: z.object({
email: z.string().trim().email(),
organizationId: z.string().trim(),
code: z.string().trim()
}),
response: {
200: z.object({
message: z.string(),
token: z.string().optional(),
user: UsersSchema
})
}
},
handler: async (req) => {
const { user, token } = await server.services.org.verifyUserToOrg({
orgId: req.body.organizationId,
code: req.body.code,
email: req.body.email
});
return {
message: "Successfully verified email",
user,
token
};
}
});
};
@@ -0,0 +1,179 @@
import { z } from "zod";
import {
IncidentContactsSchema,
OrganizationsSchema,
OrgMembershipsSchema,
UsersSchema
} from "@app/db/schemas";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
export const registerOrgRouter = async (server: FastifyZodProvider) => {
server.route({
method: "GET",
url: "/",
schema: {
response: {
200: z.object({
organizations: OrganizationsSchema.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const organizations = await server.services.org.findAllOrganizationOfUser(req.permission.id);
return { organizations };
}
});
server.route({
method: "GET",
url: "/:organizationId",
schema: {
params: z.object({
organizationId: z.string().trim()
}),
response: {
200: z.object({
organization: OrganizationsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const organization = await server.services.org.findOrganizationById(
req.permission.id,
req.params.organizationId
);
return { organization };
}
});
server.route({
method: "GET",
url: "/:organizationId/users",
schema: {
params: z.object({
organizationId: z.string().trim()
}),
response: {
200: z.object({
users: OrgMembershipsSchema.merge(
z.object({
user: UsersSchema.pick({
email: true,
firstName: true,
lastName: true,
id: true
}).merge(z.object({ publicKey: z.string().nullable() }))
})
)
.omit({ createdAt: true, updatedAt: true })
.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const users = await server.services.org.findAllOrgMembers(
req.permission.id,
req.params.organizationId
);
return { users };
}
});
server.route({
method: "PATCH",
url: "/:organizationId/name",
schema: {
params: z.object({ organizationId: z.string().trim() }),
body: z.object({ name: z.string().trim() }),
response: {
200: z.object({
message: z.string(),
organization: OrganizationsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const organization = await server.services.org.updateOrgName(
req.permission.id,
req.params.organizationId,
req.body.name
);
return {
message: "Successfully changed organization name",
organization
};
}
});
server.route({
method: "GET",
url: "/:organizationId/incidentContactOrg",
schema: {
params: z.object({ organizationId: z.string().trim() }),
response: {
200: z.object({
incidentContactsOrg: IncidentContactsSchema.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const incidentContactsOrg = await req.server.services.org.findIncidentContacts(
req.permission.id,
req.params.organizationId
);
return { incidentContactsOrg };
}
});
server.route({
method: "POST",
url: "/:organizationId/incidentContactOrg",
schema: {
params: z.object({ organizationId: z.string().trim() }),
body: z.object({ email: z.string().email().trim() }),
response: {
200: z.object({
incidentContactsOrg: IncidentContactsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const incidentContactsOrg = await req.server.services.org.createIncidentContact(
req.permission.id,
req.params.organizationId,
req.body.email
);
return { incidentContactsOrg };
}
});
server.route({
method: "DELETE",
url: "/:organizationId/incidentContactOrg/:incidentContactId",
schema: {
params: z.object({ organizationId: z.string().trim(), incidentContactId: z.string().trim() }),
response: {
200: z.object({
incidentContactsOrg: IncidentContactsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const incidentContactsOrg = await req.server.services.org.deleteIncidentContact(
req.permission.id,
req.params.organizationId,
req.params.incidentContactId
);
return { incidentContactsOrg };
}
});
};
@@ -0,0 +1,225 @@
import { z } from "zod";
import { BackupPrivateKeySchema, UsersSchema } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env";
import { passwordRateLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { validateSignUpAuthorization } from "@app/services/auth/auth-fns";
import { AuthMode } from "@app/services/auth/auth-type";
export const registerPasswordRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/srp1",
config: {
rateLimit:passwordRateLimit
},
schema: {
body: z.object({
clientPublicKey: z.string().trim()
}),
response: {
200: z.object({
serverPublicKey: z.string(),
salt: z.string()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { salt, serverPublicKey } = await server.services.password.generateServerPubKey(
req.permission.id,
req.body.clientPublicKey
);
return { salt, serverPublicKey };
}
});
server.route({
method: "POST",
url: "/change-password",
config: {
rateLimit:passwordRateLimit
},
schema: {
body: z.object({
clientProof: z.string().trim(),
protectedKey: z.string().trim(),
protectedKeyIV: z.string().trim(),
protectedKeyTag: z.string().trim(),
encryptedPrivateKey: z.string().trim(),
encryptedPrivateKeyIV: z.string().trim(),
encryptedPrivateKeyTag: z.string().trim(),
salt: z.string().trim(),
verifier: z.string().trim()
}),
response: {
200: z.object({
message: z.string()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req, res) => {
const appCfg = getConfig();
await server.services.password.changePassword({ ...req.body, userId: req.permission.id });
res.cookie("jid", appCfg.COOKIE_SECRET_SIGN_KEY, {
httpOnly: true,
path: "/",
sameSite: "strict",
secure: appCfg.HTTPS_ENABLED
});
return { message: "Successfully changed password" };
}
});
server.route({
method: "POST",
url: "/email/password-reset",
config: {
rateLimit:passwordRateLimit
},
schema: {
body: z.object({
email: z.string().email().trim()
}),
response: {
200: z.object({
message: z.string()
})
}
},
handler: async (req) => {
await server.services.password.sendPasswordResetEmail(req.body.email);
return {
message: "If an account exists with this email, a password reset link has been sent"
};
}
});
server.route({
method: "POST",
url: "/email/password-reset-verify",
config: {
rateLimit:passwordRateLimit
},
schema: {
body: z.object({
email: z.string().email().trim(),
code: z.string().trim()
}),
response: {
200: z.object({
message: z.string(),
user: UsersSchema,
token: z.string()
})
}
},
handler: async (req) => {
const { token, user } = await server.services.password.verifyPasswordResetEmail(
req.body.email,
req.body.code
);
return {
message: "Successfully verified email",
user,
token
};
}
});
server.route({
method: "POST",
url: "/backup-private-key",
config: {
rateLimit:passwordRateLimit
},
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
body: z.object({
clientProof: z.string().trim(),
encryptedPrivateKey: z.string().trim(),
iv: z.string().trim(),
tag: z.string().trim(),
salt: z.string().trim(),
verifier: z.string().trim()
}),
response: {
200: z.object({
message: z.string(),
backupPrivateKey: BackupPrivateKeySchema.omit({ verifier: true })
})
}
},
handler: async (req) => {
const token = validateSignUpAuthorization(req.headers.authorization as string, "",false)!
const backupPrivateKey = await server.services.password.createBackupPrivateKey({
...req.body,
userId: token.userId,
});
if (!backupPrivateKey) throw new Error("Failed to create backup key");
return { message: "Successfully updated backup private key", backupPrivateKey };
}
});
server.route({
method: "GET",
url: "/backup-private-key",
config: {
rateLimit:passwordRateLimit
},
schema: {
response: {
200: z.object({
message: z.string(),
backupPrivateKey: BackupPrivateKeySchema.omit({ verifier: true })
})
}
},
handler: async (req) => {
const token = validateSignUpAuthorization(req.headers.authorization as string, "",false)!
const backupPrivateKey = await server.services.password.getBackupPrivateKeyOfUser(
token.userId
);
if (!backupPrivateKey) throw new Error("Failed to find backup key");
return { message: "Successfully fetched backup private key", backupPrivateKey };
}
});
server.route({
method: "POST",
url: "/password-reset",
schema: {
body: z.object({
protectedKey: z.string().trim(),
protectedKeyIV: z.string().trim(),
protectedKeyTag: z.string().trim(),
encryptedPrivateKey: z.string().trim(),
encryptedPrivateKeyIV: z.string().trim(),
encryptedPrivateKeyTag: z.string().trim(),
salt: z.string().trim(),
verifier: z.string().trim()
}),
response: {
200: z.object({
message: z.string()
})
}
},
handler: async (req) => {
const token = validateSignUpAuthorization(req.headers.authorization as string, "",false)!
await server.services.password.resetPasswordByBackupKey({
...req.body,
userId: token.userId,
});
return { message: "Successfully updated backup private key" };
}
});
};
@@ -0,0 +1,155 @@
import { z } from "zod";
import { ProjectEnvironmentsSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
export const registerProjectEnvRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/:workspaceId/environments",
method: "POST",
schema: {
params: z.object({
workspaceId: z.string().trim()
}),
body: z.object({
name: z.string().trim(),
slug: z.string().trim()
}),
response: {
200: z.object({
message: z.string(),
workspace: z.string(),
environment: ProjectEnvironmentsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const environment = await server.services.projectEnv.createEnvironment({
actorId: req.permission.id,
actor: req.permission.type,
projectId: req.params.workspaceId,
...req.body
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: environment.projectId,
event: {
type: EventType.CREATE_ENVIRONMENT,
metadata: {
name: environment.name,
slug: environment.slug
}
}
});
return {
message: "Successfully created new environment",
workspace: req.params.workspaceId,
environment
};
}
});
server.route({
url: "/:workspaceId/environments/:id",
method: "PATCH",
schema: {
params: z.object({
workspaceId: z.string().trim(),
id: z.string().trim()
}),
body: z.object({
slug: z.string().trim().optional(),
name: z.string().trim().optional(),
position: z.number().optional()
}),
response: {
200: z.object({
message: z.string(),
workspace: z.string(),
environment: ProjectEnvironmentsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const { environment, old } = await server.services.projectEnv.updateEnvironment({
actorId: req.permission.id,
actor: req.permission.type,
projectId: req.params.workspaceId,
id: req.params.id,
...req.body
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: environment.projectId,
event: {
type: EventType.UPDATE_ENVIRONMENT,
metadata: {
oldName: old.name,
oldSlug: old.slug,
oldPos: old.position,
newName: environment.name,
newSlug: environment.slug,
newPos: environment.position
}
}
});
return {
message: "Successfully updated environment",
workspace: req.params.workspaceId,
environment
};
}
});
server.route({
url: "/:workspaceId/environments/:id",
method: "DELETE",
schema: {
params: z.object({
workspaceId: z.string().trim(),
id: z.string().trim()
}),
response: {
200: z.object({
message: z.string(),
workspace: z.string(),
environment: ProjectEnvironmentsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const environment = await server.services.projectEnv.deleteEnvironment({
actorId: req.permission.id,
actor: req.permission.type,
projectId: req.params.workspaceId,
id: req.params.id
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: environment.projectId,
event: {
type: EventType.DELETE_ENVIRONMENT,
metadata: {
slug: environment.slug,
name: environment.name
}
}
});
return {
message: "Successfully deleted environment",
workspace: req.params.workspaceId,
environment
};
}
});
};
@@ -0,0 +1,40 @@
import { z } from "zod";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
export const registerProjectKeyRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/:workspaceId/key",
method: "POST",
schema: {
params: z.object({
workspaceId: z.string().trim()
}),
body: z.object({
key: z.object({
encryptedKey: z.string().trim(),
nonce: z.string().trim(),
userId: z.string().trim()
})
}),
response: {
200: z.object({
message: z.string()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
await server.services.projectKey.uploadProjectKeys({
projectId: req.params.workspaceId,
actor: req.permission.type,
actorId: req.permission.id,
nonce: req.body.key.nonce,
receiverId: req.body.key.userId,
encryptedKey: req.body.key.encryptedKey
});
return { message: "Successfully uploaded key to workspace" };
}
});
};
@@ -0,0 +1,179 @@
import { z } from "zod";
import {
OrgMembershipsSchema,
ProjectMembershipsSchema,
UserEncryptionKeysSchema,
UsersSchema
} from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
export const registerProjectMembershipRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/:workspaceId/memberships",
method: "GET",
schema: {
params: z.object({
workspaceId: z.string().trim()
}),
response: {
200: z.object({
memberships: ProjectMembershipsSchema.merge(
z.object({
user: UsersSchema.pick({
email: true,
firstName: true,
lastName: true,
id: true
}).merge(UserEncryptionKeysSchema.pick({ publicKey: true }))
})
)
.omit({ createdAt: true, updatedAt: true })
.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const memberships = await server.services.projectMembership.getProjectMemberships({
actorId: req.permission.id,
actor: req.permission.type,
projectId: req.params.workspaceId
});
return { memberships };
}
});
server.route({
url: "/:workspaceId/memberships",
method: "POST",
schema: {
params: z.object({
workspaceId: z.string().trim()
}),
body: z.object({
members: z
.object({
orgMembershipId: z.string().trim(),
workspaceEncryptedKey: z.string().trim(),
workspaceEncryptedNonce: z.string().trim()
})
.array()
.min(1)
}),
response: {
200: z.object({
success: z.boolean(),
data: OrgMembershipsSchema.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const data = await server.services.projectMembership.addUsersToProject({
actorId: req.permission.id,
actor: req.permission.type,
projectId: req.params.workspaceId,
members: req.body.members
});
await server.services.auditLog.createAuditLog({
projectId: req.params.workspaceId,
...req.auditLogInfo,
event: {
type: EventType.ADD_BATCH_WORKSPACE_MEMBER,
metadata: data.map(({ userId }) => ({
userId: userId || "",
email: ""
}))
}
});
return { data, success: true };
}
});
server.route({
url: "/:workspaceId/memberships/:membershipId",
method: "PATCH",
schema: {
params: z.object({
workspaceId: z.string().trim(),
membershipId: z.string().trim()
}),
body: z.object({
role: z.string().trim()
}),
response: {
200: z.object({
membership: ProjectMembershipsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const membership = await server.services.projectMembership.updateProjectMembership({
actorId: req.permission.id,
actor: req.permission.type,
projectId: req.params.workspaceId,
membershipId: req.params.membershipId,
role: req.body.role
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.params.workspaceId,
event: {
type: EventType.UPDATE_USER_WORKSPACE_ROLE,
metadata: {
userId: membership.userId,
newRole: req.body.role,
oldRole: membership.role,
email: ""
}
}
});
return { membership };
}
});
server.route({
url: "/:workspaceId/memberships/:membershipId",
method: "DELETE",
schema: {
params: z.object({
workspaceId: z.string().trim(),
membershipId: z.string().trim()
}),
response: {
200: z.object({
membership: ProjectMembershipsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const membership = await server.services.projectMembership.deleteProjectMembership({
actorId: req.permission.id,
actor: req.permission.type,
projectId: req.params.workspaceId,
membershipId: req.params.membershipId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.params.workspaceId,
event: {
type: EventType.REMOVE_WORKSPACE_MEMBER,
metadata: {
userId: membership.userId,
email: ""
}
}
});
return { membership };
}
});
};
@@ -0,0 +1,365 @@
import { z } from "zod";
import {
IntegrationsSchema,
ProjectKeysSchema,
ProjectMembershipsSchema,
ProjectsSchema,
UserEncryptionKeysSchema,
UsersSchema
} from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { integrationAuthPubSchema } from "../sanitizedSchemas";
import { sanitizedServiceTokenSchema } from "../v2/service-token-router";
const projectWithEnv = ProjectsSchema.merge(
z.object({
_id: z.string(),
environments: z.object({ name: z.string(), slug: z.string(), id: z.string() }).array()
})
);
export const registerProjectRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/:workspaceId/keys",
method: "GET",
schema: {
params: z.object({
workspaceId: z.string().trim()
}),
response: {
200: z.object({
publicKeys: z
.object({
publicKey: z.string().optional(),
userId: z.string()
})
.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const publicKeys = await server.services.projectKey.getProjectPublicKeys({
actorId: req.permission.id,
actor: req.permission.type,
projectId: req.params.workspaceId
});
return { publicKeys };
}
});
server.route({
url: "/:workspaceId/users",
method: "GET",
schema: {
params: z.object({
workspaceId: z.string().trim()
}),
response: {
200: z.object({
users: ProjectMembershipsSchema.merge(
z.object({
user: UsersSchema.pick({
email: true,
firstName: true,
lastName: true,
id: true
}).merge(UserEncryptionKeysSchema.pick({ publicKey: true }))
})
)
.omit({ createdAt: true, updatedAt: true })
.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const users = await server.services.projectMembership.getProjectMemberships({
actorId: req.permission.id,
actor: req.permission.type,
projectId: req.params.workspaceId
});
return { users };
}
});
server.route({
url: "/",
method: "GET",
schema: {
response: {
200: z.object({
workspaces: projectWithEnv.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]),
handler: async (req) => {
const workspaces = await server.services.project.getProjects(req.permission.id);
return { workspaces };
}
});
server.route({
url: "/:workspaceId",
method: "GET",
schema: {
params: z.object({
workspaceId: z.string().trim()
}),
response: {
200: z.object({
workspace: projectWithEnv.optional()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const workspace = await server.services.project.getAProject({
actorId: req.permission.id,
actor: req.permission.type,
projectId: req.params.workspaceId
});
return { workspace };
}
});
server.route({
url: "/",
method: "POST",
schema: {
body: z.object({
workspaceName: z.string().trim(),
organizationId: z.string().trim()
}),
response: {
200: z.object({
workspace: projectWithEnv
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const workspace = await server.services.project.createProject({
actorId: req.permission.id,
actor: req.permission.type,
orgId: req.body.organizationId,
workspaceName: req.body.workspaceName
});
return { workspace };
}
});
server.route({
url: "/:workspaceId",
method: "DELETE",
schema: {
params: z.object({
workspaceId: z.string().trim()
}),
response: {
200: z.object({
workspace: ProjectsSchema.optional()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const workspace = await server.services.project.deleteProject({
actorId: req.permission.id,
actor: req.permission.type,
projectId: req.params.workspaceId
});
return { workspace };
}
});
server.route({
url: "/:workspaceId/name",
method: "POST",
schema: {
params: z.object({
workspaceId: z.string().trim()
}),
body: z.object({
name: z.string().trim()
}),
response: {
200: z.object({
message: z.string(),
workspace: ProjectsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const workspace = await server.services.project.updateName({
actorId: req.permission.id,
actor: req.permission.type,
projectId: req.params.workspaceId,
name: req.body.name
});
return {
message: "Successfully changed workspace name",
workspace
};
}
});
server.route({
url: "/:workspaceId/auto-capitalization",
method: "POST",
schema: {
params: z.object({
workspaceId: z.string().trim()
}),
body: z.object({
autoCapitalization: z.boolean()
}),
response: {
200: z.object({
message: z.string(),
workspace: ProjectsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const workspace = await server.services.project.toggleAutoCapitalization({
actorId: req.permission.id,
actor: req.permission.type,
projectId: req.params.workspaceId,
autoCapitalization: req.body.autoCapitalization
});
return {
message: "Successfully changed workspace settings",
workspace
};
}
});
server.route({
url: "/:workspaceId/invite-signup",
method: "POST",
schema: {
params: z.object({
workspaceId: z.string().trim()
}),
body: z.object({
email: z.string().trim()
}),
response: {
200: z.object({
invitee: UsersSchema,
latestKey: ProjectKeysSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { invitee, latestKey } = await server.services.projectMembership.inviteUserToProject({
actorId: req.permission.id,
actor: req.permission.type,
projectId: req.params.workspaceId,
email: req.body.email
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.params.workspaceId,
event: {
type: EventType.ADD_WORKSPACE_MEMBER,
metadata: {
userId: invitee.id,
email: invitee.email
}
}
});
return { invitee, latestKey };
}
});
server.route({
url: "/:workspaceId/integrations",
method: "GET",
schema: {
params: z.object({
workspaceId: z.string().trim()
}),
response: {
200: z.object({
integrations: IntegrationsSchema.merge(
z.object({
environment: z.object({
id: z.string(),
name: z.string(),
slug: z.string()
})
})
).array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const integrations = await server.services.integration.listIntegrationByProject({
actorId: req.permission.id,
actor: req.permission.type,
projectId: req.params.workspaceId
});
return { integrations };
}
});
server.route({
url: "/:workspaceId/authorizations",
method: "GET",
schema: {
params: z.object({
workspaceId: z.string().trim()
}),
response: {
200: z.object({
authorizations: integrationAuthPubSchema.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const authorizations = await server.services.integrationAuth.listIntegrationAuthByProjectId({
actorId: req.permission.id,
actor: req.permission.type,
projectId: req.params.workspaceId
});
return { authorizations };
}
});
server.route({
url: "/:workspaceId/service-token-data",
method: "GET",
schema: {
params: z.object({
workspaceId: z.string().trim()
}),
response: {
200: z.object({
serviceTokenData: sanitizedServiceTokenSchema.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const serviceTokenData = await server.services.serviceToken.getProjectServiceTokens({
actorId: req.permission.id,
actor: req.permission.type,
projectId: req.params.workspaceId
});
return { serviceTokenData };
}
});
};
@@ -0,0 +1,203 @@
import { z } from "zod";
import { SecretFoldersSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
export const registerSecretFolderRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/",
method: "POST",
schema: {
body: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
name: z.string().trim(),
path: z.string().trim().default("/"),
// backward compatiability with cli
directory: z.string().trim().default("/")
}),
response: {
200: z.object({
folder: SecretFoldersSchema
})
}
},
onRequest: verifyAuth([
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN,
AuthMode.IDENTITY_ACCESS_TOKEN
]),
handler: async (req) => {
const path = req.body.path || req.body.directory;
const folder = await server.services.folder.createFolder({
actorId: req.permission.id,
actor: req.permission.type,
...req.body,
projectId: req.body.workspaceId,
path
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.body.workspaceId,
event: {
type: EventType.CREATE_FOLDER,
metadata: {
environment: req.body.environment,
folderId: folder.id,
folderName: folder.name,
folderPath: path
}
}
});
return { folder };
}
});
server.route({
url: "/:folderId",
method: "PATCH",
schema: {
params: z.object({
// old way this was name
folderId: z.string()
}),
body: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
name: z.string().trim(),
path: z.string().trim().default("/"),
// backward compatiability with cli
directory: z.string().trim().default("/")
}),
response: {
200: z.object({
folder: SecretFoldersSchema
})
}
},
onRequest: verifyAuth([
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN,
AuthMode.IDENTITY_ACCESS_TOKEN
]),
handler: async (req) => {
const path = req.body.path || req.body.directory;
const { folder, old } = await server.services.folder.updateFolder({
actorId: req.permission.id,
actor: req.permission.type,
...req.body,
projectId: req.body.workspaceId,
id: req.params.folderId,
path
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.body.workspaceId,
event: {
type: EventType.UPDATE_FOLDER,
metadata: {
environment: req.body.environment,
folderId: folder.id,
folderPath: path,
newFolderName: folder.name,
oldFolderName: old.name
}
}
});
return { folder };
}
});
server.route({
url: "/:folderId",
method: "DELETE",
schema: {
params: z.object({
folderId: z.string()
}),
body: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
path: z.string().trim().default("/"),
// keep this here as cli need directory
directory: z.string().trim().default("/")
}),
response: {
200: z.object({
folder: SecretFoldersSchema
})
}
},
onRequest: verifyAuth([
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN,
AuthMode.IDENTITY_ACCESS_TOKEN
]),
handler: async (req) => {
const path = req.body.path || req.body.directory;
const folder = await server.services.folder.deleteFolder({
actorId: req.permission.id,
actor: req.permission.type,
...req.body,
projectId: req.body.workspaceId,
id: req.params.folderId,
path
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.body.workspaceId,
event: {
type: EventType.DELETE_FOLDER,
metadata: {
environment: req.body.environment,
folderId: folder.id,
folderPath: path,
folderName: folder.name
}
}
});
return { folder };
}
});
server.route({
url: "/",
method: "GET",
schema: {
querystring: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
path: z.string().trim().default("/"),
// backward compatiability with cli
directory: z.string().trim().default("/")
}),
response: {
200: z.object({
folders: SecretFoldersSchema.array()
})
}
},
onRequest: verifyAuth([
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN,
AuthMode.IDENTITY_ACCESS_TOKEN
]),
handler: async (req) => {
const path = req.query.path || req.query.directory;
const folders = await server.services.folder.getFolders({
actorId: req.permission.id,
actor: req.permission.type,
...req.query,
projectId: req.query.workspaceId,
path
});
return { folders };
}
});
};
@@ -0,0 +1,282 @@
import { z } from "zod";
import { SecretImportsSchema, SecretsSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
export const registerSecretImportRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/",
method: "POST",
schema: {
body: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
path: z.string().trim().default("/"),
import: z.object({
environment: z.string().trim(),
path: z.string().trim()
})
}),
response: {
200: z.object({
message: z.string(),
secretImport: SecretImportsSchema.omit({ importEnv: true }).merge(
z.object({
importEnv: z.object({ name: z.string(), slug: z.string(), id: z.string() })
})
)
})
}
},
onRequest: verifyAuth([
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN,
AuthMode.IDENTITY_ACCESS_TOKEN
]),
handler: async (req) => {
const secretImport = await server.services.secretImport.createImport({
actorId: req.permission.id,
actor: req.permission.type,
...req.body,
projectId: req.body.workspaceId,
data: req.body.import
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.body.workspaceId,
event: {
type: EventType.CREATE_SECRET_IMPORT,
metadata: {
secretImportId: secretImport.id,
folderId: secretImport.folderId,
importFromSecretPath: secretImport.importPath,
importFromEnvironment: secretImport.importEnv.slug,
importToEnvironment: req.body.environment,
importToSecretPath: req.body.path
}
}
});
return { message: "Successfully created secret import", secretImport };
}
});
server.route({
url: "/:secretImportId",
method: "PATCH",
schema: {
params: z.object({
secretImportId: z.string().trim()
}),
body: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
path: z.string().trim().default("/"),
import: z.object({
environment: z.string().trim().optional(),
path: z.string().trim().optional(),
position: z.number().optional()
})
}),
response: {
200: z.object({
message: z.string(),
secretImport: SecretImportsSchema.omit({ importEnv: true }).merge(
z.object({
importEnv: z.object({ name: z.string(), slug: z.string(), id: z.string() })
})
)
})
}
},
onRequest: verifyAuth([
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN,
AuthMode.IDENTITY_ACCESS_TOKEN
]),
handler: async (req) => {
const secretImport = await server.services.secretImport.updateImport({
actorId: req.permission.id,
actor: req.permission.type,
id: req.params.secretImportId,
...req.body,
projectId: req.body.workspaceId,
data: req.body.import
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.body.workspaceId,
event: {
type: EventType.UPDATE_SECRET_IMPORT,
metadata: {
secretImportId: secretImport.id,
folderId: secretImport.folderId,
position: secretImport.position,
importToEnvironment: req.body.environment,
importToSecretPath: req.body.path
}
}
});
return { message: "Successfully updated secret import", secretImport };
}
});
server.route({
url: "/:secretImportId",
method: "DELETE",
schema: {
params: z.object({
secretImportId: z.string().trim()
}),
body: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
path: z.string().trim().default("/")
}),
response: {
200: z.object({
message: z.string(),
secretImport: SecretImportsSchema.omit({ importEnv: true }).merge(
z.object({
importEnv: z.object({ name: z.string(), slug: z.string(), id: z.string() })
})
)
})
}
},
onRequest: verifyAuth([
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN,
AuthMode.IDENTITY_ACCESS_TOKEN
]),
handler: async (req) => {
const secretImport = await server.services.secretImport.deleteImport({
actorId: req.permission.id,
actor: req.permission.type,
id: req.params.secretImportId,
...req.body,
projectId: req.body.workspaceId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.body.workspaceId,
event: {
type: EventType.DELETE_SECRET_IMPORT,
metadata: {
secretImportId: secretImport.id,
folderId: secretImport.folderId,
importFromEnvironment: secretImport.importEnv.slug,
importFromSecretPath: secretImport.importPath,
importToEnvironment: req.body.environment,
importToSecretPath: req.body.path
}
}
});
return { message: "Successfully deleted secret import", secretImport };
}
});
server.route({
url: "/",
method: "GET",
schema: {
querystring: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
path: z.string().trim().default("/")
}),
response: {
200: z.object({
message: z.string(),
secretImports: SecretImportsSchema.omit({ importEnv: true })
.merge(
z.object({
importEnv: z.object({ name: z.string(), slug: z.string(), id: z.string() })
})
)
.array()
})
}
},
onRequest: verifyAuth([
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN,
AuthMode.IDENTITY_ACCESS_TOKEN
]),
handler: async (req) => {
const secretImports = await server.services.secretImport.getImports({
actorId: req.permission.id,
actor: req.permission.type,
...req.query,
projectId: req.query.workspaceId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.query.workspaceId,
event: {
type: EventType.GET_SECRET_IMPORTS,
metadata: {
environment: req.query.environment,
folderId: secretImports?.[0]?.folderId,
numberOfImports: secretImports.length
}
}
});
return { message: "Successfully fetched secret imports", secretImports };
}
});
server.route({
url: "/secrets",
method: "GET",
schema: {
querystring: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
path: z.string().trim().default("/")
}),
response: {
200: z.object({
secrets: z
.object({
secretPath: z.string(),
environment: z.string(),
environmentInfo: z.object({
id: z.string(),
name: z.string(),
slug: z.string()
}),
folderId: z.string().optional(),
secrets: SecretsSchema.omit({ secretBlindIndex: true }).array()
})
.array()
})
}
},
onRequest: verifyAuth([
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN,
AuthMode.IDENTITY_ACCESS_TOKEN
]),
handler: async (req) => {
const importedSecrets = await server.services.secretImport.getSecretsFromImports({
actorId: req.permission.id,
actor: req.permission.type,
...req.query,
projectId: req.query.workspaceId
});
return { secrets: importedSecrets };
}
});
};
@@ -0,0 +1,86 @@
import { z } from "zod";
import { SecretTagsSchema } from "@app/db/schemas";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
export const registerSecretTagRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/:projectId/tags",
method: "GET",
schema: {
params: z.object({
projectId: z.string().trim()
}),
response: {
200: z.object({
workspaceTags: SecretTagsSchema.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const workspaceTags = await server.services.secretTag.getProjectTags({
actor: req.permission.type,
actorId: req.permission.id,
projectId: req.params.projectId
});
return { workspaceTags };
}
});
server.route({
url: "/:projectId/tags",
method: "POST",
schema: {
params: z.object({
projectId: z.string().trim()
}),
body: z.object({
name: z.string().trim(),
slug: z.string().trim(),
color: z.string()
}),
response: {
200: z.object({
workspaceTag: SecretTagsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const workspaceTag = await server.services.secretTag.createTag({
actor: req.permission.type,
actorId: req.permission.id,
projectId: req.params.projectId,
...req.body
});
return { workspaceTag };
}
});
server.route({
url: "/:projectId/tags/:tagId",
method: "DELETE",
schema: {
params: z.object({
projectId: z.string().trim(),
tagId: z.string().trim()
}),
response: {
200: z.object({
workspaceTag: SecretTagsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const workspaceTag = await server.services.secretTag.deleteTag({
actor: req.permission.type,
actorId: req.permission.id,
id: req.params.tagId
});
return { workspaceTag };
}
});
};
+274
View File
@@ -0,0 +1,274 @@
import { Authenticator } from "@fastify/passport";
import fastifySession from "@fastify/session";
import { Strategy as GitHubStrategy } from "passport-github";
import { Strategy as GitLabStrategy } from "passport-gitlab2";
import { Strategy as GoogleStrategy } from "passport-google-oauth20";
import { z } from "zod";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError } from "@app/lib/errors";
import { logger } from "@app/lib/logger";
import { fetchGithubEmails } from "@app/lib/requests/github";
import { AuthMethod } from "@app/services/auth/auth-type";
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
export const registerSsoRouter = async (server: FastifyZodProvider) => {
const appCfg = getConfig();
const passport = new Authenticator({ key: "sso", userProperty: "passportUser" });
await server.register(fastifySession, { secret: appCfg.COOKIE_SECRET_SIGN_KEY });
await server.register(passport.initialize());
await server.register(passport.secureSession());
// passport oauth strategy for Google
const isGoogleOauthActive = Boolean(
appCfg.CLIENT_ID_GOOGLE_LOGIN && appCfg.CLIENT_SECRET_GOOGLE_LOGIN
);
if (isGoogleOauthActive) {
passport.use(
new GoogleStrategy(
{
passReqToCallback: true,
clientID: appCfg.CLIENT_ID_GOOGLE_LOGIN as string,
clientSecret: appCfg.CLIENT_SECRET_GOOGLE_LOGIN as string,
callbackURL: "/api/v1/sso/google",
scope: ["profile", " email"]
},
async (req, _accessToken, _refreshToken, profile, cb) => {
try {
const email = profile?.emails?.[0]?.value;
const serverCfg = getServerCfg();
if (!email)
throw new BadRequestError({
message: "Email not found",
name: "Oauth Google Register"
});
const { isUserCompleted, providerAuthToken } = await server.services.login.oauth2Login({
email,
firstName: profile?.name?.givenName || "",
lastName: profile?.name?.familyName || "",
authMethod: AuthMethod.GOOGLE,
callbackPort: req.query.state as string,
isSignupAllowed: Boolean(serverCfg.allowSignUp)
});
cb(null, { isUserCompleted, providerAuthToken });
} catch (error) {
logger.error(error);
cb(null, false);
}
}
)
);
}
// Passport strategy for Github
const isGithubOauthActive = Boolean(
appCfg.CLIENT_SECRET_GITHUB_LOGIN && appCfg.CLIENT_ID_GITHUB_LOGIN
);
if (isGithubOauthActive) {
passport.use(
new GitHubStrategy(
{
passReqToCallback: true,
clientID: appCfg.CLIENT_ID_GITHUB_LOGIN as string,
clientSecret: appCfg.CLIENT_SECRET_GITHUB_LOGIN as string,
callbackURL: "/api/v1/sso/github",
scope: ["user:email"]
},
async (req, accessToken, _refreshToken, profile, cb) => {
try {
const ghEmails = await fetchGithubEmails(accessToken);
const { email } = ghEmails.filter((gitHubEmail) => gitHubEmail.primary)[0];
const serverCfg = getServerCfg();
const { isUserCompleted, providerAuthToken } = await server.services.login.oauth2Login({
email,
firstName: profile.displayName,
lastName: "",
authMethod: AuthMethod.GITHUB,
callbackPort: req.query.state as string,
isSignupAllowed: Boolean(serverCfg.allowSignUp)
});
return cb(null, { isUserCompleted, providerAuthToken });
} catch (error) {
logger.error(error);
cb(null, false);
}
}
)
);
}
// passport strategy for gitlab
const isGitlabOauthActive = Boolean(
appCfg.CLIENT_ID_GITLAB_LOGIN &&
appCfg.CLIENT_SECRET_GITLAB_LOGIN &&
appCfg.CLIENT_GITLAB_LOGIN_URL
);
if (isGitlabOauthActive) {
passport.use(
new GitLabStrategy(
{
passReqToCallback: true,
clientID: appCfg.CLIENT_ID_GITLAB_LOGIN,
clientSecret: appCfg.CLIENT_SECRET_GITLAB_LOGIN,
callbackURL: "/api/v1/sso/gitlab",
baseURL: appCfg.CLIENT_GITLAB_LOGIN_URL
},
async (req: any, _accessToken: string, _refreshToken: string, profile: any, cb: any) => {
try {
const email = profile.emails[0].value;
const serverCfg = getServerCfg();
const { isUserCompleted, providerAuthToken } = await server.services.login.oauth2Login({
email,
firstName: profile.displayName,
lastName: "",
authMethod: AuthMethod.GITLAB,
callbackPort: req.query.state as string,
isSignupAllowed: Boolean(serverCfg.allowSignUp)
});
return cb(null, { isUserCompleted, providerAuthToken });
} catch (error) {
logger.error(error);
cb(null, false);
}
}
)
);
}
server.route({
url: "/redirect/google",
method: "GET",
schema: {
querystring: z.object({
callback_port: z.string().optional()
})
},
preValidation: (req, res) =>
(
passport.authenticate("google", {
scope: ["profile", "email"],
session: false,
state: req.query.callback_port,
authInfo: false
// this is due to zod type difference
}) as any
)(req, res),
handler: () => {}
});
server.route({
url: "/google",
method: "GET",
preValidation: passport.authenticate("google", {
session: false,
failureRedirect: "/login/provider/error",
authInfo: false
// this is due to zod type difference
}) as any,
handler: (req, res) => {
if (req.passportUser.isUserCompleted) {
return res.redirect(
`${appCfg.SITE_URL}/login/sso?token=${encodeURIComponent(
req.passportUser.providerAuthToken
)}`
);
}
return res.redirect(
`${appCfg.SITE_URL}/signup/sso?token=${encodeURIComponent(
req.passportUser.providerAuthToken
)}`
);
}
});
server.route({
url: "/redirect/github",
method: "GET",
schema: {
querystring: z.object({
callback_port: z.string().optional()
})
},
preValidation: (req, res) =>
(
passport.authenticate("github", {
session: false,
state: req.query.callback_port,
authInfo: false
// this is due to zod type difference
}) as any
)(req, res),
handler: () => {}
});
server.route({
url: "/github",
method: "GET",
preValidation: passport.authenticate("github", {
session: false,
failureRedirect: "/login/provider/error",
authInfo: false
// this is due to zod type difference
}) as any,
handler: (req, res) => {
if (req.passportUser.isUserCompleted) {
return res.redirect(
`${appCfg.SITE_URL}/login/sso?token=${encodeURIComponent(
req.passportUser.providerAuthToken
)}`
);
}
return res.redirect(
`${appCfg.SITE_URL}/signup/sso?token=${encodeURIComponent(
req.passportUser.providerAuthToken
)}`
);
}
});
server.route({
url: "/redirect/gitlab",
method: "GET",
schema: {
querystring: z.object({
callback_port: z.string().optional()
})
},
preValidation: (req, res) =>
(
passport.authenticate("gitlab", {
session: false,
state: req.query.callback_port,
authInfo: false
// this is due to zod type difference
}) as any
)(req, res),
handler: () => {}
});
server.route({
url: "/gitlab",
method: "GET",
preValidation: passport.authenticate("gitlab", {
session: false,
failureRedirect: "/login/provider/error",
authInfo: false
// this is due to zod type difference
}) as any,
handler: (req, res) => {
if (req.passportUser.isUserCompleted) {
return res.redirect(
`${appCfg.SITE_URL}/login/sso?token=${encodeURIComponent(
req.passportUser.providerAuthToken
)}`
);
}
return res.redirect(
`${appCfg.SITE_URL}/signup/sso?token=${encodeURIComponent(
req.passportUser.providerAuthToken
)}`
);
}
});
};
@@ -0,0 +1,54 @@
import { z } from "zod";
import { UserActionsSchema } from "@app/db/schemas";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
export const registerUserActionRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/",
method: "POST",
schema: {
body: z.object({
action: z.string().trim()
}),
response: {
200: z.object({
message: z.string(),
userAction: UserActionsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const userAction = await server.services.user.createUserAction(
req.permission.id,
req.body.action
);
return { userAction, message: "Successfully recorded user action" };
}
});
server.route({
url: "/",
method: "GET",
schema: {
querystring: z.object({
action: z.string().trim()
}),
response: {
200: z.object({
userAction: UserActionsSchema.optional().nullable()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const userAction = await server.services.user.getUserAction(
req.permission.id,
req.query.action
);
return { userAction };
}
});
};
@@ -0,0 +1,24 @@
import { z } from "zod";
import { UserEncryptionKeysSchema, UsersSchema } from "@app/db/schemas";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
export const registerUserRouter = async (server: FastifyZodProvider) => {
server.route({
method: "GET",
url: "/",
schema: {
response: {
200: z.object({
user: UsersSchema.merge(UserEncryptionKeysSchema.omit({ verifier: true }))
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const user = await server.services.user.getMe(req.permission.id);
return { user };
}
});
};
@@ -0,0 +1,204 @@
import { z } from "zod";
import { WebhooksSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
export const sanitizedWebhookSchema = WebhooksSchema.omit({
encryptedSecretKey: true,
iv: true,
tag: true,
algorithm: true,
keyEncoding: true
}).merge(
z.object({
projectId: z.string(),
environment: z.object({
id: z.string(),
name: z.string(),
slug: z.string()
})
})
);
export const registerWebhookRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
body: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
webhookUrl: z.string().url().trim(),
webhookSecretKey: z.string().trim().optional(),
secretPath: z.string().trim().default("/")
}),
response: {
200: z.object({
message: z.string(),
webhook: sanitizedWebhookSchema
})
}
},
handler: async (req) => {
const webhook = await server.services.webhook.createWebhook({
actor: req.permission.type,
actorId: req.permission.id,
projectId: req.body.workspaceId,
...req.body
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.body.workspaceId,
event: {
type: EventType.CREATE_WEBHOOK,
metadata: {
environment: webhook.environment.slug,
webhookId: webhook.id,
isDisabled: webhook.isDisabled,
secretPath: webhook.secretPath,
webhookUrl: webhook.url
}
}
});
return { message: "Successfully created webhook", webhook };
}
});
server.route({
method: "PATCH",
url: "/:webhookId",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
webhookId: z.string().trim()
}),
body: z.object({
isDisabled: z.boolean().default(false)
}),
response: {
200: z.object({
message: z.string(),
webhook: sanitizedWebhookSchema
})
}
},
handler: async (req) => {
const webhook = await server.services.webhook.updateWebhook({
actor: req.permission.type,
actorId: req.permission.id,
id: req.params.webhookId,
isDisabled: req.body.isDisabled
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: webhook.projectId,
event: {
type: EventType.UPDATE_WEBHOOK_STATUS,
metadata: {
environment: webhook.environment.slug,
webhookId: webhook.id,
isDisabled: webhook.isDisabled,
secretPath: webhook.secretPath,
webhookUrl: webhook.url
}
}
});
return { message: "Successfully updated webhook", webhook };
}
});
server.route({
method: "DELETE",
url: "/:webhookId",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
webhookId: z.string().trim()
})
},
handler: async (req) => {
const webhook = await server.services.webhook.deleteWebhook({
actor: req.permission.type,
actorId: req.permission.id,
id: req.params.webhookId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: webhook.projectId,
event: {
type: EventType.DELETE_WEBHOOK,
metadata: {
environment: webhook.environment.slug,
webhookId: webhook.id,
isDisabled: webhook.isDisabled,
secretPath: webhook.secretPath,
webhookUrl: webhook.url
}
}
});
return { message: "Successfully deleted webhook", webhook };
}
});
server.route({
method: "POST",
url: "/:webhookId/test",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
webhookId: z.string().trim()
}),
response: {
200: z.object({
message: z.string(),
webhook: sanitizedWebhookSchema
})
}
},
handler: async (req) => {
const webhook = await server.services.webhook.testWebhook({
actor: req.permission.type,
actorId: req.permission.id,
id: req.params.webhookId
});
return { message: "Successfully tested webhook", webhook };
}
});
server.route({
method: "GET",
url: "/",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
querystring: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim().optional(),
secretPath: z.string().trim().optional()
}),
response: {
200: z.object({
message: z.string(),
webhooks: sanitizedWebhookSchema.array()
})
}
},
handler: async (req) => {
const webhooks = await server.services.webhook.listWebhooks({
actor: req.permission.type,
actorId: req.permission.id,
...req.query,
projectId: req.query.workspaceId
});
return { message: "Successfully fetched webhook", webhooks };
}
});
};
@@ -0,0 +1,42 @@
import { z } from "zod";
import { IdentitiesSchema, IdentityOrgMembershipsSchema, OrgRolesSchema } from "@app/db/schemas";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
export const registerIdentityOrgRouter = async (server: FastifyZodProvider) => {
server.route({
method: "GET",
url: "/:orgId/identity-memberships",
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
params: z.object({
orgId: z.string().trim()
}),
response: {
200: z.object({
identityMemberships: IdentityOrgMembershipsSchema.merge(
z.object({
customRole: OrgRolesSchema.pick({
id: true,
name: true,
slug: true,
permissions: true,
description: true
}).optional(),
identity: IdentitiesSchema.pick({ name: true, id: true, authMethod: true })
})
).array()
})
}
},
handler: async (req) => {
const identityMemberships = await server.services.identity.listOrgIdentities({
actor: req.permission.type,
actorId: req.permission.id,
orgId: req.params.orgId
});
return { identityMemberships };
}
});
};
@@ -0,0 +1,133 @@
import { z } from "zod";
import {
IdentitiesSchema,
IdentityProjectMembershipsSchema,
ProjectMembershipRole,
ProjectRolesSchema
} from "@app/db/schemas";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
export const registerIdentityProjectRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/:projectId/identity-memberships/:identityId",
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
params: z.object({
projectId: z.string().trim(),
identityId: z.string().trim()
}),
body: z.object({
role: z.string().trim().min(1).default(ProjectMembershipRole.NoAccess)
}),
response: {
200: z.object({
identityMembership: IdentityProjectMembershipsSchema
})
}
},
handler: async (req) => {
const identityMembership = await server.services.identityProject.createProjectIdentity({
actor: req.permission.type,
actorId: req.permission.id,
identityId: req.params.identityId,
projectId: req.params.projectId,
role: req.body.role
});
return { identityMembership };
}
});
server.route({
method: "PATCH",
url: "/:projectId/identity-memberships/:identityId",
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
params: z.object({
projectId: z.string().trim(),
identityId: z.string().trim()
}),
body: z.object({
role: z.string().trim().min(1).default(ProjectMembershipRole.NoAccess)
}),
response: {
200: z.object({
identityMembership: IdentityProjectMembershipsSchema
})
}
},
handler: async (req) => {
const identityMembership = await server.services.identityProject.updateProjectIdentity({
actor: req.permission.type,
actorId: req.permission.id,
identityId: req.params.identityId,
projectId: req.params.projectId,
role: req.body.role
});
return { identityMembership };
}
});
server.route({
method: "DELETE",
url: "/:projectId/identity-memberships/:identityId",
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
params: z.object({
projectId: z.string().trim(),
identityId: z.string().trim()
}),
response: {
200: z.object({
identityMembership: IdentityProjectMembershipsSchema
})
}
},
handler: async (req) => {
const identityMembership = await server.services.identityProject.deleteProjectIdentity({
actor: req.permission.type,
actorId: req.permission.id,
identityId: req.params.identityId,
projectId: req.params.projectId
});
return { identityMembership };
}
});
server.route({
method: "GET",
url: "/:projectId/identity-memberships",
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
params: z.object({
projectId: z.string().trim()
}),
response: {
200: z.object({
identityMemberships: IdentityProjectMembershipsSchema.merge(
z.object({
customRole: ProjectRolesSchema.pick({
id: true,
name: true,
slug: true,
permissions: true,
description: true
}).optional(),
identity: IdentitiesSchema.pick({ name: true, id: true, authMethod: true })
})
).array()
})
}
},
handler: async (req) => {
const identityMemberships = await server.services.identityProject.listProjectIdentities({
actor: req.permission.type,
actorId: req.permission.id,
projectId: req.params.projectId
});
return { identityMemberships };
}
});
};
+27
View File
@@ -0,0 +1,27 @@
import { registerIdentityOrgRouter } from "./identity-org-router";
import { registerIdentityProjectRouter } from "./identity-project-router";
import { registerMfaRouter } from "./mfa-router";
import { registerOrgRouter } from "./organization-router";
import { registerProjectRouter } from "./project-router";
import { registerServiceTokenRouter } from "./service-token-router";
import { registerUserRouter } from "./user-router";
export const registerV2Routes = async (server: FastifyZodProvider) => {
await server.register(registerMfaRouter, { prefix: "/auth" });
await server.register(registerUserRouter, { prefix: "/users" });
await server.register(registerServiceTokenRouter, { prefix: "/service-token" });
await server.register(
async (orgRouter) => {
await orgRouter.register(registerOrgRouter);
await orgRouter.register(registerIdentityOrgRouter);
},
{ prefix: "/organizations" }
);
await server.register(
async (projectServer) => {
projectServer.register(registerProjectRouter);
projectServer.register(registerIdentityProjectRouter);
},
{ prefix: "/workspace" }
);
};
@@ -0,0 +1,92 @@
import jwt, { JwtPayload } from "jsonwebtoken";
import { z } from "zod";
import { getConfig } from "@app/lib/config/env";
import { AuthTokenType } from "@app/services/auth/auth-type";
export const registerMfaRouter = async (server: FastifyZodProvider) => {
const cfg = getConfig();
server.decorateRequest("mfa", null);
server.addHook("preParsing", async (req, res) => {
const authorizationHeader = req.headers.authorization;
if (!authorizationHeader || !authorizationHeader.startsWith("Bearer ")) {
res.status(401).send({ error: "Missing bearer token" });
return res;
}
const token = authorizationHeader.split(" ")[1];
if (!token) {
res.status(401).send({ error: "Missing bearer token" });
return res;
}
const decodedToken = jwt.verify(token, cfg.AUTH_SECRET) as JwtPayload;
if (decodedToken.authTokenType !== AuthTokenType.MFA_TOKEN)
throw new Error("Unauthorized access");
const user = await server.store.user.findById(decodedToken.userId);
if (!user) throw new Error("User not found");
req.mfa = { userId: user.id, user };
});
server.route({
url: "/mfa/send",
method: "POST",
schema: {
response: {
200: z.object({
message: z.string()
})
}
},
handler: async (req) => {
await server.services.login.resendMfaToken(req.mfa.userId);
return { message: "Successfully send new mfa code" };
}
});
server.route({
url: "/mfa/verify",
method: "POST",
schema: {
body: z.object({
mfaToken: z.string().trim()
}),
response: {
200: z.object({
encryptionVersion: z.number().default(1).nullable().optional(),
protectedKey: z.string().nullable(),
protectedKeyIV: z.string().nullable(),
protectedKeyTag: z.string().nullable(),
publicKey: z.string(),
encryptedPrivateKey: z.string(),
iv: z.string(),
tag: z.string(),
token: z.string()
})
}
},
handler: async (req, res) => {
const userAgent = req.headers["user-agent"];
if (!userAgent) throw new Error("user agent header is required");
const appCfg = getConfig();
const { user, token } = await server.services.login.verifyMfaToken({
userAgent,
ip: req.realIp,
userId: req.mfa.userId,
mfaToken: req.body.mfaToken
});
res.setCookie("jid", token.refresh, {
httpOnly: true,
path: "/",
sameSite: "strict",
secure: appCfg.HTTPS_ENABLED
});
return { token: token.access, ...user };
}
});
};
@@ -0,0 +1,151 @@
import { z } from "zod";
import {
OrganizationsSchema,
OrgMembershipsSchema,
UserEncryptionKeysSchema,
UsersSchema
} from "@app/db/schemas";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { ActorType, AuthMode } from "@app/services/auth/auth-type";
export const registerOrgRouter = async (server: FastifyZodProvider) => {
server.route({
method: "GET",
url: "/:organizationId/memberships",
schema: {
params: z.object({
organizationId: z.string().trim()
}),
response: {
200: z.object({
users: OrgMembershipsSchema.merge(
z.object({
user: UsersSchema.pick({
email: true,
firstName: true,
lastName: true,
id: true
}).merge(UserEncryptionKeysSchema.pick({ publicKey: true }))
})
)
.omit({ createdAt: true, updatedAt: true })
.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
if (req.auth.actor !== ActorType.USER) return;
const users = await server.services.org.findAllOrgMembers(
req.permission.id,
req.params.organizationId
);
return { users };
}
});
server.route({
method: "PATCH",
url: "/:organizationId/memberships/:membershipId",
schema: {
params: z.object({ organizationId: z.string().trim(), membershipId: z.string().trim() }),
body: z.object({
role: z.string().trim()
}),
response: {
200: z.object({
membership: OrgMembershipsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
if (req.auth.actor !== ActorType.USER) return;
const membership = await server.services.org.updateOrgMembership({
userId: req.permission.id,
role: req.body.role,
orgId: req.params.organizationId,
membershipId: req.params.membershipId
});
return { membership };
}
});
server.route({
method: "DELETE",
url: "/:organizationId/memberships/:membershipId",
schema: {
params: z.object({ organizationId: z.string().trim(), membershipId: z.string().trim() }),
response: {
200: z.object({
membership: OrgMembershipsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
if (req.auth.actor !== ActorType.USER) return;
const membership = await server.services.org.deleteOrgMembership({
userId: req.permission.id,
orgId: req.params.organizationId,
membershipId: req.params.membershipId
});
return { membership };
}
});
server.route({
method: "POST",
url: "/",
schema: {
body: z.object({
name: z.string().trim()
}),
response: {
200: z.object({
organization: OrganizationsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]),
handler: async (req) => {
if (req.auth.actor !== ActorType.USER) return;
const organization = await server.services.org.createOrganization(
req.permission.id,
req.auth.user.email,
req.body.name
);
return { organization };
}
});
server.route({
method: "DELETE",
url: "/:organizationId",
schema: {
params: z.object({
organizationId: z.string().trim()
}),
response: {
200: z.object({
organization: OrganizationsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]),
handler: async (req) => {
if (req.auth.actor !== ActorType.USER) return;
const organization = await server.services.org.deleteOrganizationById(
req.permission.id,
req.params.organizationId
);
return { organization };
}
});
};
@@ -0,0 +1,48 @@
import { z } from "zod";
import { ProjectKeysSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
export const registerProjectRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/:workspaceId/encrypted-key",
method: "GET",
schema: {
params: z.object({
workspaceId: z.string().trim()
}),
response: {
200: ProjectKeysSchema.merge(
z.object({
sender: z.object({
publicKey: z.string()
})
})
)
}
},
onResponse: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]),
handler: async (req) => {
const key = await server.services.projectKey.getLatestProjectKey({
actor: req.permission.type,
actorId: req.permission.id,
projectId: req.params.workspaceId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.params.workspaceId,
event: {
type: EventType.GET_WORKSPACE_KEY,
metadata: {
keyId: key.id
}
}
});
return key;
}
});
};
@@ -0,0 +1,121 @@
import { z } from "zod";
import { ServiceTokensSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
export const sanitizedServiceTokenSchema = ServiceTokensSchema.omit({
secretHash: true,
encryptedKey: true,
iv: true,
tag: true
});
export const registerServiceTokenRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/",
method: "GET",
onRequest: verifyAuth([AuthMode.SERVICE_TOKEN]),
schema: {
response: {
200: ServiceTokensSchema.merge(z.object({ workspace: z.string() }))
}
},
handler: async (req) => {
const serviceTokenData = await server.services.serviceToken.getServiceToken({
actorId: req.permission.id,
actor: req.permission.type
});
return { ...serviceTokenData, workspace: serviceTokenData.projectId };
}
});
server.route({
url: "/",
method: "POST",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
body: z.object({
name: z.string().trim(),
workspaceId: z.string().trim(),
scopes: z
.object({
environment: z.string().trim(),
secretPath: z.string().trim()
})
.array()
.min(1),
encryptedKey: z.string().trim(),
iv: z.string().trim(),
tag: z.string().trim(),
expiresIn: z.number().nullable(),
permissions: z.enum(["read", "write"]).array()
}),
response: {
200: z.object({
serviceToken: z.string(),
serviceTokenData: sanitizedServiceTokenSchema
})
}
},
handler: async (req) => {
const { serviceToken, token } = await server.services.serviceToken.createServiceToken({
actorId: req.permission.id,
actor: req.permission.type,
...req.body,
projectId: req.body.workspaceId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: serviceToken.projectId,
event: {
type: EventType.CREATE_SERVICE_TOKEN,
metadata: {
name: serviceToken.name,
scopes: req.body.scopes
}
}
});
return { serviceToken: token, serviceTokenData: serviceToken };
}
});
server.route({
url: "/:serviceTokenId",
method: "DELETE",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
serviceTokenId: z.string().trim()
}),
response: {
200: z.object({
serviceTokenData: sanitizedServiceTokenSchema
})
}
},
handler: async (req) => {
const serviceTokenData = await server.services.serviceToken.deleteServiceToken({
actorId: req.permission.id,
actor: req.permission.type,
id: req.params.serviceTokenId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: serviceTokenData.projectId,
event: {
type: EventType.DELETE_SERVICE_TOKEN,
metadata: {
name: serviceTokenData.name,
scopes: serviceTokenData.scopes as Array<{ environment: string; secretPath: string }>
}
}
});
return { serviceTokenData };
}
});
};
+230
View File
@@ -0,0 +1,230 @@
import { z } from "zod";
import {
AuthTokenSessionsSchema,
OrganizationsSchema,
UserEncryptionKeysSchema,
UsersSchema
} from "@app/db/schemas";
import { ApiKeysSchema } from "@app/db/schemas/api-keys";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMethod, AuthMode } from "@app/services/auth/auth-type";
export const registerUserRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/me/mfa",
method: "PATCH",
schema: {
body: z.object({
isMfaEnabled: z.boolean()
}),
response: {
200: z.object({
user: UsersSchema
})
}
},
preHandler: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]),
handler: async (req) => {
const user = await server.services.user.toggleUserMfa(req.permission.id, req.body.isMfaEnabled);
return { user };
}
});
server.route({
url: "/me/name",
method: "PATCH",
schema: {
body: z.object({
firstName: z.string().trim(),
lastName: z.string().trim()
}),
response: {
200: z.object({
user: UsersSchema
})
}
},
preHandler: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]),
handler: async (req) => {
const user = await server.services.user.updateUserName(
req.permission.id,
req.body.firstName,
req.body.lastName
);
return { user };
}
});
server.route({
url: "/me/auth-methods",
method: "PUT",
schema: {
body: z.object({
authMethods: z.nativeEnum(AuthMethod).array().min(1)
}),
response: {
200: z.object({
user: UsersSchema
})
}
},
preHandler: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]),
handler: async (req) => {
const user = await server.services.user.updateAuthMethods(
req.permission.id,
req.body.authMethods
);
return { user };
}
});
server.route({
method: "GET",
url: "/me/organizations",
schema: {
response: {
200: z.object({
organizations: OrganizationsSchema.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]),
handler: async (req) => {
const organizations = await server.services.org.findAllOrganizationOfUser(req.permission.id);
return { organizations };
}
});
server.route({
method: "GET",
url: "/me/api-keys",
schema: {
response: {
200: ApiKeysSchema.omit({ secretHash: true }).array()
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const apiKeys = await server.services.apiKey.getMyApiKeys(req.permission.id);
return apiKeys;
}
});
server.route({
method: "POST",
url: "/me/api-keys",
schema: {
body: z.object({
name: z.string().trim(),
expiresIn: z.number()
}),
response: {
200: z.object({
apiKey: z.string(),
apiKeyData: ApiKeysSchema.omit({ secretHash: true })
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const apiKeys = await server.services.apiKey.createApiKey(
req.permission.id,
req.body.name,
req.body.expiresIn
);
return apiKeys;
}
});
server.route({
method: "DELETE",
url: "/me/api-keys/:apiKeyDataId",
schema: {
params: z.object({
apiKeyDataId: z.string().trim()
}),
response: {
200: z.object({
apiKeyData: ApiKeysSchema.omit({ secretHash: true })
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const apiKeyData = await server.services.apiKey.deleteApiKey(
req.permission.id,
req.params.apiKeyDataId
);
return { apiKeyData };
}
});
server.route({
method: "GET",
url: "/me/sessions",
schema: {
response: {
200: AuthTokenSessionsSchema.array()
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const sessions = await server.services.authToken.getTokenSessionByUser(req.permission.id);
return sessions;
}
});
server.route({
method: "DELETE",
url: "/me/sessions",
schema: {
response: {
200: z.object({
message: z.string()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
await server.services.authToken.revokeAllMySessions(req.permission.id);
return {
message: "Successfully revoked all sessions"
};
}
});
server.route({
method: "GET",
url: "/me",
schema: {
response: {
200: z.object({
user: UsersSchema.merge(UserEncryptionKeysSchema.omit({ verifier: true }))
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const user = await server.services.user.getMe(req.permission.id);
return { user };
}
});
server.route({
method: "DELETE",
url: "/me",
schema: {
response: {
200: z.object({
user: UsersSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const user = await server.services.user.deleteMe(req.permission.id);
return { user };
}
});
};
+13
View File
@@ -0,0 +1,13 @@
import { registerLoginRouter } from "./login-router";
import { registerSecretBlindIndexRouter } from "./secret-blind-index-router";
import { registerSecretRouter } from "./secret-router";
import { registerSignupRouter } from "./signup-router";
import { registerUserRouter } from "./user-router";
export const registerV3Routes = async (server: FastifyZodProvider) => {
await server.register(registerSignupRouter, { prefix: "/signup" });
await server.register(registerLoginRouter, { prefix: "/auth" });
await server.register(registerUserRouter, { prefix: "/users" });
await server.register(registerSecretRouter, { prefix: "/secrets" });
await server.register(registerSecretBlindIndexRouter, { prefix: "/workspaces" });
};
@@ -0,0 +1,105 @@
import { z } from "zod";
import { getConfig } from "@app/lib/config/env";
import { authRateLimit } from "@app/server/config/rateLimiter";
export const registerLoginRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/login1",
config: {
rateLimit: authRateLimit
},
schema: {
body: z.object({
email: z.string().email().trim(),
providerAuthToken: z.string().trim().optional(),
clientPublicKey: z.string().trim()
}),
response: {
200: z.object({
serverPublicKey: z.string(),
salt: z.string()
})
}
},
handler: async (req) => {
const { serverPublicKey, salt } = await server.services.login.loginGenServerPublicKey({
email: req.body.email,
clientPublicKey: req.body.clientPublicKey,
providerAuthToken: req.body.providerAuthToken
});
return { serverPublicKey, salt };
}
});
server.route({
method: "POST",
url: "/login2",
config: {
rateLimit: authRateLimit
},
schema: {
body: z.object({
email: z.string().email().trim(),
providerAuthToken: z.string().trim().optional(),
clientProof: z.string().trim()
}),
response: {
200: z.discriminatedUnion("mfaEnabled", [
z.object({ mfaEnabled: z.literal(true), token: z.string() }),
z.object({
mfaEnabled: z.literal(false),
encryptionVersion: z.number().default(1).nullable().optional(),
protectedKey: z.string().nullable(),
protectedKeyIV: z.string().nullable(),
protectedKeyTag: z.string().nullable(),
publicKey: z.string(),
encryptedPrivateKey: z.string(),
iv: z.string(),
tag: z.string(),
token: z.string()
})
])
}
},
handler: async (req, res) => {
const userAgent = req.headers["user-agent"];
if (!userAgent) throw new Error("user agent header is required");
const appCfg = getConfig();
const data = await server.services.login.loginExchangeClientProof({
email: req.body.email,
ip: req.realIp,
userAgent,
providerAuthToken: req.body.providerAuthToken,
clientProof: req.body.clientProof
});
if (data.isMfaEnabled) {
return { mfaEnabled: true, token: data.token } as const; // for discriminated union
}
res.setCookie("jid", data.token.refresh, {
httpOnly: true,
path: "/",
sameSite: "strict",
secure: appCfg.HTTPS_ENABLED
});
return {
mfaEnabled: false,
encryptionVersion: data.user.encryptionVersion,
token: data.token.access,
publicKey: data.user.publicKey,
encryptedPrivateKey: data.user.encryptedPrivateKey,
iv: data.user.iv,
tag: data.user.tag,
protectedKey: data.user.protectedKey,
protectedKeyIV: data.user.protectedKeyIV,
protectedKeyTag: data.user.protectedKeyTag
} as const;
}
});
};
@@ -0,0 +1,93 @@
import { z } from "zod";
import { SecretsSchema } from "@app/db/schemas";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
export const registerSecretBlindIndexRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/:projectId/secrets/blind-index-status",
method: "GET",
schema: {
params: z.object({
projectId: z.string().trim()
}),
response: {
200: z.boolean()
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const count = await server.services.secretBlindIndex.getSecretBlindIndexStatus({
projectId: req.params.projectId,
actorId: req.permission.id,
actor: req.permission.type
});
return count === 0;
}
});
server.route({
url: "/:projectId/secrets",
method: "GET",
schema: {
params: z.object({
projectId: z.string().trim()
}),
response: {
200: z.object({
secrets: SecretsSchema.omit({ secretBlindIndex: true })
.merge(
z.object({
environment: z.string(),
workspace: z.string()
})
)
.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const secrets = await server.services.secretBlindIndex.getProjectSecrets({
projectId: req.params.projectId,
actorId: req.permission.id,
actor: req.permission.type
});
return { secrets };
}
});
server.route({
url: "/:projectId/secrets/name",
method: "POST",
schema: {
params: z.object({
projectId: z.string().trim()
}),
body: z.object({
secretsToUpdate: z
.object({
secretName: z.string().trim(),
secretId: z.string().trim()
})
.array()
}),
response: {
200: z.object({
message: z.string()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
await server.services.secretBlindIndex.updateProjectSecretName({
projectId: req.params.projectId,
secretsToUpdate: req.body.secretsToUpdate,
actorId: req.permission.id,
actor: req.permission.type
});
return { message: "Successfully named workspace secrets" };
}
});
};
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,182 @@
import { z } from "zod";
import { UsersSchema } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env";
import { authRateLimit } from "@app/server/config/rateLimiter";
import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
export const registerSignupRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/email/signup",
method: "POST",
config: {
rateLimit: authRateLimit
},
schema: {
body: z.object({
email: z.string().email().trim()
}),
response: {
200: z.object({
message: z.string()
})
}
},
handler: async (req) => {
await server.services.signup.beginEmailSignupProcess(req.body.email);
return { message: `Sent an email verification code to ${req.body.email}` };
}
});
server.route({
url: "/email/verify",
method: "POST",
config: {
rateLimit: authRateLimit
},
schema: {
body: z.object({
email: z.string().email().trim(),
code: z.string().trim()
}),
response: {
200: z.object({
message: z.string(),
token: z.string(),
user: UsersSchema
})
}
},
handler: async (req) => {
const { token, user } = await server.services.signup.verifyEmailSignup(
req.body.email,
req.body.code
);
return { message: "Successfuly verified email", token, user };
}
});
server.route({
url: "/complete-account/signup",
method: "POST",
config: {
rateLimit: authRateLimit
},
schema: {
body: z.object({
email: z.string().email().trim(),
firstName: z.string().trim(),
lastName: z.string().trim().optional(),
protectedKey: z.string().trim(),
protectedKeyIV: z.string().trim(),
protectedKeyTag: z.string().trim(),
publicKey: z.string().trim(),
encryptedPrivateKey: z.string().trim(),
encryptedPrivateKeyIV: z.string().trim(),
encryptedPrivateKeyTag: z.string().trim(),
salt: z.string().trim(),
verifier: z.string().trim(),
organizationName: z.string().trim().min(1),
providerAuthToken: z.string().trim().optional().nullish(),
attributionSource: z.string().trim().optional()
}),
response: {
200: z.object({
message: z.string(),
user: UsersSchema,
token: z.string()
})
}
},
handler: async (req, res) => {
const userAgent = req.headers["user-agent"];
if (!userAgent) throw new Error("user agent header is required");
const appCfg = getConfig();
const { user, accessToken, refreshToken } =
await server.services.signup.completeEmailAccountSignup({
...req.body,
ip: req.realIp,
userAgent,
authorization: req.headers.authorization as string
});
server.services.telemetry.sendLoopsEvent(
user.email,
user.firstName || "",
user.lastName || ""
);
server.services.telemetry.sendPostHogEvents({
event: PostHogEventTypes.UserSignedUp,
distinctId: user.email,
properties: {
email: user.email,
attributionSource: req.body.attributionSource
}
});
res.setCookie("jid", refreshToken, {
httpOnly: true,
path: "/",
sameSite: "strict",
secure: appCfg.HTTPS_ENABLED
});
return { message: "Successfully set up account", user, token: accessToken };
}
});
server.route({
url: "/complete-account/invite",
method: "POST",
config: {
rateLimit: authRateLimit
},
schema: {
body: z.object({
email: z.string().email().trim(),
firstName: z.string().trim(),
lastName: z.string().trim().optional(),
protectedKey: z.string().trim(),
protectedKeyIV: z.string().trim(),
protectedKeyTag: z.string().trim(),
publicKey: z.string().trim(),
encryptedPrivateKey: z.string().trim(),
encryptedPrivateKeyIV: z.string().trim(),
encryptedPrivateKeyTag: z.string().trim(),
salt: z.string().trim(),
verifier: z.string().trim()
}),
response: {
200: z.object({
message: z.string(),
user: UsersSchema,
token: z.string()
})
}
},
handler: async (req, res) => {
const userAgent = req.headers["user-agent"];
if (!userAgent) throw new Error("user agent header is required");
const appCfg = getConfig();
const { user, accessToken, refreshToken } =
await server.services.signup.completeAccountInvite({
...req.body,
ip: req.realIp,
userAgent
});
res.setCookie("jid", refreshToken, {
httpOnly: true,
path: "/",
sameSite: "strict",
secure: appCfg.HTTPS_ENABLED
});
// TODO(akhilmhdh-pg): add telemetry service
return { message: "Successfully set up account", user, token: accessToken };
}
});
};
@@ -0,0 +1,24 @@
import { z } from "zod";
import { ApiKeysSchema } from "@app/db/schemas/api-keys";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
export const registerUserRouter = async (server: FastifyZodProvider) => {
server.route({
method: "GET",
url: "/me/api-keys",
schema: {
response: {
200: z.object({
apiKeyData: ApiKeysSchema.omit({ secretHash: true }).array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const apiKeyData = await server.services.apiKey.getMyApiKeys(req.permission.id);
return { apiKeyData };
}
});
};