feat: changed line length to 120

This commit is contained in:
Akhil Mohan
2024-01-30 22:40:10 +05:30
parent 5855cc660a
commit bcbb49ed1b
142 changed files with 1183 additions and 3706 deletions
@@ -45,8 +45,7 @@ export const apiKeyServiceFactory = ({ apiKeyDAL, userDAL }: TApiKeyServiceFacto
const deleteApiKey = async (userId: string, apiKeyId: string) => {
const [apiKeyData] = await apiKeyDAL.delete({ id: apiKeyId, userId });
if (!apiKeyData)
throw new BadRequestError({ message: "Failed to find api key", name: "delete api key" });
if (!apiKeyData) throw new BadRequestError({ message: "Failed to find api key", name: "delete api key" });
return formatApiKey(apiKeyData);
};
@@ -12,9 +12,7 @@ export type TTokenDALFactory = ReturnType<typeof tokenDALFactory>;
export const tokenDALFactory = (db: TDbClient) => {
const authOrm = ormify(db, TableName.AuthTokens);
const findOneTokenSession = async (
filter: Partial<TAuthTokenSessions>
): Promise<TAuthTokenSessions | undefined> => {
const findOneTokenSession = async (filter: Partial<TAuthTokenSessions>): Promise<TAuthTokenSessions | undefined> => {
try {
const doc = await db(TableName.AuthTokenSession).where(filter).first();
return doc;
@@ -29,20 +27,14 @@ export const tokenDALFactory = (db: TDbClient) => {
orgId
}: TDeleteTokenForUserDALDTO): Promise<TAuthTokens[] | undefined> => {
try {
const doc = await db(TableName.AuthTokens)
.where({ userId, type, orgId })
.delete()
.returning("*");
const doc = await db(TableName.AuthTokens).where({ userId, type, orgId }).delete().returning("*");
return doc;
} catch (error) {
throw new DatabaseError({ error, name: "DeleteTokenForUser" });
}
};
const decrementTriesField = async ({
userId,
type
}: TDeleteTokenForUserDALDTO): Promise<void> => {
const decrementTriesField = async ({ userId, type }: TDeleteTokenForUserDALDTO): Promise<void> => {
try {
await db(TableName.AuthTokens).where({ userId, type }).decrement("triesLeft", 1);
} catch (error) {
@@ -99,10 +91,7 @@ export const tokenDALFactory = (db: TDbClient) => {
const deleteTokenSession = async (filter: Partial<TAuthTokenSessions>, tx?: Knex) => {
try {
const sessions = await (tx || db)(TableName.AuthTokenSession)
.where(filter)
.del()
.returning("*");
const sessions = await (tx || db)(TableName.AuthTokenSession).where(filter).del().returning("*");
return sessions;
} catch (error) {
throw new DatabaseError({ name: "Delete token session", error });
@@ -9,12 +9,7 @@ import { UnauthorizedError } from "@app/lib/errors";
import { AuthModeJwtTokenPayload } from "../auth/auth-type";
import { TUserDALFactory } from "../user/user-dal";
import { TTokenDALFactory } from "./auth-token-dal";
import {
TCreateTokenForUserDTO,
TIssueAuthTokenDTO,
TokenType,
TValidateTokenForUserDTO
} from "./auth-token-types";
import { TCreateTokenForUserDTO, TIssueAuthTokenDTO, TokenType, TValidateTokenForUserDTO } from "./auth-token-types";
type TAuthTokenServiceFactoryDep = {
tokenDAL: TTokenDALFactory;
@@ -125,14 +120,10 @@ export const tokenServiceFactory = ({ tokenDAL, userDAL }: TAuthTokenServiceFact
return session;
};
const clearTokenSessionById = async (
userId: string,
sessionId: string
): Promise<TAuthTokenSessions | undefined> =>
const clearTokenSessionById = async (userId: string, sessionId: string): Promise<TAuthTokenSessions | undefined> =>
tokenDAL.incrementTokenSessionVersion(userId, sessionId);
const getUserTokenSessionById = async (id: string, userId: string) =>
tokenDAL.findOneTokenSession({ id, userId });
const getUserTokenSessionById = async (id: string, userId: string) => tokenDAL.findOneTokenSession({ id, userId });
const getTokenSessionByUser = async (userId: string) => tokenDAL.findTokenSessions({ userId });
@@ -145,8 +136,7 @@ export const tokenServiceFactory = ({ tokenDAL, userDAL }: TAuthTokenServiceFact
userId: token.userId
});
if (!session) throw new UnauthorizedError({ name: "Session not found" });
if (token.accessVersion !== session.accessVersion)
throw new UnauthorizedError({ name: "Stale session" });
if (token.accessVersion !== session.accessVersion) throw new UnauthorizedError({ name: "Stale session" });
const user = await userDAL.findById(session.userId);
if (!user || !user.isAccepted) throw new UnauthorizedError({ name: "Token user not found" });
+4 -17
View File
@@ -3,19 +3,12 @@ import jwt from "jsonwebtoken";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
import {
AuthModeProviderJwtTokenPayload,
AuthModeProviderSignUpTokenPayload,
AuthTokenType
} from "./auth-type";
import { AuthModeProviderJwtTokenPayload, AuthModeProviderSignUpTokenPayload, AuthTokenType } from "./auth-type";
export const validateProviderAuthToken = (providerToken: string, email: string) => {
if (!providerToken) throw new UnauthorizedError();
const appCfg = getConfig();
const decodedToken = jwt.verify(
providerToken,
appCfg.AUTH_SECRET
) as AuthModeProviderJwtTokenPayload;
const decodedToken = jwt.verify(providerToken, appCfg.AUTH_SECRET) as AuthModeProviderJwtTokenPayload;
if (decodedToken.authTokenType !== AuthTokenType.PROVIDER_TOKEN) throw new UnauthorizedError();
if (decodedToken.email !== email) throw new Error("Invalid auth credentials");
@@ -23,10 +16,7 @@ export const validateProviderAuthToken = (providerToken: string, email: string)
export const validateSignUpAuthorization = (token: string, userId: string, validate = true) => {
const appCfg = getConfig();
const [AUTH_TOKEN_TYPE, AUTH_TOKEN_VALUE] = <[string, string]>token?.split(" ", 2) ?? [
null,
null
];
const [AUTH_TOKEN_TYPE, AUTH_TOKEN_VALUE] = <[string, string]>token?.split(" ", 2) ?? [null, null];
if (AUTH_TOKEN_TYPE === null) {
throw new BadRequestError({ message: "Missing Authorization Header in the request header." });
}
@@ -41,10 +31,7 @@ export const validateSignUpAuthorization = (token: string, userId: string, valid
});
}
const decodedToken = jwt.verify(
AUTH_TOKEN_VALUE,
appCfg.AUTH_SECRET
) as AuthModeProviderSignUpTokenPayload;
const decodedToken = jwt.verify(AUTH_TOKEN_VALUE, appCfg.AUTH_SECRET) as AuthModeProviderSignUpTokenPayload;
if (!validate) return decodedToken;
if (decodedToken.authTokenType !== AuthTokenType.SIGNUP_TOKEN) throw new UnauthorizedError();
@@ -25,11 +25,7 @@ type TAuthLoginServiceFactoryDep = {
};
export type TAuthLoginFactory = ReturnType<typeof authLoginServiceFactory>;
export const authLoginServiceFactory = ({
userDAL,
tokenService,
smtpService
}: TAuthLoginServiceFactoryDep) => {
export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }: TAuthLoginServiceFactoryDep) => {
/*
* Private
* Not exported. This is to update user device list
@@ -37,9 +33,7 @@ export const authLoginServiceFactory = ({
*/
const updateUserDeviceSession = async (user: TUsers, ip: string, userAgent: string) => {
const devices = await UserDeviceSchema.parseAsync(user.devices || []);
const isDeviceSeen = devices.some(
(device) => device.ip === ip && device.userAgent === userAgent
);
const isDeviceSeen = devices.some((device) => device.ip === ip && device.userAgent === userAgent);
if (!isDeviceSeen) {
const newDeviceList = devices.concat([{ ip, userAgent }]);
@@ -159,8 +153,7 @@ export const authLoginServiceFactory = ({
validateProviderAuthToken(providerAuthToken as string, email);
}
if (!userEnc.serverPrivateKey || !userEnc.clientPublicKey)
throw new Error("Failed to authenticate. Try again?");
if (!userEnc.serverPrivateKey || !userEnc.clientPublicKey) throw new Error("Failed to authenticate. Try again?");
const isValidClientProof = await srpCheckClientProof(
userEnc.salt,
userEnc.verifier,
@@ -176,11 +169,9 @@ export const authLoginServiceFactory = ({
});
// send multi factor auth token if they it enabled
if (userEnc.isMfaEnabled) {
const mfaToken = jwt.sign(
{ authTokenType: AuthTokenType.MFA_TOKEN, userId: userEnc.userId },
cfg.AUTH_SECRET,
{ expiresIn: cfg.JWT_MFA_LIFETIME }
);
const mfaToken = jwt.sign({ authTokenType: AuthTokenType.MFA_TOKEN, userId: userEnc.userId }, cfg.AUTH_SECRET, {
expiresIn: cfg.JWT_MFA_LIFETIME
});
await sendUserMfaCode(userEnc.userId, userEnc.email);
return { isMfaEnabled: true, token: mfaToken } as const;
@@ -230,8 +221,7 @@ export const authLoginServiceFactory = ({
let user = await userDAL.findUserByEmail(email);
const appCfg = getConfig();
const isOauthSignUpDisabled = !isSignupAllowed && !user;
if (isOauthSignUpDisabled)
throw new BadRequestError({ message: "User signup disabled", name: "Oauth 2 login" });
if (isOauthSignUpDisabled) throw new BadRequestError({ message: "User signup disabled", name: "Oauth 2 login" });
if (!user) {
user = await userDAL.create({ email, firstName, lastName, authMethods: [authMethod] });
@@ -9,11 +9,7 @@ import { TokenType } from "../auth-token/auth-token-types";
import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
import { TUserDALFactory } from "../user/user-dal";
import { TAuthDALFactory } from "./auth-dal";
import {
TChangePasswordDTO,
TCreateBackupPrivateKeyDTO,
TResetPasswordViaBackupKeyDTO
} from "./auth-password-type";
import { TChangePasswordDTO, TCreateBackupPrivateKeyDTO, TResetPasswordViaBackupKeyDTO } from "./auth-password-type";
import { AuthTokenType } from "./auth-type";
type TAuthPasswordServiceFactoryDep = {
@@ -70,8 +66,7 @@ export const authPaswordServiceFactory = ({
serverPrivateKey: null,
clientPublicKey: null
});
if (!userEnc.serverPrivateKey || !userEnc.clientPublicKey)
throw new Error("Failed to authenticate. Try again?");
if (!userEnc.serverPrivateKey || !userEnc.clientPublicKey) throw new Error("Failed to authenticate. Try again?");
const isValidClientProof = await srpCheckClientProof(
userEnc.salt,
userEnc.verifier,
@@ -200,8 +195,7 @@ export const authPaswordServiceFactory = ({
throw new Error("Failed to find user");
}
if (!userEnc.clientPublicKey || !userEnc.serverPrivateKey)
throw new Error("failed to create backup key");
if (!userEnc.clientPublicKey || !userEnc.serverPrivateKey) throw new Error("failed to create backup key");
const isValidClientProff = await srpCheckClientProof(
userEnc.salt,
userEnc.verifier,
@@ -148,9 +148,7 @@ export const authSignupServiceFactory = ({
});
const hasSamlEnabled = user?.authMethods?.some((authMethod) =>
[AuthMethod.OKTA_SAML, AuthMethod.AZURE_SAML, AuthMethod.JUMPCLOUD_SAML].includes(
authMethod as AuthMethod
)
[AuthMethod.OKTA_SAML, AuthMethod.AZURE_SAML, AuthMethod.JUMPCLOUD_SAML].includes(authMethod as AuthMethod)
);
if (!hasSamlEnabled) {
@@ -162,9 +160,7 @@ export const authSignupServiceFactory = ({
{ userId: user.id, status: OrgMembershipStatus.Accepted }
);
const uniqueOrgId = [...new Set(updatedMembersips.map(({ orgId }) => orgId))];
await Promise.allSettled(
uniqueOrgId.map((orgId) => licenseService.updateSubscriptionOrgMemberCount(orgId))
);
await Promise.allSettled(uniqueOrgId.map((orgId) => licenseService.updateSubscriptionOrgMemberCount(orgId)));
const tokenSession = await tokenService.getUserTokenSession({
userAgent,
@@ -259,9 +255,7 @@ export const authSignupServiceFactory = ({
tx
);
const uniqueOrgId = [...new Set(updatedMembersips.map(({ orgId }) => orgId))];
await Promise.allSettled(
uniqueOrgId.map((orgId) => licenseService.updateSubscriptionOrgMemberCount(orgId))
);
await Promise.allSettled(uniqueOrgId.map((orgId) => licenseService.updateSubscriptionOrgMemberCount(orgId)));
return { info: us, key: userEncKey };
});
@@ -14,11 +14,7 @@ export const identityAccessTokenDALFactory = (db: TDbClient) => {
try {
const doc = await (tx || db)(TableName.IdentityAccessToken)
.where(filter)
.join(
TableName.Identity,
`${TableName.Identity}.id`,
`${TableName.IdentityAccessToken}.identityId`
)
.join(TableName.Identity, `${TableName.Identity}.id`, `${TableName.IdentityAccessToken}.identityId`)
.leftJoin(
TableName.IdentityUaClientSecret,
`${TableName.IdentityAccessToken}.identityUAClientSecretId`,
@@ -7,18 +7,13 @@ import { checkIPAgainstBlocklist, TIp } from "@app/lib/ip";
import { AuthTokenType } from "../auth/auth-type";
import { TIdentityAccessTokenDALFactory } from "./identity-access-token-dal";
import {
TIdentityAccessTokenJwtPayload,
TRenewAccessTokenDTO
} from "./identity-access-token-types";
import { TIdentityAccessTokenJwtPayload, TRenewAccessTokenDTO } from "./identity-access-token-types";
type TIdentityAccessTokenServiceFactoryDep = {
identityAccessTokenDAL: TIdentityAccessTokenDALFactory;
};
export type TIdentityAccessTokenServiceFactory = ReturnType<
typeof identityAccessTokenServiceFactory
>;
export type TIdentityAccessTokenServiceFactory = ReturnType<typeof identityAccessTokenServiceFactory>;
export const identityAccessTokenServiceFactory = ({
identityAccessTokenDAL
@@ -33,11 +28,7 @@ export const identityAccessTokenServiceFactory = ({
createdAt: accessTokenCreatedAt
} = identityAccessToken;
if (
accessTokenNumUsesLimit > 0 &&
accessTokenNumUses > 0 &&
accessTokenNumUses >= accessTokenNumUsesLimit
) {
if (accessTokenNumUsesLimit > 0 && accessTokenNumUses > 0 && accessTokenNumUses >= accessTokenNumUsesLimit) {
throw new BadRequestError({
message: "Unable to renew because access token number of uses limit reached"
});
@@ -95,8 +86,7 @@ export const identityAccessTokenServiceFactory = ({
const decodedToken = jwt.verify(accessToken, appCfg.AUTH_SECRET) as JwtPayload & {
identityAccessTokenId: string;
};
if (decodedToken.authTokenType !== AuthTokenType.IDENTITY_ACCESS_TOKEN)
throw new UnauthorizedError();
if (decodedToken.authTokenType !== AuthTokenType.IDENTITY_ACCESS_TOKEN) throw new UnauthorizedError();
const identityAccessToken = await identityAccessTokenDAL.findOne({
[`${TableName.IdentityAccessToken}.id` as "id"]: decodedToken.identityAccessTokenId,
@@ -106,20 +96,14 @@ export const identityAccessTokenServiceFactory = ({
validateAccessTokenExp(identityAccessToken);
const updatedIdentityAccessToken = await identityAccessTokenDAL.updateById(
identityAccessToken.id,
{
accessTokenLastRenewedAt: new Date()
}
);
const updatedIdentityAccessToken = await identityAccessTokenDAL.updateById(identityAccessToken.id, {
accessTokenLastRenewedAt: new Date()
});
return { accessToken, identityAccessToken: updatedIdentityAccessToken };
};
const fnValidateIdentityAccessToken = async (
token: TIdentityAccessTokenJwtPayload,
ipAddress?: string
) => {
const fnValidateIdentityAccessToken = async (token: TIdentityAccessTokenJwtPayload, ipAddress?: string) => {
const identityAccessToken = await identityAccessTokenDAL.findOne({
[`${TableName.IdentityAccessToken}.id` as "id"]: token.identityAccessTokenId,
isAccessTokenRevoked: false
@@ -14,11 +14,7 @@ export const identityProjectDALFactory = (db: TDbClient) => {
try {
const docs = await (tx || db)(TableName.IdentityProjectMembership)
.where(`${TableName.IdentityProjectMembership}.projectId`, projectId)
.join(
TableName.Identity,
`${TableName.IdentityProjectMembership}.identityId`,
`${TableName.Identity}.id`
)
.join(TableName.Identity, `${TableName.IdentityProjectMembership}.identityId`, `${TableName.Identity}.id`)
.leftJoin(
TableName.ProjectRoles,
`${TableName.IdentityProjectMembership}.roleId`,
@@ -2,10 +2,7 @@ import { ForbiddenError } from "@casl/ability";
import { ProjectMembershipRole, TProjectRoles } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import {
ProjectPermissionActions,
ProjectPermissionSub
} from "@app/ee/services/permission/project-permission";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { isAtLeastAsPrivileged } from "@app/lib/casl";
import { BadRequestError, ForbiddenRequestError } from "@app/lib/errors";
@@ -24,10 +21,7 @@ type TIdentityProjectServiceFactoryDep = {
identityProjectDAL: TIdentityProjectDALFactory;
projectDAL: Pick<TProjectDALFactory, "findById">;
identityOrgMembershipDAL: Pick<TIdentityOrgDALFactory, "findOne">;
permissionService: Pick<
TPermissionServiceFactory,
"getProjectPermission" | "getProjectPermissionByRole"
>;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getProjectPermissionByRole">;
};
export type TIdentityProjectServiceFactory = ReturnType<typeof identityProjectServiceFactory>;
@@ -38,18 +32,9 @@ export const identityProjectServiceFactory = ({
identityOrgMembershipDAL,
projectDAL
}: TIdentityProjectServiceFactoryDep) => {
const createProjectIdentity = async ({
identityId,
actor,
actorId,
projectId,
role
}: TCreateProjectIdentityDTO) => {
const createProjectIdentity = async ({ identityId, actor, actorId, projectId, role }: TCreateProjectIdentityDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.Identity
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Identity);
const existingIdentity = await identityProjectDAL.findOne({ identityId, projectId });
if (existingIdentity)
@@ -67,8 +52,10 @@ export const identityProjectServiceFactory = ({
message: `Failed to find identity with id ${identityId}`
});
const { permission: rolePermission, role: customRole } =
await permissionService.getProjectPermissionByRole(role, project.id);
const { permission: rolePermission, role: customRole } = await permissionService.getProjectPermissionByRole(
role,
project.id
);
const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission);
if (!hasPriviledge)
throw new ForbiddenRequestError({
@@ -85,18 +72,9 @@ export const identityProjectServiceFactory = ({
return projectIdentity;
};
const updateProjectIdentity = async ({
projectId,
identityId,
role,
actor,
actorId
}: TUpdateProjectIdentityDTO) => {
const updateProjectIdentity = async ({ projectId, identityId, role, actor, actorId }: TUpdateProjectIdentityDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
ProjectPermissionSub.Identity
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
const projectIdentity = await identityProjectDAL.findOne({ identityId, projectId });
if (!projectIdentity)
@@ -115,8 +93,10 @@ export const identityProjectServiceFactory = ({
let customRole: TProjectRoles | undefined;
if (role) {
const { permission: rolePermission, role: customOrgRole } =
await permissionService.getProjectPermissionByRole(role, projectIdentity.projectId);
const { permission: rolePermission, role: customOrgRole } = await permissionService.getProjectPermissionByRole(
role,
projectIdentity.projectId
);
const isCustomRole = Boolean(customOrgRole);
const hasRequiredNewRolePermission = isAtLeastAsPrivileged(permission, rolePermission);
@@ -135,12 +115,7 @@ export const identityProjectServiceFactory = ({
return updatedProjectIdentity;
};
const deleteProjectIdentity = async ({
identityId,
actorId,
actor,
projectId
}: TDeleteProjectIdentityDTO) => {
const deleteProjectIdentity = async ({ identityId, actorId, actor, projectId }: TDeleteProjectIdentityDTO) => {
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
if (!identityProjectMembership)
throw new BadRequestError({ message: `Failed to find identity with id ${identityId}` });
@@ -150,10 +125,7 @@ export const identityProjectServiceFactory = ({
actorId,
identityProjectMembership.projectId
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.Identity
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Identity);
const { permission: identityRolePermission } = await permissionService.getProjectPermission(
ActorType.IDENTITY,
identityId,
@@ -169,10 +141,7 @@ export const identityProjectServiceFactory = ({
const listProjectIdentities = async ({ projectId, actor, actorId }: TListProjectIdentityDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Identity
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Identity);
const identityMemberhips = await identityProjectDAL.findByProjectId(projectId);
return identityMemberhips;
@@ -6,10 +6,7 @@ import jwt from "jsonwebtoken";
import { IdentityAuthMethod } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import {
OrgPermissionActions,
OrgPermissionSubjects
} from "@app/ee/services/permission/org-permission";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { isAtLeastAsPrivileged } from "@app/lib/casl";
import { getConfig } from "@app/lib/config/env";
@@ -71,8 +68,7 @@ export const identityUaServiceFactory = ({
);
if (!validClientSecretInfo) throw new UnauthorizedError();
const { clientSecretTTL, clientSecretNumUses, clientSecretNumUsesLimit } =
validClientSecretInfo;
const { clientSecretTTL, clientSecretNumUses, clientSecretNumUsesLimit } = validClientSecretInfo;
if (clientSecretTTL > 0) {
const clientSecretCreated = new Date(validClientSecretInfo.createdAt);
const ttlInMilliseconds = clientSecretTTL * 1000;
@@ -97,16 +93,12 @@ export const identityUaServiceFactory = ({
isClientSecretRevoked: true
});
throw new UnauthorizedError({
message:
"Failed to authenticate identity credentials due to client secret number of uses limit reached"
message: "Failed to authenticate identity credentials due to client secret number of uses limit reached"
});
}
const identityAccessToken = await identityUaDAL.transaction(async (tx) => {
const uaClientSecretDoc = await identityUaClientSecretDAL.incrementUsage(
validClientSecretInfo.id,
tx
);
const uaClientSecretDoc = await identityUaClientSecretDAL.incrementUsage(validClientSecretInfo.id, tx);
const newToken = await identityAccessTokenDAL.create(
{
identityId: identityUa.identityId,
@@ -132,10 +124,7 @@ export const identityUaServiceFactory = ({
} as TIdentityAccessTokenJwtPayload,
appCfg.AUTH_SECRET,
{
expiresIn:
identityAccessToken.accessTokenMaxTTL === 0
? undefined
: identityAccessToken.accessTokenMaxTTL
expiresIn: identityAccessToken.accessTokenMaxTTL === 0 ? undefined : identityAccessToken.accessTokenMaxTTL
}
);
return { accessToken, identityUa, validClientSecretInfo, identityAccessToken };
@@ -162,35 +151,26 @@ export const identityUaServiceFactory = ({
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
}
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
identityMembershipOrg.orgId
);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Create,
OrgPermissionSubjects.Identity
);
const { permission } = await permissionService.getOrgPermission(actor, actorId, identityMembershipOrg.orgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.orgId);
const reformattedClientSecretTrustedIps = clientSecretTrustedIps.map(
(clientSecretTrustedIp) => {
if (
!plan.ipAllowlisting &&
clientSecretTrustedIp.ipAddress !== "0.0.0.0/0" &&
clientSecretTrustedIp.ipAddress !== "::/0"
)
throw new BadRequestError({
message:
"Failed to add IP access range to service token due to plan restriction. Upgrade plan to add IP access range."
});
if (!isValidIpOrCidr(clientSecretTrustedIp.ipAddress))
throw new BadRequestError({
message: "The IP is not a valid IPv4, IPv6, or CIDR block"
});
return extractIPDetails(clientSecretTrustedIp.ipAddress);
}
);
const reformattedClientSecretTrustedIps = clientSecretTrustedIps.map((clientSecretTrustedIp) => {
if (
!plan.ipAllowlisting &&
clientSecretTrustedIp.ipAddress !== "0.0.0.0/0" &&
clientSecretTrustedIp.ipAddress !== "::/0"
)
throw new BadRequestError({
message:
"Failed to add IP access range to service token due to plan restriction. Upgrade plan to add IP access range."
});
if (!isValidIpOrCidr(clientSecretTrustedIp.ipAddress))
throw new BadRequestError({
message: "The IP is not a valid IPv4, IPv6, or CIDR block"
});
return extractIPDetails(clientSecretTrustedIp.ipAddress);
});
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps.map((accessTokenTrustedIp) => {
if (
!plan.ipAllowlisting &&
@@ -254,41 +234,31 @@ export const identityUaServiceFactory = ({
if (
(accessTokenMaxTTL || uaIdentityAuth.accessTokenMaxTTL) > 0 &&
(accessTokenTTL || uaIdentityAuth.accessTokenMaxTTL) >
(accessTokenMaxTTL || uaIdentityAuth.accessTokenMaxTTL)
(accessTokenTTL || uaIdentityAuth.accessTokenMaxTTL) > (accessTokenMaxTTL || uaIdentityAuth.accessTokenMaxTTL)
) {
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
}
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
identityMembershipOrg.orgId
);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Edit,
OrgPermissionSubjects.Identity
);
const { permission } = await permissionService.getOrgPermission(actor, actorId, identityMembershipOrg.orgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.orgId);
const reformattedClientSecretTrustedIps = clientSecretTrustedIps?.map(
(clientSecretTrustedIp) => {
if (
!plan.ipAllowlisting &&
clientSecretTrustedIp.ipAddress !== "0.0.0.0/0" &&
clientSecretTrustedIp.ipAddress !== "::/0"
)
throw new BadRequestError({
message:
"Failed to add IP access range to service token due to plan restriction. Upgrade plan to add IP access range."
});
if (!isValidIpOrCidr(clientSecretTrustedIp.ipAddress))
throw new BadRequestError({
message: "The IP is not a valid IPv4, IPv6, or CIDR block"
});
return extractIPDetails(clientSecretTrustedIp.ipAddress);
}
);
const reformattedClientSecretTrustedIps = clientSecretTrustedIps?.map((clientSecretTrustedIp) => {
if (
!plan.ipAllowlisting &&
clientSecretTrustedIp.ipAddress !== "0.0.0.0/0" &&
clientSecretTrustedIp.ipAddress !== "::/0"
)
throw new BadRequestError({
message:
"Failed to add IP access range to service token due to plan restriction. Upgrade plan to add IP access range."
});
if (!isValidIpOrCidr(clientSecretTrustedIp.ipAddress))
throw new BadRequestError({
message: "The IP is not a valid IPv4, IPv6, or CIDR block"
});
return extractIPDetails(clientSecretTrustedIp.ipAddress);
});
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps?.map((accessTokenTrustedIp) => {
if (
!plan.ipAllowlisting &&
@@ -330,15 +300,8 @@ export const identityUaServiceFactory = ({
const uaIdentityAuth = await identityUaDAL.findOne({ identityId });
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
identityMembershipOrg.orgId
);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Read,
OrgPermissionSubjects.Identity
);
const { permission } = await permissionService.getOrgPermission(actor, actorId, identityMembershipOrg.orgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity);
return { ...uaIdentityAuth, orgId: identityMembershipOrg.orgId };
};
@@ -356,15 +319,8 @@ export const identityUaServiceFactory = ({
throw new BadRequestError({
message: "The identity does not have universal auth"
});
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
identityMembershipOrg.orgId
);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Create,
OrgPermissionSubjects.Identity
);
const { permission } = await permissionService.getOrgPermission(actor, actorId, identityMembershipOrg.orgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
@@ -409,15 +365,8 @@ export const identityUaServiceFactory = ({
throw new BadRequestError({
message: "The identity does not have universal auth"
});
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
identityMembershipOrg.orgId
);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Read,
OrgPermissionSubjects.Identity
);
const { permission } = await permissionService.getOrgPermission(actor, actorId, identityMembershipOrg.orgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
@@ -441,27 +390,15 @@ export const identityUaServiceFactory = ({
return { clientSecrets, orgId: identityMembershipOrg.orgId };
};
const revokeUaClientSecret = async ({
identityId,
actorId,
actor,
clientSecretId
}: TRevokeUaClientSecretDTO) => {
const revokeUaClientSecret = async ({ identityId, actorId, actor, clientSecretId }: TRevokeUaClientSecretDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" });
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.Univeral)
throw new BadRequestError({
message: "The identity does not have universal auth"
});
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
identityMembershipOrg.orgId
);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Delete,
OrgPermissionSubjects.Identity
);
const { permission } = await permissionService.getOrgPermission(actor, actorId, identityMembershipOrg.orgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
@@ -14,11 +14,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
try {
const [data] = await (tx || db)(TableName.IdentityOrgMembership)
.where(filter)
.join(
TableName.Identity,
`${TableName.IdentityOrgMembership}.identityId`,
`${TableName.Identity}.id`
)
.join(TableName.Identity, `${TableName.IdentityOrgMembership}.identityId`, `${TableName.Identity}.id`)
.select(selectAllTableCols(TableName.IdentityOrgMembership))
.select(db.ref("name").withSchema(TableName.Identity))
.select(db.ref("authMethod").withSchema(TableName.Identity));
@@ -35,16 +31,8 @@ export const identityOrgDALFactory = (db: TDbClient) => {
try {
const docs = await (tx || db)(TableName.IdentityOrgMembership)
.where(`${TableName.IdentityOrgMembership}.orgId`, orgId)
.join(
TableName.Identity,
`${TableName.IdentityOrgMembership}.identityId`,
`${TableName.Identity}.id`
)
.leftJoin(
TableName.OrgRoles,
`${TableName.IdentityOrgMembership}.roleId`,
`${TableName.OrgRoles}.id`
)
.join(TableName.Identity, `${TableName.IdentityOrgMembership}.identityId`, `${TableName.Identity}.id`)
.leftJoin(TableName.OrgRoles, `${TableName.IdentityOrgMembership}.roleId`, `${TableName.OrgRoles}.id`)
.select(selectAllTableCols(TableName.IdentityOrgMembership))
// cr stands for custom role
.select(db.ref("id").as("crId").withSchema(TableName.OrgRoles))
@@ -1,10 +1,7 @@
import { ForbiddenError } from "@casl/ability";
import { OrgMembershipRole, TOrgRoles } from "@app/db/schemas";
import {
OrgPermissionActions,
OrgPermissionSubjects
} from "@app/ee/services/permission/org-permission";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { isAtLeastAsPrivileged } from "@app/lib/casl";
import { BadRequestError, ForbiddenRequestError } from "@app/lib/errors";
@@ -30,17 +27,15 @@ export const identityServiceFactory = ({
}: TIdentityServiceFactoryDep) => {
const createIdentity = async ({ name, role, actor, orgId, actorId }: TCreateIdentityDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Create,
OrgPermissionSubjects.Identity
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Identity);
const { permission: rolePermission, role: customRole } =
await permissionService.getOrgPermissionByRole(role, orgId);
const { permission: rolePermission, role: customRole } = await permissionService.getOrgPermissionByRole(
role,
orgId
);
const isCustomRole = Boolean(customRole);
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, rolePermission);
if (!hasRequiredPriviledges)
throw new BadRequestError({ message: "Failed to create a more privileged identity" });
if (!hasRequiredPriviledges) throw new BadRequestError({ message: "Failed to create a more privileged identity" });
const identity = await identityDAL.transaction(async (tx) => {
const newIdentity = await identityDAL.create({ name }, tx);
@@ -61,18 +56,10 @@ export const identityServiceFactory = ({
const updateIdentity = async ({ id, role, name, actor, actorId }: TUpdateIdentityDTO) => {
const identityOrgMembership = await identityOrgMembershipDAL.findOne({ identityId: id });
if (!identityOrgMembership)
throw new BadRequestError({ message: `Failed to find identity with id ${id}` });
if (!identityOrgMembership) throw new BadRequestError({ message: `Failed to find identity with id ${id}` });
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
identityOrgMembership.orgId
);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Edit,
OrgPermissionSubjects.Identity
);
const { permission } = await permissionService.getOrgPermission(actor, actorId, identityOrgMembership.orgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity);
const { permission: identityRolePermission } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
@@ -85,8 +72,10 @@ export const identityServiceFactory = ({
let customRole: TOrgRoles | undefined;
if (role) {
const { permission: rolePermission, role: customOrgRole } =
await permissionService.getOrgPermissionByRole(role, identityOrgMembership.orgId);
const { permission: rolePermission, role: customOrgRole } = await permissionService.getOrgPermissionByRole(
role,
identityOrgMembership.orgId
);
const isCustomRole = Boolean(customOrgRole);
const hasRequiredNewRolePermission = isAtLeastAsPrivileged(permission, rolePermission);
@@ -96,9 +85,7 @@ export const identityServiceFactory = ({
}
const identity = await identityDAL.transaction(async (tx) => {
const newIdentity = name
? await identityDAL.updateById(id, { name }, tx)
: await identityDAL.findById(id, tx);
const newIdentity = name ? await identityDAL.updateById(id, { name }, tx) : await identityDAL.findById(id, tx);
if (role) {
await identityOrgMembershipDAL.update(
{ identityId: id },
@@ -117,18 +104,10 @@ export const identityServiceFactory = ({
const deleteIdentity = async ({ actorId, actor, id }: TDeleteIdentityDTO) => {
const identityOrgMembership = await identityOrgMembershipDAL.findOne({ identityId: id });
if (!identityOrgMembership)
throw new BadRequestError({ message: `Failed to find identity with id ${id}` });
if (!identityOrgMembership) throw new BadRequestError({ message: `Failed to find identity with id ${id}` });
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
identityOrgMembership.orgId
);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Delete,
OrgPermissionSubjects.Identity
);
const { permission } = await permissionService.getOrgPermission(actor, actorId, identityOrgMembership.orgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Identity);
const { permission: identityRolePermission } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
id,
@@ -144,10 +123,7 @@ export const identityServiceFactory = ({
const listOrgIdentities = async ({ orgId, actor, actorId }: TOrgPermission) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Read,
OrgPermissionSubjects.Identity
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity);
const identityMemberhips = await identityOrgMembershipDAL.findByOrgId(orgId);
return identityMemberhips;
@@ -22,11 +22,7 @@ const getAppsGCPSecretManager = async ({ accessToken }: { accessToken: string })
interface GCPApp {
projectNumber: string;
projectId: string;
lifecycleState:
| "ACTIVE"
| "LIFECYCLE_STATE_UNSPECIFIED"
| "DELETE_REQUESTED"
| "DELETE_IN_PROGRESS";
lifecycleState: "ACTIVE" | "LIFECYCLE_STATE_UNSPECIFIED" | "DELETE_REQUESTED" | "DELETE_IN_PROGRESS";
name: string;
createTime: string;
parent: {
@@ -131,30 +127,21 @@ const getAppsHeroku = async ({ accessToken }: { accessToken: string }) => {
/**
* Return list of names of apps for Vercel integration
*/
const getAppsVercel = async ({
accessToken,
teamId
}: {
teamId?: string | null;
accessToken: string;
}) => {
const getAppsVercel = async ({ accessToken, teamId }: { teamId?: string | null; accessToken: string }) => {
const res = (
await request.get<{ projects: { name: string; id: string }[] }>(
`${IntegrationUrls.VERCEL_API_URL}/v9/projects`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json"
},
...(teamId
? {
params: {
teamId
}
await request.get<{ projects: { name: string; id: string }[] }>(`${IntegrationUrls.VERCEL_API_URL}/v9/projects`, {
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json"
},
...(teamId
? {
params: {
teamId
}
: {})
}
)
}
: {})
})
).data;
const apps = res.projects.map((a) => ({
@@ -273,16 +260,13 @@ const getAppsGithub = async ({ accessToken }: { accessToken: string }) => {
*/
const getAppsRender = async ({ accessToken }: { accessToken: string }) => {
const res = (
await request.get<{ service: { name: string; id: string } }[]>(
`${IntegrationUrls.RENDER_API_URL}/v1/services`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json",
"Accept-Encoding": "application/json"
}
await request.get<{ service: { name: string; id: string } }[]>(`${IntegrationUrls.RENDER_API_URL}/v1/services`, {
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json",
"Accept-Encoding": "application/json"
}
)
})
).data;
const apps = res.map((a) => ({
@@ -346,13 +330,7 @@ const getAppsRailway = async ({ accessToken }: { accessToken: string }) => {
/**
* Return list of sites for Laravel Forge integration
*/
const getAppsLaravelForge = async ({
accessToken,
serverId
}: {
accessToken: string;
serverId?: string;
}) => {
const getAppsLaravelForge = async ({ accessToken, serverId }: { accessToken: string; serverId?: string }) => {
const res = (
await request.get<{ sites: { name: string; id: string }[] }>(
`${IntegrationUrls.LARAVELFORGE_API_URL}/api/v1/servers/${serverId}/sites`,
@@ -464,13 +442,7 @@ const getAppsTravisCI = async ({ accessToken }: { accessToken: string }) => {
/**
* Return list of projects for Terraform Cloud integration
*/
const getAppsTerraformCloud = async ({
accessToken,
workspacesId
}: {
accessToken: string;
workspacesId?: string;
}) => {
const getAppsTerraformCloud = async ({ accessToken, workspacesId }: { accessToken: string; workspacesId?: string }) => {
const res = (
await request.get<{ data: { attributes: { name: string }; id: string } }>(
`${IntegrationUrls.TERRAFORM_CLOUD_API_URL}/api/v2/workspaces/${workspacesId}`,
@@ -566,16 +538,13 @@ const getAppsGitlab = async ({
per_page: String(perPage)
});
const { data } = await request.get<{ name: string; id: string }[]>(
`${gitLabApiUrl}/v4/users/${id}/projects`,
{
params,
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json"
}
const { data } = await request.get<{ name: string; id: string }[]>(`${gitLabApiUrl}/v4/users/${id}/projects`, {
params,
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json"
}
);
});
data.forEach((a) => {
apps.push({
@@ -642,15 +611,12 @@ const getAppsSupabase = async ({ accessToken }: { accessToken: string }) => {
* Return list of accounts for the Checkly integration
*/
const getAppsCheckly = async ({ accessToken }: { accessToken: string }) => {
const { data } = await request.get<{ name: string; id: string }[]>(
`${IntegrationUrls.CHECKLY_API_URL}/v1/accounts`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json"
}
const { data } = await request.get<{ name: string; id: string }[]>(`${IntegrationUrls.CHECKLY_API_URL}/v1/accounts`, {
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json"
}
);
});
const apps = data.map((a) => ({
name: a.name,
@@ -663,13 +629,7 @@ const getAppsCheckly = async ({ accessToken }: { accessToken: string }) => {
/**
* Return list of projects for the Cloudflare Pages integration
*/
const getAppsCloudflarePages = async ({
accessToken,
accountId
}: {
accessToken: string;
accountId?: string;
}) => {
const getAppsCloudflarePages = async ({ accessToken, accountId }: { accessToken: string; accountId?: string }) => {
const { data } = await request.get<{ result: { name: string; id: string }[] }>(
`${IntegrationUrls.CLOUDFLARE_PAGES_API_URL}/client/v4/accounts/${accountId}/pages/projects`,
{
@@ -690,13 +650,7 @@ const getAppsCloudflarePages = async ({
/**
* Return list of projects for the Cloudflare Workers integration
*/
const getAppsCloudflareWorkers = async ({
accessToken,
accountId
}: {
accessToken: string;
accountId?: string;
}) => {
const getAppsCloudflareWorkers = async ({ accessToken, accountId }: { accessToken: string; accountId?: string }) => {
const { data } = await request.get<{ result: { id: string }[] }>(
`${IntegrationUrls.CLOUDFLARE_WORKERS_API_URL}/client/v4/accounts/${accountId}/workers/services`,
{
@@ -717,13 +671,7 @@ const getAppsCloudflareWorkers = async ({
/**
* Return list of repositories for the BitBucket integration based on provided BitBucket workspace
*/
const getAppsBitBucket = async ({
accessToken,
workspaceSlug
}: {
accessToken: string;
workspaceSlug?: string;
}) => {
const getAppsBitBucket = async ({ accessToken, workspaceSlug }: { accessToken: string; workspaceSlug?: string }) => {
interface RepositoriesResponse {
size: number;
page: number;
@@ -881,25 +829,19 @@ const getAppsWindmill = async ({ accessToken }: { accessToken: string }) => {
// is write access is allowed then delete the created secrets from workspace
if (writeUser && writeFolder) {
await request.delete(
`${IntegrationUrls.WINDMILL_API_URL}/w/${app.id}/variables/delete/${userPath}`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json"
}
await request.delete(`${IntegrationUrls.WINDMILL_API_URL}/w/${app.id}/variables/delete/${userPath}`, {
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json"
}
);
});
await request.delete(
`${IntegrationUrls.WINDMILL_API_URL}/w/${app.id}/variables/delete/${folderPath}`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json"
}
await request.delete(`${IntegrationUrls.WINDMILL_API_URL}/w/${app.id}/variables/delete/${folderPath}`, {
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json"
}
);
});
return app;
}
@@ -910,9 +852,7 @@ const getAppsWindmill = async ({ accessToken }: { accessToken: string }) => {
});
const appsWriteResponses = await Promise.all(writeAccessCheck);
const appsWithWriteAccess = appsWriteResponses.filter(
(appRes) => !(appRes as { error: string })?.error
);
const appsWithWriteAccess = appsWriteResponses.filter((appRes) => !(appRes as { error: string })?.error);
const apps = (appsWithWriteAccess as { id: string; name: string }[]).map((a) => ({
name: a.name,
@@ -945,15 +885,12 @@ const getAppsDigitalOceanAppPlatform = async ({ accessToken }: { accessToken: st
}
const res = (
await request.get<{ apps: DigitalOceanApp[] }>(
`${IntegrationUrls.DIGITAL_OCEAN_API_URL}/v2/apps`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json"
}
await request.get<{ apps: DigitalOceanApp[] }>(`${IntegrationUrls.DIGITAL_OCEAN_API_URL}/v2/apps`, {
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json"
}
)
})
).data;
return (res.apps ?? []).map((a) => ({
@@ -1,21 +1,10 @@
import { ForbiddenError } from "@casl/ability";
import {
SecretEncryptionAlgo,
SecretKeyEncoding,
TIntegrationAuths,
TIntegrationAuthsInsert
} from "@app/db/schemas";
import { SecretEncryptionAlgo, SecretKeyEncoding, TIntegrationAuths, TIntegrationAuthsInsert } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import {
ProjectPermissionActions,
ProjectPermissionSub
} from "@app/ee/services/permission/project-permission";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { request } from "@app/lib/config/request";
import {
decryptSymmetric128BitHexKeyUTF8,
encryptSymmetric128BitHexKeyUTF8
} from "@app/lib/crypto";
import { decryptSymmetric128BitHexKeyUTF8, encryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
import { BadRequestError } from "@app/lib/errors";
import { TProjectPermission } from "@app/lib/types";
@@ -70,16 +59,9 @@ export const integrationAuthServiceFactory = ({
projectBotDAL,
projectBotService
}: TIntegrationAuthServiceFactoryDep) => {
const listIntegrationAuthByProjectId = async ({
actorId,
actor,
projectId
}: TProjectPermission) => {
const listIntegrationAuthByProjectId = async ({ actorId, actor, projectId }: TProjectPermission) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const authorizations = await integrationAuthDAL.find({ projectId });
return authorizations;
};
@@ -88,38 +70,20 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" });
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
integrationAuth.projectId
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
);
const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
return integrationAuth;
};
const oauthExchange = async ({
projectId,
actorId,
actor,
integration,
url,
code
}: TOauthExchangeDTO) => {
const oauthExchange = async ({ projectId, actorId, actor, integration, url, code }: TOauthExchangeDTO) => {
if (!Object.values(Integrations).includes(integration as Integrations))
throw new BadRequestError({ message: "Invalid integration" });
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.Integrations
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Integrations);
const bot = await projectBotDAL.findOne({ isActive: true, projectId });
if (!bot)
throw new BadRequestError({ message: "Bot must be enabled for oauth2 code token exchange" });
if (!bot) throw new BadRequestError({ message: "Bot must be enabled for oauth2 code token exchange" });
const tokenExchange = await exchangeCode({ integration, code, url });
const updateDoc: TIntegrationAuthsInsert = {
@@ -178,14 +142,10 @@ export const integrationAuthServiceFactory = ({
throw new BadRequestError({ message: "Invalid integration" });
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.Integrations
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Integrations);
const bot = await projectBotDAL.findOne({ isActive: true, projectId });
if (!bot)
throw new BadRequestError({ message: "Bot must be enabled for oauth2 code token exchange" });
if (!bot) throw new BadRequestError({ message: "Bot must be enabled for oauth2 code token exchange" });
const updateDoc: TIntegrationAuthsInsert = {
projectId,
@@ -251,11 +211,7 @@ export const integrationAuthServiceFactory = ({
});
}
if (
integrationAuth.refreshCiphertext &&
integrationAuth.refreshIV &&
integrationAuth.refreshTag
) {
if (integrationAuth.refreshCiphertext && integrationAuth.refreshIV && integrationAuth.refreshTag) {
const refreshToken = decryptSymmetric128BitHexKeyUTF8({
key: botKey,
ciphertext: integrationAuth.refreshCiphertext,
@@ -287,11 +243,7 @@ export const integrationAuthServiceFactory = ({
}
if (!accessToken) throw new BadRequestError({ message: "Missing access token" });
if (
integrationAuth.accessIdTag &&
integrationAuth.accessIdIV &&
integrationAuth.accessIdCiphertext
) {
if (integrationAuth.accessIdTag && integrationAuth.accessIdIV && integrationAuth.accessIdCiphertext) {
accessId = decryptSymmetric128BitHexKeyUTF8({
key: botKey,
ciphertext: integrationAuth.accessIdCiphertext,
@@ -302,25 +254,12 @@ export const integrationAuthServiceFactory = ({
return { accessId, accessToken };
};
const getIntegrationApps = async ({
actor,
actorId,
teamId,
id,
workspaceSlug
}: TIntegrationAuthAppsDTO) => {
const getIntegrationApps = async ({ actor, actorId, teamId, id, workspaceSlug }: TIntegrationAuthAppsDTO) => {
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" });
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
integrationAuth.projectId
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
);
const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const botKey = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken, accessId } = await getIntegrationAccessToken(integrationAuth, botKey);
@@ -339,15 +278,8 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" });
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
integrationAuth.projectId
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
);
const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const botKey = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey);
@@ -359,24 +291,12 @@ export const integrationAuthServiceFactory = ({
return teams;
};
const getVercelBranches = async ({
appId,
id,
actor,
actorId
}: TIntegrationAuthVercelBranchesDTO) => {
const getVercelBranches = async ({ appId, id, actor, actorId }: TIntegrationAuthVercelBranchesDTO) => {
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" });
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
integrationAuth.projectId
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
);
const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const botKey = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey);
@@ -399,37 +319,22 @@ export const integrationAuthServiceFactory = ({
return [];
};
const getChecklyGroups = async ({
actorId,
actor,
id,
accountId
}: TIntegrationAuthChecklyGroupsDTO) => {
const getChecklyGroups = async ({ actorId, actor, id, accountId }: TIntegrationAuthChecklyGroupsDTO) => {
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" });
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
integrationAuth.projectId
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
);
const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const botKey = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey);
if (accountId) {
const { data } = await request.get<TChecklyGroups[]>(
`${IntegrationUrls.CHECKLY_API_URL}/v1/check-groups`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json",
"X-Checkly-Account": accountId
}
const { data } = await request.get<TChecklyGroups[]>(`${IntegrationUrls.CHECKLY_API_URL}/v1/check-groups`, {
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json",
"X-Checkly-Account": accountId
}
);
});
return data.map(({ name, id: groupId }) => ({ name, groupId }));
}
return [];
@@ -439,15 +344,8 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" });
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
integrationAuth.projectId
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
);
const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const botKey = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey);
const { data } = await request.get<{ results: Array<{ id: string; name: string }> }>(
@@ -463,24 +361,12 @@ export const integrationAuthServiceFactory = ({
return data.results.map(({ name, id: orgId }) => ({ name, orgId }));
};
const getQoveryProjects = async ({
actorId,
actor,
id,
orgId
}: TIntegrationAuthQoveryProjectDTO) => {
const getQoveryProjects = async ({ actorId, actor, id, orgId }: TIntegrationAuthQoveryProjectDTO) => {
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" });
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
integrationAuth.projectId
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
);
const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const botKey = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey);
if (orgId) {
@@ -498,24 +384,12 @@ export const integrationAuthServiceFactory = ({
return [];
};
const getQoveryEnvs = async ({
projectId,
id,
actor,
actorId
}: TIntegrationAuthQoveryEnvironmentsDTO) => {
const getQoveryEnvs = async ({ projectId, id, actor, actorId }: TIntegrationAuthQoveryEnvironmentsDTO) => {
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" });
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
integrationAuth.projectId
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
);
const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const botKey = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey);
if (projectId && projectId !== "none") {
@@ -538,24 +412,12 @@ export const integrationAuthServiceFactory = ({
return [];
};
const getQoveryApps = async ({
id,
actor,
actorId,
environmentId
}: TIntegrationAuthQoveryScopesDTO) => {
const getQoveryApps = async ({ id, actor, actorId, environmentId }: TIntegrationAuthQoveryScopesDTO) => {
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" });
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
integrationAuth.projectId
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
);
const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const botKey = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey);
if (environmentId) {
@@ -577,24 +439,12 @@ export const integrationAuthServiceFactory = ({
return [];
};
const getQoveryContainers = async ({
id,
actor,
actorId,
environmentId
}: TIntegrationAuthQoveryScopesDTO) => {
const getQoveryContainers = async ({ id, actor, actorId, environmentId }: TIntegrationAuthQoveryScopesDTO) => {
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" });
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
integrationAuth.projectId
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
);
const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const botKey = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey);
if (environmentId) {
@@ -616,24 +466,12 @@ export const integrationAuthServiceFactory = ({
return [];
};
const getQoveryJobs = async ({
id,
actor,
actorId,
environmentId
}: TIntegrationAuthQoveryScopesDTO) => {
const getQoveryJobs = async ({ id, actor, actorId, environmentId }: TIntegrationAuthQoveryScopesDTO) => {
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" });
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
integrationAuth.projectId
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
);
const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const botKey = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey);
if (environmentId) {
@@ -655,24 +493,12 @@ export const integrationAuthServiceFactory = ({
return [];
};
const getRailwayEnvironments = async ({
id,
actor,
actorId,
appId
}: TIntegrationAuthRailwayEnvDTO) => {
const getRailwayEnvironments = async ({ id, actor, actorId, appId }: TIntegrationAuthRailwayEnvDTO) => {
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" });
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
integrationAuth.projectId
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
);
const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const botKey = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey);
if (appId) {
@@ -721,24 +547,12 @@ export const integrationAuthServiceFactory = ({
}
return [];
};
const getRailwayServices = async ({
id,
actor,
actorId,
appId
}: TIntegrationAuthRailwayServicesDTO) => {
const getRailwayServices = async ({ id, actor, actorId, appId }: TIntegrationAuthRailwayServicesDTO) => {
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" });
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
integrationAuth.projectId
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
);
const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const botKey = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey);
if (appId) {
@@ -806,23 +620,12 @@ export const integrationAuthServiceFactory = ({
return [];
};
const getBitbucketWorkspaces = async ({
actorId,
actor,
id
}: TIntegrationAuthBitbucketWorkspaceDTO) => {
const getBitbucketWorkspaces = async ({ actorId, actor, id }: TIntegrationAuthBitbucketWorkspaceDTO) => {
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" });
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
integrationAuth.projectId
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
);
const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const botKey = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey);
const workspaces: TBitbucketWorkspace[] = [];
@@ -834,12 +637,11 @@ export const integrationAuthServiceFactory = ({
const { data }: { data: { values: TBitbucketWorkspace[]; next: string } } = await request.get(
workspaceUrl,
{
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json"
}
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json"
}
);
});
if (data?.values.length > 0) {
data.values.forEach((workspace) => {
@@ -856,24 +658,12 @@ export const integrationAuthServiceFactory = ({
return workspaces;
};
const getNorthFlankSecretGroups = async ({
id,
actor,
actorId,
appId
}: TIntegrationAuthNorthflankSecretGroupDTO) => {
const getNorthFlankSecretGroups = async ({ id, actor, actorId, appId }: TIntegrationAuthNorthflankSecretGroupDTO) => {
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" });
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
integrationAuth.projectId
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
);
const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const botKey = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey);
const secretGroups: { name: string; groupId: string }[] = [];
@@ -923,41 +713,26 @@ export const integrationAuthServiceFactory = ({
return secretGroups;
};
const getTeamcityBuildConfigs = async ({
appId,
id,
actorId,
actor
}: TGetIntegrationAuthTeamCityBuildConfigDTO) => {
const getTeamcityBuildConfigs = async ({ appId, id, actorId, actor }: TGetIntegrationAuthTeamCityBuildConfigDTO) => {
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" });
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
integrationAuth.projectId
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
);
const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const botKey = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey);
if (appId) {
const {
data: { buildType }
} = await request.get<{ buildType: TTeamCityBuildConfig[] }>(
`${integrationAuth.url}/app/rest/buildTypes`,
{
params: {
locator: `project:${appId}`
},
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json"
}
} = await request.get<{ buildType: TTeamCityBuildConfig[] }>(`${integrationAuth.url}/app/rest/buildTypes`, {
params: {
locator: `project:${appId}`
},
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json"
}
);
});
return buildType.map(({ name, id: buildConfigId }) => ({
name,
@@ -967,39 +742,20 @@ export const integrationAuthServiceFactory = ({
return [];
};
const deleteIntegrationAuths = async ({
projectId,
integration,
actor,
actorId
}: TDeleteIntegrationAuthsDTO) => {
const deleteIntegrationAuths = async ({ projectId, integration, actor, actorId }: TDeleteIntegrationAuthsDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.Integrations
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Integrations);
const integrations = await integrationAuthDAL.delete({ integration, projectId });
return integrations;
};
const deleteIntegrationAuthById = async ({
id,
actorId,
actor
}: TDeleteIntegrationAuthByIdDTO) => {
const deleteIntegrationAuthById = async ({ id, actorId, actor }: TDeleteIntegrationAuthByIdDTO) => {
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" });
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
integrationAuth.projectId
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.Integrations
);
const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Integrations);
const delIntegrationAuth = await integrationAuthDAL.transaction(async (tx) => {
const doc = await integrationAuthDAL.deleteById(integrationAuth.id, tx);
@@ -26,9 +26,7 @@ import { BadRequestError } from "@app/lib/errors";
import { Integrations, IntegrationUrls } from "./integration-list";
const getSecretKeyValuePair = (
secrets: Record<string, { value: string | null; comment?: string } | null>
) =>
const getSecretKeyValuePair = (secrets: Record<string, { value: string | null; comment?: string } | null>) =>
Object.keys(secrets).reduce<Record<string, string | null | undefined>>((prev, key) => {
// eslint-disable-next-line
prev[key] = secrets?.[key] === null ? null : secrets?.[key]?.value;
@@ -289,9 +287,7 @@ const syncSecretsAzureKeyVault = async ({
return result;
};
const getAzureKeyVaultSecrets = await paginateAzureKeyVaultSecrets(
`${integration.app}/secrets?api-version=7.3`
);
const getAzureKeyVaultSecrets = await paginateAzureKeyVaultSecrets(`${integration.app}/secrets?api-version=7.3`);
let lastSlashIndex: number;
const res = (
@@ -301,14 +297,11 @@ const syncSecretsAzureKeyVault = async ({
lastSlashIndex = getAzureKeyVaultSecret.id.lastIndexOf("/");
}
const azureKeyVaultSecret = await request.get(
`${getAzureKeyVaultSecret.id}?api-version=7.3`,
{
headers: {
Authorization: `Bearer ${accessToken}`
}
const azureKeyVaultSecret = await request.get(`${getAzureKeyVaultSecret.id}?api-version=7.3`, {
headers: {
Authorization: `Bearer ${accessToken}`
}
);
});
return {
...azureKeyVaultSecret.data,
@@ -701,11 +694,7 @@ const syncSecretsVercel = async ({
return false;
}
if (
integration.targetEnvironment === "preview" &&
secret.gitBranch &&
integration.path !== secret.gitBranch
) {
if (integration.targetEnvironment === "preview" && secret.gitBranch && integration.path !== secret.gitBranch) {
// case: secret on preview environment does not have same target git branch
return false;
}
@@ -719,16 +708,13 @@ const syncSecretsVercel = async ({
if (vercelSecret.type === "encrypted") {
// case: secret is encrypted -> need to decrypt
const decryptedSecret = (
await request.get(
`${IntegrationUrls.VERCEL_API_URL}/v9/projects/${integration.app}/env/${vercelSecret.id}`,
{
params,
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json"
}
await request.get(`${IntegrationUrls.VERCEL_API_URL}/v9/projects/${integration.app}/env/${vercelSecret.id}`, {
params,
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json"
}
)
})
).data;
res[vercelSecret.key] = decryptedSecret;
@@ -798,47 +784,36 @@ const syncSecretsVercel = async ({
// Sync/push new secrets
if (newSecrets.length > 0) {
await request.post(
`${IntegrationUrls.VERCEL_API_URL}/v10/projects/${integration.app}/env`,
newSecrets,
{
params,
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json"
}
await request.post(`${IntegrationUrls.VERCEL_API_URL}/v10/projects/${integration.app}/env`, newSecrets, {
params,
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json"
}
);
});
}
for await (const secret of updateSecrets) {
if (secret.type !== "sensitive") {
const { id, ...updatedSecret } = secret;
await request.patch(
`${IntegrationUrls.VERCEL_API_URL}/v9/projects/${integration.app}/env/${id}`,
updatedSecret,
{
params,
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json"
}
}
);
}
}
for await (const secret of deleteSecrets) {
await request.delete(
`${IntegrationUrls.VERCEL_API_URL}/v9/projects/${integration.app}/env/${secret.id}`,
{
await request.patch(`${IntegrationUrls.VERCEL_API_URL}/v9/projects/${integration.app}/env/${id}`, updatedSecret, {
params,
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json"
}
});
}
}
for await (const secret of deleteSecrets) {
await request.delete(`${IntegrationUrls.VERCEL_API_URL}/v9/projects/${integration.app}/env/${secret.id}`, {
params,
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json"
}
);
});
}
};
@@ -1366,9 +1341,7 @@ const syncSecretsFlyio = async ({
)
).data.data.app.secrets;
const deleteSecretsKeys = getSecretsRes
.filter((secret) => !(secret.name in secrets))
.map((secret) => secret.name);
const deleteSecretsKeys = getSecretsRes.filter((secret) => !(secret.name in secrets)).map((secret) => secret.name);
// unset (delete) secrets
const DeleteSecrets = `mutation($input: UnsetSecretsInput!) {
@@ -1593,9 +1566,7 @@ const syncSecretsGitLab = async ({
environment_scope: string;
}
const gitLabApiUrl = integrationAuth.url
? `${integrationAuth.url}/api`
: IntegrationUrls.GITLAB_API_URL;
const gitLabApiUrl = integrationAuth.url ? `${integrationAuth.url}/api` : IntegrationUrls.GITLAB_API_URL;
const getAllEnvVariables = async (integrationAppId: string, accToken: string) => {
const headers = {
@@ -1605,8 +1576,7 @@ const syncSecretsGitLab = async ({
};
let allEnvVariables: GitLabSecret[] = [];
let url: string | null =
`${gitLabApiUrl}/v4/projects/${integrationAppId}/variables?per_page=100`;
let url: string | null = `${gitLabApiUrl}/v4/projects/${integrationAppId}/variables?per_page=100`;
while (url) {
const response = await request.get(url, { headers });
@@ -1740,29 +1710,21 @@ const syncSecretsSupabase = async ({
if (
!(secretObj.name in secrets) &&
// supbase reserved secret ref: https://supabase.com/docs/guides/functions/secrets#default-secrets
![
"SUPABASE_ANON_KEY",
"SUPABASE_SERVICE_ROLE_KEY",
"SUPABASE_DB_URL",
"SUPABASE_URL"
].includes(secretObj.name)
!["SUPABASE_ANON_KEY", "SUPABASE_SERVICE_ROLE_KEY", "SUPABASE_DB_URL", "SUPABASE_URL"].includes(secretObj.name)
) {
return secretObj.name;
}
return [];
});
await request.delete(
`${IntegrationUrls.SUPABASE_API_URL}/v1/projects/${integration.appId}/secrets`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
"Accept-Encoding": "application/json"
},
data: secretsToDelete
}
);
await request.delete(`${IntegrationUrls.SUPABASE_API_URL}/v1/projects/${integration.appId}/secrets`, {
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
"Accept-Encoding": "application/json"
},
data: secretsToDelete
});
};
/**
@@ -1841,16 +1803,13 @@ const syncSecretsCheckly = async ({
// sync secrets to checkly global envars
let getSecretsRes = (
await request.get<{ key: string; value: string }[]>(
`${IntegrationUrls.CHECKLY_API_URL}/v1/variables`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json",
"X-Checkly-Account": integration.appId
}
await request.get<{ key: string; value: string }[]>(`${IntegrationUrls.CHECKLY_API_URL}/v1/variables`, {
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json",
"X-Checkly-Account": integration.appId
}
)
})
).data.reduce(
(obj, secret) => ({
...obj,
@@ -2260,15 +2219,12 @@ const syncSecretsTeamCity = async ({
for await (const key of Object.keys(res)) {
if (!(key in secrets)) {
// delete secret
await request.delete(
`${integrationAuth.url}/app/rest/projects/id:${integration.appId}/parameters/env.${key}`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json"
}
await request.delete(`${integrationAuth.url}/app/rest/projects/id:${integration.appId}/parameters/env.${key}`, {
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json"
}
);
});
}
}
}
@@ -2353,15 +2309,12 @@ const syncSecretsCloudflarePages = async ({
const getSecretsRes = (
await request.get<{
result: { deployment_configs: Record<string, { env_vars: Record<string, unknown> }> };
}>(
`${IntegrationUrls.CLOUDFLARE_PAGES_API_URL}/client/v4/accounts/${accessId}/pages/projects/${integration.app}`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json"
}
}>(`${IntegrationUrls.CLOUDFLARE_PAGES_API_URL}/client/v4/accounts/${accessId}/pages/projects/${integration.app}`, {
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json"
}
)
})
).data.result.deployment_configs[integration.targetEnvironment as string].env_vars;
// copy the secrets object, so we can set deleted keys to null
@@ -2420,10 +2373,7 @@ const syncSecretsCloudflareWorkers = async ({
).data.result;
const secretsObj = Object.fromEntries(
Object.entries(getSecretKeyValuePair(secrets)).map(([key, val]) => [
key,
{ type: "secret_text", value: val }
])
Object.entries(getSecretKeyValuePair(secrets)).map(([key, val]) => [key, { type: "secret_text", value: val }])
);
// get deleted secrets list
@@ -2463,13 +2413,11 @@ const syncSecretsCloudflareWorkers = async ({
};
}
const data: ConvertedSecret[] = Object.entries(secretsObj as SecretsObj).map(
([name, secret]) => ({
name,
text: secret.value,
type: "secret_text"
})
);
const data: ConvertedSecret[] = Object.entries(secretsObj as SecretsObj).map(([name, secret]) => ({
name,
text: secret.value,
type: "secret_text"
}));
await Promise.all(
data.map(async (secret) => {
@@ -2697,15 +2645,12 @@ const syncSecretsWindmill = async ({
// get secrets stored in windmill workspace
const res = (
await request.get<WindmillSecret[]>(
`${IntegrationUrls.WINDMILL_API_URL}/w/${integration.appId}/variables/list`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json"
}
await request.get<WindmillSecret[]>(`${IntegrationUrls.WINDMILL_API_URL}/w/${integration.appId}/variables/list`, {
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json"
}
)
})
).data.reduce(
(obj, secret) => ({
...obj,
@@ -2858,15 +2803,12 @@ const syncSecretsCloud66 = async ({
for await (const key of Object.keys(res)) {
if (!(key in secrets)) {
// delete secret
await request.delete(
`${IntegrationUrls.CLOUD_66_API_URL}/3/stacks/${integration.appId}/environments/${key}`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json"
}
await request.delete(`${IntegrationUrls.CLOUD_66_API_URL}/3/stacks/${integration.appId}/environments/${key}`, {
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json"
}
);
});
}
}
};
@@ -2920,8 +2862,7 @@ const syncSecretsHasuraCloud = async ({
const res = await request.post(
IntegrationUrls.HASURA_CLOUD_API_URL,
{
query:
"query MyQuery($tenantId: uuid!) { getTenantEnv(tenantId: $tenantId) { hash envVars } }",
query: "query MyQuery($tenantId: uuid!) { getTenantEnv(tenantId: $tenantId) { hash envVars } }",
variables: {
tenantId: integration.appId
}
@@ -518,13 +518,7 @@ const exchangeRefreshHeroku = async ({ refreshToken }: { refreshToken: string })
* @param {String} obj.refreshToken - refresh token to use to get new access token for GitLab
* @returns
*/
const exchangeRefreshGitLab = async ({
refreshToken,
url
}: {
url?: string | null;
refreshToken: string;
}) => {
const exchangeRefreshGitLab = async ({ refreshToken, url }: { url?: string | null; refreshToken: string }) => {
const accessExpiresAt = new Date();
const appCfg = getConfig();
if (!appCfg.CLIENT_ID_GITLAB || !appCfg.CLIENT_SECRET_GITLAB) {
@@ -628,19 +622,18 @@ const exchangeRefreshGCPSecretManager = async ({
const token = jwt.sign(payload, serviceAccount.private_key, { algorithm: "RS256" });
const { data }: { data: ServiceAccountAccessTokenGCPSecretManagerResponse } =
await request.post(
IntegrationUrls.GCP_TOKEN_URL,
new URLSearchParams({
grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
assertion: token
}).toString(),
{
headers: {
"Content-Type": "application/x-www-form-urlencoded"
}
const { data }: { data: ServiceAccountAccessTokenGCPSecretManagerResponse } = await request.post(
IntegrationUrls.GCP_TOKEN_URL,
new URLSearchParams({
grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
assertion: token
}).toString(),
{
headers: {
"Content-Type": "application/x-www-form-urlencoded"
}
);
}
);
accessExpiresAt.setSeconds(accessExpiresAt.getSeconds() + data.expires_in);
@@ -66,11 +66,7 @@ export const integrationDALFactory = (db: TDbClient) => {
try {
const integrations = await (tx || db)(TableName.Integration)
.where(`${TableName.Environment}.projectId`, projectId)
.join(
TableName.Environment,
`${TableName.Integration}.envId`,
`${TableName.Environment}.id`
)
.join(TableName.Environment, `${TableName.Integration}.envId`, `${TableName.Environment}.id`)
.select(db.ref("name").withSchema(TableName.Environment).as("envName"))
.select(db.ref("slug").withSchema(TableName.Environment).as("envSlug"))
.select(db.ref("id").withSchema(TableName.Environment).as("envId"))
@@ -99,11 +95,7 @@ export const integrationDALFactory = (db: TDbClient) => {
.where("isActive", true)
.where(`${TableName.Environment}.slug`, environment)
.join(TableName.Environment, `${TableName.Integration}.envId`, `${TableName.Environment}.id`)
.join(
TableName.IntegrationAuth,
`${TableName.IntegrationAuth}.id`,
`${TableName.Integration}.integrationAuthId`
)
.join(TableName.IntegrationAuth, `${TableName.IntegrationAuth}.id`, `${TableName.Integration}.integrationAuthId`)
.select(db.ref("name").withSchema(TableName.Environment).as("envName"))
.select(db.ref("slug").withSchema(TableName.Environment).as("envSlug"))
.select(db.ref("id").withSchema(TableName.Environment).as("envId"))
@@ -119,10 +111,7 @@ export const integrationDALFactory = (db: TDbClient) => {
db.ref("refreshCiphertext").withSchema(TableName.IntegrationAuth).as("refreshCiphertextAu"),
db.ref("refreshIV").withSchema(TableName.IntegrationAuth).as("refreshIVAu"),
db.ref("refreshTag").withSchema(TableName.IntegrationAuth).as("refreshTagAu"),
db
.ref("accessIdCiphertext")
.withSchema(TableName.IntegrationAuth)
.as("accessIdCiphertextAu"),
db.ref("accessIdCiphertext").withSchema(TableName.IntegrationAuth).as("accessIdCiphertextAu"),
db.ref("accessIdIV").withSchema(TableName.IntegrationAuth).as("accessIdIVAu"),
db.ref("accessIdTag").withSchema(TableName.IntegrationAuth).as("accessIdTagAu"),
db.ref("accessIV").withSchema(TableName.IntegrationAuth).as("accessIVAu"),
@@ -1,10 +1,7 @@
import { ForbiddenError } from "@casl/ability";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import {
ProjectPermissionActions,
ProjectPermissionSub
} from "@app/ee/services/permission/project-permission";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { BadRequestError } from "@app/lib/errors";
import { TProjectPermission } from "@app/lib/types";
@@ -12,11 +9,7 @@ import { TIntegrationAuthDALFactory } from "../integration-auth/integration-auth
import { TSecretQueueFactory } from "../secret/secret-queue";
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
import { TIntegrationDALFactory } from "./integration-dal";
import {
TCreateIntegrationDTO,
TDeleteIntegrationDTO,
TUpdateIntegrationDTO
} from "./integration-types";
import { TCreateIntegrationDTO, TDeleteIntegrationDTO, TUpdateIntegrationDTO } from "./integration-types";
type TIntegrationServiceFactoryDep = {
integrationDAL: TIntegrationDALFactory;
@@ -57,21 +50,10 @@ export const integrationServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(integrationAuthId);
if (!integrationAuth) throw new BadRequestError({ message: "Integration auth not found" });
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
integrationAuth.projectId
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.Integrations
);
const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Integrations);
const folder = await folderDAL.findBySecretPath(
integrationAuth.projectId,
sourceEnvironment,
secretPath
);
const folder = await folderDAL.findBySecretPath(integrationAuth.projectId, sourceEnvironment, secretPath);
if (!folder) throw new BadRequestError({ message: "Folder path not found" });
const integration = await integrationDAL.create({
@@ -116,15 +98,8 @@ export const integrationServiceFactory = ({
const integration = await integrationDAL.findById(id);
if (!integration) throw new BadRequestError({ message: "Integration auth not found" });
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
integration.projectId
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
ProjectPermissionSub.Integrations
);
const { permission } = await permissionService.getProjectPermission(actor, actorId, integration.projectId);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Integrations);
const folder = await folderDAL.findBySecretPath(integration.projectId, environment, secretPath);
if (!folder) throw new BadRequestError({ message: "Folder path not found" });
@@ -146,15 +121,8 @@ export const integrationServiceFactory = ({
const integration = await integrationDAL.findById(id);
if (!integration) throw new BadRequestError({ message: "Integration auth not found" });
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
integration.projectId
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.Integrations
);
const { permission } = await permissionService.getProjectPermission(actor, actorId, integration.projectId);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Integrations);
const deletedIntegration = await integrationDAL.deleteById(id);
return { ...integration, ...deletedIntegration };
@@ -162,10 +130,7 @@ export const integrationServiceFactory = ({
const listIntegrationByProject = async ({ actor, actorId, projectId }: TProjectPermission) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const integrations = await integrationDAL.findByProjectId(projectId);
return integrations;
@@ -7,9 +7,7 @@ export type TIncidentContactsDALFactory = ReturnType<typeof incidentContactDALFa
export const incidentContactDALFactory = (db: TDbClient) => {
const create = async (orgId: string, email: string) => {
try {
const [incidentContact] = await db(TableName.IncidentContact)
.insert({ orgId, email })
.returning("*");
const [incidentContact] = await db(TableName.IncidentContact).insert({ orgId, email }).returning("*");
return incidentContact;
} catch (error) {
throw new DatabaseError({ name: "Incident contact create", error });
@@ -38,10 +36,7 @@ export const incidentContactDALFactory = (db: TDbClient) => {
const deleteById = async (id: string, orgId: string) => {
try {
const [incidentContact] = await db(TableName.IncidentContact)
.where({ orgId, id })
.delete()
.returning("*");
const [incidentContact] = await db(TableName.IncidentContact).where({ orgId, id }).delete().returning("*");
return incidentContact;
} catch (error) {
throw new DatabaseError({ name: "Incident contact delete", error });
+9 -40
View File
@@ -11,13 +11,7 @@ import {
TUserEncryptionKeys
} from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors";
import {
buildFindFilter,
selectAllTableCols,
TFindFilter,
TFindOpt,
withTransaction
} from "@app/lib/knex";
import { buildFindFilter, selectAllTableCols, TFindFilter, TFindOpt, withTransaction } from "@app/lib/knex";
export type TOrgDALFactory = ReturnType<typeof orgDALFactory>;
@@ -36,11 +30,7 @@ export const orgDALFactory = (db: TDbClient) => {
try {
const org = await db(TableName.OrgMembership)
.where({ userId })
.join(
TableName.Organization,
`${TableName.OrgMembership}.orgId`,
`${TableName.Organization}.id`
)
.join(TableName.Organization, `${TableName.OrgMembership}.orgId`, `${TableName.Organization}.id`)
.select(selectAllTableCols(TableName.Organization));
return org;
} catch (error) {
@@ -105,10 +95,7 @@ export const orgDALFactory = (db: TDbClient) => {
const deleteById = async (orgId: string, tx?: Knex) => {
try {
const [org] = await (tx || db)(TableName.Organization)
.where({ id: orgId })
.delete()
.returning("*");
const [org] = await (tx || db)(TableName.Organization).where({ id: orgId }).delete().returning("*");
return org;
} catch (error) {
throw new DatabaseError({ error, name: "Update organization" });
@@ -142,26 +129,16 @@ export const orgDALFactory = (db: TDbClient) => {
const updateMembershipById = async (id: string, data: TOrgMembershipsUpdate, tx?: Knex) => {
try {
const [membership] = await (tx || db)(TableName.OrgMembership)
.where({ id })
.update(data)
.returning("*");
const [membership] = await (tx || db)(TableName.OrgMembership).where({ id }).update(data).returning("*");
return membership;
} catch (error) {
throw new DatabaseError({ error, name: "Update org membership" });
}
};
const updateMembership = async (
filter: Partial<TOrgMemberships>,
data: TOrgMembershipsUpdate,
tx?: Knex
) => {
const updateMembership = async (filter: Partial<TOrgMemberships>, data: TOrgMembershipsUpdate, tx?: Knex) => {
try {
const membership = await (tx || db)(TableName.OrgMembership)
.where(filter)
.update(data)
.returning("*");
const membership = await (tx || db)(TableName.OrgMembership).where(filter).update(data).returning("*");
return membership;
} catch (error) {
throw new DatabaseError({ error, name: "Update org memberships" });
@@ -170,10 +147,7 @@ export const orgDALFactory = (db: TDbClient) => {
const deleteMembershipById = async (id: string, orgId: string, tx?: Knex) => {
try {
const [membership] = await (tx || db)(TableName.OrgMembership)
.where({ id, orgId })
.delete()
.returning("*");
const [membership] = await (tx || db)(TableName.OrgMembership).where({ id, orgId }).delete().returning("*");
return membership;
} catch (error) {
throw new DatabaseError({ error, name: "Delete org membership" });
@@ -189,16 +163,11 @@ export const orgDALFactory = (db: TDbClient) => {
// eslint-disable-next-line
.where(buildFindFilter(filter))
.join(TableName.Users, `${TableName.Users}.id`, `${TableName.OrgMembership}.userId`)
.select(
selectAllTableCols(TableName.OrgMembership),
db.ref("email").withSchema(TableName.Users)
);
.select(selectAllTableCols(TableName.OrgMembership), db.ref("email").withSchema(TableName.Users));
if (limit) void query.limit(limit);
if (offset) void query.offset(offset);
if (sort) {
void query.orderBy(
sort.map(([column, order, nulls]) => ({ column: column as string, order, nulls }))
);
void query.orderBy(sort.map(([column, order, nulls]) => ({ column: column as string, order, nulls })));
}
const res = await query;
return res;
+7 -31
View File
@@ -21,20 +21,10 @@ type TOrgRoleServiceFactoryDep = {
export type TOrgRoleServiceFactory = ReturnType<typeof orgRoleServiceFactory>;
export const orgRoleServiceFactory = ({
orgRoleDAL,
permissionService
}: TOrgRoleServiceFactoryDep) => {
const createRole = async (
userId: string,
orgId: string,
data: Omit<TOrgRolesInsert, "orgId">
) => {
export const orgRoleServiceFactory = ({ orgRoleDAL, permissionService }: TOrgRoleServiceFactoryDep) => {
const createRole = async (userId: string, orgId: string, data: Omit<TOrgRolesInsert, "orgId">) => {
const { permission } = await permissionService.getUserOrgPermission(userId, orgId);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Create,
OrgPermissionSubjects.Role
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Role);
const existingRole = await orgRoleDAL.findOne({ slug: data.slug, orgId });
if (existingRole) throw new BadRequestError({ name: "Create Role", message: "Duplicate role" });
const role = await orgRoleDAL.create({
@@ -45,17 +35,9 @@ export const orgRoleServiceFactory = ({
return role;
};
const updateRole = async (
userId: string,
orgId: string,
roleId: string,
data: Omit<TOrgRolesUpdate, "orgId">
) => {
const updateRole = async (userId: string, orgId: string, roleId: string, data: Omit<TOrgRolesUpdate, "orgId">) => {
const { permission } = await permissionService.getUserOrgPermission(userId, orgId);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Edit,
OrgPermissionSubjects.Role
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Role);
if (data?.slug) {
const existingRole = await orgRoleDAL.findOne({ slug: data.slug, orgId });
if (existingRole && existingRole.id !== roleId)
@@ -71,10 +53,7 @@ export const orgRoleServiceFactory = ({
const deleteRole = async (userId: string, orgId: string, roleId: string) => {
const { permission } = await permissionService.getUserOrgPermission(userId, orgId);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Delete,
OrgPermissionSubjects.Role
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Role);
const [deletedRole] = await orgRoleDAL.delete({ id: roleId, orgId });
if (!deleteRole) throw new BadRequestError({ message: "Role not found", name: "Update role" });
@@ -83,10 +62,7 @@ export const orgRoleServiceFactory = ({
const listRoles = async (userId: string, orgId: string) => {
const { permission } = await permissionService.getUserOrgPermission(userId, orgId);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Read,
OrgPermissionSubjects.Role
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Role);
const customRoles = await orgRoleDAL.find({ orgId });
const roles = [
{
+19 -67
View File
@@ -5,10 +5,7 @@ import jwt from "jsonwebtoken";
import { OrgMembershipRole, OrgMembershipStatus } from "@app/db/schemas";
import { TProjects } from "@app/db/schemas/projects";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import {
OrgPermissionActions,
OrgPermissionSubjects
} from "@app/ee/services/permission/org-permission";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { TSamlConfigDALFactory } from "@app/ee/services/saml-config/saml-config-dal";
import { getConfig } from "@app/lib/config/env";
@@ -74,8 +71,7 @@ export const orgServiceFactory = ({
const findOrganizationById = async (userId: string, orgId: string) => {
await permissionService.getUserOrgPermission(userId, orgId);
const org = await orgDAL.findOrgById(orgId);
if (!org)
throw new BadRequestError({ name: "Org not found", message: "Organization not found" });
if (!org) throw new BadRequestError({ name: "Org not found", message: "Organization not found" });
return org;
};
/*
@@ -90,10 +86,7 @@ export const orgServiceFactory = ({
* */
const findAllOrgMembers = async (userId: string, orgId: string) => {
const { permission } = await permissionService.getUserOrgPermission(userId, orgId);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Read,
OrgPermissionSubjects.Member
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Member);
const members = await orgDAL.findAllOrgMembers(orgId);
return members;
@@ -101,14 +94,9 @@ export const orgServiceFactory = ({
const findAllWorkspaces = async ({ actor, actorId, orgId }: TFindAllWorkspacesDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Read,
OrgPermissionSubjects.Workspace
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Workspace);
const organizationWorkspaceIds = new Set(
(await projectDAL.find({ orgId })).map((workspace) => workspace.id)
);
const organizationWorkspaceIds = new Set((await projectDAL.find({ orgId })).map((workspace) => workspace.id));
let workspaces: (TProjects & { organization: string } & {
environments: {
@@ -134,13 +122,9 @@ export const orgServiceFactory = ({
* */
const updateOrgName = async (userId: string, orgId: string, name: string) => {
const { permission } = await permissionService.getUserOrgPermission(userId, orgId);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Edit,
OrgPermissionSubjects.Settings
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
const org = await orgDAL.updateById(orgId, { name });
if (!org)
throw new BadRequestError({ name: "Org not found", message: "Organization not found" });
if (!org) throw new BadRequestError({ name: "Org not found", message: "Organization not found" });
return org;
};
/*
@@ -222,29 +206,19 @@ export const orgServiceFactory = ({
* Org membership management
* Not another service because it has close ties with how an org works doesn't make sense to seperate them
* */
const updateOrgMembership = async ({
role,
orgId,
userId,
membershipId
}: TUpdateOrgMembershipDTO) => {
const updateOrgMembership = async ({ role, orgId, userId, membershipId }: TUpdateOrgMembershipDTO) => {
const { permission } = await permissionService.getUserOrgPermission(userId, orgId);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Edit,
OrgPermissionSubjects.Member
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Member);
const isCustomRole = !Object.values(OrgMembershipRole).includes(role as OrgMembershipRole);
if (isCustomRole) {
const customRole = await orgRoleDAL.findOne({ slug: role, orgId });
if (!customRole)
throw new BadRequestError({ name: "Update membership", message: "Role not found" });
if (!customRole) throw new BadRequestError({ name: "Update membership", message: "Role not found" });
const plan = await licenseService.getPlan(orgId);
if (!plan?.rbac)
throw new BadRequestError({
message:
"Failed to assign custom role due to RBAC restriction. Upgrade plan to assign custom role to member."
message: "Failed to assign custom role due to RBAC restriction. Upgrade plan to assign custom role to member."
});
const [membership] = await orgDAL.updateMembership(
@@ -257,10 +231,7 @@ export const orgServiceFactory = ({
return membership;
}
const [membership] = await orgDAL.updateMembership(
{ id: membershipId, orgId },
{ role, roleId: null }
);
const [membership] = await orgDAL.updateMembership({ id: membershipId, orgId }, { role, roleId: null });
return membership;
};
/*
@@ -268,10 +239,7 @@ export const orgServiceFactory = ({
*/
const inviteUserToOrganization = async ({ orgId, userId, inviteeEmail }: TInviteUserToOrgDTO) => {
const { permission } = await permissionService.getUserOrgPermission(userId, orgId);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Create,
OrgPermissionSubjects.Member
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Member);
const samlCfg = await samlConfigDAL.findOne({ orgId });
if (samlCfg && samlCfg.isActive) {
@@ -284,8 +252,7 @@ export const orgServiceFactory = ({
// case: limit imposed on number of members allowed
// case: number of members used exceeds the number of members allowed
throw new BadRequestError({
message:
"Failed to invite member due to member limit reached. Upgrade plan to invite more members."
message: "Failed to invite member due to member limit reached. Upgrade plan to invite more members."
});
}
const invitee = await orgDAL.transaction(async (tx) => {
@@ -293,10 +260,7 @@ export const orgServiceFactory = ({
if (inviteeUser) {
// if user already exist means its already part of infisical
// Thus the signup flow is not needed anymore
const [inviteeMembership] = await orgDAL.findMembership(
{ orgId, userId: inviteeUser.id },
{ tx }
);
const [inviteeMembership] = await orgDAL.findMembership({ orgId, userId: inviteeUser.id }, { tx });
if (inviteeMembership && inviteeMembership.status === OrgMembershipStatus.Accepted) {
throw new BadRequestError({
message: "Failed to invite an existing member of org",
@@ -432,10 +396,7 @@ export const orgServiceFactory = ({
const deleteOrgMembership = async ({ orgId, userId, membershipId }: TDeleteOrgMembershipDTO) => {
const { permission } = await permissionService.getUserOrgPermission(userId, orgId);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Delete,
OrgPermissionSubjects.Member
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Member);
const membership = await orgDAL.deleteMembershipById(membershipId, orgId);
@@ -448,20 +409,14 @@ export const orgServiceFactory = ({
* */
const findIncidentContacts = async (userId: string, orgId: string) => {
const { permission } = await permissionService.getUserOrgPermission(userId, orgId);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Read,
OrgPermissionSubjects.IncidentAccount
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.IncidentAccount);
const incidentContacts = await incidentContactDAL.findByOrgId(orgId);
return incidentContacts;
};
const createIncidentContact = async (userId: string, orgId: string, email: string) => {
const { permission } = await permissionService.getUserOrgPermission(userId, orgId);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Create,
OrgPermissionSubjects.IncidentAccount
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.IncidentAccount);
const doesIncidentContactExist = await incidentContactDAL.findOne(orgId, { email });
if (doesIncidentContactExist) {
throw new BadRequestError({
@@ -476,10 +431,7 @@ export const orgServiceFactory = ({
const deleteIncidentContact = async (userId: string, orgId: string, id: string) => {
const { permission } = await permissionService.getUserOrgPermission(userId, orgId);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Delete,
OrgPermissionSubjects.IncidentAccount
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.IncidentAccount);
const incidentContact = await incidentContactDAL.deleteById(id, orgId);
return incidentContact;
@@ -15,11 +15,7 @@ export const projectBotDALFactory = (db: TDbClient) => {
const bot = await (tx || db)(TableName.ProjectBot)
.where(filter)
.leftJoin(TableName.Users, `${TableName.ProjectBot}.senderId`, `${TableName.Users}.id`)
.leftJoin(
TableName.UserEncryptionKey,
`${TableName.UserEncryptionKey}.userId`,
`${TableName.Users}.id`
)
.leftJoin(TableName.UserEncryptionKey, `${TableName.UserEncryptionKey}.userId`, `${TableName.Users}.id`)
.select(selectAllTableCols(TableName.ProjectBot))
.select(db.ref("publicKey").withSchema(TableName.UserEncryptionKey).as("senderPubKey"))
.first();
@@ -2,10 +2,7 @@ import { ForbiddenError } from "@casl/ability";
import { SecretEncryptionAlgo, SecretKeyEncoding } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import {
ProjectPermissionActions,
ProjectPermissionSub
} from "@app/ee/services/permission/project-permission";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { getConfig } from "@app/lib/config/env";
import {
decryptAsymmetric,
@@ -28,10 +25,7 @@ type TProjectBotServiceFactoryDep = {
export type TProjectBotServiceFactory = ReturnType<typeof projectBotServiceFactory>;
export const projectBotServiceFactory = ({
projectBotDAL,
permissionService
}: TProjectBotServiceFactoryDep) => {
export const projectBotServiceFactory = ({ projectBotDAL, permissionService }: TProjectBotServiceFactoryDep) => {
const getBotKey = async (projectId: string) => {
const appCfg = getConfig();
const encryptionKey = appCfg.ENCRYPTION_KEY;
@@ -79,10 +73,7 @@ export const projectBotServiceFactory = ({
const findBotByProjectId = async ({ actorId, actor, projectId }: TProjectPermission) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const appCfg = getConfig();
const bot = await projectBotDAL.transaction(async (tx) => {
@@ -108,10 +99,7 @@ export const projectBotServiceFactory = ({
);
}
if (appCfg.ENCRYPTION_KEY) {
const { iv, tag, ciphertext } = encryptSymmetric128BitHexKeyUTF8(
privateKey,
appCfg.ENCRYPTION_KEY
);
const { iv, tag, ciphertext } = encryptSymmetric128BitHexKeyUTF8(privateKey, appCfg.ENCRYPTION_KEY);
return projectBotDAL.create(
{
name: "Infisical Bot",
@@ -132,25 +120,12 @@ export const projectBotServiceFactory = ({
return bot;
};
const setBotActiveState = async ({
actor,
botId,
botKey,
actorId,
isActive
}: TSetActiveStateDTO) => {
const setBotActiveState = async ({ actor, botId, botKey, actorId, isActive }: TSetActiveStateDTO) => {
const bot = await projectBotDAL.findById(botId);
if (!bot) throw new BadRequestError({ message: "Bot not found" });
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
bot.projectId
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
ProjectPermissionSub.Integrations
);
const { permission } = await permissionService.getProjectPermission(actor, actorId, bot.projectId);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Integrations);
if (isActive) {
if (!botKey?.nonce || !botKey?.encryptedKey) {
@@ -12,9 +12,7 @@ export const projectEnvDALFactory = (db: TDbClient) => {
const findBySlugs = async (projectId: string, env: string[], tx?: Knex) => {
try {
const envs = await (tx || db)(TableName.Environment)
.where("projectId", projectId)
.whereIn("slug", env);
const envs = await (tx || db)(TableName.Environment).where("projectId", projectId).whereIn("slug", env);
return envs;
} catch (error) {
throw new DatabaseError({ error, name: "Find by slugs" });
@@ -31,12 +29,7 @@ export const projectEnvDALFactory = (db: TDbClient) => {
return lastPos?.position || 0;
};
const updateAllPosition = async (
projectId: string,
pos: number,
targetPos: number,
tx?: Knex
) => {
const updateAllPosition = async (projectId: string, pos: number, targetPos: number, tx?: Knex) => {
try {
if (targetPos === -1) {
// this means delete
@@ -2,10 +2,7 @@ import { ForbiddenError } from "@casl/ability";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import {
ProjectPermissionActions,
ProjectPermissionSub
} from "@app/ee/services/permission/project-permission";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { BadRequestError } from "@app/lib/errors";
import { TProjectDALFactory } from "../project/project-dal";
@@ -32,10 +29,7 @@ export const projectEnvServiceFactory = ({
}: TProjectEnvServiceFactoryDep) => {
const createEnvironment = async ({ projectId, actorId, actor, name, slug }: TCreateEnvDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.Environments
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Environments);
const envs = await projectEnvDAL.find({ projectId });
const existingEnv = envs.find(({ slug: envSlug }) => envSlug === slug);
@@ -65,20 +59,9 @@ export const projectEnvServiceFactory = ({
return env;
};
const updateEnvironment = async ({
projectId,
slug,
actor,
actorId,
name,
id,
position
}: TUpdateEnvDTO) => {
const updateEnvironment = async ({ projectId, slug, actor, actorId, name, id, position }: TUpdateEnvDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
ProjectPermissionSub.Environments
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Environments);
const oldEnv = await projectEnvDAL.findOne({ id, projectId });
if (!oldEnv) throw new BadRequestError({ message: "Environment not found" });
@@ -104,10 +87,7 @@ export const projectEnvServiceFactory = ({
const deleteEnvironment = async ({ projectId, actor, actorId, id }: TDeleteEnvDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.Environments
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Environments);
const env = await projectEnvDAL.transaction(async (tx) => {
const [doc] = await projectEnvDAL.delete({ id, projectId }, tx);
@@ -15,11 +15,7 @@ export const projectKeyDALFactory = (db: TDbClient) => {
try {
const projectKey = await db(TableName.ProjectKeys)
.join(TableName.Users, `${TableName.ProjectKeys}.senderId`, `${TableName.Users}.id`)
.join(
TableName.UserEncryptionKey,
`${TableName.UserEncryptionKey}.userId`,
`${TableName.Users}.id`
)
.join(TableName.UserEncryptionKey, `${TableName.UserEncryptionKey}.userId`, `${TableName.Users}.id`)
.where({ projectId, receiverId: userId })
.orderBy("createdAt", "desc", "last")
.select(selectAllTableCols(TableName.ProjectKeys))
@@ -38,11 +34,7 @@ export const projectKeyDALFactory = (db: TDbClient) => {
const pubKeys = await db(TableName.ProjectMembership)
.where({ projectId })
.join(TableName.Users, `${TableName.ProjectMembership}.userId`, `${TableName.Users}.id`)
.join(
TableName.UserEncryptionKey,
`${TableName.Users}.id`,
`${TableName.UserEncryptionKey}.userId`
)
.join(TableName.UserEncryptionKey, `${TableName.Users}.id`, `${TableName.UserEncryptionKey}.userId`)
.select("userId", "publicKey");
return pubKeys;
} catch (error) {
@@ -1,10 +1,7 @@
import { ForbiddenError } from "@casl/ability";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import {
ProjectPermissionActions,
ProjectPermissionSub
} from "@app/ee/services/permission/project-permission";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { BadRequestError } from "@app/lib/errors";
import { TProjectMembershipDALFactory } from "../project-membership/project-membership-dal";
@@ -33,10 +30,7 @@ export const projectKeyServiceFactory = ({
encryptedKey
}: TUploadProjectKeyDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
ProjectPermissionSub.Member
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
const receiverMembership = await projectMembershipDAL.findOne({
userId: receiverId,
@@ -59,10 +53,7 @@ export const projectKeyServiceFactory = ({
const getProjectPublicKeys = async ({ actor, actorId, projectId }: TGetLatestProjectKeyDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Member
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
return projectKeyDAL.findAllProjectUserPubKeys(projectId);
};
@@ -3,10 +3,7 @@ import { ForbiddenError } from "@casl/ability";
import { OrgMembershipStatus, ProjectMembershipRole, TableName } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import {
ProjectPermissionActions,
ProjectPermissionSub
} from "@app/ee/services/permission/project-permission";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError } from "@app/lib/errors";
import { groupBy } from "@app/lib/fn";
@@ -53,25 +50,14 @@ export const projectMembershipServiceFactory = ({
}: TProjectMembershipServiceFactoryDep) => {
const getProjectMemberships = async ({ actorId, actor, projectId }: TGetProjectMembershipDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Member
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
return projectMembershipDAL.findAllProjectMembers(projectId);
};
const inviteUserToProject = async ({
actorId,
actor,
projectId,
email
}: TInviteUserToProjectDTO) => {
const inviteUserToProject = async ({ actorId, actor, projectId, email }: TInviteUserToProjectDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.Member
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Member);
const invitee = await userDAL.findOne({ email });
if (!invitee || !invitee.isAccepted)
@@ -126,37 +112,25 @@ export const projectMembershipServiceFactory = ({
return { invitee, latestKey };
};
const addUsersToProject = async ({
projectId,
actorId,
actor,
members
}: TAddUsersToWorkspaceDTO) => {
const addUsersToProject = async ({ projectId, actorId, actor, members }: TAddUsersToWorkspaceDTO) => {
const project = await projectDAL.findById(projectId);
if (!project) throw new BadRequestError({ message: "Project not found" });
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.Member
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Member);
const orgMembers = await orgDAL.findMembership({
orgId: project.orgId,
$in: {
[`${TableName.OrgMembership}.id` as "id"]: members.map(
({ orgMembershipId }) => orgMembershipId
)
[`${TableName.OrgMembership}.id` as "id"]: members.map(({ orgMembershipId }) => orgMembershipId)
}
});
if (orgMembers.length !== members.length)
throw new BadRequestError({ message: "Some users are not part of org" });
if (orgMembers.length !== members.length) throw new BadRequestError({ message: "Some users are not part of org" });
const existingMembers = await projectMembershipDAL.find({
projectId,
$in: { userId: orgMembers.map(({ userId }) => userId).filter(Boolean) as string[] }
});
if (existingMembers.length)
throw new BadRequestError({ message: "Some users are already part of project" });
if (existingMembers.length) throw new BadRequestError({ message: "Some users are already part of project" });
await projectMembershipDAL.transaction(async (tx) => {
await projectMembershipDAL.insertMany(
@@ -203,24 +177,17 @@ export const projectMembershipServiceFactory = ({
role
}: TUpdateProjectMembershipDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
ProjectPermissionSub.Member
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
const isCustomRole = !Object.values(ProjectMembershipRole).includes(
role as ProjectMembershipRole
);
const isCustomRole = !Object.values(ProjectMembershipRole).includes(role as ProjectMembershipRole);
if (isCustomRole) {
const customRole = await projectRoleDAL.findOne({ slug: role, projectId });
if (!customRole)
throw new BadRequestError({ name: "Update project membership", message: "Role not found" });
if (!customRole) throw new BadRequestError({ name: "Update project membership", message: "Role not found" });
const project = await projectDAL.findById(customRole.projectId);
const plan = await licenseService.getPlan(project.orgId);
if (!plan?.rbac)
throw new BadRequestError({
message:
"Failed to assign custom role due to RBAC restriction. Upgrade plan to assign custom role to member."
message: "Failed to assign custom role due to RBAC restriction. Upgrade plan to assign custom role to member."
});
const [membership] = await projectMembershipDAL.update(
@@ -233,30 +200,16 @@ export const projectMembershipServiceFactory = ({
return membership;
}
const [membership] = await projectMembershipDAL.update(
{ id: membershipId, projectId },
{ role, roleId: null }
);
const [membership] = await projectMembershipDAL.update({ id: membershipId, projectId }, { role, roleId: null });
return membership;
};
const deleteProjectMembership = async ({
actorId,
actor,
projectId,
membershipId
}: TDeleteProjectMembershipDTO) => {
const deleteProjectMembership = async ({ actorId, actor, projectId, membershipId }: TDeleteProjectMembershipDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.Member
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Member);
const membership = await projectMembershipDAL.transaction(async (tx) => {
const [deletedMembership] = await projectMembershipDAL.delete(
{ projectId, id: membershipId },
tx
);
const [deletedMembership] = await projectMembershipDAL.delete({ projectId, id: membershipId }, tx);
await projectKeyDAL.delete({ receiverId: deletedMembership.userId, projectId }, tx);
return deletedMembership;
});
@@ -18,18 +18,12 @@ import { TProjectRoleDALFactory } from "./project-role-dal";
type TProjectRoleServiceFactoryDep = {
projectRoleDAL: TProjectRoleDALFactory;
permissionService: Pick<
TPermissionServiceFactory,
"getProjectPermission" | "getUserProjectPermission"
>;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getUserProjectPermission">;
};
export type TProjectRoleServiceFactory = ReturnType<typeof projectRoleServiceFactory>;
export const projectRoleServiceFactory = ({
projectRoleDAL,
permissionService
}: TProjectRoleServiceFactoryDep) => {
export const projectRoleServiceFactory = ({ projectRoleDAL, permissionService }: TProjectRoleServiceFactoryDep) => {
const createRole = async (
actor: ActorType,
actorId: string,
@@ -37,10 +31,7 @@ export const projectRoleServiceFactory = ({
data: Omit<TProjectRolesInsert, "projectId">
) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.Role
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Role);
const existingRole = await projectRoleDAL.findOne({ slug: data.slug, projectId });
if (existingRole) throw new BadRequestError({ name: "Create Role", message: "Duplicate role" });
const role = await projectRoleDAL.create({
@@ -59,10 +50,7 @@ export const projectRoleServiceFactory = ({
data: Omit<TOrgRolesUpdate, "orgId">
) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
ProjectPermissionSub.Role
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Role);
if (data?.slug) {
const existingRole = await projectRoleDAL.findOne({ slug: data.slug, projectId });
if (existingRole && existingRole.id !== roleId)
@@ -76,17 +64,9 @@ export const projectRoleServiceFactory = ({
return updatedRole;
};
const deleteRole = async (
actor: ActorType,
actorId: string,
projectId: string,
roleId: string
) => {
const deleteRole = async (actor: ActorType, actorId: string, projectId: string, roleId: string) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.Role
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Role);
const [deletedRole] = await projectRoleDAL.delete({ id: roleId, projectId });
if (!deleteRole) throw new BadRequestError({ message: "Role not found", name: "Update role" });
@@ -95,10 +75,7 @@ export const projectRoleServiceFactory = ({
const listRoles = async (actor: ActorType, actorId: string, projectId: string) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Role
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Role);
const customRoles = await projectRoleDAL.find({ projectId });
const roles = [
{
@@ -151,10 +128,7 @@ export const projectRoleServiceFactory = ({
};
const getUserPermission = async (userId: string, projectId: string) => {
const { permission, membership } = await permissionService.getUserProjectPermission(
userId,
projectId
);
const { permission, membership } = await permissionService.getUserProjectPermission(userId, projectId);
return { permissions: packRules(permission.rules), membership };
};
+6 -30
View File
@@ -12,16 +12,8 @@ export const projectDALFactory = (db: TDbClient) => {
try {
const workspaces = await db(TableName.ProjectMembership)
.where({ userId })
.join(
TableName.Project,
`${TableName.ProjectMembership}.projectId`,
`${TableName.Project}.id`
)
.leftJoin(
TableName.Environment,
`${TableName.Environment}.projectId`,
`${TableName.Project}.id`
)
.join(TableName.Project, `${TableName.ProjectMembership}.projectId`, `${TableName.Project}.id`)
.leftJoin(TableName.Environment, `${TableName.Environment}.projectId`, `${TableName.Project}.id`)
.select(
selectAllTableCols(TableName.Project),
db.ref("id").withSchema(TableName.Project).as("_id"),
@@ -60,16 +52,8 @@ export const projectDALFactory = (db: TDbClient) => {
try {
const workspaces = await db(TableName.IdentityProjectMembership)
.where({ identityId })
.join(
TableName.Project,
`${TableName.IdentityProjectMembership}.projectId`,
`${TableName.Project}.id`
)
.leftJoin(
TableName.Environment,
`${TableName.Environment}.projectId`,
`${TableName.Project}.id`
)
.join(TableName.Project, `${TableName.IdentityProjectMembership}.projectId`, `${TableName.Project}.id`)
.leftJoin(TableName.Environment, `${TableName.Environment}.projectId`, `${TableName.Project}.id`)
.select(
selectAllTableCols(TableName.Project),
db.ref("id").withSchema(TableName.Project).as("_id"),
@@ -110,16 +94,8 @@ export const projectDALFactory = (db: TDbClient) => {
try {
const workspaces = await db(TableName.ProjectMembership)
.where(`${TableName.Project}.id`, id)
.join(
TableName.Project,
`${TableName.ProjectMembership}.projectId`,
`${TableName.Project}.id`
)
.join(
TableName.Environment,
`${TableName.Environment}.projectId`,
`${TableName.Project}.id`
)
.join(TableName.Project, `${TableName.ProjectMembership}.projectId`, `${TableName.Project}.id`)
.join(TableName.Environment, `${TableName.Environment}.projectId`, `${TableName.Project}.id`)
.select(
selectAllTableCols(TableName.Project),
db.ref("id").withSchema(TableName.Project).as("_id"),
@@ -3,15 +3,9 @@ import slugify from "@sindresorhus/slugify";
import { ProjectMembershipRole } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import {
OrgPermissionActions,
OrgPermissionSubjects
} from "@app/ee/services/permission/org-permission";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import {
ProjectPermissionActions,
ProjectPermissionSub
} from "@app/ee/services/permission/project-permission";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { getConfig } from "@app/lib/config/env";
import { createSecretBlindIndex } from "@app/lib/crypto";
import { BadRequestError } from "@app/lib/errors";
@@ -56,10 +50,7 @@ export const projectServiceFactory = ({
* */
const createProject = async ({ orgId, actor, actorId, workspaceName }: TCreateProjectDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Create,
OrgPermissionSubjects.Workspace
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Workspace);
const appCfg = getConfig();
const blindIndex = createSecretBlindIndex(appCfg.ROOT_ENCRYPTION_KEY, appCfg.ENCRYPTION_KEY);
@@ -69,8 +60,7 @@ export const projectServiceFactory = ({
// case: limit imposed on number of workspaces allowed
// case: number of workspaces used exceeds the number of workspaces allowed
throw new BadRequestError({
message:
"Failed to create workspace due to plan limit reached. Upgrade plan to add more workspaces."
message: "Failed to create workspace due to plan limit reached. Upgrade plan to add more workspaces."
});
}
@@ -118,10 +108,7 @@ export const projectServiceFactory = ({
const deleteProject = async ({ actor, actorId, projectId }: TDeleteProjectDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.Project
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Project);
const deletedProject = await projectDAL.deleteById(projectId);
return deletedProject;
@@ -144,26 +131,15 @@ export const projectServiceFactory = ({
autoCapitalization
}: TGetProjectDTO & { autoCapitalization: boolean }) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
ProjectPermissionSub.Settings
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings);
const updatedProject = await projectDAL.updateById(projectId, { autoCapitalization });
return updatedProject;
};
const updateName = async ({
projectId,
actor,
actorId,
name
}: TGetProjectDTO & { name: string }) => {
const updateName = async ({ projectId, actor, actorId, name }: TGetProjectDTO & { name: string }) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
ProjectPermissionSub.Settings
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings);
const updatedProject = await projectDAL.updateById(projectId, { name });
return updatedProject;
@@ -13,16 +13,8 @@ export const secretBlindIndexDALFactory = (db: TDbClient) => {
const countOfSecretsWithNullSecretBlindIndex = async (projectId: string, tx?: Knex) => {
try {
const doc = await (tx || db)(TableName.Secret)
.leftJoin(
TableName.SecretFolder,
`${TableName.SecretFolder}.id`,
`${TableName.Secret}.folderId`
)
.leftJoin(
TableName.Environment,
`${TableName.Environment}.id`,
`${TableName.SecretFolder}.envId`
)
.leftJoin(TableName.SecretFolder, `${TableName.SecretFolder}.id`, `${TableName.Secret}.folderId`)
.leftJoin(TableName.Environment, `${TableName.Environment}.id`, `${TableName.SecretFolder}.envId`)
.where({ projectId })
.whereNull("secretBlindIndex")
.count(`${TableName.Secret}.id` as "id");
@@ -35,16 +27,8 @@ export const secretBlindIndexDALFactory = (db: TDbClient) => {
const findAllSecretsByProjectId = async (projectId: string, tx?: Knex) => {
try {
const docs = await (tx || db)(TableName.Secret)
.leftJoin(
TableName.SecretFolder,
`${TableName.SecretFolder}.id`,
`${TableName.Secret}.folderId`
)
.leftJoin(
TableName.Environment,
`${TableName.Environment}.id`,
`${TableName.SecretFolder}.envId`
)
.leftJoin(TableName.SecretFolder, `${TableName.SecretFolder}.id`, `${TableName.Secret}.folderId`)
.leftJoin(TableName.Environment, `${TableName.Environment}.id`, `${TableName.SecretFolder}.envId`)
.where({ projectId })
.whereNull("secretBlindIndex")
.select(selectAllTableCols(TableName.Secret))
@@ -61,16 +45,8 @@ export const secretBlindIndexDALFactory = (db: TDbClient) => {
const findSecretsByProjectId = async (projectId: string, secretIds: string[], tx?: Knex) => {
try {
const docs = await (tx || db)(TableName.Secret)
.leftJoin(
TableName.SecretFolder,
`${TableName.SecretFolder}.id`,
`${TableName.Secret}.folderId`
)
.leftJoin(
TableName.Environment,
`${TableName.Environment}.id`,
`${TableName.SecretFolder}.envId`
)
.leftJoin(TableName.SecretFolder, `${TableName.SecretFolder}.id`, `${TableName.Secret}.folderId`)
.leftJoin(TableName.Environment, `${TableName.Environment}.id`, `${TableName.SecretFolder}.envId`)
.where({ projectId })
.whereIn(`${TableName.Secret}.id`, secretIds)
.whereNull("secretBlindIndex")
@@ -24,11 +24,7 @@ export const secretBlindIndexServiceFactory = ({
permissionService,
secretDAL
}: TSecretBlindIndexServiceFactoryDep) => {
const getSecretBlindIndexStatus = async ({
actor,
projectId,
actorId
}: TGetProjectBlindIndexStatusDTO) => {
const getSecretBlindIndexStatus = async ({ actor, projectId, actorId }: TGetProjectBlindIndexStatusDTO) => {
await permissionService.getProjectPermission(actor, actorId, projectId);
const secretCount = await secretBlindIndexDAL.countOfSecretsWithNullSecretBlindIndex(projectId);
@@ -57,15 +53,13 @@ export const secretBlindIndexServiceFactory = ({
}
const blindIndexCfg = await secretBlindIndexDAL.findOne({ projectId });
if (!blindIndexCfg)
throw new BadRequestError({ message: "Blind index not found", name: "CreateSecret" });
if (!blindIndexCfg) throw new BadRequestError({ message: "Blind index not found", name: "CreateSecret" });
const secrets = await secretBlindIndexDAL.findSecretsByProjectId(
projectId,
secretsToUpdate.map(({ secretId }) => secretId)
);
if (secrets.length !== secretsToUpdate.length)
throw new BadRequestError({ message: "Secret not found" });
if (secrets.length !== secretsToUpdate.length) throw new BadRequestError({ message: "Secret not found" });
const operations = await Promise.all(
secretsToUpdate.map(async ({ secretName, secretId: id }) => {
@@ -1,12 +1,7 @@
import { Knex } from "knex";
import { TDbClient } from "@app/db";
import {
TableName,
TProjectEnvironments,
TSecretFolders,
TSecretFoldersUpdate
} from "@app/db/schemas";
import { TableName, TProjectEnvironments, TSecretFolders, TSecretFoldersUpdate } from "@app/db/schemas";
import { BadRequestError, DatabaseError } from "@app/lib/errors";
import { groupBy, removeTrailingSlash } from "@app/lib/fn";
import { ormify, selectAllTableCols } from "@app/lib/knex";
@@ -16,14 +11,10 @@ export const validateFolderName = (folderName: string) => {
return validNameRegex.test(folderName);
};
const sqlFindMultipleFolderByEnvPathQuery = (
db: Knex,
query: Array<{ envId: string; secretPath: string }>
) => {
const sqlFindMultipleFolderByEnvPathQuery = (db: Knex, query: Array<{ envId: string; secretPath: string }>) => {
// this is removing an trailing slash like /folder1/folder2/ -> /folder1/folder2
const formatedQuery = query.map(({ envId, secretPath }) => {
const formatedPath =
secretPath.at(-1) === "/" && secretPath.length > 1 ? secretPath.slice(0, -1) : secretPath;
const formatedPath = secretPath.at(-1) === "/" && secretPath.length > 1 ? secretPath.slice(0, -1) : secretPath;
const segments = formatedPath.split("/").filter(Boolean);
if (segments.some((segment) => !validateFolderName(segment))) {
throw new BadRequestError({ message: "Invalid folder name" });
@@ -73,9 +64,9 @@ const sqlFindMultipleFolderByEnvPathQuery = (
.where((wb) =>
formatedQuery.map(({ secretPath }) =>
wb.orWhereRaw(
`depth = array_position(ARRAY[${secretPath
.map(() => "?")
.join(",")}]::varchar[], ${TableName.SecretFolder}.name,depth)`,
`depth = array_position(ARRAY[${secretPath.map(() => "?").join(",")}]::varchar[], ${
TableName.SecretFolder
}.name,depth)`,
[...secretPath]
)
)
@@ -92,15 +83,9 @@ const sqlFindMultipleFolderByEnvPathQuery = (
.from<TSecretFolders & { depth: number; path: string }>("parent");
};
const sqlFindFolderByPathQuery = (
db: Knex,
projectId: string,
environment: string,
secretPath: string
) => {
const sqlFindFolderByPathQuery = (db: Knex, projectId: string, environment: string, secretPath: string) => {
// this is removing an trailing slash like /folder1/folder2/ -> /folder1/folder2
const formatedPath =
secretPath.at(-1) === "/" && secretPath.length > 1 ? secretPath.slice(0, -1) : secretPath;
const formatedPath = secretPath.at(-1) === "/" && secretPath.length > 1 ? secretPath.slice(0, -1) : secretPath;
// next goal to sanitize saw the raw sql query is safe
// for this we ensure folder name contains only string and - nothing else
const pathSegments = formatedPath.split("/").filter(Boolean);
@@ -121,11 +106,7 @@ const sqlFindFolderByPathQuery = (
path: db.raw("'/'")
})
.from(TableName.SecretFolder)
.join(
TableName.Environment,
`${TableName.SecretFolder}.envId`,
`${TableName.Environment}.id`
)
.join(TableName.Environment, `${TableName.SecretFolder}.envId`, `${TableName.Environment}.id`)
.where({
projectId,
parentId: null
@@ -156,11 +137,7 @@ const sqlFindFolderByPathQuery = (
);
})
.from<TSecretFolders & { depth: number; path: string }>("parent")
.leftJoin<TProjectEnvironments>(
TableName.Environment,
`${TableName.Environment}.id`,
"parent.envId"
)
.leftJoin<TProjectEnvironments>(TableName.Environment, `${TableName.Environment}.id`, "parent.envId")
.select<
TSecretFolders & {
depth: number;
@@ -192,16 +169,10 @@ const sqlFindSecretPathByFolderId = (db: Knex, projectId: string, folderIds: str
// this is for root condition
// if the given folder id is root folder id then intial path is set as / instead of /root
// if not root folder the path here will be /<folder name>
path: db.raw(
`CONCAT('/', (CASE WHEN "parentId" is NULL THEN '' ELSE ${TableName.SecretFolder}.name END))`
),
path: db.raw(`CONCAT('/', (CASE WHEN "parentId" is NULL THEN '' ELSE ${TableName.SecretFolder}.name END))`),
child: db.raw("NULL::uuid")
})
.join(
TableName.Environment,
`${TableName.SecretFolder}.envId`,
`${TableName.Environment}.id`
)
.join(TableName.Environment, `${TableName.SecretFolder}.envId`, `${TableName.Environment}.id`)
.where({ projectId })
.whereIn(`${TableName.SecretFolder}.id`, folderIds)
.union(
@@ -234,19 +205,9 @@ export const ROOT_FOLDER_NAME = "root";
export const secretFolderDALFactory = (db: TDbClient) => {
const secretFolderOrm = ormify(db, TableName.SecretFolder);
const findBySecretPath = async (
projectId: string,
environment: string,
path: string,
tx?: Knex
) => {
const findBySecretPath = async (projectId: string, environment: string, path: string, tx?: Knex) => {
try {
const folder = await sqlFindFolderByPathQuery(
tx || db,
projectId,
environment,
removeTrailingSlash(path)
)
const folder = await sqlFindFolderByPathQuery(tx || db, projectId, environment, removeTrailingSlash(path))
.orderBy("depth", "desc")
.first();
if (folder && folder.path !== removeTrailingSlash(path)) {
@@ -263,19 +224,9 @@ export const secretFolderDALFactory = (db: TDbClient) => {
// used in folder creation
// even if its the original given /path1/path2
// it will stop automatically at /path2
const findClosestFolder = async (
projectId: string,
environment: string,
path: string,
tx?: Knex
) => {
const findClosestFolder = async (projectId: string, environment: string, path: string, tx?: Knex) => {
try {
const folder = await sqlFindFolderByPathQuery(
tx || db,
projectId,
environment,
removeTrailingSlash(path)
)
const folder = await sqlFindFolderByPathQuery(tx || db, projectId, environment, removeTrailingSlash(path))
.orderBy("depth", "desc")
.first();
if (!folder) return;
@@ -286,10 +237,7 @@ export const secretFolderDALFactory = (db: TDbClient) => {
}
};
const findByManySecretPath = async (
query: Array<{ envId: string; secretPath: string }>,
tx?: Knex
) => {
const findByManySecretPath = async (query: Array<{ envId: string; secretPath: string }>, tx?: Knex) => {
try {
const formatedQuery = query.map(({ secretPath, envId }) => ({
envId,
@@ -297,10 +245,7 @@ export const secretFolderDALFactory = (db: TDbClient) => {
}));
const folders = await sqlFindMultipleFolderByEnvPathQuery(tx || db, formatedQuery);
return formatedQuery.map(({ envId, secretPath }) =>
folders.find(
({ path: targetPath, envId: targetEnvId }) =>
targetPath === secretPath && targetEnvId === envId
)
folders.find(({ path: targetPath, envId: targetEnvId }) => targetPath === secretPath && targetEnvId === envId)
);
} catch (error) {
throw new DatabaseError({ error, name: "FindByManySecretPath" });
@@ -339,11 +284,7 @@ export const secretFolderDALFactory = (db: TDbClient) => {
try {
const folder = await (tx || db)(TableName.SecretFolder)
.where({ [`${TableName.SecretFolder}.id` as "id"]: id })
.join(
TableName.Environment,
`${TableName.SecretFolder}.envId`,
`${TableName.Environment}.id`
)
.join(TableName.Environment, `${TableName.SecretFolder}.envId`, `${TableName.Environment}.id`)
.select(selectAllTableCols(TableName.SecretFolder))
.select(
db.ref("id").withSchema(TableName.Environment).as("envId"),
@@ -4,21 +4,13 @@ import { v4 as uuidv4 } from "uuid";
import { TSecretFoldersInsert } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import {
ProjectPermissionActions,
ProjectPermissionSub
} from "@app/ee/services/permission/project-permission";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { TSecretSnapshotServiceFactory } from "@app/ee/services/secret-snapshot/secret-snapshot-service";
import { BadRequestError } from "@app/lib/errors";
import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
import { TSecretFolderDALFactory } from "./secret-folder-dal";
import {
TCreateFolderDTO,
TDeleteFolderDTO,
TGetFolderDTO,
TUpdateFolderDTO
} from "./secret-folder-types";
import { TCreateFolderDTO, TDeleteFolderDTO, TGetFolderDTO, TUpdateFolderDTO } from "./secret-folder-types";
import { TSecretFolderVersionDALFactory } from "./secret-folder-version-dal";
type TSecretFolderServiceFactoryDep = {
@@ -38,14 +30,7 @@ export const secretFolderServiceFactory = ({
projectEnvDAL,
folderVersionDAL
}: TSecretFolderServiceFactoryDep) => {
const createFolder = async ({
projectId,
actor,
actorId,
name,
environment,
path: secretPath
}: TCreateFolderDTO) => {
const createFolder = async ({ projectId, actor, actorId, name, environment, path: secretPath }: TCreateFolderDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
@@ -53,8 +38,7 @@ export const secretFolderServiceFactory = ({
);
const env = await projectEnvDAL.findOne({ projectId, slug: environment });
if (!env)
throw new BadRequestError({ message: "Environment not found", name: "Create folder" });
if (!env) throw new BadRequestError({ message: "Environment not found", name: "Create folder" });
const folder = await folderDAL.transaction(async (tx) => {
// the logic is simple we need to avoid creating same folder in same path multiple times
@@ -62,12 +46,7 @@ export const secretFolderServiceFactory = ({
// so we do a tricky move. we try to find the to be created folder path if that is exactly match return that
// else we get some path before that then we will start creating remaining folder
const pathWithFolder = path.join(secretPath, name);
const parentFolder = await folderDAL.findClosestFolder(
projectId,
environment,
pathWithFolder,
tx
);
const parentFolder = await folderDAL.findClosestFolder(projectId, environment, pathWithFolder, tx);
// no folder found is not possible root should be their
if (!parentFolder) throw new BadRequestError({ message: "Secret path not found" });
// exact folder
@@ -78,24 +57,19 @@ export const secretFolderServiceFactory = ({
// this is upsert folder in a path
// we are not taking snapshots of this because
// snapshot will be removed from automatic for all commits to user click or cron based
const missingSegment = secretPath
.substring(parentFolder.path.length)
.split("/")
.filter(Boolean);
const missingSegment = secretPath.substring(parentFolder.path.length).split("/").filter(Boolean);
if (missingSegment.length) {
const newFolders: Array<TSecretFoldersInsert & { id: string }> = missingSegment.map(
(segment) => {
const newFolder = {
name: segment,
parentId: parentFolderId,
id: uuidv4(),
envId: env.id,
version: 1
};
parentFolderId = newFolder.id;
return newFolder;
}
);
const newFolders: Array<TSecretFoldersInsert & { id: string }> = missingSegment.map((segment) => {
const newFolder = {
name: segment,
parentId: parentFolderId,
id: uuidv4(),
envId: env.id,
version: 1
};
parentFolderId = newFolder.id;
return newFolder;
});
parentFolderId = newFolders.at(-1)?.id as string;
const docs = await folderDAL.insertMany(newFolders, tx);
await folderVersionDAL.insertMany(
@@ -110,10 +84,7 @@ export const secretFolderServiceFactory = ({
}
}
const doc = await folderDAL.create(
{ name, envId: env.id, version: 1, parentId: parentFolderId },
tx
);
const doc = await folderDAL.create({ name, envId: env.id, version: 1, parentId: parentFolderId }, tx);
await folderVersionDAL.create(
{
name: doc.name,
@@ -149,8 +120,7 @@ export const secretFolderServiceFactory = ({
if (!parentFolder) throw new BadRequestError({ message: "Secret path not found" });
const env = await projectEnvDAL.findOne({ projectId, slug: environment });
if (!env)
throw new BadRequestError({ message: "Environment not found", name: "Update folder" });
if (!env) throw new BadRequestError({ message: "Environment not found", name: "Update folder" });
const folder = await folderDAL
.findOne({ envId: env.id, id, parentId: parentFolder.id })
// now folder api accepts id based change
@@ -160,11 +130,7 @@ export const secretFolderServiceFactory = ({
if (!folder) throw new BadRequestError({ message: "Folder not found" });
const newFolder = await folderDAL.transaction(async (tx) => {
const [doc] = await folderDAL.update(
{ envId: env.id, id: folder.id, parentId: parentFolder.id },
{ name },
tx
);
const [doc] = await folderDAL.update({ envId: env.id, id: folder.id, parentId: parentFolder.id }, { name }, tx);
await folderVersionDAL.create(
{
name: doc.name,
@@ -182,14 +148,7 @@ export const secretFolderServiceFactory = ({
return { folder: newFolder, old: folder };
};
const deleteFolder = async ({
projectId,
actor,
actorId,
environment,
path: secretPath,
id
}: TDeleteFolderDTO) => {
const deleteFolder = async ({ projectId, actor, actorId, environment, path: secretPath, id }: TDeleteFolderDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
@@ -197,8 +156,7 @@ export const secretFolderServiceFactory = ({
);
const env = await projectEnvDAL.findOne({ projectId, slug: environment });
if (!env)
throw new BadRequestError({ message: "Environment not found", name: "Create folder" });
if (!env) throw new BadRequestError({ message: "Environment not found", name: "Create folder" });
const folder = await folderDAL.transaction(async (tx) => {
const parentFolder = await folderDAL.findBySecretPath(projectId, environment, secretPath, tx);
@@ -213,13 +171,7 @@ export const secretFolderServiceFactory = ({
return folder;
};
const getFolders = async ({
projectId,
actor,
actorId,
environment,
path: secretPath
}: TGetFolderDTO) => {
const getFolders = async ({ projectId, actor, actorId, environment, path: secretPath }: TGetFolderDTO) => {
// folder list is allowed to be read by anyone
// permission to check does user has access
await permissionService.getProjectPermission(actor, actorId, projectId);
@@ -14,11 +14,7 @@ export const secretFolderVersionDALFactory = (db: TDbClient) => {
const findLatestVersionByFolderId = async (folderId: string, tx?: Knex) => {
try {
const docs = await (tx || db)(TableName.SecretFolderVersion)
.join(
TableName.SecretFolder,
`${TableName.SecretFolderVersion}.folderId`,
`${TableName.SecretFolder}.id`
)
.join(TableName.SecretFolder, `${TableName.SecretFolderVersion}.folderId`, `${TableName.SecretFolder}.id`)
.where({ parentId: folderId })
.join<TSecretFolderVersions>(
(tx || db)(TableName.SecretFolderVersion)
@@ -42,9 +38,7 @@ export const secretFolderVersionDALFactory = (db: TDbClient) => {
const findLatestFolderVersions = async (folderIds: string[], tx?: Knex) => {
try {
const docs: Array<TSecretFolderVersions & { max: number }> = await (tx || db)(
TableName.SecretFolderVersion
)
const docs: Array<TSecretFolderVersions & { max: number }> = await (tx || db)(TableName.SecretFolderVersion)
.whereIn("folderId", folderIds)
.join(
(tx || db)(TableName.SecretFolderVersion)
@@ -53,11 +53,7 @@ export const secretImportDALFactory = (db: TDbClient) => {
try {
const docs = await (tx || db)(TableName.SecretImport)
.where(filter)
.join(
TableName.Environment,
`${TableName.SecretImport}.importEnv`,
`${TableName.Environment}.id`
)
.join(TableName.Environment, `${TableName.SecretImport}.importEnv`, `${TableName.Environment}.id`)
.select(
db.ref("*").withSchema(TableName.SecretImport) as unknown as keyof TSecretImports,
db.ref("slug").withSchema(TableName.Environment),
@@ -1,10 +1,7 @@
import { ForbiddenError, subject } from "@casl/ability";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import {
ProjectPermissionActions,
ProjectPermissionSub
} from "@app/ee/services/permission/project-permission";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { BadRequestError } from "@app/lib/errors";
import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
@@ -39,14 +36,7 @@ export const secretImportServiceFactory = ({
folderDAL,
secretDAL
}: TSecretImportServiceFactoryDep) => {
const createImport = async ({
environment,
data,
actor,
actorId,
projectId,
path
}: TCreateSecretImportDTO) => {
const createImport = async ({ environment, data, actor, actorId, projectId, path }: TCreateSecretImportDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
// check if user has permission to import into destination path
@@ -69,8 +59,7 @@ export const secretImportServiceFactory = ({
// TODO(akhilmhdh-pg): updated permission check add here
const [importEnv] = await projectEnvDAL.findBySlugs(projectId, [data.environment]);
if (!importEnv)
throw new BadRequestError({ error: "Imported env not found", name: "Create import" });
if (!importEnv) throw new BadRequestError({ error: "Imported env not found", name: "Create import" });
const secImport = await secretImportDAL.transaction(async (tx) => {
const lastPos = await secretImportDAL.findLastImportPosition(folder.id, tx);
@@ -88,15 +77,7 @@ export const secretImportServiceFactory = ({
return { ...secImport, importEnv };
};
const updateImport = async ({
path,
environment,
projectId,
actor,
actorId,
data,
id
}: TUpdateSecretImportDTO) => {
const updateImport = async ({ path, environment, projectId, actor, actorId, data, id }: TUpdateSecretImportDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
@@ -112,8 +93,7 @@ export const secretImportServiceFactory = ({
const importedEnv = data.environment // this is get env information of new one or old one
? (await projectEnvDAL.findBySlugs(projectId, [data.environment]))?.[0]
: await projectEnvDAL.findById(secImpDoc.importEnv);
if (!importedEnv)
throw new BadRequestError({ error: "Imported env not found", name: "Create import" });
if (!importedEnv) throw new BadRequestError({ error: "Imported env not found", name: "Create import" });
const updatedSecImport = await secretImportDAL.transaction(async (tx) => {
const secImp = await secretImportDAL.findOne({ folderId: folder.id, id });
@@ -135,14 +115,7 @@ export const secretImportServiceFactory = ({
return { ...updatedSecImport, importEnv: importedEnv };
};
const deleteImport = async ({
path,
environment,
projectId,
actor,
actorId,
id
}: TDeleteSecretImportDTO) => {
const deleteImport = async ({ path, environment, projectId, actor, actorId, id }: TDeleteSecretImportDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
@@ -154,25 +127,17 @@ export const secretImportServiceFactory = ({
const secImport = await secretImportDAL.transaction(async (tx) => {
const [doc] = await secretImportDAL.delete({ folderId: folder.id, id }, tx);
if (!doc)
throw new BadRequestError({ name: "Sec imp del", message: "Secret import doc not found" });
if (!doc) throw new BadRequestError({ name: "Sec imp del", message: "Secret import doc not found" });
await secretImportDAL.updateAllPosition(folder.id, doc.position, -1, tx);
const importEnv = await projectEnvDAL.findById(doc.importEnv);
if (!importEnv)
throw new BadRequestError({ error: "Imported env not found", name: "Create import" });
if (!importEnv) throw new BadRequestError({ error: "Imported env not found", name: "Create import" });
return { ...doc, importEnv };
});
return secImport;
};
const getImports = async ({
path,
environment,
projectId,
actor,
actorId
}: TGetSecretImportsDTO) => {
const getImports = async ({ path, environment, projectId, actor, actorId }: TGetSecretImportsDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
@@ -186,13 +151,7 @@ export const secretImportServiceFactory = ({
return secImports;
};
const getSecretsFromImports = async ({
path,
environment,
projectId,
actor,
actorId
}: TGetSecretsFromImportDTO) => {
const getSecretsFromImports = async ({ path, environment, projectId, actor, actorId }: TGetSecretsFromImportDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
@@ -23,11 +23,7 @@ export const secretTagDALFactory = (db: TDbClient) => {
const deleteTagsManySecret = async (projectId: string, secretIds: string[], tx?: Knex) => {
try {
const tags = await (tx || db)(TableName.JnSecretTag)
.join(
TableName.SecretTag,
`${TableName.JnSecretTag}.${TableName.SecretTag}Id`,
`${TableName.SecretTag}.id`
)
.join(TableName.SecretTag, `${TableName.JnSecretTag}.${TableName.SecretTag}Id`, `${TableName.SecretTag}.id`)
.where("projectId", projectId)
.whereIn(`${TableName.Secret}Id`, secretIds)
.delete()
@@ -1,10 +1,7 @@
import { ForbiddenError } from "@casl/ability";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import {
ProjectPermissionActions,
ProjectPermissionSub
} from "@app/ee/services/permission/project-permission";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { BadRequestError } from "@app/lib/errors";
import { TSecretTagDALFactory } from "./secret-tag-dal";
@@ -17,16 +14,10 @@ type TSecretTagServiceFactoryDep = {
export type TSecretTagServiceFactory = ReturnType<typeof secretTagServiceFactory>;
export const secretTagServiceFactory = ({
secretTagDAL,
permissionService
}: TSecretTagServiceFactoryDep) => {
export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSecretTagServiceFactoryDep) => {
const createTag = async ({ name, slug, actor, color, actorId, projectId }: TCreateTagDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.Tags
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Tags);
const existingTag = await secretTagDAL.findOne({ slug });
if (existingTag) throw new BadRequestError({ message: "Tag already exist" });
@@ -45,15 +36,8 @@ export const secretTagServiceFactory = ({
const tag = await secretTagDAL.findById(id);
if (!tag) throw new BadRequestError({ message: "Tag doesn't exist" });
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
tag.projectId
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.Tags
);
const { permission } = await permissionService.getProjectPermission(actor, actorId, tag.projectId);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Tags);
const deletedTag = await secretTagDAL.deleteById(tag.id);
return deletedTag;
@@ -61,10 +45,7 @@ export const secretTagServiceFactory = ({
const getProjectTags = async ({ actor, actorId, projectId }: TListProjectTagsDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Tags
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Tags);
const tags = await secretTagDAL.find({ projectId });
return tags;
+5 -24
View File
@@ -11,17 +11,9 @@ export type TSecretDALFactory = ReturnType<typeof secretDALFactory>;
export const secretDALFactory = (db: TDbClient) => {
const secretOrm = ormify(db, TableName.Secret);
const update = async (
filter: Partial<TSecrets>,
data: Omit<TSecretsUpdate, "version">,
tx?: Knex
) => {
const update = async (filter: Partial<TSecrets>, data: Omit<TSecretsUpdate, "version">, tx?: Knex) => {
try {
const sec = await (tx || db)(TableName.Secret)
.where(filter)
.update(data)
.increment("version", 1)
.returning("*");
const sec = await (tx || db)(TableName.Secret).where(filter).update(data).increment("version", 1).returning("*");
return sec;
} catch (error) {
throw new DatabaseError({ error, name: "update secret" });
@@ -30,10 +22,7 @@ export const secretDALFactory = (db: TDbClient) => {
// the idea is to use postgres specific function
// insert with id this will cause a conflict then merge the data
const bulkUpdate = async (
data: Array<{ filter: Partial<TSecrets>; data: TSecretsUpdate }>,
tx?: Knex
) => {
const bulkUpdate = async (data: Array<{ filter: Partial<TSecrets>; data: TSecretsUpdate }>, tx?: Knex) => {
try {
const secs = await Promise.all(
data.map(async ({ filter, data: updateData }) => {
@@ -91,16 +80,8 @@ export const secretDALFactory = (db: TDbClient) => {
.where((bd) => {
void bd.whereNull("userId").orWhere({ userId: userId || null });
})
.leftJoin(
TableName.JnSecretTag,
`${TableName.Secret}.id`,
`${TableName.JnSecretTag}.${TableName.Secret}Id`
)
.leftJoin(
TableName.SecretTag,
`${TableName.JnSecretTag}.${TableName.SecretTag}Id`,
`${TableName.SecretTag}.id`
)
.leftJoin(TableName.JnSecretTag, `${TableName.Secret}.id`, `${TableName.JnSecretTag}.${TableName.Secret}Id`)
.leftJoin(TableName.SecretTag, `${TableName.JnSecretTag}.${TableName.SecretTag}Id`, `${TableName.SecretTag}.id`)
.select(selectAllTableCols(TableName.Secret))
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
+3 -14
View File
@@ -8,10 +8,7 @@ import { buildSecretBlindIndexFromName, decryptSymmetric128BitHexKeyUTF8 } from
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
import { TSecretDALFactory } from "./secret-dal";
export const generateSecretBlindIndexBySalt = async (
secretName: string,
secretBlindIndexDoc: TSecretBlindIndexes
) => {
export const generateSecretBlindIndexBySalt = async (secretName: string, secretBlindIndexDoc: TSecretBlindIndexes) => {
const appCfg = getConfig();
const secretBlindIndex = await buildSecretBlindIndexFromName({
secretName,
@@ -32,12 +29,7 @@ type TInterpolateSecretArg = {
folderDAL: Pick<TSecretFolderDALFactory, "findBySecretPath">;
};
export const interpolateSecrets = ({
projectId,
secretEncKey,
secretDAL,
folderDAL
}: TInterpolateSecretArg) => {
export const interpolateSecrets = ({ projectId, secretEncKey, secretDAL, folderDAL }: TInterpolateSecretArg) => {
const fetchSecretsCrossEnv = () => {
const fetchCache: Record<string, Record<string, string>> = {};
@@ -197,10 +189,7 @@ export const interpolateSecrets = ({
return expandSecrets;
};
export const decryptSecretRaw = (
secret: TSecrets & { workspace: string; environment: string },
key: string
) => {
export const decryptSecretRaw = (secret: TSecrets & { workspace: string; environment: string }, key: string) => {
const secretKey = decryptSymmetric128BitHexKeyUTF8({
ciphertext: secret.secretKeyCiphertext,
iv: secret.secretKeyIV,
+11 -39
View File
@@ -23,11 +23,7 @@ import { TWebhookDALFactory } from "../webhook/webhook-dal";
import { fnTriggerWebhook } from "../webhook/webhook-fns";
import { TSecretDALFactory } from "./secret-dal";
import { interpolateSecrets } from "./secret-fns";
import {
TCreateSecretReminderDTO,
THandleReminderDTO,
TRemoveSecretReminderDTO
} from "./secret-types";
import { TCreateSecretReminderDTO, THandleReminderDTO, TRemoveSecretReminderDTO } from "./secret-types";
export type TSecretQueueFactory = ReturnType<typeof secretQueueFactory>;
@@ -105,20 +101,13 @@ export const secretQueueFactory = ({
QueueJobs.SecretReminder,
{
// on prod it this will be in days, in development this will be second
every:
appCfg.NODE_ENV === "development"
? secondsToMillis(dto.repeatDays)
: daysToMillisecond(dto.repeatDays)
every: appCfg.NODE_ENV === "development" ? secondsToMillis(dto.repeatDays) : daysToMillisecond(dto.repeatDays)
},
`reminder-${dto.secretId}`
);
};
const addSecretReminder = async ({
oldSecret,
newSecret,
projectId
}: TCreateSecretReminderDTO) => {
const addSecretReminder = async ({ oldSecret, newSecret, projectId }: TCreateSecretReminderDTO) => {
try {
const appCfg = getConfig();
@@ -179,8 +168,7 @@ export const secretQueueFactory = ({
if (newSecret.type !== "personal" && secretReminderRepeatDays !== undefined) {
if (
(secretReminderRepeatDays &&
oldSecret.secretReminderRepeatDays !== secretReminderRepeatDays) ||
(secretReminderRepeatDays && oldSecret.secretReminderRepeatDays !== secretReminderRepeatDays) ||
(secretReminderNote && oldSecret.secretReminderNote !== secretReminderNote)
) {
await addSecretReminder({
@@ -212,10 +200,7 @@ export const secretQueueFactory = ({
secretDAL,
folderDAL
});
const content: Record<
string,
{ value: string; comment?: string; skipMultilineEncoding?: boolean }
> = {};
const content: Record<string, { value: string; comment?: string; skipMultilineEncoding?: boolean }> = {};
importedSecrets.forEach(({ secrets: secs }) => {
secs.forEach((secret) => {
@@ -294,8 +279,7 @@ export const secretQueueFactory = ({
const integrations = await integrationDAL.findByProjectIdV2(projectId, environment);
const toBeSyncedIntegrations = integrations.filter(
({ secretPath: integrationSecPath, isActive }) =>
isActive && isSamePath(secretPath, integrationSecPath)
({ secretPath: integrationSecPath, isActive }) => isActive && isSamePath(secretPath, integrationSecPath)
);
if (!integrations.length) return;
@@ -309,14 +293,8 @@ export const secretQueueFactory = ({
};
const botKey = await projectBotService.getBotKey(projectId);
const { accessToken, accessId } = await integrationAuthService.getIntegrationAccessToken(
integrationAuth,
botKey
);
const secrets = await getIntegrationSecrets(
{ environment, projectId, secretPath, folderId: folder.id },
botKey
);
const { accessToken, accessId } = await integrationAuthService.getIntegrationAccessToken(integrationAuth, botKey);
const secrets = await getIntegrationSecrets({ environment, projectId, secretPath, folderId: folder.id }, botKey);
const suffixedSecrets: typeof secrets = {};
const metadata = integration.metadata as Record<string, string>;
if (metadata) {
@@ -353,25 +331,19 @@ export const secretQueueFactory = ({
const project = await projectDAL.findById(projectId);
if (!organization) {
logger.info(
`secretReminderQueue.process: [secretDocument=${data.secretId}] no organization found`
);
logger.info(`secretReminderQueue.process: [secretDocument=${data.secretId}] no organization found`);
return;
}
if (!project) {
logger.info(
`secretReminderQueue.process: [secretDocument=${data.secretId}] no project found`
);
logger.info(`secretReminderQueue.process: [secretDocument=${data.secretId}] no project found`);
return;
}
const projectMembers = await projectMembershipDAL.findAllProjectMembers(projectId);
if (!projectMembers || !projectMembers.length) {
logger.info(
`secretReminderQueue.process: [secretDocument=${data.secretId}] no project members found`
);
logger.info(`secretReminderQueue.process: [secretDocument=${data.secretId}] no project members found`);
return;
}
+39 -159
View File
@@ -1,17 +1,8 @@
import { ForbiddenError, subject } from "@casl/ability";
import {
SecretEncryptionAlgo,
SecretKeyEncoding,
SecretsSchema,
SecretType,
TableName
} from "@app/db/schemas";
import { SecretEncryptionAlgo, SecretKeyEncoding, SecretsSchema, SecretType, TableName } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import {
ProjectPermissionActions,
ProjectPermissionSub
} from "@app/ee/services/permission/project-permission";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { TSecretSnapshotServiceFactory } from "@app/ee/services/secret-snapshot/secret-snapshot-service";
import { getConfig } from "@app/lib/config/env";
import { buildSecretBlindIndexFromName, encryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
@@ -58,17 +49,11 @@ type TSecretServiceFactoryDep = {
secretDAL: TSecretDALFactory;
secretTagDAL: TSecretTagDALFactory;
secretVersionDAL: TSecretVersionDALFactory;
folderDAL: Pick<
TSecretFolderDALFactory,
"findBySecretPath" | "updateById" | "findById" | "findByManySecretPath"
>;
folderDAL: Pick<TSecretFolderDALFactory, "findBySecretPath" | "updateById" | "findById" | "findByManySecretPath">;
secretBlindIndexDAL: TSecretBlindIndexDALFactory;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
snapshotService: Pick<TSecretSnapshotServiceFactory, "performSnapshot">;
secretQueueService: Pick<
TSecretQueueFactory,
"syncSecrets" | "handleSecretReminder" | "removeSecretReminder"
>;
secretQueueService: Pick<TSecretQueueFactory, "syncSecrets" | "handleSecretReminder" | "removeSecretReminder">;
projectBotService: Pick<TProjectBotServiceFactory, "getBotKey">;
secretImportDAL: Pick<TSecretImportDALFactory, "find">;
secretVersionTagDAL: Pick<TSecretVersionTagDALFactory, "insertMany">;
@@ -93,8 +78,7 @@ export const secretServiceFactory = ({
const appCfg = getConfig();
const secretBlindIndexDoc = await secretBlindIndexDAL.findOne({ projectId });
if (!secretBlindIndexDoc)
throw new BadRequestError({ message: "Blind index not found", name: "Create secret" });
if (!secretBlindIndexDoc) throw new BadRequestError({ message: "Blind index not found", name: "Create secret" });
const secretBlindIndex = await buildSecretBlindIndexFromName({
secretName,
@@ -116,15 +100,11 @@ export const secretServiceFactory = ({
inputSecrets.map(({ tags, ...el }) => ({ ...el, folderId })),
tx
);
const newSecretGroupByBlindIndex = groupBy(
newSecrets,
(item) => item.secretBlindIndex as string
);
const newSecretGroupByBlindIndex = groupBy(newSecrets, (item) => item.secretBlindIndex as string);
const newSecretTags = inputSecrets.flatMap(({ tags: secretTags = [], secretBlindIndex }) =>
secretTags.map((tag) => ({
[`${TableName.SecretTag}Id` as const]: tag,
[`${TableName.Secret}Id` as const]:
newSecretGroupByBlindIndex[secretBlindIndex as string][0].id
[`${TableName.Secret}Id` as const]: newSecretGroupByBlindIndex[secretBlindIndex as string][0].id
}))
);
const secretVersions = await secretVersionDAL.insertMany(
@@ -148,12 +128,7 @@ export const secretServiceFactory = ({
return newSecrets.map((secret) => ({ ...secret, _id: secret.id }));
};
const fnSecretBulkUpdate = async ({
tx,
inputSecrets,
folderId,
projectId
}: TFnSecretBulkUpdate) => {
const fnSecretBulkUpdate = async ({ tx, inputSecrets, folderId, projectId }: TFnSecretBulkUpdate) => {
const newSecrets = await secretDAL.bulkUpdate(
inputSecrets.map(({ filter, data: { tags, ...data } }) => ({
filter: { ...filter, folderId },
@@ -197,12 +172,7 @@ export const secretServiceFactory = ({
return newSecrets.map((secret) => ({ ...secret, _id: secret.id }));
};
const fnSecretBulkDelete = async ({
folderId,
inputSecrets,
tx,
actorId
}: TFnSecretBulkDelete) => {
const fnSecretBulkDelete = async ({ folderId, inputSecrets, tx, actorId }: TFnSecretBulkDelete) => {
const deletedSecrets = await secretDAL.deleteMany(
inputSecrets.map(({ type, secretBlindIndex }) => ({
blindIndex: secretBlindIndex,
@@ -245,9 +215,7 @@ export const secretServiceFactory = ({
}: TFnSecretBlindIndexCheck) => {
const blindIndex2KeyName: Record<string, string> = {}; // used at audit log point
const keyName2BlindIndex = await Promise.all(
inputSecrets.map(({ secretName }) =>
generateSecretBlindIndexBySalt(secretName, blindIndexCfg)
)
inputSecrets.map(({ secretName }) => generateSecretBlindIndexBySalt(secretName, blindIndexCfg))
).then((blindIndexes) =>
blindIndexes.reduce<Record<string, string>>((prev, curr, i) => {
// eslint-disable-next-line
@@ -282,11 +250,7 @@ export const secretServiceFactory = ({
// this is used when secret blind index already exist
// mainly for secret approval
const fnSecretBlindIndexCheckV2 = async ({
inputSecrets,
folderId,
userId
}: TFnSecretBlindIndexCheckV2) => {
const fnSecretBlindIndexCheckV2 = async ({ inputSecrets, folderId, userId }: TFnSecretBlindIndexCheckV2) => {
if (inputSecrets.some(({ type }) => type === SecretType.Personal) && !userId) {
throw new BadRequestError({ message: "Missing user id for personal secret" });
}
@@ -303,14 +267,7 @@ export const secretServiceFactory = ({
return { secsGroupedByBlindIndex, secrets };
};
const createSecret = async ({
path,
actor,
actorId,
environment,
projectId,
...inputSecret
}: TCreateSecretDTO) => {
const createSecret = async ({ path, actor, actorId, environment, projectId, ...inputSecret }: TCreateSecretDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
@@ -322,8 +279,7 @@ export const secretServiceFactory = ({
const folderId = folder.id;
const blindIndexCfg = await secretBlindIndexDAL.findOne({ projectId });
if (!blindIndexCfg)
throw new BadRequestError({ message: "Blind index not found", name: "CreateSecret" });
if (!blindIndexCfg) throw new BadRequestError({ message: "Blind index not found", name: "CreateSecret" });
if (ActorType.USER !== actor && inputSecret.type === SecretType.Personal) {
throw new BadRequestError({ message: "Must be user to create personal secret" });
@@ -352,11 +308,8 @@ export const secretServiceFactory = ({
// validate tags
// fetch all tags and if not same count throw error meaning one was invalid tags
const tags = inputSecret.tags
? await secretTagDAL.findManyTagsById(projectId, inputSecret.tags)
: [];
if ((inputSecret.tags || []).length !== tags.length)
throw new BadRequestError({ message: "Tag not found" });
const tags = inputSecret.tags ? await secretTagDAL.findManyTagsById(projectId, inputSecret.tags) : [];
if ((inputSecret.tags || []).length !== tags.length) throw new BadRequestError({ message: "Tag not found" });
const { secretName, type, ...el } = inputSecret;
const secret = await secretDAL.transaction((tx) =>
@@ -384,14 +337,7 @@ export const secretServiceFactory = ({
return { ...secret[0], environment, workspace: projectId, tags };
};
const updateSecret = async ({
path,
actor,
actorId,
environment,
projectId,
...inputSecret
}: TUpdateSecretDTO) => {
const updateSecret = async ({ path, actor, actorId, environment, projectId, ...inputSecret }: TUpdateSecretDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
@@ -403,8 +349,7 @@ export const secretServiceFactory = ({
const folderId = folder.id;
const blindIndexCfg = await secretBlindIndexDAL.findOne({ projectId });
if (!blindIndexCfg)
throw new BadRequestError({ message: "Blind index not found", name: "CreateSecret" });
if (!blindIndexCfg) throw new BadRequestError({ message: "Blind index not found", name: "CreateSecret" });
if (ActorType.USER !== actor && inputSecret.type === SecretType.Personal) {
throw new BadRequestError({ message: "Must be user to create personal secret" });
@@ -441,11 +386,8 @@ export const secretServiceFactory = ({
projectId
});
const tags = inputSecret.tags
? await secretTagDAL.findManyTagsById(projectId, inputSecret.tags)
: [];
if ((inputSecret.tags || []).length !== tags.length)
throw new BadRequestError({ message: "Tag not found" });
const tags = inputSecret.tags ? await secretTagDAL.findManyTagsById(projectId, inputSecret.tags) : [];
if ((inputSecret.tags || []).length !== tags.length) throw new BadRequestError({ message: "Tag not found" });
const { secretName, ...el } = inputSecret;
const updatedSecret = await secretDAL.transaction(async (tx) =>
@@ -487,14 +429,7 @@ export const secretServiceFactory = ({
return { ...updatedSecret[0], workspace: projectId, environment };
};
const deleteSecret = async ({
path,
actor,
actorId,
environment,
projectId,
...inputSecret
}: TDeleteSecretDTO) => {
const deleteSecret = async ({ path, actor, actorId, environment, projectId, ...inputSecret }: TDeleteSecretDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
@@ -506,8 +441,7 @@ export const secretServiceFactory = ({
const folderId = folder.id;
const blindIndexCfg = await secretBlindIndexDAL.findOne({ projectId });
if (!blindIndexCfg)
throw new BadRequestError({ message: "Blind index not found", name: "CreateSecret" });
if (!blindIndexCfg) throw new BadRequestError({ message: "Blind index not found", name: "CreateSecret" });
if (ActorType.USER !== actor && inputSecret.type === SecretType.Personal) {
throw new BadRequestError({ message: "Must be user to create personal secret" });
@@ -542,14 +476,7 @@ export const secretServiceFactory = ({
return { ...deletedSecret[0], _id: deletedSecret[0].id, workspace: projectId, environment };
};
const getSecrets = async ({
actorId,
path,
environment,
projectId,
actor,
includeImports
}: TGetSecretsDTO) => {
const getSecrets = async ({ actorId, path, environment, projectId, actor, includeImports }: TGetSecretsDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
@@ -683,8 +610,7 @@ export const secretServiceFactory = ({
const folderId = folder.id;
const blindIndexCfg = await secretBlindIndexDAL.findOne({ projectId });
if (!blindIndexCfg)
throw new BadRequestError({ message: "Blind index not found", name: "Update secret" });
if (!blindIndexCfg) throw new BadRequestError({ message: "Blind index not found", name: "Update secret" });
const { keyName2BlindIndex } = await fnSecretBlindIndexCheck({
inputSecrets,
@@ -738,8 +664,7 @@ export const secretServiceFactory = ({
const folderId = folder.id;
const blindIndexCfg = await secretBlindIndexDAL.findOne({ projectId });
if (!blindIndexCfg)
throw new BadRequestError({ message: "Blind index not found", name: "Update secret" });
if (!blindIndexCfg) throw new BadRequestError({ message: "Blind index not found", name: "Update secret" });
const { keyName2BlindIndex } = await fnSecretBlindIndexCheck({
inputSecrets,
@@ -809,8 +734,7 @@ export const secretServiceFactory = ({
const folderId = folder.id;
const blindIndexCfg = await secretBlindIndexDAL.findOne({ projectId });
if (!blindIndexCfg)
throw new BadRequestError({ message: "Blind index not found", name: "Update secret" });
if (!blindIndexCfg) throw new BadRequestError({ message: "Blind index not found", name: "Update secret" });
const { keyName2BlindIndex } = await fnSecretBlindIndexCheck({
inputSecrets,
@@ -838,46 +762,22 @@ export const secretServiceFactory = ({
return secretsDeleted;
};
const listSecretVersionsBySecretId = async ({
actorId,
actor,
limit,
offset,
secretId
}: TListSecretVersionDTO) => {
const listSecretVersionsBySecretId = async ({ actorId, actor, limit, offset, secretId }: TListSecretVersionDTO) => {
const secret = await secretDAL.findById(secretId);
if (!secret) throw new BadRequestError({ message: "Failed to find secret" });
const folder = await folderDAL.findById(secret.folderId);
if (!folder) throw new BadRequestError({ message: "Folder not found" });
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
folder.projectId
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.SecretRollback
);
const { permission } = await permissionService.getProjectPermission(actor, actorId, folder.projectId);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
const secretVersions = await secretVersionDAL.find(
{ secretId },
{ limit, offset, sort: [["createdAt", "desc"]] }
);
const secretVersions = await secretVersionDAL.find({ secretId }, { limit, offset, sort: [["createdAt", "desc"]] });
return secretVersions;
};
const getSecretsRaw = async ({
projectId,
path,
actor,
actorId,
environment,
includeImports
}: TGetSecretsRawDTO) => {
const getSecretsRaw = async ({ projectId, path, actor, actorId, environment, includeImports }: TGetSecretsRawDTO) => {
const botKey = await projectBotService.getBotKey(projectId);
if (!botKey)
throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" });
if (!botKey) throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" });
const { secrets, imports } = await getSecrets({
actorId,
@@ -911,8 +811,7 @@ export const secretServiceFactory = ({
version
}: TGetASecretRawDTO) => {
const botKey = await projectBotService.getBotKey(projectId);
if (!botKey)
throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" });
if (!botKey) throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" });
const secret = await getSecretByName({
actorId,
@@ -941,8 +840,7 @@ export const secretServiceFactory = ({
skipMultilineEncoding
}: TCreateSecretRawDTO) => {
const botKey = await projectBotService.getBotKey(projectId);
if (!botKey)
throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" });
if (!botKey) throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" });
const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8(secretName, botKey);
const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8(secretValue || "", botKey);
@@ -986,8 +884,7 @@ export const secretServiceFactory = ({
skipMultilineEncoding
}: TUpdateSecretRawDTO) => {
const botKey = await projectBotService.getBotKey(projectId);
if (!botKey)
throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" });
if (!botKey) throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" });
const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8(secretValue || "", botKey);
@@ -1021,8 +918,7 @@ export const secretServiceFactory = ({
secretPath
}: TDeleteSecretRawDTO) => {
const botKey = await projectBotService.getBotKey(projectId);
if (!botKey)
throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" });
if (!botKey) throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" });
const secret = await deleteSecret({
secretName,
@@ -1040,33 +936,17 @@ export const secretServiceFactory = ({
return decryptSecretRaw(secret, botKey);
};
const getSecretVersions = async ({
actorId,
actor,
limit = 20,
offset = 0,
secretId
}: TGetSecretVersionsDTO) => {
const getSecretVersions = async ({ actorId, actor, limit = 20, offset = 0, secretId }: TGetSecretVersionsDTO) => {
const secret = await secretDAL.findById(secretId);
if (!secret) throw new BadRequestError({ message: "Failed to find secret" });
const folder = await folderDAL.findById(secret.folderId);
if (!folder) throw new BadRequestError({ message: "Failed to find secret" });
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
folder.projectId
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.SecretRollback
);
const { permission } = await permissionService.getProjectPermission(actor, actorId, folder.projectId);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
const secretVersions = await secretVersionDAL.find(
{ secretId },
{ offset, limit, sort: [["createdAt", "desc"]] }
);
const secretVersions = await secretVersionDAL.find({ secretId }, { offset, limit, sort: [["createdAt", "desc"]] });
return secretVersions;
};
+2 -11
View File
@@ -1,20 +1,11 @@
import { Knex } from "knex";
import {
SecretType,
TSecretBlindIndexes,
TSecrets,
TSecretsInsert,
TSecretsUpdate
} from "@app/db/schemas";
import { SecretType, TSecretBlindIndexes, TSecrets, TSecretsInsert, TSecretsUpdate } from "@app/db/schemas";
import { TProjectPermission } from "@app/lib/types";
type TPartialSecret = Pick<TSecrets, "id" | "secretReminderRepeatDays" | "secretReminderNote">;
type TPartialInputSecret = Pick<
TSecrets,
"type" | "secretReminderNote" | "secretReminderRepeatDays" | "id"
>;
type TPartialInputSecret = Pick<TSecrets, "type" | "secretReminderNote" | "secretReminderRepeatDays" | "id">;
export type TCreateSecretDTO = {
secretName: string;
@@ -38,17 +38,11 @@ export const secretVersionDALFactory = (db: TDbClient) => {
const findLatestVersionMany = async (folderId: string, secretIds: string[], tx?: Knex) => {
try {
const docs: Array<TSecretVersions & { max: number }> = await (tx || db)(
TableName.SecretVersion
)
const docs: Array<TSecretVersions & { max: number }> = await (tx || db)(TableName.SecretVersion)
.where("folderId", folderId)
.whereIn(`${TableName.SecretVersion}.secretId`, secretIds)
.join(
(tx || db)(TableName.SecretVersion)
.groupBy("secretId")
.max("version")
.select("secretId")
.as("latestVersion"),
(tx || db)(TableName.SecretVersion).groupBy("secretId").max("version").select("secretId").as("latestVersion"),
(bd) => {
bd.on(`${TableName.SecretVersion}.secretId`, "latestVersion.secretId").andOn(
`${TableName.SecretVersion}.version`,
@@ -4,10 +4,7 @@ import { ForbiddenError, subject } from "@casl/ability";
import bcrypt from "bcrypt";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import {
ProjectPermissionActions,
ProjectPermissionSub
} from "@app/ee/services/permission/project-permission";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
@@ -50,10 +47,7 @@ export const serviceTokenServiceFactory = ({
encryptedKey
}: TCreateServiceTokenDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.ServiceTokens
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.ServiceTokens);
scopes.forEach(({ environment, secretPath }) => {
ForbiddenError.from(permission).throwUnlessCan(
@@ -67,8 +61,7 @@ export const serviceTokenServiceFactory = ({
// validates env
const scopeEnvs = [...new Set(scopes.map(({ environment }) => environment))];
const inputEnvs = await projectEnvDAL.findBySlugs(projectId, scopeEnvs);
if (inputEnvs.length !== scopeEnvs.length)
throw new BadRequestError({ message: "Environment not found" });
if (inputEnvs.length !== scopeEnvs.length) throw new BadRequestError({ message: "Environment not found" });
const secret = crypto.randomBytes(16).toString("hex");
const secretHash = await bcrypt.hash(secret, appCfg.SALT_ROUNDS);
@@ -101,23 +94,15 @@ export const serviceTokenServiceFactory = ({
const serviceToken = await serviceTokenDAL.findById(id);
if (!serviceToken) throw new BadRequestError({ message: "Token not found" });
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
serviceToken.projectId
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.ServiceTokens
);
const { permission } = await permissionService.getProjectPermission(actor, actorId, serviceToken.projectId);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.ServiceTokens);
const deletedServiceToken = await serviceTokenDAL.deleteById(id);
return deletedServiceToken;
};
const getServiceToken = async ({ actor, actorId }: TGetServiceTokenInfoDTO) => {
if (actor !== ActorType.SERVICE)
throw new BadRequestError({ message: "Service token not found" });
if (actor !== ActorType.SERVICE) throw new BadRequestError({ message: "Service token not found" });
const serviceToken = await serviceTokenDAL.findById(actorId);
if (!serviceToken) throw new BadRequestError({ message: "Token not found" });
@@ -128,16 +113,9 @@ export const serviceTokenServiceFactory = ({
return { serviceToken, user: serviceTokenUser };
};
const getProjectServiceTokens = async ({
actorId,
actor,
projectId
}: TProjectServiceTokensDTO) => {
const getProjectServiceTokens = async ({ actorId, actor, projectId }: TProjectServiceTokensDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.ServiceTokens
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.ServiceTokens);
const tokens = await serviceTokenDAL.find({ projectId });
return tokens;
@@ -19,8 +19,7 @@ export type TSuperAdminServiceFactory = ReturnType<typeof superAdminServiceFacto
let serverCfg: Readonly<TSuperAdmin>;
export const getServerCfg = () => {
if (!serverCfg)
throw new BadRequestError({ name: "Get server cfg", message: "Server cfg not initialized" });
if (!serverCfg) throw new BadRequestError({ name: "Get server cfg", message: "Server cfg not initialized" });
return serverCfg;
};
@@ -64,8 +63,7 @@ export const superAdminServiceFactory = ({
userAgent
}: TAdminSignUpDTO) => {
const existingUser = await userDAL.findOne({ email });
if (existingUser)
throw new BadRequestError({ name: "Admin sign up", message: "User already exist" });
if (existingUser) throw new BadRequestError({ name: "Admin sign up", message: "User already exist" });
const userInfo = await userDAL.transaction(async (tx) => {
const newUser = await userDAL.create(
+4 -19
View File
@@ -24,11 +24,7 @@ export const userDALFactory = (db: TDbClient) => {
try {
return await db(TableName.Users)
.where({ email })
.join(
TableName.UserEncryptionKey,
`${TableName.Users}.id`,
`${TableName.UserEncryptionKey}.userId`
)
.join(TableName.UserEncryptionKey, `${TableName.Users}.id`, `${TableName.UserEncryptionKey}.userId`)
.first();
} catch (error) {
throw new DatabaseError({ error, name: "Find user enc by email" });
@@ -39,11 +35,7 @@ export const userDALFactory = (db: TDbClient) => {
try {
const user = await db(TableName.Users)
.where(`${TableName.Users}.id`, userId)
.join(
TableName.UserEncryptionKey,
`${TableName.Users}.id`,
`${TableName.UserEncryptionKey}.userId`
)
.join(TableName.UserEncryptionKey, `${TableName.Users}.id`, `${TableName.UserEncryptionKey}.userId`)
.first();
if (user?.id) {
// change to user id
@@ -64,11 +56,7 @@ export const userDALFactory = (db: TDbClient) => {
}
};
const updateUserEncryptionByUserId = async (
userId: string,
data: TUserEncryptionKeysUpdate,
tx?: Knex
) => {
const updateUserEncryptionByUserId = async (userId: string, data: TUserEncryptionKeysUpdate, tx?: Knex) => {
try {
const [userEnc] = await (tx || db)(TableName.UserEncryptionKey)
.where({ userId })
@@ -86,10 +74,7 @@ export const userDALFactory = (db: TDbClient) => {
tx?: Knex
) => {
try {
const [userEnc] = await (tx
? tx(TableName.UserEncryptionKey)
: db(TableName.UserEncryptionKey)
)
const [userEnc] = await (tx ? tx(TableName.UserEncryptionKey) : db(TableName.UserEncryptionKey))
// if user insert make sure to pass all required data
.insert({ userId, ...data } as TUserEncryptionKeys)
.onConflict("userId")
+1 -3
View File
@@ -31,9 +31,7 @@ export const userServiceFactory = ({ userDAL }: TUserServiceFactoryDep) => {
if (!user) throw new BadRequestError({ name: "Update auth methods" });
const hasSamlEnabled = user?.authMethods?.some((method) =>
[AuthMethod.OKTA_SAML, AuthMethod.AZURE_SAML, AuthMethod.JUMPCLOUD_SAML].includes(
method as AuthMethod
)
[AuthMethod.OKTA_SAML, AuthMethod.AZURE_SAML, AuthMethod.JUMPCLOUD_SAML].includes(method as AuthMethod)
);
if (hasSamlEnabled)
throw new BadRequestError({
+2 -9
View File
@@ -63,12 +63,7 @@ export const webhookDALFactory = (db: TDbClient) => {
}
};
const findAllWebhooks = async (
projectId: string,
environment?: string,
secretPath?: string,
tx?: Knex
) => {
const findAllWebhooks = async (projectId: string, environment?: string, secretPath?: string, tx?: Knex) => {
try {
const webhooks = await (tx || db)(TableName.Webhook)
.where(`${TableName.Environment}.projectId`, projectId)
@@ -103,9 +98,7 @@ export const webhookDALFactory = (db: TDbClient) => {
const bulkUpdate = async (data: Array<TWebhooksUpdate & { id: string }>, tx?: Knex) => {
try {
const queries = data.map(({ id, ...el }) =>
(tx || db)(TableName.Webhook).where({ id }).update(el)
);
const queries = data.map(({ id, ...el }) => (tx || db)(TableName.Webhook).where({ id }).update(el));
const docs = await Promise.all(queries);
return docs;
} catch (error) {
+2 -8
View File
@@ -44,10 +44,7 @@ export const triggerWebhookRequest = async (
});
}
if (secretKey) {
const webhookSign = crypto
.createHmac("sha256", secretKey)
.update(JSON.stringify(payload))
.digest("hex");
const webhookSign = crypto.createHmac("sha256", secretKey).update(JSON.stringify(payload)).digest("hex");
headers["x-infisical-signature"] = `t=${payload.timestamp};${webhookSign}`;
}
}
@@ -98,10 +95,7 @@ export const fnTriggerWebhook = async ({
logger.info("Secret webhook job started", { environment, secretPath, projectId });
const webhooksTriggered = await Promise.allSettled(
toBeTriggeredHooks.map((hook) =>
triggerWebhookRequest(
hook,
getWebhookPayload("secrets.modified", projectId, environment, secretPath)
)
triggerWebhookRequest(hook, getWebhookPayload("secrets.modified", projectId, environment, secretPath))
)
);
// filter hooks by status
+12 -55
View File
@@ -2,10 +2,7 @@ import { ForbiddenError } from "@casl/ability";
import { SecretEncryptionAlgo, SecretKeyEncoding, TWebhooksInsert } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import {
ProjectPermissionActions,
ProjectPermissionSub
} from "@app/ee/services/permission/project-permission";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { getConfig } from "@app/lib/config/env";
import { encryptSymmetric, encryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
import { BadRequestError } from "@app/lib/errors";
@@ -29,11 +26,7 @@ type TWebhookServiceFactoryDep = {
export type TWebhookServiceFactory = ReturnType<typeof webhookServiceFactory>;
export const webhookServiceFactory = ({
webhookDAL,
projectEnvDAL,
permissionService
}: TWebhookServiceFactoryDep) => {
export const webhookServiceFactory = ({ webhookDAL, projectEnvDAL, permissionService }: TWebhookServiceFactoryDep) => {
const createWebhook = async ({
actor,
actorId,
@@ -44,10 +37,7 @@ export const webhookServiceFactory = ({
webhookSecretKey
}: TCreateWebhookDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.Webhooks
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Webhooks);
const env = await projectEnvDAL.findOne({ projectId, slug: environment });
if (!env) throw new BadRequestError({ message: "Env not found" });
@@ -69,10 +59,7 @@ export const webhookServiceFactory = ({
insertDoc.algorithm = SecretEncryptionAlgo.AES_256_GCM;
insertDoc.keyEncoding = SecretKeyEncoding.BASE64;
} else if (encryptionKey) {
const { ciphertext, iv, tag } = encryptSymmetric128BitHexKeyUTF8(
webhookSecretKey,
encryptionKey
);
const { ciphertext, iv, tag } = encryptSymmetric128BitHexKeyUTF8(webhookSecretKey, encryptionKey);
insertDoc.encryptedSecretKey = ciphertext;
insertDoc.iv = iv;
insertDoc.tag = tag;
@@ -89,15 +76,8 @@ export const webhookServiceFactory = ({
const webhook = await webhookDAL.findById(id);
if (!webhook) throw new BadRequestError({ message: "Webhook not found" });
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
webhook.projectId
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
ProjectPermissionSub.Webhooks
);
const { permission } = await permissionService.getProjectPermission(actor, actorId, webhook.projectId);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Webhooks);
const updatedWebhook = await webhookDAL.updateById(id, { isDisabled });
return { ...webhook, ...updatedWebhook };
@@ -107,15 +87,8 @@ export const webhookServiceFactory = ({
const webhook = await webhookDAL.findById(id);
if (!webhook) throw new BadRequestError({ message: "Webhook not found" });
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
webhook.projectId
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.Webhooks
);
const { permission } = await permissionService.getProjectPermission(actor, actorId, webhook.projectId);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Webhooks);
const deletedWebhook = await webhookDAL.deleteById(id);
return { ...webhook, ...deletedWebhook };
@@ -125,15 +98,8 @@ export const webhookServiceFactory = ({
const webhook = await webhookDAL.findById(id);
if (!webhook) throw new BadRequestError({ message: "Webhook not found" });
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
webhook.projectId
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Webhooks
);
const { permission } = await permissionService.getProjectPermission(actor, actorId, webhook.projectId);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks);
let webhookError: string | undefined;
try {
@@ -152,18 +118,9 @@ export const webhookServiceFactory = ({
return { ...webhook, ...updatedWebhook };
};
const listWebhooks = async ({
actorId,
actor,
projectId,
secretPath,
environment
}: TListWebhookDTO) => {
const listWebhooks = async ({ actorId, actor, projectId, secretPath, environment }: TListWebhookDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Webhooks
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks);
return webhookDAL.findAllWebhooks(projectId, environment, secretPath);
};