feat: changed backend-pg to backend

This commit is contained in:
Akhil Mohan
2024-01-27 19:14:33 +05:30
parent d13eafcef7
commit 66d258f02b
792 changed files with 40298 additions and 40607 deletions
@@ -0,0 +1,114 @@
import { z } from "zod";
import { SuperAdminSchema, UsersSchema } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env";
import { UnauthorizedError } from "@app/lib/errors";
import { verifySuperAdmin } from "@app/server/plugins/auth/superAdmin";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
export const registerAdminRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/config",
method: "GET",
schema: {
response: {
200: z.object({
config: SuperAdminSchema
})
}
},
handler: () => {
const config = getServerCfg();
return { config };
}
});
server.route({
url: "/config",
method: "PATCH",
schema: {
body: z.object({
allowSignUp: z.boolean().optional()
}),
response: {
200: z.object({
config: SuperAdminSchema
})
}
},
onRequest: (req, _, done) => {
verifyAuth([AuthMode.JWT, AuthMode.API_KEY])(req);
verifySuperAdmin(req);
done();
},
handler: async (req) => {
const config = await server.services.superAdmin.updateServerCfg(req.body);
return { config };
}
});
server.route({
url: "/signup",
method: "POST",
schema: {
body: z.object({
email: z.string().email().trim(),
firstName: z.string().trim(),
lastName: z.string().trim().optional(),
protectedKey: z.string().trim(),
protectedKeyIV: z.string().trim(),
protectedKeyTag: z.string().trim(),
publicKey: z.string().trim(),
encryptedPrivateKey: z.string().trim(),
encryptedPrivateKeyIV: z.string().trim(),
encryptedPrivateKeyTag: z.string().trim(),
salt: z.string().trim(),
verifier: z.string().trim()
}),
response: {
200: z.object({
message: z.string(),
user: UsersSchema,
token: z.string()
})
}
},
handler: async (req, res) => {
const appCfg = getConfig();
const serverCfg = getServerCfg();
if (serverCfg.initialized)
throw new UnauthorizedError({ name: "Admin sign up", message: "Admin has been created" });
const { user, token } = await server.services.superAdmin.adminSignUp({
...req.body,
ip: req.realIp,
userAgent: req.headers["user-agent"] || ""
});
server.services.telemetry.sendPostHogEvents({
event: PostHogEventTypes.AdminInit,
distinctId: user.user.email,
properties: {
email: user.user.email,
lastName: user.user.lastName || "",
firstName: user.user.firstName || ""
}
});
res.setCookie("jid", token.refresh, {
httpOnly: true,
path: "/",
sameSite: "strict",
secure: appCfg.HTTPS_ENABLED
});
return {
message: "Successfully set up admin account",
user: user.user,
token: token.access
};
}
});
};
+108
View File
@@ -0,0 +1,108 @@
import jwt from "jsonwebtoken";
import { z } from "zod";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
import { authRateLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import {
AuthMode,
AuthModeRefreshJwtTokenPayload,
AuthTokenType
} from "@app/services/auth/auth-type";
export const registerAuthRoutes = async (server: FastifyZodProvider) => {
server.route({
url: "/logout",
method: "POST",
config:{
rateLimit:authRateLimit
},
schema: {
response: {
200: z.object({
message: z.string()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req, res) => {
const appCfg = getConfig();
if (req.auth.authMode === AuthMode.JWT) {
await server.services.login.logout(req.permission.id, req.auth.tokenVersionId);
}
res.cookie("jid", "", {
httpOnly: true,
path: "/",
sameSite: "strict",
secure: appCfg.HTTPS_ENABLED
});
return { message: "Successfully logged out" };
}
});
server.route({
url: "/checkAuth",
method: "POST",
schema: {
response: {
200: z.object({
message: z.literal("Authenticated")
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: () => ({ message: "Authenticated" as const })
});
server.route({
url: "/token",
method: "POST",
schema: {
response: {
200: z.object({
token: z.string()
})
}
},
handler: async (req) => {
const refreshToken = req.cookies.jid;
const appCfg = getConfig();
if (!refreshToken)
throw new BadRequestError({
name: "Auth token route",
message: "Failed to find refresh token"
});
const decodedToken = jwt.verify(
refreshToken,
appCfg.AUTH_SECRET
) as AuthModeRefreshJwtTokenPayload;
if (decodedToken.authTokenType !== AuthTokenType.REFRESH_TOKEN)
throw new UnauthorizedError({ message: "Invalid token", name: "Auth token route" });
const tokenVersion = await server.services.authToken.getUserTokenSessionById(
decodedToken.tokenVersionId,
decodedToken.userId
);
if (!tokenVersion)
throw new UnauthorizedError({ message: "Invalid token", name: "Auth token route" });
if (decodedToken.refreshVersion !== tokenVersion.refreshVersion)
throw new UnauthorizedError({ message: "Invalid token", name: "Auth token route" });
const token = jwt.sign(
{
authTokenType: AuthTokenType.ACCESS_TOKEN,
userId: decodedToken.userId,
tokenVersionId: tokenVersion.id,
accessVersion: tokenVersion.accessVersion
},
appCfg.AUTH_SECRET,
{ expiresIn: appCfg.JWT_AUTH_LIFETIME }
);
return { token };
}
});
};
@@ -0,0 +1,78 @@
import { z } from "zod";
import { ProjectBotsSchema } from "@app/db/schemas";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
export const registerProjectBotRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/:projectId",
method: "GET",
schema: {
params: z.object({
projectId: z.string().trim()
}),
response: {
200: z.object({
bot: ProjectBotsSchema.omit({
iv: true,
encryptedPrivateKey: true,
tag: true,
algorithm: true,
keyEncoding: true
})
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const bot = await server.services.projectBot.findBotByProjectId({
actor: req.permission.type,
actorId: req.permission.id,
projectId: req.params.projectId
});
return { bot };
}
});
server.route({
url: "/:botId/active",
method: "PATCH",
schema: {
body: z.object({
isActive: z.boolean(),
botKey: z
.object({
nonce: z.string().trim().optional(),
encryptedKey: z.string().trim().optional()
})
.optional()
}),
params: z.object({
botId: z.string().trim()
}),
response: {
200: z.object({
bot: ProjectBotsSchema.omit({
iv: true,
encryptedPrivateKey: true,
tag: true,
algorithm: true,
keyEncoding: true
})
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const bot = await server.services.projectBot.setBotActiveState({
actor: req.permission.type,
actorId: req.permission.id,
botId: req.params.botId,
botKey: req.body.botKey,
isActive: req.body.isActive
});
return { bot };
}
});
};
@@ -0,0 +1,33 @@
import { z } from "zod";
export const registerIdentityAccessTokenRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/token/renew",
method: "POST",
schema: {
body: z.object({
accessToken: z.string().trim()
}),
response: {
200: z.object({
accessToken: z.string(),
expiresIn: z.coerce.number(),
accessTokenMaxTTL: z.coerce.number(),
tokenType: z.literal("Bearer")
})
}
},
handler: async (req) => {
const { accessToken, identityAccessToken } =
await server.services.identityAccessToken.renewAccessToken({
accessToken: req.body.accessToken
});
return {
accessToken,
tokenType: "Bearer" as const,
expiresIn: identityAccessToken.accessTokenTTL,
accessTokenMaxTTL: identityAccessToken.accessTokenMaxTTL
};
}
});
};
@@ -0,0 +1,125 @@
import { z } from "zod";
import { IdentitiesSchema, OrgMembershipRole } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
export const registerIdentityRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
body: z.object({
name: z.string().trim(),
organizationId: z.string().trim(),
role: z.string().trim().min(1).default(OrgMembershipRole.NoAccess)
}),
response: {
200: z.object({
identity: IdentitiesSchema
})
}
},
handler: async (req) => {
const identity = await server.services.identity.createIdentity({
actor: req.permission.type,
actorId: req.permission.id,
...req.body,
orgId: req.body.organizationId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.body.organizationId,
event: {
type: EventType.CREATE_IDENTITY,
metadata: {
name: identity.name,
identityId: identity.id
}
}
});
return { identity };
}
});
server.route({
method: "PATCH",
url: "/:identityId",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
identityId: z.string()
}),
body: z.object({
name: z.string().trim().optional(),
role: z.string().trim().min(1).optional()
}),
response: {
200: z.object({
identity: IdentitiesSchema
})
}
},
handler: async (req) => {
const identity = await server.services.identity.updateIdentity({
actor: req.permission.type,
actorId: req.permission.id,
id: req.params.identityId,
...req.body
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identity.orgId,
event: {
type: EventType.UPDATE_IDENTITY,
metadata: {
name: identity.name,
identityId: identity.id
}
}
});
return { identity };
}
});
server.route({
method: "DELETE",
url: "/:identityId",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
identityId: z.string()
}),
response: {
200: z.object({
identity: IdentitiesSchema
})
}
},
handler: async (req) => {
const identity = await server.services.identity.deleteIdentity({
actor: req.permission.type,
actorId: req.permission.id,
id: req.params.identityId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identity.orgId,
event: {
type: EventType.DELETE_IDENTITY,
metadata: {
identityId: identity.id
}
}
});
return { identity };
}
});
};
+368
View File
@@ -0,0 +1,368 @@
import { z } from "zod";
import { IdentityUaClientSecretsSchema, IdentityUniversalAuthsSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { TIdentityTrustedIp } from "@app/services/identity/identity-types";
export const sanitizedClientSecretSchema = IdentityUaClientSecretsSchema.pick({
id: true,
createdAt: true,
updatedAt: true,
description: true,
clientSecretPrefix: true,
clientSecretNumUses: true,
clientSecretNumUsesLimit: true,
clientSecretTTL: true,
identityUAId: true,
isClientSecretRevoked: true
});
export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/universal-auth/login",
method: "POST",
schema: {
body: z.object({
clientId: z.string().trim(),
clientSecret: z.string().trim()
}),
response: {
200: z.object({
accessToken: z.string(),
expiresIn: z.coerce.number(),
accessTokenMaxTTL: z.coerce.number(),
tokenType: z.literal("Bearer")
})
}
},
handler: async (req) => {
const { identityUa, accessToken, identityAccessToken, validClientSecretInfo } =
await server.services.identityUa.login(
req.body.clientId,
req.body.clientSecret,
req.realIp
);
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
event: {
type: EventType.LOGIN_IDENTITY_UNIVERSAL_AUTH,
metadata: {
clientSecretId: validClientSecretInfo.id,
identityId: identityUa.identityId,
identityAccessTokenId: identityAccessToken.id,
identityUniversalAuthId: identityUa.id
}
}
});
return {
accessToken,
tokenType: "Bearer" as const,
expiresIn: identityUa.accessTokenTTL,
accessTokenMaxTTL: identityUa.accessTokenMaxTTL
};
}
});
server.route({
url: "/universal-auth/identities/:identityId",
method: "POST",
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
params: z.object({
identityId: z.string().trim()
}),
body: z.object({
clientSecretTrustedIps: z
.object({
ipAddress: z.string().trim()
})
.array()
.min(1)
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]),
accessTokenTrustedIps: z
.object({
ipAddress: z.string().trim()
})
.array()
.min(1)
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]),
accessTokenTTL: z
.number()
.int()
.min(1)
.refine((value) => value !== 0, {
message: "accessTokenTTL must have a non zero number"
})
.default(2592000),
accessTokenMaxTTL: z
.number()
.int()
.refine((value) => value !== 0, {
message: "accessTokenMaxTTL must have a non zero number"
})
.default(2592000), // 30 days
accessTokenNumUsesLimit: z.number().int().min(0).default(0)
}),
response: {
200: z.object({
identityUniversalAuth: IdentityUniversalAuthsSchema
})
}
},
handler: async (req) => {
const identityUniversalAuth = await server.services.identityUa.attachUa({
actor: req.permission.type,
actorId: req.permission.id,
...req.body,
identityId: req.params.identityId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityUniversalAuth.orgId,
event: {
type: EventType.ADD_IDENTITY_UNIVERSAL_AUTH,
metadata: {
identityId: identityUniversalAuth.identityId,
accessTokenTTL: identityUniversalAuth.accessTokenTTL,
accessTokenMaxTTL: identityUniversalAuth.accessTokenMaxTTL,
accessTokenTrustedIps:
identityUniversalAuth.accessTokenTrustedIps as TIdentityTrustedIp[],
clientSecretTrustedIps:
identityUniversalAuth.clientSecretTrustedIps as TIdentityTrustedIp[],
accessTokenNumUsesLimit: identityUniversalAuth.accessTokenNumUsesLimit
}
}
});
return { identityUniversalAuth };
}
});
server.route({
url: "/universal-auth/identities/:identityId",
method: "PATCH",
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
params: z.object({
identityId: z.string()
}),
body: z.object({
clientSecretTrustedIps: z
.object({
ipAddress: z.string().trim()
})
.array()
.min(1)
.optional(),
accessTokenTrustedIps: z
.object({
ipAddress: z.string().trim()
})
.array()
.min(1)
.optional(),
accessTokenTTL: z.number().int().min(0).optional(),
accessTokenNumUsesLimit: z.number().int().min(0).optional(),
accessTokenMaxTTL: z
.number()
.int()
.refine((value) => value !== 0, {
message: "accessTokenMaxTTL must have a non zero number"
})
.optional()
}),
response: {
200: z.object({
identityUniversalAuth: IdentityUniversalAuthsSchema
})
}
},
handler: async (req) => {
const identityUniversalAuth = await server.services.identityUa.updateUa({
actor: req.permission.type,
actorId: req.permission.id,
...req.body,
identityId: req.params.identityId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityUniversalAuth.orgId,
event: {
type: EventType.UPDATE_IDENTITY_UNIVERSAL_AUTH,
metadata: {
identityId: identityUniversalAuth.identityId,
accessTokenTTL: identityUniversalAuth.accessTokenTTL,
accessTokenMaxTTL: identityUniversalAuth.accessTokenMaxTTL,
accessTokenTrustedIps:
identityUniversalAuth.accessTokenTrustedIps as TIdentityTrustedIp[],
clientSecretTrustedIps:
identityUniversalAuth.clientSecretTrustedIps as TIdentityTrustedIp[],
accessTokenNumUsesLimit: identityUniversalAuth.accessTokenNumUsesLimit
}
}
});
return { identityUniversalAuth };
}
});
server.route({
url: "/universal-auth/identities/:identityId",
method: "GET",
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
params: z.object({
identityId: z.string()
}),
response: {
200: z.object({
identityUniversalAuth: IdentityUniversalAuthsSchema
})
}
},
handler: async (req) => {
const identityUniversalAuth = await server.services.identityUa.getIdentityUa({
actor: req.permission.type,
actorId: req.permission.id,
identityId: req.params.identityId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityUniversalAuth.orgId,
event: {
type: EventType.GET_IDENTITY_UNIVERSAL_AUTH,
metadata: {
identityId: identityUniversalAuth.identityId
}
}
});
return { identityUniversalAuth };
}
});
server.route({
url: "/universal-auth/identities/:identityId/client-secrets",
method: "POST",
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
params: z.object({
identityId: z.string()
}),
body: z.object({
description: z.string().trim().default(""),
numUsesLimit: z.number().min(0).default(0),
ttl: z.number().min(0).default(0)
}),
response: {
200: z.object({
clientSecret: z.string(),
clientSecretData: sanitizedClientSecretSchema
})
}
},
handler: async (req) => {
const { clientSecret, clientSecretData, orgId } =
await server.services.identityUa.createUaClientSecret({
actor: req.permission.type,
actorId: req.permission.id,
identityId: req.params.identityId,
...req.body
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId,
event: {
type: EventType.CREATE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET,
metadata: {
identityId: req.params.identityId,
clientSecretId: clientSecretData.id
}
}
});
return { clientSecret, clientSecretData };
}
});
server.route({
url: "/universal-auth/identities/:identityId/client-secrets",
method: "GET",
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
params: z.object({
identityId: z.string()
}),
response: {
200: z.object({
clientSecretData: sanitizedClientSecretSchema.array()
})
}
},
handler: async (req) => {
const { clientSecrets: clientSecretData, orgId } =
await server.services.identityUa.getUaClientSecrets({
actor: req.permission.type,
actorId: req.permission.id,
identityId: req.params.identityId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId,
event: {
type: EventType.GET_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRETS,
metadata: {
identityId: req.params.identityId
}
}
});
return { clientSecretData };
}
});
server.route({
url: "/universal-auth/identities/:identityId/client-secrets/:clientSecretId/revoke",
method: "POST",
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
params: z.object({
identityId: z.string(),
clientSecretId: z.string()
}),
response: {
200: z.object({
clientSecretData: sanitizedClientSecretSchema
})
}
},
handler: async (req) => {
const clientSecretData = await server.services.identityUa.revokeUaClientSecret({
actor: req.permission.type,
actorId: req.permission.id,
identityId: req.params.identityId,
clientSecretId: req.params.clientSecretId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: clientSecretData.orgId,
event: {
type: EventType.REVOKE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET,
metadata: {
identityId: clientSecretData.identityId,
clientSecretId: clientSecretData.id
}
}
});
return { clientSecretData };
}
});
};
+59
View File
@@ -0,0 +1,59 @@
import { registerAdminRouter } from "./admin-router";
import { registerAuthRoutes } from "./auth-router";
import { registerProjectBotRouter } from "./bot-router";
import { registerIdentityAccessTokenRouter } from "./identity-access-token-router";
import { registerIdentityRouter } from "./identity-router";
import { registerIdentityUaRouter } from "./identity-ua";
import { registerIntegrationAuthRouter } from "./integration-auth-router";
import { registerIntegrationRouter } from "./integration-router";
import { registerInviteOrgRouter } from "./invite-org-router";
import { registerOrgRouter } from "./organization-router";
import { registerPasswordRouter } from "./password-router";
import { registerProjectEnvRouter } from "./project-env-router";
import { registerProjectKeyRouter } from "./project-key-router";
import { registerProjectMembershipRouter } from "./project-membership-router";
import { registerProjectRouter } from "./project-router";
import { registerSecretFolderRouter } from "./secret-folder-router";
import { registerSecretImportRouter } from "./secret-import-router";
import { registerSecretTagRouter } from "./secret-tag-router";
import { registerSsoRouter } from "./sso-router";
import { registerUserActionRouter } from "./user-action-router";
import { registerUserRouter } from "./user-router";
import { registerWebhookRouter } from "./webhook-router";
export const registerV1Routes = async (server: FastifyZodProvider) => {
await server.register(registerSsoRouter, { prefix: "/sso" });
await server.register(
async (authRouter) => {
await authRouter.register(registerAuthRoutes);
await authRouter.register(registerIdentityUaRouter);
await authRouter.register(registerIdentityAccessTokenRouter);
},
{ prefix: "/auth" }
);
await server.register(registerPasswordRouter, { prefix: "/password" });
await server.register(registerOrgRouter, { prefix: "/organization" });
await server.register(registerAdminRouter, { prefix: "/admin" });
await server.register(registerUserRouter, { prefix: "/user" });
await server.register(registerInviteOrgRouter, { prefix: "/invite-org" });
await server.register(registerUserActionRouter, { prefix: "/user-action" });
await server.register(registerSecretImportRouter, { prefix: "/secret-imports" });
await server.register(registerSecretFolderRouter, { prefix: "/folders" });
await server.register(
async (projectRouter) => {
await projectRouter.register(registerProjectRouter);
await projectRouter.register(registerProjectEnvRouter);
await projectRouter.register(registerProjectKeyRouter);
await projectRouter.register(registerProjectMembershipRouter);
await projectRouter.register(registerSecretTagRouter);
},
{ prefix: "/workspace" }
);
await server.register(registerProjectBotRouter, { prefix: "/bot" });
await server.register(registerIntegrationRouter, { prefix: "/integration" });
await server.register(registerIntegrationAuthRouter, { prefix: "/integration-auth" });
await server.register(registerWebhookRouter, { prefix: "/webhooks" });
await server.register(registerIdentityRouter, { prefix: "/identities" });
};
@@ -0,0 +1,656 @@
import { z } from "zod";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { integrationAuthPubSchema } from "../sanitizedSchemas";
export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/integration-options",
method: "GET",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
response: {
200: z.object({
integrationOptions: z
.object({
name: z.string(),
slug: z.string(),
clientSlug: z.string().optional(),
image: z.string(),
isAvailable: z.boolean().optional(),
type: z.string(),
clientId: z.string().optional(),
docsLink: z.string().optional()
})
.array()
})
}
},
handler: async () => {
const integrationOptions = await server.services.integrationAuth.getIntegrationOptions();
return { integrationOptions };
}
});
server.route({
url: "/:integrationAuthId",
method: "GET",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
integrationAuthId: z.string().trim()
}),
response: {
200: z.object({
integrationAuth: integrationAuthPubSchema
})
}
},
handler: async (req) => {
const integrationAuth = await server.services.integrationAuth.getIntegrationAuth({
actorId: req.permission.id,
actor: req.permission.type,
id: req.params.integrationAuthId
});
return { integrationAuth };
}
});
server.route({
url: "/",
method: "DELETE",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
querystring: z.object({
integration: z.string().trim(),
projectId: z.string().trim()
}),
response: {
200: z.object({
integrationAuth: integrationAuthPubSchema.array()
})
}
},
handler: async (req) => {
const integrationAuth = await server.services.integrationAuth.deleteIntegrationAuths({
actorId: req.permission.id,
actor: req.permission.type,
integration: req.query.integration,
projectId: req.query.projectId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.query.projectId,
event: {
type: EventType.UNAUTHORIZE_INTEGRATION,
metadata: {
integration: req.query.integration
}
}
});
return { integrationAuth };
}
});
server.route({
url: "/:integrationAuthId",
method: "DELETE",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
integrationAuthId: z.string().trim()
}),
response: {
200: z.object({
integrationAuth: integrationAuthPubSchema
})
}
},
handler: async (req) => {
const integrationAuth = await server.services.integrationAuth.deleteIntegrationAuthById({
actorId: req.permission.id,
actor: req.permission.type,
id: req.params.integrationAuthId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: integrationAuth.projectId,
event: {
type: EventType.UNAUTHORIZE_INTEGRATION,
metadata: {
integration: integrationAuth.integration
}
}
});
return { integrationAuth };
}
});
server.route({
url: "/oauth-token",
method: "POST",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
body: z.object({
workspaceId: z.string().trim(),
code: z.string().trim(),
integration: z.string().trim(),
url: z.string().trim().url().optional()
}),
response: {
200: z.object({
integrationAuth: integrationAuthPubSchema
})
}
},
handler: async (req) => {
const integrationAuth = await server.services.integrationAuth.oauthExchange({
actorId: req.permission.id,
actor: req.permission.type,
projectId: req.body.workspaceId,
...req.body
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.body.workspaceId,
event: {
type: EventType.AUTHORIZE_INTEGRATION,
metadata: {
integration: integrationAuth.integration
}
}
});
return { integrationAuth };
}
});
server.route({
url: "/access-token",
method: "POST",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
body: z.object({
workspaceId: z.string().trim(),
integration: z.string().trim(),
accessId: z.string().trim().optional(),
accessToken: z.string().trim().optional(),
url: z.string().url().trim().optional(),
namespace: z.string().trim().optional(),
refreshToken: z.string().trim().optional()
}),
response: {
200: z.object({
integrationAuth: integrationAuthPubSchema
})
}
},
handler: async (req) => {
const integrationAuth = await server.services.integrationAuth.saveIntegrationToken({
actorId: req.permission.id,
actor: req.permission.type,
projectId: req.body.workspaceId,
...req.body
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.body.workspaceId,
event: {
type: EventType.AUTHORIZE_INTEGRATION,
metadata: {
integration: integrationAuth.integration
}
}
});
return { integrationAuth };
}
});
server.route({
url: "/:integrationAuthId/apps",
method: "GET",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
integrationAuthId: z.string().trim()
}),
querystring: z.object({
teamId: z.string().trim().optional(),
workspaceSlug: z.string().trim().optional()
}),
response: {
200: z.object({
apps: z
.object({
name: z.string(),
appId: z.coerce.string().optional(),
owner: z.string().optional()
})
.array()
})
}
},
handler: async (req) => {
const apps = await server.services.integrationAuth.getIntegrationApps({
actorId: req.permission.id,
actor: req.permission.type,
id: req.params.integrationAuthId,
...req.query
});
return { apps };
}
});
server.route({
url: "/:integrationAuthId/teams",
method: "GET",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
integrationAuthId: z.string().trim()
}),
response: {
200: z.object({
teams: z
.object({
name: z.string(),
id: z.string().optional()
})
.array()
})
}
},
handler: async (req) => {
const teams = await server.services.integrationAuth.getIntegrationAuthTeams({
actorId: req.permission.id,
actor: req.permission.type,
id: req.params.integrationAuthId
});
return { teams };
}
});
server.route({
url: "/:integrationAuthId/vercel/branches",
method: "GET",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
integrationAuthId: z.string().trim()
}),
querystring: z.object({
appId: z.string().trim()
}),
response: {
200: z.object({
branches: z.string().array()
})
}
},
handler: async (req) => {
const branches = await server.services.integrationAuth.getVercelBranches({
actorId: req.permission.id,
actor: req.permission.type,
id: req.params.integrationAuthId,
appId: req.query.appId
});
return { branches };
}
});
server.route({
url: "/:integrationAuthId/checkly/groups",
method: "GET",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
integrationAuthId: z.string().trim()
}),
querystring: z.object({
accountId: z.string().trim()
}),
response: {
200: z.object({
groups: z.object({ name: z.string(), groupId: z.number() }).array()
})
}
},
handler: async (req) => {
const groups = await server.services.integrationAuth.getChecklyGroups({
actorId: req.permission.id,
actor: req.permission.type,
id: req.params.integrationAuthId,
accountId: req.query.accountId
});
return { groups };
}
});
server.route({
url: "/:integrationAuthId/qovery/orgs",
method: "GET",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
integrationAuthId: z.string().trim()
}),
response: {
200: z.object({
orgs: z.object({ name: z.string(), orgId: z.string() }).array()
})
}
},
handler: async (req) => {
const orgs = await server.services.integrationAuth.getQoveryOrgs({
actorId: req.permission.id,
actor: req.permission.type,
id: req.params.integrationAuthId
});
return { orgs };
}
});
server.route({
url: "/:integrationAuthId/qovery/projects",
method: "GET",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
integrationAuthId: z.string().trim()
}),
querystring: z.object({
orgId: z.string().trim()
}),
response: {
200: z.object({
projects: z.object({ name: z.string(), projectId: z.string() }).array()
})
}
},
handler: async (req) => {
const projects = await server.services.integrationAuth.getQoveryProjects({
actorId: req.permission.id,
actor: req.permission.type,
id: req.params.integrationAuthId,
orgId: req.query.orgId
});
return { projects };
}
});
server.route({
url: "/:integrationAuthId/qovery/environments",
method: "GET",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
integrationAuthId: z.string().trim()
}),
querystring: z.object({
projectId: z.string().trim()
}),
response: {
200: z.object({
environments: z.object({ name: z.string(), environmentId: z.string() }).array()
})
}
},
handler: async (req) => {
const environments = await server.services.integrationAuth.getQoveryEnvs({
actorId: req.permission.id,
actor: req.permission.type,
id: req.params.integrationAuthId,
projectId: req.query.projectId
});
return { environments };
}
});
server.route({
url: "/:integrationAuthId/qovery/apps",
method: "GET",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
integrationAuthId: z.string().trim()
}),
querystring: z.object({
environmentId: z.string().trim()
}),
response: {
200: z.object({
apps: z.object({ name: z.string(), appId: z.string() }).array()
})
}
},
handler: async (req) => {
const apps = await server.services.integrationAuth.getQoveryApps({
actorId: req.permission.id,
actor: req.permission.type,
id: req.params.integrationAuthId,
environmentId: req.query.environmentId
});
return { apps };
}
});
server.route({
url: "/:integrationAuthId/qovery/containers",
method: "GET",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
integrationAuthId: z.string().trim()
}),
querystring: z.object({
environmentId: z.string().trim()
}),
response: {
200: z.object({
containers: z.object({ name: z.string(), appId: z.string() }).array()
})
}
},
handler: async (req) => {
const containers = await server.services.integrationAuth.getQoveryContainers({
actorId: req.permission.id,
actor: req.permission.type,
id: req.params.integrationAuthId,
environmentId: req.query.environmentId
});
return { containers };
}
});
server.route({
url: "/:integrationAuthId/qovery/jobs",
method: "GET",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
integrationAuthId: z.string().trim()
}),
querystring: z.object({
environmentId: z.string().trim()
}),
response: {
200: z.object({
jobs: z.object({ name: z.string(), appId: z.string() }).array()
})
}
},
handler: async (req) => {
const jobs = await server.services.integrationAuth.getQoveryJobs({
actorId: req.permission.id,
actor: req.permission.type,
id: req.params.integrationAuthId,
environmentId: req.query.environmentId
});
return { jobs };
}
});
server.route({
url: "/:integrationAuthId/railway/environments",
method: "GET",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
integrationAuthId: z.string().trim()
}),
querystring: z.object({
appId: z.string().trim()
}),
response: {
200: z.object({
environments: z.object({ name: z.string(), environmentId: z.string() }).array()
})
}
},
handler: async (req) => {
const environments = await server.services.integrationAuth.getRailwayEnvironments({
actorId: req.permission.id,
actor: req.permission.type,
id: req.params.integrationAuthId,
appId: req.query.appId
});
return { environments };
}
});
server.route({
url: "/:integrationAuthId/railway/services",
method: "GET",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
integrationAuthId: z.string().trim()
}),
querystring: z.object({
appId: z.string().trim()
}),
response: {
200: z.object({
services: z.object({ name: z.string(), serviceId: z.string() }).array()
})
}
},
handler: async (req) => {
const services = await server.services.integrationAuth.getRailwayServices({
actorId: req.permission.id,
actor: req.permission.type,
id: req.params.integrationAuthId,
appId: req.query.appId
});
return { services };
}
});
server.route({
url: "/:integrationAuthId/bitbucket/workspaces",
method: "GET",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
integrationAuthId: z.string().trim()
}),
response: {
200: z.object({
workspaces: z
.object({
name: z.string(),
slug: z.string(),
uuid: z.string(),
type: z.string(),
is_private: z.boolean(),
created_on: z.string(),
updated_on: z.string().optional()
})
.array()
})
}
},
handler: async (req) => {
const workspaces = await server.services.integrationAuth.getBitbucketWorkspaces({
actorId: req.permission.id,
actor: req.permission.type,
id: req.params.integrationAuthId
});
return { workspaces };
}
});
server.route({
url: "/:integrationAuthId/northflank/secret-groups",
method: "GET",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
integrationAuthId: z.string().trim()
}),
querystring: z.object({
appId: z.string().trim()
}),
response: {
200: z.object({
secretGroups: z
.object({
name: z.string(),
groupId: z.string()
})
.array()
})
}
},
handler: async (req) => {
const secretGroups = await server.services.integrationAuth.getNorthFlankSecretGroups({
actorId: req.permission.id,
actor: req.permission.type,
id: req.params.integrationAuthId,
appId: req.query.appId
});
return { secretGroups };
}
});
server.route({
url: "/:integrationAuthId/teamcity/build-configs",
method: "GET",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
integrationAuthId: z.string().trim()
}),
querystring: z.object({
appId: z.string().trim()
}),
response: {
200: z.object({
buildConfigs: z
.object({
name: z.string(),
buildConfigId: z.string()
})
.array()
})
}
},
handler: async (req) => {
const buildConfigs = await server.services.integrationAuth.getTeamcityBuildConfigs({
actorId: req.permission.id,
actor: req.permission.type,
id: req.params.integrationAuthId,
appId: req.query.appId
});
return { buildConfigs };
}
});
};
@@ -0,0 +1,163 @@
import { z } from "zod";
import { IntegrationsSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { shake } from "@app/lib/fn";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/",
method: "POST",
schema: {
body: z.object({
integrationAuthId: z.string().trim(),
app: z.string().trim().optional(),
isActive: z.boolean(),
appId: z.string().trim().optional(),
secretPath: z.string().trim().default("/"),
sourceEnvironment: z.string().trim(),
targetEnvironment: z.string().trim().optional(),
targetEnvironmentId: z.string().trim().optional(),
targetService: z.string().trim().optional(),
targetServiceId: z.string().trim().optional(),
owner: z.string().trim().optional(),
path: z.string().trim().optional(),
region: z.string().trim().optional(),
scope: z.string().trim().optional(),
metadata: z
.object({
secretPrefix: z.string().optional(),
secretSuffix: z.string().optional(),
secretGCPLabel: z
.object({
labelName: z.string(),
labelValue: z.string()
})
.optional()
})
.optional()
}),
response: {
200: z.object({
integration: IntegrationsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { integration, integrationAuth } = await server.services.integration.createIntegration({
actorId: req.permission.id,
actor: req.permission.type,
...req.body
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: integrationAuth.projectId,
event: {
type: EventType.CREATE_INTEGRATION,
metadata: shake({
integrationId: integration.id.toString(),
integration: integration.integration,
environment: req.body.sourceEnvironment,
secretPath: req.body.secretPath,
url: integration.url,
app: integration.app,
appId: integration.appId,
targetEnvironment: integration.targetEnvironment,
targetEnvironmentId: integration.targetEnvironmentId,
targetService: integration.targetService,
targetServiceId: integration.targetServiceId,
path: integration.path,
region: integration.region
}) as any
}
});
return { integration };
}
});
server.route({
url: "/:integrationId",
method: "PATCH",
schema: {
params: z.object({
integrationId: z.string().trim()
}),
body: z.object({
app: z.string().trim(),
appId: z.string().trim(),
isActive: z.boolean(),
secretPath: z.string().trim().default("/"),
targetEnvironment: z.string().trim(),
owner: z.string().trim(),
environment: z.string().trim()
}),
response: {
200: z.object({
integration: IntegrationsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const integration = await server.services.integration.updateIntegration({
actorId: req.permission.id,
actor: req.permission.type,
id: req.params.integrationId,
...req.body
});
return { integration };
}
});
server.route({
url: "/:integrationId",
method: "DELETE",
schema: {
params: z.object({
integrationId: z.string().trim()
}),
response: {
200: z.object({
integration: IntegrationsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const integration = await server.services.integration.deleteIntegration({
actorId: req.permission.id,
actor: req.permission.type,
id: req.params.integrationId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: integration.projectId,
event: {
type: EventType.DELETE_INTEGRATION,
metadata: shake({
integrationId: integration.id,
integration: integration.integration,
environment: integration.environment.slug,
secretPath: integration.secretPath,
url: integration.url,
app: integration.app,
appId: integration.appId,
targetEnvironment: integration.targetEnvironment,
targetEnvironmentId: integration.targetEnvironmentId,
targetService: integration.targetService,
targetServiceId: integration.targetServiceId,
path: integration.path,
region: integration.region
}) as any
}
});
return { integration };
}
});
// TODO(akhilmhdh-pg): manual sync
};
@@ -0,0 +1,70 @@
import { z } from "zod";
import { UsersSchema } from "@app/db/schemas";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { ActorType, AuthMode } from "@app/services/auth/auth-type";
export const registerInviteOrgRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/signup",
method: "POST",
schema: {
body: z.object({
inviteeEmail: z.string().trim().email(),
organizationId: z.string().trim()
}),
response: {
200: z.object({
message: z.string(),
completeInviteLink: z.string().optional()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
if (req.auth.actor !== ActorType.USER) return;
const completeInviteLink = await server.services.org.inviteUserToOrganization({
orgId: req.body.organizationId,
userId: req.permission.id,
inviteeEmail: req.body.inviteeEmail
});
return {
completeInviteLink,
message: `Send an invite link to ${req.body.inviteeEmail}`
};
}
});
server.route({
url: "/verify",
method: "POST",
schema: {
body: z.object({
email: z.string().trim().email(),
organizationId: z.string().trim(),
code: z.string().trim()
}),
response: {
200: z.object({
message: z.string(),
token: z.string().optional(),
user: UsersSchema
})
}
},
handler: async (req) => {
const { user, token } = await server.services.org.verifyUserToOrg({
orgId: req.body.organizationId,
code: req.body.code,
email: req.body.email
});
return {
message: "Successfully verified email",
user,
token
};
}
});
};
@@ -0,0 +1,179 @@
import { z } from "zod";
import {
IncidentContactsSchema,
OrganizationsSchema,
OrgMembershipsSchema,
UsersSchema
} from "@app/db/schemas";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
export const registerOrgRouter = async (server: FastifyZodProvider) => {
server.route({
method: "GET",
url: "/",
schema: {
response: {
200: z.object({
organizations: OrganizationsSchema.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const organizations = await server.services.org.findAllOrganizationOfUser(req.permission.id);
return { organizations };
}
});
server.route({
method: "GET",
url: "/:organizationId",
schema: {
params: z.object({
organizationId: z.string().trim()
}),
response: {
200: z.object({
organization: OrganizationsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const organization = await server.services.org.findOrganizationById(
req.permission.id,
req.params.organizationId
);
return { organization };
}
});
server.route({
method: "GET",
url: "/:organizationId/users",
schema: {
params: z.object({
organizationId: z.string().trim()
}),
response: {
200: z.object({
users: OrgMembershipsSchema.merge(
z.object({
user: UsersSchema.pick({
email: true,
firstName: true,
lastName: true,
id: true
}).merge(z.object({ publicKey: z.string().nullable() }))
})
)
.omit({ createdAt: true, updatedAt: true })
.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const users = await server.services.org.findAllOrgMembers(
req.permission.id,
req.params.organizationId
);
return { users };
}
});
server.route({
method: "PATCH",
url: "/:organizationId/name",
schema: {
params: z.object({ organizationId: z.string().trim() }),
body: z.object({ name: z.string().trim() }),
response: {
200: z.object({
message: z.string(),
organization: OrganizationsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const organization = await server.services.org.updateOrgName(
req.permission.id,
req.params.organizationId,
req.body.name
);
return {
message: "Successfully changed organization name",
organization
};
}
});
server.route({
method: "GET",
url: "/:organizationId/incidentContactOrg",
schema: {
params: z.object({ organizationId: z.string().trim() }),
response: {
200: z.object({
incidentContactsOrg: IncidentContactsSchema.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const incidentContactsOrg = await req.server.services.org.findIncidentContacts(
req.permission.id,
req.params.organizationId
);
return { incidentContactsOrg };
}
});
server.route({
method: "POST",
url: "/:organizationId/incidentContactOrg",
schema: {
params: z.object({ organizationId: z.string().trim() }),
body: z.object({ email: z.string().email().trim() }),
response: {
200: z.object({
incidentContactsOrg: IncidentContactsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const incidentContactsOrg = await req.server.services.org.createIncidentContact(
req.permission.id,
req.params.organizationId,
req.body.email
);
return { incidentContactsOrg };
}
});
server.route({
method: "DELETE",
url: "/:organizationId/incidentContactOrg/:incidentContactId",
schema: {
params: z.object({ organizationId: z.string().trim(), incidentContactId: z.string().trim() }),
response: {
200: z.object({
incidentContactsOrg: IncidentContactsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const incidentContactsOrg = await req.server.services.org.deleteIncidentContact(
req.permission.id,
req.params.organizationId,
req.params.incidentContactId
);
return { incidentContactsOrg };
}
});
};
@@ -0,0 +1,225 @@
import { z } from "zod";
import { BackupPrivateKeySchema, UsersSchema } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env";
import { passwordRateLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { validateSignUpAuthorization } from "@app/services/auth/auth-fns";
import { AuthMode } from "@app/services/auth/auth-type";
export const registerPasswordRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/srp1",
config: {
rateLimit:passwordRateLimit
},
schema: {
body: z.object({
clientPublicKey: z.string().trim()
}),
response: {
200: z.object({
serverPublicKey: z.string(),
salt: z.string()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { salt, serverPublicKey } = await server.services.password.generateServerPubKey(
req.permission.id,
req.body.clientPublicKey
);
return { salt, serverPublicKey };
}
});
server.route({
method: "POST",
url: "/change-password",
config: {
rateLimit:passwordRateLimit
},
schema: {
body: z.object({
clientProof: z.string().trim(),
protectedKey: z.string().trim(),
protectedKeyIV: z.string().trim(),
protectedKeyTag: z.string().trim(),
encryptedPrivateKey: z.string().trim(),
encryptedPrivateKeyIV: z.string().trim(),
encryptedPrivateKeyTag: z.string().trim(),
salt: z.string().trim(),
verifier: z.string().trim()
}),
response: {
200: z.object({
message: z.string()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req, res) => {
const appCfg = getConfig();
await server.services.password.changePassword({ ...req.body, userId: req.permission.id });
res.cookie("jid", appCfg.COOKIE_SECRET_SIGN_KEY, {
httpOnly: true,
path: "/",
sameSite: "strict",
secure: appCfg.HTTPS_ENABLED
});
return { message: "Successfully changed password" };
}
});
server.route({
method: "POST",
url: "/email/password-reset",
config: {
rateLimit:passwordRateLimit
},
schema: {
body: z.object({
email: z.string().email().trim()
}),
response: {
200: z.object({
message: z.string()
})
}
},
handler: async (req) => {
await server.services.password.sendPasswordResetEmail(req.body.email);
return {
message: "If an account exists with this email, a password reset link has been sent"
};
}
});
server.route({
method: "POST",
url: "/email/password-reset-verify",
config: {
rateLimit:passwordRateLimit
},
schema: {
body: z.object({
email: z.string().email().trim(),
code: z.string().trim()
}),
response: {
200: z.object({
message: z.string(),
user: UsersSchema,
token: z.string()
})
}
},
handler: async (req) => {
const { token, user } = await server.services.password.verifyPasswordResetEmail(
req.body.email,
req.body.code
);
return {
message: "Successfully verified email",
user,
token
};
}
});
server.route({
method: "POST",
url: "/backup-private-key",
config: {
rateLimit:passwordRateLimit
},
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
body: z.object({
clientProof: z.string().trim(),
encryptedPrivateKey: z.string().trim(),
iv: z.string().trim(),
tag: z.string().trim(),
salt: z.string().trim(),
verifier: z.string().trim()
}),
response: {
200: z.object({
message: z.string(),
backupPrivateKey: BackupPrivateKeySchema.omit({ verifier: true })
})
}
},
handler: async (req) => {
const token = validateSignUpAuthorization(req.headers.authorization as string, "",false)!
const backupPrivateKey = await server.services.password.createBackupPrivateKey({
...req.body,
userId: token.userId,
});
if (!backupPrivateKey) throw new Error("Failed to create backup key");
return { message: "Successfully updated backup private key", backupPrivateKey };
}
});
server.route({
method: "GET",
url: "/backup-private-key",
config: {
rateLimit:passwordRateLimit
},
schema: {
response: {
200: z.object({
message: z.string(),
backupPrivateKey: BackupPrivateKeySchema.omit({ verifier: true })
})
}
},
handler: async (req) => {
const token = validateSignUpAuthorization(req.headers.authorization as string, "",false)!
const backupPrivateKey = await server.services.password.getBackupPrivateKeyOfUser(
token.userId
);
if (!backupPrivateKey) throw new Error("Failed to find backup key");
return { message: "Successfully fetched backup private key", backupPrivateKey };
}
});
server.route({
method: "POST",
url: "/password-reset",
schema: {
body: z.object({
protectedKey: z.string().trim(),
protectedKeyIV: z.string().trim(),
protectedKeyTag: z.string().trim(),
encryptedPrivateKey: z.string().trim(),
encryptedPrivateKeyIV: z.string().trim(),
encryptedPrivateKeyTag: z.string().trim(),
salt: z.string().trim(),
verifier: z.string().trim()
}),
response: {
200: z.object({
message: z.string()
})
}
},
handler: async (req) => {
const token = validateSignUpAuthorization(req.headers.authorization as string, "",false)!
await server.services.password.resetPasswordByBackupKey({
...req.body,
userId: token.userId,
});
return { message: "Successfully updated backup private key" };
}
});
};
@@ -0,0 +1,155 @@
import { z } from "zod";
import { ProjectEnvironmentsSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
export const registerProjectEnvRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/:workspaceId/environments",
method: "POST",
schema: {
params: z.object({
workspaceId: z.string().trim()
}),
body: z.object({
name: z.string().trim(),
slug: z.string().trim()
}),
response: {
200: z.object({
message: z.string(),
workspace: z.string(),
environment: ProjectEnvironmentsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const environment = await server.services.projectEnv.createEnvironment({
actorId: req.permission.id,
actor: req.permission.type,
projectId: req.params.workspaceId,
...req.body
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: environment.projectId,
event: {
type: EventType.CREATE_ENVIRONMENT,
metadata: {
name: environment.name,
slug: environment.slug
}
}
});
return {
message: "Successfully created new environment",
workspace: req.params.workspaceId,
environment
};
}
});
server.route({
url: "/:workspaceId/environments/:id",
method: "PATCH",
schema: {
params: z.object({
workspaceId: z.string().trim(),
id: z.string().trim()
}),
body: z.object({
slug: z.string().trim().optional(),
name: z.string().trim().optional(),
position: z.number().optional()
}),
response: {
200: z.object({
message: z.string(),
workspace: z.string(),
environment: ProjectEnvironmentsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const { environment, old } = await server.services.projectEnv.updateEnvironment({
actorId: req.permission.id,
actor: req.permission.type,
projectId: req.params.workspaceId,
id: req.params.id,
...req.body
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: environment.projectId,
event: {
type: EventType.UPDATE_ENVIRONMENT,
metadata: {
oldName: old.name,
oldSlug: old.slug,
oldPos: old.position,
newName: environment.name,
newSlug: environment.slug,
newPos: environment.position
}
}
});
return {
message: "Successfully updated environment",
workspace: req.params.workspaceId,
environment
};
}
});
server.route({
url: "/:workspaceId/environments/:id",
method: "DELETE",
schema: {
params: z.object({
workspaceId: z.string().trim(),
id: z.string().trim()
}),
response: {
200: z.object({
message: z.string(),
workspace: z.string(),
environment: ProjectEnvironmentsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const environment = await server.services.projectEnv.deleteEnvironment({
actorId: req.permission.id,
actor: req.permission.type,
projectId: req.params.workspaceId,
id: req.params.id
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: environment.projectId,
event: {
type: EventType.DELETE_ENVIRONMENT,
metadata: {
slug: environment.slug,
name: environment.name
}
}
});
return {
message: "Successfully deleted environment",
workspace: req.params.workspaceId,
environment
};
}
});
};
@@ -0,0 +1,40 @@
import { z } from "zod";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
export const registerProjectKeyRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/:workspaceId/key",
method: "POST",
schema: {
params: z.object({
workspaceId: z.string().trim()
}),
body: z.object({
key: z.object({
encryptedKey: z.string().trim(),
nonce: z.string().trim(),
userId: z.string().trim()
})
}),
response: {
200: z.object({
message: z.string()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
await server.services.projectKey.uploadProjectKeys({
projectId: req.params.workspaceId,
actor: req.permission.type,
actorId: req.permission.id,
nonce: req.body.key.nonce,
receiverId: req.body.key.userId,
encryptedKey: req.body.key.encryptedKey
});
return { message: "Successfully uploaded key to workspace" };
}
});
};
@@ -0,0 +1,179 @@
import { z } from "zod";
import {
OrgMembershipsSchema,
ProjectMembershipsSchema,
UserEncryptionKeysSchema,
UsersSchema
} from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
export const registerProjectMembershipRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/:workspaceId/memberships",
method: "GET",
schema: {
params: z.object({
workspaceId: z.string().trim()
}),
response: {
200: z.object({
memberships: ProjectMembershipsSchema.merge(
z.object({
user: UsersSchema.pick({
email: true,
firstName: true,
lastName: true,
id: true
}).merge(UserEncryptionKeysSchema.pick({ publicKey: true }))
})
)
.omit({ createdAt: true, updatedAt: true })
.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const memberships = await server.services.projectMembership.getProjectMemberships({
actorId: req.permission.id,
actor: req.permission.type,
projectId: req.params.workspaceId
});
return { memberships };
}
});
server.route({
url: "/:workspaceId/memberships",
method: "POST",
schema: {
params: z.object({
workspaceId: z.string().trim()
}),
body: z.object({
members: z
.object({
orgMembershipId: z.string().trim(),
workspaceEncryptedKey: z.string().trim(),
workspaceEncryptedNonce: z.string().trim()
})
.array()
.min(1)
}),
response: {
200: z.object({
success: z.boolean(),
data: OrgMembershipsSchema.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const data = await server.services.projectMembership.addUsersToProject({
actorId: req.permission.id,
actor: req.permission.type,
projectId: req.params.workspaceId,
members: req.body.members
});
await server.services.auditLog.createAuditLog({
projectId: req.params.workspaceId,
...req.auditLogInfo,
event: {
type: EventType.ADD_BATCH_WORKSPACE_MEMBER,
metadata: data.map(({ userId }) => ({
userId: userId || "",
email: ""
}))
}
});
return { data, success: true };
}
});
server.route({
url: "/:workspaceId/memberships/:membershipId",
method: "PATCH",
schema: {
params: z.object({
workspaceId: z.string().trim(),
membershipId: z.string().trim()
}),
body: z.object({
role: z.string().trim()
}),
response: {
200: z.object({
membership: ProjectMembershipsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const membership = await server.services.projectMembership.updateProjectMembership({
actorId: req.permission.id,
actor: req.permission.type,
projectId: req.params.workspaceId,
membershipId: req.params.membershipId,
role: req.body.role
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.params.workspaceId,
event: {
type: EventType.UPDATE_USER_WORKSPACE_ROLE,
metadata: {
userId: membership.userId,
newRole: req.body.role,
oldRole: membership.role,
email: ""
}
}
});
return { membership };
}
});
server.route({
url: "/:workspaceId/memberships/:membershipId",
method: "DELETE",
schema: {
params: z.object({
workspaceId: z.string().trim(),
membershipId: z.string().trim()
}),
response: {
200: z.object({
membership: ProjectMembershipsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const membership = await server.services.projectMembership.deleteProjectMembership({
actorId: req.permission.id,
actor: req.permission.type,
projectId: req.params.workspaceId,
membershipId: req.params.membershipId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.params.workspaceId,
event: {
type: EventType.REMOVE_WORKSPACE_MEMBER,
metadata: {
userId: membership.userId,
email: ""
}
}
});
return { membership };
}
});
};
@@ -0,0 +1,365 @@
import { z } from "zod";
import {
IntegrationsSchema,
ProjectKeysSchema,
ProjectMembershipsSchema,
ProjectsSchema,
UserEncryptionKeysSchema,
UsersSchema
} from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { integrationAuthPubSchema } from "../sanitizedSchemas";
import { sanitizedServiceTokenSchema } from "../v2/service-token-router";
const projectWithEnv = ProjectsSchema.merge(
z.object({
_id: z.string(),
environments: z.object({ name: z.string(), slug: z.string(), id: z.string() }).array()
})
);
export const registerProjectRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/:workspaceId/keys",
method: "GET",
schema: {
params: z.object({
workspaceId: z.string().trim()
}),
response: {
200: z.object({
publicKeys: z
.object({
publicKey: z.string().optional(),
userId: z.string()
})
.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const publicKeys = await server.services.projectKey.getProjectPublicKeys({
actorId: req.permission.id,
actor: req.permission.type,
projectId: req.params.workspaceId
});
return { publicKeys };
}
});
server.route({
url: "/:workspaceId/users",
method: "GET",
schema: {
params: z.object({
workspaceId: z.string().trim()
}),
response: {
200: z.object({
users: ProjectMembershipsSchema.merge(
z.object({
user: UsersSchema.pick({
email: true,
firstName: true,
lastName: true,
id: true
}).merge(UserEncryptionKeysSchema.pick({ publicKey: true }))
})
)
.omit({ createdAt: true, updatedAt: true })
.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const users = await server.services.projectMembership.getProjectMemberships({
actorId: req.permission.id,
actor: req.permission.type,
projectId: req.params.workspaceId
});
return { users };
}
});
server.route({
url: "/",
method: "GET",
schema: {
response: {
200: z.object({
workspaces: projectWithEnv.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]),
handler: async (req) => {
const workspaces = await server.services.project.getProjects(req.permission.id);
return { workspaces };
}
});
server.route({
url: "/:workspaceId",
method: "GET",
schema: {
params: z.object({
workspaceId: z.string().trim()
}),
response: {
200: z.object({
workspace: projectWithEnv.optional()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const workspace = await server.services.project.getAProject({
actorId: req.permission.id,
actor: req.permission.type,
projectId: req.params.workspaceId
});
return { workspace };
}
});
server.route({
url: "/",
method: "POST",
schema: {
body: z.object({
workspaceName: z.string().trim(),
organizationId: z.string().trim()
}),
response: {
200: z.object({
workspace: projectWithEnv
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const workspace = await server.services.project.createProject({
actorId: req.permission.id,
actor: req.permission.type,
orgId: req.body.organizationId,
workspaceName: req.body.workspaceName
});
return { workspace };
}
});
server.route({
url: "/:workspaceId",
method: "DELETE",
schema: {
params: z.object({
workspaceId: z.string().trim()
}),
response: {
200: z.object({
workspace: ProjectsSchema.optional()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const workspace = await server.services.project.deleteProject({
actorId: req.permission.id,
actor: req.permission.type,
projectId: req.params.workspaceId
});
return { workspace };
}
});
server.route({
url: "/:workspaceId/name",
method: "POST",
schema: {
params: z.object({
workspaceId: z.string().trim()
}),
body: z.object({
name: z.string().trim()
}),
response: {
200: z.object({
message: z.string(),
workspace: ProjectsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const workspace = await server.services.project.updateName({
actorId: req.permission.id,
actor: req.permission.type,
projectId: req.params.workspaceId,
name: req.body.name
});
return {
message: "Successfully changed workspace name",
workspace
};
}
});
server.route({
url: "/:workspaceId/auto-capitalization",
method: "POST",
schema: {
params: z.object({
workspaceId: z.string().trim()
}),
body: z.object({
autoCapitalization: z.boolean()
}),
response: {
200: z.object({
message: z.string(),
workspace: ProjectsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const workspace = await server.services.project.toggleAutoCapitalization({
actorId: req.permission.id,
actor: req.permission.type,
projectId: req.params.workspaceId,
autoCapitalization: req.body.autoCapitalization
});
return {
message: "Successfully changed workspace settings",
workspace
};
}
});
server.route({
url: "/:workspaceId/invite-signup",
method: "POST",
schema: {
params: z.object({
workspaceId: z.string().trim()
}),
body: z.object({
email: z.string().trim()
}),
response: {
200: z.object({
invitee: UsersSchema,
latestKey: ProjectKeysSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { invitee, latestKey } = await server.services.projectMembership.inviteUserToProject({
actorId: req.permission.id,
actor: req.permission.type,
projectId: req.params.workspaceId,
email: req.body.email
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.params.workspaceId,
event: {
type: EventType.ADD_WORKSPACE_MEMBER,
metadata: {
userId: invitee.id,
email: invitee.email
}
}
});
return { invitee, latestKey };
}
});
server.route({
url: "/:workspaceId/integrations",
method: "GET",
schema: {
params: z.object({
workspaceId: z.string().trim()
}),
response: {
200: z.object({
integrations: IntegrationsSchema.merge(
z.object({
environment: z.object({
id: z.string(),
name: z.string(),
slug: z.string()
})
})
).array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const integrations = await server.services.integration.listIntegrationByProject({
actorId: req.permission.id,
actor: req.permission.type,
projectId: req.params.workspaceId
});
return { integrations };
}
});
server.route({
url: "/:workspaceId/authorizations",
method: "GET",
schema: {
params: z.object({
workspaceId: z.string().trim()
}),
response: {
200: z.object({
authorizations: integrationAuthPubSchema.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const authorizations = await server.services.integrationAuth.listIntegrationAuthByProjectId({
actorId: req.permission.id,
actor: req.permission.type,
projectId: req.params.workspaceId
});
return { authorizations };
}
});
server.route({
url: "/:workspaceId/service-token-data",
method: "GET",
schema: {
params: z.object({
workspaceId: z.string().trim()
}),
response: {
200: z.object({
serviceTokenData: sanitizedServiceTokenSchema.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const serviceTokenData = await server.services.serviceToken.getProjectServiceTokens({
actorId: req.permission.id,
actor: req.permission.type,
projectId: req.params.workspaceId
});
return { serviceTokenData };
}
});
};
@@ -0,0 +1,203 @@
import { z } from "zod";
import { SecretFoldersSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
export const registerSecretFolderRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/",
method: "POST",
schema: {
body: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
name: z.string().trim(),
path: z.string().trim().default("/"),
// backward compatiability with cli
directory: z.string().trim().default("/")
}),
response: {
200: z.object({
folder: SecretFoldersSchema
})
}
},
onRequest: verifyAuth([
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN,
AuthMode.IDENTITY_ACCESS_TOKEN
]),
handler: async (req) => {
const path = req.body.path || req.body.directory;
const folder = await server.services.folder.createFolder({
actorId: req.permission.id,
actor: req.permission.type,
...req.body,
projectId: req.body.workspaceId,
path
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.body.workspaceId,
event: {
type: EventType.CREATE_FOLDER,
metadata: {
environment: req.body.environment,
folderId: folder.id,
folderName: folder.name,
folderPath: path
}
}
});
return { folder };
}
});
server.route({
url: "/:folderId",
method: "PATCH",
schema: {
params: z.object({
// old way this was name
folderId: z.string()
}),
body: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
name: z.string().trim(),
path: z.string().trim().default("/"),
// backward compatiability with cli
directory: z.string().trim().default("/")
}),
response: {
200: z.object({
folder: SecretFoldersSchema
})
}
},
onRequest: verifyAuth([
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN,
AuthMode.IDENTITY_ACCESS_TOKEN
]),
handler: async (req) => {
const path = req.body.path || req.body.directory;
const { folder, old } = await server.services.folder.updateFolder({
actorId: req.permission.id,
actor: req.permission.type,
...req.body,
projectId: req.body.workspaceId,
id: req.params.folderId,
path
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.body.workspaceId,
event: {
type: EventType.UPDATE_FOLDER,
metadata: {
environment: req.body.environment,
folderId: folder.id,
folderPath: path,
newFolderName: folder.name,
oldFolderName: old.name
}
}
});
return { folder };
}
});
server.route({
url: "/:folderId",
method: "DELETE",
schema: {
params: z.object({
folderId: z.string()
}),
body: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
path: z.string().trim().default("/"),
// keep this here as cli need directory
directory: z.string().trim().default("/")
}),
response: {
200: z.object({
folder: SecretFoldersSchema
})
}
},
onRequest: verifyAuth([
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN,
AuthMode.IDENTITY_ACCESS_TOKEN
]),
handler: async (req) => {
const path = req.body.path || req.body.directory;
const folder = await server.services.folder.deleteFolder({
actorId: req.permission.id,
actor: req.permission.type,
...req.body,
projectId: req.body.workspaceId,
id: req.params.folderId,
path
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.body.workspaceId,
event: {
type: EventType.DELETE_FOLDER,
metadata: {
environment: req.body.environment,
folderId: folder.id,
folderPath: path,
folderName: folder.name
}
}
});
return { folder };
}
});
server.route({
url: "/",
method: "GET",
schema: {
querystring: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
path: z.string().trim().default("/"),
// backward compatiability with cli
directory: z.string().trim().default("/")
}),
response: {
200: z.object({
folders: SecretFoldersSchema.array()
})
}
},
onRequest: verifyAuth([
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN,
AuthMode.IDENTITY_ACCESS_TOKEN
]),
handler: async (req) => {
const path = req.query.path || req.query.directory;
const folders = await server.services.folder.getFolders({
actorId: req.permission.id,
actor: req.permission.type,
...req.query,
projectId: req.query.workspaceId,
path
});
return { folders };
}
});
};
@@ -0,0 +1,282 @@
import { z } from "zod";
import { SecretImportsSchema, SecretsSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
export const registerSecretImportRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/",
method: "POST",
schema: {
body: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
path: z.string().trim().default("/"),
import: z.object({
environment: z.string().trim(),
path: z.string().trim()
})
}),
response: {
200: z.object({
message: z.string(),
secretImport: SecretImportsSchema.omit({ importEnv: true }).merge(
z.object({
importEnv: z.object({ name: z.string(), slug: z.string(), id: z.string() })
})
)
})
}
},
onRequest: verifyAuth([
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN,
AuthMode.IDENTITY_ACCESS_TOKEN
]),
handler: async (req) => {
const secretImport = await server.services.secretImport.createImport({
actorId: req.permission.id,
actor: req.permission.type,
...req.body,
projectId: req.body.workspaceId,
data: req.body.import
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.body.workspaceId,
event: {
type: EventType.CREATE_SECRET_IMPORT,
metadata: {
secretImportId: secretImport.id,
folderId: secretImport.folderId,
importFromSecretPath: secretImport.importPath,
importFromEnvironment: secretImport.importEnv.slug,
importToEnvironment: req.body.environment,
importToSecretPath: req.body.path
}
}
});
return { message: "Successfully created secret import", secretImport };
}
});
server.route({
url: "/:secretImportId",
method: "PATCH",
schema: {
params: z.object({
secretImportId: z.string().trim()
}),
body: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
path: z.string().trim().default("/"),
import: z.object({
environment: z.string().trim().optional(),
path: z.string().trim().optional(),
position: z.number().optional()
})
}),
response: {
200: z.object({
message: z.string(),
secretImport: SecretImportsSchema.omit({ importEnv: true }).merge(
z.object({
importEnv: z.object({ name: z.string(), slug: z.string(), id: z.string() })
})
)
})
}
},
onRequest: verifyAuth([
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN,
AuthMode.IDENTITY_ACCESS_TOKEN
]),
handler: async (req) => {
const secretImport = await server.services.secretImport.updateImport({
actorId: req.permission.id,
actor: req.permission.type,
id: req.params.secretImportId,
...req.body,
projectId: req.body.workspaceId,
data: req.body.import
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.body.workspaceId,
event: {
type: EventType.UPDATE_SECRET_IMPORT,
metadata: {
secretImportId: secretImport.id,
folderId: secretImport.folderId,
position: secretImport.position,
importToEnvironment: req.body.environment,
importToSecretPath: req.body.path
}
}
});
return { message: "Successfully updated secret import", secretImport };
}
});
server.route({
url: "/:secretImportId",
method: "DELETE",
schema: {
params: z.object({
secretImportId: z.string().trim()
}),
body: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
path: z.string().trim().default("/")
}),
response: {
200: z.object({
message: z.string(),
secretImport: SecretImportsSchema.omit({ importEnv: true }).merge(
z.object({
importEnv: z.object({ name: z.string(), slug: z.string(), id: z.string() })
})
)
})
}
},
onRequest: verifyAuth([
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN,
AuthMode.IDENTITY_ACCESS_TOKEN
]),
handler: async (req) => {
const secretImport = await server.services.secretImport.deleteImport({
actorId: req.permission.id,
actor: req.permission.type,
id: req.params.secretImportId,
...req.body,
projectId: req.body.workspaceId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.body.workspaceId,
event: {
type: EventType.DELETE_SECRET_IMPORT,
metadata: {
secretImportId: secretImport.id,
folderId: secretImport.folderId,
importFromEnvironment: secretImport.importEnv.slug,
importFromSecretPath: secretImport.importPath,
importToEnvironment: req.body.environment,
importToSecretPath: req.body.path
}
}
});
return { message: "Successfully deleted secret import", secretImport };
}
});
server.route({
url: "/",
method: "GET",
schema: {
querystring: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
path: z.string().trim().default("/")
}),
response: {
200: z.object({
message: z.string(),
secretImports: SecretImportsSchema.omit({ importEnv: true })
.merge(
z.object({
importEnv: z.object({ name: z.string(), slug: z.string(), id: z.string() })
})
)
.array()
})
}
},
onRequest: verifyAuth([
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN,
AuthMode.IDENTITY_ACCESS_TOKEN
]),
handler: async (req) => {
const secretImports = await server.services.secretImport.getImports({
actorId: req.permission.id,
actor: req.permission.type,
...req.query,
projectId: req.query.workspaceId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.query.workspaceId,
event: {
type: EventType.GET_SECRET_IMPORTS,
metadata: {
environment: req.query.environment,
folderId: secretImports?.[0]?.folderId,
numberOfImports: secretImports.length
}
}
});
return { message: "Successfully fetched secret imports", secretImports };
}
});
server.route({
url: "/secrets",
method: "GET",
schema: {
querystring: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
path: z.string().trim().default("/")
}),
response: {
200: z.object({
secrets: z
.object({
secretPath: z.string(),
environment: z.string(),
environmentInfo: z.object({
id: z.string(),
name: z.string(),
slug: z.string()
}),
folderId: z.string().optional(),
secrets: SecretsSchema.omit({ secretBlindIndex: true }).array()
})
.array()
})
}
},
onRequest: verifyAuth([
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN,
AuthMode.IDENTITY_ACCESS_TOKEN
]),
handler: async (req) => {
const importedSecrets = await server.services.secretImport.getSecretsFromImports({
actorId: req.permission.id,
actor: req.permission.type,
...req.query,
projectId: req.query.workspaceId
});
return { secrets: importedSecrets };
}
});
};
@@ -0,0 +1,86 @@
import { z } from "zod";
import { SecretTagsSchema } from "@app/db/schemas";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
export const registerSecretTagRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/:projectId/tags",
method: "GET",
schema: {
params: z.object({
projectId: z.string().trim()
}),
response: {
200: z.object({
workspaceTags: SecretTagsSchema.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const workspaceTags = await server.services.secretTag.getProjectTags({
actor: req.permission.type,
actorId: req.permission.id,
projectId: req.params.projectId
});
return { workspaceTags };
}
});
server.route({
url: "/:projectId/tags",
method: "POST",
schema: {
params: z.object({
projectId: z.string().trim()
}),
body: z.object({
name: z.string().trim(),
slug: z.string().trim(),
color: z.string()
}),
response: {
200: z.object({
workspaceTag: SecretTagsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const workspaceTag = await server.services.secretTag.createTag({
actor: req.permission.type,
actorId: req.permission.id,
projectId: req.params.projectId,
...req.body
});
return { workspaceTag };
}
});
server.route({
url: "/:projectId/tags/:tagId",
method: "DELETE",
schema: {
params: z.object({
projectId: z.string().trim(),
tagId: z.string().trim()
}),
response: {
200: z.object({
workspaceTag: SecretTagsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const workspaceTag = await server.services.secretTag.deleteTag({
actor: req.permission.type,
actorId: req.permission.id,
id: req.params.tagId
});
return { workspaceTag };
}
});
};
+274
View File
@@ -0,0 +1,274 @@
import { Authenticator } from "@fastify/passport";
import fastifySession from "@fastify/session";
import { Strategy as GitHubStrategy } from "passport-github";
import { Strategy as GitLabStrategy } from "passport-gitlab2";
import { Strategy as GoogleStrategy } from "passport-google-oauth20";
import { z } from "zod";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError } from "@app/lib/errors";
import { logger } from "@app/lib/logger";
import { fetchGithubEmails } from "@app/lib/requests/github";
import { AuthMethod } from "@app/services/auth/auth-type";
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
export const registerSsoRouter = async (server: FastifyZodProvider) => {
const appCfg = getConfig();
const passport = new Authenticator({ key: "sso", userProperty: "passportUser" });
await server.register(fastifySession, { secret: appCfg.COOKIE_SECRET_SIGN_KEY });
await server.register(passport.initialize());
await server.register(passport.secureSession());
// passport oauth strategy for Google
const isGoogleOauthActive = Boolean(
appCfg.CLIENT_ID_GOOGLE_LOGIN && appCfg.CLIENT_SECRET_GOOGLE_LOGIN
);
if (isGoogleOauthActive) {
passport.use(
new GoogleStrategy(
{
passReqToCallback: true,
clientID: appCfg.CLIENT_ID_GOOGLE_LOGIN as string,
clientSecret: appCfg.CLIENT_SECRET_GOOGLE_LOGIN as string,
callbackURL: "/api/v1/sso/google",
scope: ["profile", " email"]
},
async (req, _accessToken, _refreshToken, profile, cb) => {
try {
const email = profile?.emails?.[0]?.value;
const serverCfg = getServerCfg();
if (!email)
throw new BadRequestError({
message: "Email not found",
name: "Oauth Google Register"
});
const { isUserCompleted, providerAuthToken } = await server.services.login.oauth2Login({
email,
firstName: profile?.name?.givenName || "",
lastName: profile?.name?.familyName || "",
authMethod: AuthMethod.GOOGLE,
callbackPort: req.query.state as string,
isSignupAllowed: Boolean(serverCfg.allowSignUp)
});
cb(null, { isUserCompleted, providerAuthToken });
} catch (error) {
logger.error(error);
cb(null, false);
}
}
)
);
}
// Passport strategy for Github
const isGithubOauthActive = Boolean(
appCfg.CLIENT_SECRET_GITHUB_LOGIN && appCfg.CLIENT_ID_GITHUB_LOGIN
);
if (isGithubOauthActive) {
passport.use(
new GitHubStrategy(
{
passReqToCallback: true,
clientID: appCfg.CLIENT_ID_GITHUB_LOGIN as string,
clientSecret: appCfg.CLIENT_SECRET_GITHUB_LOGIN as string,
callbackURL: "/api/v1/sso/github",
scope: ["user:email"]
},
async (req, accessToken, _refreshToken, profile, cb) => {
try {
const ghEmails = await fetchGithubEmails(accessToken);
const { email } = ghEmails.filter((gitHubEmail) => gitHubEmail.primary)[0];
const serverCfg = getServerCfg();
const { isUserCompleted, providerAuthToken } = await server.services.login.oauth2Login({
email,
firstName: profile.displayName,
lastName: "",
authMethod: AuthMethod.GITHUB,
callbackPort: req.query.state as string,
isSignupAllowed: Boolean(serverCfg.allowSignUp)
});
return cb(null, { isUserCompleted, providerAuthToken });
} catch (error) {
logger.error(error);
cb(null, false);
}
}
)
);
}
// passport strategy for gitlab
const isGitlabOauthActive = Boolean(
appCfg.CLIENT_ID_GITLAB_LOGIN &&
appCfg.CLIENT_SECRET_GITLAB_LOGIN &&
appCfg.CLIENT_GITLAB_LOGIN_URL
);
if (isGitlabOauthActive) {
passport.use(
new GitLabStrategy(
{
passReqToCallback: true,
clientID: appCfg.CLIENT_ID_GITLAB_LOGIN,
clientSecret: appCfg.CLIENT_SECRET_GITLAB_LOGIN,
callbackURL: "/api/v1/sso/gitlab",
baseURL: appCfg.CLIENT_GITLAB_LOGIN_URL
},
async (req: any, _accessToken: string, _refreshToken: string, profile: any, cb: any) => {
try {
const email = profile.emails[0].value;
const serverCfg = getServerCfg();
const { isUserCompleted, providerAuthToken } = await server.services.login.oauth2Login({
email,
firstName: profile.displayName,
lastName: "",
authMethod: AuthMethod.GITLAB,
callbackPort: req.query.state as string,
isSignupAllowed: Boolean(serverCfg.allowSignUp)
});
return cb(null, { isUserCompleted, providerAuthToken });
} catch (error) {
logger.error(error);
cb(null, false);
}
}
)
);
}
server.route({
url: "/redirect/google",
method: "GET",
schema: {
querystring: z.object({
callback_port: z.string().optional()
})
},
preValidation: (req, res) =>
(
passport.authenticate("google", {
scope: ["profile", "email"],
session: false,
state: req.query.callback_port,
authInfo: false
// this is due to zod type difference
}) as any
)(req, res),
handler: () => {}
});
server.route({
url: "/google",
method: "GET",
preValidation: passport.authenticate("google", {
session: false,
failureRedirect: "/login/provider/error",
authInfo: false
// this is due to zod type difference
}) as any,
handler: (req, res) => {
if (req.passportUser.isUserCompleted) {
return res.redirect(
`${appCfg.SITE_URL}/login/sso?token=${encodeURIComponent(
req.passportUser.providerAuthToken
)}`
);
}
return res.redirect(
`${appCfg.SITE_URL}/signup/sso?token=${encodeURIComponent(
req.passportUser.providerAuthToken
)}`
);
}
});
server.route({
url: "/redirect/github",
method: "GET",
schema: {
querystring: z.object({
callback_port: z.string().optional()
})
},
preValidation: (req, res) =>
(
passport.authenticate("github", {
session: false,
state: req.query.callback_port,
authInfo: false
// this is due to zod type difference
}) as any
)(req, res),
handler: () => {}
});
server.route({
url: "/github",
method: "GET",
preValidation: passport.authenticate("github", {
session: false,
failureRedirect: "/login/provider/error",
authInfo: false
// this is due to zod type difference
}) as any,
handler: (req, res) => {
if (req.passportUser.isUserCompleted) {
return res.redirect(
`${appCfg.SITE_URL}/login/sso?token=${encodeURIComponent(
req.passportUser.providerAuthToken
)}`
);
}
return res.redirect(
`${appCfg.SITE_URL}/signup/sso?token=${encodeURIComponent(
req.passportUser.providerAuthToken
)}`
);
}
});
server.route({
url: "/redirect/gitlab",
method: "GET",
schema: {
querystring: z.object({
callback_port: z.string().optional()
})
},
preValidation: (req, res) =>
(
passport.authenticate("gitlab", {
session: false,
state: req.query.callback_port,
authInfo: false
// this is due to zod type difference
}) as any
)(req, res),
handler: () => {}
});
server.route({
url: "/gitlab",
method: "GET",
preValidation: passport.authenticate("gitlab", {
session: false,
failureRedirect: "/login/provider/error",
authInfo: false
// this is due to zod type difference
}) as any,
handler: (req, res) => {
if (req.passportUser.isUserCompleted) {
return res.redirect(
`${appCfg.SITE_URL}/login/sso?token=${encodeURIComponent(
req.passportUser.providerAuthToken
)}`
);
}
return res.redirect(
`${appCfg.SITE_URL}/signup/sso?token=${encodeURIComponent(
req.passportUser.providerAuthToken
)}`
);
}
});
};
@@ -0,0 +1,54 @@
import { z } from "zod";
import { UserActionsSchema } from "@app/db/schemas";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
export const registerUserActionRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/",
method: "POST",
schema: {
body: z.object({
action: z.string().trim()
}),
response: {
200: z.object({
message: z.string(),
userAction: UserActionsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const userAction = await server.services.user.createUserAction(
req.permission.id,
req.body.action
);
return { userAction, message: "Successfully recorded user action" };
}
});
server.route({
url: "/",
method: "GET",
schema: {
querystring: z.object({
action: z.string().trim()
}),
response: {
200: z.object({
userAction: UserActionsSchema.optional().nullable()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const userAction = await server.services.user.getUserAction(
req.permission.id,
req.query.action
);
return { userAction };
}
});
};
@@ -0,0 +1,24 @@
import { z } from "zod";
import { UserEncryptionKeysSchema, UsersSchema } from "@app/db/schemas";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
export const registerUserRouter = async (server: FastifyZodProvider) => {
server.route({
method: "GET",
url: "/",
schema: {
response: {
200: z.object({
user: UsersSchema.merge(UserEncryptionKeysSchema.omit({ verifier: true }))
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const user = await server.services.user.getMe(req.permission.id);
return { user };
}
});
};
@@ -0,0 +1,204 @@
import { z } from "zod";
import { WebhooksSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
export const sanitizedWebhookSchema = WebhooksSchema.omit({
encryptedSecretKey: true,
iv: true,
tag: true,
algorithm: true,
keyEncoding: true
}).merge(
z.object({
projectId: z.string(),
environment: z.object({
id: z.string(),
name: z.string(),
slug: z.string()
})
})
);
export const registerWebhookRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
body: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
webhookUrl: z.string().url().trim(),
webhookSecretKey: z.string().trim().optional(),
secretPath: z.string().trim().default("/")
}),
response: {
200: z.object({
message: z.string(),
webhook: sanitizedWebhookSchema
})
}
},
handler: async (req) => {
const webhook = await server.services.webhook.createWebhook({
actor: req.permission.type,
actorId: req.permission.id,
projectId: req.body.workspaceId,
...req.body
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.body.workspaceId,
event: {
type: EventType.CREATE_WEBHOOK,
metadata: {
environment: webhook.environment.slug,
webhookId: webhook.id,
isDisabled: webhook.isDisabled,
secretPath: webhook.secretPath,
webhookUrl: webhook.url
}
}
});
return { message: "Successfully created webhook", webhook };
}
});
server.route({
method: "PATCH",
url: "/:webhookId",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
webhookId: z.string().trim()
}),
body: z.object({
isDisabled: z.boolean().default(false)
}),
response: {
200: z.object({
message: z.string(),
webhook: sanitizedWebhookSchema
})
}
},
handler: async (req) => {
const webhook = await server.services.webhook.updateWebhook({
actor: req.permission.type,
actorId: req.permission.id,
id: req.params.webhookId,
isDisabled: req.body.isDisabled
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: webhook.projectId,
event: {
type: EventType.UPDATE_WEBHOOK_STATUS,
metadata: {
environment: webhook.environment.slug,
webhookId: webhook.id,
isDisabled: webhook.isDisabled,
secretPath: webhook.secretPath,
webhookUrl: webhook.url
}
}
});
return { message: "Successfully updated webhook", webhook };
}
});
server.route({
method: "DELETE",
url: "/:webhookId",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
webhookId: z.string().trim()
})
},
handler: async (req) => {
const webhook = await server.services.webhook.deleteWebhook({
actor: req.permission.type,
actorId: req.permission.id,
id: req.params.webhookId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: webhook.projectId,
event: {
type: EventType.DELETE_WEBHOOK,
metadata: {
environment: webhook.environment.slug,
webhookId: webhook.id,
isDisabled: webhook.isDisabled,
secretPath: webhook.secretPath,
webhookUrl: webhook.url
}
}
});
return { message: "Successfully deleted webhook", webhook };
}
});
server.route({
method: "POST",
url: "/:webhookId/test",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
webhookId: z.string().trim()
}),
response: {
200: z.object({
message: z.string(),
webhook: sanitizedWebhookSchema
})
}
},
handler: async (req) => {
const webhook = await server.services.webhook.testWebhook({
actor: req.permission.type,
actorId: req.permission.id,
id: req.params.webhookId
});
return { message: "Successfully tested webhook", webhook };
}
});
server.route({
method: "GET",
url: "/",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
querystring: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim().optional(),
secretPath: z.string().trim().optional()
}),
response: {
200: z.object({
message: z.string(),
webhooks: sanitizedWebhookSchema.array()
})
}
},
handler: async (req) => {
const webhooks = await server.services.webhook.listWebhooks({
actor: req.permission.type,
actorId: req.permission.id,
...req.query,
projectId: req.query.workspaceId
});
return { message: "Successfully fetched webhook", webhooks };
}
});
};