mirror of
https://github.com/Infisical/infisical.git
synced 2025-03-22 11:45:48 +00:00
Merge pull request #1067 from Infisical/delete-org
Delete user, organization, project capabilities feature/update
This commit is contained in:
backend/src
controllers
v1
v2
ee
controllers
models
services/GithubSecretScanning
helpers
queues/secret-scanning
routes
utils/setup
validation
frontend/src
components/utilities
hooks/api
layouts/AppLayout
pages
views
Login
Org/NonePage
SecretScanning/components
Settings
OrgSettingsPage/components
OrgDeleteSection
OrgGeneralTab
OrgIncidentContactsSection
OrgNameChangeSection
PersonalSettingsPage
ProjectSettingsPage/components/DeleteProjectSection
@ -1,7 +1,7 @@
|
||||
import { Request, Response } from "express";
|
||||
import { Types } from "mongoose";
|
||||
import { IUser, Key, Membership, MembershipOrg, User, Workspace } from "../../models";
|
||||
import { EventType } from "../../ee/models";
|
||||
import { EventType, Role } from "../../ee/models";
|
||||
import { deleteMembership as deleteMember, findMembership } from "../../helpers/membership";
|
||||
import { sendMail } from "../../helpers/nodemailer";
|
||||
import { ACCEPTED, ADMIN, CUSTOM, MEMBER, VIEWER } from "../../variables";
|
||||
@ -15,7 +15,6 @@ import {
|
||||
getUserProjectPermissions
|
||||
} from "../../ee/services/ProjectRoleService";
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import Role from "../../ee/models/role";
|
||||
import { BadRequestError } from "../../utils/errors";
|
||||
import { InviteUserToWorkspaceV1 } from "../../validation/workspace";
|
||||
|
||||
|
@ -6,13 +6,11 @@ import {
|
||||
Organization,
|
||||
Workspace
|
||||
} from "../../models";
|
||||
import { createOrganization as create } from "../../helpers/organization";
|
||||
import { addMembershipsOrg } from "../../helpers/membershipOrg";
|
||||
import { ACCEPTED, ADMIN } from "../../variables";
|
||||
import { getLicenseServerUrl, getSiteURL } from "../../config";
|
||||
import { licenseServerKeyRequest } from "../../config/request";
|
||||
import { validateRequest } from "../../helpers/validation";
|
||||
import * as reqValidator from "../../validation/organization";
|
||||
import { ACCEPTED } from "../../variables";
|
||||
import {
|
||||
OrgPermissionActions,
|
||||
OrgPermissionSubjects,
|
||||
@ -34,36 +32,6 @@ export const getOrganizations = async (req: Request, res: Response) => {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Create new organization named [organizationName]
|
||||
* and add user as owner
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
*/
|
||||
export const createOrganization = async (req: Request, res: Response) => {
|
||||
const {
|
||||
body: { organizationName }
|
||||
} = await validateRequest(reqValidator.CreateOrgv1, req);
|
||||
|
||||
// create organization and add user as member
|
||||
const organization = await create({
|
||||
email: req.user.email,
|
||||
name: organizationName
|
||||
});
|
||||
|
||||
await addMembershipsOrg({
|
||||
userIds: [req.user._id.toString()],
|
||||
organizationId: organization._id.toString(),
|
||||
roles: [ADMIN],
|
||||
statuses: [ACCEPTED]
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
organization
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Return organization with id [organizationId]
|
||||
* @param req
|
||||
|
@ -1,12 +1,15 @@
|
||||
import { Request, Response } from "express";
|
||||
import GitAppInstallationSession from "../../ee/models/gitAppInstallationSession";
|
||||
import {
|
||||
GitAppInstallationSession,
|
||||
GitAppOrganizationInstallation,
|
||||
GitRisks
|
||||
} from "../../ee/models";
|
||||
import crypto from "crypto";
|
||||
import { Types } from "mongoose";
|
||||
import { OrganizationNotFoundError, UnauthorizedRequestError } from "../../utils/errors";
|
||||
import GitAppOrganizationInstallation from "../../ee/models/gitAppOrganizationInstallation";
|
||||
import { scanGithubFullRepoForSecretLeaks } from "../../queues/secret-scanning/githubScanFullRepository";
|
||||
import { getSecretScanningGitAppId, getSecretScanningPrivateKey } from "../../config";
|
||||
import GitRisks, {
|
||||
import {
|
||||
STATUS_RESOLVED_FALSE_POSITIVE,
|
||||
STATUS_RESOLVED_NOT_REVOKED,
|
||||
STATUS_RESOLVED_REVOKED
|
||||
|
@ -202,12 +202,12 @@ export const deleteWorkspace = async (req: Request, res: Response) => {
|
||||
);
|
||||
|
||||
// delete workspace
|
||||
await deleteWork({
|
||||
id: workspaceId
|
||||
const workspace = await deleteWork({
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
message: "Successfully deleted workspace"
|
||||
workspace
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -1,11 +1,25 @@
|
||||
import { Request, Response } from "express";
|
||||
import { Types } from "mongoose";
|
||||
import { Membership, MembershipOrg, ServiceAccount, Workspace } from "../../models";
|
||||
import {
|
||||
Membership,
|
||||
MembershipOrg,
|
||||
ServiceAccount,
|
||||
Workspace
|
||||
} from "../../models";
|
||||
import { Role } from "../../ee/models";
|
||||
import { deleteMembershipOrg } from "../../helpers/membershipOrg";
|
||||
import { updateSubscriptionOrgQuantity } from "../../helpers/organization";
|
||||
import Role from "../../ee/models/role";
|
||||
import { BadRequestError } from "../../utils/errors";
|
||||
import { CUSTOM } from "../../variables";
|
||||
import {
|
||||
createOrganization as create,
|
||||
deleteOrganization,
|
||||
updateSubscriptionOrgQuantity
|
||||
} from "../../helpers/organization";
|
||||
import { addMembershipsOrg } from "../../helpers/membershipOrg";
|
||||
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
|
||||
import {
|
||||
ACCEPTED,
|
||||
ADMIN,
|
||||
CUSTOM
|
||||
} from "../../variables";
|
||||
import * as reqValidator from "../../validation/organization";
|
||||
import { validateRequest } from "../../helpers/validation";
|
||||
import {
|
||||
@ -332,3 +346,60 @@ export const getOrganizationServiceAccounts = async (req: Request, res: Response
|
||||
serviceAccounts
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Create new organization named [organizationName]
|
||||
* and add user as owner
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
*/
|
||||
export const createOrganization = async (req: Request, res: Response) => {
|
||||
const {
|
||||
body: { name }
|
||||
} = await validateRequest(reqValidator.CreateOrgv2, req);
|
||||
|
||||
// create organization and add user as member
|
||||
const organization = await create({
|
||||
email: req.user.email,
|
||||
name
|
||||
});
|
||||
|
||||
await addMembershipsOrg({
|
||||
userIds: [req.user._id.toString()],
|
||||
organizationId: organization._id.toString(),
|
||||
roles: [ADMIN],
|
||||
statuses: [ACCEPTED]
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
organization
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete organization with id [organizationId]
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
export const deleteOrganizationById = async (req: Request, res: Response) => {
|
||||
const {
|
||||
params: { organizationId }
|
||||
} = await validateRequest(reqValidator.DeleteOrgv2, req);
|
||||
|
||||
const membershipOrg = await MembershipOrg.findOne({
|
||||
user: req.user._id,
|
||||
organization: new Types.ObjectId(organizationId),
|
||||
role: ADMIN
|
||||
});
|
||||
|
||||
if (!membershipOrg) throw UnauthorizedRequestError();
|
||||
|
||||
const organization = await deleteOrganization({
|
||||
organizationId: new Types.ObjectId(organizationId)
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
organization
|
||||
});
|
||||
}
|
@ -5,49 +5,9 @@ import bcrypt from "bcrypt";
|
||||
import { APIKeyData, AuthMethod, MembershipOrg, TokenVersion, User } from "../../models";
|
||||
import { getSaltRounds } from "../../config";
|
||||
import { validateRequest } from "../../helpers/validation";
|
||||
import { deleteUser } from "../../helpers/user";
|
||||
import * as reqValidator from "../../validation";
|
||||
|
||||
/**
|
||||
* Return the current user.
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
*/
|
||||
export const getMe = async (req: Request, res: Response) => {
|
||||
/*
|
||||
#swagger.summary = "Retrieve the current user on the request"
|
||||
#swagger.description = "Retrieve the current user on the request"
|
||||
|
||||
#swagger.security = [{
|
||||
"apiKeyAuth": []
|
||||
}]
|
||||
|
||||
#swagger.responses[200] = {
|
||||
content: {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"user": {
|
||||
"type": "object",
|
||||
$ref: "#/components/schemas/CurrentUser",
|
||||
"description": "Current user on request"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
const user = await User.findById(req.user._id).select(
|
||||
"+salt +publicKey +encryptedPrivateKey +iv +tag +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag"
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
user
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the current user's MFA-enabled status [isMfaEnabled].
|
||||
* Note: Infisical currently only supports email-based 2FA only; this will expand to
|
||||
@ -296,3 +256,59 @@ export const deleteMySessions = async (req: Request, res: Response) => {
|
||||
message: "Successfully revoked all sessions"
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the current user.
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
*/
|
||||
export const getMe = async (req: Request, res: Response) => {
|
||||
/*
|
||||
#swagger.summary = "Retrieve the current user on the request"
|
||||
#swagger.description = "Retrieve the current user on the request"
|
||||
|
||||
#swagger.security = [{
|
||||
"apiKeyAuth": []
|
||||
}]
|
||||
|
||||
#swagger.responses[200] = {
|
||||
content: {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"user": {
|
||||
"type": "object",
|
||||
$ref: "#/components/schemas/CurrentUser",
|
||||
"description": "Current user on request"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
const user = await User.findById(req.user._id).select(
|
||||
"+salt +publicKey +encryptedPrivateKey +iv +tag +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag"
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
user
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete the current user.
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
export const deleteMe = async (req: Request, res: Response) => {
|
||||
const user = await deleteUser({
|
||||
userId: req.user._id
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
user
|
||||
});
|
||||
}
|
@ -23,7 +23,7 @@ import {
|
||||
memberPermissions
|
||||
} from "../../services/RoleService";
|
||||
import { BadRequestError } from "../../../utils/errors";
|
||||
import Role from "../../models/role";
|
||||
import { Role } from "../../models";
|
||||
import { validateRequest } from "../../../helpers/validation";
|
||||
import { packRules } from "@casl/ability/extra";
|
||||
|
||||
@ -212,6 +212,7 @@ export const getUserPermissions = async (req: Request, res: Response) => {
|
||||
const {
|
||||
params: { orgId }
|
||||
} = await validateRequest(GetUserPermission, req);
|
||||
|
||||
const { permission } = await getUserOrgPermissions(req.user._id, orgId);
|
||||
|
||||
res.status(200).json({
|
||||
|
@ -298,6 +298,10 @@ export const deleteServiceTokenData = async (req: Request, res: Response) => {
|
||||
message: "Failed to delete service token"
|
||||
});
|
||||
|
||||
await ServiceTokenDataV3Key.findOneAndDelete({
|
||||
serviceTokenData: serviceTokenData._id
|
||||
});
|
||||
|
||||
await EEAuditLogService.createAuditLog(
|
||||
req.authData,
|
||||
{
|
||||
|
@ -29,6 +29,4 @@ const gitAppInstallationSession = new Schema<GitAppInstallationSession>({
|
||||
});
|
||||
|
||||
|
||||
const GitAppInstallationSession = model<GitAppInstallationSession>("git_app_installation_session", gitAppInstallationSession);
|
||||
|
||||
export default GitAppInstallationSession;
|
||||
export const GitAppInstallationSession = model<GitAppInstallationSession>("git_app_installation_session", gitAppInstallationSession);
|
@ -26,6 +26,4 @@ const gitAppOrganizationInstallation = new Schema<Installation>({
|
||||
});
|
||||
|
||||
|
||||
const GitAppOrganizationInstallation = model<Installation>("git_app_organization_installation", gitAppOrganizationInstallation);
|
||||
|
||||
export default GitAppOrganizationInstallation;
|
||||
export const GitAppOrganizationInstallation = model<Installation>("git_app_organization_installation", gitAppOrganizationInstallation);
|
@ -5,7 +5,7 @@ export const STATUS_RESOLVED_REVOKED = "RESOLVED_REVOKED";
|
||||
export const STATUS_RESOLVED_NOT_REVOKED = "RESOLVED_NOT_REVOKED";
|
||||
export const STATUS_UNRESOLVED = "UNRESOLVED";
|
||||
|
||||
export type GitRisks = {
|
||||
export type IGitRisks = {
|
||||
id: string;
|
||||
description: string;
|
||||
startLine: string;
|
||||
@ -42,7 +42,7 @@ export type GitRisks = {
|
||||
organization: Schema.Types.ObjectId,
|
||||
}
|
||||
|
||||
const gitRisks = new Schema<GitRisks>({
|
||||
const gitRisks = new Schema<IGitRisks>({
|
||||
id: {
|
||||
type: String,
|
||||
},
|
||||
@ -147,6 +147,4 @@ const gitRisks = new Schema<GitRisks>({
|
||||
}
|
||||
}, { timestamps: true });
|
||||
|
||||
const GitRisks = model<GitRisks>("GitRisks", gitRisks);
|
||||
|
||||
export default GitRisks;
|
||||
export const GitRisks = model<IGitRisks>("GitRisks", gitRisks);
|
@ -2,6 +2,7 @@ export * from "./secretSnapshot";
|
||||
export * from "./secretVersion";
|
||||
export * from "./folderVersion";
|
||||
export * from "./log";
|
||||
export * from "./role";
|
||||
export * from "./action";
|
||||
export * from "./ssoConfig";
|
||||
export * from "./trustedIp";
|
||||
@ -9,3 +10,5 @@ export * from "./auditLog";
|
||||
export * from "./gitRisks";
|
||||
export * from "./gitAppOrganizationInstallation";
|
||||
export * from "./gitAppInstallationSession";
|
||||
export * from "./secretApprovalPolicy";
|
||||
export * from "./secretApprovalRequest";
|
||||
|
@ -50,6 +50,4 @@ const roleSchema = new Schema<IRole>(
|
||||
|
||||
roleSchema.index({ organization: 1, workspace: 1 });
|
||||
|
||||
const Role = model<IRole>("Role", roleSchema);
|
||||
|
||||
export default Role;
|
||||
export const Role = model<IRole>("Role", roleSchema);
|
@ -1,6 +1,8 @@
|
||||
import { Probot } from "probot";
|
||||
import GitRisks from "../../models/gitRisks";
|
||||
import GitAppOrganizationInstallation from "../../models/gitAppOrganizationInstallation";
|
||||
import {
|
||||
GitAppOrganizationInstallation,
|
||||
GitRisks
|
||||
} from "../../models";
|
||||
import { scanGithubPushEventForSecretLeaks } from "../../../queues/secret-scanning/githubScanPushEvent";
|
||||
export default async (app: Probot) => {
|
||||
app.on("installation.deleted", async (context) => {
|
||||
|
@ -1,5 +1,43 @@
|
||||
import { Types } from "mongoose";
|
||||
import { MembershipOrg, Organization } from "../models";
|
||||
import mongoose, { Types } from "mongoose";
|
||||
import {
|
||||
Bot,
|
||||
BotKey,
|
||||
BotOrg,
|
||||
Folder,
|
||||
IncidentContactOrg,
|
||||
Integration,
|
||||
IntegrationAuth,
|
||||
Key,
|
||||
Membership,
|
||||
MembershipOrg,
|
||||
Organization,
|
||||
Secret,
|
||||
SecretBlindIndexData,
|
||||
SecretImport,
|
||||
ServiceToken,
|
||||
ServiceTokenData,
|
||||
ServiceTokenDataV3,
|
||||
ServiceTokenDataV3Key,
|
||||
Tag,
|
||||
Webhook,
|
||||
Workspace
|
||||
} from "../models";
|
||||
import {
|
||||
Action,
|
||||
AuditLog,
|
||||
FolderVersion,
|
||||
GitAppInstallationSession,
|
||||
GitAppOrganizationInstallation,
|
||||
GitRisks,
|
||||
Log,
|
||||
Role,
|
||||
SSOConfig,
|
||||
SecretApprovalPolicy,
|
||||
SecretApprovalRequest,
|
||||
SecretSnapshot,
|
||||
SecretVersion,
|
||||
TrustedIP
|
||||
} from "../ee/models";
|
||||
import {
|
||||
ACCEPTED,
|
||||
} from "../variables";
|
||||
@ -17,6 +55,7 @@ import {
|
||||
import {
|
||||
createBotOrg
|
||||
} from "./botOrg";
|
||||
import { InternalServerError, ResourceNotFoundError } from "../utils/errors";
|
||||
|
||||
/**
|
||||
* Create an organization with name [name]
|
||||
@ -65,6 +104,227 @@ export const createOrganization = async ({
|
||||
return organization;
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete organization with id [organizationId]
|
||||
* @param {Object} obj
|
||||
* @param {Types.ObjectId} obj.organizationId - id of organization to delete
|
||||
* @returns
|
||||
*/
|
||||
export const deleteOrganization = async ({
|
||||
organizationId
|
||||
}: {
|
||||
organizationId: Types.ObjectId;
|
||||
}) => {
|
||||
const session = await mongoose.startSession();
|
||||
session.startTransaction();
|
||||
|
||||
try {
|
||||
const organization = await Organization.findByIdAndDelete(organizationId);
|
||||
|
||||
if (!organization) throw ResourceNotFoundError();
|
||||
|
||||
await MembershipOrg.deleteMany({
|
||||
organization: organization._id
|
||||
});
|
||||
|
||||
await BotOrg.deleteMany({
|
||||
organization: organization._id
|
||||
});
|
||||
|
||||
await SSOConfig.deleteMany({
|
||||
organization: organization._id
|
||||
});
|
||||
|
||||
await Role.deleteMany({
|
||||
organization: organization._id
|
||||
});
|
||||
|
||||
await IncidentContactOrg.deleteMany({
|
||||
organization: organization._id
|
||||
});
|
||||
|
||||
await GitRisks.deleteMany({
|
||||
organization: organization._id
|
||||
});
|
||||
|
||||
await GitAppInstallationSession.deleteMany({
|
||||
organization: organization._id
|
||||
});
|
||||
|
||||
await GitAppOrganizationInstallation.deleteMany({
|
||||
organization: organization._id
|
||||
});
|
||||
|
||||
const workspaceIds = await Workspace.distinct("_id", {
|
||||
organization: organization._id
|
||||
});
|
||||
|
||||
await Workspace.deleteMany({
|
||||
organization: organization._id
|
||||
});
|
||||
|
||||
await Membership.deleteMany({
|
||||
workspace: {
|
||||
$in: workspaceIds
|
||||
}
|
||||
});
|
||||
|
||||
await Key.deleteMany({
|
||||
workspace: {
|
||||
$in: workspaceIds
|
||||
}
|
||||
});
|
||||
|
||||
await Bot.deleteMany({
|
||||
workspace: {
|
||||
$in: workspaceIds
|
||||
}
|
||||
});
|
||||
|
||||
await BotKey.deleteMany({
|
||||
workspace: {
|
||||
$in: workspaceIds
|
||||
}
|
||||
});
|
||||
|
||||
await SecretBlindIndexData.deleteMany({
|
||||
workspace: {
|
||||
$in: workspaceIds
|
||||
}
|
||||
});
|
||||
|
||||
await Secret.deleteMany({
|
||||
workspace: {
|
||||
$in: workspaceIds
|
||||
}
|
||||
});
|
||||
|
||||
await SecretVersion.deleteMany({
|
||||
workspace: {
|
||||
$in: workspaceIds
|
||||
}
|
||||
});
|
||||
|
||||
await SecretSnapshot.deleteMany({
|
||||
workspace: {
|
||||
$in: workspaceIds
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
await SecretImport.deleteMany({
|
||||
workspace: {
|
||||
$in: workspaceIds
|
||||
}
|
||||
});
|
||||
|
||||
await Folder.deleteMany({
|
||||
workspace: {
|
||||
$in: workspaceIds
|
||||
}
|
||||
});
|
||||
|
||||
await FolderVersion.deleteMany({
|
||||
workspace: {
|
||||
$in: workspaceIds
|
||||
}
|
||||
});
|
||||
|
||||
await Webhook.deleteMany({
|
||||
workspace: {
|
||||
$in: workspaceIds
|
||||
}
|
||||
});
|
||||
|
||||
await TrustedIP.deleteMany({
|
||||
workspace: {
|
||||
$in: workspaceIds
|
||||
}
|
||||
});
|
||||
|
||||
await Tag.deleteMany({
|
||||
workspace: {
|
||||
$in: workspaceIds
|
||||
}
|
||||
});
|
||||
|
||||
await IntegrationAuth.deleteMany({
|
||||
workspace: {
|
||||
$in: workspaceIds
|
||||
}
|
||||
});
|
||||
|
||||
await Integration.deleteMany({
|
||||
workspace: {
|
||||
$in: workspaceIds
|
||||
}
|
||||
});
|
||||
|
||||
await ServiceToken.deleteMany({
|
||||
workspace: {
|
||||
$in: workspaceIds
|
||||
}
|
||||
});
|
||||
|
||||
await ServiceTokenData.deleteMany({
|
||||
workspace: {
|
||||
$in: workspaceIds
|
||||
}
|
||||
});
|
||||
|
||||
await ServiceTokenDataV3.deleteMany({
|
||||
workspace: {
|
||||
$in: workspaceIds
|
||||
}
|
||||
});
|
||||
|
||||
await ServiceTokenDataV3Key.deleteMany({
|
||||
workspace: {
|
||||
$in: workspaceIds
|
||||
}
|
||||
});
|
||||
|
||||
await AuditLog.deleteMany({
|
||||
workspace: {
|
||||
$in: workspaceIds
|
||||
}
|
||||
});
|
||||
|
||||
await Log.deleteMany({
|
||||
workspace: {
|
||||
$in: workspaceIds
|
||||
}
|
||||
});
|
||||
|
||||
await Action.deleteMany({
|
||||
workspace: {
|
||||
$in: workspaceIds
|
||||
}
|
||||
});
|
||||
|
||||
await SecretApprovalPolicy.deleteMany({
|
||||
workspace: {
|
||||
$in: workspaceIds
|
||||
}
|
||||
});
|
||||
|
||||
await SecretApprovalRequest.deleteMany({
|
||||
workspace: {
|
||||
$in: workspaceIds
|
||||
}
|
||||
});
|
||||
|
||||
return organization;
|
||||
} catch (err) {
|
||||
await session.abortTransaction();
|
||||
throw InternalServerError({
|
||||
message: "Failed to delete organization"
|
||||
});
|
||||
} finally {
|
||||
session.endSession();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update organization subscription quantity to reflect number of members in
|
||||
* the organization.
|
||||
|
@ -1,5 +1,27 @@
|
||||
import { IUser, User } from "../models";
|
||||
import mongoose, { Types } from "mongoose";
|
||||
import {
|
||||
APIKeyData,
|
||||
BackupPrivateKey,
|
||||
IUser,
|
||||
Key,
|
||||
Membership,
|
||||
MembershipOrg,
|
||||
TokenVersion,
|
||||
User,
|
||||
UserAction
|
||||
} from "../models";
|
||||
import {
|
||||
Action,
|
||||
Log
|
||||
} from "../ee/models";
|
||||
import { sendMail } from "./nodemailer";
|
||||
import {
|
||||
InternalServerError,
|
||||
ResourceNotFoundError
|
||||
} from "../utils/errors";
|
||||
import { ADMIN } from "../variables";
|
||||
import { deleteOrganization } from "../helpers/organization";
|
||||
import { deleteWorkspace } from "../helpers/workspace";
|
||||
|
||||
/**
|
||||
* Initialize a user under email [email]
|
||||
@ -134,3 +156,169 @@ export const checkUserDevice = async ({
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check that if we delete user with id [userId] then
|
||||
* there won't be any admin-less organizations or projects
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.userId - id of user to check deletion conditions for
|
||||
*/
|
||||
const checkDeleteUserConditions = async ({
|
||||
userId
|
||||
}: {
|
||||
userId: Types.ObjectId;
|
||||
}) => {
|
||||
const memberships = await Membership.find({
|
||||
user: userId
|
||||
});
|
||||
|
||||
const membershipOrgs = await MembershipOrg.find({
|
||||
user: userId
|
||||
});
|
||||
|
||||
// delete organizations where user is only member
|
||||
for await (const membershipOrg of membershipOrgs) {
|
||||
const orgMemberCount = await MembershipOrg.countDocuments({
|
||||
organization: membershipOrg.organization,
|
||||
});
|
||||
|
||||
const otherOrgAdminCount = await MembershipOrg.countDocuments({
|
||||
organization: membershipOrg.organization,
|
||||
user: { $ne: userId },
|
||||
role: ADMIN
|
||||
});
|
||||
|
||||
if (orgMemberCount > 1 && otherOrgAdminCount === 0) {
|
||||
throw InternalServerError({
|
||||
message: "Failed to delete account because an org would be admin-less"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// delete workspaces where user is only member
|
||||
for await (const membership of memberships) {
|
||||
const workspaceMemberCount = await Membership.countDocuments({
|
||||
workspace: membership.workspace
|
||||
});
|
||||
|
||||
const otherWorkspaceAdminCount = await Membership.countDocuments({
|
||||
workspace: membership.workspace,
|
||||
user: { $ne: userId },
|
||||
role: ADMIN
|
||||
});
|
||||
|
||||
if (workspaceMemberCount > 1 && otherWorkspaceAdminCount === 0) {
|
||||
throw InternalServerError({
|
||||
message: "Failed to delete account because a workspace would be admin-less"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete account with id [userId]
|
||||
* @param {Object} obj
|
||||
* @param {Types.ObjectId} obj.userId - id of user to delete
|
||||
* @returns {User} user - deleted user
|
||||
*/
|
||||
export const deleteUser = async ({
|
||||
userId
|
||||
}: {
|
||||
userId: Types.ObjectId
|
||||
}) => {
|
||||
const session = await mongoose.startSession();
|
||||
session.startTransaction();
|
||||
|
||||
try {
|
||||
const user = await User.findByIdAndDelete(userId);
|
||||
|
||||
if (!user) throw ResourceNotFoundError();
|
||||
|
||||
await checkDeleteUserConditions({
|
||||
userId: user._id
|
||||
});
|
||||
|
||||
await UserAction.deleteMany({
|
||||
user: user._id
|
||||
});
|
||||
|
||||
await BackupPrivateKey.deleteMany({
|
||||
user: user._id
|
||||
});
|
||||
|
||||
await APIKeyData.deleteMany({
|
||||
user: user._id
|
||||
});
|
||||
|
||||
await Action.deleteMany({
|
||||
user: user._id
|
||||
});
|
||||
|
||||
await Log.deleteMany({
|
||||
user: user._id
|
||||
});
|
||||
|
||||
await TokenVersion.deleteMany({
|
||||
user: user._id
|
||||
});
|
||||
|
||||
await Key.deleteMany({
|
||||
receiver: user._id
|
||||
});
|
||||
|
||||
const membershipOrgs = await MembershipOrg.find({
|
||||
user: userId
|
||||
});
|
||||
|
||||
// delete organizations where user is only member
|
||||
for await (const membershipOrg of membershipOrgs) {
|
||||
const memberCount = await MembershipOrg.countDocuments({
|
||||
organization: membershipOrg.organization
|
||||
});
|
||||
|
||||
if (memberCount === 1) {
|
||||
// organization only has 1 member (the current user)
|
||||
|
||||
await deleteOrganization({
|
||||
organizationId: membershipOrg.organization
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const memberships = await Membership.find({
|
||||
user: userId
|
||||
});
|
||||
|
||||
// delete workspaces where user is only member
|
||||
for await (const membership of memberships) {
|
||||
const memberCount = await Membership.countDocuments({
|
||||
workspace: membership.workspace
|
||||
});
|
||||
|
||||
if (memberCount === 1) {
|
||||
// workspace only has 1 member (the current user) -> delete workspace
|
||||
|
||||
await deleteWorkspace({
|
||||
workspaceId: membership.workspace
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await MembershipOrg.deleteMany({
|
||||
user: userId
|
||||
});
|
||||
|
||||
await Membership.deleteMany({
|
||||
user: userId
|
||||
});
|
||||
|
||||
return user;
|
||||
} catch (err) {
|
||||
await session.abortTransaction();
|
||||
throw InternalServerError({
|
||||
message: "Failed to delete account"
|
||||
})
|
||||
} finally {
|
||||
session.endSession();
|
||||
}
|
||||
}
|
@ -1,18 +1,42 @@
|
||||
import { Types } from "mongoose";
|
||||
import mongoose, { Types } from "mongoose";
|
||||
import {
|
||||
Bot,
|
||||
BotKey,
|
||||
Folder,
|
||||
Integration,
|
||||
IntegrationAuth,
|
||||
Key,
|
||||
Membership,
|
||||
Secret,
|
||||
Workspace,
|
||||
SecretBlindIndexData,
|
||||
SecretImport,
|
||||
ServiceToken,
|
||||
ServiceTokenData,
|
||||
ServiceTokenDataV3,
|
||||
ServiceTokenDataV3Key,
|
||||
Tag,
|
||||
Webhook,
|
||||
Workspace
|
||||
} from "../models";
|
||||
import {
|
||||
Action,
|
||||
AuditLog,
|
||||
FolderVersion,
|
||||
IPType,
|
||||
Log,
|
||||
SecretApprovalPolicy,
|
||||
SecretApprovalRequest,
|
||||
SecretSnapshot,
|
||||
SecretVersion,
|
||||
TrustedIP
|
||||
} from "../ee/models";
|
||||
import { createBot } from "../helpers/bot";
|
||||
import { EELicenseService } from "../ee/services";
|
||||
import { SecretService } from "../services";
|
||||
import {
|
||||
InternalServerError,
|
||||
ResourceNotFoundError
|
||||
} from "../utils/errors";
|
||||
|
||||
/**
|
||||
* Create a workspace with name [name] in organization with id [organizationId]
|
||||
@ -77,18 +101,126 @@ export const createWorkspace = async ({
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.id - id of workspace to delete
|
||||
*/
|
||||
export const deleteWorkspace = async ({ id }: { id: string }) => {
|
||||
await Workspace.deleteOne({ _id: id });
|
||||
await Bot.deleteOne({
|
||||
workspace: id,
|
||||
});
|
||||
await Membership.deleteMany({
|
||||
workspace: id,
|
||||
});
|
||||
await Secret.deleteMany({
|
||||
workspace: id,
|
||||
});
|
||||
await Key.deleteMany({
|
||||
workspace: id,
|
||||
});
|
||||
export const deleteWorkspace = async ({
|
||||
workspaceId
|
||||
}: {
|
||||
workspaceId: Types.ObjectId;
|
||||
}) => {
|
||||
const session = await mongoose.startSession();
|
||||
session.startTransaction();
|
||||
|
||||
try {
|
||||
const workspace = await Workspace.findByIdAndDelete(workspaceId);
|
||||
|
||||
if (!workspace) throw ResourceNotFoundError();
|
||||
|
||||
await Membership.deleteMany({
|
||||
workspace: workspace._id
|
||||
});
|
||||
|
||||
await Key.deleteMany({
|
||||
workspace: workspace._id
|
||||
});
|
||||
|
||||
await Bot.deleteMany({
|
||||
workspace: workspace._id
|
||||
});
|
||||
|
||||
await BotKey.deleteMany({
|
||||
workspace: workspace._id
|
||||
});
|
||||
|
||||
await SecretBlindIndexData.deleteMany({
|
||||
workspace: workspace._id
|
||||
});
|
||||
|
||||
await Secret.deleteMany({
|
||||
workspace: workspace._id
|
||||
});
|
||||
|
||||
await SecretVersion.deleteMany({
|
||||
workspace: workspace._id
|
||||
});
|
||||
|
||||
await SecretSnapshot.deleteMany({
|
||||
workspace: workspace._id
|
||||
});
|
||||
|
||||
await SecretImport.deleteMany({
|
||||
workspace: workspace._id
|
||||
});
|
||||
|
||||
await Folder.deleteMany({
|
||||
workspace: workspace._id
|
||||
});
|
||||
|
||||
await FolderVersion.deleteMany({
|
||||
workspace: workspace._id
|
||||
});
|
||||
|
||||
await Webhook.deleteMany({
|
||||
workspace: workspace._id
|
||||
});
|
||||
|
||||
await TrustedIP.deleteMany({
|
||||
workspace: workspace._id
|
||||
});
|
||||
|
||||
await Tag.deleteMany({
|
||||
workspace: workspace._id
|
||||
});
|
||||
|
||||
await IntegrationAuth.deleteMany({
|
||||
workspace: workspace._id
|
||||
});
|
||||
|
||||
await Integration.deleteMany({
|
||||
workspace: workspace._id
|
||||
});
|
||||
|
||||
await ServiceToken.deleteMany({
|
||||
workspace: workspace._id
|
||||
});
|
||||
|
||||
await ServiceTokenData.deleteMany({
|
||||
workspace: workspace._id
|
||||
});
|
||||
|
||||
await ServiceTokenDataV3.deleteMany({
|
||||
workspace: workspace._id
|
||||
});
|
||||
|
||||
await ServiceTokenDataV3Key.deleteMany({
|
||||
workspace: workspace._id
|
||||
});
|
||||
|
||||
await AuditLog.deleteMany({
|
||||
workspace: workspace._id
|
||||
});
|
||||
|
||||
await Log.deleteMany({
|
||||
workspace: workspace._id
|
||||
});
|
||||
|
||||
await Action.deleteMany({
|
||||
workspace: workspace._id
|
||||
});
|
||||
|
||||
await SecretApprovalPolicy.deleteMany({
|
||||
workspace: workspace._id
|
||||
});
|
||||
|
||||
await SecretApprovalRequest.deleteMany({
|
||||
workspace: workspace._id
|
||||
});
|
||||
|
||||
return workspace;
|
||||
} catch (err) {
|
||||
await session.abortTransaction();
|
||||
throw InternalServerError({
|
||||
message: "Failed to delete organization"
|
||||
});
|
||||
} finally {
|
||||
session.endSession();
|
||||
}
|
||||
};
|
||||
|
@ -2,7 +2,7 @@ import Queue, { Job } from "bull";
|
||||
import { ProbotOctokit } from "probot"
|
||||
import TelemetryService from "../../services/TelemetryService";
|
||||
import { sendMail } from "../../helpers";
|
||||
import GitRisks from "../../ee/models/gitRisks";
|
||||
import { GitRisks } from "../../ee/models";
|
||||
import { MembershipOrg, User } from "../../models";
|
||||
import { ADMIN } from "../../variables";
|
||||
import { convertKeysToLowercase, scanFullRepoContentAndGetFindings } from "../../ee/services/GithubSecretScanning/helper";
|
||||
|
@ -3,7 +3,7 @@ import { ProbotOctokit } from "probot"
|
||||
import { Commit } from "@octokit/webhooks-types";
|
||||
import TelemetryService from "../../services/TelemetryService";
|
||||
import { sendMail } from "../../helpers";
|
||||
import GitRisks from "../../ee/models/gitRisks";
|
||||
import { GitRisks } from "../../ee/models";
|
||||
import { MembershipOrg, User } from "../../models";
|
||||
import { ADMIN } from "../../variables";
|
||||
import { convertKeysToLowercase, scanContentAndGetFindings } from "../../ee/services/GithubSecretScanning/helper";
|
||||
|
@ -13,15 +13,6 @@ router.get(
|
||||
organizationController.getOrganizations
|
||||
);
|
||||
|
||||
router.post(
|
||||
// not used on frontend
|
||||
"/",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT]
|
||||
}),
|
||||
organizationController.createOrganization
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/:organizationId",
|
||||
requireAuth({
|
||||
|
@ -54,4 +54,20 @@ router.get(
|
||||
organizationsController.getOrganizationServiceAccounts
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT]
|
||||
}),
|
||||
organizationsController.createOrganization
|
||||
);
|
||||
|
||||
router.delete(
|
||||
"/:organizationId",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
|
||||
}),
|
||||
organizationsController.deleteOrganizationById
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -4,14 +4,6 @@ import { requireAuth } from "../../middleware";
|
||||
import { usersController } from "../../controllers/v2";
|
||||
import { AuthMode } from "../../variables";
|
||||
|
||||
router.get(
|
||||
"/me",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
|
||||
}),
|
||||
usersController.getMe
|
||||
);
|
||||
|
||||
router.patch(
|
||||
"/me/mfa",
|
||||
requireAuth({
|
||||
@ -84,4 +76,20 @@ router.delete(
|
||||
usersController.deleteMySessions
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/me",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
|
||||
}),
|
||||
usersController.getMe
|
||||
);
|
||||
|
||||
router.delete(
|
||||
"/me",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
|
||||
}),
|
||||
usersController.deleteMe
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -4,7 +4,14 @@ import { Types } from "mongoose";
|
||||
import { encryptSymmetric128BitHexKeyUTF8 } from "../crypto";
|
||||
import { EESecretService } from "../../ee/services";
|
||||
import { redisClient } from "../../services/RedisService"
|
||||
import { IPType, ISecretVersion, SecretSnapshot, SecretVersion, TrustedIP } from "../../ee/models";
|
||||
import {
|
||||
IPType,
|
||||
ISecretVersion,
|
||||
Role,
|
||||
SecretSnapshot,
|
||||
SecretVersion,
|
||||
TrustedIP
|
||||
} from "../../ee/models";
|
||||
import {
|
||||
AuthMethod,
|
||||
BackupPrivateKey,
|
||||
@ -34,14 +41,12 @@ import {
|
||||
MEMBER,
|
||||
OWNER
|
||||
} from "../../variables";
|
||||
|
||||
import { InternalServerError } from "../errors";
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSub,
|
||||
memberProjectPermissions
|
||||
} from "../../ee/services/ProjectRoleService";
|
||||
import Role from "../../ee/models/role";
|
||||
|
||||
/**
|
||||
* Backfill secrets to ensure that they're all versioned and have
|
||||
|
@ -136,12 +136,6 @@ export const GetOrgLicencesv1 = z.object({
|
||||
params: z.object({ organizationId: z.string().trim() })
|
||||
});
|
||||
|
||||
export const CreateOrgv1 = z.object({
|
||||
body: z.object({
|
||||
organizationName: z.string().trim()
|
||||
})
|
||||
});
|
||||
|
||||
export const GetOrgv1 = z.object({
|
||||
params: z.object({
|
||||
organizationId: z.string().trim()
|
||||
@ -209,3 +203,13 @@ export const VerfiyUserToOrganizationV1 = z.object({
|
||||
code: z.string().trim()
|
||||
})
|
||||
});
|
||||
|
||||
export const CreateOrgv2 = z.object({
|
||||
body: z.object({
|
||||
name: z.string().trim()
|
||||
})
|
||||
});
|
||||
|
||||
export const DeleteOrgv2 = z.object({
|
||||
params: z.object({ organizationId: z.string().trim() })
|
||||
});
|
@ -2,8 +2,6 @@
|
||||
import jsrp from "jsrp";
|
||||
|
||||
import { login1, login2 } from "@app/hooks/api/auth/queries";
|
||||
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
|
||||
import { fetchMyOrganizationProjects } from "@app/hooks/api/users/queries";
|
||||
import KeyService from "@app/services/KeyService";
|
||||
|
||||
import Telemetry from "./telemetry/Telemetry";
|
||||
@ -122,17 +120,7 @@ const attemptLogin = async (
|
||||
iv,
|
||||
tag,
|
||||
privateKey
|
||||
});
|
||||
|
||||
const userOrgs = await fetchOrganizations();
|
||||
const orgId = userOrgs[0]._id;
|
||||
localStorage.setItem("orgData.id", orgId);
|
||||
|
||||
const orgUserProjects = await fetchMyOrganizationProjects(orgId);
|
||||
|
||||
if (orgUserProjects.length > 0) {
|
||||
localStorage.setItem("projectData.id", orgUserProjects[0]._id);
|
||||
}
|
||||
});
|
||||
|
||||
if (email) {
|
||||
telemetry.identify(email, email);
|
||||
|
@ -2,9 +2,6 @@
|
||||
import jsrp from "jsrp";
|
||||
|
||||
import { login1 , verifyMfaToken } from "@app/hooks/api/auth/queries";
|
||||
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
|
||||
import { fetchMyOrganizationProjects } from "@app/hooks/api/users/queries";
|
||||
// import verifyMfaToken from "@app/pages/api/auth/verifyMfaToken";
|
||||
import KeyService from "@app/services/KeyService";
|
||||
|
||||
import { saveTokenToLocalStorage } from "./saveTokenToLocalStorage";
|
||||
@ -19,7 +16,6 @@ interface IsMfaLoginSuccessful {
|
||||
privateKey: string;
|
||||
JTWToken: string;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -93,16 +89,6 @@ const attemptLoginMfa = async ({
|
||||
privateKey
|
||||
});
|
||||
|
||||
// TODO: in the future - move this logic elsewhere
|
||||
// because this function is about logging the user in
|
||||
// and not initializing the login details
|
||||
const userOrgs = await fetchOrganizations();
|
||||
const orgId = userOrgs[0]._id;
|
||||
localStorage.setItem("orgData.id", orgId);
|
||||
|
||||
const orgUserProjects = await fetchMyOrganizationProjects(orgId);
|
||||
localStorage.setItem("projectData.id", orgUserProjects[0]._id);
|
||||
|
||||
resolve({
|
||||
success: true,
|
||||
loginResponse:{
|
||||
|
@ -2,8 +2,6 @@
|
||||
import jsrp from "jsrp";
|
||||
|
||||
import { login1, login2 } from "@app/hooks/api/auth/queries";
|
||||
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
|
||||
import { fetchMyOrganizationProjects } from "@app/hooks/api/users/queries";
|
||||
import KeyService from "@app/services/KeyService";
|
||||
|
||||
import Telemetry from "./telemetry/Telemetry";
|
||||
@ -119,20 +117,6 @@ const attemptLogin = async (
|
||||
tag,
|
||||
privateKey
|
||||
});
|
||||
|
||||
// TODO: in the future - move this logic elsewhere
|
||||
// because this function is about logging the user in
|
||||
// and not initializing the login details
|
||||
const userOrgs = await fetchOrganizations();
|
||||
|
||||
const orgId = userOrgs[0]._id;
|
||||
localStorage.setItem("orgData.id", orgId);
|
||||
|
||||
const orgUserProjects = await fetchMyOrganizationProjects(orgId);
|
||||
|
||||
if (orgUserProjects.length > 0) {
|
||||
localStorage.setItem("projectData.id", orgUserProjects[0]._id);
|
||||
}
|
||||
|
||||
if (email) {
|
||||
telemetry.identify(email, email);
|
||||
|
@ -2,8 +2,6 @@
|
||||
import jsrp from "jsrp";
|
||||
|
||||
import { login1 , verifyMfaToken } from "@app/hooks/api/auth/queries";
|
||||
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
|
||||
import { fetchMyOrganizationProjects } from "@app/hooks/api/users/queries";
|
||||
import KeyService from "@app/services/KeyService";
|
||||
|
||||
import { saveTokenToLocalStorage } from "./saveTokenToLocalStorage";
|
||||
@ -83,16 +81,6 @@ const attemptLoginMfa = async ({
|
||||
privateKey
|
||||
});
|
||||
|
||||
// TODO: in the future - move this logic elsewhere
|
||||
// because this function is about logging the user in
|
||||
// and not initializing the login details
|
||||
const userOrgs = await fetchOrganizations();
|
||||
const orgId = userOrgs[0]._id;
|
||||
localStorage.setItem("orgData.id", orgId);
|
||||
|
||||
const orgUserProjects = await fetchMyOrganizationProjects(orgId);
|
||||
localStorage.setItem("projectData.id", orgUserProjects[0]._id);
|
||||
|
||||
resolve(true);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
|
@ -2,6 +2,8 @@ export {
|
||||
useAddOrgPmtMethod,
|
||||
useAddOrgTaxId,
|
||||
useCreateCustomerPortalSession,
|
||||
useCreateOrg,
|
||||
useDeleteOrgById,
|
||||
useDeleteOrgPmtMethod,
|
||||
useDeleteOrgTaxId,
|
||||
useGetOrganizations,
|
||||
|
@ -41,6 +41,25 @@ export const useGetOrganizations = () => {
|
||||
});
|
||||
}
|
||||
|
||||
export const useCreateOrg = () => {
|
||||
return useMutation({
|
||||
mutationFn: async ({
|
||||
name
|
||||
}: {
|
||||
name: string;
|
||||
}) => {
|
||||
const { data: { organization } } = await apiRequest.post(
|
||||
"/api/v2/organizations",
|
||||
{
|
||||
name
|
||||
}
|
||||
);
|
||||
|
||||
return organization;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useRenameOrg = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
@ -333,4 +352,33 @@ export const useGetOrgLicenses = (organizationId: string) => {
|
||||
},
|
||||
enabled: true
|
||||
});
|
||||
}
|
||||
|
||||
export const useDeleteOrgById = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({
|
||||
organizationId,
|
||||
}: {
|
||||
organizationId: string;
|
||||
}) => {
|
||||
const { data: { organization } } = await apiRequest.delete<{ organization: Organization }>(
|
||||
`/api/v2/organizations/${organizationId}`
|
||||
);
|
||||
return organization;
|
||||
},
|
||||
onSuccess(_, dto) {
|
||||
queryClient.invalidateQueries(organizationKeys.getUserOrganizations);
|
||||
queryClient.invalidateQueries(organizationKeys.getOrgPlanBillingInfo(dto.organizationId));
|
||||
queryClient.invalidateQueries(organizationKeys.getOrgPlanTable(dto.organizationId));
|
||||
queryClient.invalidateQueries(organizationKeys.getOrgPlansTable(dto.organizationId, "monthly")); // You might need to invalidate for 'yearly' as well.
|
||||
queryClient.invalidateQueries(organizationKeys.getOrgPlansTable(dto.organizationId, "yearly"));
|
||||
queryClient.invalidateQueries(organizationKeys.getOrgBillingDetails(dto.organizationId));
|
||||
queryClient.invalidateQueries(organizationKeys.getOrgPmtMethods(dto.organizationId));
|
||||
queryClient.invalidateQueries(organizationKeys.getOrgTaxIds(dto.organizationId));
|
||||
queryClient.invalidateQueries(organizationKeys.getOrgInvoices(dto.organizationId));
|
||||
queryClient.invalidateQueries(organizationKeys.getOrgLicenses(dto.organizationId));
|
||||
}
|
||||
});
|
||||
}
|
@ -63,10 +63,12 @@ export const useGetRoles = ({ orgId, workspaceId }: TGetRolesDTO) =>
|
||||
});
|
||||
|
||||
const getUserOrgPermissions = async ({ orgId }: TGetUserOrgPermissionsDTO) => {
|
||||
if (orgId === "") return [];
|
||||
|
||||
const { data } = await apiRequest.get<{
|
||||
data: { permissions: PackRule<RawRuleOf<MongoAbility<OrgPermissionSet>>>[] };
|
||||
}>(`/api/v1/roles/organization/${orgId}/permissions`, {});
|
||||
|
||||
|
||||
return data.data.permissions;
|
||||
};
|
||||
|
||||
@ -74,7 +76,7 @@ export const useGetUserOrgPermissions = ({ orgId }: TGetUserOrgPermissionsDTO) =
|
||||
useQuery({
|
||||
queryKey: roleQueryKeys.getUserOrgPermissions({ orgId }),
|
||||
queryFn: () => getUserOrgPermissions({ orgId }),
|
||||
enabled: Boolean(orgId),
|
||||
// enabled: Boolean(orgId),
|
||||
select: (data) => {
|
||||
const rule = unpackRules<RawRuleOf<MongoAbility<OrgPermissionSet>>>(data);
|
||||
const ability = createMongoAbility<OrgPermissionSet>(rule, { conditionsMatcher });
|
||||
|
@ -5,6 +5,7 @@ export {
|
||||
useCreateAPIKey,
|
||||
useDeleteAPIKey,
|
||||
useDeleteOrgMembership,
|
||||
useDeleteUser,
|
||||
useGetMyAPIKeys,
|
||||
useGetMyIp,
|
||||
useGetMyOrganizationProjects,
|
||||
|
@ -42,6 +42,20 @@ export const fetchUserDetails = async () => {
|
||||
|
||||
export const useGetUser = () => useQuery(userKeys.getUser, fetchUserDetails);
|
||||
|
||||
export const useDeleteUser = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async () => {
|
||||
const { data: { user } } = await apiRequest.delete<{ user: User }>("/api/v2/users/me");
|
||||
return user;
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.clear();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const fetchUserAction = async (action: string) => {
|
||||
const { data } = await apiRequest.get<{ userAction: string }>("/api/v1/user-action", {
|
||||
params: {
|
||||
@ -208,21 +222,31 @@ export const useRegisterUserAction = () => {
|
||||
});
|
||||
};
|
||||
|
||||
export const useLogoutUser = () =>
|
||||
useMutation({
|
||||
export const useLogoutUser = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: async () => {
|
||||
await apiRequest.post("/api/v1/auth/logout");
|
||||
},
|
||||
onSuccess: () => {
|
||||
setAuthToken("");
|
||||
// Delete the cookie by not setting a value; Alternatively clear the local storage
|
||||
localStorage.setItem("publicKey", "");
|
||||
localStorage.setItem("encryptedPrivateKey", "");
|
||||
localStorage.setItem("iv", "");
|
||||
localStorage.setItem("tag", "");
|
||||
localStorage.setItem("PRIVATE_KEY", "");
|
||||
localStorage.removeItem("protectedKey");
|
||||
localStorage.removeItem("protectedKeyIV");
|
||||
localStorage.removeItem("protectedKeyTag");
|
||||
localStorage.removeItem("publicKey");
|
||||
localStorage.removeItem("encryptedPrivateKey");
|
||||
localStorage.removeItem("iv");
|
||||
localStorage.removeItem("tag");
|
||||
localStorage.removeItem("PRIVATE_KEY");
|
||||
localStorage.removeItem("orgData.id");
|
||||
localStorage.removeItem("projectData.id");
|
||||
|
||||
queryClient.clear();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export const useGetMyIp = () => {
|
||||
return useQuery({
|
||||
|
@ -115,6 +115,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
||||
// eslint-disable-next-line prefer-const
|
||||
const { workspaces, currentWorkspace } = useWorkspace();
|
||||
const { orgs, currentOrg } = useOrganization();
|
||||
|
||||
const { user } = useUser();
|
||||
const { subscription } = useSubscription();
|
||||
const workspaceId = currentWorkspace?._id || "";
|
||||
@ -157,16 +158,6 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
||||
try {
|
||||
console.log("Logging out...");
|
||||
await logout.mutateAsync();
|
||||
localStorage.removeItem("protectedKey");
|
||||
localStorage.removeItem("protectedKeyIV");
|
||||
localStorage.removeItem("protectedKeyTag");
|
||||
localStorage.removeItem("publicKey");
|
||||
localStorage.removeItem("encryptedPrivateKey");
|
||||
localStorage.removeItem("iv");
|
||||
localStorage.removeItem("tag");
|
||||
localStorage.removeItem("PRIVATE_KEY");
|
||||
localStorage.removeItem("orgData.id");
|
||||
localStorage.removeItem("projectData.id");
|
||||
router.push("/login");
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
@ -1,6 +1,6 @@
|
||||
import SecurityClient from "@app/components/utilities/SecurityClient";
|
||||
|
||||
export type GitRisks = {
|
||||
export type IGitRisks = {
|
||||
_id: string;
|
||||
description: string;
|
||||
startLine: string;
|
||||
@ -41,7 +41,7 @@ export type GitRisks = {
|
||||
* Will create a new integration session and return it for the given org
|
||||
* @returns
|
||||
*/
|
||||
const getRisksByOrganization = (oranizationId: string): Promise<GitRisks[]> =>
|
||||
const getRisksByOrganization = (oranizationId: string): Promise<IGitRisks[]> =>
|
||||
SecurityClient.fetchCall(`/api/v1/secret-scanning/organization/${oranizationId}/risks`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
|
@ -18,4 +18,4 @@ export default function SettingsOrg() {
|
||||
);
|
||||
}
|
||||
|
||||
SettingsOrg.requireAuth = true;
|
||||
SettingsOrg.requireAuth = true;
|
20
frontend/src/pages/org/none/index.tsx
Normal file
20
frontend/src/pages/org/none/index.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Head from "next/head";
|
||||
|
||||
import { NonePage } from "@app/views/Org/NonePage";
|
||||
|
||||
export default function NoneOrganization() {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{t("common.head-title", { title: t("settings.org.title") })}</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
<NonePage />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
NoneOrganization.requireAuth = true;
|
@ -2,7 +2,6 @@ import { useEffect, useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import axios from "axios"
|
||||
|
||||
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
|
||||
import { fetchUserDetails } from "@app/hooks/api/users/queries";
|
||||
import { getAuthToken, isLoggedIn } from "@app/reactQuery";
|
||||
|
||||
@ -11,6 +10,8 @@ import {
|
||||
MFAStep,
|
||||
SAMLSSOStep
|
||||
} from "./components";
|
||||
// import { navigateUserToOrg } from "../../Login.utils";
|
||||
import { navigateUserToOrg } from "./Login.utils";
|
||||
|
||||
export const Login = () => {
|
||||
const router = useRouter();
|
||||
@ -24,10 +25,6 @@ export const Login = () => {
|
||||
// TODO(akhilmhdh): workspace will be controlled by a workspace context
|
||||
const redirectToDashboard = async () => {
|
||||
try {
|
||||
const userOrgs = await fetchOrganizations();
|
||||
// userWorkspace = userWorkspaces[0] && userWorkspaces[0]._id;
|
||||
const userOrg = userOrgs[0] && userOrgs[0]._id;
|
||||
|
||||
// user details
|
||||
const userDetails = await fetchUserDetails()
|
||||
// send details back to client
|
||||
@ -40,7 +37,8 @@ export const Login = () => {
|
||||
const instance = axios.create()
|
||||
await instance.post(cliUrl, { email: userDetails.email, privateKey: localStorage.getItem("PRIVATE_KEY"), JTWToken: getAuthToken() })
|
||||
}
|
||||
router.push(`/org/${userOrg}/overview`);
|
||||
|
||||
await navigateUserToOrg(router);
|
||||
} catch (error) {
|
||||
console.log("Error - Not logged in yet");
|
||||
}
|
||||
|
17
frontend/src/views/Login/Login.utils.tsx
Normal file
17
frontend/src/views/Login/Login.utils.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import { NextRouter } from "next/router";
|
||||
|
||||
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
|
||||
|
||||
export const navigateUserToOrg = async (router: NextRouter) => {
|
||||
const userOrgs = await fetchOrganizations();
|
||||
|
||||
if (userOrgs.length > 0) {
|
||||
// user is part of at least 1 org
|
||||
const userOrg = userOrgs[0] && userOrgs[0]._id;
|
||||
localStorage.setItem("orgData.id", userOrg);
|
||||
router.push(`/org/${userOrg}/overview`);
|
||||
} else {
|
||||
// user is not part of any org
|
||||
router.push("/org/none");
|
||||
}
|
||||
}
|
@ -12,9 +12,10 @@ import { useNotificationContext } from "@app/components/context/Notifications/No
|
||||
import attemptCliLogin from "@app/components/utilities/attemptCliLogin";
|
||||
import attemptLogin from "@app/components/utilities/attemptLogin";
|
||||
import { Button, Input } from "@app/components/v2";
|
||||
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
|
||||
import { useFetchServerStatus } from "@app/hooks/api/serverDetails";
|
||||
|
||||
import { navigateUserToOrg } from "../../Login.utils";
|
||||
|
||||
type Props = {
|
||||
setStep: (step: number) => void;
|
||||
email: string;
|
||||
@ -73,6 +74,7 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }:
|
||||
email: email.toLowerCase(),
|
||||
password
|
||||
});
|
||||
|
||||
if (isLoginSuccessful && isLoginSuccessful.success) {
|
||||
// case: login was successful
|
||||
|
||||
@ -82,15 +84,14 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }:
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
const userOrgs = await fetchOrganizations();
|
||||
const userOrg = userOrgs[0] && userOrgs[0]._id;
|
||||
|
||||
await navigateUserToOrg(router);
|
||||
|
||||
// case: login does not require MFA step
|
||||
createNotification({
|
||||
text: "Successfully logged in",
|
||||
type: "success"
|
||||
});
|
||||
router.push(`/org/${userOrg}/overview`);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
|
@ -12,10 +12,10 @@ import attemptLoginMfa from "@app/components/utilities/attemptLoginMfa";
|
||||
import { Button } from "@app/components/v2";
|
||||
import { useUpdateUserAuthMethods } from "@app/hooks/api";
|
||||
import { useSendMfaToken } from "@app/hooks/api/auth";
|
||||
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
|
||||
import { fetchUserDetails } from "@app/hooks/api/users/queries";
|
||||
import { AuthMethod } from "@app/hooks/api/users/types";
|
||||
|
||||
import { navigateUserToOrg } from "../../Login.utils";
|
||||
|
||||
// The style for the verification code input
|
||||
const props = {
|
||||
@ -127,8 +127,6 @@ export const MFAStep = ({
|
||||
|
||||
if (isLoginSuccessful) {
|
||||
setIsLoading(false);
|
||||
const userOrgs = await fetchOrganizations();
|
||||
const userOrg = userOrgs[0] && userOrgs[0]._id;
|
||||
|
||||
// case: login does not require MFA step
|
||||
createNotification({
|
||||
@ -144,7 +142,7 @@ export const MFAStep = ({
|
||||
});
|
||||
}
|
||||
|
||||
router.push(`/org/${userOrg}/overview`);
|
||||
await navigateUserToOrg(router);
|
||||
} else {
|
||||
createNotification({
|
||||
text: "Failed to log in",
|
||||
|
@ -10,9 +10,10 @@ import attemptCliLogin from "@app/components/utilities/attemptCliLogin";
|
||||
import attemptLogin from "@app/components/utilities/attemptLogin";
|
||||
import { Button, Input } from "@app/components/v2";
|
||||
import { useUpdateUserAuthMethods } from "@app/hooks/api";
|
||||
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
|
||||
import { fetchUserDetails } from "@app/hooks/api/users/queries";
|
||||
|
||||
import { navigateUserToOrg } from "../../Login.utils";
|
||||
|
||||
type Props = {
|
||||
providerAuthToken: string;
|
||||
email: string;
|
||||
@ -92,8 +93,6 @@ export const PasswordStep = ({
|
||||
}
|
||||
|
||||
// case: login does not require MFA step
|
||||
const userOrgs = await fetchOrganizations();
|
||||
const userOrg = userOrgs[0]._id;
|
||||
setIsLoading(false);
|
||||
createNotification({
|
||||
text: "Successfully logged in",
|
||||
@ -108,7 +107,7 @@ export const PasswordStep = ({
|
||||
});
|
||||
}
|
||||
|
||||
router.push(`/org/${userOrg}/overview`);
|
||||
await navigateUserToOrg(router);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
@ -120,8 +119,6 @@ export const PasswordStep = ({
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<form
|
||||
|
112
frontend/src/views/Org/NonePage/NonePage.tsx
Normal file
112
frontend/src/views/Org/NonePage/NonePage.tsx
Normal file
@ -0,0 +1,112 @@
|
||||
import { useEffect } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import * as yup from "yup";
|
||||
|
||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||
import {
|
||||
Button,
|
||||
FormControl,
|
||||
Input,
|
||||
Modal,
|
||||
ModalContent} from "@app/components/v2";
|
||||
import { useCreateOrg } from "@app/hooks/api";
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
|
||||
const schema = yup.object({
|
||||
name: yup.string().required("Organization name is required"),
|
||||
}).required();
|
||||
|
||||
export type FormData = yup.InferType<typeof schema>;
|
||||
|
||||
export const NonePage = () => {
|
||||
const { createNotification } = useNotificationContext();
|
||||
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([
|
||||
"createOrg",
|
||||
] as const);
|
||||
|
||||
const { mutateAsync } = useCreateOrg();
|
||||
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
reset,
|
||||
formState: { isSubmitting }
|
||||
} = useForm<FormData>({
|
||||
resolver: yupResolver(schema),
|
||||
defaultValues: {
|
||||
name: ""
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
handlePopUpOpen("createOrg");
|
||||
}, []);
|
||||
|
||||
const onFormSubmit = async ({ name }: FormData) => {
|
||||
try {
|
||||
|
||||
const organization = await mutateAsync({
|
||||
name
|
||||
});
|
||||
|
||||
createNotification({
|
||||
text: "Successfully created organization",
|
||||
type: "success"
|
||||
});
|
||||
|
||||
window.location.href = `/org/${organization._id}/overview`;
|
||||
|
||||
reset();
|
||||
handlePopUpToggle("createOrg", false);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
createNotification({
|
||||
text: "Failed to created organization",
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex justify-center bg-bunker-800 text-white w-full h-full">
|
||||
<Modal
|
||||
isOpen={popUp?.createOrg?.isOpen}
|
||||
>
|
||||
<ModalContent
|
||||
title="Create Organization"
|
||||
subTitle="Looks like you're not part of any organizations. Create one to start using Infisical"
|
||||
>
|
||||
<form onSubmit={handleSubmit(onFormSubmit)}>
|
||||
<Controller
|
||||
control={control}
|
||||
defaultValue=""
|
||||
name="name"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Name"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Input
|
||||
{...field}
|
||||
placeholder="Acme Corp"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Button
|
||||
className=""
|
||||
size="sm"
|
||||
type="submit"
|
||||
isLoading={isSubmitting}
|
||||
isDisabled={isSubmitting}
|
||||
>
|
||||
Create
|
||||
</Button>
|
||||
</form>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
1
frontend/src/views/Org/NonePage/index.tsx
Normal file
1
frontend/src/views/Org/NonePage/index.tsx
Normal file
@ -0,0 +1 @@
|
||||
export { NonePage } from "./NonePage";
|
@ -14,14 +14,14 @@ import {
|
||||
} from "@app/components/v2";
|
||||
import timeSince from "@app/ee/utilities/timeSince";
|
||||
import getRisksByOrganization, {
|
||||
GitRisks
|
||||
IGitRisks
|
||||
} from "@app/pages/api/secret-scanning/getRisksByOrganization";
|
||||
|
||||
import { RiskStatusSelection } from "./RiskStatusSelection";
|
||||
|
||||
export const SecretScanningLogsTable = () => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [gitRisks, setGitRisks] = useState<GitRisks[]>([]);
|
||||
const [gitRisks, setGitRisks] = useState<IGitRisks[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchRisks = async () => {
|
||||
|
81
frontend/src/views/Settings/OrgSettingsPage/components/OrgDeleteSection/OrgDeleteSection.tsx
Normal file
81
frontend/src/views/Settings/OrgSettingsPage/components/OrgDeleteSection/OrgDeleteSection.tsx
Normal file
@ -0,0 +1,81 @@
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||
import {
|
||||
Button,
|
||||
DeleteActionModal
|
||||
} from "@app/components/v2";
|
||||
import { useOrganization, useUser } from "@app/context";
|
||||
import {
|
||||
useDeleteOrgById,
|
||||
useGetOrgUsers
|
||||
} from "@app/hooks/api";
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
import { navigateUserToOrg } from "@app/views/Login/Login.utils";
|
||||
|
||||
export const OrgDeleteSection = () => {
|
||||
const router = useRouter();
|
||||
const { currentOrg } = useOrganization();
|
||||
const { user } = useUser();
|
||||
const { createNotification } = useNotificationContext();
|
||||
const { data: members } = useGetOrgUsers(currentOrg?._id ?? "");
|
||||
|
||||
const membershipOrg = members?.find((member) => member.user._id === user._id);
|
||||
|
||||
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||
"deleteOrg"
|
||||
] as const);
|
||||
|
||||
const { mutateAsync, isLoading } = useDeleteOrgById();
|
||||
|
||||
const handleDeleteOrgSubmit = async () => {
|
||||
try {
|
||||
if (!currentOrg?._id) return;
|
||||
|
||||
await mutateAsync({
|
||||
organizationId: currentOrg?._id
|
||||
});
|
||||
|
||||
createNotification({
|
||||
text: "Successfully deleted organization",
|
||||
type: "success"
|
||||
});
|
||||
|
||||
await navigateUserToOrg(router);
|
||||
|
||||
handlePopUpClose("deleteOrg");
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
createNotification({
|
||||
text: "Failed to delete organization",
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-4 bg-mineshaft-900 rounded-lg border border-mineshaft-600 mb-6">
|
||||
<p className="text-xl font-semibold text-mineshaft-100 mb-4">
|
||||
Danger Zone
|
||||
</p>
|
||||
<Button
|
||||
isLoading={isLoading}
|
||||
colorSchema="danger"
|
||||
variant="outline_bg"
|
||||
type="submit"
|
||||
onClick={() => handlePopUpOpen("deleteOrg")}
|
||||
isDisabled={(membershipOrg && membershipOrg.role !== "admin")}
|
||||
>
|
||||
{`Delete ${currentOrg?.name}`}
|
||||
</Button>
|
||||
<DeleteActionModal
|
||||
isOpen={popUp.deleteOrg.isOpen}
|
||||
title="Are you sure want to delete this organization?"
|
||||
subTitle={`Permanently remove ${currentOrg?.name} and all of its data. This action is not reversible, so please be careful.`}
|
||||
onChange={(isOpen) => handlePopUpToggle("deleteOrg", isOpen)}
|
||||
deleteKey="confirm"
|
||||
onDeleteApproved={handleDeleteOrgSubmit}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1 @@
|
||||
export { OrgDeleteSection } from "./OrgDeleteSection";
|
@ -1,11 +1,24 @@
|
||||
import { useOrganization, useUser } from "@app/context";
|
||||
import { useGetOrgUsers } from "@app/hooks/api";
|
||||
|
||||
import { OrgDeleteSection } from "../OrgDeleteSection";
|
||||
import { OrgIncidentContactsSection } from "../OrgIncidentContactsSection";
|
||||
import { OrgNameChangeSection } from "../OrgNameChangeSection";
|
||||
|
||||
export const OrgGeneralTab = () => {
|
||||
const { currentOrg } = useOrganization();
|
||||
const { user } = useUser();
|
||||
const { data: members } = useGetOrgUsers(currentOrg?._id ?? "");
|
||||
|
||||
const membershipOrg = members?.find((member) => member.user._id === user._id);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<OrgNameChangeSection />
|
||||
<OrgIncidentContactsSection />
|
||||
{(membershipOrg && membershipOrg.role === "admin") && (
|
||||
<OrgDeleteSection />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -18,7 +18,7 @@ export const OrgIncidentContactsSection = () => {
|
||||
const permission = useOrgPermission();
|
||||
|
||||
return (
|
||||
<div className="p-4 bg-mineshaft-900 rounded-lg border border-mineshaft-600">
|
||||
<div className="p-4 bg-mineshaft-900 mb-6 rounded-lg border border-mineshaft-600">
|
||||
<div className="flex justify-between mb-4">
|
||||
<p className="min-w-max text-xl font-semibold">{t("section.incident.incident-contacts")}</p>
|
||||
<OrgPermissionCan I={OrgPermissionActions.Create} a={OrgPermissionSubjects.IncidentAccount}>
|
||||
|
2
frontend/src/views/Settings/OrgSettingsPage/components/OrgNameChangeSection/OrgNameChangeSection.tsx
2
frontend/src/views/Settings/OrgSettingsPage/components/OrgNameChangeSection/OrgNameChangeSection.tsx
@ -53,7 +53,7 @@ export const OrgNameChangeSection = (): JSX.Element => {
|
||||
onSubmit={handleSubmit(onFormSubmit)}
|
||||
className="p-4 bg-mineshaft-900 mb-6 rounded-lg border border-mineshaft-600"
|
||||
>
|
||||
<p className="text-xl font-semibold text-mineshaft-100 mb-4">Organization name</p>
|
||||
<p className="text-xl font-semibold text-mineshaft-100 mb-4">Name</p>
|
||||
<div className="mb-2 max-w-md">
|
||||
<Controller
|
||||
defaultValue=""
|
||||
|
64
frontend/src/views/Settings/PersonalSettingsPage/DeleteAccountSection/DeleteAccountSection.tsx
Normal file
64
frontend/src/views/Settings/PersonalSettingsPage/DeleteAccountSection/DeleteAccountSection.tsx
Normal file
@ -0,0 +1,64 @@
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||
import {
|
||||
Button,
|
||||
DeleteActionModal
|
||||
} from "@app/components/v2";
|
||||
import { useDeleteUser } from "@app/hooks/api";
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
|
||||
export const DeleteAccountSection = () => {
|
||||
const router = useRouter();
|
||||
const { createNotification } = useNotificationContext();
|
||||
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||
"deleteAccount"
|
||||
] as const);
|
||||
|
||||
const { mutateAsync: deleteUserMutateAsync, isLoading } = useDeleteUser();
|
||||
|
||||
const handleDeleteAccountSubmit = async () => {
|
||||
try {
|
||||
await deleteUserMutateAsync();
|
||||
|
||||
createNotification({
|
||||
text: "Successfully deleted account",
|
||||
type: "success"
|
||||
});
|
||||
|
||||
router.push("/login");
|
||||
handlePopUpClose("deleteAccount");
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
createNotification({
|
||||
text: "Failed to delete account",
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-4 bg-mineshaft-900 rounded-lg border border-mineshaft-600 mb-6">
|
||||
<p className="text-xl font-semibold text-mineshaft-100 mb-4">
|
||||
Danger Zone
|
||||
</p>
|
||||
<Button
|
||||
isLoading={isLoading}
|
||||
colorSchema="danger"
|
||||
variant="outline_bg"
|
||||
type="submit"
|
||||
onClick={() => handlePopUpOpen("deleteAccount")}
|
||||
>
|
||||
Delete my account
|
||||
</Button>
|
||||
<DeleteActionModal
|
||||
isOpen={popUp.deleteAccount.isOpen}
|
||||
title="Are you sure want to delete your account?"
|
||||
subTitle="Permanently remove this account and all of its data. This action is not reversible, so please be careful."
|
||||
onChange={(isOpen) => handlePopUpToggle("deleteAccount", isOpen)}
|
||||
deleteKey="confirm"
|
||||
onDeleteApproved={handleDeleteAccountSubmit}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1 @@
|
||||
export { DeleteAccountSection } from "./DeleteAccountSection";
|
@ -1,4 +1,5 @@
|
||||
import { ChangeLanguageSection } from "../ChangeLanguageSection";
|
||||
import { DeleteAccountSection } from "../DeleteAccountSection";
|
||||
import { EmergencyKitSection } from "../EmergencyKitSection";
|
||||
import { SessionsSection } from "../SessionsSection";
|
||||
import { UserNameSection } from "../UserNameSection";
|
||||
@ -10,6 +11,7 @@ export const PersonalGeneralTab = () => {
|
||||
<ChangeLanguageSection />
|
||||
<SessionsSection />
|
||||
<EmergencyKitSection />
|
||||
<DeleteAccountSection />
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,10 +1,11 @@
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||
import { Button, FormControl, Input } from "@app/components/v2";
|
||||
import {
|
||||
Button,
|
||||
DeleteActionModal,
|
||||
} from "@app/components/v2";
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSub,
|
||||
@ -13,78 +14,74 @@ import {
|
||||
} from "@app/context";
|
||||
import { useToggle } from "@app/hooks";
|
||||
import { useDeleteWorkspace } from "@app/hooks/api";
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
|
||||
export const DeleteProjectSection = () => {
|
||||
const { t } = useTranslation();
|
||||
const router = useRouter();
|
||||
const { createNotification } = useNotificationContext();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { currentOrg } = useOrganization();
|
||||
const [isDeleting, setIsDeleting] = useToggle();
|
||||
const [deleteProjectInput, setDeleteProjectInput] = useState("");
|
||||
const deleteWorkspace = useDeleteWorkspace();
|
||||
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||
"deleteWorkspace"
|
||||
] as const);
|
||||
|
||||
const onDeleteWorkspace = async () => {
|
||||
const { currentOrg } = useOrganization();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const [isDeleting, setIsDeleting] = useToggle();
|
||||
const deleteWorkspace = useDeleteWorkspace();
|
||||
|
||||
const handleDeleteWorkspaceSubmit = async () => {
|
||||
setIsDeleting.on();
|
||||
try {
|
||||
if (!currentWorkspace?._id) return;
|
||||
|
||||
await deleteWorkspace.mutateAsync({
|
||||
workspaceID: currentWorkspace?._id
|
||||
});
|
||||
// redirect user to the org overview
|
||||
router.push(`/org/${currentOrg?._id}/overview`);
|
||||
|
||||
|
||||
createNotification({
|
||||
text: "Successfully deleted workspace",
|
||||
text: "Successfully deleted project",
|
||||
type: "success"
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
||||
router.push(`/org/${currentOrg?._id}/overview`);
|
||||
handlePopUpClose("deleteWorkspace");
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
createNotification({
|
||||
text: "Failed to delete workspace",
|
||||
text: "Failed to delete project",
|
||||
type: "error"
|
||||
});
|
||||
} finally {
|
||||
setIsDeleting.off();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mb-6 p-4 bg-mineshaft-900 rounded-lg border border-red">
|
||||
<p className="mb-3 text-xl font-semibold text-red">{t("settings.project.danger-zone")}</p>
|
||||
<p className="text-gray-400 mb-8">{t("settings.project.danger-zone-note")}</p>
|
||||
<div className="mr-auto mt-4 max-h-28 w-full max-w-md">
|
||||
<FormControl
|
||||
label={
|
||||
<div className="mb-0.5 text-sm font-normal text-gray-400">
|
||||
Type <span className="font-bold">{currentWorkspace?.name}</span> to delete the
|
||||
workspace
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Input
|
||||
onChange={(e) => setDeleteProjectInput(e.target.value)}
|
||||
value={deleteProjectInput}
|
||||
placeholder="Type the project name to delete"
|
||||
className="bg-mineshaft-800"
|
||||
/>
|
||||
</FormControl>
|
||||
</div>
|
||||
<ProjectPermissionCan I={ProjectPermissionActions.Delete} a={ProjectPermissionSub.Workspace}>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
colorSchema="danger"
|
||||
onClick={onDeleteWorkspace}
|
||||
isDisabled={!isAllowed || deleteProjectInput !== currentWorkspace?.name || isDeleting}
|
||||
isLoading={isDeleting}
|
||||
>
|
||||
{t("settings.project.delete-project")}
|
||||
</Button>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
<p className="mt-3 ml-0.5 text-xs text-gray-500">
|
||||
{t("settings.project.delete-project-note")}
|
||||
</p>
|
||||
<div className="p-4 bg-mineshaft-900 rounded-lg border border-mineshaft-600 mb-6">
|
||||
<p className="text-xl font-semibold text-mineshaft-100 mb-4">
|
||||
Danger Zone
|
||||
</p>
|
||||
<ProjectPermissionCan I={ProjectPermissionActions.Delete} a={ProjectPermissionSub.Workspace}>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
isLoading={isDeleting}
|
||||
isDisabled={!isAllowed || isDeleting}
|
||||
colorSchema="danger"
|
||||
variant="outline_bg"
|
||||
type="submit"
|
||||
onClick={() => handlePopUpOpen("deleteWorkspace")}
|
||||
>
|
||||
{`Delete ${currentWorkspace?.name}`}
|
||||
</Button>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
<DeleteActionModal
|
||||
isOpen={popUp.deleteWorkspace.isOpen}
|
||||
title="Are you sure want to delete this project?"
|
||||
subTitle={`Permanently remove ${currentWorkspace?.name} and all of its data. This action is not reversible, so please be careful.`}
|
||||
onChange={(isOpen) => handlePopUpToggle("deleteWorkspace", isOpen)}
|
||||
deleteKey="confirm"
|
||||
onDeleteApproved={handleDeleteWorkspaceSubmit}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
Reference in New Issue
Block a user