Compare commits

...

16 Commits

Author SHA1 Message Date
BlackMagiq
9bba9ee9b1 Merge pull request #1342 from Infisical/patch-integration-path-sync
Remove async from isSamePath check in integration sync
2024-01-29 16:42:25 +07:00
Tuan Dang
74ac75b878 Remove async from isSamePath check in integration sync 2024-01-29 16:37:31 +07:00
Maidul Islam
8478fea52a Merge pull request #1341 from akhilmhdh/feat/add-cloudwatch
feat: added cloudwatch support and removed parsed secret blindindex
2024-01-29 04:25:42 -05:00
Akhil Mohan
703ff2c12b feat: added cloudwatch support and removed parsed secret blindindex nullable 2024-01-29 14:52:20 +05:30
BlackMagiq
6b4aee2a44 Merge pull request #1340 from Infisical/vercel-issue
Patch Vercel integration for team accounts
2024-01-29 09:25:18 +07:00
Tuan Dang
5593464287 Patch Vercel integration missing teamId not being passed in from frontend 2024-01-29 09:20:39 +07:00
Maidul Islam
7d556cb09b Merge pull request #1308 from Tchoupinax/patch-1
Update the path where managing service tokens
2024-01-28 17:00:33 -05:00
Maidul Islam
dcb6f5891f add license service timeout 2024-01-28 15:32:37 -05:00
Maidul Islam
1254215b51 Merge pull request #1338 from akhilmhdh/feat/blind-index-fix
feat: changed blind index banner for everyone
2024-01-28 14:37:31 -05:00
Maidul Islam
a6ead9396c nit: small patch to request error status 2024-01-28 14:36:57 -05:00
Maidul Islam
d33ef9e4e1 jump cloud patch 2024-01-28 13:53:24 -05:00
Akhil Mohan
4e20735f98 feat: resolved trailing slash in secret paths 2024-01-29 00:21:42 +05:30
Akhil Mohan
f010a3a932 feat: changed blind index banner for everyone 2024-01-28 23:16:04 +05:30
Maidul Islam
bbf2634e73 prepend sso with site url 2024-01-27 20:20:02 -05:00
Maidul Islam
1980f802fa update rate limits 2024-01-27 19:05:17 -05:00
Tchoupinax
e18abc6e22 Update the path where managing service tokens
I did not find anymore where to manage token and reading the doc did not help me. I found the MR which changes the section of the link and so I update the doc according it
2024-01-17 22:15:07 +01:00
41 changed files with 1015 additions and 331 deletions

1017
backend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -81,6 +81,7 @@
"@node-saml/passport-saml": "^4.0.4",
"@octokit/rest": "^20.0.2",
"@octokit/webhooks-types": "^7.3.1",
"@serdnam/pino-cloudwatch-transport": "^1.0.4",
"@sindresorhus/slugify": "^2.2.1",
"@ucast/mongo2js": "^1.3.4",
"ajv": "^8.12.0",

View File

@@ -10,8 +10,8 @@ import { TImmutableDBKeys } from "./models";
export const SecretVersionsSchema = z.object({
id: z.string().uuid(),
version: z.number().default(1),
type: z.string().default('shared'),
secretBlindIndex: z.string(),
type: z.string().default("shared"),
secretBlindIndex: z.string().nullable().optional(),
secretKeyCiphertext: z.string(),
secretKeyIV: z.string(),
secretKeyTag: z.string(),
@@ -24,15 +24,15 @@ export const SecretVersionsSchema = z.object({
secretReminderNote: z.string().nullable().optional(),
secretReminderRepeatDays: z.number().nullable().optional(),
skipMultilineEncoding: z.boolean().default(false).nullable().optional(),
algorithm: z.string().default('aes-256-gcm'),
keyEncoding: z.string().default('utf8'),
algorithm: z.string().default("aes-256-gcm"),
keyEncoding: z.string().default("utf8"),
metadata: z.unknown().nullable().optional(),
envId: z.string().uuid().nullable().optional(),
secretId: z.string().uuid(),
folderId: z.string().uuid(),
userId: z.string().uuid().nullable().optional(),
createdAt: z.date(),
updatedAt: z.date(),
updatedAt: z.date()
});
export type TSecretVersions = z.infer<typeof SecretVersionsSchema>;

View File

@@ -10,8 +10,8 @@ import { TImmutableDBKeys } from "./models";
export const SecretsSchema = z.object({
id: z.string().uuid(),
version: z.number().default(1),
type: z.string().default('shared'),
secretBlindIndex: z.string(),
type: z.string().default("shared"),
secretBlindIndex: z.string().nullable().optional(),
secretKeyCiphertext: z.string(),
secretKeyIV: z.string(),
secretKeyTag: z.string(),
@@ -24,13 +24,13 @@ export const SecretsSchema = z.object({
secretReminderNote: z.string().nullable().optional(),
secretReminderRepeatDays: z.number().nullable().optional(),
skipMultilineEncoding: z.boolean().default(false).nullable().optional(),
algorithm: z.string().default('aes-256-gcm'),
keyEncoding: z.string().default('utf8'),
algorithm: z.string().default("aes-256-gcm"),
keyEncoding: z.string().default("utf8"),
metadata: z.unknown().nullable().optional(),
userId: z.string().uuid().nullable().optional(),
folderId: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date(),
updatedAt: z.date()
});
export type TSecrets = z.infer<typeof SecretsSchema>;

View File

@@ -2,6 +2,7 @@ import { z } from "zod";
import { AuditLogsSchema, SecretSnapshotsSchema } from "@app/db/schemas";
import { EventType, UserAgentType } from "@app/ee/services/audit-log/audit-log-types";
import { removeTrailingSlash } from "@app/lib/fn";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
@@ -15,7 +16,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
}),
querystring: z.object({
environment: z.string().trim(),
path: z.string().trim().default("/"),
path: z.string().trim().default("/").transform(removeTrailingSlash),
offset: z.coerce.number().default(0),
limit: z.coerce.number().default(20)
}),
@@ -46,7 +47,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
}),
querystring: z.object({
environment: z.string().trim(),
path: z.string().trim().default("/")
path: z.string().trim().default("/").transform(removeTrailingSlash)
}),
response: {
200: z.object({

View File

@@ -1,6 +1,7 @@
import { z } from "zod";
import { SecretRotationOutputsSchema, SecretRotationsSchema, SecretsSchema } from "@app/db/schemas";
import { removeTrailingSlash } from "@app/lib/fn";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
@@ -11,7 +12,7 @@ export const registerSecretRotationRouter = async (server: FastifyZodProvider) =
schema: {
body: z.object({
workspaceId: z.string().trim(),
secretPath: z.string().trim(),
secretPath: z.string().trim().transform(removeTrailingSlash),
environment: z.string().trim(),
interval: z.number().min(1),
provider: z.string().trim(),

View File

@@ -39,7 +39,7 @@ export const setupLicenceRequestWithStore = (
let token: string;
const licenceReq = axios.create({
baseURL,
// timeout: 60 * 1000,
timeout: 35 * 1000,
// signal: AbortSignal.timeout(60 * 1000)
});

View File

@@ -226,7 +226,24 @@ export const samlConfigServiceFactory = ({
ssoConfig = await samlConfigDAL.findOne({ orgId: dto.orgId });
if (!ssoConfig) return;
} else if (dto.type === "ssoId") {
ssoConfig = await samlConfigDAL.findById(dto.id);
// TODO:
// We made this change because saml config ids were not moved over during the migration
// This will patch this issue.
// Remove in the future
const UUIDToMongoId: Record<string, string> = {
"64c81ff7905fadcfead01e9a": "0978bcbe-8f94-4d95-8600-009787262613",
"652d4777c74d008c85c8bed5": "42044bf5-119e-443e-a51b-0308ac7e45ea",
"6527df39771217236f8721f6": "6311ec4b-d692-4422-b52a-337f719ae6b0",
"650374a561d12cd3d835aeb8": "6453516c-930d-4ff0-ad3b-496ba6eb80ca",
"655d67d10a0f4d307c8b1536": "73b9f1b1-f946-4f18-9a2d-310f157f7df5",
"64f23239a5d4ed17f1e544c4": "9256337f-e3da-43d7-8266-39c9276e8426",
"65348e49db355e6e4782571f": "b8a227c7-843e-410e-8982-b4976a599b69",
"657a219fc8a80c2eff97eb38": "fcab1573-ae7f-4fcf-9645-646207acf035"
};
const id = UUIDToMongoId[dto.id] ?? dto.id
ssoConfig = await samlConfigDAL.findById(id);
}
if (!ssoConfig) throw new BadRequestError({ message: "Failed to find organization SSO data" });

View File

@@ -499,7 +499,10 @@ export const secretApprovalRequestServiceFactory = ({
blindIndexCfg
});
const secsGroupedByBlindIndex = groupBy(secretsToBeUpdated, (el) => el.secretBlindIndex);
const secsGroupedByBlindIndex = groupBy(
secretsToBeUpdated,
(el) => el.secretBlindIndex as string
);
const updatedSecretIds = updatedSecrets.map(
(el) => secsGroupedByBlindIndex[keyName2BlindIndex[el.secretName]][0].id
);
@@ -540,7 +543,11 @@ export const secretApprovalRequestServiceFactory = ({
isNew: false,
blindIndexCfg
});
const secretsGroupedByBlindIndex = groupBy(secrets, (i) => i.secretBlindIndex);
const secretsGroupedByBlindIndex = groupBy(secrets, (i) => {
if (!i.secretBlindIndex)
throw new BadRequestError({ message: "Missing secret blind index" });
return i.secretBlindIndex;
});
const deletedSecretIds = deletedSecrets.map(
(el) => secretsGroupedByBlindIndex[keyName2BlindIndex[el.secretName]][0].id
);
@@ -551,9 +558,12 @@ export const secretApprovalRequestServiceFactory = ({
commits.push(
...deletedSecrets.map((el) => {
const secretId = secretsGroupedByBlindIndex[keyName2BlindIndex[el.secretName]][0].id;
if (!latestSecretVersions[secretId].secretBlindIndex)
throw new BadRequestError({ message: "Failed to find secret blind index" });
return {
op: CommitType.Delete as const,
...latestSecretVersions[secretId],
secretBlindIndex: latestSecretVersions[secretId].secretBlindIndex as string,
secret: secretId,
secretVersion: latestSecretVersions[secretId].id
};

View File

@@ -34,6 +34,7 @@ import {
TSecretRotationDbFn,
TSecretRotationEncData
} from "./secret-rotation-queue-types";
import { BadRequestError } from "@app/lib/errors";
export type TSecretRotationQueueFactory = ReturnType<typeof secretRotationQueueFactory>;
@@ -259,10 +260,14 @@ export const secretRotationQueueFactory = ({
tx
);
await secretVersionDAL.insertMany(
updatedSecrets.map(({ id, updatedAt, createdAt, ...el }) => ({
updatedSecrets.map(({ id, updatedAt, createdAt, ...el }) => {
if (!el.secretBlindIndex) throw new BadRequestError({ message: "Missing blind index" });
return {
...el,
secretId: id
})),
secretId: id,
secretBlindIndex: el.secretBlindIndex as string
};
}),
tx
);
});

View File

@@ -2,4 +2,10 @@ import path from "path";
// given two paths irrespective of ending with / or not
// this will return true if its equal
export const isSamePath = async (from: string, to: string) => !path.relative(from, to);
export const isSamePath = (from: string, to: string) => !path.relative(from, to);
export const removeTrailingSlash = (str: string) => {
if (str === "/") return str;
return str.endsWith("/") ? str.slice(0, -1) : str;
};

View File

@@ -11,7 +11,7 @@ export const globalRateLimiterCfg = (): RateLimitPluginOptions => {
return {
timeWindow: 60 * 1000,
max: 650,
max: 600,
redis,
allowList: (req) => req.url === "/healthcheck" || req.url === "/api/status",
keyGenerator: (req) => req.realIp
@@ -20,12 +20,12 @@ export const globalRateLimiterCfg = (): RateLimitPluginOptions => {
export const authRateLimit: RateLimitOptions = {
timeWindow: 60 * 1000,
max: 650,
max: 600,
keyGenerator: (req) => req.realIp
};
export const passwordRateLimit: RateLimitOptions = {
timeWindow: 60 * 1000,
max: 650,
max: 600,
keyGenerator: (req) => req.realIp
};

View File

@@ -15,15 +15,15 @@ export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider
req.log.error(error);
if (error instanceof BadRequestError) {
res.status(400).send({ statusCode: 400, message: error.message, error: error.name });
} else if (error instanceof UnauthorizedError || error instanceof ForbiddenRequestError) {
} else if (error instanceof UnauthorizedError) {
res.status(403).send({ statusCode: 403, message: error.message, error: error.name });
} else if (error instanceof DatabaseError || error instanceof InternalServerError) {
res.status(500).send({ statusCode: 500, message: "Something went wrong", error: error.name });
} else if (error instanceof ZodError) {
res.status(403).send({ statusCode: 403, error: "ValidationFailure", message: error.issues });
} else if (error instanceof ForbiddenError) {
res.status(403).send({
statusCode: 403,
res.status(401).send({
statusCode: 401,
error: "PermissionDenied",
message: `You are not allowed to ${error.action} on ${error.subjectType}`
});

View File

@@ -2,7 +2,7 @@ import { z } from "zod";
import { IntegrationsSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { shake } from "@app/lib/fn";
import { removeTrailingSlash, shake } from "@app/lib/fn";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
@@ -16,7 +16,7 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
app: z.string().trim().optional(),
isActive: z.boolean(),
appId: z.string().trim().optional(),
secretPath: z.string().trim().default("/"),
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
sourceEnvironment: z.string().trim(),
targetEnvironment: z.string().trim().optional(),
targetEnvironmentId: z.string().trim().optional(),
@@ -89,7 +89,7 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
app: z.string().trim(),
appId: z.string().trim(),
isActive: z.boolean(),
secretPath: z.string().trim().default("/"),
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
targetEnvironment: z.string().trim(),
owner: z.string().trim(),
environment: z.string().trim()

View File

@@ -2,6 +2,7 @@ import { z } from "zod";
import { SecretFoldersSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { removeTrailingSlash } from "@app/lib/fn";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
@@ -14,9 +15,9 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
workspaceId: z.string().trim(),
environment: z.string().trim(),
name: z.string().trim(),
path: z.string().trim().default("/"),
path: z.string().trim().default("/").transform(removeTrailingSlash),
// backward compatiability with cli
directory: z.string().trim().default("/")
directory: z.string().trim().default("/").transform(removeTrailingSlash)
}),
response: {
200: z.object({
@@ -68,9 +69,9 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
workspaceId: z.string().trim(),
environment: z.string().trim(),
name: z.string().trim(),
path: z.string().trim().default("/"),
path: z.string().trim().default("/").transform(removeTrailingSlash),
// backward compatiability with cli
directory: z.string().trim().default("/")
directory: z.string().trim().default("/").transform(removeTrailingSlash)
}),
response: {
200: z.object({
@@ -122,9 +123,9 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
body: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
path: z.string().trim().default("/"),
path: z.string().trim().default("/").transform(removeTrailingSlash),
// keep this here as cli need directory
directory: z.string().trim().default("/")
directory: z.string().trim().default("/").transform(removeTrailingSlash)
}),
response: {
200: z.object({
@@ -172,9 +173,9 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
querystring: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
path: z.string().trim().default("/"),
path: z.string().trim().default("/").transform(removeTrailingSlash),
// backward compatiability with cli
directory: z.string().trim().default("/")
directory: z.string().trim().default("/").transform(removeTrailingSlash)
}),
response: {
200: z.object({

View File

@@ -2,6 +2,7 @@ import { z } from "zod";
import { SecretImportsSchema, SecretsSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { removeTrailingSlash } from "@app/lib/fn";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
@@ -13,10 +14,10 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
body: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
path: z.string().trim().default("/"),
path: z.string().trim().default("/").transform(removeTrailingSlash),
import: z.object({
environment: z.string().trim(),
path: z.string().trim()
path: z.string().trim().transform(removeTrailingSlash)
})
}),
response: {
@@ -74,10 +75,14 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
body: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
path: z.string().trim().default("/"),
path: z.string().trim().default("/").transform(removeTrailingSlash),
import: z.object({
environment: z.string().trim().optional(),
path: z.string().trim().optional(),
path: z
.string()
.trim()
.optional()
.transform((val) => (val ? removeTrailingSlash(val) : val)),
position: z.number().optional()
})
}),
@@ -137,7 +142,7 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
body: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
path: z.string().trim().default("/")
path: z.string().trim().default("/").transform(removeTrailingSlash)
}),
response: {
200: z.object({
@@ -191,7 +196,7 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
querystring: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
path: z.string().trim().default("/")
path: z.string().trim().default("/").transform(removeTrailingSlash)
}),
response: {
200: z.object({
@@ -243,7 +248,7 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
querystring: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
path: z.string().trim().default("/")
path: z.string().trim().default("/").transform(removeTrailingSlash)
}),
response: {
200: z.object({

View File

@@ -29,7 +29,7 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => {
passReqToCallback: true,
clientID: appCfg.CLIENT_ID_GOOGLE_LOGIN as string,
clientSecret: appCfg.CLIENT_SECRET_GOOGLE_LOGIN as string,
callbackURL: "/api/v1/sso/google",
callbackURL: `${appCfg.SITE_URL}/api/v1/sso/google`,
scope: ["profile", " email"]
},
async (req, _accessToken, _refreshToken, profile, cb) => {
@@ -71,7 +71,7 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => {
passReqToCallback: true,
clientID: appCfg.CLIENT_ID_GITHUB_LOGIN as string,
clientSecret: appCfg.CLIENT_SECRET_GITHUB_LOGIN as string,
callbackURL: "/api/v1/sso/github",
callbackURL: `${appCfg.SITE_URL}/api/v1/sso/github`,
scope: ["user:email"]
},
async (req, accessToken, _refreshToken, profile, cb) => {
@@ -110,7 +110,7 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => {
passReqToCallback: true,
clientID: appCfg.CLIENT_ID_GITLAB_LOGIN,
clientSecret: appCfg.CLIENT_SECRET_GITLAB_LOGIN,
callbackURL: "/api/v1/sso/gitlab",
callbackURL: `${appCfg.SITE_URL}/api/v1/sso/gitlab`,
baseURL: appCfg.CLIENT_GITLAB_LOGIN_URL
},
async (req: any, _accessToken: string, _refreshToken: string, profile: any, cb: any) => {

View File

@@ -2,6 +2,7 @@ import { z } from "zod";
import { WebhooksSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { removeTrailingSlash } from "@app/lib/fn";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
@@ -33,7 +34,7 @@ export const registerWebhookRouter = async (server: FastifyZodProvider) => {
environment: z.string().trim(),
webhookUrl: z.string().url().trim(),
webhookSecretKey: z.string().trim().optional(),
secretPath: z.string().trim().default("/")
secretPath: z.string().trim().default("/").transform(removeTrailingSlash)
}),
response: {
200: z.object({
@@ -182,7 +183,7 @@ export const registerWebhookRouter = async (server: FastifyZodProvider) => {
querystring: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim().optional(),
secretPath: z.string().trim().optional()
secretPath: z.string().trim().optional().transform((val)=> val?removeTrailingSlash(val):val)
}),
response: {
200: z.object({

View File

@@ -2,6 +2,7 @@ import { z } from "zod";
import { ServiceTokensSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { removeTrailingSlash } from "@app/lib/fn";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
@@ -42,7 +43,7 @@ export const registerServiceTokenRouter = async (server: FastifyZodProvider) =>
scopes: z
.object({
environment: z.string().trim(),
secretPath: z.string().trim()
secretPath: z.string().trim().transform(removeTrailingSlash)
})
.array()
.min(1),

View File

@@ -12,6 +12,7 @@ import {
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { CommitType } from "@app/ee/services/secret-approval-request/secret-approval-request-types";
import { BadRequestError } from "@app/lib/errors";
import { removeTrailingSlash } from "@app/lib/fn";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { ActorType, AuthMode } from "@app/services/auth/auth-type";
import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
@@ -39,7 +40,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
querystring: z.object({
workspaceId: z.string().trim().optional(),
environment: z.string().trim().optional(),
secretPath: z.string().trim().default("/"),
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
include_imports: z
.enum(["true", "false"])
.default("false")
@@ -129,7 +130,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
querystring: z.object({
workspaceId: z.string().trim().optional(),
environment: z.string().trim().optional(),
secretPath: z.string().trim().default("/"),
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
version: z.coerce.number().optional(),
type: z.nativeEnum(SecretType).default(SecretType.Shared),
include_imports: z
@@ -216,7 +217,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
body: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
secretPath: z.string().trim().default("/"),
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
secretValue: z
.string()
.transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim())),
@@ -295,7 +296,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
secretValue: z
.string()
.transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim())),
secretPath: z.string().trim().default("/"),
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
skipMultilineEncoding: z.boolean().optional(),
type: z.nativeEnum(SecretType).default(SecretType.Shared)
}),
@@ -365,7 +366,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
body: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
secretPath: z.string().trim().default("/"),
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
type: z.nativeEnum(SecretType).default(SecretType.Shared)
}),
response: {
@@ -430,7 +431,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
querystring: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
secretPath: z.string().trim().default("/"),
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
include_imports: z
.enum(["true", "false"])
.default("false")
@@ -518,7 +519,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
querystring: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
secretPath: z.string().trim().default("/"),
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
type: z.nativeEnum(SecretType).default(SecretType.Shared),
version: z.coerce.number().optional(),
include_imports: z
@@ -590,7 +591,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
workspaceId: z.string().trim(),
environment: z.string().trim(),
type: z.nativeEnum(SecretType).default(SecretType.Shared),
secretPath: z.string().trim().default("/"),
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
secretKeyCiphertext: z.string().trim(),
secretKeyIV: z.string().trim(),
secretKeyTag: z.string().trim(),
@@ -758,7 +759,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
environment: z.string().trim(),
secretId: z.string().trim().optional(),
type: z.nativeEnum(SecretType).default(SecretType.Shared),
secretPath: z.string().trim().default("/"),
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
secretValueCiphertext: z.string().trim(),
secretValueIV: z.string().trim(),
secretValueTag: z.string().trim(),
@@ -935,7 +936,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
}),
body: z.object({
type: z.nativeEnum(SecretType).default(SecretType.Shared),
secretPath: z.string().trim().default("/"),
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
secretId: z.string().trim().optional(),
workspaceId: z.string().trim(),
environment: z.string().trim()
@@ -1050,7 +1051,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
body: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
secretPath: z.string().trim().default("/"),
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
secrets: z
.object({
secretName: z.string().trim(),
@@ -1176,7 +1177,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
body: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
secretPath: z.string().trim().default("/"),
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
secrets: z
.object({
secretName: z.string().trim(),
@@ -1301,7 +1302,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
body: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
secretPath: z.string().trim().default("/"),
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
secrets: z
.object({
secretName: z.string().trim(),

View File

@@ -29,10 +29,7 @@ export const secretBlindIndexServiceFactory = ({
projectId,
actorId
}: TGetProjectBlindIndexStatusDTO) => {
const { membership } = await permissionService.getProjectPermission(actor, actorId, projectId);
if (membership?.role !== ProjectMembershipRole.Admin) {
throw new UnauthorizedError({ message: "User must be admin" });
}
await permissionService.getProjectPermission(actor, actorId, projectId);
const secretCount = await secretBlindIndexDAL.countOfSecretsWithNullSecretBlindIndex(projectId);
return Number(secretCount);

View File

@@ -8,7 +8,7 @@ import {
TSecretFoldersUpdate
} from "@app/db/schemas";
import { BadRequestError, DatabaseError } from "@app/lib/errors";
import { groupBy } from "@app/lib/fn";
import { groupBy, removeTrailingSlash } from "@app/lib/fn";
import { ormify, selectAllTableCols } from "@app/lib/knex";
export const validateFolderName = (folderName: string) => {
@@ -238,10 +238,15 @@ export const secretFolderDALFactory = (db: TDbClient) => {
tx?: Knex
) => {
try {
const folder = await sqlFindFolderByPathQuery(tx || db, projectId, environment, path)
const folder = await sqlFindFolderByPathQuery(
tx || db,
projectId,
environment,
removeTrailingSlash(path)
)
.orderBy("depth", "desc")
.first();
if (folder && folder.path !== path) {
if (folder && folder.path !== removeTrailingSlash(path)) {
return;
}
if (!folder) return;
@@ -262,7 +267,12 @@ export const secretFolderDALFactory = (db: TDbClient) => {
tx?: Knex
) => {
try {
const folder = await sqlFindFolderByPathQuery(tx || db, projectId, environment, path)
const folder = await sqlFindFolderByPathQuery(
tx || db,
projectId,
environment,
removeTrailingSlash(path)
)
.orderBy("depth", "desc")
.first();
if (!folder) return;
@@ -278,8 +288,12 @@ export const secretFolderDALFactory = (db: TDbClient) => {
tx?: Knex
) => {
try {
const folders = await sqlFindMultipleFolderByEnvPathQuery(tx || db, query);
return query.map(({ envId, secretPath }) =>
const formatedQuery = query.map(({ secretPath, envId }) => ({
envId,
secretPath: removeTrailingSlash(secretPath)
}));
const folders = await sqlFindMultipleFolderByEnvPathQuery(tx || db, formatedQuery);
return formatedQuery.map(({ envId, secretPath }) =>
folders.find(
({ path: targetPath, envId: targetEnvId }) =>
targetPath === secretPath && targetEnvId === envId

View File

@@ -116,18 +116,22 @@ export const secretServiceFactory = ({
inputSecrets.map(({ tags, ...el }) => ({ ...el, folderId })),
tx
);
const newSecretGroupByBlindIndex = groupBy(newSecrets, (item) => item.secretBlindIndex);
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][0].id
[`${TableName.Secret}Id` as const]:
newSecretGroupByBlindIndex[secretBlindIndex as string][0].id
}))
);
const secretVersions = await secretVersionDAL.insertMany(
inputSecrets.map(({ tags, ...el }) => ({
...el,
folderId,
secretId: newSecretGroupByBlindIndex[el.secretBlindIndex][0].id
secretId: newSecretGroupByBlindIndex[el.secretBlindIndex as string][0].id
})),
tx
);
@@ -269,7 +273,9 @@ export const secretServiceFactory = ({
if (isNew) {
if (secrets.length) throw new BadRequestError({ message: "Secret already exist" });
} else if (secrets.length !== inputSecrets.length)
throw new BadRequestError({ message: `Secret not found: blind index ${JSON.stringify(keyName2BlindIndex)}` });
throw new BadRequestError({
message: `Secret not found: blind index ${JSON.stringify(keyName2BlindIndex)}`
});
return { blindIndex2KeyName, keyName2BlindIndex, secrets };
};
@@ -292,7 +298,7 @@ export const secretServiceFactory = ({
})),
userId
);
const secsGroupedByBlindIndex = groupBy(secrets, (i) => i.secretBlindIndex);
const secsGroupedByBlindIndex = groupBy(secrets, (i) => i.secretBlindIndex as string);
return { secsGroupedByBlindIndex, secrets };
};

View File

@@ -8,7 +8,7 @@ Each service token can be provisioned scoped access to select environment(s) and
## Service Tokens
You can manage service tokens in Project Settings > Service Tokens.
You can manage service tokens in Access Control > Service Tokens (tab).
### Service Token (Current)
@@ -25,7 +25,7 @@ of the token.
## Creating a service token
To create a service token, head to Project Settings > Service Tokens as shown below and press **Create token**.
To create a service token, head to Access Control > Service Tokens as shown below and press **Create token**.
![token add](../../images/project-token-old-add.png)

View File

@@ -25,7 +25,7 @@ export const ProjectPermissionCan: FunctionComponent<Props> = ({
allowedLabel,
...props
}) => {
const permission = useProjectPermission();
const { permission } = useProjectPermission();
return (
<Can {...props} passThrough={passThrough} ability={props?.ability || permission}>
{(isAllowed, ability) => {

View File

@@ -1,6 +1,7 @@
import { createContext, ReactNode, useContext } from "react";
import { useGetUserProjectPermissions } from "@app/hooks/api";
import { TProjectMembership } from "@app/hooks/api/users/types";
import { useWorkspace } from "../WorkspaceContext";
import { TProjectPermission } from "./types";
@@ -9,7 +10,10 @@ type Props = {
children: ReactNode;
};
const ProjectPermissionContext = createContext<null | TProjectPermission>(null);
const ProjectPermissionContext = createContext<null | {
permission: TProjectPermission;
membership: TProjectMembership;
}>(null);
export const ProjectPermissionProvider = ({ children }: Props): JSX.Element => {
const { currentWorkspace, isLoading: isWsLoading } = useWorkspace();

View File

@@ -21,7 +21,7 @@ export const withProjectPermission = <T extends {}, J extends TProjectPermission
{ action, subject, className, containerClassName }: Props<Generics<J>["abilities"]>
) => {
const HOC = (hocProps: T) => {
const permission = useProjectPermission();
const { permission } = useProjectPermission();
// akhilmhdh: Set as any due to casl/react ts type bug
// REASON: casl due to its type checking can't seem to union even if union intersection is applied
@@ -29,13 +29,13 @@ export const withProjectPermission = <T extends {}, J extends TProjectPermission
return (
<div
className={twMerge(
"container h-full mx-auto flex justify-center items-center",
"container mx-auto flex h-full items-center justify-center",
containerClassName
)}
>
<div
className={twMerge(
"rounded-md bg-mineshaft-800 text-bunker-300 p-16 flex space-x-12 items-end",
"flex items-end space-x-12 rounded-md bg-mineshaft-800 p-16 text-bunker-300",
className
)}
>
@@ -43,7 +43,7 @@ export const withProjectPermission = <T extends {}, J extends TProjectPermission
<FontAwesomeIcon icon={faLock} size="6x" />
</div>
<div>
<div className="text-4xl font-medium mb-2">Permission Denied</div>
<div className="mb-2 text-4xl font-medium">Permission Denied</div>
<div className="text-sm">
You do not have permission to this page. <br /> Kindly contact your organization
administrator

View File

@@ -7,6 +7,7 @@ export type IntegrationAuth = {
updatedAt: string;
algorithm: string;
keyEncoding: string;
teamId?: string;
};
export type App = {

View File

@@ -8,7 +8,7 @@ import { apiRequest } from "@app/config/request";
import { OrgPermissionSet } from "@app/context/OrgPermissionContext/types";
import { ProjectPermissionSet } from "@app/context/ProjectPermissionContext/types";
import { OrgUser } from "../users/types";
import { OrgUser, TProjectMembership } from "../users/types";
import {
TGetUserOrgPermissionsDTO,
TGetUserProjectPermissionDTO,
@@ -104,10 +104,13 @@ export const useGetUserOrgPermissions = ({ orgId }: TGetUserOrgPermissionsDTO) =
const getUserProjectPermissions = async ({ workspaceId }: TGetUserProjectPermissionDTO) => {
const { data } = await apiRequest.get<{
data: { permissions: PackRule<RawRuleOf<MongoAbility<OrgPermissionSet>>>[] };
data: {
permissions: PackRule<RawRuleOf<MongoAbility<OrgPermissionSet>>>[];
membership: TProjectMembership;
};
}>(`/api/v1/workspace/${workspaceId}/permissions`, {});
return data.data.permissions;
return data.data;
};
export const useGetUserProjectPermissions = ({ workspaceId }: TGetUserProjectPermissionDTO) =>
@@ -116,8 +119,8 @@ export const useGetUserProjectPermissions = ({ workspaceId }: TGetUserProjectPer
queryFn: () => getUserProjectPermissions({ workspaceId }),
enabled: Boolean(workspaceId),
select: (data) => {
const rule = unpackRules<RawRuleOf<MongoAbility<ProjectPermissionSet>>>(data);
const rule = unpackRules<RawRuleOf<MongoAbility<ProjectPermissionSet>>>(data.permissions);
const ability = createMongoAbility<ProjectPermissionSet>(rule, { conditionsMatcher });
return ability;
return { permission: ability, membership: data.membership };
}
});

View File

@@ -52,6 +52,15 @@ export type OrgUser = {
roleId: string;
};
export type TProjectMembership = {
id: string;
role: string;
createdAt: string;
updatedAt: string;
projectId: string;
roleId: string;
};
export type TWorkspaceUser = OrgUser;
export type AddUserToWsDTO = {

View File

@@ -52,7 +52,8 @@ export default function VercelCreateIntegrationPage() {
const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? "");
const { data: integrationAuthApps, isLoading: isIntegrationAuthAppsLoading } =
useGetIntegrationAuthApps({
integrationAuthId: (integrationAuthId as string) ?? ""
integrationAuthId: (integrationAuthId as string) ?? "",
teamId: integrationAuth?.teamId as string
});
const { data: branches } = useGetIntegrationAuthVercelBranches({

View File

@@ -30,7 +30,7 @@ export const CloudIntegrationSection = ({
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
"deleteConfirmation"
] as const);
const permission = useProjectPermission();
const { permission } = useProjectPermission();
const { createNotification } = useNotificationContext();
const isEmpty = !isLoading && !cloudIntegrations?.length;
@@ -43,7 +43,7 @@ export const CloudIntegrationSection = ({
<h1 className="text-3xl font-semibold">{t("integrations.cloud-integrations")}</h1>
<p className="text-base text-gray-400">{t("integrations.click-to-start")}</p>
</div>
<div className="mx-6 grid grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4 gap-4">
<div className="mx-6 grid grid-cols-2 gap-4 lg:grid-cols-3 2xl:grid-cols-4">
{isLoading &&
Array.from({ length: 12 }).map((_, index) => (
<Skeleton className="h-32" key={`cloud-integration-skeleton-${index + 1}`} />

View File

@@ -44,7 +44,7 @@ export const SecretApprovalPolicyList = ({ workspaceId }: Props) => {
"deletePolicy",
"upgradePlan"
] as const);
const permission = useProjectPermission();
const { permission } = useProjectPermission();
const { subscription } = useSubscription();
const { createNotification } = useNotificationContext();

View File

@@ -36,7 +36,7 @@ export const SecretApprovalPolicyRow = ({
}: Props) => {
const [selectedApprovers, setSelectedApprovers] = useState<string[]>([]);
const { mutate: updateSecretApprovalPolicy, isLoading } = useUpdateSecretApprovalPolicy();
const permission = useProjectPermission();
const { permission } = useProjectPermission();
return (
<Tr>

View File

@@ -48,7 +48,7 @@ export const SecretMainPage = () => {
const { t } = useTranslation();
const { currentWorkspace } = useWorkspace();
const router = useRouter();
const permission = useProjectPermission();
const { permission } = useProjectPermission();
const [isVisible, setIsVisible] = useState(false);
const [sortDir, setSortDir] = useState<SortDir>(SortDir.ASC);

View File

@@ -81,7 +81,7 @@ export const SecretDetailSidebar = ({
resolver: zodResolver(formSchema),
values: secret
});
const permission = useProjectPermission();
const { permission } = useProjectPermission();
const cannotEditSecret = permission.cannot(
ProjectPermissionActions.Edit,
subject(ProjectPermissionSub.Secrets, { environment, secretPath })

View File

@@ -85,7 +85,7 @@ export const SecretItem = memo(
secretPath
}: Props) => {
const { currentWorkspace } = useWorkspace();
const permission = useProjectPermission();
const { permission } = useProjectPermission();
const isReadOnly =
permission.can(
ProjectPermissionActions.Read,

View File

@@ -4,9 +4,15 @@ import {
decryptSymmetric
} from "@app/components/utilities/cryptography/crypto";
import { Button, Spinner } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import {
ProjectPermissionActions,
ProjectPermissionSub,
useProjectPermission,
useWorkspace
} from "@app/context";
import { useToggle } from "@app/hooks";
import { useGetWorkspaceIndexStatus, useNameWorkspaceSecrets } from "@app/hooks/api";
import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
import { UserWsKeyPair } from "@app/hooks/api/types";
import { fetchWorkspaceSecrets } from "@app/hooks/api/workspace/queries";
@@ -18,6 +24,7 @@ type Props = {
export const ProjectIndexSecretsSection = ({ decryptFileKey }: Props) => {
const { currentWorkspace } = useWorkspace();
const { membership } = useProjectPermission();
const { data: isBlindIndexed, isLoading: isBlindIndexedLoading } = useGetWorkspaceIndexStatus(
currentWorkspace?.id ?? ""
);
@@ -74,17 +81,19 @@ export const ProjectIndexSecretsSection = ({ decryptFileKey }: Props) => {
</div>
</div>
)}
<p className="mb-2 text-lg font-semibold">Enable Blind Indices</p>
<p className="mb-2 text-lg font-semibold">Action Required</p>
<p className="mb-4 leading-7 text-gray-400">
Your project was created before the introduction of blind indexing. To continue accessing
secrets by name through the SDK, public API and web dashboard, please enable blind indexing.{" "}
<b>This is a one time process.</b>
<b>
{membership.role !== ProjectMembershipRole.Admin && "This is an admin only operation."}
</b>
</p>
<ProjectPermissionCan I={ProjectPermissionActions.Edit} a={ProjectPermissionSub.Settings}>
{(isAllowed) => (
<Button
onClick={onEnableBlindIndices}
isDisabled={!isAllowed}
isDisabled={!isAllowed || membership.role !== ProjectMembershipRole.Admin}
color="mineshaft"
type="submit"
isLoading={isIndexing}

View File

@@ -62,7 +62,7 @@ export const SecretRotationPage = withProjectPermission(
() => {
const { currentWorkspace } = useWorkspace();
const { t } = useTranslation();
const permission = useProjectPermission();
const { permission } = useProjectPermission();
const { createNotification } = useNotificationContext();
const { popUp, handlePopUpOpen, handlePopUpToggle, handlePopUpClose } = usePopUp([
"createRotation",

View File

@@ -22,7 +22,7 @@ export const EnvironmentSection = () => {
const { createNotification } = useNotificationContext();
const { subscription } = useSubscription();
const { currentWorkspace } = useWorkspace();
const permision = useProjectPermission();
const { permission } = useProjectPermission();
const deleteWsEnvironment = useDeleteWsEnvironment();
@@ -94,7 +94,7 @@ export const EnvironmentSection = () => {
Choose which environments will show up in your dashboard like development, staging,
production
</p>
{permision.can(ProjectPermissionActions.Read, ProjectPermissionSub.Environments) ? (
{permission.can(ProjectPermissionActions.Read, ProjectPermissionSub.Environments) ? (
<EnvironmentTable handlePopUpOpen={handlePopUpOpen} />
) : (
<PermissionDeniedBanner />

View File

@@ -4,7 +4,12 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
import { PermissionDeniedBanner, ProjectPermissionCan } from "@app/components/permissions";
import { Button, DeleteActionModal } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useProjectPermission, useWorkspace } from "@app/context";
import {
ProjectPermissionActions,
ProjectPermissionSub,
useProjectPermission,
useWorkspace
} from "@app/context";
import { usePopUp } from "@app/hooks";
import { useDeleteWsTag } from "@app/hooks/api";
@@ -20,7 +25,7 @@ export const SecretTagsSection = (): JSX.Element => {
"deleteTagConfirmation"
] as const);
const { currentWorkspace } = useWorkspace();
const permission = useProjectPermission();
const { permission } = useProjectPermission();
const deleteWsTag = useDeleteWsTag();
@@ -47,8 +52,8 @@ export const SecretTagsSection = (): JSX.Element => {
};
return (
<div className="mb-6 p-4 bg-mineshaft-900 rounded-lg border border-mineshaft-600">
<div className="flex justify-between mb-8">
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="mb-8 flex justify-between">
<p className="mb-3 text-xl font-semibold">Secret Tags</p>
<ProjectPermissionCan I={ProjectPermissionActions.Create} a={ProjectPermissionSub.Tags}>
{(isAllowed) => (
@@ -65,7 +70,7 @@ export const SecretTagsSection = (): JSX.Element => {
)}
</ProjectPermissionCan>
</div>
<p className="text-gray-400 mb-8">
<p className="mb-8 text-gray-400">
Every secret can be assigned to one or more tags. Here you can add and remove tags for the
current project.
</p>