mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-14 11:29:42 +00:00
feat: changed backend-pg to backend
This commit is contained in:
@@ -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);
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,142 @@
|
||||
/* eslint-disable */
|
||||
// Code taken from https://www.npmjs.com/package/fastify-type-provider-zod
|
||||
// Full credits goes to https://github.com/turkerdev
|
||||
// Code taken to keep in in house
|
||||
import type { FastifySchema, FastifySchemaCompiler, FastifyTypeProvider } from "fastify";
|
||||
import type { FastifySerializerCompiler } from "fastify/types/schema";
|
||||
import type { z, ZodAny, ZodTypeAny } from "zod";
|
||||
import { zodToJsonSchema } from "zod-to-json-schema";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type FreeformRecord = Record<string, any>;
|
||||
|
||||
const defaultSkipList = [
|
||||
"/documentation/",
|
||||
"/documentation/initOAuth",
|
||||
"/documentation/json",
|
||||
"/documentation/uiConfig",
|
||||
"/documentation/yaml",
|
||||
"/documentation/*",
|
||||
"/documentation/static/*"
|
||||
];
|
||||
|
||||
export interface ZodTypeProvider extends FastifyTypeProvider {
|
||||
output: this["input"] extends ZodTypeAny ? z.infer<this["input"]> : never;
|
||||
}
|
||||
|
||||
interface Schema extends FastifySchema {
|
||||
hide?: boolean;
|
||||
}
|
||||
|
||||
const zodToJsonSchemaOptions = {
|
||||
target: "openApi3",
|
||||
$refStrategy: "none"
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function hasOwnProperty<T, K extends PropertyKey>(obj: T, prop: K): obj is T & Record<K, any> {
|
||||
return Object.prototype.hasOwnProperty.call(obj, prop);
|
||||
}
|
||||
|
||||
function resolveSchema(maybeSchema: ZodAny | { properties: ZodAny }): Pick<ZodAny, "safeParse"> {
|
||||
if (hasOwnProperty(maybeSchema, "safeParse")) {
|
||||
return maybeSchema;
|
||||
}
|
||||
if (hasOwnProperty(maybeSchema, "properties")) {
|
||||
return maybeSchema.properties;
|
||||
}
|
||||
throw new Error(`Invalid schema passed: ${JSON.stringify(maybeSchema)}`);
|
||||
}
|
||||
|
||||
export const createJsonSchemaTransform = ({ skipList }: { skipList: readonly string[] }) => {
|
||||
return ({ schema, url }: { schema: Schema; url: string }) => {
|
||||
if (!schema) {
|
||||
return {
|
||||
schema,
|
||||
url
|
||||
};
|
||||
}
|
||||
|
||||
const { response, headers, querystring, body, params, hide, ...rest } = schema;
|
||||
|
||||
const transformed: FreeformRecord = {};
|
||||
|
||||
if (skipList.includes(url) || hide) {
|
||||
transformed.hide = true;
|
||||
return { schema: transformed, url };
|
||||
}
|
||||
|
||||
const zodSchemas: FreeformRecord = { headers, querystring, body, params };
|
||||
|
||||
for (const prop in zodSchemas) {
|
||||
const zodSchema = zodSchemas[prop];
|
||||
if (zodSchema) {
|
||||
transformed[prop] = zodToJsonSchema(zodSchema, zodToJsonSchemaOptions);
|
||||
}
|
||||
}
|
||||
|
||||
if (response) {
|
||||
transformed.response = {};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
for (const prop in response as any) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const schema = resolveSchema((response as any)[prop]);
|
||||
|
||||
const transformedResponse = zodToJsonSchema(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
schema as any,
|
||||
zodToJsonSchemaOptions
|
||||
);
|
||||
transformed.response[prop] = transformedResponse;
|
||||
}
|
||||
}
|
||||
|
||||
for (const prop in rest) {
|
||||
const meta = rest[prop as keyof typeof rest];
|
||||
if (meta) {
|
||||
transformed[prop] = meta;
|
||||
}
|
||||
}
|
||||
|
||||
return { schema: transformed, url };
|
||||
};
|
||||
};
|
||||
|
||||
export const jsonSchemaTransform = createJsonSchemaTransform({
|
||||
skipList: defaultSkipList
|
||||
});
|
||||
|
||||
export const validatorCompiler: FastifySchemaCompiler<ZodAny> =
|
||||
({ schema }) =>
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(data): any => {
|
||||
try {
|
||||
return { value: schema.parse(data) };
|
||||
} catch (error) {
|
||||
return { error };
|
||||
}
|
||||
};
|
||||
|
||||
export class ResponseValidationError extends Error {
|
||||
public details: FreeformRecord;
|
||||
|
||||
constructor(validationResult: FreeformRecord) {
|
||||
super("Response doesn't match the schema");
|
||||
this.name = "ResponseValidationError";
|
||||
this.details = validationResult.error;
|
||||
}
|
||||
}
|
||||
|
||||
export const serializerCompiler: FastifySerializerCompiler<ZodAny | { properties: ZodAny }> =
|
||||
({ schema: maybeSchema }) =>
|
||||
(data) => {
|
||||
const schema: Pick<ZodAny, "safeParse"> = resolveSchema(maybeSchema);
|
||||
|
||||
const result = schema.safeParse(data);
|
||||
if (result.success) {
|
||||
return JSON.stringify(result.data);
|
||||
}
|
||||
|
||||
throw new ResponseValidationError(result);
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
import fp from "fastify-plugin";
|
||||
|
||||
/*! https://github.com/pbojinov/request-ip/blob/9501cdf6e73059cc70fc6890adb086348d7cca46/src/index.js.
|
||||
MIT License. 2022 Petar Bojinov - [email protected] */
|
||||
const headersOrder = [
|
||||
"x-client-ip", // Most common
|
||||
"x-forwarded-for", // Mostly used by proxies
|
||||
"cf-connecting-ip", // Cloudflare
|
||||
"Cf-Pseudo-IPv4", // Cloudflare
|
||||
"fastly-client-ip",
|
||||
"true-client-ip", // Akamai and Cloudflare
|
||||
"x-real-ip", // Nginx
|
||||
"x-cluser-client-ip", // Rackspace LB
|
||||
"forwarded-for",
|
||||
"x-forwarded",
|
||||
"forwarded",
|
||||
"x-appengine-user-ip" // GCP App Engine
|
||||
];
|
||||
|
||||
export const fastifyIp = fp(async (fastify) => {
|
||||
fastify.decorateRequest("realIp", null);
|
||||
fastify.addHook("onRequest", async (req) => {
|
||||
const forwardedIpHeader = headersOrder.find((header) => Boolean(req.headers[header]));
|
||||
const forwardedIp = forwardedIpHeader ? req.headers[forwardedIpHeader] : undefined;
|
||||
if (forwardedIp) {
|
||||
req.realIp = Array.isArray(forwardedIp) ? forwardedIp[0] : forwardedIp;
|
||||
} else {
|
||||
req.realIp = req.ip;
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,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");
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -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"
|
||||
});
|
||||
});
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -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 };
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -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 };
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -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 };
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -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 };
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -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 };
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -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 };
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -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 };
|
||||
}
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user