Merge pull request #2136 from Infisical/polish-scim-groups

Add SCIM user activation/deactivation
This commit is contained in:
Maidul Islam
2024-07-16 12:09:50 -04:00
committed by GitHub
17 changed files with 182 additions and 55 deletions
@@ -0,0 +1,25 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
if (await knex.schema.hasTable(TableName.OrgMembership)) {
const doesUserIdExist = await knex.schema.hasColumn(TableName.OrgMembership, "userId");
const doesOrgIdExist = await knex.schema.hasColumn(TableName.OrgMembership, "orgId");
await knex.schema.alterTable(TableName.OrgMembership, (t) => {
t.boolean("isActive").notNullable().defaultTo(true);
if (doesUserIdExist && doesOrgIdExist) t.index(["userId", "orgId"]);
});
}
}
export async function down(knex: Knex): Promise<void> {
if (await knex.schema.hasTable(TableName.OrgMembership)) {
const doesUserIdExist = await knex.schema.hasColumn(TableName.OrgMembership, "userId");
const doesOrgIdExist = await knex.schema.hasColumn(TableName.OrgMembership, "orgId");
await knex.schema.alterTable(TableName.OrgMembership, (t) => {
t.dropColumn("isActive");
if (doesUserIdExist && doesOrgIdExist) t.dropIndex(["userId", "orgId"]);
});
}
}
+2 -1
View File
@@ -17,7 +17,8 @@ export const OrgMembershipsSchema = z.object({
userId: z.string().uuid().nullable().optional(), userId: z.string().uuid().nullable().optional(),
orgId: z.string().uuid(), orgId: z.string().uuid(),
roleId: z.string().uuid().nullable().optional(), roleId: z.string().uuid().nullable().optional(),
projectFavorites: z.string().array().nullable().optional() projectFavorites: z.string().array().nullable().optional(),
isActive: z.boolean()
}); });
export type TOrgMemberships = z.infer<typeof OrgMembershipsSchema>; export type TOrgMemberships = z.infer<typeof OrgMembershipsSchema>;
+2 -1
View File
@@ -29,7 +29,8 @@ export async function seed(knex: Knex): Promise<void> {
role: OrgMembershipRole.Admin, role: OrgMembershipRole.Admin,
orgId: org.id, orgId: org.id,
status: OrgMembershipStatus.Accepted, status: OrgMembershipStatus.Accepted,
userId: user.id userId: user.id,
isActive: true
} }
]); ]);
} }
+14 -2
View File
@@ -186,7 +186,13 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
}) })
), ),
displayName: z.string().trim(), displayName: z.string().trim(),
active: z.boolean() active: z.boolean(),
groups: z.array(
z.object({
value: z.string().trim(),
display: z.string().trim()
})
)
}) })
} }
}, },
@@ -572,7 +578,13 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
}) })
), ),
displayName: z.string().trim(), displayName: z.string().trim(),
active: z.boolean() active: z.boolean(),
groups: z.array(
z.object({
value: z.string().trim(),
display: z.string().trim()
})
)
}) })
} }
}, },
@@ -162,11 +162,26 @@ export const userGroupMembershipDALFactory = (db: TDbClient) => {
} }
}; };
const findUserGroupMembershipsInOrg = async (userId: string, orgId: string) => {
try {
const docs = await db
.replicaNode()(TableName.UserGroupMembership)
.join(TableName.Groups, `${TableName.UserGroupMembership}.groupId`, `${TableName.Groups}.id`)
.where(`${TableName.UserGroupMembership}.userId`, userId)
.where(`${TableName.Groups}.orgId`, orgId);
return docs;
} catch (error) {
throw new DatabaseError({ error, name: "findTest" });
}
};
return { return {
...userGroupMembershipOrm, ...userGroupMembershipOrm,
filterProjectsByUserMembership, filterProjectsByUserMembership,
findUserGroupMembershipsInProject, findUserGroupMembershipsInProject,
findGroupMembersNotInProject, findGroupMembersNotInProject,
deletePendingUserGroupMembershipsByUserIds deletePendingUserGroupMembershipsByUserIds,
findUserGroupMembershipsInOrg
}; };
}; };
@@ -449,7 +449,8 @@ export const ldapConfigServiceFactory = ({
userId: userAlias.userId, userId: userAlias.userId,
orgId, orgId,
role: OrgMembershipRole.Member, role: OrgMembershipRole.Member,
status: OrgMembershipStatus.Accepted status: OrgMembershipStatus.Accepted,
isActive: true
}, },
tx tx
); );
@@ -534,7 +535,8 @@ export const ldapConfigServiceFactory = ({
inviteEmail: email, inviteEmail: email,
orgId, orgId,
role: OrgMembershipRole.Member, role: OrgMembershipRole.Member,
status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
isActive: true
}, },
tx tx
); );
@@ -193,7 +193,8 @@ export const oidcConfigServiceFactory = ({
inviteEmail: email, inviteEmail: email,
orgId, orgId,
role: OrgMembershipRole.Member, role: OrgMembershipRole.Member,
status: foundUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later status: foundUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
isActive: true
}, },
tx tx
); );
@@ -266,7 +267,8 @@ export const oidcConfigServiceFactory = ({
inviteEmail: email, inviteEmail: email,
orgId, orgId,
role: OrgMembershipRole.Member, role: OrgMembershipRole.Member,
status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
isActive: true
}, },
tx tx
); );
@@ -370,7 +370,8 @@ export const samlConfigServiceFactory = ({
inviteEmail: email, inviteEmail: email,
orgId, orgId,
role: OrgMembershipRole.Member, role: OrgMembershipRole.Member,
status: foundUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later status: foundUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
isActive: true
}, },
tx tx
); );
@@ -457,7 +458,8 @@ export const samlConfigServiceFactory = ({
inviteEmail: email, inviteEmail: email,
orgId, orgId,
role: OrgMembershipRole.Member, role: OrgMembershipRole.Member,
status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
isActive: true
}, },
tx tx
); );
+12 -1
View File
@@ -32,12 +32,19 @@ export const parseScimFilter = (filterToParse: string | undefined) => {
return { [attributeName]: parsedValue.replace(/"/g, "") }; return { [attributeName]: parsedValue.replace(/"/g, "") };
}; };
export function extractScimValueFromPath(path: string): string | null {
const regex = /members\[value eq "([^"]+)"\]/;
const match = path.match(regex);
return match ? match[1] : null;
}
export const buildScimUser = ({ export const buildScimUser = ({
orgMembershipId, orgMembershipId,
username, username,
email, email,
firstName, firstName,
lastName, lastName,
groups = [],
active active
}: { }: {
orgMembershipId: string; orgMembershipId: string;
@@ -45,6 +52,10 @@ export const buildScimUser = ({
email?: string | null; email?: string | null;
firstName: string; firstName: string;
lastName: string; lastName: string;
groups?: {
value: string;
display: string;
}[];
active: boolean; active: boolean;
}): TScimUser => { }): TScimUser => {
const scimUser = { const scimUser = {
@@ -67,7 +78,7 @@ export const buildScimUser = ({
] ]
: [], : [],
active, active,
groups: [], groups,
meta: { meta: {
resourceType: "User", resourceType: "User",
location: null location: null
+53 -30
View File
@@ -30,7 +30,14 @@ import { UserAliasType } from "@app/services/user-alias/user-alias-types";
import { TLicenseServiceFactory } from "../license/license-service"; import { TLicenseServiceFactory } from "../license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission"; import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
import { TPermissionServiceFactory } from "../permission/permission-service"; import { TPermissionServiceFactory } from "../permission/permission-service";
import { buildScimGroup, buildScimGroupList, buildScimUser, buildScimUserList, parseScimFilter } from "./scim-fns"; import {
buildScimGroup,
buildScimGroupList,
buildScimUser,
buildScimUserList,
extractScimValueFromPath,
parseScimFilter
} from "./scim-fns";
import { import {
TCreateScimGroupDTO, TCreateScimGroupDTO,
TCreateScimTokenDTO, TCreateScimTokenDTO,
@@ -61,7 +68,7 @@ type TScimServiceFactoryDep = {
TOrgDALFactory, TOrgDALFactory,
"createMembership" | "findById" | "findMembership" | "deleteMembershipById" | "transaction" | "updateMembershipById" "createMembership" | "findById" | "findMembership" | "deleteMembershipById" | "transaction" | "updateMembershipById"
>; >;
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "find" | "findOne" | "create" | "updateById">; orgMembershipDAL: Pick<TOrgMembershipDALFactory, "find" | "findOne" | "create" | "updateById" | "findById">;
projectDAL: Pick<TProjectDALFactory, "find" | "findProjectGhostUser">; projectDAL: Pick<TProjectDALFactory, "find" | "findProjectGhostUser">;
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "find" | "delete" | "findProjectMembershipsByUserId">; projectMembershipDAL: Pick<TProjectMembershipDALFactory, "find" | "delete" | "findProjectMembershipsByUserId">;
groupDAL: Pick< groupDAL: Pick<
@@ -71,7 +78,12 @@ type TScimServiceFactoryDep = {
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">; groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
userGroupMembershipDAL: Pick< userGroupMembershipDAL: Pick<
TUserGroupMembershipDALFactory, TUserGroupMembershipDALFactory,
"find" | "transaction" | "insertMany" | "filterProjectsByUserMembership" | "delete" | "find"
| "transaction"
| "insertMany"
| "filterProjectsByUserMembership"
| "delete"
| "findUserGroupMembershipsInOrg"
>; >;
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "findLatestProjectKey" | "insertMany" | "delete">; projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "findLatestProjectKey" | "insertMany" | "delete">;
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">; projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
@@ -197,14 +209,14 @@ export const scimServiceFactory = ({
findOpts findOpts
); );
const scimUsers = users.map(({ id, externalId, username, firstName, lastName, email }) => const scimUsers = users.map(({ id, externalId, username, firstName, lastName, email, isActive }) =>
buildScimUser({ buildScimUser({
orgMembershipId: id ?? "", orgMembershipId: id ?? "",
username: externalId ?? username, username: externalId ?? username,
firstName: firstName ?? "", firstName: firstName ?? "",
lastName: lastName ?? "", lastName: lastName ?? "",
email, email,
active: true active: isActive
}) })
); );
@@ -240,13 +252,19 @@ export const scimServiceFactory = ({
status: 403 status: 403
}); });
const groupMembershipsInOrg = await userGroupMembershipDAL.findUserGroupMembershipsInOrg(membership.userId, orgId);
return buildScimUser({ return buildScimUser({
orgMembershipId: membership.id, orgMembershipId: membership.id,
username: membership.externalId ?? membership.username, username: membership.externalId ?? membership.username,
email: membership.email ?? "", email: membership.email ?? "",
firstName: membership.firstName as string, firstName: membership.firstName as string,
lastName: membership.lastName as string, lastName: membership.lastName as string,
active: true active: membership.isActive,
groups: groupMembershipsInOrg.map((group) => ({
value: group.groupId,
display: group.name
}))
}); });
}; };
@@ -296,7 +314,8 @@ export const scimServiceFactory = ({
inviteEmail: email, inviteEmail: email,
orgId, orgId,
role: OrgMembershipRole.Member, role: OrgMembershipRole.Member,
status: user.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later status: user.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
isActive: true
}, },
tx tx
); );
@@ -364,7 +383,8 @@ export const scimServiceFactory = ({
inviteEmail: email, inviteEmail: email,
orgId, orgId,
role: OrgMembershipRole.Member, role: OrgMembershipRole.Member,
status: user.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later status: user.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
isActive: true
}, },
tx tx
); );
@@ -401,7 +421,7 @@ export const scimServiceFactory = ({
firstName: createdUser.firstName as string, firstName: createdUser.firstName as string,
lastName: createdUser.lastName as string, lastName: createdUser.lastName as string,
email: createdUser.email ?? "", email: createdUser.email ?? "",
active: true active: createdOrgMembership.isActive
}); });
}; };
@@ -445,14 +465,8 @@ export const scimServiceFactory = ({
}); });
if (!active) { if (!active) {
await deleteOrgMembershipFn({ await orgMembershipDAL.updateById(membership.id, {
orgMembershipId: membership.id, isActive: false
orgId: membership.orgId,
orgDAL,
projectMembershipDAL,
projectKeyDAL,
userAliasDAL,
licenseService
}); });
} }
@@ -491,17 +505,11 @@ export const scimServiceFactory = ({
status: 403 status: 403
}); });
if (!active) { await orgMembershipDAL.updateById(membership.id, {
await deleteOrgMembershipFn({ isActive: active
orgMembershipId: membership.id, });
orgId: membership.orgId,
orgDAL, const groupMembershipsInOrg = await userGroupMembershipDAL.findUserGroupMembershipsInOrg(membership.userId, orgId);
projectMembershipDAL,
projectKeyDAL,
userAliasDAL,
licenseService
});
}
return buildScimUser({ return buildScimUser({
orgMembershipId: membership.id, orgMembershipId: membership.id,
@@ -509,7 +517,11 @@ export const scimServiceFactory = ({
email: membership.email, email: membership.email,
firstName: membership.firstName as string, firstName: membership.firstName as string,
lastName: membership.lastName as string, lastName: membership.lastName as string,
active active,
groups: groupMembershipsInOrg.map((group) => ({
value: group.groupId,
display: group.name
}))
}); });
}; };
@@ -881,7 +893,18 @@ export const scimServiceFactory = ({
break; break;
} }
case "remove": { case "remove": {
// TODO const orgMembershipId = extractScimValueFromPath(operation.path);
if (!orgMembershipId) throw new ScimRequestError({ detail: "Invalid path value", status: 400 });
const orgMembership = await orgMembershipDAL.findById(orgMembershipId);
if (!orgMembership) throw new ScimRequestError({ detail: "Org Membership Not Found", status: 400 });
await removeUsersFromGroupByUserIds({
group,
userIds: [orgMembership.userId as string],
userDAL,
userGroupMembershipDAL,
groupProjectDAL,
projectKeyDAL
});
break; break;
} }
default: { default: {
+4 -1
View File
@@ -158,7 +158,10 @@ export type TScimUser = {
type: string; type: string;
}[]; }[];
active: boolean; active: boolean;
groups: string[]; groups: {
value: string;
display: string;
}[];
meta: { meta: {
resourceType: string; resourceType: string;
location: null; location: null;
+1 -1
View File
@@ -345,7 +345,7 @@ export const registerRoutes = async (
permissionService, permissionService,
secretApprovalPolicyDAL secretApprovalPolicyDAL
}); });
const tokenService = tokenServiceFactory({ tokenDAL: authTokenDAL, userDAL }); const tokenService = tokenServiceFactory({ tokenDAL: authTokenDAL, userDAL, orgMembershipDAL });
const samlService = samlConfigServiceFactory({ const samlService = samlConfigServiceFactory({
permissionService, permissionService,
@@ -4,7 +4,8 @@ import bcrypt from "bcrypt";
import { TAuthTokens, TAuthTokenSessions } from "@app/db/schemas"; import { TAuthTokens, TAuthTokenSessions } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { UnauthorizedError } from "@app/lib/errors"; import { ForbiddenRequestError, UnauthorizedError } from "@app/lib/errors";
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
import { AuthModeJwtTokenPayload } from "../auth/auth-type"; import { AuthModeJwtTokenPayload } from "../auth/auth-type";
import { TUserDALFactory } from "../user/user-dal"; import { TUserDALFactory } from "../user/user-dal";
@@ -14,6 +15,7 @@ import { TCreateTokenForUserDTO, TIssueAuthTokenDTO, TokenType, TValidateTokenFo
type TAuthTokenServiceFactoryDep = { type TAuthTokenServiceFactoryDep = {
tokenDAL: TTokenDALFactory; tokenDAL: TTokenDALFactory;
userDAL: Pick<TUserDALFactory, "findById" | "transaction">; userDAL: Pick<TUserDALFactory, "findById" | "transaction">;
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "findOne">;
}; };
export type TAuthTokenServiceFactory = ReturnType<typeof tokenServiceFactory>; export type TAuthTokenServiceFactory = ReturnType<typeof tokenServiceFactory>;
@@ -67,7 +69,7 @@ export const getTokenConfig = (tokenType: TokenType) => {
} }
}; };
export const tokenServiceFactory = ({ tokenDAL, userDAL }: TAuthTokenServiceFactoryDep) => { export const tokenServiceFactory = ({ tokenDAL, userDAL, orgMembershipDAL }: TAuthTokenServiceFactoryDep) => {
const createTokenForUser = async ({ type, userId, orgId }: TCreateTokenForUserDTO) => { const createTokenForUser = async ({ type, userId, orgId }: TCreateTokenForUserDTO) => {
const { token, ...tkCfg } = getTokenConfig(type); const { token, ...tkCfg } = getTokenConfig(type);
const appCfg = getConfig(); const appCfg = getConfig();
@@ -154,6 +156,16 @@ export const tokenServiceFactory = ({ tokenDAL, userDAL }: TAuthTokenServiceFact
const user = await userDAL.findById(session.userId); const user = await userDAL.findById(session.userId);
if (!user || !user.isAccepted) throw new UnauthorizedError({ name: "Token user not found" }); if (!user || !user.isAccepted) throw new UnauthorizedError({ name: "Token user not found" });
if (token.organizationId) {
const orgMembership = await orgMembershipDAL.findOne({
userId: user.id,
orgId: token.organizationId
});
if (!orgMembership) throw new ForbiddenRequestError({ message: "User not member of organization" });
if (!orgMembership.isActive) throw new ForbiddenRequestError({ message: "User not active in organization" });
}
return { user, tokenVersionId: token.tokenVersionId, orgId: token.organizationId }; return { user, tokenVersionId: token.tokenVersionId, orgId: token.organizationId };
}; };
+1
View File
@@ -74,6 +74,7 @@ export const orgDALFactory = (db: TDbClient) => {
db.ref("role").withSchema(TableName.OrgMembership), db.ref("role").withSchema(TableName.OrgMembership),
db.ref("roleId").withSchema(TableName.OrgMembership), db.ref("roleId").withSchema(TableName.OrgMembership),
db.ref("status").withSchema(TableName.OrgMembership), db.ref("status").withSchema(TableName.OrgMembership),
db.ref("isActive").withSchema(TableName.OrgMembership),
db.ref("email").withSchema(TableName.Users), db.ref("email").withSchema(TableName.Users),
db.ref("username").withSchema(TableName.Users), db.ref("username").withSchema(TableName.Users),
db.ref("firstName").withSchema(TableName.Users), db.ref("firstName").withSchema(TableName.Users),
+8 -4
View File
@@ -204,7 +204,8 @@ export const orgServiceFactory = ({
orgId, orgId,
userId: user.id, userId: user.id,
role: OrgMembershipRole.Admin, role: OrgMembershipRole.Admin,
status: OrgMembershipStatus.Accepted status: OrgMembershipStatus.Accepted,
isActive: true
}; };
await orgDAL.createMembership(createMembershipData, tx); await orgDAL.createMembership(createMembershipData, tx);
@@ -308,7 +309,8 @@ export const orgServiceFactory = ({
userId, userId,
orgId: org.id, orgId: org.id,
role: OrgMembershipRole.Admin, role: OrgMembershipRole.Admin,
status: OrgMembershipStatus.Accepted status: OrgMembershipStatus.Accepted,
isActive: true
}, },
tx tx
); );
@@ -457,7 +459,8 @@ export const orgServiceFactory = ({
inviteEmail: inviteeEmail, inviteEmail: inviteeEmail,
orgId, orgId,
role: OrgMembershipRole.Member, role: OrgMembershipRole.Member,
status: OrgMembershipStatus.Invited status: OrgMembershipStatus.Invited,
isActive: true
}, },
tx tx
); );
@@ -488,7 +491,8 @@ export const orgServiceFactory = ({
orgId, orgId,
userId: user.id, userId: user.id,
role: OrgMembershipRole.Member, role: OrgMembershipRole.Member,
status: OrgMembershipStatus.Invited status: OrgMembershipStatus.Invited,
isActive: true
}, },
tx tx
); );
+1
View File
@@ -60,6 +60,7 @@ export type OrgUser = {
status: "invited" | "accepted" | "verified" | "completed"; status: "invited" | "accepted" | "verified" | "completed";
deniedPermissions: any[]; deniedPermissions: any[];
roleId: string; roleId: string;
isActive: boolean;
}; };
export type TProjectMembership = { export type TProjectMembership = {
@@ -171,14 +171,14 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLink }: Prop
{isLoading && <TableSkeleton columns={5} innerKey="org-members" />} {isLoading && <TableSkeleton columns={5} innerKey="org-members" />}
{!isLoading && {!isLoading &&
filterdUser?.map( filterdUser?.map(
({ user: u, inviteEmail, role, roleId, id: orgMembershipId, status }) => { ({ user: u, inviteEmail, role, roleId, id: orgMembershipId, status, isActive }) => {
const name = u && u.firstName ? `${u.firstName} ${u.lastName}` : "-"; const name = u && u.firstName ? `${u.firstName} ${u.lastName}` : "-";
const email = u?.email || inviteEmail; const email = u?.email || inviteEmail;
const username = u?.username ?? inviteEmail ?? "-"; const username = u?.username ?? inviteEmail ?? "-";
return ( return (
<Tr key={`org-membership-${orgMembershipId}`} className="w-full"> <Tr key={`org-membership-${orgMembershipId}`} className="w-full">
<Td>{name}</Td> <Td className={isActive ? "" : "text-mineshaft-400"}>{name}</Td>
<Td>{username}</Td> <Td className={isActive ? "" : "text-mineshaft-400"}>{username}</Td>
<Td> <Td>
<OrgPermissionCan <OrgPermissionCan
I={OrgPermissionActions.Edit} I={OrgPermissionActions.Edit}
@@ -186,7 +186,18 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLink }: Prop
> >
{(isAllowed) => ( {(isAllowed) => (
<> <>
{status === "accepted" && ( {!isActive && (
<Button
isDisabled
className="w-40"
colorSchema="primary"
variant="outline_bg"
onClick={() => {}}
>
Suspended
</Button>
)}
{isActive && status === "accepted" && (
<Select <Select
value={role === "custom" ? findRoleFromId(roleId)?.slug : role} value={role === "custom" ? findRoleFromId(roleId)?.slug : role}
isDisabled={userId === u?.id || !isAllowed} isDisabled={userId === u?.id || !isAllowed}
@@ -207,7 +218,8 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLink }: Prop
))} ))}
</Select> </Select>
)} )}
{(status === "invited" || status === "verified") && {isActive &&
(status === "invited" || status === "verified") &&
email && email &&
serverDetails?.emailConfigured && ( serverDetails?.emailConfigured && (
<Button <Button