mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 22:27:22 +00:00
fix: handle group memberships in secret versions history and fix secret versions query to always return all versions
This commit is contained in:
@@ -141,7 +141,8 @@ export const secretRawSchema = z.object({
|
|||||||
actorId: z.string().nullable().optional(),
|
actorId: z.string().nullable().optional(),
|
||||||
actorType: z.string().nullable().optional(),
|
actorType: z.string().nullable().optional(),
|
||||||
name: z.string().nullable().optional(),
|
name: z.string().nullable().optional(),
|
||||||
membershipId: z.string().nullable().optional()
|
membershipId: z.string().nullable().optional(),
|
||||||
|
groupId: z.string().nullable().optional()
|
||||||
})
|
})
|
||||||
.optional()
|
.optional()
|
||||||
.nullable(),
|
.nullable(),
|
||||||
|
|||||||
@@ -793,6 +793,7 @@ export const reshapeBridgeSecret = (
|
|||||||
userActorId?: string | null;
|
userActorId?: string | null;
|
||||||
identityActorId?: string | null;
|
identityActorId?: string | null;
|
||||||
membershipId?: string | null;
|
membershipId?: string | null;
|
||||||
|
groupId?: string | null;
|
||||||
actorType?: string | null;
|
actorType?: string | null;
|
||||||
tags?: {
|
tags?: {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -823,7 +824,8 @@ export const reshapeBridgeSecret = (
|
|||||||
actorType: secret.actorType,
|
actorType: secret.actorType,
|
||||||
actorId: secret.userActorId || secret.identityActorId,
|
actorId: secret.userActorId || secret.identityActorId,
|
||||||
name: secret.identityActorName || secret.userActorName,
|
name: secret.identityActorName || secret.userActorName,
|
||||||
membershipId: secret.membershipId
|
membershipId: secret.membershipId,
|
||||||
|
groupId: secret.groupId
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
tags: secret.tags,
|
tags: secret.tags,
|
||||||
|
|||||||
@@ -182,7 +182,6 @@ export const secretVersionV2BridgeDALFactory = (db: TDbClient) => {
|
|||||||
|
|
||||||
const findVersionsBySecretIdWithActors = async ({
|
const findVersionsBySecretIdWithActors = async ({
|
||||||
secretId,
|
secretId,
|
||||||
projectId,
|
|
||||||
secretVersions,
|
secretVersions,
|
||||||
findOpt = {},
|
findOpt = {},
|
||||||
tx
|
tx
|
||||||
@@ -196,11 +195,19 @@ export const secretVersionV2BridgeDALFactory = (db: TDbClient) => {
|
|||||||
try {
|
try {
|
||||||
const { offset, limit, sort = [["createdAt", "desc"]] } = findOpt;
|
const { offset, limit, sort = [["createdAt", "desc"]] } = findOpt;
|
||||||
const query = (tx || db.replicaNode())(TableName.SecretVersionV2)
|
const query = (tx || db.replicaNode())(TableName.SecretVersionV2)
|
||||||
|
.leftJoin(TableName.SecretFolder, `${TableName.SecretFolder}.id`, `${TableName.SecretVersionV2}.folderId`)
|
||||||
|
.leftJoin(TableName.Environment, `${TableName.Environment}.id`, `${TableName.SecretFolder}.envId`)
|
||||||
.leftJoin(TableName.Users, `${TableName.Users}.id`, `${TableName.SecretVersionV2}.userActorId`)
|
.leftJoin(TableName.Users, `${TableName.Users}.id`, `${TableName.SecretVersionV2}.userActorId`)
|
||||||
|
.leftJoin(TableName.UserGroupMembership, `${TableName.UserGroupMembership}.userId`, `${TableName.Users}.id`)
|
||||||
.leftJoin(TableName.Membership, (qb) => {
|
.leftJoin(TableName.Membership, (qb) => {
|
||||||
void qb
|
void qb
|
||||||
.on(`${TableName.Membership}.actorUserId`, `${TableName.SecretVersionV2}.userActorId`)
|
.on(`${TableName.Membership}.scope`, db.raw("?", [AccessScope.Project]))
|
||||||
.andOn(`${TableName.Membership}.scope`, db.raw("?", [AccessScope.Project]));
|
.andOn(`${TableName.Membership}.scopeProjectId`, `${TableName.Environment}.projectId`)
|
||||||
|
.andOn((sqb) => {
|
||||||
|
void sqb
|
||||||
|
.on(`${TableName.Membership}.actorUserId`, `${TableName.SecretVersionV2}.userActorId`)
|
||||||
|
.orOn(`${TableName.Membership}.actorGroupId`, `${TableName.UserGroupMembership}.groupId`);
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.leftJoin(TableName.Identity, `${TableName.Identity}.id`, `${TableName.SecretVersionV2}.identityActorId`)
|
.leftJoin(TableName.Identity, `${TableName.Identity}.id`, `${TableName.SecretVersionV2}.identityActorId`)
|
||||||
.leftJoin(TableName.SecretV2, `${TableName.SecretVersionV2}.secretId`, `${TableName.SecretV2}.id`)
|
.leftJoin(TableName.SecretV2, `${TableName.SecretVersionV2}.secretId`, `${TableName.SecretV2}.id`)
|
||||||
@@ -216,12 +223,6 @@ export const secretVersionV2BridgeDALFactory = (db: TDbClient) => {
|
|||||||
)
|
)
|
||||||
.where((qb) => {
|
.where((qb) => {
|
||||||
void qb.where(`${TableName.SecretVersionV2}.secretId`, secretId);
|
void qb.where(`${TableName.SecretVersionV2}.secretId`, secretId);
|
||||||
void qb.where(`${TableName.Membership}.scopeProjectId`, projectId);
|
|
||||||
if (secretVersions?.length) void qb.whereIn(`${TableName.SecretVersionV2}.version`, secretVersions);
|
|
||||||
})
|
|
||||||
.orWhere((qb) => {
|
|
||||||
void qb.where(`${TableName.SecretVersionV2}.secretId`, secretId);
|
|
||||||
void qb.whereNull(`${TableName.Membership}.scopeProjectId`);
|
|
||||||
if (secretVersions?.length) void qb.whereIn(`${TableName.SecretVersionV2}.version`, secretVersions);
|
if (secretVersions?.length) void qb.whereIn(`${TableName.SecretVersionV2}.version`, secretVersions);
|
||||||
})
|
})
|
||||||
.select(
|
.select(
|
||||||
@@ -229,6 +230,7 @@ export const secretVersionV2BridgeDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("username").withSchema(TableName.Users).as("userActorName"),
|
db.ref("username").withSchema(TableName.Users).as("userActorName"),
|
||||||
db.ref("name").withSchema(TableName.Identity).as("identityActorName"),
|
db.ref("name").withSchema(TableName.Identity).as("identityActorName"),
|
||||||
db.ref("id").withSchema(TableName.Membership).as("membershipId"),
|
db.ref("id").withSchema(TableName.Membership).as("membershipId"),
|
||||||
|
db.ref("actorGroupId").withSchema(TableName.Membership).as("groupId"),
|
||||||
db.ref("id").withSchema(TableName.SecretTag).as("tagId"),
|
db.ref("id").withSchema(TableName.SecretTag).as("tagId"),
|
||||||
db.ref("color").withSchema(TableName.SecretTag).as("tagColor"),
|
db.ref("color").withSchema(TableName.SecretTag).as("tagColor"),
|
||||||
db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug")
|
db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug")
|
||||||
@@ -256,7 +258,8 @@ export const secretVersionV2BridgeDALFactory = (db: TDbClient) => {
|
|||||||
...SecretVersionsV2Schema.parse(el),
|
...SecretVersionsV2Schema.parse(el),
|
||||||
userActorName: el.userActorName,
|
userActorName: el.userActorName,
|
||||||
identityActorName: el.identityActorName,
|
identityActorName: el.identityActorName,
|
||||||
membershipId: el.membershipId
|
membershipId: el.membershipId,
|
||||||
|
groupId: el.groupId
|
||||||
}),
|
}),
|
||||||
childrenMapper: [
|
childrenMapper: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ export type SecretVersions = {
|
|||||||
actorType?: string | null;
|
actorType?: string | null;
|
||||||
name?: string | null;
|
name?: string | null;
|
||||||
membershipId?: string | null;
|
membershipId?: string | null;
|
||||||
|
groupId?: string | null;
|
||||||
} | null;
|
} | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+18
-1
@@ -1,4 +1,4 @@
|
|||||||
import { useMemo } from "react";
|
import { useEffect, useMemo } from "react";
|
||||||
import {
|
import {
|
||||||
faArrowDown,
|
faArrowDown,
|
||||||
faArrowUp,
|
faArrowUp,
|
||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
faSearch
|
faSearch
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { useNavigate, useSearch } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import {
|
import {
|
||||||
@@ -48,6 +49,7 @@ enum GroupMembersOrderBy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const GroupMembersTable = ({ groupMembership }: Props) => {
|
export const GroupMembersTable = ({ groupMembership }: Props) => {
|
||||||
|
const navigate = useNavigate();
|
||||||
const {
|
const {
|
||||||
search,
|
search,
|
||||||
setSearch,
|
setSearch,
|
||||||
@@ -62,6 +64,21 @@ export const GroupMembersTable = ({ groupMembership }: Props) => {
|
|||||||
initPerPage: getUserTablePreference("projectGroupMembersTable", PreferenceKey.PerPage, 20)
|
initPerPage: getUserTablePreference("projectGroupMembersTable", PreferenceKey.PerPage, 20)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// this handles links from secret versions when the actor is in a group membership
|
||||||
|
const { username, ...restSearch } = useSearch({
|
||||||
|
strict: false
|
||||||
|
});
|
||||||
|
useEffect(() => {
|
||||||
|
if (username) {
|
||||||
|
setSearch(username);
|
||||||
|
navigate({
|
||||||
|
to: ".",
|
||||||
|
replace: true,
|
||||||
|
search: restSearch
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [username]);
|
||||||
|
|
||||||
const { handlePopUpToggle, popUp, handlePopUpOpen } = usePopUp(["assumePrivileges"] as const);
|
const { handlePopUpToggle, popUp, handlePopUpOpen } = usePopUp(["assumePrivileges"] as const);
|
||||||
|
|
||||||
const handlePerPageChange = (newPerPage: number) => {
|
const handlePerPageChange = (newPerPage: number) => {
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||||
|
import { zodValidator } from "@tanstack/zod-adapter";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
import { ProjectAccessControlTabs } from "@app/types/project";
|
import { ProjectAccessControlTabs } from "@app/types/project";
|
||||||
|
|
||||||
@@ -8,6 +10,11 @@ export const Route = createFileRoute(
|
|||||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/groups/$groupId"
|
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/groups/$groupId"
|
||||||
)({
|
)({
|
||||||
component: GroupDetailsByIDPage,
|
component: GroupDetailsByIDPage,
|
||||||
|
validateSearch: zodValidator(
|
||||||
|
z.object({
|
||||||
|
username: z.string().optional().catch(undefined)
|
||||||
|
})
|
||||||
|
),
|
||||||
beforeLoad: ({ context, params }) => {
|
beforeLoad: ({ context, params }) => {
|
||||||
return {
|
return {
|
||||||
breadcrumbs: [
|
breadcrumbs: [
|
||||||
|
|||||||
+46
-7
@@ -2,6 +2,7 @@ import { useState } from "react";
|
|||||||
import { faEye } from "@fortawesome/free-regular-svg-icons";
|
import { faEye } from "@fortawesome/free-regular-svg-icons";
|
||||||
import {
|
import {
|
||||||
faArrowRotateRight,
|
faArrowRotateRight,
|
||||||
|
faBan,
|
||||||
faDesktop,
|
faDesktop,
|
||||||
faEyeSlash,
|
faEyeSlash,
|
||||||
faServer,
|
faServer,
|
||||||
@@ -68,10 +69,14 @@ export const SecretVersionItem = ({
|
|||||||
const getLinkToModifyHistoryEntity = (
|
const getLinkToModifyHistoryEntity = (
|
||||||
actorId: string,
|
actorId: string,
|
||||||
actorType: string,
|
actorType: string,
|
||||||
membershipId: string | null = ""
|
membershipId: string | null = "",
|
||||||
|
groupId: string | null = "",
|
||||||
|
actorName: string | null = ""
|
||||||
) => {
|
) => {
|
||||||
switch (actorType) {
|
switch (actorType) {
|
||||||
case ActorType.USER:
|
case ActorType.USER:
|
||||||
|
if (groupId)
|
||||||
|
return `/projects/secret-management/${currentProject.id}/groups/${groupId}?username=${actorName}`;
|
||||||
return `/projects/secret-management/${currentProject.id}/members/${membershipId}`;
|
return `/projects/secret-management/${currentProject.id}/members/${membershipId}`;
|
||||||
case ActorType.IDENTITY:
|
case ActorType.IDENTITY:
|
||||||
return `/projects/secret-management/${currentProject.id}/identities/${actorId}`;
|
return `/projects/secret-management/${currentProject.id}/identities/${actorId}`;
|
||||||
@@ -83,10 +88,26 @@ export const SecretVersionItem = ({
|
|||||||
const onModifyHistoryClick = (
|
const onModifyHistoryClick = (
|
||||||
actorId: string | undefined | null,
|
actorId: string | undefined | null,
|
||||||
actorType: string | undefined | null,
|
actorType: string | undefined | null,
|
||||||
membershipId: string | undefined | null
|
membershipId: string | undefined | null,
|
||||||
|
groupId: string | undefined | null,
|
||||||
|
actorName: string | undefined | null
|
||||||
) => {
|
) => {
|
||||||
|
if (!membershipId) {
|
||||||
|
createNotification({
|
||||||
|
type: "info",
|
||||||
|
text: `This ${actorType === ActorType.USER ? "user" : "identity"} is no longer a member of this project.`
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (actorType && actorId && actorType !== ActorType.PLATFORM) {
|
if (actorType && actorId && actorType !== ActorType.PLATFORM) {
|
||||||
const redirectLink = getLinkToModifyHistoryEntity(actorId, actorType, membershipId);
|
const redirectLink = getLinkToModifyHistoryEntity(
|
||||||
|
actorId,
|
||||||
|
actorType,
|
||||||
|
membershipId,
|
||||||
|
groupId,
|
||||||
|
actorName
|
||||||
|
);
|
||||||
if (redirectLink) {
|
if (redirectLink) {
|
||||||
navigate({ to: redirectLink });
|
navigate({ to: redirectLink });
|
||||||
}
|
}
|
||||||
@@ -157,15 +178,33 @@ export const SecretVersionItem = ({
|
|||||||
<div className="flex flex-row">
|
<div className="flex flex-row">
|
||||||
<div className="flex w-fit flex-row text-sm">
|
<div className="flex w-fit flex-row text-sm">
|
||||||
Modified by:
|
Modified by:
|
||||||
<Tooltip content={getModifiedByName(actor.actorType, actor.name)}>
|
<Tooltip
|
||||||
|
className="z-[100] max-w-sm"
|
||||||
|
content={
|
||||||
|
getModifiedByName(actor.actorType, actor.name) +
|
||||||
|
(!actor.membershipId && actor.actorId ? " (Removed from project)" : "")
|
||||||
|
}
|
||||||
|
>
|
||||||
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
|
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
|
||||||
<div
|
<div
|
||||||
onClick={() =>
|
onClick={
|
||||||
onModifyHistoryClick(actor.actorId, actor.actorType, actor.membershipId)
|
actor.membershipId
|
||||||
|
? () =>
|
||||||
|
onModifyHistoryClick(
|
||||||
|
actor.actorId,
|
||||||
|
actor.actorType,
|
||||||
|
actor.membershipId,
|
||||||
|
actor.groupId,
|
||||||
|
actor.name
|
||||||
|
)
|
||||||
|
: undefined
|
||||||
}
|
}
|
||||||
className="cursor-pointer"
|
className={actor.membershipId ? "cursor-pointer" : undefined}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={getModifiedByIcon(actor.actorType)} className="ml-2" />
|
<FontAwesomeIcon icon={getModifiedByIcon(actor.actorType)} className="ml-2" />
|
||||||
|
{!actor.membershipId && (
|
||||||
|
<FontAwesomeIcon className="ml-1 text-mineshaft-400" icon={faBan} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user