mirror of
https://github.com/Infisical/infisical.git
synced 2025-07-22 13:29:55 +00:00
Compare commits
42 Commits
infisical/
...
infisical/
Author | SHA1 | Date | |
---|---|---|---|
e6a7c5cb6c | |||
af4ac90617 | |||
e78b14c6ec | |||
8ade2f3758 | |||
571e3c4961 | |||
5aeb8f6b03 | |||
3702d411ca | |||
3eba4815c8 | |||
9563c09951 | |||
62eacc712d | |||
0d9ec7cd76 | |||
36a4bf73a6 | |||
fc8bd0470f | |||
d5165e5086 | |||
f335101369 | |||
fbfe797547 | |||
abd3652910 | |||
2a3f136b68 | |||
5239836e0f | |||
d62e1c3703 | |||
9bba9ee9b1 | |||
74ac75b878 | |||
8478fea52a | |||
703ff2c12b | |||
6b4aee2a44 | |||
5593464287 | |||
7d556cb09b | |||
dcb6f5891f | |||
1254215b51 | |||
a6ead9396c | |||
d33ef9e4e1 | |||
4e20735f98 | |||
f010a3a932 | |||
bbf2634e73 | |||
1980f802fa | |||
6ecd289e6c | |||
b8a6f5dc54 | |||
dedbc4fd60 | |||
d14099990f | |||
3f5ab2a09e | |||
a191f437e9 | |||
e18abc6e22 |
@ -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
1017
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -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",
|
||||
|
@ -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),
|
||||
|
@ -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>;
|
||||
|
@ -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(),
|
||||
|
@ -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>;
|
||||
|
@ -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>;
|
||||
|
@ -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(),
|
||||
|
@ -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({
|
||||
|
@ -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(),
|
||||
|
@ -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)
|
||||
});
|
||||
|
||||
|
@ -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" });
|
||||
|
||||
|
@ -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) =>
|
||||
|
@ -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
|
||||
);
|
||||
});
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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
|
||||
};
|
||||
|
@ -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}`
|
||||
});
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
};
|
||||
}
|
||||
});
|
||||
|
@ -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(),
|
||||
|
@ -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()
|
||||
|
@ -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({
|
||||
|
@ -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({
|
||||
|
@ -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) => {
|
||||
|
@ -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({
|
||||
|
@ -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
|
||||
};
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -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),
|
||||
|
@ -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;
|
||||
}
|
||||
});
|
||||
|
@ -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({
|
||||
|
@ -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(),
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
}))
|
||||
: []
|
||||
}));
|
||||
};
|
||||
|
@ -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",
|
||||
|
@ -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 ({
|
||||
|
@ -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 ({
|
||||
|
@ -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"
|
||||
});
|
||||
|
@ -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**.
|
||||
|
||||

|
||||
|
||||
|
@ -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) => {
|
||||
|
@ -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" : "•";
|
||||
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("<", "<").replaceAll(">", ">"),
|
||||
sanitizeConf
|
||||
);
|
||||
const newContent = sanitizedContent.replace(
|
||||
REGEX,
|
||||
(_a, b) =>
|
||||
`<span class="ph-no-capture text-yellow">${<span class="ph-no-capture text-yello-200/80">${b}</span>}</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}`}>
|
||||
${<span className="ph-no-capture text-yello-200/80">{el.slice(2, -1)}</span>
|
||||
}
|
||||
</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
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -7,6 +7,7 @@ export type IntegrationAuth = {
|
||||
updatedAt: string;
|
||||
algorithm: string;
|
||||
keyEncoding: string;
|
||||
teamId?: string;
|
||||
};
|
||||
|
||||
export type App = {
|
||||
|
@ -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 };
|
||||
}
|
||||
});
|
||||
|
@ -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 = {
|
||||
|
@ -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({
|
||||
|
@ -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'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
|
||||
|
@ -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}`} />
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
|
@ -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 })
|
||||
|
@ -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,
|
||||
|
@ -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}
|
||||
|
@ -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",
|
||||
|
@ -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 />
|
||||
|
@ -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>
|
||||
|
Reference in New Issue
Block a user