mirror of
https://github.com/Infisical/infisical.git
synced 2025-04-10 07:25:40 +00:00
Compare commits
56 Commits
infisical/
...
infisical/
Author | SHA1 | Date | |
---|---|---|---|
8fbca05052 | |||
d99830067e | |||
cdc8ef95ab | |||
072e97b956 | |||
4f26a7cad3 | |||
7bb6ff3d0c | |||
ecccec8e35 | |||
7fd15a06e5 | |||
5d4a37004e | |||
aa61fd091d | |||
04ac54bcfa | |||
38dbf1e738 | |||
ddf9d7848c | |||
0b40de49ec | |||
b1d16cab39 | |||
fb7c7045e9 | |||
d570828e47 | |||
2a92b6c787 | |||
ee54fdabe1 | |||
136308f299 | |||
ba41244877 | |||
c4dcf334f0 | |||
66bac3ef48 | |||
e5347719c3 | |||
275416a08f | |||
abe1f54aab | |||
13c1e2b349 | |||
f5a9afec61 | |||
d0a85c98b2 | |||
e0669cae7c | |||
2c011b7d53 | |||
1b24a9b6e9 | |||
00c173aead | |||
2e15ad0625 | |||
3f0b6dc6c1 | |||
f766a1eb29 | |||
543c55b5a6 | |||
cdb1d38f99 | |||
0a53b72cce | |||
b921c376b2 | |||
b1ec59eb67 | |||
4e6e12932a | |||
792c382743 | |||
f5c8e537c9 | |||
4bf09a8efc | |||
001265cf2a | |||
a56a135396 | |||
9838c29867 | |||
4f5946b252 | |||
dc23517133 | |||
f119c921d0 | |||
b6ef55783e | |||
feade5d029 | |||
8f74d20e74 | |||
0eb7896b59 | |||
9fcecc9c92 |
@ -1,2 +1,10 @@
|
||||
backend/node_modules
|
||||
frontend/node_modules
|
||||
frontend/node_modules
|
||||
backend/frontend-build
|
||||
**/node_modules
|
||||
**/.next
|
||||
.dockerignore
|
||||
.git
|
||||
README.md
|
||||
.dockerignore
|
||||
**/Dockerfile
|
||||
|
@ -73,3 +73,6 @@ jobs:
|
||||
infisical/infisical:${{ steps.extract_version.outputs.version }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
file: Dockerfile.standalone-infisical
|
||||
build-args: |
|
||||
POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }}
|
||||
NEXT_INFISICAL_PLATFORM_VERSION=${{ steps.extract_version.outputs.version }}
|
||||
|
6
.gitignore
vendored
6
.gitignore
vendored
@ -33,7 +33,7 @@ reports
|
||||
junit.xml
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
@ -59,4 +59,6 @@ yarn-error.log*
|
||||
.infisical.json
|
||||
|
||||
# Editor specific
|
||||
.vscode/*
|
||||
.vscode/*
|
||||
|
||||
frontend-build
|
@ -1,7 +1,12 @@
|
||||
ARG POSTHOG_HOST=https://app.posthog.com
|
||||
ARG POSTHOG_API_KEY=posthog-api-key
|
||||
|
||||
FROM node:16-alpine AS frontend-dependencies
|
||||
FROM node:16-alpine AS base
|
||||
|
||||
FROM base AS frontend-dependencies
|
||||
|
||||
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
|
||||
RUN apk add --no-cache libc6-compat
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@ -11,7 +16,7 @@ COPY frontend/package.json frontend/package-lock.json frontend/next.config.js ./
|
||||
RUN npm ci --only-production --ignore-scripts
|
||||
|
||||
# Rebuild the source code only when needed
|
||||
FROM node:16-alpine AS frontend-builder
|
||||
FROM base AS frontend-builder
|
||||
WORKDIR /app
|
||||
|
||||
# Copy dependencies
|
||||
@ -27,18 +32,20 @@ ARG POSTHOG_API_KEY
|
||||
ENV NEXT_PUBLIC_POSTHOG_API_KEY $POSTHOG_API_KEY
|
||||
ARG INTERCOM_ID
|
||||
ENV NEXT_PUBLIC_INTERCOM_ID $INTERCOM_ID
|
||||
ARG INFISICAL_PLATFORM_VERSION
|
||||
ENV NEXT_PUBLIC_INFISICAL_PLATFORM_VERSION $INFISICAL_PLATFORM_VERSION
|
||||
|
||||
# Build
|
||||
RUN npm run build
|
||||
|
||||
# Production image
|
||||
FROM node:16-alpine AS frontend-runner
|
||||
FROM base AS frontend-runner
|
||||
WORKDIR /app
|
||||
|
||||
RUN addgroup --system --gid 1001 nodejs
|
||||
RUN adduser --system --uid 1001 nextjs
|
||||
RUN adduser --system --uid 1001 non-root-user
|
||||
|
||||
RUN mkdir -p /app/.next/cache/images && chown nextjs:nodejs /app/.next/cache/images
|
||||
RUN mkdir -p /app/.next/cache/images && chown non-root-user:nodejs /app/.next/cache/images
|
||||
VOLUME /app/.next/cache/images
|
||||
|
||||
ARG POSTHOG_API_KEY
|
||||
@ -48,20 +55,22 @@ ARG INTERCOM_ID
|
||||
ENV NEXT_PUBLIC_INTERCOM_ID=$INTERCOM_ID \
|
||||
BAKED_NEXT_PUBLIC_INTERCOM_ID=$INTERCOM_ID
|
||||
|
||||
COPY --chown=nextjs:nodejs --chmod=555 frontend/scripts ./scripts
|
||||
COPY --chown=non-root-user:nodejs --chmod=555 frontend/scripts ./scripts
|
||||
COPY --from=frontend-builder /app/public ./public
|
||||
RUN chown nextjs:nodejs ./public/data
|
||||
COPY --from=frontend-builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||
COPY --from=frontend-builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||
RUN chown non-root-user:nodejs ./public/data
|
||||
COPY --from=frontend-builder --chown=non-root-user:nodejs /app/.next/standalone ./
|
||||
COPY --from=frontend-builder --chown=non-root-user:nodejs /app/.next/static ./.next/static
|
||||
|
||||
USER nextjs
|
||||
USER non-root-user
|
||||
|
||||
ENV NEXT_TELEMETRY_DISABLED 1
|
||||
|
||||
##
|
||||
## BACKEND
|
||||
##
|
||||
FROM node:16-alpine AS backend-build
|
||||
FROM base AS backend-build
|
||||
RUN addgroup --system --gid 1001 nodejs \
|
||||
&& adduser --system --uid 1001 non-root-user
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@ -69,10 +78,11 @@ COPY backend/package*.json ./
|
||||
RUN npm ci --only-production
|
||||
|
||||
COPY /backend .
|
||||
COPY --chown=non-root-user:nodejs standalone-entrypoint.sh standalone-entrypoint.sh
|
||||
RUN npm run build
|
||||
|
||||
# Production stage
|
||||
FROM node:16-alpine AS backend-runner
|
||||
FROM base AS backend-runner
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@ -81,27 +91,36 @@ RUN npm ci --only-production
|
||||
|
||||
COPY --from=backend-build /app .
|
||||
|
||||
RUN mkdir frontend-build
|
||||
|
||||
# Production stage
|
||||
FROM node:16-alpine AS production
|
||||
FROM base AS production
|
||||
RUN addgroup --system --gid 1001 nodejs \
|
||||
&& adduser --system --uid 1001 non-root-user
|
||||
|
||||
WORKDIR /
|
||||
|
||||
# Install PM2
|
||||
RUN npm install -g pm2
|
||||
# Copy ecosystem.config.js
|
||||
COPY ecosystem.config.js .
|
||||
|
||||
RUN apk add --no-cache nginx
|
||||
|
||||
COPY nginx/default-stand-alone-docker.conf /etc/nginx/nginx.conf
|
||||
|
||||
COPY --from=backend-runner /app /backend
|
||||
|
||||
COPY --from=frontend-runner /app/ /app/
|
||||
COPY --from=frontend-runner /app ./backend/frontend-build
|
||||
|
||||
EXPOSE 80
|
||||
ENV PORT 8080
|
||||
ENV HTTPS_ENABLED false
|
||||
ENV NODE_ENV production
|
||||
ENV STANDALONE_BUILD true
|
||||
|
||||
WORKDIR /backend
|
||||
|
||||
ENV TELEMETRY_ENABLED true
|
||||
|
||||
HEALTHCHECK --interval=10s --timeout=3s --start-period=10s \
|
||||
CMD node healthcheck.js
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
USER non-root-user
|
||||
|
||||
CMD ["./standalone-entrypoint.sh"]
|
||||
|
||||
CMD ["pm2-runtime", "start", "ecosystem.config.js"]
|
||||
|
||||
|
||||
|
@ -133,7 +133,6 @@ Whether it's big or small, we love contributions. Check out our guide to see how
|
||||
|
||||
Not sure where to get started? You can:
|
||||
|
||||
- [Book a free, non-pressure pairing session / code walkthrough with one of our teammates](https://cal.com/tony-infisical/30-min-meeting-contributing)!
|
||||
- Join our <a href="https://infisical.com/slack">Slack</a>, and ask us any questions there.
|
||||
- Join our [community calls](https://us06web.zoom.us/j/82623506356) every Wednesday at 11am EST to ask any questions, provide feedback, hangout and more.
|
||||
|
||||
|
@ -26,6 +26,7 @@
|
||||
],
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"unused-imports/no-unused-imports": "error",
|
||||
"@typescript-eslint/no-extra-semi": "off", // added to be able to push
|
||||
"unused-imports/no-unused-vars": [
|
||||
"warn",
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -9,7 +9,6 @@ import { Secret, ServiceTokenData } from "../../models";
|
||||
import { Folder } from "../../models/folder";
|
||||
import {
|
||||
appendFolder,
|
||||
generateFolderId,
|
||||
getAllFolderIds,
|
||||
getFolderByPath,
|
||||
getFolderWithPathFromId,
|
||||
@ -132,9 +131,6 @@ export const createFolder = async (req: Request, res: Response) => {
|
||||
|
||||
// space has no folders initialized
|
||||
if (!folders) {
|
||||
if (directory !== "/") throw ERR_FOLDER_NOT_FOUND;
|
||||
|
||||
const id = generateFolderId();
|
||||
const folder = new Folder({
|
||||
workspace: workspaceId,
|
||||
environment,
|
||||
@ -142,14 +138,15 @@ export const createFolder = async (req: Request, res: Response) => {
|
||||
id: "root",
|
||||
name: "root",
|
||||
version: 1,
|
||||
children: [{ id, name: folderName, children: [], version: 1 }]
|
||||
children: []
|
||||
}
|
||||
});
|
||||
const { parent, child } = appendFolder(folder.nodes, { folderName, directory });
|
||||
await folder.save();
|
||||
const folderVersion = new FolderVersion({
|
||||
workspace: workspaceId,
|
||||
environment,
|
||||
nodes: folder.nodes
|
||||
nodes: parent
|
||||
});
|
||||
await folderVersion.save();
|
||||
await EESecretService.takeSecretSnapshot({
|
||||
@ -163,9 +160,9 @@ export const createFolder = async (req: Request, res: Response) => {
|
||||
type: EventType.CREATE_FOLDER,
|
||||
metadata: {
|
||||
environment,
|
||||
folderId: id,
|
||||
folderId: child.id,
|
||||
folderName,
|
||||
folderPath: `root/${folderName}`
|
||||
folderPath: directory
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -173,26 +170,26 @@ export const createFolder = async (req: Request, res: Response) => {
|
||||
}
|
||||
);
|
||||
|
||||
return res.json({ folder: { id, name: folderName } });
|
||||
return res.json({ folder: { id: child.id, name: folderName } });
|
||||
}
|
||||
|
||||
const parentFolder = getFolderByPath(folders.nodes, directory);
|
||||
if (!parentFolder) throw ERR_FOLDER_NOT_FOUND;
|
||||
const { parent, child, hasCreated } = appendFolder(folders.nodes, { folderName, directory });
|
||||
|
||||
if (!hasCreated) return res.json({ folder: child });
|
||||
|
||||
const folder = appendFolder(folders.nodes, { folderName, parentFolderId: parentFolder.id });
|
||||
await Folder.findByIdAndUpdate(folders._id, folders);
|
||||
|
||||
const folderVersion = new FolderVersion({
|
||||
workspace: workspaceId,
|
||||
environment,
|
||||
nodes: parentFolder
|
||||
nodes: parent
|
||||
});
|
||||
await folderVersion.save();
|
||||
|
||||
await EESecretService.takeSecretSnapshot({
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
folderId: parentFolder.id
|
||||
folderId: child.id
|
||||
});
|
||||
|
||||
await EEAuditLogService.createAuditLog(
|
||||
@ -201,7 +198,7 @@ export const createFolder = async (req: Request, res: Response) => {
|
||||
type: EventType.CREATE_FOLDER,
|
||||
metadata: {
|
||||
environment,
|
||||
folderId: folder.id,
|
||||
folderId: child.id,
|
||||
folderName,
|
||||
folderPath: directory
|
||||
}
|
||||
@ -211,7 +208,7 @@ export const createFolder = async (req: Request, res: Response) => {
|
||||
}
|
||||
);
|
||||
|
||||
return res.json({ folder });
|
||||
return res.json({ folder: child });
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
});
|
||||
}
|
@ -476,7 +476,7 @@ export const getSecrets = async (req: Request, res: Response) => {
|
||||
|
||||
if (folderId && folderId !== "root") {
|
||||
const folder = await Folder.findOne({ workspace: workspaceId, environment });
|
||||
if (!folder) throw BadRequestError({ message: "Folder not found" });
|
||||
if (!folder) return res.send({ secrets: [] });
|
||||
|
||||
secretPath = getFolderWithPathFromId(folder.nodes, folderId).folderPath;
|
||||
}
|
||||
@ -673,6 +673,7 @@ export const updateSecretByName = async (req: Request, res: Response) => {
|
||||
secretValueCiphertext,
|
||||
secretValueTag,
|
||||
secretValueIV,
|
||||
secretId,
|
||||
type,
|
||||
environment,
|
||||
secretPath,
|
||||
@ -741,6 +742,7 @@ export const updateSecretByName = async (req: Request, res: Response) => {
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
type,
|
||||
secretId,
|
||||
authData: req.authData,
|
||||
newSecretName,
|
||||
secretValueCiphertext,
|
||||
@ -777,7 +779,7 @@ export const updateSecretByName = async (req: Request, res: Response) => {
|
||||
*/
|
||||
export const deleteSecretByName = async (req: Request, res: Response) => {
|
||||
const {
|
||||
body: { type, environment, secretPath, workspaceId },
|
||||
body: { type, environment, secretPath, workspaceId, secretId },
|
||||
params: { secretName }
|
||||
} = await validateRequest(reqValidator.DeleteSecretByNameV3, req);
|
||||
|
||||
@ -813,6 +815,7 @@ export const deleteSecretByName = async (req: Request, res: Response) => {
|
||||
|
||||
const { secret } = await SecretService.deleteSecret({
|
||||
secretName,
|
||||
secretId,
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
type,
|
||||
|
@ -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) => {
|
||||
|
@ -14,7 +14,7 @@ export const initDatabaseHelper = async ({
|
||||
}) => {
|
||||
try {
|
||||
await mongoose.connect(mongoURL);
|
||||
|
||||
|
||||
// allow empty strings to pass the required validator
|
||||
mongoose.Schema.Types.String.checkRequired(v => typeof v === "string");
|
||||
|
||||
@ -31,14 +31,10 @@ export const initDatabaseHelper = async ({
|
||||
* Close database conection
|
||||
*/
|
||||
export const closeDatabaseHelper = async () => {
|
||||
return Promise.all([
|
||||
new Promise((resolve) => {
|
||||
if (mongoose.connection && mongoose.connection.readyState == 1) {
|
||||
mongoose.connection.close()
|
||||
.then(() => resolve("Database connection closed"));
|
||||
} else {
|
||||
resolve("Database connection already closed");
|
||||
}
|
||||
}),
|
||||
]);
|
||||
}
|
||||
if (mongoose.connection && mongoose.connection.readyState === 1) {
|
||||
await mongoose.connection.close();
|
||||
return "Database connection closed";
|
||||
} else {
|
||||
return "Database connection already closed";
|
||||
}
|
||||
};
|
@ -1,5 +1,43 @@
|
||||
import { Types } from "mongoose";
|
||||
import { MembershipOrg, Organization } from "../models";
|
||||
import mongoose, { Types, mongo } 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,320 @@ 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,
|
||||
existingSession
|
||||
}: {
|
||||
organizationId: Types.ObjectId;
|
||||
existingSession?: mongo.ClientSession;
|
||||
}) => {
|
||||
|
||||
let session;
|
||||
|
||||
if (existingSession) {
|
||||
session = existingSession;
|
||||
} else {
|
||||
session = await mongoose.startSession();
|
||||
session.startTransaction();
|
||||
}
|
||||
|
||||
try {
|
||||
const organization = await Organization.findByIdAndDelete(
|
||||
organizationId,
|
||||
{
|
||||
session
|
||||
}
|
||||
);
|
||||
|
||||
if (!organization) throw ResourceNotFoundError();
|
||||
|
||||
await MembershipOrg.deleteMany({
|
||||
organization: organization._id
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await BotOrg.deleteMany({
|
||||
organization: organization._id
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await SSOConfig.deleteMany({
|
||||
organization: organization._id
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await Role.deleteMany({
|
||||
organization: organization._id
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await IncidentContactOrg.deleteMany({
|
||||
organization: organization._id
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await GitRisks.deleteMany({
|
||||
organization: organization._id
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await GitAppInstallationSession.deleteMany({
|
||||
organization: organization._id
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await GitAppOrganizationInstallation.deleteMany({
|
||||
organization: organization._id
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
const workspaceIds = await Workspace.distinct("_id", {
|
||||
organization: organization._id
|
||||
});
|
||||
|
||||
await Workspace.deleteMany({
|
||||
organization: organization._id
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await Membership.deleteMany({
|
||||
workspace: {
|
||||
$in: workspaceIds
|
||||
}
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await Key.deleteMany({
|
||||
workspace: {
|
||||
$in: workspaceIds
|
||||
}
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await Bot.deleteMany({
|
||||
workspace: {
|
||||
$in: workspaceIds
|
||||
}
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await BotKey.deleteMany({
|
||||
workspace: {
|
||||
$in: workspaceIds
|
||||
}
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await SecretBlindIndexData.deleteMany({
|
||||
workspace: {
|
||||
$in: workspaceIds
|
||||
}
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await Secret.deleteMany({
|
||||
workspace: {
|
||||
$in: workspaceIds
|
||||
}
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await SecretVersion.deleteMany({
|
||||
workspace: {
|
||||
$in: workspaceIds
|
||||
}
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await SecretSnapshot.deleteMany({
|
||||
workspace: {
|
||||
$in: workspaceIds
|
||||
}
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await SecretImport.deleteMany({
|
||||
workspace: {
|
||||
$in: workspaceIds
|
||||
}
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await Folder.deleteMany({
|
||||
workspace: {
|
||||
$in: workspaceIds
|
||||
}
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await FolderVersion.deleteMany({
|
||||
workspace: {
|
||||
$in: workspaceIds
|
||||
}
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await Webhook.deleteMany({
|
||||
workspace: {
|
||||
$in: workspaceIds
|
||||
}
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await TrustedIP.deleteMany({
|
||||
workspace: {
|
||||
$in: workspaceIds
|
||||
}
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await Tag.deleteMany({
|
||||
workspace: {
|
||||
$in: workspaceIds
|
||||
}
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await IntegrationAuth.deleteMany({
|
||||
workspace: {
|
||||
$in: workspaceIds
|
||||
}
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await Integration.deleteMany({
|
||||
workspace: {
|
||||
$in: workspaceIds
|
||||
}
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await ServiceToken.deleteMany({
|
||||
workspace: {
|
||||
$in: workspaceIds
|
||||
}
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await ServiceTokenData.deleteMany({
|
||||
workspace: {
|
||||
$in: workspaceIds
|
||||
}
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await ServiceTokenDataV3.deleteMany({
|
||||
workspace: {
|
||||
$in: workspaceIds
|
||||
}
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await ServiceTokenDataV3Key.deleteMany({
|
||||
workspace: {
|
||||
$in: workspaceIds
|
||||
}
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await AuditLog.deleteMany({
|
||||
workspace: {
|
||||
$in: workspaceIds
|
||||
}
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await Log.deleteMany({
|
||||
workspace: {
|
||||
$in: workspaceIds
|
||||
}
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await Action.deleteMany({
|
||||
workspace: {
|
||||
$in: workspaceIds
|
||||
}
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await SecretApprovalPolicy.deleteMany({
|
||||
workspace: {
|
||||
$in: workspaceIds
|
||||
}
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await SecretApprovalRequest.deleteMany({
|
||||
workspace: {
|
||||
$in: workspaceIds
|
||||
}
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
if (organization.customerId) {
|
||||
// delete from stripe here
|
||||
await licenseServerKeyRequest.delete(
|
||||
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${organization.customerId}`
|
||||
);
|
||||
}
|
||||
|
||||
return organization;
|
||||
} catch (err) {
|
||||
if (!existingSession) {
|
||||
await session.abortTransaction();
|
||||
}
|
||||
throw InternalServerError({
|
||||
message: "Failed to delete organization"
|
||||
});
|
||||
} finally {
|
||||
if (!existingSession) {
|
||||
await session.commitTransaction();
|
||||
session.endSession();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update organization subscription quantity to reflect number of members in
|
||||
* the organization.
|
||||
|
@ -57,10 +57,10 @@ import { getAnImportedSecret } from "../services/SecretImportService";
|
||||
|
||||
/**
|
||||
* Validate scope for service token v3
|
||||
* @param authPayload
|
||||
* @param environment
|
||||
* @param secretPath
|
||||
* @returns
|
||||
* @param authPayload
|
||||
* @param environment
|
||||
* @param secretPath
|
||||
* @returns
|
||||
*/
|
||||
export const isValidScopeV3 = ({
|
||||
authPayload,
|
||||
@ -68,37 +68,40 @@ export const isValidScopeV3 = ({
|
||||
secretPath,
|
||||
requiredPermissions
|
||||
}: {
|
||||
authPayload: IServiceTokenDataV3,
|
||||
environment: string,
|
||||
secretPath: string,
|
||||
requiredPermissions: Permission[]
|
||||
authPayload: IServiceTokenDataV3;
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
requiredPermissions: Permission[];
|
||||
}) => {
|
||||
const { scopes } = authPayload;
|
||||
|
||||
|
||||
const validScope = scopes.find(
|
||||
(scope) =>
|
||||
picomatch.isMatch(secretPath, scope.secretPath, { strictSlashes: false }) &&
|
||||
scope.environment === environment
|
||||
);
|
||||
|
||||
if (validScope && !requiredPermissions.every(permission => validScope.permissions.includes(permission))) {
|
||||
if (
|
||||
validScope &&
|
||||
!requiredPermissions.every((permission) => validScope.permissions.includes(permission))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return Boolean(validScope);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate scope for service token v2
|
||||
* @param authPayload
|
||||
* @param environment
|
||||
* @param secretPath
|
||||
* @returns
|
||||
* @param authPayload
|
||||
* @param environment
|
||||
* @param secretPath
|
||||
* @returns
|
||||
*/
|
||||
export const isValidScope = (
|
||||
authPayload: IServiceTokenData,
|
||||
environment: string,
|
||||
secretPath: string,
|
||||
secretPath: string
|
||||
) => {
|
||||
const { scopes: tkScopes } = authPayload;
|
||||
const validScope = tkScopes.find(
|
||||
@ -787,6 +790,7 @@ export const getSecretHelper = async ({
|
||||
export const updateSecretHelper = async ({
|
||||
secretName,
|
||||
workspaceId,
|
||||
secretId,
|
||||
environment,
|
||||
type,
|
||||
authData,
|
||||
@ -809,11 +813,20 @@ export const updateSecretHelper = async ({
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
const oldSecretBlindIndex = await generateSecretBlindIndexWithSaltHelper({
|
||||
let oldSecretBlindIndex = await generateSecretBlindIndexWithSaltHelper({
|
||||
secretName,
|
||||
salt
|
||||
});
|
||||
|
||||
if (secretId) {
|
||||
const secret = await Secret.findOne({
|
||||
workspace: workspaceId,
|
||||
environment,
|
||||
_id: secretId
|
||||
}).select("secretBlindIndex");
|
||||
if (secret && secret.secretBlindIndex) oldSecretBlindIndex = secret.secretBlindIndex;
|
||||
}
|
||||
|
||||
let secret: ISecret | null = null;
|
||||
const folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath);
|
||||
|
||||
@ -888,6 +901,9 @@ export const updateSecretHelper = async ({
|
||||
skipMultilineEncoding,
|
||||
secretBlindIndex: newSecretNameBlindIndex,
|
||||
$inc: { version: 1 }
|
||||
},
|
||||
{
|
||||
new: true
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -1000,12 +1016,22 @@ export const deleteSecretHelper = async ({
|
||||
environment,
|
||||
type,
|
||||
authData,
|
||||
secretPath = "/"
|
||||
secretPath = "/",
|
||||
// used for update corner case and blindIndex goes wrong way
|
||||
secretId
|
||||
}: DeleteSecretParams) => {
|
||||
const secretBlindIndex = await generateSecretBlindIndexHelper({
|
||||
let secretBlindIndex = await generateSecretBlindIndexHelper({
|
||||
secretName,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
if (secretId) {
|
||||
const secret = await Secret.findOne({
|
||||
workspace: workspaceId,
|
||||
environment,
|
||||
_id: secretId
|
||||
}).select("secretBlindIndex");
|
||||
if (secret && secret.secretBlindIndex) secretBlindIndex = secret.secretBlindIndex;
|
||||
}
|
||||
|
||||
const folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath);
|
||||
|
||||
|
@ -1,5 +1,27 @@
|
||||
import { IUser, User } from "../models";
|
||||
import mongoose, { Types, mongo } 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,207 @@ 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,
|
||||
existingSession
|
||||
}: {
|
||||
userId: Types.ObjectId;
|
||||
existingSession?: mongo.ClientSession;
|
||||
}) => {
|
||||
|
||||
let session;
|
||||
|
||||
if (existingSession) {
|
||||
session = existingSession;
|
||||
} else {
|
||||
session = await mongoose.startSession();
|
||||
session.startTransaction();
|
||||
}
|
||||
|
||||
try {
|
||||
const user = await User.findByIdAndDelete(userId, {
|
||||
session
|
||||
});
|
||||
|
||||
if (!user) throw ResourceNotFoundError();
|
||||
|
||||
await checkDeleteUserConditions({
|
||||
userId: user._id
|
||||
});
|
||||
|
||||
await UserAction.deleteMany({
|
||||
user: user._id
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await BackupPrivateKey.deleteMany({
|
||||
user: user._id
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await APIKeyData.deleteMany({
|
||||
user: user._id
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await Action.deleteMany({
|
||||
user: user._id
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await Log.deleteMany({
|
||||
user: user._id
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await TokenVersion.deleteMany({
|
||||
user: user._id
|
||||
});
|
||||
|
||||
await Key.deleteMany({
|
||||
receiver: user._id
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
const membershipOrgs = await MembershipOrg.find({
|
||||
user: userId
|
||||
}, null, {
|
||||
session
|
||||
});
|
||||
|
||||
// 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,
|
||||
existingSession: session
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const memberships = await Membership.find({
|
||||
user: userId
|
||||
}, null, {
|
||||
session
|
||||
});
|
||||
|
||||
// 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,
|
||||
existingSession: session
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await MembershipOrg.deleteMany({
|
||||
user: userId
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await Membership.deleteMany({
|
||||
user: userId
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
return user;
|
||||
} catch (err) {
|
||||
if (!existingSession) {
|
||||
await session.abortTransaction();
|
||||
}
|
||||
throw InternalServerError({
|
||||
message: "Failed to delete account"
|
||||
})
|
||||
} finally {
|
||||
if (!existingSession) {
|
||||
await session.commitTransaction();
|
||||
session.endSession();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,18 +1,42 @@
|
||||
import { Types } from "mongoose";
|
||||
import mongoose, { Types, mongo } 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,190 @@ 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,
|
||||
existingSession
|
||||
}: {
|
||||
workspaceId: Types.ObjectId;
|
||||
existingSession?: mongo.ClientSession;
|
||||
}) => {
|
||||
|
||||
let session;
|
||||
|
||||
if (existingSession) {
|
||||
session = existingSession;
|
||||
} else {
|
||||
session = await mongoose.startSession();
|
||||
session.startTransaction();
|
||||
}
|
||||
|
||||
try {
|
||||
const workspace = await Workspace.findByIdAndDelete(workspaceId, { session });
|
||||
|
||||
if (!workspace) throw ResourceNotFoundError();
|
||||
|
||||
await Membership.deleteMany({
|
||||
workspace: workspace._id
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await Key.deleteMany({
|
||||
workspace: workspace._id
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await Bot.deleteMany({
|
||||
workspace: workspace._id
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await BotKey.deleteMany({
|
||||
workspace: workspace._id
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await SecretBlindIndexData.deleteMany({
|
||||
workspace: workspace._id
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await Secret.deleteMany({
|
||||
workspace: workspace._id
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await SecretVersion.deleteMany({
|
||||
workspace: workspace._id
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await SecretSnapshot.deleteMany({
|
||||
workspace: workspace._id
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await SecretImport.deleteMany({
|
||||
workspace: workspace._id
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await Folder.deleteMany({
|
||||
workspace: workspace._id
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await FolderVersion.deleteMany({
|
||||
workspace: workspace._id
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await Webhook.deleteMany({
|
||||
workspace: workspace._id
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await TrustedIP.deleteMany({
|
||||
workspace: workspace._id
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await Tag.deleteMany({
|
||||
workspace: workspace._id
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await IntegrationAuth.deleteMany({
|
||||
workspace: workspace._id
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await Integration.deleteMany({
|
||||
workspace: workspace._id
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await ServiceToken.deleteMany({
|
||||
workspace: workspace._id
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await ServiceTokenData.deleteMany({
|
||||
workspace: workspace._id
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await ServiceTokenDataV3.deleteMany({
|
||||
workspace: workspace._id
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await ServiceTokenDataV3Key.deleteMany({
|
||||
workspace: workspace._id
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await AuditLog.deleteMany({
|
||||
workspace: workspace._id
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await Log.deleteMany({
|
||||
workspace: workspace._id
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await Action.deleteMany({
|
||||
workspace: workspace._id
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await SecretApprovalPolicy.deleteMany({
|
||||
workspace: workspace._id
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
await SecretApprovalRequest.deleteMany({
|
||||
workspace: workspace._id
|
||||
}, {
|
||||
session
|
||||
});
|
||||
|
||||
return workspace;
|
||||
} catch (err) {
|
||||
if (!existingSession) {
|
||||
await session.abortTransaction();
|
||||
}
|
||||
throw InternalServerError({
|
||||
message: "Failed to delete organization"
|
||||
});
|
||||
} finally {
|
||||
if (!existingSession) {
|
||||
await session.commitTransaction();
|
||||
session.endSession();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -87,6 +87,9 @@ import { setup } from "./utils/setup";
|
||||
import { syncSecretsToThirdPartyServices } from "./queues/integrations/syncSecretsToThirdPartyServices";
|
||||
import { githubPushEventSecretScan } from "./queues/secret-scanning/githubScanPushEvent";
|
||||
const SmeeClient = require("smee-client"); // eslint-disable-line
|
||||
import path from "path";
|
||||
|
||||
let handler: null | any = null;
|
||||
|
||||
const main = async () => {
|
||||
await setup();
|
||||
@ -147,6 +150,27 @@ const main = async () => {
|
||||
next();
|
||||
});
|
||||
|
||||
if ((await getNodeEnv()) === "production" && process.env.STANDALONE_BUILD === "true") {
|
||||
const nextJsBuildPath = path.join(__dirname, "../frontend-build");
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const conf = require("../frontend-build/.next/required-server-files.json").config;
|
||||
const NextServer =
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
require("../frontend-build/node_modules/next/dist/server/next-server").default;
|
||||
const nextApp = new NextServer({
|
||||
dev: false,
|
||||
dir: nextJsBuildPath,
|
||||
port: await getPort(),
|
||||
conf,
|
||||
hostname: "local",
|
||||
customServer: false
|
||||
});
|
||||
|
||||
handler = nextApp.getRequestHandler();
|
||||
}
|
||||
|
||||
// (EE) routes
|
||||
app.use("/api/v1/secret", eeSecretRouter);
|
||||
app.use("/api/v1/secret-snapshot", eeSecretSnapshotRouter);
|
||||
@ -209,6 +233,12 @@ const main = async () => {
|
||||
// server status
|
||||
app.use("/api", healthCheck);
|
||||
|
||||
if (handler) {
|
||||
app.all("*", (req, res) => {
|
||||
return handler(req, res);
|
||||
});
|
||||
}
|
||||
|
||||
//* Handle unrouted requests and respond with proper error message as well as status code
|
||||
app.use((req, res, next) => {
|
||||
if (res.headersSent) return next();
|
||||
|
@ -44,6 +44,7 @@ export interface GetSecretParams {
|
||||
export interface UpdateSecretParams {
|
||||
secretName: string;
|
||||
newSecretName?: string;
|
||||
secretId?: string;
|
||||
secretKeyCiphertext?: string;
|
||||
secretKeyIV?: string;
|
||||
secretKeyTag?: string;
|
||||
@ -64,6 +65,7 @@ export interface UpdateSecretParams {
|
||||
|
||||
export interface DeleteSecretParams {
|
||||
secretName: string;
|
||||
secretId?: string;
|
||||
workspaceId: Types.ObjectId;
|
||||
environment: string;
|
||||
type: "shared" | "personal";
|
||||
|
@ -3,8 +3,8 @@ import { ErrorRequestHandler } from "express";
|
||||
import { TokenExpiredError } from "jsonwebtoken";
|
||||
import { InternalServerError, UnauthorizedRequestError } from "../utils/errors";
|
||||
import { getLogger } from "../utils/logger";
|
||||
import RequestError, { LogLevel } from "../utils/requestError";
|
||||
import { getNodeEnv } from "../config";
|
||||
import RequestError from "../utils/requestError";
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
|
||||
export const requestErrorHandler: ErrorRequestHandler = async (
|
||||
error: RequestError | Error,
|
||||
@ -14,41 +14,37 @@ export const requestErrorHandler: ErrorRequestHandler = async (
|
||||
) => {
|
||||
if (res.headersSent) return next();
|
||||
|
||||
if (await getNodeEnv() !== "production") {
|
||||
/* eslint-disable no-console */
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
//TODO: Find better way to type check for error. In current setting you need to cast type to get the functions and variables from RequestError
|
||||
if (error instanceof TokenExpiredError) {
|
||||
error = UnauthorizedRequestError({ stack: error.stack, message: "Token expired" });
|
||||
} else if (!(error instanceof RequestError)) {
|
||||
error = InternalServerError({
|
||||
context: { exception: error.message },
|
||||
stack: error.stack,
|
||||
});
|
||||
const logAndCaptureException = async (error: RequestError) => {
|
||||
(await getLogger("backend-main")).log(
|
||||
(<RequestError>error).levelName.toLowerCase(),
|
||||
(<RequestError>error).message
|
||||
`${error.stack}\n${error.message}`
|
||||
);
|
||||
}
|
||||
|
||||
//* Set Sentry user identification if req.user is populated
|
||||
if (req.user !== undefined && req.user !== null) {
|
||||
Sentry.setUser({ email: (req.user as any).email });
|
||||
}
|
||||
//* Only sent error to Sentry if LogLevel is one of the following level 'ERROR', 'EMERGENCY' or 'CRITICAL'
|
||||
//* with this we will eliminate false-positive errors like 'BadRequestError', 'UnauthorizedRequestError' and so on
|
||||
if (
|
||||
[LogLevel.ERROR, LogLevel.EMERGENCY, LogLevel.CRITICAL].includes(
|
||||
(<RequestError>error).level
|
||||
)
|
||||
) {
|
||||
//* Set Sentry user identification if req.user is populated
|
||||
if (req.user !== undefined && req.user !== null) {
|
||||
Sentry.setUser({ email: (req.user as any).email });
|
||||
}
|
||||
|
||||
Sentry.captureException(error);
|
||||
};
|
||||
|
||||
if (error instanceof RequestError) {
|
||||
if (error instanceof TokenExpiredError) {
|
||||
error = UnauthorizedRequestError({ stack: error.stack, message: "Token expired" });
|
||||
}
|
||||
await logAndCaptureException((<RequestError>error));
|
||||
} else {
|
||||
if (error instanceof ForbiddenError) {
|
||||
error = UnauthorizedRequestError({ context: { exception: error.message }, stack: error.stack })
|
||||
} else {
|
||||
error = InternalServerError({ context: { exception: error.message }, stack: error.stack });
|
||||
}
|
||||
|
||||
await logAndCaptureException((<RequestError>error));
|
||||
}
|
||||
|
||||
res
|
||||
.status((<RequestError>error).statusCode)
|
||||
.json((<RequestError>error).format(req));
|
||||
delete (<any>error).stacktrace // remove stack trace from being sent to client
|
||||
res.status((<RequestError>error).statusCode).json(error);
|
||||
|
||||
next();
|
||||
};
|
||||
|
@ -40,9 +40,9 @@ syncSecretsToThirdPartyServices.process(async (job: Job) => {
|
||||
const prefix = (integration.metadata?.secretPrefix || "");
|
||||
const suffix = (integration.metadata?.secretSuffix || "");
|
||||
const newKey = prefix + key + suffix;
|
||||
|
||||
|
||||
suffixedSecrets[newKey] = secrets[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const integrationAuth = await IntegrationAuth.findById(integration.integrationAuth);
|
||||
@ -67,7 +67,7 @@ syncSecretsToThirdPartyServices.process(async (job: Job) => {
|
||||
})
|
||||
|
||||
syncSecretsToThirdPartyServices.on("error", (error) => {
|
||||
console.log("QUEUE ERROR:", error) // eslint-disable-line
|
||||
// console.log("QUEUE ERROR:", error) // eslint-disable-line
|
||||
})
|
||||
|
||||
export const syncSecretsToActiveIntegrationsQueue = (jobDetails: TSyncSecretsToThirdPartyServices) => {
|
||||
|
@ -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;
|
||||
|
@ -5,7 +5,7 @@ import path from "path";
|
||||
|
||||
type TAppendFolderDTO = {
|
||||
folderName: string;
|
||||
parentFolderId?: string;
|
||||
directory: string;
|
||||
};
|
||||
|
||||
type TRenameFolderDTO = {
|
||||
@ -50,9 +50,8 @@ export const folderBfsTraversal = async (
|
||||
// bfs and then append to the folder
|
||||
const appendChild = (folders: TFolderSchema, folderName: string) => {
|
||||
const folder = folders.children.find(({ name }) => name === folderName);
|
||||
if (folder) {
|
||||
throw new Error("Folder already exists");
|
||||
}
|
||||
if (folder) return { folder, hasCreated: false };
|
||||
|
||||
const id = generateFolderId();
|
||||
folders.version += 1;
|
||||
folders.children.push({
|
||||
@ -61,24 +60,32 @@ const appendChild = (folders: TFolderSchema, folderName: string) => {
|
||||
children: [],
|
||||
version: 1
|
||||
});
|
||||
return { id, name: folderName };
|
||||
// last element that is the new one
|
||||
return { folder: folders.children[folders.children.length - 1], hasCreated: true };
|
||||
};
|
||||
|
||||
// root of append child wrapper
|
||||
export const appendFolder = (
|
||||
folders: TFolderSchema,
|
||||
{ folderName, parentFolderId }: TAppendFolderDTO
|
||||
) => {
|
||||
const isRoot = !parentFolderId;
|
||||
{ folderName, directory }: TAppendFolderDTO
|
||||
): { parent: TFolderSchema; child: TFolderSchema; hasCreated?: boolean } => {
|
||||
if (directory === "/") {
|
||||
const newFolder = appendChild(folders, folderName);
|
||||
return { parent: folders, child: newFolder.folder, hasCreated: newFolder.hasCreated };
|
||||
}
|
||||
|
||||
if (isRoot) {
|
||||
return appendChild(folders, folderName);
|
||||
const segments = directory.split("/").filter(Boolean);
|
||||
const segment = segments.shift();
|
||||
if (segment) {
|
||||
const nestedFolders = appendChild(folders, segment);
|
||||
return appendFolder(nestedFolders.folder, {
|
||||
folderName,
|
||||
directory: path.join("/", ...segments)
|
||||
});
|
||||
}
|
||||
const folder = searchByFolderId(folders, parentFolderId);
|
||||
if (!folder) {
|
||||
throw new Error("Parent Folder not found");
|
||||
}
|
||||
return appendChild(folder, folderName);
|
||||
|
||||
const newFolder = appendChild(folders, folderName);
|
||||
return { parent: folders, child: newFolder.folder, hasCreated: newFolder.hasCreated };
|
||||
};
|
||||
|
||||
export const renameFolder = (
|
||||
|
@ -291,8 +291,7 @@ const initializePassport = async () => {
|
||||
});
|
||||
|
||||
interface ISAMLConfig {
|
||||
path: string;
|
||||
callbackURL: string;
|
||||
callbackUrl: string;
|
||||
entryPoint: string;
|
||||
issuer: string;
|
||||
cert: string;
|
||||
@ -301,8 +300,7 @@ const initializePassport = async () => {
|
||||
}
|
||||
|
||||
const samlConfig: ISAMLConfig = ({
|
||||
path: `${await getSiteURL()}/api/v1/sso/saml2/${ssoIdentifier}`,
|
||||
callbackURL: `${await getSiteURL()}/api/v1/sso/saml2${ssoIdentifier}`,
|
||||
callbackUrl: `${await getSiteURL()}/api/v1/sso/saml2/${ssoIdentifier}`,
|
||||
entryPoint: ssoConfig.entryPoint,
|
||||
issuer: ssoConfig.issuer,
|
||||
cert: ssoConfig.cert,
|
||||
@ -313,6 +311,10 @@ const initializePassport = async () => {
|
||||
samlConfig.wantAuthnResponseSigned = false;
|
||||
}
|
||||
|
||||
if (ssoConfig.authProvider.toString() === AuthMethod.AZURE_SAML.toString()) {
|
||||
samlConfig.audience = `spn:${ssoConfig.issuer}`;
|
||||
}
|
||||
|
||||
req.ssoConfig = ssoConfig;
|
||||
|
||||
done(null, samlConfig);
|
||||
|
@ -12,6 +12,27 @@ export enum LogLevel {
|
||||
EMERGENCY = 600,
|
||||
}
|
||||
|
||||
export const mapToWinstonLogLevel = (customLogLevel: LogLevel): string => {
|
||||
switch (customLogLevel) {
|
||||
case LogLevel.DEBUG:
|
||||
return "debug";
|
||||
case LogLevel.INFO:
|
||||
return "info";
|
||||
case LogLevel.NOTICE:
|
||||
return "notice";
|
||||
case LogLevel.WARNING:
|
||||
return "warn";
|
||||
case LogLevel.ERROR:
|
||||
return "error";
|
||||
case LogLevel.CRITICAL:
|
||||
return "crit";
|
||||
case LogLevel.ALERT:
|
||||
return "alert";
|
||||
case LogLevel.EMERGENCY:
|
||||
return "emerg";
|
||||
}
|
||||
}
|
||||
|
||||
export type RequestErrorContext = {
|
||||
logLevel?: LogLevel,
|
||||
statusCode: number,
|
||||
@ -87,7 +108,8 @@ export default class RequestError extends Error{
|
||||
}, this.context)
|
||||
|
||||
//* Omit sensitive information from context that can leak internal workings of this program if user is not developer
|
||||
if(!(await getVerboseErrorOutput())){
|
||||
const verboseErrorOutput = await getVerboseErrorOutput();
|
||||
if (verboseErrorOutput !== undefined) {
|
||||
_context = this._omit(_context, [
|
||||
"stacktrace",
|
||||
"exception",
|
||||
@ -110,4 +132,4 @@ export default class RequestError extends Error{
|
||||
return formatObject
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -55,9 +55,6 @@ export const setup = async () => {
|
||||
// initializing global feature set
|
||||
await EELicenseService.initGlobalFeatureSet();
|
||||
|
||||
// initializing the database connection
|
||||
await DatabaseService.initDatabase(await getMongoURL());
|
||||
|
||||
await initializePassport();
|
||||
|
||||
// re-encrypt any data previously encrypted under server hex 128-bit ENCRYPTION_KEY
|
||||
|
@ -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() })
|
||||
});
|
@ -353,6 +353,7 @@ export const UpdateSecretByNameV3 = z.object({
|
||||
body: z.object({
|
||||
workspaceId: z.string().trim(),
|
||||
environment: z.string().trim(),
|
||||
secretId: z.string().trim().optional(),
|
||||
type: z.enum([SECRET_SHARED, SECRET_PERSONAL]),
|
||||
secretPath: z.string().trim().default("/"),
|
||||
secretValueCiphertext: z.string().trim(),
|
||||
@ -379,7 +380,8 @@ export const DeleteSecretByNameV3 = z.object({
|
||||
workspaceId: z.string().trim(),
|
||||
environment: z.string().trim(),
|
||||
type: z.enum([SECRET_SHARED, SECRET_PERSONAL]),
|
||||
secretPath: z.string().trim().default("/")
|
||||
secretPath: z.string().trim().default("/"),
|
||||
secretId: z.string().trim().optional()
|
||||
}),
|
||||
params: z.object({
|
||||
secretName: z.string()
|
||||
|
@ -88,7 +88,7 @@ Export environment variables from the platform into a file format.
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="--format">
|
||||
Format of the output file. Accepted values: `dotenv`, `dotenv-export`, `csv` and `json`
|
||||
Format of the output file. Accepted values: `dotenv`, `dotenv-export`, `csv`, `json` and `yaml`
|
||||
|
||||
Default value: `dotenv`
|
||||
</Accordion>
|
||||
|
@ -89,3 +89,13 @@ Back in Azure, navigate to the **Users and groups** tab and select **+ Add user/
|
||||
Enabling SAML SSO enforces all members in your organization to only be able to log into Infisical via Azure.
|
||||
|
||||

|
||||
|
||||
<Note>
|
||||
If you're configuring SAML SSO on a self-hosted instance of Infisical, make sure to
|
||||
set the `JWT_PROVIDER_AUTH_SECRET` and `SITE_URL` environment variable for it to work:
|
||||
|
||||
- `JWT_PROVIDER_AUTH_SECRET`: This is secret key used for signing and verifying JWT. This could be a randomly-generated 256-bit hex string.
|
||||
- `SITE_URL`: The URL of your self-hosted instance of Infisical - should be an absolute URL including the protocol (e.g. https://app.infisical.com)
|
||||
</Note>
|
||||
|
||||
|
||||
|
@ -29,9 +29,24 @@ Obtain the **Client ID** and generate a new **Client Secret** for your GitHub OA
|
||||
|
||||

|
||||
|
||||
Back in your Infisical instance, add two new environment variables for the credentials of your GitHub OAuth application:
|
||||
Back in your Infisical instance, make sure to set the following environment variables:
|
||||
|
||||
- `CLIENT_ID_GITHUB_LOGIN`: The **Client ID** of your GitHub OAuth application.
|
||||
- `CLIENT_SECRET_GITHUB_LOGIN`: The **Client Secret** of your GitHub OAuth application.
|
||||
- `JWT_PROVIDER_AUTH_SECRET`: A secret key used for signing and verifying JWT. This could be a randomly-generated 256-bit hex string.
|
||||
- `SITE_URL`: The URL of your self-hosted instance of Infisical - should be an absolute URL including the protocol (e.g. https://app.infisical.com)
|
||||
|
||||
Once added, restart your Infisical instance and log in with GitHub.
|
||||
Once added, restart your Infisical instance and log in with GitHub.
|
||||
|
||||
## FAQ
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Why is GitHub SSO not working?">
|
||||
It is likely that you have misconfigured your self-hosted instance of Infisical. You should:
|
||||
|
||||
- Check that you have set the `CLIENT_ID_GITHUB_LOGIN`, `CLIENT_SECRET_GITHUB_LOGIN`,
|
||||
`JWT_PROVIDER_AUTH_SECRET`, and `SITE_URL` environment variables.
|
||||
- Check that the **Authorization callback URL** specified in GitHub matches the `SITE_URL` environment variable.
|
||||
For example, if the former is `https://app.infisical.com/api/v1/sso/github` then the latter should be `https://app.infisical.com`.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
@ -28,10 +28,25 @@ Obtain the **Application ID** and **Secret** for your GitLab application.
|
||||
|
||||

|
||||
|
||||
Back in your Infisical instance, add 2-3 new environment variables for the credentials of your GitLab application:
|
||||
Back in your Infisical instance, make sure to set the following environment variables:
|
||||
|
||||
- `CLIENT_ID_GITLAB_LOGIN`: The **Client ID** of your GitLab application.
|
||||
- `CLIENT_SECRET_GITLAB_LOGIN`: The **Secret** of your GitLab application.
|
||||
- (optional) `URL_GITLAB_LOGIN`: The URL of your self-hosted instance of GitLab where the OAuth application is registered. If no URL is passed in, this will default to `https://gitlab.com`.
|
||||
- `JWT_PROVIDER_AUTH_SECRET`: A secret key used for signing and verifying JWT. This could be a randomly-generated 256-bit hex string.
|
||||
- `SITE_URL`: The URL of your self-hosted instance of Infisical - should be an absolute URL including the protocol (e.g. https://app.infisical.com)
|
||||
|
||||
Once added, restart your Infisical instance and log in with GitLab.
|
||||
Once added, restart your Infisical instance and log in with GitLab.
|
||||
|
||||
## FAQ
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Why is GitLab SSO not working?">
|
||||
It is likely that you have misconfigured your self-hosted instance of Infisical. You should:
|
||||
|
||||
- Check that you have set the `CLIENT_ID_GITLAB_LOGIN`, `CLIENT_SECRET_GITLAB_LOGIN`,
|
||||
`JWT_PROVIDER_AUTH_SECRET`, and `SITE_URL` environment variables.
|
||||
- Check that the **Redirect URI** specified in GitLab matches the `SITE_URL` environment variable.
|
||||
For example, if the former is `https://app.infisical.com/api/v1/sso/gitlab` then the latter should be `https://app.infisical.com`.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
@ -22,9 +22,24 @@ Obtain the **Client ID** and **Client Secret** for your GCP OAuth2 application.
|
||||
|
||||

|
||||
|
||||
Back in your Infisical instance, add two new environment variables for the credentials of your GCP OAuth2 application:
|
||||
Back in your Infisical instance, make sure to set the following environment variables:
|
||||
|
||||
- `CLIENT_ID_GOOGLE_LOGIN`: The **Client ID** of your GCP OAuth2 application.
|
||||
- `CLIENT_SECRET_GOOGLE_LOGIN`: The **Client Secret** of your GCP OAuth2 application.
|
||||
- `JWT_PROVIDER_AUTH_SECRET`: A secret key used for signing and verifying JWT. This could be a randomly-generated 256-bit hex string.
|
||||
- `SITE_URL`: The URL of your self-hosted instance of Infisical - should be an absolute URL including the protocol (e.g. https://app.infisical.com)
|
||||
|
||||
Once added, restart your Infisical instance and log in with Google
|
||||
Once added, restart your Infisical instance and log in with Google
|
||||
|
||||
## FAQ
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Why is Google SSO not working?">
|
||||
It is likely that you have misconfigured your self-hosted instance of Infisical. You should:
|
||||
|
||||
- Check that you have set the `CLIENT_ID_GOOGLE_LOGIN`, `CLIENT_SECRET_GOOGLE_LOGIN`,
|
||||
`JWT_PROVIDER_AUTH_SECRET`, and `SITE_URL` environment variables.
|
||||
- Check that the **Authorized redirect URI** specified in GCP matches the `SITE_URL` environment variable.
|
||||
For example, if the former is `https://app.infisical.com/api/v1/sso/google` then the latter should be `https://app.infisical.com`.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
@ -72,3 +72,11 @@ Back in JumpCloud, navigate to the **User Groups** tab and assign users to the n
|
||||
Enabling SAML SSO enforces all members in your organization to only be able to log into Infisical via JumpCloud.
|
||||
|
||||

|
||||
|
||||
<Note>
|
||||
If you're configuring SAML SSO on a self-hosted instance of Infisical, make sure to
|
||||
set the `JWT_PROVIDER_AUTH_SECRET` and `SITE_URL` environment variable for it to work:
|
||||
|
||||
- `JWT_PROVIDER_AUTH_SECRET`: This is secret key used for signing and verifying JWT. This could be a randomly-generated 256-bit hex string.
|
||||
- `SITE_URL`: The URL of your self-hosted instance of Infisical - should be an absolute URL including the protocol (e.g. https://app.infisical.com)
|
||||
</Note>
|
||||
|
@ -77,3 +77,11 @@ At this point, you have configured everything you need within the context of the
|
||||
Enabling SAML SSO enforces all members in your organization to only be able to log into Infisical via Okta.
|
||||
|
||||

|
||||
|
||||
<Note>
|
||||
If you're configuring SAML SSO on a self-hosted instance of Infisical, make sure to
|
||||
set the `JWT_PROVIDER_AUTH_SECRET` and `SITE_URL` environment variable for it to work:
|
||||
|
||||
- `JWT_PROVIDER_AUTH_SECRET`: This is secret key used for signing and verifying JWT. This could be a randomly-generated 256-bit hex string.
|
||||
- `SITE_URL`: The URL of your self-hosted instance of Infisical - should be an absolute URL including the protocol (e.g. https://app.infisical.com)
|
||||
</Note>
|
@ -1,32 +0,0 @@
|
||||
module.exports = {
|
||||
apps: [
|
||||
{
|
||||
name: 'frontend',
|
||||
script: "./scripts/start.sh",
|
||||
instances: 1,
|
||||
cwd: "./app",
|
||||
interpreter: 'sh',
|
||||
exec_mode: "fork",
|
||||
autorestart: true,
|
||||
watch: false,
|
||||
max_memory_restart: '500M',
|
||||
},
|
||||
{
|
||||
name: 'backend',
|
||||
script: 'npm',
|
||||
args: 'run start',
|
||||
cwd: "./backend",
|
||||
instances: 1,
|
||||
exec_mode: "fork",
|
||||
autorestart: true,
|
||||
watch: false,
|
||||
max_memory_restart: '500M',
|
||||
},
|
||||
{
|
||||
name: "nginx",
|
||||
script: "nginx",
|
||||
args: "-g 'daemon off;'",
|
||||
exec_interpreter: "none",
|
||||
},
|
||||
],
|
||||
};
|
@ -1,8 +1,4 @@
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* @type {import('next').NextConfig}
|
||||
**/
|
||||
const path = require("path");
|
||||
|
||||
const ContentSecurityPolicy = `
|
||||
@ -53,7 +49,9 @@ const securityHeaders = [
|
||||
value: ContentSecurityPolicy.replace(/\s{2,}/g, " ").trim()
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* @type {import('next').NextConfig}
|
||||
**/
|
||||
module.exports = {
|
||||
output: "standalone",
|
||||
i18n: {
|
||||
|
2
frontend/package-lock.json
generated
2
frontend/package-lock.json
generated
@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "npm-proj-1695919945735-0.225773463026700768rr1Oh",
|
||||
"name": "frontend",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
|
13
frontend/scripts/initialize-standalone-build.sh
Executable file
13
frontend/scripts/initialize-standalone-build.sh
Executable file
@ -0,0 +1,13 @@
|
||||
#!/bin/sh
|
||||
|
||||
scripts/replace-standalone-build-variable.sh "$BAKED_NEXT_PUBLIC_POSTHOG_API_KEY" "$NEXT_PUBLIC_POSTHOG_API_KEY"
|
||||
|
||||
scripts/replace-standalone-build-variable.sh "$BAKED_NEXT_PUBLIC_INTERCOM_ID" "$NEXT_PUBLIC_INTERCOM_ID"
|
||||
|
||||
if [ "$TELEMETRY_ENABLED" != "false" ]; then
|
||||
echo "Telemetry is enabled"
|
||||
scripts/set-standalone-build-telemetry.sh true
|
||||
else
|
||||
echo "Client opted out of telemetry"
|
||||
scripts/set-standalone-build-telemetry.sh false
|
||||
fi
|
16
frontend/scripts/replace-standalone-build-variable.sh
Executable file
16
frontend/scripts/replace-standalone-build-variable.sh
Executable file
@ -0,0 +1,16 @@
|
||||
#!/bin/sh
|
||||
|
||||
ORIGINAL=$1
|
||||
REPLACEMENT=$2
|
||||
|
||||
if [ "${ORIGINAL}" = "${REPLACEMENT}" ]; then
|
||||
echo "Environment variable replacement is the same, skipping.."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Replacing pre-baked value.."
|
||||
|
||||
find public .next -type f -name "*.js" |
|
||||
while read file; do
|
||||
sed -i "s|$ORIGINAL|$REPLACEMENT|g" "$file"
|
||||
done
|
0
frontend/scripts/replace-variable.sh
Normal file → Executable file
0
frontend/scripts/replace-variable.sh
Normal file → Executable file
8
frontend/scripts/set-standalone-build-telemetry.sh
Normal file
8
frontend/scripts/set-standalone-build-telemetry.sh
Normal file
@ -0,0 +1,8 @@
|
||||
#!/bin/sh
|
||||
|
||||
VALUE=$1
|
||||
|
||||
find public .next -type f -name "*.js" |
|
||||
while read file; do
|
||||
sed -i "s|TELEMETRY_CAPTURING_ENABLED|$VALUE|g" "$file"
|
||||
done
|
0
frontend/scripts/set-telemetry.sh
Normal file → Executable file
0
frontend/scripts/set-telemetry.sh
Normal file → Executable file
@ -10,7 +10,7 @@ export const initPostHog = () => {
|
||||
try {
|
||||
if (typeof window !== "undefined") {
|
||||
// @ts-ignore
|
||||
if (ENV === "production" && TELEMETRY_CAPTURING_ENABLED) {
|
||||
if (ENV === "production" && TELEMETRY_CAPTURING_ENABLED === "true") {
|
||||
posthog.init(POSTHOG_API_KEY, {
|
||||
api_host: POSTHOG_HOST
|
||||
});
|
||||
|
@ -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);
|
||||
|
@ -13,7 +13,7 @@ class Capturer {
|
||||
}
|
||||
|
||||
capture(item: string) {
|
||||
if (ENV === 'production' && TELEMETRY_CAPTURING_ENABLED) {
|
||||
if (ENV === 'production' && TELEMETRY_CAPTURING_ENABLED === "true") {
|
||||
try {
|
||||
this.api.capture(item);
|
||||
} catch (error) {
|
||||
@ -23,7 +23,7 @@ class Capturer {
|
||||
}
|
||||
|
||||
identify(id: string, email?: string) {
|
||||
if (ENV === 'production' && TELEMETRY_CAPTURING_ENABLED) {
|
||||
if (ENV === 'production' && TELEMETRY_CAPTURING_ENABLED === "true") {
|
||||
try {
|
||||
this.api.identify(id, {
|
||||
email: email
|
||||
|
@ -37,7 +37,7 @@ export const DrawerContent = forwardRef<HTMLDivElement, DrawerContentProps>(
|
||||
) => (
|
||||
<DialogPrimitive.Portal>
|
||||
<DialogPrimitive.Overlay
|
||||
className="fixed inset-0 z-[70] h-full w-full"
|
||||
className="fixed inset-0 z-20 h-full w-full"
|
||||
style={{ backgroundColor: "rgba(0, 0, 0, 0.7)" }}
|
||||
/>
|
||||
<DialogPrimitive.Content
|
||||
|
@ -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 });
|
||||
|
@ -131,6 +131,7 @@ export const useUpdateSecretV3 = ({
|
||||
mutationFn: async ({
|
||||
secretPath = "/",
|
||||
type,
|
||||
secretId,
|
||||
environment,
|
||||
workspaceId,
|
||||
secretName,
|
||||
@ -157,6 +158,7 @@ export const useUpdateSecretV3 = ({
|
||||
environment,
|
||||
type,
|
||||
secretPath,
|
||||
secretId,
|
||||
...encryptSecret(randomBytes, newSecretName ?? secretName, secretValue, secretComment),
|
||||
tags,
|
||||
skipMultilineEncoding,
|
||||
@ -189,12 +191,20 @@ export const useDeleteSecretV3 = ({
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<{}, {}, TDeleteSecretsV3DTO>({
|
||||
mutationFn: async ({ secretPath = "/", type, environment, workspaceId, secretName }) => {
|
||||
mutationFn: async ({
|
||||
secretPath = "/",
|
||||
type,
|
||||
environment,
|
||||
workspaceId,
|
||||
secretName,
|
||||
secretId
|
||||
}) => {
|
||||
const reqBody = {
|
||||
workspaceId,
|
||||
environment,
|
||||
type,
|
||||
secretPath
|
||||
secretPath,
|
||||
secretId
|
||||
};
|
||||
|
||||
const { data } = await apiRequest.delete(`/api/v3/secrets/${secretName}`, {
|
||||
|
@ -109,6 +109,7 @@ export type TUpdateSecretsV3DTO = {
|
||||
skipMultilineEncoding?: boolean;
|
||||
newSecretName?: string;
|
||||
secretName: string;
|
||||
secretId?: string;
|
||||
secretValue: string;
|
||||
secretComment?: string;
|
||||
tags?: string[];
|
||||
@ -120,6 +121,7 @@ export type TDeleteSecretsV3DTO = {
|
||||
type: "shared" | "personal";
|
||||
secretPath: string;
|
||||
secretName: string;
|
||||
secretId?: string;
|
||||
};
|
||||
|
||||
export type TCreateSecretBatchDTO = {
|
||||
|
@ -5,6 +5,7 @@ export {
|
||||
useCreateAPIKey,
|
||||
useDeleteAPIKey,
|
||||
useDeleteOrgMembership,
|
||||
useDeleteUser,
|
||||
useGetMyAPIKeys,
|
||||
useGetMyIp,
|
||||
useGetMyOrganizationProjects,
|
||||
|
@ -42,6 +42,31 @@ 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: () => {
|
||||
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 fetchUserAction = async (action: string) => {
|
||||
const { data } = await apiRequest.get<{ userAction: string }>("/api/v1/user-action", {
|
||||
params: {
|
||||
@ -208,21 +233,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({
|
||||
|
@ -34,7 +34,6 @@ import * as yup from "yup";
|
||||
|
||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
import onboardingCheck from "@app/components/utilities/checks/OnboardingCheck";
|
||||
import { tempLocalStorage } from "@app/components/utilities/checks/tempLocalStorage";
|
||||
import { encryptAssymmetric } from "@app/components/utilities/cryptography/crypto";
|
||||
import {
|
||||
@ -115,6 +114,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 +157,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);
|
||||
@ -223,7 +213,6 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
||||
// }
|
||||
};
|
||||
putUserInOrg();
|
||||
onboardingCheck({});
|
||||
}, [router.query.id]);
|
||||
|
||||
const onCreateProject = async ({ name, addMembers }: TAddProjectFormData) => {
|
||||
@ -703,7 +692,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
||||
{infisicalPlatformVersion && (
|
||||
<div className="mb-2 w-full pl-5 duration-200 hover:text-mineshaft-200">
|
||||
<FontAwesomeIcon icon={faInfo} className="mr-4 px-[0.1rem]" />
|
||||
Platform Version: {infisicalPlatformVersion}
|
||||
Version: {infisicalPlatformVersion}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
@ -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");
|
||||
}
|
||||
|
18
frontend/src/views/Login/Login.utils.tsx
Normal file
18
frontend/src/views/Login/Login.utils.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
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
|
||||
localStorage.removeItem("orgData.id");
|
||||
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
|
||||
|
114
frontend/src/views/Org/NonePage/NonePage.tsx
Normal file
114
frontend/src/views/Org/NonePage/NonePage.tsx
Normal file
@ -0,0 +1,114 @@
|
||||
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
|
||||
});
|
||||
|
||||
localStorage.setItem("orgData.id", organization._id);
|
||||
|
||||
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";
|
@ -124,13 +124,15 @@ export const SecretListView = ({
|
||||
comment,
|
||||
tags,
|
||||
skipMultilineEncoding,
|
||||
newKey
|
||||
newKey,
|
||||
secretId
|
||||
}: Partial<{
|
||||
value: string;
|
||||
comment: string;
|
||||
tags: string[];
|
||||
skipMultilineEncoding: boolean;
|
||||
newKey: string;
|
||||
secretId: string;
|
||||
}> = {}
|
||||
) => {
|
||||
if (operation === "delete") {
|
||||
@ -139,7 +141,8 @@ export const SecretListView = ({
|
||||
workspaceId,
|
||||
secretPath,
|
||||
secretName: key,
|
||||
type
|
||||
type,
|
||||
secretId
|
||||
});
|
||||
return;
|
||||
}
|
||||
@ -150,6 +153,7 @@ export const SecretListView = ({
|
||||
workspaceId,
|
||||
secretPath,
|
||||
secretName: key,
|
||||
secretId,
|
||||
secretValue: value || "",
|
||||
type,
|
||||
latestFileKey: decryptFileKey,
|
||||
@ -198,11 +202,14 @@ export const SecretListView = ({
|
||||
try {
|
||||
// personal secret change
|
||||
if (overrideAction === "deleted") {
|
||||
await handleSecretOperation("delete", "personal", oldKey);
|
||||
await handleSecretOperation("delete", "personal", oldKey, {
|
||||
secretId: orgSecret.idOverride
|
||||
});
|
||||
} else if (overrideAction && idOverride) {
|
||||
await handleSecretOperation("update", "personal", oldKey, {
|
||||
value: valueOverride,
|
||||
newKey: hasKeyChanged ? key : undefined,
|
||||
secretId: orgSecret.idOverride,
|
||||
skipMultilineEncoding: modSecret.skipMultilineEncoding
|
||||
});
|
||||
} else if (overrideAction) {
|
||||
@ -215,6 +222,7 @@ export const SecretListView = ({
|
||||
value,
|
||||
tags: tagIds,
|
||||
comment,
|
||||
secretId: orgSecret._id,
|
||||
newKey: hasKeyChanged ? key : undefined,
|
||||
skipMultilineEncoding: modSecret.skipMultilineEncoding
|
||||
});
|
||||
@ -249,9 +257,9 @@ export const SecretListView = ({
|
||||
);
|
||||
|
||||
const handleSecretDelete = useCallback(async () => {
|
||||
const { key } = popUp.deleteSecret?.data as DecryptedSecret;
|
||||
const { key, _id: secretId } = popUp.deleteSecret?.data as DecryptedSecret;
|
||||
try {
|
||||
await handleSecretOperation("delete", "shared", key);
|
||||
await handleSecretOperation("delete", "shared", key, { secretId });
|
||||
queryClient.invalidateQueries(
|
||||
secretKeys.getProjectSecret({ workspaceId, environment, secretPath })
|
||||
);
|
||||
@ -305,7 +313,6 @@ export const SecretListView = ({
|
||||
>
|
||||
{namespace}
|
||||
</div>
|
||||
|
||||
{filteredSecrets.map((secret) => (
|
||||
<SecretItem
|
||||
environment={environment}
|
||||
|
@ -31,6 +31,7 @@ import {
|
||||
} from "@app/components/v2";
|
||||
import { useOrganization, useWorkspace } from "@app/context";
|
||||
import {
|
||||
useCreateFolder,
|
||||
useCreateSecretV3,
|
||||
useDeleteSecretV3,
|
||||
useGetFoldersByEnv,
|
||||
@ -104,9 +105,24 @@ export const SecretOverviewPage = () => {
|
||||
const { mutateAsync: createSecretV3 } = useCreateSecretV3();
|
||||
const { mutateAsync: updateSecretV3 } = useUpdateSecretV3();
|
||||
const { mutateAsync: deleteSecretV3 } = useDeleteSecretV3();
|
||||
const { mutateAsync: createFolder } = useCreateFolder();
|
||||
|
||||
const handleSecretCreate = async (env: string, key: string, value: string) => {
|
||||
try {
|
||||
// create folder if not existing
|
||||
if (secretPath !== "/") {
|
||||
const path = secretPath.split("/");
|
||||
const directory = path.slice(0, -1).join("/");
|
||||
const folderName = path.at(-1);
|
||||
if (folderName && directory) {
|
||||
await createFolder({
|
||||
workspaceId,
|
||||
environment: env,
|
||||
directory,
|
||||
folderName
|
||||
});
|
||||
}
|
||||
}
|
||||
await createSecretV3({
|
||||
environment: env,
|
||||
workspaceId,
|
||||
@ -130,12 +146,13 @@ export const SecretOverviewPage = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleSecretUpdate = async (env: string, key: string, value: string) => {
|
||||
const handleSecretUpdate = async (env: string, key: string, value: string, secretId?: string) => {
|
||||
try {
|
||||
await updateSecretV3({
|
||||
environment: env,
|
||||
workspaceId,
|
||||
secretPath,
|
||||
secretId,
|
||||
secretName: key,
|
||||
secretValue: value,
|
||||
type: "shared",
|
||||
@ -154,13 +171,14 @@ export const SecretOverviewPage = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleSecretDelete = async (env: string, key: string) => {
|
||||
const handleSecretDelete = async (env: string, key: string, secretId?: string) => {
|
||||
try {
|
||||
await deleteSecretV3({
|
||||
environment: env,
|
||||
workspaceId,
|
||||
secretPath,
|
||||
secretName: key,
|
||||
secretId,
|
||||
type: "shared"
|
||||
});
|
||||
createNotification({
|
||||
@ -188,7 +206,20 @@ export const SecretOverviewPage = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const handleExploreEnvClick = (slug: string) => {
|
||||
const handleExploreEnvClick = async (slug: string) => {
|
||||
if (secretPath !== "/") {
|
||||
const path = secretPath.split("/");
|
||||
const directory = path.slice(0, -1).join("/");
|
||||
const folderName = path.at(-1);
|
||||
if (folderName && directory) {
|
||||
await createFolder({
|
||||
workspaceId,
|
||||
environment: slug,
|
||||
directory,
|
||||
folderName
|
||||
});
|
||||
}
|
||||
}
|
||||
const query: Record<string, string> = { ...router.query, env: slug };
|
||||
const envIndex = userAvailableEnvs.findIndex((el) => slug === el.slug);
|
||||
if (envIndex !== -1) {
|
||||
@ -212,7 +243,6 @@ export const SecretOverviewPage = () => {
|
||||
);
|
||||
|
||||
const canViewOverviewPage = Boolean(userAvailableEnvs.length);
|
||||
|
||||
const filteredSecretNames = secKeys
|
||||
?.filter((name) => name.toUpperCase().includes(searchFilter.toUpperCase()))
|
||||
.sort((a, b) => (sortDir === "asc" ? a.localeCompare(b) : b.localeCompare(a)));
|
||||
@ -335,7 +365,14 @@ export const SecretOverviewPage = () => {
|
||||
query: { id: workspaceId, env: userAvailableEnvs?.[0]?.slug }
|
||||
}}
|
||||
>
|
||||
<Button className="mt-4" variant="outline_bg" colorSchema="primary" size="md">Go to {userAvailableEnvs?.[0]?.name}</Button>
|
||||
<Button
|
||||
className="mt-4"
|
||||
variant="outline_bg"
|
||||
colorSchema="primary"
|
||||
size="md"
|
||||
>
|
||||
Go to {userAvailableEnvs?.[0]?.name}
|
||||
</Button>
|
||||
</Link>
|
||||
</EmptyState>
|
||||
</Td>
|
||||
|
@ -12,13 +12,14 @@ import { useToggle } from "@app/hooks";
|
||||
type Props = {
|
||||
defaultValue?: string | null;
|
||||
secretName: string;
|
||||
secretId?: string;
|
||||
isCreatable?: boolean;
|
||||
isVisible?: boolean;
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
onSecretCreate: (env: string, key: string, value: string) => Promise<void>;
|
||||
onSecretUpdate: (env: string, key: string, value: string) => Promise<void>;
|
||||
onSecretDelete: (env: string, key: string) => Promise<void>;
|
||||
onSecretUpdate: (env: string, key: string, value: string, secretId?: string) => Promise<void>;
|
||||
onSecretDelete: (env: string, key: string, secretId?: string) => Promise<void>;
|
||||
};
|
||||
|
||||
export const SecretEditRow = ({
|
||||
@ -30,7 +31,8 @@ export const SecretEditRow = ({
|
||||
onSecretDelete,
|
||||
environment,
|
||||
secretPath,
|
||||
isVisible
|
||||
isVisible,
|
||||
secretId
|
||||
}: Props) => {
|
||||
const {
|
||||
handleSubmit,
|
||||
@ -40,7 +42,7 @@ export const SecretEditRow = ({
|
||||
formState: { isDirty, isSubmitting }
|
||||
} = useForm({
|
||||
values: {
|
||||
value: defaultValue
|
||||
value: defaultValue || null
|
||||
}
|
||||
});
|
||||
const [isDeleting, setIsDeleting] = useToggle();
|
||||
@ -68,7 +70,7 @@ export const SecretEditRow = ({
|
||||
if (isCreatable) {
|
||||
await onSecretCreate(environment, secretName, value);
|
||||
} else {
|
||||
await onSecretUpdate(environment, secretName, value);
|
||||
await onSecretUpdate(environment, secretName, value, secretId);
|
||||
}
|
||||
}
|
||||
reset({ value });
|
||||
@ -77,8 +79,8 @@ export const SecretEditRow = ({
|
||||
const handleDeleteSecret = async () => {
|
||||
setIsDeleting.on();
|
||||
try {
|
||||
await onSecretDelete(environment, secretName);
|
||||
reset({ value: undefined });
|
||||
await onSecretDelete(environment, secretName, secretId);
|
||||
reset({ value: null });
|
||||
} finally {
|
||||
setIsDeleting.off();
|
||||
}
|
||||
|
@ -23,8 +23,8 @@ type Props = {
|
||||
expandableColWidth: number;
|
||||
getSecretByKey: (slug: string, key: string) => DecryptedSecret | undefined;
|
||||
onSecretCreate: (env: string, key: string, value: string) => Promise<void>;
|
||||
onSecretUpdate: (env: string, key: string, value: string) => Promise<void>;
|
||||
onSecretDelete: (env: string, key: string) => Promise<void>;
|
||||
onSecretUpdate: (env: string, key: string, value: string, secretId?: string) => Promise<void>;
|
||||
onSecretDelete: (env: string, key: string, secretId?: string) => Promise<void>;
|
||||
};
|
||||
|
||||
export const SecretOverviewTableRow = ({
|
||||
@ -149,6 +149,7 @@ export const SecretOverviewTableRow = ({
|
||||
isVisible={isSecretVisible}
|
||||
secretName={secretKey}
|
||||
defaultValue={secret?.value}
|
||||
secretId={secret?._id}
|
||||
isCreatable={isCreatable}
|
||||
onSecretDelete={onSecretDelete}
|
||||
onSecretCreate={onSecretCreate}
|
||||
|
@ -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 () => {
|
||||
|
@ -71,7 +71,7 @@ export const PreviewSection = () => {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
{subscription &&
|
||||
|
@ -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}>
|
||||
|
@ -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=""
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -54,9 +54,7 @@ export const SecretTagsSection = (): JSX.Element => {
|
||||
colorSchema="secondary"
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
onClick={() => {
|
||||
console.log("x");
|
||||
handlePopUpOpen("CreateSecretTag");
|
||||
console.log("x2");
|
||||
}}
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
|
@ -7,7 +7,7 @@ type: application
|
||||
# This is the chart version. This version number should be incremented each time you make changes
|
||||
# to the chart and its templates, including the app version.
|
||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||
version: 0.3.4
|
||||
version: 0.3.5
|
||||
|
||||
# This is the version number of the application being deployed. This version number should be
|
||||
# incremented each time you make changes to the application. Versions are not expected to
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user