mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 07:26:45 +00:00
feat: added manageby and conditional rendering more items
This commit is contained in:
@@ -408,7 +408,7 @@ export const samlConfigServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (dto.type === "orgSlug") {
|
} else if (dto.type === "orgSlug") {
|
||||||
const org = await orgDAL.findOne({ slug: dto.orgSlug });
|
const org = await orgDAL.findOne({ slug: dto.orgSlug, rootOrgId: null });
|
||||||
if (!org) {
|
if (!org) {
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
message: `Organization with slug '${dto.orgSlug}' not found`
|
message: `Organization with slug '${dto.orgSlug}' not found`
|
||||||
|
|||||||
@@ -393,7 +393,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
|
|||||||
permissions: true,
|
permissions: true,
|
||||||
description: true
|
description: true
|
||||||
}).optional(),
|
}).optional(),
|
||||||
identity: IdentitiesSchema.pick({ name: true, id: true, hasDeleteProtection: true }).extend({
|
identity: IdentitiesSchema.pick({ name: true, id: true, hasDeleteProtection: true, orgId: true }).extend({
|
||||||
authMethods: z.array(z.string())
|
authMethods: z.array(z.string())
|
||||||
})
|
})
|
||||||
}).array(),
|
}).array(),
|
||||||
|
|||||||
@@ -25,11 +25,12 @@ import { buildAuthMethods } from "../identity/identity-fns";
|
|||||||
export type TIdentityProjectDALFactory = ReturnType<typeof identityProjectDALFactory>;
|
export type TIdentityProjectDALFactory = ReturnType<typeof identityProjectDALFactory>;
|
||||||
|
|
||||||
export const identityProjectDALFactory = (db: TDbClient) => {
|
export const identityProjectDALFactory = (db: TDbClient) => {
|
||||||
const findByIdentityId = async (identityId: string, tx?: Knex) => {
|
const findByIdentityId = async (identityId: string, orgId: string, tx?: Knex) => {
|
||||||
try {
|
try {
|
||||||
const docs = await (tx || db.replicaNode())(TableName.Membership)
|
const docs = await (tx || db.replicaNode())(TableName.Membership)
|
||||||
.where(`${TableName.Membership}.actorIdentityId`, identityId)
|
.where(`${TableName.Membership}.actorIdentityId`, identityId)
|
||||||
.where(`${TableName.Membership}.scope`, AccessScope.Project)
|
.where(`${TableName.Membership}.scope`, AccessScope.Project)
|
||||||
|
.where(`${TableName.Membership}.scopeOrgId`, orgId)
|
||||||
.whereNotNull(`${TableName.Membership}.actorIdentityId`)
|
.whereNotNull(`${TableName.Membership}.actorIdentityId`)
|
||||||
.join(TableName.Project, `${TableName.Membership}.scopeProjectId`, `${TableName.Project}.id`)
|
.join(TableName.Project, `${TableName.Membership}.scopeProjectId`, `${TableName.Project}.id`)
|
||||||
.join(TableName.Identity, `${TableName.Membership}.actorIdentityId`, `${TableName.Identity}.id`)
|
.join(TableName.Identity, `${TableName.Membership}.actorIdentityId`, `${TableName.Identity}.id`)
|
||||||
|
|||||||
@@ -519,6 +519,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("actorIdentityId").withSchema(TableName.Membership).as("identityId"),
|
db.ref("actorIdentityId").withSchema(TableName.Membership).as("identityId"),
|
||||||
db.ref("name").withSchema(TableName.Identity).as("identityName"),
|
db.ref("name").withSchema(TableName.Identity).as("identityName"),
|
||||||
db.ref("hasDeleteProtection").withSchema(TableName.Identity),
|
db.ref("hasDeleteProtection").withSchema(TableName.Identity),
|
||||||
|
db.ref("orgId").withSchema(TableName.Identity).as("identityOrgId"),
|
||||||
|
|
||||||
db.ref("id").as("uaId").withSchema(TableName.IdentityUniversalAuth),
|
db.ref("id").as("uaId").withSchema(TableName.IdentityUniversalAuth),
|
||||||
db.ref("id").as("gcpId").withSchema(TableName.IdentityGcpAuth),
|
db.ref("id").as("gcpId").withSchema(TableName.IdentityGcpAuth),
|
||||||
@@ -570,6 +571,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
|||||||
crPermission,
|
crPermission,
|
||||||
crName,
|
crName,
|
||||||
identityId,
|
identityId,
|
||||||
|
identityOrgId,
|
||||||
identityName,
|
identityName,
|
||||||
hasDeleteProtection,
|
hasDeleteProtection,
|
||||||
role,
|
role,
|
||||||
@@ -615,6 +617,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
|||||||
id: identityId as string,
|
id: identityId as string,
|
||||||
name: identityName,
|
name: identityName,
|
||||||
hasDeleteProtection,
|
hasDeleteProtection,
|
||||||
|
orgId: identityOrgId,
|
||||||
authMethods: buildAuthMethods({
|
authMethods: buildAuthMethods({
|
||||||
uaId,
|
uaId,
|
||||||
alicloudId,
|
alicloudId,
|
||||||
|
|||||||
@@ -477,7 +477,7 @@ export const identityServiceFactory = ({
|
|||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
|
||||||
|
|
||||||
const identityMemberships = await identityProjectDAL.findByIdentityId(identityId);
|
const identityMemberships = await identityProjectDAL.findByIdentityId(identityId, actorOrgId);
|
||||||
return identityMemberships;
|
return identityMemberships;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useSuspenseQuery } from "@tanstack/react-query";
|
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||||
import { useRouteContext } from "@tanstack/react-router";
|
import { useRouteContext, useSearch } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { fetchOrganizationById, organizationKeys } from "@app/hooks/api/organization/queries";
|
import { fetchOrganizationById, organizationKeys } from "@app/hooks/api/organization/queries";
|
||||||
|
|
||||||
@@ -9,8 +9,13 @@ export const useOrganization = () => {
|
|||||||
select: (el) => el.organizationId
|
select: (el) => el.organizationId
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const subOrganization = useSearch({
|
||||||
|
strict: false,
|
||||||
|
select: (el) => el?.subOrganization
|
||||||
|
});
|
||||||
|
|
||||||
const { data: currentOrg } = useSuspenseQuery({
|
const { data: currentOrg } = useSuspenseQuery({
|
||||||
queryKey: organizationKeys.getOrgById(organizationId),
|
queryKey: organizationKeys.getOrgById(organizationId, subOrganization),
|
||||||
queryFn: () => fetchOrganizationById(organizationId),
|
queryFn: () => fetchOrganizationById(organizationId),
|
||||||
staleTime: Infinity
|
staleTime: Infinity
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export const organizationKeys = {
|
|||||||
[...organizationKeys.getOrgIdentityMemberships(orgId), params] as const,
|
[...organizationKeys.getOrgIdentityMemberships(orgId), params] as const,
|
||||||
getOrgGroups: (orgId: string) => [{ orgId }, "organization-groups"] as const,
|
getOrgGroups: (orgId: string) => [{ orgId }, "organization-groups"] as const,
|
||||||
getOrgIntegrationAuths: (orgId: string) => [{ orgId }, "integration-auths"] as const,
|
getOrgIntegrationAuths: (orgId: string) => [{ orgId }, "integration-auths"] as const,
|
||||||
getOrgById: (orgId: string) => ["organization", { orgId }],
|
getOrgById: (orgId: string, subOrg?: string) => ["organization", { orgId, subOrg }],
|
||||||
getAvailableIdentities: () => ["available-identities"],
|
getAvailableIdentities: () => ["available-identities"],
|
||||||
getAvailableUsers: () => ["available-users"]
|
getAvailableUsers: () => ["available-users"]
|
||||||
};
|
};
|
||||||
|
|||||||
+15
-3
@@ -2,6 +2,7 @@ import { useCallback, useState } from "react";
|
|||||||
import {
|
import {
|
||||||
faArrowDown,
|
faArrowDown,
|
||||||
faArrowUp,
|
faArrowUp,
|
||||||
|
faBuilding,
|
||||||
faCheckCircle,
|
faCheckCircle,
|
||||||
faChevronRight,
|
faChevronRight,
|
||||||
faEdit,
|
faEdit,
|
||||||
@@ -78,7 +79,7 @@ type Filter = {
|
|||||||
|
|
||||||
export const IdentityTable = ({ handlePopUpOpen }: Props) => {
|
export const IdentityTable = ({ handlePopUpOpen }: Props) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { currentOrg } = useOrganization();
|
const { currentOrg, isSubOrganization } = useOrganization();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
offset,
|
offset,
|
||||||
@@ -286,15 +287,18 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => {
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
</div>
|
</div>
|
||||||
</Th>
|
</Th>
|
||||||
|
{isSubOrganization && <Th>Managed By</Th>}
|
||||||
<Th className="w-16">{isFetching ? <Spinner size="xs" /> : null}</Th>
|
<Th className="w-16">{isFetching ? <Spinner size="xs" /> : null}</Th>
|
||||||
</Tr>
|
</Tr>
|
||||||
</THead>
|
</THead>
|
||||||
<TBody>
|
<TBody>
|
||||||
{isPending && <TableSkeleton columns={3} innerKey="org-identities" />}
|
{isPending && (
|
||||||
|
<TableSkeleton columns={isSubOrganization ? 4 : 3} innerKey="org-identities" />
|
||||||
|
)}
|
||||||
{!isPending &&
|
{!isPending &&
|
||||||
data?.identities?.map(
|
data?.identities?.map(
|
||||||
({
|
({
|
||||||
identity: { id, name },
|
identity: { id, name, orgId },
|
||||||
role,
|
role,
|
||||||
customRole,
|
customRole,
|
||||||
lastLoginAuthMethod,
|
lastLoginAuthMethod,
|
||||||
@@ -362,6 +366,14 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => {
|
|||||||
}}
|
}}
|
||||||
</OrgPermissionCan>
|
</OrgPermissionCan>
|
||||||
</Td>
|
</Td>
|
||||||
|
{isSubOrganization && (
|
||||||
|
<Td>
|
||||||
|
<p className="truncate">
|
||||||
|
<FontAwesomeIcon size="sm" className="mr-1.5" icon={faBuilding} />
|
||||||
|
{currentOrg.id === orgId ? "Organization" : "Root Organization"}
|
||||||
|
</p>
|
||||||
|
</Td>
|
||||||
|
)}
|
||||||
<Td>
|
<Td>
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
|
|||||||
@@ -91,7 +91,11 @@ const Page = () => {
|
|||||||
<PageHeader scope="org" description="Organization Identity" title={data.identity.name} />
|
<PageHeader scope="org" description="Organization Identity" title={data.identity.name} />
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<div className="mr-4 w-96">
|
<div className="mr-4 w-96">
|
||||||
<IdentityDetailsSection identityId={identityId} handlePopUpOpen={handlePopUpOpen} />
|
<IdentityDetailsSection
|
||||||
|
isOrgIdentity={data.identity.orgId === currentOrg.id}
|
||||||
|
identityId={identityId}
|
||||||
|
handlePopUpOpen={handlePopUpOpen}
|
||||||
|
/>
|
||||||
{!isAuthHidden && (
|
{!isAuthHidden && (
|
||||||
<IdentityAuthenticationSection
|
<IdentityAuthenticationSection
|
||||||
identityId={identityId}
|
identityId={identityId}
|
||||||
|
|||||||
+10
-1
@@ -21,7 +21,7 @@ import {
|
|||||||
Tag,
|
Tag,
|
||||||
Tooltip
|
Tooltip
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/context";
|
import { OrgPermissionIdentityActions, OrgPermissionSubjects, useOrganization } from "@app/context";
|
||||||
import { useTimedReset } from "@app/hooks";
|
import { useTimedReset } from "@app/hooks";
|
||||||
import { identityAuthToNameMap, useGetIdentityById } from "@app/hooks/api";
|
import { identityAuthToNameMap, useGetIdentityById } from "@app/hooks/api";
|
||||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||||
@@ -39,6 +39,7 @@ export const IdentityDetailsSection = ({ identityId, handlePopUpOpen, isOrgIdent
|
|||||||
const [copyTextId, isCopyingId, setCopyTextId] = useTimedReset<string>({
|
const [copyTextId, isCopyingId, setCopyTextId] = useTimedReset<string>({
|
||||||
initialState: "Copy ID to clipboard"
|
initialState: "Copy ID to clipboard"
|
||||||
});
|
});
|
||||||
|
const { isSubOrganization } = useOrganization();
|
||||||
|
|
||||||
const { data } = useGetIdentityById(identityId);
|
const { data } = useGetIdentityById(identityId);
|
||||||
return data ? (
|
return data ? (
|
||||||
@@ -142,6 +143,14 @@ export const IdentityDetailsSection = ({ identityId, handlePopUpOpen, isOrgIdent
|
|||||||
<p className="text-sm font-medium text-mineshaft-300">Name</p>
|
<p className="text-sm font-medium text-mineshaft-300">Name</p>
|
||||||
<p className="text-sm text-mineshaft-300">{data.identity.name}</p>
|
<p className="text-sm text-mineshaft-300">{data.identity.name}</p>
|
||||||
</div>
|
</div>
|
||||||
|
{isSubOrganization && (
|
||||||
|
<div className="mb-4">
|
||||||
|
<p className="text-sm font-medium text-mineshaft-300">Manage By</p>
|
||||||
|
<p className="text-sm text-mineshaft-300">
|
||||||
|
{isOrgIdentity ? "Organization" : "Root Organization"}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{isOrgIdentity && (
|
{isOrgIdentity && (
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<p className="text-sm font-medium text-mineshaft-300">Last Login Auth Method</p>
|
<p className="text-sm font-medium text-mineshaft-300">Last Login Auth Method</p>
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
import { useTimedReset } from "@app/hooks";
|
import { useTimedReset } from "@app/hooks";
|
||||||
import { useCreatePublicSharedSecret, useCreateSharedSecret } from "@app/hooks/api";
|
import { useCreatePublicSharedSecret, useCreateSharedSecret } from "@app/hooks/api";
|
||||||
import { SecretSharingAccessType } from "@app/hooks/api/secretSharing";
|
import { SecretSharingAccessType } from "@app/hooks/api/secretSharing";
|
||||||
|
import { useSearch } from "@tanstack/react-router";
|
||||||
|
|
||||||
// values in ms
|
// values in ms
|
||||||
const expiresInOptions = [
|
const expiresInOptions = [
|
||||||
@@ -87,6 +88,10 @@ export const ShareSecretForm = ({
|
|||||||
const [, isCopyingSecret, setCopyTextSecret] = useTimedReset<string>({
|
const [, isCopyingSecret, setCopyTextSecret] = useTimedReset<string>({
|
||||||
initialState: "Copy to clipboard"
|
initialState: "Copy to clipboard"
|
||||||
});
|
});
|
||||||
|
const subOrganization = useSearch({
|
||||||
|
strict: false,
|
||||||
|
select: (el) => el?.subOrganization
|
||||||
|
});
|
||||||
|
|
||||||
const publicSharedSecretCreator = useCreatePublicSharedSecret();
|
const publicSharedSecretCreator = useCreatePublicSharedSecret();
|
||||||
const privateSharedSecretCreator = useCreateSharedSecret();
|
const privateSharedSecretCreator = useCreateSharedSecret();
|
||||||
@@ -148,11 +153,14 @@ export const ShareSecretForm = ({
|
|||||||
type: "success"
|
type: "success"
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const link = `${window.location.origin}/shared/secret/${id}`;
|
const link = new URL(`${window.location.origin}/shared/secret/${id}`);
|
||||||
|
if (subOrganization) {
|
||||||
|
link.searchParams.set("subOrganization", subOrganization);
|
||||||
|
}
|
||||||
|
|
||||||
setSecretLink(link);
|
setSecretLink(link.toString());
|
||||||
|
|
||||||
navigator.clipboard.writeText(link);
|
navigator.clipboard.writeText(link.toString());
|
||||||
setCopyTextSecret("secret");
|
setCopyTextSecret("secret");
|
||||||
|
|
||||||
createNotification({
|
createNotification({
|
||||||
|
|||||||
Reference in New Issue
Block a user