mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
fix: view secret value permission (requested changes)
This commit is contained in:
@@ -221,20 +221,25 @@ export async function down(knex: Knex): Promise<void> {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (updatedServiceTokens.length > 0) {
|
if (updatedServiceTokens.length > 0) {
|
||||||
await knex(TableName.ServiceToken)
|
for (let i = 0; i < updatedServiceTokens.length; i += CHUNK_SIZE) {
|
||||||
.whereIn(
|
const chunk = updatedServiceTokens.slice(i, i + CHUNK_SIZE);
|
||||||
"id",
|
|
||||||
updatedServiceTokens.map((t) => t.id)
|
// eslint-disable-next-line no-await-in-loop
|
||||||
)
|
await knex(TableName.ServiceToken)
|
||||||
.update({
|
.whereIn(
|
||||||
// @ts-expect-error -- raw query
|
"id",
|
||||||
permissions: knex.raw(
|
chunk.map((t) => t.id)
|
||||||
`CASE id
|
|
||||||
${updatedServiceTokens.map((t) => `WHEN '${t.id}' THEN ?::text[]`).join(" ")}
|
|
||||||
END`,
|
|
||||||
updatedServiceTokens.map((t) => t.permissions)
|
|
||||||
)
|
)
|
||||||
});
|
.update({
|
||||||
|
// @ts-expect-error -- raw query
|
||||||
|
permissions: knex.raw(
|
||||||
|
`CASE id
|
||||||
|
${chunk.map((t) => `WHEN '${t.id}' THEN ?::text[]`).join(" ")}
|
||||||
|
END`,
|
||||||
|
chunk.map((t) => t.permissions)
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedRoles = projectRoles.reduce<typeof projectRoles>((acc, projectRole) => {
|
const updatedRoles = projectRoles.reduce<typeof projectRoles>((acc, projectRole) => {
|
||||||
|
|||||||
@@ -22,7 +22,11 @@ export const registerSecretVersionRouter = async (server: FastifyZodProvider) =>
|
|||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
secretVersions: secretRawSchema.array()
|
secretVersions: secretRawSchema
|
||||||
|
.extend({
|
||||||
|
secretValueHidden: z.boolean()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -37,6 +41,7 @@ export const registerSecretVersionRouter = async (server: FastifyZodProvider) =>
|
|||||||
offset: req.query.offset,
|
offset: req.query.offset,
|
||||||
secretId: req.params.secretId
|
secretId: req.params.secretId
|
||||||
});
|
});
|
||||||
|
|
||||||
return { secretVersions };
|
return { secretVersions };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { SecretSnapshotsSchema, SecretTagsSchema } from "@app/db/schemas";
|
import { SecretSnapshotsSchema } from "@app/db/schemas";
|
||||||
import { PROJECTS } from "@app/lib/api-docs";
|
import { PROJECTS } from "@app/lib/api-docs";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { secretRawSchema } from "@app/server/routes/sanitizedSchemas";
|
import { SanitizedTagSchema, secretRawSchema } from "@app/server/routes/sanitizedSchemas";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
export const registerSnapshotRouter = async (server: FastifyZodProvider) => {
|
export const registerSnapshotRouter = async (server: FastifyZodProvider) => {
|
||||||
@@ -33,11 +33,7 @@ export const registerSnapshotRouter = async (server: FastifyZodProvider) => {
|
|||||||
.extend({
|
.extend({
|
||||||
secretValueHidden: z.boolean(),
|
secretValueHidden: z.boolean(),
|
||||||
secretId: z.string(),
|
secretId: z.string(),
|
||||||
tags: SecretTagsSchema.pick({
|
tags: SanitizedTagSchema.array()
|
||||||
id: true,
|
|
||||||
slug: true,
|
|
||||||
color: true
|
|
||||||
}).array()
|
|
||||||
})
|
})
|
||||||
.array(),
|
.array(),
|
||||||
folderVersion: z.object({ id: z.string(), name: z.string() }).array(),
|
folderVersion: z.object({ id: z.string(), name: z.string() }).array(),
|
||||||
|
|||||||
@@ -442,7 +442,7 @@ export const ProjectPermissionV1Schema = z.discriminatedUnion("subject", [
|
|||||||
z.object({
|
z.object({
|
||||||
subject: z.literal(ProjectPermissionSub.Secrets).describe("The entity this permission pertains to."),
|
subject: z.literal(ProjectPermissionSub.Secrets).describe("The entity this permission pertains to."),
|
||||||
inverted: z.boolean().optional().describe("Whether rule allows or forbids."),
|
inverted: z.boolean().optional().describe("Whether rule allows or forbids."),
|
||||||
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionSecretActions).describe(
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
|
||||||
"Describe what action an entity can take."
|
"Describe what action an entity can take."
|
||||||
),
|
),
|
||||||
conditions: SecretConditionV1Schema.describe(
|
conditions: SecretConditionV1Schema.describe(
|
||||||
|
|||||||
@@ -417,6 +417,8 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
if ((inputSecret.tagIds || []).length !== newTags.length)
|
if ((inputSecret.tagIds || []).length !== newTags.length)
|
||||||
throw new NotFoundError({ message: `Tag not found. Found ${newTags.map((el) => el.slug).join(",")}` });
|
throw new NotFoundError({ message: `Tag not found. Found ${newTags.map((el) => el.slug).join(",")}` });
|
||||||
|
|
||||||
|
const tagsToCheck = inputSecret.tagIds ? newTags : secret.tags;
|
||||||
|
|
||||||
// now check with new ids
|
// now check with new ids
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionSecretActions.Edit,
|
ProjectPermissionSecretActions.Edit,
|
||||||
@@ -424,7 +426,9 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
environment,
|
environment,
|
||||||
secretPath,
|
secretPath,
|
||||||
secretName: inputSecret.secretName,
|
secretName: inputSecret.secretName,
|
||||||
secretTags: newTags?.map((el) => el.slug)
|
...(tagsToCheck.length && {
|
||||||
|
secretTags: tagsToCheck.map((el) => el.slug)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -441,7 +445,9 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
environment,
|
environment,
|
||||||
secretPath,
|
secretPath,
|
||||||
secretName: inputSecret.newSecretName,
|
secretName: inputSecret.newSecretName,
|
||||||
secretTags: newTags?.map((el) => el.slug)
|
...(tagsToCheck.length && {
|
||||||
|
secretTags: tagsToCheck.map((el) => el.slug)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -519,14 +525,15 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const tagsToCheck = newTags?.length ? newTags : secret.tags;
|
|
||||||
const secretValueHidden = !permission.can(
|
const secretValueHidden = !permission.can(
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment,
|
environment,
|
||||||
secretPath,
|
secretPath,
|
||||||
secretName: inputSecret.secretName,
|
secretName: inputSecret.secretName,
|
||||||
secretTags: tagsToCheck.length ? tagsToCheck.map((el) => el.slug) : undefined
|
...(tagsToCheck.length && {
|
||||||
|
secretTags: tagsToCheck.map((el) => el.slug)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1073,26 +1080,27 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
expandSecretReferences,
|
expandSecretReferences,
|
||||||
decryptor: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : ""),
|
decryptor: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : ""),
|
||||||
hasSecretAccess: (expandEnvironment, expandSecretPath, expandSecretKey, expandSecretTags) => {
|
hasSecretAccess: (expandEnvironment, expandSecretPath, expandSecretKey, expandSecretTags) => {
|
||||||
return (
|
const canDescribe = permission.can(
|
||||||
permission.can(
|
ProjectPermissionSecretActions.DescribeSecret,
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
environment: expandEnvironment,
|
||||||
environment: expandEnvironment,
|
secretPath: expandSecretPath,
|
||||||
secretPath: expandSecretPath,
|
secretName: expandSecretKey,
|
||||||
secretName: expandSecretKey,
|
secretTags: expandSecretTags
|
||||||
secretTags: expandSecretTags
|
})
|
||||||
})
|
|
||||||
) &&
|
|
||||||
permission.can(
|
|
||||||
ProjectPermissionSecretActions.DescribeSecret,
|
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
|
||||||
environment: expandEnvironment,
|
|
||||||
secretPath: expandSecretPath,
|
|
||||||
secretName: expandSecretKey,
|
|
||||||
secretTags: expandSecretTags
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const canReadValue = permission.can(
|
||||||
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
|
environment: expandEnvironment,
|
||||||
|
secretPath: expandSecretPath,
|
||||||
|
secretName: expandSecretKey,
|
||||||
|
secretTags: expandSecretTags
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return viewSecretValue ? canDescribe && canReadValue : canDescribe;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1220,26 +1228,26 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
decryptor: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : ""),
|
decryptor: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : ""),
|
||||||
expandSecretReferences: shouldExpandSecretReferences ? expandSecretReferences : undefined,
|
expandSecretReferences: shouldExpandSecretReferences ? expandSecretReferences : undefined,
|
||||||
hasSecretAccess: (expandEnvironment, expandSecretPath, expandSecretKey, expandSecretTags) => {
|
hasSecretAccess: (expandEnvironment, expandSecretPath, expandSecretKey, expandSecretTags) => {
|
||||||
return (
|
const canDescribe = permission.can(
|
||||||
permission.can(
|
ProjectPermissionSecretActions.DescribeSecret,
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
environment: expandEnvironment,
|
||||||
environment: expandEnvironment,
|
secretPath: expandSecretPath,
|
||||||
secretPath: expandSecretPath,
|
secretName: expandSecretKey,
|
||||||
secretName: expandSecretKey,
|
secretTags: expandSecretTags
|
||||||
secretTags: expandSecretTags
|
})
|
||||||
})
|
|
||||||
) &&
|
|
||||||
permission.can(
|
|
||||||
ProjectPermissionSecretActions.DescribeSecret,
|
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
|
||||||
environment: expandEnvironment,
|
|
||||||
secretPath: expandSecretPath,
|
|
||||||
secretName: expandSecretKey,
|
|
||||||
secretTags: expandSecretTags
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
const canReadValue = permission.can(
|
||||||
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
|
environment: expandEnvironment,
|
||||||
|
secretPath: expandSecretPath,
|
||||||
|
secretName: expandSecretKey,
|
||||||
|
secretTags: expandSecretTags
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return viewSecretValue ? canDescribe && canReadValue : canDescribe;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1461,7 +1469,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
environment,
|
environment,
|
||||||
secretPath,
|
secretPath,
|
||||||
secretName: el.key,
|
secretName: el.key,
|
||||||
secretTags: el.tags.map((i) => i.slug)
|
secretTags: el.tags?.map((i) => i.slug)
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1970,9 +1978,24 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
projectId: folder.projectId
|
projectId: folder.projectId
|
||||||
});
|
});
|
||||||
|
|
||||||
const secretVersions = await secretVersionDAL.find({ secretId }, { offset, limit, sort: [["createdAt", "desc"]] });
|
const secretVersions = await secretVersionDAL.findBySecretId(secretId, {
|
||||||
return secretVersions.map((el) =>
|
offset,
|
||||||
reshapeBridgeSecret(
|
limit,
|
||||||
|
sort: [["createdAt", "desc"]]
|
||||||
|
});
|
||||||
|
return secretVersions.map((el) => {
|
||||||
|
const secretValueHidden = permission.cannot(
|
||||||
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
|
environment: folder.environment.envSlug,
|
||||||
|
secretPath: "/",
|
||||||
|
secretName: el.key,
|
||||||
|
...(el.tags?.length && {
|
||||||
|
secretTags: el.tags.map((tag) => tag.slug)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return reshapeBridgeSecret(
|
||||||
folder.projectId,
|
folder.projectId,
|
||||||
folder.environment.envSlug,
|
folder.environment.envSlug,
|
||||||
"/",
|
"/",
|
||||||
@@ -1981,9 +2004,9 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
value: el.encryptedValue ? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString() : "",
|
value: el.encryptedValue ? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString() : "",
|
||||||
comment: el.encryptedComment ? secretManagerDecryptor({ cipherTextBlob: el.encryptedComment }).toString() : ""
|
comment: el.encryptedComment ? secretManagerDecryptor({ cipherTextBlob: el.encryptedComment }).toString() : ""
|
||||||
},
|
},
|
||||||
false
|
secretValueHidden
|
||||||
)
|
);
|
||||||
);
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// this is a backfilling API for secret references
|
// this is a backfilling API for secret references
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { TDbClient } from "@app/db";
|
import { TDbClient } from "@app/db";
|
||||||
import { TableName, TSecretVersionsV2, TSecretVersionsV2Update } from "@app/db/schemas";
|
import { SecretVersionsV2Schema, TableName, TSecretVersionsV2, TSecretVersionsV2Update } from "@app/db/schemas";
|
||||||
import { BadRequestError, DatabaseError } from "@app/lib/errors";
|
import { BadRequestError, DatabaseError } from "@app/lib/errors";
|
||||||
import { ormify, selectAllTableCols } from "@app/lib/knex";
|
import { ormify, selectAllTableCols, sqlNestRelationships, TFindOpt } from "@app/lib/knex";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
import { QueueName } from "@app/queue";
|
import { QueueName } from "@app/queue";
|
||||||
|
|
||||||
@@ -12,6 +12,58 @@ export type TSecretVersionV2DALFactory = ReturnType<typeof secretVersionV2Bridge
|
|||||||
export const secretVersionV2BridgeDALFactory = (db: TDbClient) => {
|
export const secretVersionV2BridgeDALFactory = (db: TDbClient) => {
|
||||||
const secretVersionV2Orm = ormify(db, TableName.SecretVersionV2);
|
const secretVersionV2Orm = ormify(db, TableName.SecretVersionV2);
|
||||||
|
|
||||||
|
const findBySecretId = async (secretId: string, { offset, limit, sort, tx }: TFindOpt<TSecretVersionsV2> = {}) => {
|
||||||
|
try {
|
||||||
|
const query = (tx || db.replicaNode())(TableName.SecretVersionV2)
|
||||||
|
.where(`${TableName.SecretVersionV2}.secretId`, secretId)
|
||||||
|
.leftJoin(TableName.SecretV2, `${TableName.SecretVersionV2}.secretId`, `${TableName.SecretV2}.id`)
|
||||||
|
.leftJoin(
|
||||||
|
TableName.SecretV2JnTag,
|
||||||
|
`${TableName.SecretV2}.id`,
|
||||||
|
`${TableName.SecretV2JnTag}.${TableName.SecretV2}Id`
|
||||||
|
)
|
||||||
|
.leftJoin(
|
||||||
|
TableName.SecretTag,
|
||||||
|
`${TableName.SecretV2JnTag}.${TableName.SecretTag}Id`,
|
||||||
|
`${TableName.SecretTag}.id`
|
||||||
|
)
|
||||||
|
.select(selectAllTableCols(TableName.SecretVersionV2))
|
||||||
|
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
|
||||||
|
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
|
||||||
|
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"));
|
||||||
|
|
||||||
|
if (limit) void query.limit(limit);
|
||||||
|
if (offset) void query.offset(offset);
|
||||||
|
if (sort) {
|
||||||
|
void query.orderBy(sort.map(([column, order, nulls]) => ({ column: column as string, order, nulls })));
|
||||||
|
}
|
||||||
|
|
||||||
|
const docs = await query;
|
||||||
|
|
||||||
|
const data = sqlNestRelationships({
|
||||||
|
data: docs,
|
||||||
|
key: "id",
|
||||||
|
parentMapper: (el) => ({ _id: el.id, ...SecretVersionsV2Schema.parse(el) }),
|
||||||
|
childrenMapper: [
|
||||||
|
{
|
||||||
|
key: "tagId",
|
||||||
|
label: "tags" as const,
|
||||||
|
mapper: ({ tagId: id, tagColor: color, tagSlug: slug }) => ({
|
||||||
|
id,
|
||||||
|
color,
|
||||||
|
slug,
|
||||||
|
name: slug
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: `${TableName.SecretVersionV2}: FindBySecretId` });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// This will fetch all latest secret versions from a folder
|
// This will fetch all latest secret versions from a folder
|
||||||
const findLatestVersionByFolderId = async (folderId: string, tx?: Knex) => {
|
const findLatestVersionByFolderId = async (folderId: string, tx?: Knex) => {
|
||||||
try {
|
try {
|
||||||
@@ -124,6 +176,7 @@ export const secretVersionV2BridgeDALFactory = (db: TDbClient) => {
|
|||||||
pruneExcessVersions,
|
pruneExcessVersions,
|
||||||
findLatestVersionMany,
|
findLatestVersionMany,
|
||||||
bulkUpdate,
|
bulkUpdate,
|
||||||
findLatestVersionByFolderId
|
findLatestVersionByFolderId,
|
||||||
|
findBySecretId
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -667,15 +667,7 @@ export const secretServiceFactory = ({
|
|||||||
environment,
|
environment,
|
||||||
secretPath: groupedPaths[secret.folderId][0].path
|
secretPath: groupedPaths[secret.folderId][0].path
|
||||||
})),
|
})),
|
||||||
imports: importedSecrets.map((el) => {
|
imports: importedSecrets
|
||||||
return {
|
|
||||||
...el,
|
|
||||||
secrets: el.secrets.map((secret) => ({
|
|
||||||
...secret,
|
|
||||||
secretValueHidden: false
|
|
||||||
}))
|
|
||||||
};
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2399,19 +2391,42 @@ export const secretServiceFactory = ({
|
|||||||
actionProjectType: ActionProjectType.SecretManager
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
|
||||||
const secretVersions = await secretVersionDAL.find({ secretId }, { offset, limit, sort: [["createdAt", "desc"]] });
|
const secretVersions = await secretVersionDAL.findBySecretId(secretId, {
|
||||||
return secretVersions.map((el) =>
|
offset,
|
||||||
decryptSecretRaw(
|
limit,
|
||||||
|
sort: [["createdAt", "desc"]]
|
||||||
|
});
|
||||||
|
return secretVersions.map((el) => {
|
||||||
|
const secretKey = decryptSymmetric128BitHexKeyUTF8({
|
||||||
|
ciphertext: secret.secretKeyCiphertext,
|
||||||
|
iv: secret.secretKeyIV,
|
||||||
|
tag: secret.secretKeyTag,
|
||||||
|
key: botKey
|
||||||
|
});
|
||||||
|
|
||||||
|
const secretValueHidden = permission.cannot(
|
||||||
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
|
environment: folder.environment.envSlug,
|
||||||
|
secretPath: "/",
|
||||||
|
secretName: secretKey,
|
||||||
|
...(el.tags?.length && {
|
||||||
|
secretTags: el.tags.map((tag) => tag.slug)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return decryptSecretRaw(
|
||||||
{
|
{
|
||||||
secretValueHidden: false,
|
secretValueHidden,
|
||||||
...el,
|
...el,
|
||||||
workspace: folder.projectId,
|
workspace: folder.projectId,
|
||||||
environment: folder.environment.envSlug,
|
environment: folder.environment.envSlug,
|
||||||
secretPath: "/"
|
secretPath: "/"
|
||||||
},
|
},
|
||||||
botKey
|
botKey
|
||||||
)
|
);
|
||||||
);
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const attachTags = async ({
|
const attachTags = async ({
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { TDbClient } from "@app/db";
|
import { TDbClient } from "@app/db";
|
||||||
import { TableName, TSecretVersions, TSecretVersionsUpdate } from "@app/db/schemas";
|
import { SecretVersionsSchema, TableName, TSecretVersions, TSecretVersionsUpdate } from "@app/db/schemas";
|
||||||
import { BadRequestError, DatabaseError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, DatabaseError, NotFoundError } from "@app/lib/errors";
|
||||||
import { ormify, selectAllTableCols } from "@app/lib/knex";
|
import { ormify, selectAllTableCols, sqlNestRelationships, TFindOpt } from "@app/lib/knex";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
import { QueueName } from "@app/queue";
|
import { QueueName } from "@app/queue";
|
||||||
|
|
||||||
@@ -12,6 +12,50 @@ export type TSecretVersionDALFactory = ReturnType<typeof secretVersionDALFactory
|
|||||||
export const secretVersionDALFactory = (db: TDbClient) => {
|
export const secretVersionDALFactory = (db: TDbClient) => {
|
||||||
const secretVersionOrm = ormify(db, TableName.SecretVersion);
|
const secretVersionOrm = ormify(db, TableName.SecretVersion);
|
||||||
|
|
||||||
|
const findBySecretId = async (secretId: string, { offset, limit, sort, tx }: TFindOpt<TSecretVersions> = {}) => {
|
||||||
|
try {
|
||||||
|
const query = (tx || db.replicaNode())(TableName.SecretVersion)
|
||||||
|
.where(`${TableName.SecretVersion}.secretId`, secretId)
|
||||||
|
.leftJoin(TableName.Secret, `${TableName.SecretVersion}.secretId`, `${TableName.Secret}.id`)
|
||||||
|
.leftJoin(TableName.JnSecretTag, `${TableName.Secret}.id`, `${TableName.JnSecretTag}.${TableName.Secret}Id`)
|
||||||
|
.leftJoin(TableName.SecretTag, `${TableName.JnSecretTag}.${TableName.SecretTag}Id`, `${TableName.SecretTag}.id`)
|
||||||
|
.select(selectAllTableCols(TableName.SecretVersion))
|
||||||
|
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
|
||||||
|
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
|
||||||
|
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"));
|
||||||
|
|
||||||
|
if (limit) void query.limit(limit);
|
||||||
|
if (offset) void query.offset(offset);
|
||||||
|
if (sort) {
|
||||||
|
void query.orderBy(sort.map(([column, order, nulls]) => ({ column: column as string, order, nulls })));
|
||||||
|
}
|
||||||
|
|
||||||
|
const docs = await query;
|
||||||
|
|
||||||
|
const data = sqlNestRelationships({
|
||||||
|
data: docs,
|
||||||
|
key: "id",
|
||||||
|
parentMapper: (el) => ({ _id: el.id, ...SecretVersionsSchema.parse(el) }),
|
||||||
|
childrenMapper: [
|
||||||
|
{
|
||||||
|
key: "tagId",
|
||||||
|
label: "tags" as const,
|
||||||
|
mapper: ({ tagId: id, tagColor: color, tagSlug: slug }) => ({
|
||||||
|
id,
|
||||||
|
color,
|
||||||
|
slug,
|
||||||
|
name: slug
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: `${TableName.SecretVersion}: FindBySecretId` });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// This will fetch all latest secret versions from a folder
|
// This will fetch all latest secret versions from a folder
|
||||||
const findLatestVersionByFolderId = async (folderId: string, tx?: Knex) => {
|
const findLatestVersionByFolderId = async (folderId: string, tx?: Knex) => {
|
||||||
try {
|
try {
|
||||||
@@ -149,6 +193,7 @@ export const secretVersionDALFactory = (db: TDbClient) => {
|
|||||||
findLatestVersionMany,
|
findLatestVersionMany,
|
||||||
bulkUpdate,
|
bulkUpdate,
|
||||||
findLatestVersionByFolderId,
|
findLatestVersionByFolderId,
|
||||||
|
findBySecretId,
|
||||||
bulkUpdateNoVersionIncrement
|
bulkUpdateNoVersionIncrement
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
22
frontend/src/components/v2/Blur/Blur.tsx
Normal file
22
frontend/src/components/v2/Blur/Blur.tsx
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
|
import { Tooltip } from "../Tooltip/Tooltip";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
className?: string;
|
||||||
|
tooltipText?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Blur = ({ className, tooltipText }: IProps) => {
|
||||||
|
return (
|
||||||
|
<Tooltip content={tooltipText} isDisabled={!tooltipText}>
|
||||||
|
<div
|
||||||
|
className={twMerge("flex w-80 flex-grow items-center py-1 pl-4 pr-2", className)}
|
||||||
|
tabIndex={0}
|
||||||
|
role="button"
|
||||||
|
>
|
||||||
|
<span className="blur">********</span>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
1
frontend/src/components/v2/Blur/index.tsx
Normal file
1
frontend/src/components/v2/Blur/index.tsx
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { Blur } from "./Blur";
|
||||||
@@ -75,7 +75,7 @@ export const useGetSnapshotSecrets = ({ snapshotId }: TSnapshotDataProps) =>
|
|||||||
id: secretVersion.secretId,
|
id: secretVersion.secretId,
|
||||||
env: data.environment.slug,
|
env: data.environment.slug,
|
||||||
key: secretVersion.secretKey,
|
key: secretVersion.secretKey,
|
||||||
secretValueHidden: false,
|
secretValueHidden: secretVersion.secretValueHidden,
|
||||||
value: secretVersion.secretValue || "",
|
value: secretVersion.secretValue || "",
|
||||||
tags: secretVersion.tags,
|
tags: secretVersion.tags,
|
||||||
comment: secretVersion.secretComment,
|
comment: secretVersion.secretComment,
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ export type SecretVersions = {
|
|||||||
envId: string;
|
envId: string;
|
||||||
secretKey: string;
|
secretKey: string;
|
||||||
secretValue?: string;
|
secretValue?: string;
|
||||||
|
secretValueHidden: boolean;
|
||||||
secretComment?: string;
|
secretComment?: string;
|
||||||
tags: WsTag[];
|
tags: WsTag[];
|
||||||
__v: number;
|
__v: number;
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import {
|
|||||||
ModalTrigger,
|
ModalTrigger,
|
||||||
Tooltip
|
Tooltip
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
|
import { Blur } from "@app/components/v2/Blur";
|
||||||
import { InfisicalSecretInput } from "@app/components/v2/InfisicalSecretInput";
|
import { InfisicalSecretInput } from "@app/components/v2/InfisicalSecretInput";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
||||||
import { useToggle } from "@app/hooks";
|
import { useToggle } from "@app/hooks";
|
||||||
@@ -143,11 +144,7 @@ export const SecretEditRow = ({
|
|||||||
|
|
||||||
<div className="flex-grow border-r border-r-mineshaft-600 pl-1 pr-2">
|
<div className="flex-grow border-r border-r-mineshaft-600 pl-1 pr-2">
|
||||||
{secretValueHidden ? (
|
{secretValueHidden ? (
|
||||||
<Tooltip content="You do not have permission to read the value of this secret.">
|
<Blur tooltipText="You do not have permission to read the value of this secret." />
|
||||||
<div className="flex w-80 flex-grow items-center py-1 pl-4 pr-2">
|
|
||||||
<span className="blur">********</span>
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
) : (
|
) : (
|
||||||
<Controller
|
<Controller
|
||||||
disabled={isImportedSecret && !defaultValue}
|
disabled={isImportedSecret && !defaultValue}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { faLock } from "@fortawesome/free-solid-svg-icons";
|
|||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
import { Td, Tooltip, Tr } from "@app/components/v2";
|
import { Td, Tooltip, Tr } from "@app/components/v2";
|
||||||
|
import { Blur } from "@app/components/v2/Blur";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
environments: { name: string; slug: string }[];
|
environments: { name: string; slug: string }[];
|
||||||
@@ -25,7 +26,7 @@ export const SecretNoAccessOverviewTableRow = ({ environments = [], count }: Pro
|
|||||||
<div className="text-bunker-300">
|
<div className="text-bunker-300">
|
||||||
<FontAwesomeIcon className="block" icon={faLock} />
|
<FontAwesomeIcon className="block" icon={faLock} />
|
||||||
</div>
|
</div>
|
||||||
<div className="blur-sm">NO ACCESS</div>
|
<Blur />
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { Link } from "@tanstack/react-router";
|
import { Link } from "@tanstack/react-router";
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
|
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
@@ -207,9 +208,16 @@ export const SecretDetailSidebar = ({
|
|||||||
await onSaveSecret(secret, { ...secret, ...data }, () => reset());
|
await onSaveSecret(secret, { ...secret, ...data }, () => reset());
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleReminderSubmit = async (reminderRepeatDays: number | null | undefined, reminderNote: string | null | undefined) => {
|
const handleReminderSubmit = async (
|
||||||
await onSaveSecret(secret, { ...secret, reminderRepeatDays, reminderNote, isReminderEvent: true }, () => { });
|
reminderRepeatDays: number | null | undefined,
|
||||||
}
|
reminderNote: string | null | undefined
|
||||||
|
) => {
|
||||||
|
await onSaveSecret(
|
||||||
|
secret,
|
||||||
|
{ ...secret, reminderRepeatDays, reminderNote, isReminderEvent: true },
|
||||||
|
() => {}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const [createReminderFormOpen, setCreateReminderFormOpen] = useToggle(false);
|
const [createReminderFormOpen, setCreateReminderFormOpen] = useToggle(false);
|
||||||
|
|
||||||
@@ -228,7 +236,7 @@ export const SecretDetailSidebar = ({
|
|||||||
if (data) {
|
if (data) {
|
||||||
setValue("reminderRepeatDays", data.days, { shouldDirty: false });
|
setValue("reminderRepeatDays", data.days, { shouldDirty: false });
|
||||||
setValue("reminderNote", data.note, { shouldDirty: false });
|
setValue("reminderNote", data.note, { shouldDirty: false });
|
||||||
handleReminderSubmit(data.days, data.note)
|
handleReminderSubmit(data.days, data.note);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -278,6 +286,7 @@ export const SecretDetailSidebar = ({
|
|||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<FormControl
|
<FormControl
|
||||||
|
className="flex-1"
|
||||||
helperText={
|
helperText={
|
||||||
cannotReadSecretValue ? (
|
cannotReadSecretValue ? (
|
||||||
<div className="flex space-x-2">
|
<div className="flex space-x-2">
|
||||||
@@ -651,51 +660,34 @@ export const SecretDetailSidebar = ({
|
|||||||
<div className="mb-4flex-grow dark cursor-default text-sm text-bunker-300">
|
<div className="mb-4flex-grow dark cursor-default text-sm text-bunker-300">
|
||||||
<div className="mb-2 pl-1">Version History</div>
|
<div className="mb-2 pl-1">Version History</div>
|
||||||
<div className="thin-scrollbar flex h-48 flex-col space-y-2 overflow-y-auto overflow-x-hidden rounded-md border border-mineshaft-600 bg-mineshaft-900 p-4 dark:[color-scheme:dark]">
|
<div className="thin-scrollbar flex h-48 flex-col space-y-2 overflow-y-auto overflow-x-hidden rounded-md border border-mineshaft-600 bg-mineshaft-900 p-4 dark:[color-scheme:dark]">
|
||||||
{secretVersion?.map(({ createdAt, secretValue, version, id }) => (
|
{secretVersion?.map(
|
||||||
<div className="flex flex-row">
|
({ createdAt, secretValue, version, id, secretValueHidden }) => (
|
||||||
<div key={id} className="flex w-full flex-col space-y-1">
|
<div className="flex flex-row">
|
||||||
<div className="flex items-center">
|
<div key={id} className="flex w-full flex-col space-y-1">
|
||||||
<div className="w-10">
|
<div className="flex items-center">
|
||||||
<div className="w-fit rounded-md border border-mineshaft-600 bg-mineshaft-700 px-1 text-sm text-mineshaft-300">
|
<div className="w-10">
|
||||||
v{version}
|
<div className="w-fit rounded-md border border-mineshaft-600 bg-mineshaft-700 px-1 text-sm text-mineshaft-300">
|
||||||
|
v{version}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div>{format(new Date(createdAt), "Pp")}</div>
|
||||||
</div>
|
</div>
|
||||||
<div>{format(new Date(createdAt), "Pp")}</div>
|
<div className="flex w-full cursor-default">
|
||||||
</div>
|
<div className="relative w-10">
|
||||||
<div className="flex w-full cursor-default">
|
<div className="absolute bottom-0 left-3 top-0 mt-0.5 border-l border-mineshaft-400/60" />
|
||||||
<div className="relative w-10">
|
|
||||||
<div className="absolute bottom-0 left-3 top-0 mt-0.5 border-l border-mineshaft-400/60" />
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-row">
|
|
||||||
<div className="h-min w-fit rounded-sm bg-primary-500/10 px-1 text-primary-300/70">
|
|
||||||
Value:
|
|
||||||
</div>
|
</div>
|
||||||
<div className="group break-all pl-1 font-mono">
|
<div className="flex flex-row">
|
||||||
<div className="relative hidden cursor-pointer transition-all duration-200 group-[.show-value]:inline">
|
<div className="h-min w-fit rounded-sm bg-primary-500/10 px-1 text-primary-300/70">
|
||||||
<button
|
Value:
|
||||||
type="button"
|
</div>
|
||||||
className="select-none"
|
<div className="group break-all pl-1 font-mono">
|
||||||
onClick={(e) => {
|
<div className="relative hidden cursor-pointer transition-all duration-200 group-[.show-value]:inline">
|
||||||
navigator.clipboard.writeText(secretValue || "");
|
<button
|
||||||
const target = e.currentTarget;
|
type="button"
|
||||||
target.style.borderBottom = "1px dashed";
|
className="select-none"
|
||||||
target.style.paddingBottom = "-1px";
|
onClick={(e) => {
|
||||||
|
if (secretValueHidden) return;
|
||||||
|
|
||||||
// Create and insert popup
|
|
||||||
const popup = document.createElement("div");
|
|
||||||
popup.className =
|
|
||||||
"w-16 flex justify-center absolute top-6 left-0 text-xs text-primary-100 bg-mineshaft-800 px-1 py-0.5 rounded-md border border-primary-500/50";
|
|
||||||
popup.textContent = "Copied!";
|
|
||||||
target.parentElement?.appendChild(popup);
|
|
||||||
|
|
||||||
// Remove popup and border after delay
|
|
||||||
setTimeout(() => {
|
|
||||||
popup.remove();
|
|
||||||
target.style.borderBottom = "none";
|
|
||||||
}, 3000);
|
|
||||||
}}
|
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (e.key === "Enter" || e.key === " ") {
|
|
||||||
navigator.clipboard.writeText(secretValue || "");
|
navigator.clipboard.writeText(secretValue || "");
|
||||||
const target = e.currentTarget;
|
const target = e.currentTarget;
|
||||||
target.style.borderBottom = "1px dashed";
|
target.style.borderBottom = "1px dashed";
|
||||||
@@ -713,72 +705,109 @@ export const SecretDetailSidebar = ({
|
|||||||
popup.remove();
|
popup.remove();
|
||||||
target.style.borderBottom = "none";
|
target.style.borderBottom = "none";
|
||||||
}, 3000);
|
}, 3000);
|
||||||
}
|
}}
|
||||||
}}
|
onKeyDown={(e) => {
|
||||||
>
|
if (secretValueHidden) return;
|
||||||
{secretValue}
|
|
||||||
</button>
|
if (e.key === "Enter" || e.key === " ") {
|
||||||
<button
|
navigator.clipboard.writeText(secretValue || "");
|
||||||
type="button"
|
const target = e.currentTarget;
|
||||||
className="ml-1 cursor-pointer"
|
target.style.borderBottom = "1px dashed";
|
||||||
onClick={(e) => {
|
target.style.paddingBottom = "-1px";
|
||||||
e.stopPropagation();
|
|
||||||
e.currentTarget
|
// Create and insert popup
|
||||||
.closest(".group")
|
const popup = document.createElement("div");
|
||||||
?.classList.remove("show-value");
|
popup.className =
|
||||||
}}
|
"w-16 flex justify-center absolute top-6 left-0 text-xs text-primary-100 bg-mineshaft-800 px-1 py-0.5 rounded-md border border-primary-500/50";
|
||||||
onKeyDown={(e) => {
|
popup.textContent = "Copied!";
|
||||||
if (e.key === "Enter" || e.key === " ") {
|
target.parentElement?.appendChild(popup);
|
||||||
|
|
||||||
|
// Remove popup and border after delay
|
||||||
|
setTimeout(() => {
|
||||||
|
popup.remove();
|
||||||
|
target.style.borderBottom = "none";
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Tooltip
|
||||||
|
className="break-normal text-xs"
|
||||||
|
content="You do not have permission to view this secret value"
|
||||||
|
isDisabled={!secretValueHidden}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={twMerge(
|
||||||
|
secretValueHidden && "text-xs text-bunker-300 opacity-40"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{secretValueHidden ? "Hidden" : secretValue}
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="ml-1 cursor-pointer"
|
||||||
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.currentTarget
|
e.currentTarget
|
||||||
.closest(".group")
|
.closest(".group")
|
||||||
?.classList.remove("show-value");
|
?.classList.remove("show-value");
|
||||||
}
|
}}
|
||||||
}}
|
onKeyDown={(e) => {
|
||||||
>
|
if (e.key === "Enter" || e.key === " ") {
|
||||||
<FontAwesomeIcon icon={faEyeSlash} />
|
e.stopPropagation();
|
||||||
</button>
|
e.currentTarget
|
||||||
</div>
|
.closest(".group")
|
||||||
<span className="group-[.show-value]:hidden">
|
?.classList.remove("show-value");
|
||||||
{secretValue?.replace(/./g, "*")}
|
}
|
||||||
<button
|
}}
|
||||||
type="button"
|
>
|
||||||
className="ml-1 cursor-pointer"
|
<FontAwesomeIcon icon={faEyeSlash} />
|
||||||
onClick={(e) => {
|
</button>
|
||||||
e.currentTarget.closest(".group")?.classList.add("show-value");
|
</div>
|
||||||
}}
|
<span className="group-[.show-value]:hidden">
|
||||||
onKeyDown={(e) => {
|
{secretValueHidden ? "******" : secretValue?.replace(/./g, "*")}
|
||||||
if (e.key === "Enter" || e.key === " ") {
|
<button
|
||||||
|
type="button"
|
||||||
|
className="ml-1 cursor-pointer"
|
||||||
|
onClick={(e) => {
|
||||||
e.currentTarget
|
e.currentTarget
|
||||||
.closest(".group")
|
.closest(".group")
|
||||||
?.classList.add("show-value");
|
?.classList.add("show-value");
|
||||||
}
|
}}
|
||||||
}}
|
onKeyDown={(e) => {
|
||||||
>
|
if (e.key === "Enter" || e.key === " ") {
|
||||||
<FontAwesomeIcon icon={faEye} />
|
e.currentTarget
|
||||||
</button>
|
.closest(".group")
|
||||||
</span>
|
?.classList.add("show-value");
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faEye} />
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
className={`flex items-center justify-center ${version === secretVersion.length ? "hidden" : ""}`}
|
||||||
|
>
|
||||||
|
<Tooltip content="Restore Secret Value">
|
||||||
|
<IconButton
|
||||||
|
ariaLabel="Restore"
|
||||||
|
variant="outline_bg"
|
||||||
|
size="sm"
|
||||||
|
className="h-8 w-8 rounded-md"
|
||||||
|
onClick={() => setValue("value", secretValue)}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faArrowRotateRight} />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
)
|
||||||
className={`flex items-center justify-center ${version === secretVersion.length ? "hidden" : ""}`}
|
)}
|
||||||
>
|
|
||||||
<Tooltip content="Restore Secret Value">
|
|
||||||
<IconButton
|
|
||||||
ariaLabel="Restore"
|
|
||||||
variant="outline_bg"
|
|
||||||
size="sm"
|
|
||||||
className="h-8 w-8 rounded-md"
|
|
||||||
onClick={() => setValue("value", secretValue)}
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon icon={faArrowRotateRight} />
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="dark mb-4 flex-grow text-sm text-bunker-300">
|
<div className="dark mb-4 flex-grow text-sm text-bunker-300">
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import {
|
|||||||
} from "@app/components/secrets/SecretReferenceDetails";
|
} from "@app/components/secrets/SecretReferenceDetails";
|
||||||
|
|
||||||
import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types";
|
import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types";
|
||||||
|
import { Blur } from "@app/components/v2/Blur";
|
||||||
import {
|
import {
|
||||||
FontAwesomeSpriteName,
|
FontAwesomeSpriteName,
|
||||||
formSchema,
|
formSchema,
|
||||||
@@ -283,15 +284,7 @@ export const SecretItem = memo(
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
) : secretValueHidden ? (
|
) : secretValueHidden ? (
|
||||||
<Tooltip content="You do not have permission to read the value of this secret.">
|
<Blur tooltipText="You do not have permission to read the value of this secret." />
|
||||||
<div
|
|
||||||
className="flex w-80 flex-grow items-center py-1 pl-4 pr-2"
|
|
||||||
tabIndex={0}
|
|
||||||
role="button"
|
|
||||||
>
|
|
||||||
<span className="blur">********</span>
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
) : (
|
) : (
|
||||||
<Controller
|
<Controller
|
||||||
name="value"
|
name="value"
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { FontAwesomeSymbol, Input, Tooltip } from "@app/components/v2";
|
import { FontAwesomeSymbol, Input, Tooltip } from "@app/components/v2";
|
||||||
|
import { Blur } from "@app/components/v2/Blur";
|
||||||
|
|
||||||
import { FontAwesomeSpriteName } from "./SecretListView.utils";
|
import { FontAwesomeSpriteName } from "./SecretListView.utils";
|
||||||
|
|
||||||
@@ -34,13 +35,7 @@ export const SecretNoAccessListView = ({ count }: Props) => {
|
|||||||
className="w-full px-0 blur-sm placeholder:text-red-500 focus:text-bunker-100 focus:ring-transparent"
|
className="w-full px-0 blur-sm placeholder:text-red-500 focus:text-bunker-100 focus:ring-transparent"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<Blur />
|
||||||
className="flex w-80 flex-grow items-center border-x border-mineshaft-600 py-1 pl-4 pr-2"
|
|
||||||
tabIndex={0}
|
|
||||||
role="button"
|
|
||||||
>
|
|
||||||
<span className="blur">********</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
Tr
|
Tr
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
|
import { Blur } from "@app/components/v2/Blur";
|
||||||
import { useToggle } from "@app/hooks";
|
import { useToggle } from "@app/hooks";
|
||||||
import { SecretV3RawSanitized } from "@app/hooks/api/secrets/types";
|
import { SecretV3RawSanitized } from "@app/hooks/api/secrets/types";
|
||||||
|
|
||||||
@@ -120,11 +121,25 @@ export const SecretItem = ({ mode, preSecret, postSecret }: Props) => {
|
|||||||
<Td className="border-r border-mineshaft-600">Value</Td>
|
<Td className="border-r border-mineshaft-600">Value</Td>
|
||||||
{isModified && (
|
{isModified && (
|
||||||
<Td className="border-r border-mineshaft-600">
|
<Td className="border-r border-mineshaft-600">
|
||||||
<SecretInput value={preSecret?.value} />
|
{preSecret?.secretValueHidden ? (
|
||||||
|
<Blur
|
||||||
|
className="w-min"
|
||||||
|
tooltipText="You do not have permission to read the value of this secret."
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<SecretInput value={preSecret?.value} />
|
||||||
|
)}
|
||||||
</Td>
|
</Td>
|
||||||
)}
|
)}
|
||||||
<Td>
|
<Td>
|
||||||
<SecretInput value={postSecret?.value} />
|
{postSecret?.secretValueHidden ? (
|
||||||
|
<Blur
|
||||||
|
className="w-min"
|
||||||
|
tooltipText="You do not have permission to read the value of this secret."
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<SecretInput value={postSecret?.value} />
|
||||||
|
)}
|
||||||
</Td>
|
</Td>
|
||||||
</Tr>
|
</Tr>
|
||||||
{Boolean(preSecret?.idOverride || postSecret?.idOverride) && (
|
{Boolean(preSecret?.idOverride || postSecret?.idOverride) && (
|
||||||
|
|||||||
Reference in New Issue
Block a user