mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 22:27:48 +00:00
Merge pull request #2136 from Infisical/polish-scim-groups
Add SCIM user activation/deactivation
This commit is contained in:
@@ -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"]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>;
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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: {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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 = {
|
||||||
|
|||||||
+17
-5
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user