Compare commits

..

42 Commits

Author SHA1 Message Date
e6a7c5cb6c Merge pull request #1346 from akhilmhdh/fix/multi-line-fixed
fix: resolved encoding issue in multi line input
2024-01-29 18:41:55 +05:30
af4ac90617 Merge pull request #1347 from Infisical/daniel/even-more-pg-endpoint-fixes
(Fix): More Old Python SDK fixes
2024-01-29 07:58:19 -05:00
e78b14c6ec Update service-token-router.ts 2024-01-29 16:55:37 +04:00
8ade2f3758 Some more fixes for python structure 2024-01-29 16:55:36 +04:00
571e3c4961 fix: resolved encoding issue in multi line input 2024-01-29 18:22:09 +05:30
5aeb8f6b03 Merge pull request #1345 from Infisical/daniel/more-pg-endpoint-fixes
(Fix): Old Python SDK versions
2024-01-29 07:10:29 -05:00
3702d411ca Added formatting 2024-01-29 16:05:18 +04:00
3eba4815c8 Add __v and __id 2024-01-29 16:04:52 +04:00
9563c09951 Merge pull request #1343 from Infisical/daniel/pg-endpoint-fixes
(Postgres Fix): Secret endpoints return data
2024-01-29 06:23:55 -05:00
62eacc712d Update service-token-service.ts 2024-01-29 15:21:53 +04:00
0d9ec7cd76 Update service-token-router.ts 2024-01-29 15:19:45 +04:00
36a4bf73a6 Types 2024-01-29 15:10:55 +04:00
fc8bd0470f Update service-token-service.ts 2024-01-29 14:40:51 +04:00
d5165e5086 Update secret-router.ts 2024-01-29 14:40:51 +04:00
f335101369 Add user 2024-01-29 14:40:51 +04:00
fbfe797547 Update sanitizedSchemas.ts 2024-01-29 14:40:51 +04:00
abd3652910 Add user DAL 2024-01-29 14:40:51 +04:00
2a3f136b68 Schemas 2024-01-29 14:40:51 +04:00
5239836e0f set source map to inline 2024-01-29 05:28:14 -05:00
d62e1c3703 Bump Cloudflare IP priority 2024-01-29 16:51:38 +07:00
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
74ac75b878 Remove async from isSamePath check in integration sync 2024-01-29 16:37:31 +07:00
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
703ff2c12b feat: added cloudwatch support and removed parsed secret blindindex nullable 2024-01-29 14:52:20 +05:30
6b4aee2a44 Merge pull request #1340 from Infisical/vercel-issue
Patch Vercel integration for team accounts
2024-01-29 09:25:18 +07:00
5593464287 Patch Vercel integration missing teamId not being passed in from frontend 2024-01-29 09:20:39 +07:00
7d556cb09b Merge pull request #1308 from Tchoupinax/patch-1
Update the path where managing service tokens
2024-01-28 17:00:33 -05:00
dcb6f5891f add license service timeout 2024-01-28 15:32:37 -05:00
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
a6ead9396c nit: small patch to request error status 2024-01-28 14:36:57 -05:00
d33ef9e4e1 jump cloud patch 2024-01-28 13:53:24 -05:00
4e20735f98 feat: resolved trailing slash in secret paths 2024-01-29 00:21:42 +05:30
f010a3a932 feat: changed blind index banner for everyone 2024-01-28 23:16:04 +05:30
bbf2634e73 prepend sso with site url 2024-01-27 20:20:02 -05:00
1980f802fa update rate limits 2024-01-27 19:05:17 -05:00
6ecd289e6c update rate limits 2024-01-27 18:38:57 -05:00
b8a6f5dc54 add email templates and disposable_emails.text to build 2024-01-27 18:15:12 -05:00
dedbc4fd60 remove unused imports 2024-01-27 17:11:40 -05:00
d14099990f remove notice 2024-01-27 17:04:53 -05:00
3f5ab2a09e remove console.log 2024-01-27 17:04:02 -05:00
a191f437e9 patch blind index route 2024-01-27 17:03:16 -05:00
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
58 changed files with 1209 additions and 440 deletions

View File

@ -13,7 +13,7 @@ module.exports = {
tsconfigRootDir: __dirname
},
rules: {
// "@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/no-empty-function": "off",
"consistent-return": "off", // my style
"import/order": "off", // for simple-import-order
"import/prefer-default-export": "off", // why

1017
backend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,7 @@
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "tsx watch --clear-screen=false ./src/main.ts | pino-pretty --colorize --colorizeObjects --singleLine",
"dev:docker": "nodemon",
"build": "rimraf dist && tsup",
"build": "rimraf dist && tsup && cp -R ./src/lib/validator/disposable_emails.txt ./dist && cp -R ./src/services/smtp/templates ./dist",
"start": "node dist/main.mjs",
"type:check": "tsc --noEmit",
"lint:fix": "eslint --fix --ext js,ts ./src",
@ -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

@ -8,7 +8,7 @@ import { z } from "zod";
import { TImmutableDBKeys } from "./models";
export const IdentityAccessTokensSchema = z.object({
id: z.string().uuid(),
id: z.string(),
accessTokenTTL: z.coerce.number().default(2592000),
accessTokenMaxTTL: z.coerce.number().default(2592000),
accessTokenNumUses: z.coerce.number().default(0),

View File

@ -13,7 +13,7 @@ export const OrganizationsSchema = z.object({
customerId: z.string().nullable().optional(),
slug: z.string(),
createdAt: z.date(),
updatedAt: z.date()
updatedAt: z.date(),
});
export type TOrganizations = z.infer<typeof OrganizationsSchema>;

View File

@ -10,7 +10,7 @@ import { TImmutableDBKeys } from "./models";
export const SecretApprovalRequestsSecretsSchema = z.object({
id: z.string().uuid(),
version: z.number().default(1).nullable().optional(),
secretBlindIndex: z.string(),
secretBlindIndex: z.string().nullable().optional(),
secretKeyCiphertext: z.string(),
secretKeyIV: z.string(),
secretKeyTag: z.string(),

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

@ -12,9 +12,9 @@ export const UserEncryptionKeysSchema = z.object({
clientPublicKey: z.string().nullable().optional(),
serverPrivateKey: z.string().nullable().optional(),
encryptionVersion: z.number().default(2).nullable().optional(),
protectedKey: z.string().nullable(),
protectedKeyIV: z.string().nullable(),
protectedKeyTag: z.string().nullable(),
protectedKey: z.string().nullable().optional(),
protectedKeyIV: z.string().nullable().optional(),
protectedKeyTag: z.string().nullable().optional(),
publicKey: z.string(),
encryptedPrivateKey: z.string(),
iv: z.string(),

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

@ -269,7 +269,14 @@ export const secretApprovalRequestServiceFactory = ({
const { secsGroupedByBlindIndex: conflictGroupByBlindIndex } =
await secretService.fnSecretBlindIndexCheckV2({
folderId,
inputSecrets: secretCreationCommits.map(({ secretBlindIndex }) => ({ secretBlindIndex }))
inputSecrets: secretCreationCommits.map(({ secretBlindIndex }) => {
if (!secretBlindIndex) {
throw new BadRequestError({
message: "Missing secret blind index"
});
}
return { secretBlindIndex };
})
});
secretCreationCommits
.filter(({ secretBlindIndex }) => conflictGroupByBlindIndex[secretBlindIndex || ""])
@ -291,7 +298,14 @@ export const secretApprovalRequestServiceFactory = ({
({ secretBlindIndex, secret }) =>
secret && secret.secretBlindIndex !== secretBlindIndex
)
.map(({ secretBlindIndex }) => ({ secretBlindIndex }))
.map(({ secretBlindIndex }) => {
if (!secretBlindIndex) {
throw new BadRequestError({
message: "Missing secret blind index"
});
}
return { secretBlindIndex };
})
});
secretUpdationCommits
.filter(
@ -381,10 +395,14 @@ export const secretApprovalRequestServiceFactory = ({
folderId,
tx,
actorId: "",
inputSecrets: secretDeletionCommits.map(({ secretBlindIndex }) => ({
secretBlindIndex,
type: SecretType.Shared
}))
inputSecrets: secretDeletionCommits.map(({ secretBlindIndex }) => {
if (!secretBlindIndex) {
throw new BadRequestError({
message: "Missing secret blind index"
});
}
return { secretBlindIndex, type: SecretType.Shared };
})
})
: [];
const updatedSecretApproval = await secretApprovalRequestDAL.updateById(
@ -499,7 +517,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 +561,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 +576,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
};
@ -628,7 +656,13 @@ export const secretApprovalRequestServiceFactory = ({
),
tx
);
const commitsGroupByBlindIndex = groupBy(approvalCommits, (i) => i.secretBlindIndex);
const commitsGroupByBlindIndex = groupBy(approvalCommits, (i) => {
if (!i.secretBlindIndex) {
throw new BadRequestError({ message: "Missing secret blind index" });
}
return i.secretBlindIndex;
});
if (tagIds.length) {
await secretApprovalRequestSecretDAL.insertApprovalSecretTags(
Object.keys(commitTagIds).flatMap((blindIndex) =>

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 }) => ({
...el,
secretId: id
})),
updatedSecrets.map(({ id, updatedAt, createdAt, ...el }) => {
if (!el.secretBlindIndex) throw new BadRequestError({ message: "Missing blind index" });
return {
...el,
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: 400,
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: 300,
max: 600,
keyGenerator: (req) => req.realIp
};
export const passwordRateLimit: RateLimitOptions = {
timeWindow: 60 * 1000,
max: 300,
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

@ -3,10 +3,10 @@ import fp from "fastify-plugin";
/*! https://github.com/pbojinov/request-ip/blob/9501cdf6e73059cc70fc6890adb086348d7cca46/src/index.js.
MIT License. 2022 Petar Bojinov - petarbojinov+github@gmail.com */
const headersOrder = [
"x-client-ip", // Most common
"x-forwarded-for", // Mostly used by proxies
"cf-connecting-ip", // Cloudflare
"Cf-Pseudo-IPv4", // Cloudflare
"x-client-ip", // Most common
"x-forwarded-for", // Mostly used by proxies
"fastly-client-ip",
"true-client-ip", // Akamai and Cloudflare
"x-real-ip", // Nginx

View File

@ -92,7 +92,10 @@ import { serviceTokenDALFactory } from "@app/services/service-token/service-toke
import { serviceTokenServiceFactory } from "@app/services/service-token/service-token-service";
import { TSmtpService } from "@app/services/smtp/smtp-service";
import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal";
import { getServerCfg, superAdminServiceFactory } from "@app/services/super-admin/super-admin-service";
import {
getServerCfg,
superAdminServiceFactory
} from "@app/services/super-admin/super-admin-service";
import { telemetryServiceFactory } from "@app/services/telemetry/telemetry-service";
import { userDALFactory } from "@app/services/user/user-dal";
import { userServiceFactory } from "@app/services/user/user-service";
@ -420,6 +423,7 @@ export const registerRoutes = async (
const serviceTokenService = serviceTokenServiceFactory({
projectEnvDAL,
serviceTokenDAL,
userDAL,
permissionService
});
@ -516,14 +520,14 @@ export const registerRoutes = async (
},
handler: () => {
const cfg = getConfig();
const serverCfg = getServerCfg()
const serverCfg = getServerCfg();
return {
date: new Date(),
message: "Ok" as const,
emailConfigured: cfg.isSmtpConfigured,
inviteOnlySignup: Boolean(serverCfg.allowSignUp),
redisConfigured: cfg.isRedisConfigured,
secretScanningConfigured: cfg.isSecretScanningConfigured,
secretScanningConfigured: cfg.isSecretScanningConfigured
};
}
});

View File

@ -1,6 +1,6 @@
import { z } from "zod";
import { IntegrationAuthsSchema, SecretApprovalPoliciesSchema } from "@app/db/schemas";
import { IntegrationAuthsSchema, SecretApprovalPoliciesSchema, UsersSchema } from "@app/db/schemas";
// sometimes the return data must be santizied to avoid leaking important values
// always prefer pick over omit in zod
@ -28,6 +28,23 @@ export const sapPubSchema = SecretApprovalPoliciesSchema.merge(
})
);
export const sanitizedServiceTokenUserSchema = UsersSchema.pick({
authMethods: true,
id: true,
createdAt: true,
updatedAt: true,
devices: true,
email: true,
firstName: true,
lastName: true,
mfaMethods: true
}).merge(
z.object({
__v: z.number().default(0),
_id: z.string()
})
);
export const secretRawSchema = z.object({
id: z.string(),
_id: z.string(),

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

@ -86,7 +86,13 @@ export const registerMfaRouter = async (server: FastifyZodProvider) => {
secure: appCfg.HTTPS_ENABLED
});
return { token: token.access, ...user };
return {
...user,
token: token.access,
protectedKey: user.protectedKey || null,
protectedKeyIV: user.protectedKeyIV || null,
protectedKeyTag: user.protectedKeyTag || null
};
}
});
};

View File

@ -2,9 +2,12 @@ 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";
import { sanitizedServiceTokenUserSchema } from "../sanitizedSchemas";
export const sanitizedServiceTokenSchema = ServiceTokensSchema.omit({
secretHash: true,
encryptedKey: true,
@ -19,15 +22,41 @@ export const registerServiceTokenRouter = async (server: FastifyZodProvider) =>
onRequest: verifyAuth([AuthMode.SERVICE_TOKEN]),
schema: {
response: {
200: ServiceTokensSchema.merge(z.object({ workspace: z.string() }))
200: ServiceTokensSchema.merge(
z.object({
workspace: z.string(),
user: sanitizedServiceTokenUserSchema.merge(
z.object({
_id: z.string(),
__v: z.number().default(0)
})
),
_id: z.string(),
__v: z.number().default(0)
})
)
}
},
handler: async (req) => {
const serviceTokenData = await server.services.serviceToken.getServiceToken({
const { serviceToken, user } = await server.services.serviceToken.getServiceToken({
actorId: req.permission.id,
actor: req.permission.type
});
return { ...serviceTokenData, workspace: serviceTokenData.projectId };
const formattedUser = {
...user,
_id: user.id,
__v: 0
} as const;
const formattedServiceToken = {
...serviceToken,
_id: serviceToken.id,
__v: 0
} as const;
// We return the user here because older versions of the deprecated Python SDK depend on it to properly parse the API response.
return { ...formattedServiceToken, workspace: serviceToken.projectId, user: formattedUser };
}
});
@ -42,7 +71,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

@ -96,9 +96,9 @@ export const registerLoginRouter = async (server: FastifyZodProvider) => {
encryptedPrivateKey: data.user.encryptedPrivateKey,
iv: data.user.iv,
tag: data.user.tag,
protectedKey: data.user.protectedKey,
protectedKeyIV: data.user.protectedKeyIV,
protectedKeyTag: data.user.protectedKeyTag
protectedKey: data.user.protectedKey || null,
protectedKeyIV: data.user.protectedKeyIV || null,
protectedKeyTag: data.user.protectedKeyTag || null
} as const;
}
});

View File

@ -59,7 +59,7 @@ export const registerSecretBlindIndexRouter = async (server: FastifyZodProvider)
});
server.route({
url: "/:projectId/secrets/name",
url: "/:projectId/secrets/names",
method: "POST",
schema: {
params: z.object({

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())),
@ -256,7 +257,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
event: {
type: EventType.CREATE_SECRET,
metadata: {
environment: req.body.environment,
environment: req.body.environment,
secretPath: req.body.secretPath,
secretId: secret.id,
secretKey: req.params.secretName,
@ -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")
@ -441,6 +442,9 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
secrets: SecretsSchema.omit({ secretBlindIndex: true })
.merge(
z.object({
_id: z.string(),
workspace: z.string(),
environment: z.string(),
tags: SecretTagsSchema.pick({
id: true,
slug: true,
@ -455,7 +459,15 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
secretPath: z.string(),
environment: z.string(),
folderId: z.string().optional(),
secrets: SecretsSchema.omit({ secretBlindIndex: true }).array()
secrets: SecretsSchema.omit({ secretBlindIndex: true })
.merge(
z.object({
_id: z.string(),
workspace: z.string(),
environment: z.string()
})
)
.array()
})
.array()
.optional()
@ -518,7 +530,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
@ -528,7 +540,12 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
}),
response: {
200: z.object({
secret: SecretsSchema.omit({ secretBlindIndex: true })
secret: SecretsSchema.omit({ secretBlindIndex: true }).merge(
z.object({
workspace: z.string(),
environment: z.string()
})
)
})
}
},
@ -590,7 +607,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(),
@ -609,7 +626,13 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
response: {
200: z.union([
z.object({
secret: SecretsSchema.omit({ secretBlindIndex: true })
secret: SecretsSchema.omit({ secretBlindIndex: true }).merge(
z.object({
_id: z.string(),
workspace: z.string(),
environment: z.string()
})
)
}),
z
.object({ approval: SecretApprovalRequestsSchema })
@ -758,7 +781,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(),
@ -779,7 +802,13 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
response: {
200: z.union([
z.object({
secret: SecretsSchema.omit({ secretBlindIndex: true })
secret: SecretsSchema.omit({ secretBlindIndex: true }).merge(
z.object({
_id: z.string(),
workspace: z.string(),
environment: z.string()
})
)
}),
z
.object({ approval: SecretApprovalRequestsSchema })
@ -935,7 +964,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()
@ -943,7 +972,13 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
response: {
200: z.union([
z.object({
secret: SecretsSchema.omit({ secretBlindIndex: true })
secret: SecretsSchema.omit({ secretBlindIndex: true }).merge(
z.object({
_id: z.string(),
workspace: z.string(),
environment: z.string()
})
)
}),
z
.object({ approval: SecretApprovalRequestsSchema })
@ -1050,7 +1085,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 +1211,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 +1336,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

@ -37,7 +37,12 @@ export const fnSecretsFromImports = async ({
environmentInfo: importEnv,
folderId: importedFolders?.[i]?.id,
secrets: importedFolders?.[i]?.id
? importedSecsGroupByFolderId[importedFolders?.[i]?.id as string]
? importedSecsGroupByFolderId[importedFolders?.[i]?.id as string].map((item) => ({
...item,
environment: importEnv.slug,
workspace: "", // This field should not be used, it's only here to keep the older Python SDK versions backwards compatible with the new Postgres backend.
_id: item.id // The old Python SDK depends on the _id field being returned. We return this to keep the older Python SDK versions backwards compatible with the new Postgres backend.
}))
: []
}));
};

View File

@ -109,7 +109,7 @@ export const secretDALFactory = (db: TDbClient) => {
const data = sqlNestRelationships({
data: secs,
key: "id",
parentMapper: (el) => SecretsSchema.parse(el),
parentMapper: (el) => ({ _id: el.id, ...SecretsSchema.parse(el) }),
childrenMapper: [
{
key: "tagId",

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
);
@ -141,7 +145,7 @@ export const secretServiceFactory = ({
await secretVersionTagDAL.insertMany(newSecretVersionTags, tx);
}
return newSecrets;
return newSecrets.map((secret) => ({ ...secret, _id: secret.id }));
};
const fnSecretBulkUpdate = async ({
@ -190,7 +194,7 @@ export const secretServiceFactory = ({
}
}
return newSecrets;
return newSecrets.map((secret) => ({ ...secret, _id: secret.id }));
};
const fnSecretBulkDelete = async ({
@ -239,7 +243,6 @@ export const secretServiceFactory = ({
userId,
blindIndexCfg
}: TFnSecretBlindIndexCheck) => {
console.log(`inputSecrets=[${JSON.stringify(inputSecrets, null, 4)}] folderId=[${folderId}] isNew=[${isNew}] userId=[${userId}] blindIndexCfg=[${JSON.stringify(blindIndexCfg, null, 4)}] `)
const blindIndex2KeyName: Record<string, string> = {}; // used at audit log point
const keyName2BlindIndex = await Promise.all(
inputSecrets.map(({ secretName }) =>
@ -254,8 +257,6 @@ export const secretServiceFactory = ({
}, {})
);
console.log("keyName2BlindIndex:", JSON.stringify(keyName2BlindIndex, null, 4))
if (inputSecrets.some(({ type }) => type === SecretType.Personal) && !userId) {
throw new BadRequestError({ message: "Missing user id for personal secret" });
}
@ -269,13 +270,12 @@ export const secretServiceFactory = ({
userId
);
console.log("fnSecretBlindIndexCheck:", JSON.stringify(secrets, null, 4))
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 };
};
@ -298,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 };
};
@ -539,7 +539,7 @@ export const secretServiceFactory = ({
await secretQueueService.syncSecrets({ secretPath: path, projectId, environment });
// TODO(akhilmhdh-pg): licence check, posthog service and snapshot
return { ...deletedSecret[0], workspace: projectId, environment };
return { ...deletedSecret[0], _id: deletedSecret[0].id, workspace: projectId, environment };
};
const getSecrets = async ({

View File

@ -13,6 +13,7 @@ import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
import { ActorType } from "../auth/auth-type";
import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
import { TUserDALFactory } from "../user/user-dal";
import { TServiceTokenDALFactory } from "./service-token-dal";
import {
TCreateServiceTokenDTO,
@ -23,6 +24,7 @@ import {
type TServiceTokenServiceFactoryDep = {
serviceTokenDAL: TServiceTokenDALFactory;
userDAL: TUserDALFactory;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
projectEnvDAL: Pick<TProjectEnvDALFactory, "findBySlugs">;
};
@ -31,6 +33,7 @@ export type TServiceTokenServiceFactory = ReturnType<typeof serviceTokenServiceF
export const serviceTokenServiceFactory = ({
serviceTokenDAL,
userDAL,
permissionService,
projectEnvDAL
}: TServiceTokenServiceFactoryDep) => {
@ -51,14 +54,14 @@ export const serviceTokenServiceFactory = ({
ProjectPermissionActions.Create,
ProjectPermissionSub.ServiceTokens
);
scopes.forEach(({ environment, secretPath }) => {
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
);
})
});
const appCfg = getConfig();
// validates env
@ -119,7 +122,10 @@ export const serviceTokenServiceFactory = ({
const serviceToken = await serviceTokenDAL.findById(actorId);
if (!serviceToken) throw new BadRequestError({ message: "Token not found" });
return serviceToken;
const serviceTokenUser = await userDAL.findById(serviceToken.createdBy);
if (!serviceTokenUser) throw new BadRequestError({ message: "Service token user not found" });
return { serviceToken, user: serviceTokenUser };
};
const getProjectServiceTokens = async ({

View File

@ -9,5 +9,6 @@ export default defineConfig({
},
external: ["../../../frontend/node_modules/next/dist/server/next-server.js"],
outDir: "dist",
entry: ["./src"]
entry: ["./src"],
sourceMap: "inline"
});

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,43 +1,46 @@
/* eslint-disable react/no-danger */
import { forwardRef, TextareaHTMLAttributes } from "react";
import sanitizeHtml, { DisallowedTagsModes } from "sanitize-html";
import { twMerge } from "tailwind-merge";
import { useToggle } from "@app/hooks";
const REGEX = /\${([^}]+)}/g;
const REGEX = /(\${([^}]+)})/g;
const replaceContentWithDot = (str: string) => {
let finalStr = "";
for (let i = 0; i < str.length; i += 1) {
const char = str.at(i);
finalStr += char === "\n" ? "\n" : "&#8226;";
finalStr += char === "\n" ? "\n" : "*";
}
return finalStr;
};
const sanitizeConf = {
allowedTags: ["span"],
disallowedTagsMode: "escape" as DisallowedTagsModes
};
const syntaxHighlight = (content?: string | null, isVisible?: boolean) => {
if (content === "") return "EMPTY";
if (!content) return "EMPTY";
if (!isVisible) return replaceContentWithDot(content);
const sanitizedContent = sanitizeHtml(
content.replaceAll("<", "&lt;").replaceAll(">", "&gt;"),
sanitizeConf
);
const newContent = sanitizedContent.replace(
REGEX,
(_a, b) =>
`<span class="ph-no-capture text-yellow">&#36;&#123;<span class="ph-no-capture text-yello-200/80">${b}</span>&#125;</span>`
);
let skipNext = false;
const formatedContent = content.split(REGEX).flatMap((el, i) => {
const isInterpolationSyntax = el.startsWith("${") && el.endsWith("}");
if (isInterpolationSyntax) {
skipNext = true;
return (
<span className="ph-no-capture text-yellow" key={`secret-value-${i + 1}`}>
&#36;&#123;<span className="ph-no-capture text-yello-200/80">{el.slice(2, -1)}</span>
&#125;
</span>
);
}
if (skipNext) {
skipNext = false;
return [];
}
return el;
});
// akhilmhdh: Dont remove this br. I am still clueless how this works but weirdly enough
// when break is added a line break works properly
return `${newContent}<br/>`;
return formatedContent.concat(<br />);
};
type Props = TextareaHTMLAttributes<HTMLTextAreaElement> & {
@ -59,18 +62,15 @@ export const SecretInput = forwardRef<HTMLTextAreaElement, Props>(
return (
<div
className={twMerge("overflow-auto w-full no-scrollbar rounded-md", containerClassName)}
className={twMerge("w-full overflow-auto rounded-md no-scrollbar", containerClassName)}
style={{ maxHeight: `${21 * 7}px` }}
>
<div className="relative overflow-hidden">
<pre aria-hidden className="m-0 ">
<code className={`inline-block w-full ${commonClassName}`}>
<span
style={{ whiteSpace: "break-spaces" }}
dangerouslySetInnerHTML={{
__html: syntaxHighlight(value, isVisible || isSecretFocused) ?? ""
}}
/>
<span style={{ whiteSpace: "break-spaces" }}>
{syntaxHighlight(value, isVisible || isSecretFocused)}
</span>
</code>
</pre>
<textarea

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

@ -24,8 +24,6 @@ import {
faPlug,
faPlus,
faUserPlus,
faWarning,
faXmark
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { yupResolver } from "@hookform/resolvers/yup";
@ -58,7 +56,6 @@ import {
fetchOrgUsers,
useAddUserToWs,
useCreateWorkspace,
useGetUserAction,
useRegisterUserAction,
useUploadWsKey
} from "@app/hooks/api";
@ -480,13 +477,6 @@ const OrganizationPage = withPermission(
const { createNotification } = useNotificationContext();
const addWsUser = useAddUserToWs();
const { data: updateClosed } = useGetUserAction("jan_2024_db_update_closed");
const registerUserAction = useRegisterUserAction();
const closeUpdate = async () => {
await registerUserAction.mutateAsync("jan_2024_db_update_closed");
};
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
"addNewWs",
"upgradePlan"
@ -615,30 +605,6 @@ const OrganizationPage = withPermission(
</div>
)}
<div className="mb-4 flex flex-col items-start justify-start px-6 py-6 pb-0 text-3xl">
<div
className={`${
!updateClosed ? "block" : "hidden"
} mb-4 flex w-full flex-row items-center rounded-md border border-primary-600 bg-primary/10 p-2 text-base text-white`}
>
<FontAwesomeIcon icon={faWarning} className="p-6 text-4xl text-primary" />
<div className="text-sm">
<span className="text-lg font-semibold">Scheduled maintenance on January 27th</span>{" "}
<br />
We&apos;ve planned a database upgrade and need to pause certain functionality for
approximately 3 hours on Saturday, January 27th, 10am EST. During these hours, read
operations will continue to function normally but no resources will be editable. No
action is required on your end your applications can continue to fetch secrets.
<br />
</div>
<button
type="button"
onClick={() => closeUpdate()}
aria-label="close"
className="flex h-full items-start text-mineshaft-100 duration-200 hover:text-red-400"
>
<FontAwesomeIcon icon={faXmark} />
</button>
</div>
<p className="mr-4 font-semibold text-white">Projects</p>
<div className="mt-6 flex w-full flex-row">
<Input

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,14 +25,14 @@ export const SecretTagsSection = (): JSX.Element => {
"deleteTagConfirmation"
] as const);
const { currentWorkspace } = useWorkspace();
const permission = useProjectPermission();
const { permission } = useProjectPermission();
const deleteWsTag = useDeleteWsTag();
const onDeleteApproved = async () => {
try {
await deleteWsTag.mutateAsync({
projectId:currentWorkspace?.id || "",
projectId: currentWorkspace?.id || "",
tagID: (popUp?.deleteTagConfirmation?.data as DeleteModalData)?.id
});
@ -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>