mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-10 14:28:35 +00:00
Merge pull request #838 from Infisical/deprecation
Cleaning & deprecating parts of code
This commit is contained in:
@@ -260,22 +260,6 @@ export const createOrganizationPortalSession = async (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Return organization subscriptions
|
|
||||||
* @param req
|
|
||||||
* @param res
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export const getOrganizationSubscriptions = async (
|
|
||||||
req: Request,
|
|
||||||
res: Response
|
|
||||||
) => {
|
|
||||||
return res.status(200).send({
|
|
||||||
subscriptions: []
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a org id, return the projects each member of the org belongs to
|
* Given a org id, return the projects each member of the org belongs to
|
||||||
* @param req
|
* @param req
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { Request, Response } from "express";
|
import { Request, Response } from "express";
|
||||||
import { ISecret, Secret, ServiceTokenData } from "../../models";
|
import { ISecret, Secret, ServiceTokenData } from "../../models";
|
||||||
import { IAction, SecretVersion, EventType, AuditLog } from "../../ee/models";
|
import { AuditLog, EventType, IAction, SecretVersion } from "../../ee/models";
|
||||||
import {
|
import {
|
||||||
ACTION_ADD_SECRETS,
|
ACTION_ADD_SECRETS,
|
||||||
ACTION_DELETE_SECRETS,
|
ACTION_DELETE_SECRETS,
|
||||||
@@ -14,7 +14,7 @@ import {
|
|||||||
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
|
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
|
||||||
import { EventService } from "../../services";
|
import { EventService } from "../../services";
|
||||||
import { eventPushSecrets } from "../../events";
|
import { eventPushSecrets } from "../../events";
|
||||||
import { EELogService, EESecretService, EEAuditLogService } from "../../ee/services";
|
import { EEAuditLogService, EELogService, EESecretService } from "../../ee/services";
|
||||||
import { SecretService, TelemetryService } from "../../services";
|
import { SecretService, TelemetryService } from "../../services";
|
||||||
import { getUserAgentType } from "../../utils/posthog";
|
import { getUserAgentType } from "../../utils/posthog";
|
||||||
import { PERMISSION_WRITE_SECRETS } from "../../variables";
|
import { PERMISSION_WRITE_SECRETS } from "../../variables";
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ type ValidEventScope =
|
|||||||
| Required<EventScope>
|
| Required<EventScope>
|
||||||
|
|
||||||
export default class EEAuditLogService {
|
export default class EEAuditLogService {
|
||||||
static async createAuditLog(authData: AuthData, event: Event, eventScope: ValidEventScope, shouldSave: boolean = true) {
|
static async createAuditLog(authData: AuthData, event: Event, eventScope: ValidEventScope, shouldSave = true) {
|
||||||
|
|
||||||
const MS_IN_DAY = 24 * 60 * 60 * 1000;
|
const MS_IN_DAY = 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { AuthMode } from "../../variables";
|
|||||||
|
|
||||||
router.post("/token", validateRequest, authController.getNewToken);
|
router.post("/token", validateRequest, authController.getNewToken);
|
||||||
|
|
||||||
router.post( // deprecated (moved to api/v2/auth/login1)
|
router.post( // TODO endpoint: deprecate (moved to api/v3/auth/login1)
|
||||||
"/login1",
|
"/login1",
|
||||||
authLimiter,
|
authLimiter,
|
||||||
body("email").exists().trim().notEmpty(),
|
body("email").exists().trim().notEmpty(),
|
||||||
@@ -17,7 +17,7 @@ router.post( // deprecated (moved to api/v2/auth/login1)
|
|||||||
authController.login1
|
authController.login1
|
||||||
);
|
);
|
||||||
|
|
||||||
router.post( // deprecated (moved to api/v2/auth/login2)
|
router.post( // TODO endpoint: deprecate (moved to api/v3/auth/login2)
|
||||||
"/login2",
|
"/login2",
|
||||||
authLimiter,
|
authLimiter,
|
||||||
body("email").exists().trim().notEmpty(),
|
body("email").exists().trim().notEmpty(),
|
||||||
@@ -49,7 +49,7 @@ router.get(
|
|||||||
authController.getCommonPasswords
|
authController.getCommonPasswords
|
||||||
);
|
);
|
||||||
|
|
||||||
router.delete(
|
router.delete( // TODO endpoint: deprecate (moved to DELETE v2/users/me/sessions)
|
||||||
"/sessions",
|
"/sessions",
|
||||||
authLimiter,
|
authLimiter,
|
||||||
requireAuth({
|
requireAuth({
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import { requireAuth, validateRequest } from "../../middleware";
|
|||||||
import { membershipOrgController } from "../../controllers/v1";
|
import { membershipOrgController } from "../../controllers/v1";
|
||||||
import { AuthMode } from "../../variables";
|
import { AuthMode } from "../../variables";
|
||||||
|
|
||||||
|
// TODO endpoint: consider moving these endpoints to be under /organization to be more RESTful
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/signup",
|
"/signup",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import { body, param } from "express-validator";
|
|||||||
import { ADMIN, AuthMode, MEMBER } from "../../variables";
|
import { ADMIN, AuthMode, MEMBER } from "../../variables";
|
||||||
import { keyController } from "../../controllers/v1";
|
import { keyController } from "../../controllers/v1";
|
||||||
|
|
||||||
|
// TODO endpoint: consider moving these endpoints to be under /workspaces to be more RESTful
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/:workspaceId",
|
"/:workspaceId",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
@@ -24,7 +26,7 @@ router.post(
|
|||||||
keyController.uploadKey
|
keyController.uploadKey
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get(
|
router.get( // TODO endpoint: deprecate (note: move frontend to v2/workspace/key or something)
|
||||||
"/:workspaceId/latest",
|
"/:workspaceId/latest",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT],
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ import { membershipController as EEMembershipControllers } from "../../ee/contro
|
|||||||
import { AuthMode } from "../../variables";
|
import { AuthMode } from "../../variables";
|
||||||
|
|
||||||
// note: ALL DEPRECIATED (moved to api/v2/workspace/:workspaceId/memberships/:membershipId)
|
// note: ALL DEPRECIATED (moved to api/v2/workspace/:workspaceId/memberships/:membershipId)
|
||||||
|
// TODO endpoint: consider moving these endpoints to be under /workspace to be more RESTful
|
||||||
|
|
||||||
router.get( // used for old CLI (deprecate)
|
router.get( // TODO endpoint: deprecate - used for old CLI (deprecate)
|
||||||
"/:workspaceId/connect",
|
"/:workspaceId/connect",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT],
|
||||||
@@ -18,7 +19,7 @@ router.get( // used for old CLI (deprecate)
|
|||||||
membershipController.validateMembership
|
membershipController.validateMembership
|
||||||
);
|
);
|
||||||
|
|
||||||
router.delete(
|
router.delete( // TODO endpoint: check dashboard
|
||||||
"/:membershipId",
|
"/:membershipId",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT],
|
||||||
@@ -28,7 +29,7 @@ router.delete(
|
|||||||
membershipController.deleteMembership
|
membershipController.deleteMembership
|
||||||
);
|
);
|
||||||
|
|
||||||
router.post(
|
router.post( // TODO endpoint: check dashboard
|
||||||
"/:membershipId/change-role",
|
"/:membershipId/change-role",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT],
|
||||||
@@ -38,7 +39,7 @@ router.post(
|
|||||||
membershipController.changeMembershipRole
|
membershipController.changeMembershipRole
|
||||||
);
|
);
|
||||||
|
|
||||||
router.post(
|
router.post( // TODO endpoint: check dashboard
|
||||||
"/:membershipId/deny-permissions",
|
"/:membershipId/deny-permissions",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT],
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ import { requireAuth, validateRequest } from "../../middleware";
|
|||||||
import { membershipOrgController } from "../../controllers/v1";
|
import { membershipOrgController } from "../../controllers/v1";
|
||||||
import { AuthMode } from "../../variables";
|
import { AuthMode } from "../../variables";
|
||||||
|
|
||||||
router.post(
|
router.post( // TODO endpoint: check dashboard
|
||||||
// TODO
|
|
||||||
"/membershipOrg/:membershipOrgId/change-role",
|
"/membershipOrg/:membershipOrgId/change-role",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT],
|
||||||
@@ -17,7 +16,7 @@ router.post(
|
|||||||
);
|
);
|
||||||
|
|
||||||
router.delete(
|
router.delete(
|
||||||
"/:membershipOrgId",
|
"/:membershipOrgId", // TODO endpoint: check dashboard
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT],
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
} from "../../variables";
|
} from "../../variables";
|
||||||
import { organizationController } from "../../controllers/v1";
|
import { organizationController } from "../../controllers/v1";
|
||||||
|
|
||||||
router.get( // deprecated (moved to api/v2/users/me/organizations)
|
router.get( // TODO endpoint: deprecate (moved to api/v2/users/me/organizations)
|
||||||
"/",
|
"/",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT],
|
||||||
@@ -47,7 +47,7 @@ router.get(
|
|||||||
organizationController.getOrganization
|
organizationController.getOrganization
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get( // deprecated (moved to api/v2/organizations/:organizationId/memberships)
|
router.get( // TODO endpoint: deprecate (moved to api/v2/organizations/:organizationId/memberships)
|
||||||
"/:organizationId/users",
|
"/:organizationId/users",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT],
|
||||||
@@ -61,7 +61,7 @@ router.get( // deprecated (moved to api/v2/organizations/:organizationId/members
|
|||||||
organizationController.getOrganizationMembers
|
organizationController.getOrganizationMembers
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get(
|
router.get( // TODO endpoint: move to /v2/users/me/organizations/:organizationId/workspaces
|
||||||
"/:organizationId/my-workspaces", // deprecated (moved to api/v2/organizations/:organizationId/workspaces)
|
"/:organizationId/my-workspaces", // deprecated (moved to api/v2/organizations/:organizationId/workspaces)
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT],
|
||||||
@@ -135,7 +135,7 @@ router.delete(
|
|||||||
);
|
);
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/:organizationId/customer-portal-session",
|
"/:organizationId/customer-portal-session", // TODO endpoint: move to EE
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT],
|
||||||
}),
|
}),
|
||||||
@@ -148,20 +148,6 @@ router.post(
|
|||||||
organizationController.createOrganizationPortalSession
|
organizationController.createOrganizationPortalSession
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get(
|
|
||||||
"/:organizationId/subscriptions",
|
|
||||||
requireAuth({
|
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
|
||||||
}),
|
|
||||||
requireOrganizationAuth({
|
|
||||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
|
||||||
acceptedStatuses: [ACCEPTED],
|
|
||||||
}),
|
|
||||||
param("organizationId").exists().trim(),
|
|
||||||
validateRequest,
|
|
||||||
organizationController.getOrganizationSubscriptions
|
|
||||||
);
|
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/:organizationId/workspace-memberships",
|
"/:organizationId/workspace-memberships",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ import {
|
|||||||
MEMBER
|
MEMBER
|
||||||
} from "../../variables";
|
} from "../../variables";
|
||||||
|
|
||||||
// note to devs: these endpoints will be deprecated in favor of v2
|
// note: endpoints deprecated in favor of v3/secrets
|
||||||
|
|
||||||
router.post(
|
router.post( // TODO endpoint: deprecate (moved to POST api/v3/secrets)
|
||||||
"/:workspaceId",
|
"/:workspaceId",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT],
|
||||||
@@ -34,7 +34,7 @@ router.post(
|
|||||||
secretController.pushSecrets
|
secretController.pushSecrets
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get(
|
router.get( // TODO endpoint: deprecate (moved to GET api/v3/secrets)
|
||||||
"/:workspaceId",
|
"/:workspaceId",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT],
|
||||||
@@ -50,7 +50,7 @@ router.get(
|
|||||||
secretController.pullSecrets
|
secretController.pullSecrets
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get(
|
router.get( // TODO endpoint: deprecate (moved to GET api/v3/secrets)
|
||||||
"/:workspaceId/service-token",
|
"/:workspaceId/service-token",
|
||||||
requireServiceTokenAuth,
|
requireServiceTokenAuth,
|
||||||
query("environment").exists().trim(),
|
query("environment").exists().trim(),
|
||||||
|
|||||||
@@ -16,13 +16,13 @@ import { serviceTokenController } from "../../controllers/v1";
|
|||||||
|
|
||||||
// note: deprecate service-token routes in favor of service-token data routes/structure
|
// note: deprecate service-token routes in favor of service-token data routes/structure
|
||||||
|
|
||||||
router.get(
|
router.get( // TODO endpoint: deprecate
|
||||||
"/",
|
"/",
|
||||||
requireServiceTokenAuth,
|
requireServiceTokenAuth,
|
||||||
serviceTokenController.getServiceToken
|
serviceTokenController.getServiceToken
|
||||||
);
|
);
|
||||||
|
|
||||||
router.post(
|
router.post( // TODO endpoint: deprecate
|
||||||
"/",
|
"/",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT],
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ import { validateRequest } from "../../middleware";
|
|||||||
import { signupController } from "../../controllers/v1";
|
import { signupController } from "../../controllers/v1";
|
||||||
import { authLimiter } from "../../helpers/rateLimiter";
|
import { authLimiter } from "../../helpers/rateLimiter";
|
||||||
|
|
||||||
router.post(
|
// TODO: consider moving to users/v3/signup
|
||||||
|
|
||||||
|
router.post( // TODO endpoint: consider moving to v3/users/signup/mail
|
||||||
"/email/signup",
|
"/email/signup",
|
||||||
authLimiter,
|
authLimiter,
|
||||||
body("email").exists().trim().notEmpty().isEmail(),
|
body("email").exists().trim().notEmpty().isEmail(),
|
||||||
@@ -14,7 +16,7 @@ router.post(
|
|||||||
);
|
);
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/email/verify",
|
"/email/verify", // TODO endpoint: consider moving to v3/users/signup/verify
|
||||||
authLimiter,
|
authLimiter,
|
||||||
body("email").exists().trim().notEmpty().isEmail(),
|
body("email").exists().trim().notEmpty().isEmail(),
|
||||||
body("code").exists().trim().notEmpty(),
|
body("code").exists().trim().notEmpty(),
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { requireAuth } from "../../middleware";
|
|||||||
import { userController } from "../../controllers/v1";
|
import { userController } from "../../controllers/v1";
|
||||||
import { AuthMode } from "../../variables";
|
import { AuthMode } from "../../variables";
|
||||||
|
|
||||||
router.get(
|
router.get( // TODO endpoint: deprecate (moved to v2/users/me)
|
||||||
"/",
|
"/",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT],
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { userActionController } from "../../controllers/v1";
|
|||||||
import { AuthMode } from "../../variables";
|
import { AuthMode } from "../../variables";
|
||||||
|
|
||||||
// note: [userAction] will be deprecated in /v2 in favor of [action]
|
// note: [userAction] will be deprecated in /v2 in favor of [action]
|
||||||
router.post(
|
router.post( // TODO endpoint: move this into /users/me
|
||||||
"/",
|
"/",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT],
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ router.get(
|
|||||||
);
|
);
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/:workspaceId/service-tokens", // deprecate
|
"/:workspaceId/service-tokens", // TODO endpoint: deprecate
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT],
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { requireMfaAuth, validateRequest } from "../../middleware";
|
|||||||
import { authController } from "../../controllers/v2";
|
import { authController } from "../../controllers/v2";
|
||||||
import { authLimiter } from "../../helpers/rateLimiter";
|
import { authLimiter } from "../../helpers/rateLimiter";
|
||||||
|
|
||||||
router.post(
|
router.post( // TODO: deprecate (moved to api/v3/auth/login1)
|
||||||
"/login1",
|
"/login1",
|
||||||
authLimiter,
|
authLimiter,
|
||||||
body("email").isString().trim().notEmpty(),
|
body("email").isString().trim().notEmpty(),
|
||||||
@@ -14,7 +14,7 @@ router.post(
|
|||||||
authController.login1
|
authController.login1
|
||||||
);
|
);
|
||||||
|
|
||||||
router.post(
|
router.post( // TODO: deprecate (moved to api/v3/auth/login1)
|
||||||
"/login2",
|
"/login2",
|
||||||
authLimiter,
|
authLimiter,
|
||||||
body("email").isString().trim().notEmpty(),
|
body("email").isString().trim().notEmpty(),
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ router.get(
|
|||||||
organizationsController.getOrganizationWorkspaces
|
organizationsController.getOrganizationWorkspaces
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get(
|
router.get( // TODO endpoint: deprecate service accounts
|
||||||
"/:organizationId/service-accounts",
|
"/:organizationId/service-accounts",
|
||||||
param("organizationId").exists().trim(),
|
param("organizationId").exists().trim(),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import express from "express";
|
import express from "express";
|
||||||
|
const router = express.Router();
|
||||||
import {
|
import {
|
||||||
requireAuth,
|
requireAuth,
|
||||||
requireSecretAuth,
|
requireSecretAuth,
|
||||||
@@ -16,11 +17,9 @@ import {
|
|||||||
import { CreateSecretRequestBody, ModifySecretRequestBody } from "../../types/secret";
|
import { CreateSecretRequestBody, ModifySecretRequestBody } from "../../types/secret";
|
||||||
import { secretController } from "../../controllers/v2";
|
import { secretController } from "../../controllers/v2";
|
||||||
|
|
||||||
// note to devs: stop supporting these routes [deprecated]
|
// note: endpoints deprecated in favor of v3/secrets
|
||||||
|
|
||||||
const router = express.Router();
|
router.post( // TODO endpoint: deprecate (moved to POST api/v3/secrets)
|
||||||
|
|
||||||
router.post(
|
|
||||||
"/batch-create/workspace/:workspaceId/environment/:environment",
|
"/batch-create/workspace/:workspaceId/environment/:environment",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT],
|
||||||
@@ -38,7 +37,7 @@ router.post(
|
|||||||
);
|
);
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/workspace/:workspaceId/environment/:environment",
|
"/workspace/:workspaceId/environment/:environment", // TODO endpoint: deprecate (moved to POST api/v3/secrets)
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT],
|
||||||
}),
|
}),
|
||||||
@@ -54,7 +53,7 @@ router.post(
|
|||||||
secretController.createSecret
|
secretController.createSecret
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get(
|
router.get( // TODO endpoint: deprecate (moved to GET api/v3/secrets)
|
||||||
"/workspace/:workspaceId",
|
"/workspace/:workspaceId",
|
||||||
param("workspaceId").exists().trim(),
|
param("workspaceId").exists().trim(),
|
||||||
query("environment").exists(),
|
query("environment").exists(),
|
||||||
@@ -70,7 +69,7 @@ router.get(
|
|||||||
secretController.getSecrets
|
secretController.getSecrets
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get(
|
router.get( // TODO endpoint: deprecate (moved to POST api/v3/secrets)
|
||||||
"/:secretId",
|
"/:secretId",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN],
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN],
|
||||||
@@ -83,7 +82,7 @@ router.get(
|
|||||||
secretController.getSecret
|
secretController.getSecret
|
||||||
);
|
);
|
||||||
|
|
||||||
router.delete(
|
router.delete( // TODO endpoint: deprecate (moved to DELETE api/v3/secrets)
|
||||||
"/batch/workspace/:workspaceId/environment/:environmentName",
|
"/batch/workspace/:workspaceId/environment/:environmentName",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT],
|
||||||
@@ -99,7 +98,7 @@ router.delete(
|
|||||||
secretController.deleteSecrets
|
secretController.deleteSecrets
|
||||||
);
|
);
|
||||||
|
|
||||||
router.delete(
|
router.delete( // TODO endpoint: deprecate (moved to DELETE api/v3/secrets)
|
||||||
"/:secretId",
|
"/:secretId",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT],
|
||||||
@@ -113,7 +112,7 @@ router.delete(
|
|||||||
secretController.deleteSecret
|
secretController.deleteSecret
|
||||||
);
|
);
|
||||||
|
|
||||||
router.patch(
|
router.patch( // TODO endpoint: deprecate (moved to PATCH api/v3/secrets)
|
||||||
"/batch-modify/workspace/:workspaceId/environment/:environmentName",
|
"/batch-modify/workspace/:workspaceId/environment/:environmentName",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT],
|
||||||
@@ -129,7 +128,7 @@ router.patch(
|
|||||||
secretController.updateSecrets
|
secretController.updateSecrets
|
||||||
);
|
);
|
||||||
|
|
||||||
router.patch(
|
router.patch( // TODO endpoint: deprecate (moved to PATCH api/v3/secrets)
|
||||||
"/workspace/:workspaceId/environment/:environmentName",
|
"/workspace/:workspaceId/environment/:environmentName",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT],
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import {
|
|||||||
} from "../../variables";
|
} from "../../variables";
|
||||||
import { BatchSecretRequest } from "../../types/secret";
|
import { BatchSecretRequest } from "../../types/secret";
|
||||||
|
|
||||||
router.post(
|
router.post( // TODO endpoint: strongly consider deprecation in favor of a single operation experience on dashboard
|
||||||
"/batch",
|
"/batch",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN]
|
||||||
@@ -56,7 +56,7 @@ router.post(
|
|||||||
secretsController.batchSecrets
|
secretsController.batchSecrets
|
||||||
);
|
);
|
||||||
|
|
||||||
router.post(
|
router.post( // TODO endpoint: deprecate (moved to POST api/v3/secrets)
|
||||||
"/",
|
"/",
|
||||||
body("workspaceId").exists().isString().trim(),
|
body("workspaceId").exists().isString().trim(),
|
||||||
body("environment").exists().isString().trim(),
|
body("environment").exists().isString().trim(),
|
||||||
@@ -117,7 +117,7 @@ router.post(
|
|||||||
secretsController.createSecrets
|
secretsController.createSecrets
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get(
|
router.get( // TODO endpoint: deprecate (moved to GET api/v3/secrets)
|
||||||
"/",
|
"/",
|
||||||
query("workspaceId").exists().trim(),
|
query("workspaceId").exists().trim(),
|
||||||
query("environment").exists().trim(),
|
query("environment").exists().trim(),
|
||||||
@@ -138,7 +138,7 @@ router.get(
|
|||||||
secretsController.getSecrets
|
secretsController.getSecrets
|
||||||
);
|
);
|
||||||
|
|
||||||
router.patch(
|
router.patch( // TODO endpoint: deprecate (moved to PATCH api/v3/secrets)
|
||||||
"/",
|
"/",
|
||||||
body("secrets")
|
body("secrets")
|
||||||
.exists()
|
.exists()
|
||||||
@@ -173,7 +173,7 @@ router.patch(
|
|||||||
secretsController.updateSecrets
|
secretsController.updateSecrets
|
||||||
);
|
);
|
||||||
|
|
||||||
router.delete(
|
router.delete( // TODO endpoint: deprecate (moved to DELETE api/v3/secrets)
|
||||||
"/",
|
"/",
|
||||||
body("secretIds")
|
body("secretIds")
|
||||||
.exists()
|
.exists()
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import express from "express";
|
import express from "express";
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
|
// TODO endpoint: deprecate all
|
||||||
|
|
||||||
// import {
|
// import {
|
||||||
// requireAuth,
|
// requireAuth,
|
||||||
// requireOrganizationAuth,
|
// requireOrganizationAuth,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { signupController } from "../../controllers/v2";
|
|||||||
import { authLimiter } from "../../helpers/rateLimiter";
|
import { authLimiter } from "../../helpers/rateLimiter";
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/complete-account/signup",
|
"/complete-account/signup", // TODO endpoint: deprecate (moved to v3/signup/complete/account-signup)
|
||||||
authLimiter,
|
authLimiter,
|
||||||
requireSignupAuth,
|
requireSignupAuth,
|
||||||
body("email").exists().isString().trim().notEmpty().isEmail(),
|
body("email").exists().isString().trim().notEmpty().isEmail(),
|
||||||
@@ -27,7 +27,7 @@ router.post(
|
|||||||
);
|
);
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/complete-account/invite",
|
"/complete-account/invite", // TODO: consider moving to v3/users/new/complete-account/invite
|
||||||
authLimiter,
|
authLimiter,
|
||||||
requireSignupAuth,
|
requireSignupAuth,
|
||||||
body("email").exists().isString().trim().notEmpty().isEmail(),
|
body("email").exists().isString().trim().notEmpty().isEmail(),
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import {
|
|||||||
} from "../../variables";
|
} from "../../variables";
|
||||||
import { workspaceController } from "../../controllers/v2";
|
import { workspaceController } from "../../controllers/v2";
|
||||||
|
|
||||||
router.post(
|
router.post( // TODO endpoint: deprecate (moved to POST v3/secrets)
|
||||||
"/:workspaceId/secrets",
|
"/:workspaceId/secrets",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT],
|
||||||
@@ -32,7 +32,7 @@ router.post(
|
|||||||
workspaceController.pushWorkspaceSecrets
|
workspaceController.pushWorkspaceSecrets
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get(
|
router.get( // TODO endpoint: deprecate (moved to GET v3/secrets)
|
||||||
"/:workspaceId/secrets",
|
"/:workspaceId/secrets",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN],
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN],
|
||||||
@@ -48,7 +48,7 @@ router.get(
|
|||||||
workspaceController.pullSecrets
|
workspaceController.pullSecrets
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get(
|
router.get( // TODO endpoint: consider moving to v3/users/me/workspaces/:workspaceId/key
|
||||||
"/:workspaceId/encrypted-key",
|
"/:workspaceId/encrypted-key",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY],
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY],
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { authLimiter } from "../../helpers/rateLimiter";
|
|||||||
import { validateRequest } from "../../middleware";
|
import { validateRequest } from "../../middleware";
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/complete-account/signup",
|
"/complete-account/signup", // TODO: consider moving endpoint to v3/users/new/complete-account/signup
|
||||||
authLimiter,
|
authLimiter,
|
||||||
body("email").exists().isString().trim().notEmpty().isEmail(),
|
body("email").exists().isString().trim().notEmpty().isEmail(),
|
||||||
body("firstName").exists().isString().trim().notEmpty(),
|
body("firstName").exists().isString().trim().notEmpty(),
|
||||||
|
|||||||
@@ -1,99 +0,0 @@
|
|||||||
import { Fragment, useState } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
|
||||||
|
|
||||||
import addIncidentContact from "@app/pages/api/organization/addIncidentContact";
|
|
||||||
|
|
||||||
import Button from "../buttons/Button";
|
|
||||||
import InputField from "../InputField";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
isOpen: boolean;
|
|
||||||
closeModal: () => void;
|
|
||||||
incidentContacts: string[];
|
|
||||||
setIncidentContacts: (arg: string[]) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
const AddIncidentContactDialog = ({
|
|
||||||
isOpen,
|
|
||||||
closeModal,
|
|
||||||
incidentContacts,
|
|
||||||
setIncidentContacts
|
|
||||||
}: Props) => {
|
|
||||||
const [incidentContactEmail, setIncidentContactEmail] = useState("");
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const submit = () => {
|
|
||||||
setIncidentContacts(
|
|
||||||
incidentContacts?.length > 0
|
|
||||||
? incidentContacts.concat([incidentContactEmail])
|
|
||||||
: [incidentContactEmail]
|
|
||||||
);
|
|
||||||
addIncidentContact(localStorage.getItem("orgData.id") as string, incidentContactEmail);
|
|
||||||
closeModal();
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Transition appear show={isOpen} as={Fragment}>
|
|
||||||
<Dialog as="div" className="relative z-10" onClose={closeModal}>
|
|
||||||
<Transition.Child
|
|
||||||
as={Fragment}
|
|
||||||
enter="ease-out duration-300"
|
|
||||||
enterFrom="opacity-0"
|
|
||||||
enterTo="opacity-100"
|
|
||||||
leave="ease-in duration-200"
|
|
||||||
leaveFrom="opacity-100"
|
|
||||||
leaveTo="opacity-0"
|
|
||||||
>
|
|
||||||
<div className="fixed inset-0 bg-black bg-opacity-70" />
|
|
||||||
</Transition.Child>
|
|
||||||
|
|
||||||
<div className="fixed inset-0 overflow-y-auto">
|
|
||||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
|
||||||
<Transition.Child
|
|
||||||
as={Fragment}
|
|
||||||
enter="ease-out duration-300"
|
|
||||||
enterFrom="opacity-0 scale-95"
|
|
||||||
enterTo="opacity-100 scale-100"
|
|
||||||
leave="ease-in duration-200"
|
|
||||||
leaveFrom="opacity-100 scale-100"
|
|
||||||
leaveTo="opacity-0 scale-95"
|
|
||||||
>
|
|
||||||
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-md border border-gray-700 bg-bunker-800 p-6 text-left align-middle shadow-xl transition-all">
|
|
||||||
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-gray-400">
|
|
||||||
{t("section.incident.add-dialog.title")}
|
|
||||||
</Dialog.Title>
|
|
||||||
<div className="mt-2 mb-2">
|
|
||||||
<p className="text-sm text-gray-500">
|
|
||||||
{t("section.incident.add-dialog.description")}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="max-h-28">
|
|
||||||
<InputField
|
|
||||||
label={t("common.email")}
|
|
||||||
onChangeHandler={setIncidentContactEmail}
|
|
||||||
type="varName"
|
|
||||||
value={incidentContactEmail}
|
|
||||||
placeholder=""
|
|
||||||
isRequired
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="mt-6 max-w-max">
|
|
||||||
<Button
|
|
||||||
onButtonPressed={submit}
|
|
||||||
color="mineshaft"
|
|
||||||
text={t("section.incident.add-dialog.add-incident") as string}
|
|
||||||
size="md"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Dialog.Panel>
|
|
||||||
</Transition.Child>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Dialog>
|
|
||||||
</Transition>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AddIncidentContactDialog;
|
|
||||||
@@ -1,271 +0,0 @@
|
|||||||
import crypto from "crypto";
|
|
||||||
|
|
||||||
import { Fragment, useState } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { faCheck, faCopy } from "@fortawesome/free-solid-svg-icons";
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
|
||||||
|
|
||||||
import addServiceToken from "@app/pages/api/serviceToken/addServiceToken";
|
|
||||||
import getLatestFileKey from "@app/pages/api/workspace/getLatestFileKey";
|
|
||||||
|
|
||||||
import { decryptAssymmetric, encryptSymmetric } from "../../utilities/cryptography/crypto";
|
|
||||||
import Button from "../buttons/Button";
|
|
||||||
import InputField from "../InputField";
|
|
||||||
import ListBox from "../Listbox";
|
|
||||||
|
|
||||||
const expiryMapping = {
|
|
||||||
"1 day": 86400,
|
|
||||||
"7 days": 604800,
|
|
||||||
"1 month": 2592000,
|
|
||||||
"6 months": 15552000,
|
|
||||||
"12 months": 31104000
|
|
||||||
};
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
isOpen: boolean;
|
|
||||||
closeModal: () => void;
|
|
||||||
workspaceId: string;
|
|
||||||
workspaceName: string;
|
|
||||||
serviceTokens: any[];
|
|
||||||
environments: Array<{ name: string; slug: string }>;
|
|
||||||
setServiceTokens: (arg: any[]) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
const AddServiceTokenDialog = ({
|
|
||||||
isOpen,
|
|
||||||
closeModal,
|
|
||||||
workspaceId,
|
|
||||||
workspaceName,
|
|
||||||
serviceTokens,
|
|
||||||
environments,
|
|
||||||
setServiceTokens
|
|
||||||
}: Props) => {
|
|
||||||
const [serviceToken, setServiceToken] = useState("");
|
|
||||||
const [serviceTokenName, setServiceTokenName] = useState("");
|
|
||||||
const [selectedServiceTokenEnv, setSelectedServiceTokenEnv] = useState(environments?.[0]);
|
|
||||||
const [serviceTokenExpiresIn, setServiceTokenExpiresIn] = useState("1 day");
|
|
||||||
const [serviceTokenCopied, setServiceTokenCopied] = useState(false);
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const generateServiceToken = async () => {
|
|
||||||
const latestFileKey = await getLatestFileKey({ workspaceId });
|
|
||||||
|
|
||||||
const key = decryptAssymmetric({
|
|
||||||
ciphertext: latestFileKey.latestKey.encryptedKey,
|
|
||||||
nonce: latestFileKey.latestKey.nonce,
|
|
||||||
publicKey: latestFileKey.latestKey.sender.publicKey,
|
|
||||||
privateKey: localStorage.getItem("PRIVATE_KEY") as string
|
|
||||||
});
|
|
||||||
|
|
||||||
const randomBytes = crypto.randomBytes(16).toString("hex");
|
|
||||||
const { ciphertext, iv, tag } = encryptSymmetric({
|
|
||||||
plaintext: key,
|
|
||||||
key: randomBytes
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
1234,
|
|
||||||
selectedServiceTokenEnv,
|
|
||||||
environments,
|
|
||||||
selectedServiceTokenEnv?.slug ? selectedServiceTokenEnv.slug : environments[0]?.slug
|
|
||||||
);
|
|
||||||
const newServiceToken = await addServiceToken({
|
|
||||||
name: serviceTokenName,
|
|
||||||
workspaceId,
|
|
||||||
environment: selectedServiceTokenEnv?.slug
|
|
||||||
? selectedServiceTokenEnv.slug
|
|
||||||
: environments[0]?.slug,
|
|
||||||
expiresIn: expiryMapping[serviceTokenExpiresIn as keyof typeof expiryMapping],
|
|
||||||
encryptedKey: ciphertext,
|
|
||||||
iv,
|
|
||||||
tag
|
|
||||||
});
|
|
||||||
|
|
||||||
setServiceTokens(serviceTokens.concat([newServiceToken.serviceTokenData]));
|
|
||||||
setServiceToken(`${newServiceToken.serviceToken}.${randomBytes}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
function copyToClipboard() {
|
|
||||||
// Get the text field
|
|
||||||
const copyText = document.getElementById("serviceToken") as HTMLInputElement;
|
|
||||||
|
|
||||||
// Select the text field
|
|
||||||
copyText.select();
|
|
||||||
copyText.setSelectionRange(0, 99999); // For mobile devices
|
|
||||||
|
|
||||||
// Copy the text inside the text field
|
|
||||||
navigator.clipboard.writeText(copyText.value);
|
|
||||||
|
|
||||||
setServiceTokenCopied(true);
|
|
||||||
setTimeout(() => setServiceTokenCopied(false), 2000);
|
|
||||||
// Alert the copied text
|
|
||||||
// alert("Copied the text: " + copyText.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
const closeAddServiceTokenModal = () => {
|
|
||||||
closeModal();
|
|
||||||
setServiceTokenName("");
|
|
||||||
setServiceToken("");
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="z-50">
|
|
||||||
<Transition appear show={isOpen} as={Fragment}>
|
|
||||||
<Dialog as="div" className="relative" onClose={closeModal}>
|
|
||||||
<Transition.Child
|
|
||||||
as={Fragment}
|
|
||||||
enter="ease-out duration-300"
|
|
||||||
enterFrom="opacity-0"
|
|
||||||
enterTo="opacity-100"
|
|
||||||
leave="ease-in duration-200"
|
|
||||||
leaveFrom="opacity-100"
|
|
||||||
leaveTo="opacity-0"
|
|
||||||
>
|
|
||||||
<div className="fixed inset-0 bg-bunker-700 bg-opacity-80" />
|
|
||||||
</Transition.Child>
|
|
||||||
|
|
||||||
<div className="fixed inset-0 overflow-y-auto">
|
|
||||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
|
||||||
<Transition.Child
|
|
||||||
as={Fragment}
|
|
||||||
enter="ease-out duration-300"
|
|
||||||
enterFrom="opacity-0 scale-95"
|
|
||||||
enterTo="opacity-100 scale-100"
|
|
||||||
leave="ease-in duration-200"
|
|
||||||
leaveFrom="opacity-100 scale-100"
|
|
||||||
leaveTo="opacity-0 scale-95"
|
|
||||||
>
|
|
||||||
{serviceToken === "" ? (
|
|
||||||
<Dialog.Panel className="w-full max-w-md transform rounded-md border border-gray-700 bg-bunker-800 p-6 text-left align-middle shadow-xl transition-all">
|
|
||||||
<Dialog.Title
|
|
||||||
as="h3"
|
|
||||||
className="z-50 text-lg font-medium leading-6 text-gray-400"
|
|
||||||
>
|
|
||||||
{t("section.token.add-dialog.title", {
|
|
||||||
target: workspaceName
|
|
||||||
})}
|
|
||||||
</Dialog.Title>
|
|
||||||
<div className="mt-2 mb-4">
|
|
||||||
<div className="flex flex-col">
|
|
||||||
<p className="text-sm text-gray-500">
|
|
||||||
{t("section.token.add-dialog.description")}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mb-2 max-h-28">
|
|
||||||
<InputField
|
|
||||||
label={t("section.token.add-dialog.name")}
|
|
||||||
onChangeHandler={setServiceTokenName}
|
|
||||||
type="varName"
|
|
||||||
value={serviceTokenName}
|
|
||||||
placeholder=""
|
|
||||||
isRequired
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="mb-2 max-h-28">
|
|
||||||
<ListBox
|
|
||||||
isSelected={
|
|
||||||
selectedServiceTokenEnv?.name
|
|
||||||
? selectedServiceTokenEnv?.name
|
|
||||||
: environments[0]?.name
|
|
||||||
}
|
|
||||||
data={environments.map(({ name }) => name)}
|
|
||||||
onChange={(envName) =>
|
|
||||||
setSelectedServiceTokenEnv(
|
|
||||||
environments.find(({ name }) => envName === name) || {
|
|
||||||
name: "unknown",
|
|
||||||
slug: "unknown"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
isFull
|
|
||||||
text={`${t("common.environment")}: `}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="max-h-28">
|
|
||||||
<ListBox
|
|
||||||
isSelected={serviceTokenExpiresIn}
|
|
||||||
onChange={setServiceTokenExpiresIn}
|
|
||||||
data={["1 day", "7 days", "1 month", "6 months", "12 months"]}
|
|
||||||
isFull
|
|
||||||
text={`${t("common.expired-in")}: `}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="max-w-max">
|
|
||||||
<div className="mt-6 flex w-max flex-col justify-start">
|
|
||||||
<Button
|
|
||||||
onButtonPressed={() => generateServiceToken()}
|
|
||||||
color="mineshaft"
|
|
||||||
text={t("section.token.add-dialog.add") as string}
|
|
||||||
textDisabled={t("section.token.add-dialog.add") as string}
|
|
||||||
size="md"
|
|
||||||
active={serviceTokenName !== ""}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Dialog.Panel>
|
|
||||||
) : (
|
|
||||||
<Dialog.Panel className="w-full max-w-md transform rounded-md border border-gray-700 bg-bunker-800 p-6 text-left align-middle shadow-xl transition-all">
|
|
||||||
<Dialog.Title
|
|
||||||
as="h3"
|
|
||||||
className="z-50 text-lg font-medium leading-6 text-gray-400"
|
|
||||||
>
|
|
||||||
{t("section.token.add-dialog.copy-service-token")}
|
|
||||||
</Dialog.Title>
|
|
||||||
<div className="mt-2 mb-4">
|
|
||||||
<div className="flex flex-col">
|
|
||||||
<p className="text-sm text-gray-500">
|
|
||||||
{t("section.token.add-dialog.copy-service-token-description")}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="w-full">
|
|
||||||
<div className="mt-2 mr-2 flex h-20 w-full items-center justify-end rounded-md bg-white/[0.07] text-base text-gray-400">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={serviceToken}
|
|
||||||
id="serviceToken"
|
|
||||||
className="invisible w-full min-w-full bg-white/0 py-2 px-2 text-gray-400 outline-none"
|
|
||||||
/>
|
|
||||||
<div className="w-full max-w-md break-words bg-white/0 py-2 pl-14 pr-2 text-sm text-gray-400 outline-none">
|
|
||||||
{serviceToken}
|
|
||||||
</div>
|
|
||||||
<div className="group relative inline-block h-full font-normal text-gray-400 underline duration-200 hover:text-primary">
|
|
||||||
<button
|
|
||||||
onClick={copyToClipboard}
|
|
||||||
type="button"
|
|
||||||
className="h-full border-l border-white/20 py-2 pl-3.5 pr-4 duration-200 hover:bg-white/[0.12]"
|
|
||||||
>
|
|
||||||
{serviceTokenCopied ? (
|
|
||||||
<FontAwesomeIcon icon={faCheck} className="pr-0.5" />
|
|
||||||
) : (
|
|
||||||
<FontAwesomeIcon icon={faCopy} />
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
<span className="absolute -left-8 -top-20 hidden w-28 translate-y-full rounded-md bg-chicago-900 px-3 py-2 text-center text-sm text-gray-400 duration-300 group-hover:flex group-hover:animate-popup">
|
|
||||||
{t("common.click-to-copy")}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mt-6 flex w-max flex-col justify-start">
|
|
||||||
<Button
|
|
||||||
onButtonPressed={() => closeAddServiceTokenModal()}
|
|
||||||
color="mineshaft"
|
|
||||||
text="Close"
|
|
||||||
size="md"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Dialog.Panel>
|
|
||||||
)}
|
|
||||||
</Transition.Child>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Dialog>
|
|
||||||
</Transition>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AddServiceTokenDialog;
|
|
||||||
@@ -1,184 +0,0 @@
|
|||||||
import { useEffect, useState } from "react";
|
|
||||||
import { faPencil, faPlus, faX } from "@fortawesome/free-solid-svg-icons";
|
|
||||||
import { plans } from "public/data/frequentConstants";
|
|
||||||
|
|
||||||
import { usePopUp } from "../../../hooks/usePopUp";
|
|
||||||
import getOrganizationSubscriptions from "../../../pages/api/organization/GetOrgSubscription";
|
|
||||||
import Button from "../buttons/Button";
|
|
||||||
import { AddUpdateEnvironmentDialog } from "../dialog/AddUpdateEnvironmentDialog";
|
|
||||||
import DeleteActionModal from "../dialog/DeleteActionModal";
|
|
||||||
import UpgradePlanModal from "../dialog/UpgradePlan";
|
|
||||||
|
|
||||||
type Env = { name: string; slug: string };
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
data: Env[];
|
|
||||||
onCreateEnv: (arg0: Env) => Promise<void>;
|
|
||||||
onUpdateEnv: (oldSlug: string, arg0: Env) => Promise<void>;
|
|
||||||
onDeleteEnv: (slug: string) => Promise<void>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const EnvironmentTable = ({ data = [], onCreateEnv, onDeleteEnv, onUpdateEnv }: Props) => {
|
|
||||||
const { popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
|
|
||||||
"createUpdateEnv",
|
|
||||||
"deleteEnv",
|
|
||||||
"upgradePlan"
|
|
||||||
] as const);
|
|
||||||
const [plan, setPlan] = useState("");
|
|
||||||
const host = window.location.origin;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// on initial load - run auth check
|
|
||||||
(async () => {
|
|
||||||
const orgId = localStorage.getItem("orgData.id") as string;
|
|
||||||
const subscriptions = await getOrganizationSubscriptions({
|
|
||||||
orgId
|
|
||||||
});
|
|
||||||
if (subscriptions) {
|
|
||||||
setPlan(subscriptions.data[0].plan.product)
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const onEnvCreateCB = async (env: Env) => {
|
|
||||||
try {
|
|
||||||
await onCreateEnv(env);
|
|
||||||
handlePopUpClose("createUpdateEnv");
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onEnvUpdateCB = async (env: Env) => {
|
|
||||||
try {
|
|
||||||
await onUpdateEnv((popUp.createUpdateEnv?.data as Pick<Env, "slug">)?.slug, env);
|
|
||||||
handlePopUpClose("createUpdateEnv");
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onEnvDeleteCB = async () => {
|
|
||||||
try {
|
|
||||||
await onDeleteEnv((popUp.deleteEnv?.data as Pick<Env, "slug">)?.slug);
|
|
||||||
handlePopUpClose("deleteEnv");
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="flex w-full flex-row justify-between">
|
|
||||||
<div className="flex w-full flex-col">
|
|
||||||
<p className="mb-3 text-xl font-semibold">Project Environments</p>
|
|
||||||
<p className="mb-4 text-base text-gray-400">
|
|
||||||
Choose which environments will show up in your dashboard like development, staging,
|
|
||||||
production
|
|
||||||
</p>
|
|
||||||
<p className="mr-1 self-start text-sm text-gray-500">
|
|
||||||
Note: the text in slugs shows how these environmant should be accessed in CLI.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="w-48">
|
|
||||||
<Button
|
|
||||||
text="Add New Environment"
|
|
||||||
onButtonPressed={() => {
|
|
||||||
if (plan !== plans.starter || host !== "https://app.infisical.com" || localStorage.getItem("projectData.id") === "63ea8121b6e2b0543ba79616") {
|
|
||||||
handlePopUpOpen("createUpdateEnv");
|
|
||||||
} else {
|
|
||||||
handlePopUpOpen("upgradePlan");
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
color="mineshaft"
|
|
||||||
icon={faPlus}
|
|
||||||
size="md"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="table-container relative mb-6 mt-1 w-full rounded-md border border-mineshaft-700 bg-bunker">
|
|
||||||
<div className="absolute h-12 w-full rounded-t-md bg-white/5" />
|
|
||||||
<table className="my-1 w-full">
|
|
||||||
<thead className="text-bunker-300">
|
|
||||||
<tr>
|
|
||||||
<th className="pl-6 pt-2.5 pb-2 text-left">Name</th>
|
|
||||||
<th className="pl-6 pt-2.5 pb-2 text-left">Slug</th>
|
|
||||||
<th aria-label="buttons" />
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{data?.length > 0 ? (
|
|
||||||
data.map(({ name, slug }) => (
|
|
||||||
<tr key={name} className="bg-bunker-800 duration-100 hover:bg-bunker-800/5">
|
|
||||||
<td className="border-t border-mineshaft-700 py-2 pl-6 text-gray-300">{name}</td>
|
|
||||||
<td className="border-t border-mineshaft-700 py-2 pl-6 text-gray-300">{slug}</td>
|
|
||||||
<td className="flex justify-end border-t border-mineshaft-700 py-2">
|
|
||||||
<div className="mr-2 flex items-center duration-200 hover:opacity-100">
|
|
||||||
<Button
|
|
||||||
onButtonPressed={() => {
|
|
||||||
if (plan !== plans.starter || host !== "https://app.infisical.com") {
|
|
||||||
handlePopUpOpen("createUpdateEnv", { name, slug });
|
|
||||||
} else {
|
|
||||||
handlePopUpOpen("upgradePlan");
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
color="mineshaft"
|
|
||||||
size="icon-sm"
|
|
||||||
icon={faPencil}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="mr-6 flex items-center opacity-50 duration-200 hover:opacity-100">
|
|
||||||
<Button
|
|
||||||
onButtonPressed={() => {
|
|
||||||
if (plan !== plans.starter || host !== "https://app.infisical.com") {
|
|
||||||
handlePopUpOpen("deleteEnv", { name, slug });
|
|
||||||
} else {
|
|
||||||
handlePopUpOpen("upgradePlan");
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
color="red"
|
|
||||||
size="icon-sm"
|
|
||||||
icon={faX}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<tr>
|
|
||||||
<td colSpan={4} className="pt-7 pb-4 text-center text-bunker-400">
|
|
||||||
No environments found
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
)}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<DeleteActionModal
|
|
||||||
isOpen={popUp.deleteEnv.isOpen}
|
|
||||||
title={`Are you sure want to delete ${
|
|
||||||
(popUp?.deleteEnv?.data as { name: string })?.name || " "
|
|
||||||
}?`}
|
|
||||||
deleteKey={(popUp?.deleteEnv?.data as { slug: string })?.slug || ""}
|
|
||||||
onClose={() => handlePopUpClose("deleteEnv")}
|
|
||||||
onSubmit={onEnvDeleteCB}
|
|
||||||
/>
|
|
||||||
<AddUpdateEnvironmentDialog
|
|
||||||
isOpen={popUp.createUpdateEnv.isOpen}
|
|
||||||
isEditMode={Boolean(popUp.createUpdateEnv?.data)}
|
|
||||||
initialValues={popUp?.createUpdateEnv?.data as any}
|
|
||||||
onClose={() => handlePopUpClose("createUpdateEnv")}
|
|
||||||
onCreateSubmit={onEnvCreateCB}
|
|
||||||
onEditSubmit={onEnvUpdateCB}
|
|
||||||
/>
|
|
||||||
<UpgradePlanModal
|
|
||||||
isOpen={popUp.upgradePlan.isOpen}
|
|
||||||
onClose={() => handlePopUpClose("upgradePlan")}
|
|
||||||
text="You can add custom environments if you switch to Infisical's Team plan."
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default EnvironmentTable;
|
|
||||||
@@ -4,13 +4,13 @@ import { faEye, faEyeSlash, faPenToSquare, faPlus, faX } from "@fortawesome/free
|
|||||||
|
|
||||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||||
import { Select, SelectItem } from "@app/components/v2";
|
import { Select, SelectItem } from "@app/components/v2";
|
||||||
import { useSubscription } from "@app/context";
|
import { useSubscription, useWorkspace } from "@app/context";
|
||||||
import updateUserProjectPermission from "@app/ee/api/memberships/UpdateUserProjectPermission";
|
import updateUserProjectPermission from "@app/ee/api/memberships/UpdateUserProjectPermission";
|
||||||
import changeUserRoleInWorkspace from "@app/pages/api/workspace/changeUserRoleInWorkspace";
|
import {
|
||||||
import deleteUserFromWorkspace from "@app/pages/api/workspace/deleteUserFromWorkspace";
|
useDeleteUserFromWorkspace,
|
||||||
import getLatestFileKey from "@app/pages/api/workspace/getLatestFileKey";
|
useGetUserWsKey,
|
||||||
import getProjectInfo from "@app/pages/api/workspace/getProjectInfo";
|
useUpdateUserWorkspaceRole,
|
||||||
import uploadKeys from "@app/pages/api/workspace/uploadKeys";
|
useUploadWsKey} from "@app/hooks/api";
|
||||||
|
|
||||||
import { decryptAssymmetric, encryptAssymmetric } from "../../utilities/cryptography/crypto";
|
import { decryptAssymmetric, encryptAssymmetric } from "../../utilities/cryptography/crypto";
|
||||||
import guidGenerator from "../../utilities/randomId";
|
import guidGenerator from "../../utilities/randomId";
|
||||||
@@ -39,10 +39,16 @@ type EnvironmentProps = {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const ProjectUsersTable = ({ userData, changeData, myUser, filter, isUserListLoading }: Props) => {
|
const ProjectUsersTable = ({ userData, changeData, myUser, filter, isUserListLoading }: Props) => {
|
||||||
|
const { currentWorkspace } = useWorkspace();
|
||||||
const { subscription } = useSubscription();
|
const { subscription } = useSubscription();
|
||||||
const [roleSelected, setRoleSelected] = useState(
|
const { data: wsKey } = useGetUserWsKey(currentWorkspace?._id ?? "");
|
||||||
Array(userData?.length).fill(userData.map((user) => user.role))
|
|
||||||
);
|
const { mutateAsync: deleteUserFromWorkspaceMutateAsync } = useDeleteUserFromWorkspace();
|
||||||
|
const { mutateAsync: uploadWsKeyMutateAsync } = useUploadWsKey();
|
||||||
|
const { mutateAsync: updateUserWorkspaceRoleMutateAsync } = useUpdateUserWorkspaceRole();
|
||||||
|
// const [roleSelected, setRoleSelected] = useState(
|
||||||
|
// Array(userData?.length).fill(userData.map((user) => user.role))
|
||||||
|
// );
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [myRole, setMyRole] = useState("member");
|
const [myRole, setMyRole] = useState("member");
|
||||||
const [workspaceEnvs, setWorkspaceEnvs] = useState<EnvironmentProps[]>([]);
|
const [workspaceEnvs, setWorkspaceEnvs] = useState<EnvironmentProps[]>([]);
|
||||||
@@ -52,38 +58,15 @@ const ProjectUsersTable = ({ userData, changeData, myUser, filter, isUserListLoa
|
|||||||
const workspaceId = router.query.id as string;
|
const workspaceId = router.query.id as string;
|
||||||
// Delete the row in the table (e.g. a user)
|
// Delete the row in the table (e.g. a user)
|
||||||
// #TODO: Add a pop-up that warns you that the user is going to be deleted.
|
// #TODO: Add a pop-up that warns you that the user is going to be deleted.
|
||||||
const handleDelete = (membershipId: string, index: number) => {
|
const handleDelete = async (membershipId: string) => {
|
||||||
// setUserIdToBeDeleted(userId);
|
await deleteUserFromWorkspaceMutateAsync(membershipId);
|
||||||
// onClick();
|
|
||||||
deleteUserFromWorkspace(membershipId);
|
|
||||||
changeData(userData.filter((v, i) => i !== index));
|
|
||||||
setRoleSelected([
|
|
||||||
...roleSelected.slice(0, index),
|
|
||||||
...roleSelected.slice(index + 1, userData?.length)
|
|
||||||
]);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Update the rold of a certain user
|
const handleRoleUpdate = async (index: number, e: string) => {
|
||||||
const handleRoleUpdate = (index: number, e: string) => {
|
await updateUserWorkspaceRoleMutateAsync({
|
||||||
changeUserRoleInWorkspace(userData[index].membershipId, e.toLowerCase());
|
|
||||||
changeData([
|
|
||||||
...userData.slice(0, index),
|
|
||||||
...[
|
|
||||||
{
|
|
||||||
key: userData[index].key,
|
|
||||||
firstName: userData[index].firstName,
|
|
||||||
lastName: userData[index].lastName,
|
|
||||||
email: userData[index].email,
|
|
||||||
role: e.toLocaleLowerCase(),
|
|
||||||
status: userData[index].status,
|
|
||||||
userId: userData[index].userId,
|
|
||||||
membershipId: userData[index].membershipId,
|
membershipId: userData[index].membershipId,
|
||||||
publicKey: userData[index].publicKey,
|
role: e.toLowerCase()
|
||||||
deniedPermissions: userData[index].deniedPermissions
|
});
|
||||||
}
|
|
||||||
],
|
|
||||||
...userData.slice(index + 1, userData?.length)
|
|
||||||
]);
|
|
||||||
createNotification({
|
createNotification({
|
||||||
text: "Successfully changed user role.",
|
text: "Successfully changed user role.",
|
||||||
type: "success"
|
type: "success"
|
||||||
@@ -163,21 +146,21 @@ const ProjectUsersTable = ({ userData, changeData, myUser, filter, isUserListLoa
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setMyRole(userData.filter((user) => user.email === myUser)[0]?.role);
|
setMyRole(userData.filter((user) => user.email === myUser)[0]?.role);
|
||||||
(async () => {
|
(async () => {
|
||||||
const result = await getProjectInfo({ projectId: workspaceId });
|
if (currentWorkspace) {
|
||||||
setWorkspaceEnvs(result.environments);
|
setWorkspaceEnvs(currentWorkspace.environments);
|
||||||
|
}
|
||||||
})();
|
})();
|
||||||
}, [userData, myUser]);
|
}, [userData, myUser, currentWorkspace]);
|
||||||
|
|
||||||
const grantAccess = async (id: string, publicKey: string) => {
|
const grantAccess = async (id: string, publicKey: string) => {
|
||||||
const result = await getLatestFileKey({ workspaceId });
|
if (wsKey) {
|
||||||
|
|
||||||
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string;
|
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string;
|
||||||
|
|
||||||
// assymmetrically decrypt symmetric key with local private key
|
// assymmetrically decrypt symmetric key with local private key
|
||||||
const key = decryptAssymmetric({
|
const key = decryptAssymmetric({
|
||||||
ciphertext: result.latestKey.encryptedKey,
|
ciphertext: wsKey.encryptedKey,
|
||||||
nonce: result.latestKey.nonce,
|
nonce: wsKey.nonce,
|
||||||
publicKey: result.latestKey.sender.publicKey,
|
publicKey: wsKey.sender.publicKey,
|
||||||
privateKey: PRIVATE_KEY
|
privateKey: PRIVATE_KEY
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -187,8 +170,14 @@ const ProjectUsersTable = ({ userData, changeData, myUser, filter, isUserListLoa
|
|||||||
privateKey: PRIVATE_KEY
|
privateKey: PRIVATE_KEY
|
||||||
});
|
});
|
||||||
|
|
||||||
uploadKeys(workspaceId, id, ciphertext, nonce);
|
await uploadWsKeyMutateAsync({
|
||||||
|
workspaceId,
|
||||||
|
userId: id,
|
||||||
|
encryptedKey: ciphertext,
|
||||||
|
nonce
|
||||||
|
});
|
||||||
router.reload();
|
router.reload();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const closeUpgradeModal = () => {
|
const closeUpgradeModal = () => {
|
||||||
@@ -372,7 +361,7 @@ const ProjectUsersTable = ({ userData, changeData, myUser, filter, isUserListLoa
|
|||||||
myRole !== "member" ? (
|
myRole !== "member" ? (
|
||||||
<div className="mt-0.5 flex items-center opacity-50 hover:opacity-100">
|
<div className="mt-0.5 flex items-center opacity-50 hover:opacity-100">
|
||||||
<Button
|
<Button
|
||||||
onButtonPressed={() => handleDelete(row.membershipId, index)}
|
onButtonPressed={() => handleDelete(row.membershipId)}
|
||||||
color="red"
|
color="red"
|
||||||
size="icon-sm"
|
size="icon-sm"
|
||||||
icon={faX}
|
icon={faX}
|
||||||
|
|||||||
@@ -1,98 +0,0 @@
|
|||||||
import { faX } from "@fortawesome/free-solid-svg-icons";
|
|
||||||
|
|
||||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
|
||||||
|
|
||||||
import deleteServiceToken from "../../../pages/api/serviceToken/deleteServiceToken";
|
|
||||||
import guidGenerator from "../../utilities/randomId";
|
|
||||||
import Button from "../buttons/Button";
|
|
||||||
|
|
||||||
interface TokenProps {
|
|
||||||
_id: string;
|
|
||||||
name: string;
|
|
||||||
environment: string;
|
|
||||||
expiresAt: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ServiceTokensProps {
|
|
||||||
data: TokenProps[];
|
|
||||||
workspaceName: string;
|
|
||||||
setServiceTokens: (value: TokenProps[]) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is the component that we utilize for the service token table
|
|
||||||
* #TODO: add the possibility of choosing and doing operations on multiple users.
|
|
||||||
* @param {object} obj
|
|
||||||
* @param {any[]} obj.data - current state of the service token table
|
|
||||||
* @param {string} obj.workspaceName - name of the current project
|
|
||||||
* @param {function} obj.setServiceTokens - updating the state of the service token table
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const ServiceTokenTable = ({ data, workspaceName, setServiceTokens }: ServiceTokensProps) => {
|
|
||||||
const { createNotification } = useNotificationContext();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="table-container w-full bg-bunker rounded-md mb-6 border border-mineshaft-700 relative mt-1">
|
|
||||||
<div className="absolute rounded-t-md w-full h-12 bg-white/5" />
|
|
||||||
<table className="w-full my-1">
|
|
||||||
<thead className="text-bunker-300 text-sm font-light">
|
|
||||||
<tr>
|
|
||||||
<th className="text-left pl-6 pt-2.5 pb-2">TOKEN NAME</th>
|
|
||||||
<th className="text-left pl-6 pt-2.5 pb-2">PROJECT</th>
|
|
||||||
<th className="text-left pl-6 pt-2.5 pb-2">ENVIRONMENT</th>
|
|
||||||
<th className="text-left pl-6 pt-2.5 pb-2">VAILD UNTIL</th>
|
|
||||||
<th aria-label="button" />
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{data?.length > 0 ? (
|
|
||||||
data?.map((row) => (
|
|
||||||
<tr
|
|
||||||
key={guidGenerator()}
|
|
||||||
className="bg-bunker-800 hover:bg-bunker-800/5 duration-100"
|
|
||||||
>
|
|
||||||
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
|
||||||
{row.name}
|
|
||||||
</td>
|
|
||||||
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
|
||||||
{workspaceName}
|
|
||||||
</td>
|
|
||||||
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
|
||||||
{row.environment}
|
|
||||||
</td>
|
|
||||||
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
|
||||||
{new Date(row.expiresAt).toUTCString()}
|
|
||||||
</td>
|
|
||||||
<td className="py-2 border-mineshaft-700 border-t">
|
|
||||||
<div className="opacity-50 hover:opacity-100 duration-200 flex items-center">
|
|
||||||
<Button
|
|
||||||
onButtonPressed={() => {
|
|
||||||
deleteServiceToken({ serviceTokenId: row._id });
|
|
||||||
setServiceTokens(data.filter((token) => token._id !== row._id));
|
|
||||||
createNotification({
|
|
||||||
text: `'${row.name}' token has been revoked.`,
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
color="red"
|
|
||||||
size="icon-sm"
|
|
||||||
icon={faX}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<tr>
|
|
||||||
<td colSpan={4} className="text-center pt-7 pb-5 text-bunker-300 text-sm">
|
|
||||||
No service tokens yet
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
)}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ServiceTokenTable;
|
|
||||||
@@ -1,234 +0,0 @@
|
|||||||
import { useEffect, useState } from "react";
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import { faX } from "@fortawesome/free-solid-svg-icons";
|
|
||||||
|
|
||||||
import changeUserRoleInOrganization from "@app/pages/api/organization/changeUserRoleInOrganization";
|
|
||||||
import deleteUserFromOrganization from "@app/pages/api/organization/deleteUserFromOrganization";
|
|
||||||
import getOrganizationProjectMemberships from "@app/pages/api/organization/GetOrgProjectMemberships";
|
|
||||||
import deleteUserFromWorkspace from "@app/pages/api/workspace/deleteUserFromWorkspace";
|
|
||||||
import getLatestFileKey from "@app/pages/api/workspace/getLatestFileKey";
|
|
||||||
import uploadKeys from "@app/pages/api/workspace/uploadKeys";
|
|
||||||
|
|
||||||
import { decryptAssymmetric, encryptAssymmetric } from "../../utilities/cryptography/crypto";
|
|
||||||
import guidGenerator from "../../utilities/randomId";
|
|
||||||
import Button from "../buttons/Button";
|
|
||||||
import Listbox from "../Listbox";
|
|
||||||
|
|
||||||
// const roles = ['admin', 'user'];
|
|
||||||
// TODO: Set type for this
|
|
||||||
type Props = {
|
|
||||||
userData: any[];
|
|
||||||
changeData: (users: any[]) => void;
|
|
||||||
myUser: string;
|
|
||||||
filter: string;
|
|
||||||
resendInvite: (email: string) => void;
|
|
||||||
isOrg: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is the component that we utilize for the user table - in future, can reuse it for some other purposes too.
|
|
||||||
* #TODO: add the possibility of choosing and doing operations on multiple users.
|
|
||||||
* @param {*} props
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const UserTable = ({ userData, changeData, myUser, filter, resendInvite, isOrg }: Props) => {
|
|
||||||
const [roleSelected, setRoleSelected] = useState(
|
|
||||||
Array(userData?.length).fill(userData.map((user) => user.role))
|
|
||||||
);
|
|
||||||
const router = useRouter();
|
|
||||||
const [myRole, setMyRole] = useState("member");
|
|
||||||
const [userProjectMemberships, setUserProjectMemberships] = useState<any[]>([]);
|
|
||||||
|
|
||||||
const workspaceId = router.query.id as string;
|
|
||||||
// Delete the row in the table (e.g. a user)
|
|
||||||
// #TODO: Add a pop-up that warns you that the user is going to be deleted.
|
|
||||||
const handleDelete = (membershipId: string, index: number) => {
|
|
||||||
// setUserIdToBeDeleted(userId);
|
|
||||||
// onClick();
|
|
||||||
if (isOrg) {
|
|
||||||
deleteUserFromOrganization(membershipId);
|
|
||||||
} else {
|
|
||||||
deleteUserFromWorkspace(membershipId);
|
|
||||||
}
|
|
||||||
changeData(userData.filter((v, i) => i !== index));
|
|
||||||
setRoleSelected([
|
|
||||||
...roleSelected.slice(0, index),
|
|
||||||
...roleSelected.slice(index + 1, userData?.length)
|
|
||||||
]);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Update the role of a certain user
|
|
||||||
const handleRoleUpdate = (index: number, e: string) => {
|
|
||||||
changeUserRoleInOrganization(String(localStorage.getItem("orgData.id")), userData[index].membershipId, e);
|
|
||||||
changeData([
|
|
||||||
...userData.slice(0, index),
|
|
||||||
...[
|
|
||||||
{
|
|
||||||
key: userData[index].key,
|
|
||||||
firstName: userData[index].firstName,
|
|
||||||
lastName: userData[index].lastName,
|
|
||||||
email: userData[index].email,
|
|
||||||
role: e,
|
|
||||||
status: userData[index].status,
|
|
||||||
userId: userData[index].userId,
|
|
||||||
membershipId: userData[index].membershipId,
|
|
||||||
publicKey: userData[index].publicKey
|
|
||||||
}
|
|
||||||
],
|
|
||||||
...userData.slice(index + 1, userData?.length)
|
|
||||||
]);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setMyRole(userData.filter((user) => user.email === myUser)[0]?.role);
|
|
||||||
(async () => {
|
|
||||||
const result = await getOrganizationProjectMemberships({ orgId: String(localStorage.getItem("orgData.id"))})
|
|
||||||
setUserProjectMemberships(result);
|
|
||||||
})();
|
|
||||||
}, [userData, myUser]);
|
|
||||||
|
|
||||||
const grantAccess = async (id: string, publicKey: string) => {
|
|
||||||
const result = await getLatestFileKey({ workspaceId });
|
|
||||||
|
|
||||||
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string;
|
|
||||||
|
|
||||||
// assymmetrically decrypt symmetric key with local private key
|
|
||||||
const key = decryptAssymmetric({
|
|
||||||
ciphertext: result.latestKey.encryptedKey,
|
|
||||||
nonce: result.latestKey.nonce,
|
|
||||||
publicKey: result.latestKey.sender.publicKey,
|
|
||||||
privateKey: PRIVATE_KEY
|
|
||||||
});
|
|
||||||
|
|
||||||
const { ciphertext, nonce } = encryptAssymmetric({
|
|
||||||
plaintext: key,
|
|
||||||
publicKey,
|
|
||||||
privateKey: PRIVATE_KEY
|
|
||||||
});
|
|
||||||
|
|
||||||
uploadKeys(workspaceId, id, ciphertext, nonce);
|
|
||||||
router.reload();
|
|
||||||
};
|
|
||||||
|
|
||||||
const deleteMembershipAndResendInvite = (email: string) => {
|
|
||||||
// deleteUserFromWorkspace(membershipId);
|
|
||||||
resendInvite(email);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="table-container bg-bunker rounded-md mb-6 border border-mineshaft-700 relative mt-1 min-w-max w-full">
|
|
||||||
<div className="absolute rounded-t-md w-full h-[3.25rem] bg-white/5" />
|
|
||||||
<table className="w-full my-0.5">
|
|
||||||
<thead className="text-gray-400 text-sm font-light">
|
|
||||||
<tr>
|
|
||||||
<th className="text-left pl-4 py-3.5">NAME</th>
|
|
||||||
<th className="text-left pl-4 py-3.5">EMAIL</th>
|
|
||||||
<th className="text-left pl-6 pr-10 py-3.5">ROLE</th>
|
|
||||||
<th className="text-left pl-6 pr-10 py-3.5">PROJECTS</th>
|
|
||||||
<th aria-label="buttons" />
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{userData?.filter(
|
|
||||||
(user) =>
|
|
||||||
user.firstName?.toLowerCase().includes(filter) ||
|
|
||||||
user.lastName?.toLowerCase().includes(filter) ||
|
|
||||||
user.email?.toLowerCase().includes(filter)
|
|
||||||
).length > 0 &&
|
|
||||||
userData
|
|
||||||
?.filter(
|
|
||||||
(user) =>
|
|
||||||
user.firstName?.toLowerCase().includes(filter) ||
|
|
||||||
user.lastName?.toLowerCase().includes(filter) ||
|
|
||||||
user.email?.toLowerCase().includes(filter)
|
|
||||||
)
|
|
||||||
.map((row, index) => (
|
|
||||||
<tr key={guidGenerator()} className="bg-bunker-800 hover:bg-bunker-700">
|
|
||||||
<td className="pl-4 py-2 border-mineshaft-700 border-t text-gray-300">
|
|
||||||
{row.firstName} {row.lastName}
|
|
||||||
</td>
|
|
||||||
<td className="pl-4 py-2 border-mineshaft-700 border-t text-gray-300">
|
|
||||||
{row.email}
|
|
||||||
</td>
|
|
||||||
<td className="pl-6 pr-10 py-2 border-mineshaft-700 border-t text-gray-300">
|
|
||||||
<div className="justify-start h-full flex flex-row items-center">
|
|
||||||
{row.status === "accepted" &&
|
|
||||||
((myRole === "admin" && row.role !== "owner") || myRole === "owner") &&
|
|
||||||
(myUser !== row.email) ? (
|
|
||||||
<Listbox
|
|
||||||
isSelected={row.role}
|
|
||||||
onChange={(e) => handleRoleUpdate(index, e)}
|
|
||||||
data={
|
|
||||||
myRole === "owner" ? ["owner", "admin", "member"] : ["admin", "member"]
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
row.status !== "invited" &&
|
|
||||||
row.status !== "verified" && (
|
|
||||||
<Listbox
|
|
||||||
isSelected={row.role}
|
|
||||||
onChange={() => {
|
|
||||||
throw new Error("Function not implemented.");
|
|
||||||
}}
|
|
||||||
data={null}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
{(row.status === "invited" || row.status === "verified") && (
|
|
||||||
<div className="w-full pr-20">
|
|
||||||
<Button
|
|
||||||
onButtonPressed={() => deleteMembershipAndResendInvite(row.email)}
|
|
||||||
color="mineshaft"
|
|
||||||
text="Resend Invite"
|
|
||||||
size="md"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{row.status === "completed" && myUser !== row.email && (
|
|
||||||
<div className="border border-mineshaft-700 rounded-md bg-white/5 hover:bg-primary text-white hover:text-black duration-200">
|
|
||||||
<Button
|
|
||||||
onButtonPressed={() => grantAccess(row.userId, row.publicKey)}
|
|
||||||
color="mineshaft"
|
|
||||||
text="Grant Access"
|
|
||||||
size="md"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td className="pl-4 py-2 border-mineshaft-700 border-t text-gray-300">
|
|
||||||
<div className="flex items-center max-h-16 overflow-x-auto w-full max-w-xl break-all">
|
|
||||||
{userProjectMemberships[row.userId]
|
|
||||||
? userProjectMemberships[row.userId]?.map((project: any) => (
|
|
||||||
<div key={project._id} className='mx-1 min-w-max px-1.5 bg-mineshaft-500 rounded-sm text-sm text-bunker-200 flex items-center'>
|
|
||||||
<span className='mb-0.5 cursor-default'>{project.name}</span>
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
: <span className='ml-1 text-bunker-100 rounded-sm px-1 py-0.5 text-sm bg-red/80'>This user isn't part of any projects yet.</span>}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td className="flex flex-row justify-end pl-8 pr-8 py-2 border-t border-0.5 border-mineshaft-700">
|
|
||||||
{myUser !== row.email &&
|
|
||||||
// row.role !== "admin" &&
|
|
||||||
myRole !== "member" ? (
|
|
||||||
<div className="opacity-50 hover:opacity-100 flex items-center mt-0.5">
|
|
||||||
<Button
|
|
||||||
onButtonPressed={() => handleDelete(row.membershipId, index)}
|
|
||||||
color="red"
|
|
||||||
size="icon-sm"
|
|
||||||
icon={faX}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="w-9 h-9" />
|
|
||||||
)}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default UserTable;
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
import { SetStateAction, useEffect, useState } from "react";
|
|
||||||
import Image from "next/image";
|
|
||||||
|
|
||||||
import { WorkspaceEnv } from "@app/hooks/api/types";
|
|
||||||
|
|
||||||
import getSecretsForProject from "../utilities/secrets/getSecretsForProject";
|
|
||||||
import { Modal, ModalContent } from "../v2";
|
|
||||||
|
|
||||||
interface Secrets {
|
|
||||||
label: string;
|
|
||||||
secret: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface CompareSecretsModalProps {
|
|
||||||
compareModal: boolean;
|
|
||||||
setCompareModal: React.Dispatch<SetStateAction<boolean>>;
|
|
||||||
selectedEnv: WorkspaceEnv;
|
|
||||||
workspaceEnvs: WorkspaceEnv[];
|
|
||||||
workspaceId: string;
|
|
||||||
currentSecret: {
|
|
||||||
key: string;
|
|
||||||
value: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const CompareSecretsModal = ({
|
|
||||||
compareModal,
|
|
||||||
setCompareModal,
|
|
||||||
selectedEnv,
|
|
||||||
workspaceEnvs,
|
|
||||||
workspaceId,
|
|
||||||
currentSecret
|
|
||||||
}: CompareSecretsModalProps) => {
|
|
||||||
const [secrets, setSecrets] = useState<Secrets[]>([]);
|
|
||||||
|
|
||||||
const getEnvSecrets = async () => {
|
|
||||||
const workspaceEnvironments = workspaceEnvs?.filter((env) => env !== selectedEnv);
|
|
||||||
const newSecrets = await Promise.all(
|
|
||||||
workspaceEnvironments.map(async (env) => {
|
|
||||||
// #TODO: optimize this query somehow...
|
|
||||||
const allSecrets = await getSecretsForProject({ env: env.slug, workspaceId });
|
|
||||||
const secret =
|
|
||||||
allSecrets.find((item) => item.key === currentSecret.key)?.value ?? "Not found";
|
|
||||||
return { label: env.name, secret };
|
|
||||||
})
|
|
||||||
);
|
|
||||||
setSecrets([{ label: selectedEnv.name, secret: currentSecret.value }, ...newSecrets]);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (compareModal) {
|
|
||||||
(async () => {
|
|
||||||
await getEnvSecrets();
|
|
||||||
})();
|
|
||||||
}
|
|
||||||
}, [compareModal]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal isOpen={compareModal} onOpenChange={setCompareModal}>
|
|
||||||
<ModalContent title={currentSecret?.key} onOpenAutoFocus={(e) => e.preventDefault()}>
|
|
||||||
<div className="space-y-4">
|
|
||||||
{secrets.length === 0 ? (
|
|
||||||
<div className="flex items-center bg-bunker-900 justify-center h-full py-4 rounded-md">
|
|
||||||
<Image
|
|
||||||
src="/images/loading/loading.gif"
|
|
||||||
height={60}
|
|
||||||
width={100}
|
|
||||||
alt="infisical loading indicator"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
secrets.map((item) => (
|
|
||||||
<div key={`${currentSecret.key}${item.label}`} className="space-y-0.5">
|
|
||||||
<p className="text-sm text-bunker-300">{item.label}</p>
|
|
||||||
<input
|
|
||||||
defaultValue={item.secret}
|
|
||||||
className="h-no-capture border border-mineshaft-500 text-md min-w-16 no-scrollbar::-webkit-scrollbar peer z-10 w-full rounded-md bg-bunker-800 px-2 py-1.5 font-mono text-gray-400 caret-white outline-none duration-200 no-scrollbar focus:ring-2 focus:ring-primary/50 "
|
|
||||||
readOnly
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</ModalContent>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
export default CompareSecretsModal;
|
|
||||||
@@ -1,222 +0,0 @@
|
|||||||
/* eslint-disable react/no-unused-prop-types */
|
|
||||||
import { useState } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import Image from "next/image";
|
|
||||||
import { faXmark } from "@fortawesome/free-solid-svg-icons";
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
||||||
|
|
||||||
import SecretVersionList from "@app/ee/components/SecretVersionList";
|
|
||||||
import { WorkspaceEnv } from "@app/hooks/api/types";
|
|
||||||
|
|
||||||
import Button from "../basic/buttons/Button";
|
|
||||||
import Toggle from "../basic/Toggle";
|
|
||||||
import CommentField from "./CommentField";
|
|
||||||
import CompareSecretsModal from "./CompareSecretsModal";
|
|
||||||
import DashboardInputField from "./DashboardInputField";
|
|
||||||
import { DeleteActionButton } from "./DeleteActionButton";
|
|
||||||
import GenerateSecretMenu from "./GenerateSecretMenu";
|
|
||||||
|
|
||||||
interface SecretProps {
|
|
||||||
key: string;
|
|
||||||
value: string | undefined;
|
|
||||||
valueOverride: string | undefined;
|
|
||||||
pos: number;
|
|
||||||
id: string;
|
|
||||||
comment: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DeleteRowFunctionProps {
|
|
||||||
ids: string[];
|
|
||||||
secretName: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SideBarProps {
|
|
||||||
toggleSidebar: (value: string) => void;
|
|
||||||
data: SecretProps[];
|
|
||||||
modifyKey: (value: string, id: string) => void;
|
|
||||||
modifyValue: (value: string, id: string) => void;
|
|
||||||
modifyValueOverride: (value: string | undefined, id: string) => void;
|
|
||||||
modifyComment: (value: string, id: string) => void;
|
|
||||||
buttonReady: boolean;
|
|
||||||
savePush: () => void;
|
|
||||||
sharedToHide: string[];
|
|
||||||
setSharedToHide: (values: string[]) => void;
|
|
||||||
deleteRow: (props: DeleteRowFunctionProps) => void;
|
|
||||||
workspaceEnvs: WorkspaceEnv[];
|
|
||||||
selectedEnv: WorkspaceEnv;
|
|
||||||
workspaceId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {object} obj
|
|
||||||
* @param {function} obj.toggleSidebar - function that opens or closes the sidebar
|
|
||||||
* @param {SecretProps[]} obj.data - data of a certain key valeu pair
|
|
||||||
* @param {function} obj.modifyKey - function that modifies the secret key
|
|
||||||
* @param {function} obj.modifyValue - function that modifies the secret value
|
|
||||||
* @param {function} obj.modifyValueOverride - function that modifies the secret value if it is an override
|
|
||||||
* @param {boolean} obj.buttonReady - is the button for saving chagnes active
|
|
||||||
* @param {function} obj.savePush - save changes andp ush secrets
|
|
||||||
* @param {function} obj.deleteRow - a function to delete a certain keyPair
|
|
||||||
* @returns the sidebar with 'secret's settings'
|
|
||||||
*/
|
|
||||||
const SideBar = ({
|
|
||||||
toggleSidebar,
|
|
||||||
data,
|
|
||||||
modifyKey,
|
|
||||||
modifyValue,
|
|
||||||
modifyValueOverride,
|
|
||||||
modifyComment,
|
|
||||||
buttonReady,
|
|
||||||
savePush,
|
|
||||||
deleteRow,
|
|
||||||
workspaceEnvs,
|
|
||||||
selectedEnv,
|
|
||||||
workspaceId
|
|
||||||
}: SideBarProps) => {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
|
||||||
const [overrideEnabled, setOverrideEnabled] = useState(data[0]?.valueOverride !== undefined);
|
|
||||||
const [compareModal, setCompareModal] = useState(false);
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="min-w-sm absolute sticky top-0 right-0 z-[70] flex h-full w-full max-w-sm flex-col justify-between border-l border-mineshaft-500 bg-bunker shadow-xl">
|
|
||||||
{isLoading ? (
|
|
||||||
<div className="flex h-full w-full items-center justify-center">
|
|
||||||
<Image
|
|
||||||
src="/images/loading/loading.gif"
|
|
||||||
height={60}
|
|
||||||
width={100}
|
|
||||||
alt="infisical loading indicator"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="h-min w-full overflow-y-auto">
|
|
||||||
<div className="flex flex-row items-center justify-between border-b border-mineshaft-500 px-4 py-3">
|
|
||||||
<p className="text-lg font-semibold text-bunker-200">{t("dashboard.sidebar.secret")}</p>
|
|
||||||
<div
|
|
||||||
onKeyDown={() => null}
|
|
||||||
role="button"
|
|
||||||
tabIndex={0}
|
|
||||||
className="p-1"
|
|
||||||
onClick={() => toggleSidebar("None")}
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon icon={faXmark} className="h-4 w-4 cursor-pointer text-bunker-300" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="pointer-events-none mt-4 px-4">
|
|
||||||
<p className="text-sm text-bunker-300">{t("dashboard.sidebar.key")}</p>
|
|
||||||
<div className="overflow-hidden rounded-md border border-mineshaft-600 bg-white/5">
|
|
||||||
<DashboardInputField
|
|
||||||
onChangeHandler={modifyKey}
|
|
||||||
type="varName"
|
|
||||||
id={data[0]?.id}
|
|
||||||
value={data[0]?.key}
|
|
||||||
isDuplicate={false}
|
|
||||||
blurred={false}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{data[0]?.value || data[0]?.value === "" ? (
|
|
||||||
<div
|
|
||||||
className={`relative mt-2 px-4 ${
|
|
||||||
overrideEnabled && "pointer-events-none opacity-40"
|
|
||||||
} duration-200`}
|
|
||||||
>
|
|
||||||
<p className="text-sm text-bunker-300">{t("dashboard.sidebar.value")}</p>
|
|
||||||
<div className="overflow-hidden rounded-md border border-mineshaft-600 bg-white/5">
|
|
||||||
<DashboardInputField
|
|
||||||
onChangeHandler={modifyValue}
|
|
||||||
type="value"
|
|
||||||
id={data[0].id}
|
|
||||||
value={data[0]?.value}
|
|
||||||
isDuplicate={false}
|
|
||||||
blurred
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="absolute right-[1.07rem] top-[1.6rem] z-50 bg-bunker-800">
|
|
||||||
<GenerateSecretMenu modifyValue={modifyValue} id={data[0]?.id} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="px-4 pt-4 text-sm text-bunker-300">
|
|
||||||
<span className="mr-1 rounded-md bg-primary-200/10 py-0.5 px-1">
|
|
||||||
{t("common.note")}:
|
|
||||||
</span>
|
|
||||||
{t("dashboard.sidebar.personal-explanation")}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="mt-4 px-4">
|
|
||||||
{(data[0]?.value || data[0]?.value === "") && (
|
|
||||||
<div className="my-2 flex flex-row items-center justify-between pl-1 pr-2">
|
|
||||||
<p className="text-sm text-bunker-300">{t("dashboard.sidebar.override")}</p>
|
|
||||||
<Toggle
|
|
||||||
enabled={overrideEnabled}
|
|
||||||
setEnabled={setOverrideEnabled}
|
|
||||||
addOverride={modifyValueOverride}
|
|
||||||
id={data[0]?.id}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div
|
|
||||||
className={`relative ${
|
|
||||||
!overrideEnabled && "pointer-events-none opacity-40"
|
|
||||||
} duration-200`}
|
|
||||||
>
|
|
||||||
<div className="overflow-hidden rounded-md border border-mineshaft-600 bg-white/5">
|
|
||||||
<DashboardInputField
|
|
||||||
onChangeHandler={modifyValueOverride}
|
|
||||||
type="value"
|
|
||||||
id={data[0]?.id}
|
|
||||||
value={overrideEnabled ? data[0]?.valueOverride : data[0]?.value}
|
|
||||||
isDuplicate={false}
|
|
||||||
blurred
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="absolute right-[0.57rem] top-[0.3rem] z-50">
|
|
||||||
<GenerateSecretMenu modifyValue={modifyValueOverride} id={data[0]?.id} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<SecretVersionList secretId={data[0]?.id} />
|
|
||||||
<CommentField comment={data[0]?.comment} modifyComment={modifyComment} id={data[0]?.id} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="mt-full mt-4 mb-4 flex w-96 max-w-sm flex-col justify-start space-y-2 px-4">
|
|
||||||
<div>
|
|
||||||
<Button
|
|
||||||
text="Compare secret across environments"
|
|
||||||
color="mineshaft"
|
|
||||||
size="md"
|
|
||||||
onButtonPressed={() => setCompareModal(true)}
|
|
||||||
/>
|
|
||||||
<CompareSecretsModal
|
|
||||||
compareModal={compareModal}
|
|
||||||
setCompareModal={setCompareModal}
|
|
||||||
currentSecret={{ key: data[0]?.key, value: data[0]?.value ?? "" }}
|
|
||||||
workspaceEnvs={workspaceEnvs}
|
|
||||||
selectedEnv={selectedEnv}
|
|
||||||
workspaceId={workspaceId}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex">
|
|
||||||
<Button
|
|
||||||
text={String(t("common.save-changes"))}
|
|
||||||
onButtonPressed={savePush}
|
|
||||||
color="primary"
|
|
||||||
size="md"
|
|
||||||
active={buttonReady}
|
|
||||||
textDisabled="Saved"
|
|
||||||
/>
|
|
||||||
<DeleteActionButton
|
|
||||||
onSubmit={() =>
|
|
||||||
deleteRow({ ids: data.map((secret) => secret.id), secretName: data[0]?.key })
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SideBar;
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
import Image from "next/image";
|
|
||||||
import { faCheck, faXmark } from "@fortawesome/free-solid-svg-icons";
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
||||||
|
|
||||||
import deleteIntegrationAuth from "../../pages/api/integrations/DeleteIntegrationAuth";
|
|
||||||
|
|
||||||
interface IntegrationOption {
|
|
||||||
clientId: string;
|
|
||||||
clientSlug?: string; // vercel-integration specific
|
|
||||||
docsLink: string;
|
|
||||||
image: string;
|
|
||||||
isAvailable: boolean;
|
|
||||||
name: string;
|
|
||||||
slug: string;
|
|
||||||
type: string;
|
|
||||||
}
|
|
||||||
interface IntegrationAuth {
|
|
||||||
_id: string;
|
|
||||||
integration: string;
|
|
||||||
workspace: string;
|
|
||||||
createdAt: string;
|
|
||||||
updatedAt: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
cloudIntegrationOption: IntegrationOption;
|
|
||||||
setSelectedIntegrationOption: (cloudIntegration: IntegrationOption) => void;
|
|
||||||
integrationOptionPress: (cloudIntegrationOption: IntegrationOption) => void;
|
|
||||||
integrationAuths: IntegrationAuth[];
|
|
||||||
handleDeleteIntegrationAuth: (args: { integrationAuth: IntegrationAuth }) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const CloudIntegration = ({
|
|
||||||
cloudIntegrationOption,
|
|
||||||
setSelectedIntegrationOption,
|
|
||||||
integrationOptionPress,
|
|
||||||
integrationAuths,
|
|
||||||
handleDeleteIntegrationAuth
|
|
||||||
}: Props) => {
|
|
||||||
return integrationAuths ? (
|
|
||||||
<div
|
|
||||||
onKeyDown={() => null}
|
|
||||||
role="button"
|
|
||||||
tabIndex={0}
|
|
||||||
className={`relative ${
|
|
||||||
cloudIntegrationOption.isAvailable
|
|
||||||
? "cursor-pointer duration-200 hover:bg-mineshaft-700"
|
|
||||||
: "opacity-50"
|
|
||||||
} flex h-32 flex-row items-center rounded-md bg-mineshaft-800 border border-mineshaft-600 p-4`}
|
|
||||||
onClick={() => {
|
|
||||||
if (!cloudIntegrationOption.isAvailable) return;
|
|
||||||
setSelectedIntegrationOption(cloudIntegrationOption);
|
|
||||||
integrationOptionPress(cloudIntegrationOption);
|
|
||||||
}}
|
|
||||||
key={cloudIntegrationOption.name}
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
src={`/images/integrations/${cloudIntegrationOption.image}`}
|
|
||||||
height={70}
|
|
||||||
width={70}
|
|
||||||
alt="integration logo"
|
|
||||||
/>
|
|
||||||
{cloudIntegrationOption.name.split(" ").length > 2 ? (
|
|
||||||
<div className="ml-4 max-w-xs text-3xl font-semibold text-gray-300 duration-200 group-hover:text-gray-200">
|
|
||||||
<div>{cloudIntegrationOption.name.split(" ")[0]}</div>
|
|
||||||
<div className="text-base">
|
|
||||||
{cloudIntegrationOption.name.split(" ")[1]} {cloudIntegrationOption.name.split(" ")[2]}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="ml-4 max-w-xs text-xl font-semibold text-gray-300 duration-200 group-hover:text-gray-200">
|
|
||||||
{cloudIntegrationOption.name}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{cloudIntegrationOption.isAvailable &&
|
|
||||||
integrationAuths
|
|
||||||
.map((authorization) => authorization?.integration)
|
|
||||||
.includes(cloudIntegrationOption.slug) && (
|
|
||||||
<div className="group absolute top-0 right-0 z-40 flex flex-row">
|
|
||||||
<div
|
|
||||||
onKeyDown={() => null}
|
|
||||||
role="button"
|
|
||||||
tabIndex={0}
|
|
||||||
onClick={async (event) => {
|
|
||||||
event.stopPropagation();
|
|
||||||
const deletedIntegrationAuth = await deleteIntegrationAuth({
|
|
||||||
integrationAuthId: integrationAuths
|
|
||||||
.filter(
|
|
||||||
(authorization) => authorization.integration === cloudIntegrationOption.slug
|
|
||||||
)
|
|
||||||
.map((authorization) => authorization._id)[0]
|
|
||||||
});
|
|
||||||
|
|
||||||
handleDeleteIntegrationAuth({
|
|
||||||
integrationAuth: deletedIntegrationAuth
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
className="flex w-max cursor-pointer flex-row items-center rounded-bl-md bg-red py-0.5 px-2 text-xs opacity-30 duration-200 group-hover:opacity-100"
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon icon={faXmark} className="mr-2 text-xs" />
|
|
||||||
Revoke
|
|
||||||
</div>
|
|
||||||
<div className="flex w-max flex-row items-center rounded-tr-md bg-primary py-0.5 px-2 text-xs text-black opacity-70 duration-200 group-hover:opacity-100">
|
|
||||||
<FontAwesomeIcon icon={faCheck} className="mr-2 text-xs" />
|
|
||||||
Authorized
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{!cloudIntegrationOption.isAvailable && (
|
|
||||||
<div className="group absolute top-0 right-0 z-50 flex flex-row">
|
|
||||||
<div className="flex w-max flex-row items-center rounded-bl-md rounded-tr-md bg-yellow py-0.5 px-2 text-xs text-black opacity-90">
|
|
||||||
Coming Soon
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div />
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CloudIntegration;
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
import { useTranslation } from "react-i18next";
|
|
||||||
|
|
||||||
import CloudIntegration from "./CloudIntegration";
|
|
||||||
|
|
||||||
interface IntegrationOption {
|
|
||||||
clientId: string;
|
|
||||||
clientSlug?: string; // vercel-integration specific
|
|
||||||
docsLink: string;
|
|
||||||
image: string;
|
|
||||||
isAvailable: boolean;
|
|
||||||
name: string;
|
|
||||||
slug: string;
|
|
||||||
type: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IntegrationAuth {
|
|
||||||
_id: string;
|
|
||||||
integration: string;
|
|
||||||
workspace: string;
|
|
||||||
createdAt: string;
|
|
||||||
updatedAt: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
cloudIntegrationOptions: IntegrationOption[];
|
|
||||||
setSelectedIntegrationOption: () => void;
|
|
||||||
integrationOptionPress: (integrationOption: IntegrationOption) => void;
|
|
||||||
integrationAuths: IntegrationAuth[];
|
|
||||||
handleDeleteIntegrationAuth: (args: { integrationAuth: IntegrationAuth }) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const CloudIntegrationSection = ({
|
|
||||||
cloudIntegrationOptions,
|
|
||||||
setSelectedIntegrationOption,
|
|
||||||
integrationOptionPress,
|
|
||||||
integrationAuths,
|
|
||||||
handleDeleteIntegrationAuth
|
|
||||||
}: Props) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="m-4 mt-7 flex max-w-5xl flex-col items-start justify-between px-2 text-xl">
|
|
||||||
<h1 className="text-3xl font-semibold">{t("integrations.cloud-integrations")}</h1>
|
|
||||||
<p className="text-base text-gray-400">{t("integrations.click-to-start")}</p>
|
|
||||||
</div>
|
|
||||||
<div className="mx-6 grid max-w-5xl grid-cols-4 grid-rows-2 gap-4">
|
|
||||||
{cloudIntegrationOptions.map((cloudIntegrationOption) => (
|
|
||||||
<CloudIntegration
|
|
||||||
cloudIntegrationOption={cloudIntegrationOption}
|
|
||||||
setSelectedIntegrationOption={setSelectedIntegrationOption}
|
|
||||||
integrationOptionPress={integrationOptionPress}
|
|
||||||
integrationAuths={integrationAuths}
|
|
||||||
handleDeleteIntegrationAuth={handleDeleteIntegrationAuth}
|
|
||||||
key={`cloud-integration-${cloudIntegrationOption.slug}`}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CloudIntegrationSection;
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
import Image from "next/image";
|
|
||||||
|
|
||||||
interface Framework {
|
|
||||||
name: string;
|
|
||||||
slug: string;
|
|
||||||
image: string;
|
|
||||||
docsLink: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const FrameworkIntegration = ({ framework }: { framework: Framework }) => (
|
|
||||||
<a
|
|
||||||
href={framework.docsLink}
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
target="_blank"
|
|
||||||
className="relative flex flex-row justify-center duration-200 h-32 rounded-md p-0.5 items-center cursor-pointer"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={`hover:bg-mineshaft-700 cursor-pointer font-semibold bg-mineshaft-800 border border-mineshaft-600 flex flex-col items-center justify-center h-full w-full rounded-md text-gray-300 group-hover:text-gray-200 duration-200 ${
|
|
||||||
framework?.name?.split(" ").length > 1 ? "text-sm px-1" : "text-xl px-2"
|
|
||||||
} text-center w-full max-w-xs`}
|
|
||||||
>
|
|
||||||
{framework?.image && (
|
|
||||||
<Image
|
|
||||||
src={`/images/integrations/${framework.image}.png`}
|
|
||||||
height={framework?.name ? 60 : 90}
|
|
||||||
width={framework?.name ? 60 : 90}
|
|
||||||
alt="integration logo"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{framework?.name && framework?.image && <div className="h-2" />}
|
|
||||||
{framework?.name && framework.name}
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default FrameworkIntegration;
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
import { useTranslation } from "react-i18next";
|
|
||||||
|
|
||||||
import FrameworkIntegration from "./FrameworkIntegration";
|
|
||||||
|
|
||||||
interface Framework {
|
|
||||||
name: string;
|
|
||||||
image: string;
|
|
||||||
link: string;
|
|
||||||
slug: string;
|
|
||||||
docsLink: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
frameworks: [Framework];
|
|
||||||
}
|
|
||||||
|
|
||||||
const FrameworkIntegrationSection = ({ frameworks }: Props) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="mx-4 mt-12 mb-4 flex max-w-5xl flex-col items-start justify-between px-2 text-xl">
|
|
||||||
<h1 className="text-3xl font-semibold">{t("integrations.framework-integrations")}</h1>
|
|
||||||
<p className="text-base text-gray-400">{t("integrations.click-to-setup")}</p>
|
|
||||||
</div>
|
|
||||||
<div className="mx-6 mt-4 grid max-w-5xl grid-cols-7 grid-rows-2 gap-4">
|
|
||||||
{frameworks.map((framework) => (
|
|
||||||
<FrameworkIntegration
|
|
||||||
framework={framework}
|
|
||||||
key={`framework-integration-${framework.slug}`}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default FrameworkIntegrationSection;
|
|
||||||
@@ -1,313 +0,0 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import { faArrowRight, faCheck, faXmark } from "@fortawesome/free-solid-svg-icons";
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
||||||
// TODO: This needs to be moved from public folder
|
|
||||||
import {
|
|
||||||
contextNetlifyMapping,
|
|
||||||
integrationSlugNameMapping,
|
|
||||||
reverseContextNetlifyMapping
|
|
||||||
} from "public/data/frequentConstants";
|
|
||||||
|
|
||||||
import Button from "@app/components/basic/buttons/Button";
|
|
||||||
import ListBox from "@app/components/basic/Listbox";
|
|
||||||
|
|
||||||
import deleteIntegration from "../../pages/api/integrations/DeleteIntegration";
|
|
||||||
import getIntegrationApps from "../../pages/api/integrations/GetIntegrationApps";
|
|
||||||
import updateIntegration from "../../pages/api/integrations/updateIntegration";
|
|
||||||
|
|
||||||
interface Integration {
|
|
||||||
_id: string;
|
|
||||||
isActive: boolean;
|
|
||||||
app: string | null;
|
|
||||||
appId: string | null;
|
|
||||||
path: string | null;
|
|
||||||
region: string | null;
|
|
||||||
createdAt: string;
|
|
||||||
updatedAt: string;
|
|
||||||
environment: string;
|
|
||||||
integration: string;
|
|
||||||
targetEnvironment: string;
|
|
||||||
workspace: string;
|
|
||||||
integrationAuth: string;
|
|
||||||
secretPath: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IntegrationApp {
|
|
||||||
name: string;
|
|
||||||
appId?: string;
|
|
||||||
owner?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
integration: Integration;
|
|
||||||
integrations: Integration[];
|
|
||||||
setIntegrations: any;
|
|
||||||
bot: any;
|
|
||||||
setBot: any;
|
|
||||||
environments: Array<{ name: string; slug: string }>;
|
|
||||||
handleDeleteIntegration: (args: { integration: Integration }) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: refactor
|
|
||||||
const IntegrationTile = ({
|
|
||||||
integration,
|
|
||||||
integrations,
|
|
||||||
bot,
|
|
||||||
setBot,
|
|
||||||
setIntegrations,
|
|
||||||
environments = [],
|
|
||||||
handleDeleteIntegration
|
|
||||||
}: Props) => {
|
|
||||||
const [integrationEnvironment, setIntegrationEnvironment] = useState<Props["environments"][0]>(
|
|
||||||
environments.find(({ slug }) => slug === integration?.environment) || {
|
|
||||||
name: "",
|
|
||||||
slug: ""
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const router = useRouter();
|
|
||||||
const [apps, setApps] = useState<IntegrationApp[]>([]); // integration app objects
|
|
||||||
const [integrationApp, setIntegrationApp] = useState(""); // integration app name
|
|
||||||
const [integrationTargetEnvironment, setIntegrationTargetEnvironment] = useState("");
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const loadIntegration = async () => {
|
|
||||||
const tempApps: [IntegrationApp] = await getIntegrationApps({
|
|
||||||
integrationAuthId: integration?.integrationAuth
|
|
||||||
});
|
|
||||||
|
|
||||||
setApps(tempApps);
|
|
||||||
|
|
||||||
if (integration?.app) {
|
|
||||||
setIntegrationApp(integration.app);
|
|
||||||
} else if (integration?.path && integration?.region) {
|
|
||||||
setIntegrationApp(`${integration.path} (${integration.region})`);
|
|
||||||
} else if (tempApps.length > 0) {
|
|
||||||
setIntegrationApp(tempApps[0].name);
|
|
||||||
} else {
|
|
||||||
setIntegrationApp("");
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (integration.integration) {
|
|
||||||
case "vercel":
|
|
||||||
setIntegrationTargetEnvironment(
|
|
||||||
integration?.targetEnvironment
|
|
||||||
? integration.targetEnvironment.charAt(0).toUpperCase() +
|
|
||||||
integration.targetEnvironment.substring(1)
|
|
||||||
: "Development"
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case "netlify":
|
|
||||||
setIntegrationTargetEnvironment(
|
|
||||||
integration?.targetEnvironment
|
|
||||||
? contextNetlifyMapping[integration.targetEnvironment]
|
|
||||||
: "Local development"
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
loadIntegration();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleStartIntegration = async () => {
|
|
||||||
const reformatTargetEnvironment = (targetEnvironment: string) => {
|
|
||||||
switch (integration.integration) {
|
|
||||||
case "vercel":
|
|
||||||
return targetEnvironment.toLowerCase();
|
|
||||||
case "netlify":
|
|
||||||
return reverseContextNetlifyMapping[targetEnvironment];
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
const siteApp = apps.find((app) => app.name === integrationApp); // obj or undefined
|
|
||||||
const appId = siteApp?.appId ?? null;
|
|
||||||
const owner = siteApp?.owner ?? null;
|
|
||||||
|
|
||||||
// return updated integration
|
|
||||||
const updatedIntegration = await updateIntegration({
|
|
||||||
integrationId: integration._id,
|
|
||||||
environment: integrationEnvironment.slug,
|
|
||||||
isActive: true,
|
|
||||||
app: integrationApp,
|
|
||||||
appId,
|
|
||||||
targetEnvironment: reformatTargetEnvironment(integrationTargetEnvironment),
|
|
||||||
owner
|
|
||||||
});
|
|
||||||
|
|
||||||
setIntegrations(
|
|
||||||
integrations.map((i) => (i._id === updatedIntegration._id ? updatedIntegration : i))
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
|
||||||
const renderIntegrationSpecificParams = (integration: Integration) => {
|
|
||||||
try {
|
|
||||||
switch (integration.integration) {
|
|
||||||
case "vercel":
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div className="mb-2 w-60 text-xs font-semibold text-gray-400">ENVIRONMENT</div>
|
|
||||||
<ListBox
|
|
||||||
data={!integration.isActive ? ["Development", "Preview", "Production"] : null}
|
|
||||||
isSelected={integrationTargetEnvironment}
|
|
||||||
onChange={setIntegrationTargetEnvironment}
|
|
||||||
isFull
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
case "netlify":
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div className="mb-2 text-xs font-semibold text-gray-400">CONTEXT</div>
|
|
||||||
<ListBox
|
|
||||||
data={
|
|
||||||
!integration.isActive
|
|
||||||
? ["Production", "Deploy previews", "Branch deploys", "Local development"]
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
isSelected={integrationTargetEnvironment}
|
|
||||||
onChange={setIntegrationTargetEnvironment}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
case "railway":
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div className="mb-2 text-xs font-semibold text-gray-400">ENVIRONMENT</div>
|
|
||||||
<ListBox
|
|
||||||
data={
|
|
||||||
!integration.isActive
|
|
||||||
? ["Production", "Deploy previews", "Branch deploys", "Local development"]
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
isSelected={integration.targetEnvironment}
|
|
||||||
onChange={setIntegrationTargetEnvironment}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
case "gitlab":
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div className="mb-2 text-xs font-semibold text-gray-400">ENVIRONMENT</div>
|
|
||||||
<ListBox
|
|
||||||
data={null}
|
|
||||||
isSelected={integration.targetEnvironment}
|
|
||||||
onChange={setIntegrationTargetEnvironment}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
return <div />;
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
return <div />;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!integrationApp && integration.integration !== "checkly") return <div />;
|
|
||||||
|
|
||||||
const isSelected =
|
|
||||||
integration.integration === "hashicorp-vault"
|
|
||||||
? `${integration.app} - path: ${integration.path}`
|
|
||||||
: integrationApp;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="mx-6 mb-8 flex max-w-5xl justify-between rounded-md border border-mineshaft-600 bg-mineshaft-800 p-6">
|
|
||||||
<div className="flex">
|
|
||||||
<div>
|
|
||||||
<p className="mb-2 text-xs font-semibold text-gray-400">ENVIRONMENT</p>
|
|
||||||
<ListBox
|
|
||||||
data={!integration.isActive ? environments.map(({ name }) => name) : null}
|
|
||||||
isSelected={integrationEnvironment.name}
|
|
||||||
onChange={(envName) =>
|
|
||||||
setIntegrationEnvironment(
|
|
||||||
environments.find(({ name }) => envName === name) || {
|
|
||||||
name: "unknown",
|
|
||||||
slug: "unknown"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
isFull
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="ml-2">
|
|
||||||
<p className="mb-2 text-xs font-semibold text-gray-400">SECRET PATH</p>
|
|
||||||
<div className="cursor-default rounded-md bg-white/[.07] py-2.5 pl-4 pr-10 text-sm font-semibold text-gray-300">
|
|
||||||
{/* {integration.integration.charAt(0).toUpperCase() + integration.integration.slice(1)} */}
|
|
||||||
{integration.secretPath}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="pt-2">
|
|
||||||
<FontAwesomeIcon icon={faArrowRight} className="mx-4 mt-8 text-gray-400" />
|
|
||||||
</div>
|
|
||||||
<div className="mr-2">
|
|
||||||
<p className="mb-2 text-xs font-semibold text-gray-400">INTEGRATION</p>
|
|
||||||
<div className="cursor-default rounded-md bg-white/[.07] py-2.5 pl-4 pr-10 text-sm font-semibold text-gray-300">
|
|
||||||
{/* {integration.integration.charAt(0).toUpperCase() + integration.integration.slice(1)} */}
|
|
||||||
{integrationSlugNameMapping[integration.integration]}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mr-2">
|
|
||||||
<div className="mb-2 text-xs font-semibold text-gray-400">APP</div>
|
|
||||||
{integrationApp ? (
|
|
||||||
<div title={integrationApp}>
|
|
||||||
<ListBox
|
|
||||||
data={!integration.isActive ? apps.map((app) => app.name) : null}
|
|
||||||
isSelected={isSelected}
|
|
||||||
onChange={(app) => {
|
|
||||||
setIntegrationApp(app);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="h-10 w-52 animate-pulse rounded-md bg-mineshaft-600 px-4 py-2 font-bold">
|
|
||||||
-
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{renderIntegrationSpecificParams(integration)}
|
|
||||||
</div>
|
|
||||||
<div className="flex cursor-default items-end">
|
|
||||||
{integration.isActive ? (
|
|
||||||
<div className="flex max-w-5xl flex-row items-center rounded-md border border-mineshaft-500 bg-mineshaft-600 p-[0.44rem] px-4">
|
|
||||||
<FontAwesomeIcon icon={faCheck} className="mr-2.5 text-lg text-primary" />
|
|
||||||
<div className="font-semibold text-gray-300">In Sync</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<Button
|
|
||||||
text="Start Integration"
|
|
||||||
onButtonPressed={() => handleStartIntegration()}
|
|
||||||
color="mineshaft"
|
|
||||||
size="md"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<div className="ml-2 opacity-80 duration-200 hover:opacity-100">
|
|
||||||
<Button
|
|
||||||
onButtonPressed={() =>
|
|
||||||
handleDeleteIntegration({
|
|
||||||
integration
|
|
||||||
})
|
|
||||||
}
|
|
||||||
color="red"
|
|
||||||
size="icon-md"
|
|
||||||
icon={faXmark}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default IntegrationTile;
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
import IntegrationTile from "./Integration";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
integrations: any;
|
|
||||||
setIntegrations: any;
|
|
||||||
bot: any;
|
|
||||||
setBot: any;
|
|
||||||
environments: Array<{ name: string; slug: string }>;
|
|
||||||
handleDeleteIntegration: (args: { integration: Integration }) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Integration {
|
|
||||||
_id: string;
|
|
||||||
isActive: boolean;
|
|
||||||
app: string | null;
|
|
||||||
appId: string | null;
|
|
||||||
path: string | null;
|
|
||||||
region: string | null;
|
|
||||||
createdAt: string;
|
|
||||||
updatedAt: string;
|
|
||||||
environment: string;
|
|
||||||
integration: string;
|
|
||||||
targetEnvironment: string;
|
|
||||||
workspace: string;
|
|
||||||
integrationAuth: string;
|
|
||||||
secretPath: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ProjectIntegrationSection = ({
|
|
||||||
integrations,
|
|
||||||
setIntegrations,
|
|
||||||
bot,
|
|
||||||
setBot,
|
|
||||||
environments = [],
|
|
||||||
handleDeleteIntegration
|
|
||||||
}: Props) => {
|
|
||||||
return integrations.length > 0 ? (
|
|
||||||
<div className="mb-12">
|
|
||||||
<div className="mx-4 mb-4 mt-6 flex max-w-5xl flex-col items-start justify-between px-2 text-xl">
|
|
||||||
<h1 className="text-3xl font-semibold">Current Integrations</h1>
|
|
||||||
<p className="text-base text-bunker-300">Manage integrations with third-party services.</p>
|
|
||||||
</div>
|
|
||||||
{integrations.map((integration: Integration) => {
|
|
||||||
return (
|
|
||||||
<IntegrationTile
|
|
||||||
key={`integration-${integration?._id.toString()}`}
|
|
||||||
integration={integration}
|
|
||||||
integrations={integrations}
|
|
||||||
bot={bot}
|
|
||||||
setBot={setBot}
|
|
||||||
setIntegrations={setIntegrations}
|
|
||||||
environments={environments}
|
|
||||||
handleDeleteIntegration={handleDeleteIntegration}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div />
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ProjectIntegrationSection;
|
|
||||||
@@ -3,7 +3,9 @@ import React, { useState } from "react";
|
|||||||
import ReactCodeInput from "react-code-input";
|
import ReactCodeInput from "react-code-input";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import sendVerificationEmail from "@app/pages/api/auth/SendVerificationEmail";
|
import {
|
||||||
|
useSendVerificationEmail
|
||||||
|
} from "@app/hooks/api";
|
||||||
|
|
||||||
import Error from "../basic/Error";
|
import Error from "../basic/Error";
|
||||||
import { Button } from "../v2";
|
import { Button } from "../v2";
|
||||||
@@ -70,6 +72,7 @@ export default function CodeInputStep({
|
|||||||
codeError,
|
codeError,
|
||||||
isCodeInputCheckLoading
|
isCodeInputCheckLoading
|
||||||
}: CodeInputStepProps): JSX.Element {
|
}: CodeInputStepProps): JSX.Element {
|
||||||
|
const { mutateAsync } = useSendVerificationEmail();
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [isResendingVerificationEmail, setIsResendingVerificationEmail] = useState(false);
|
const [isResendingVerificationEmail, setIsResendingVerificationEmail] = useState(false);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -77,7 +80,7 @@ export default function CodeInputStep({
|
|||||||
const resendVerificationEmail = async () => {
|
const resendVerificationEmail = async () => {
|
||||||
setIsResendingVerificationEmail(true);
|
setIsResendingVerificationEmail(true);
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
sendVerificationEmail(email);
|
await mutateAsync({ email });
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
setIsResendingVerificationEmail(false);
|
setIsResendingVerificationEmail(false);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React, { useState } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
import sendVerificationEmail from "@app/pages/api/auth/SendVerificationEmail";
|
import { useSendVerificationEmail } from "@app/hooks/api";
|
||||||
|
|
||||||
import { Button, Input } from "../v2";
|
import { Button, Input } from "../v2";
|
||||||
|
|
||||||
@@ -25,13 +25,14 @@ export default function EnterEmailStep({
|
|||||||
setEmail,
|
setEmail,
|
||||||
incrementStep
|
incrementStep
|
||||||
}: DownloadBackupPDFStepProps): JSX.Element {
|
}: DownloadBackupPDFStepProps): JSX.Element {
|
||||||
|
const { mutateAsync } = useSendVerificationEmail();
|
||||||
const [emailError, setEmailError] = useState(false);
|
const [emailError, setEmailError] = useState(false);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verifies if the entered email "looks" correct
|
* Verifies if the entered email "looks" correct
|
||||||
*/
|
*/
|
||||||
const emailCheck = () => {
|
const emailCheck = async () => {
|
||||||
let emailCheckBool = false;
|
let emailCheckBool = false;
|
||||||
if (!email) {
|
if (!email) {
|
||||||
setEmailError(true);
|
setEmailError(true);
|
||||||
@@ -45,7 +46,7 @@ export default function EnterEmailStep({
|
|||||||
|
|
||||||
// If everything is correct, go to the next step
|
// If everything is correct, go to the next step
|
||||||
if (!emailCheckBool) {
|
if (!emailCheckBool) {
|
||||||
sendVerificationEmail(email);
|
await mutateAsync({ email });
|
||||||
incrementStep();
|
incrementStep();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ import React, { useState } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
|
import { useAddUserToOrg } from "@app/hooks/api";
|
||||||
import { useFetchServerStatus } from "@app/hooks/api/serverDetails";
|
import { useFetchServerStatus } from "@app/hooks/api/serverDetails";
|
||||||
import { usePopUp } from "@app/hooks/usePopUp";
|
import { usePopUp } from "@app/hooks/usePopUp";
|
||||||
import addUserToOrg from "@app/pages/api/organization/addUserToOrg";
|
|
||||||
|
|
||||||
import { Button, EmailServiceSetupModal } from "../v2";
|
import { Button, EmailServiceSetupModal } from "../v2";
|
||||||
|
|
||||||
@@ -12,10 +12,12 @@ import { Button, EmailServiceSetupModal } from "../v2";
|
|||||||
* This is the last step of the signup flow. People can optionally invite their teammates here.
|
* This is the last step of the signup flow. People can optionally invite their teammates here.
|
||||||
*/
|
*/
|
||||||
export default function TeamInviteStep(): JSX.Element {
|
export default function TeamInviteStep(): JSX.Element {
|
||||||
const [emails, setEmails] = useState("");
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const [emails, setEmails] = useState("");
|
||||||
const { data: serverDetails } = useFetchServerStatus();
|
const { data: serverDetails } = useFetchServerStatus();
|
||||||
|
|
||||||
|
const { mutateAsync } = useAddUserToOrg();
|
||||||
const { handlePopUpToggle, popUp, handlePopUpOpen } = usePopUp(["setUpEmail"] as const);
|
const { handlePopUpToggle, popUp, handlePopUpOpen } = usePopUp(["setUpEmail"] as const);
|
||||||
|
|
||||||
// Redirect user to the getting started page
|
// Redirect user to the getting started page
|
||||||
@@ -27,7 +29,12 @@ export default function TeamInviteStep(): JSX.Element {
|
|||||||
inviteEmails
|
inviteEmails
|
||||||
.split(",")
|
.split(",")
|
||||||
.map((email) => email.trim())
|
.map((email) => email.trim())
|
||||||
.map(async (email) => addUserToOrg(email, String(localStorage.getItem("orgData.id"))));
|
.map(async (email) => {
|
||||||
|
mutateAsync({
|
||||||
|
inviteeEmail: email,
|
||||||
|
organizationId: String(localStorage.getItem("orgData.id"))
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
await redirectToHome();
|
await redirectToHome();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import nacl from "tweetnacl";
|
|||||||
import { encodeBase64 } from "tweetnacl-util";
|
import { encodeBase64 } from "tweetnacl-util";
|
||||||
|
|
||||||
import { useGetCommonPasswords } from "@app/hooks/api";
|
import { useGetCommonPasswords } from "@app/hooks/api";
|
||||||
import completeAccountInformationSignup from "@app/pages/api/auth/CompleteAccountInformationSignup";
|
import { completeAccountSignup } from "@app/hooks/api/auth/queries";
|
||||||
import getOrganizations from "@app/pages/api/organization/getOrgs";
|
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
|
||||||
import ProjectService from "@app/services/ProjectService";
|
import ProjectService from "@app/services/ProjectService";
|
||||||
|
|
||||||
import InputField from "../basic/InputField";
|
import InputField from "../basic/InputField";
|
||||||
@@ -159,7 +159,7 @@ export default function UserInfoStep({
|
|||||||
secret: Buffer.from(derivedKey.hash)
|
secret: Buffer.from(derivedKey.hash)
|
||||||
});
|
});
|
||||||
|
|
||||||
const response = await completeAccountInformationSignup({
|
const response = await completeAccountSignup({
|
||||||
email,
|
email,
|
||||||
firstName: name.split(" ")[0],
|
firstName: name.split(" ")[0],
|
||||||
lastName: name.split(" ").slice(1).join(" "),
|
lastName: name.split(" ").slice(1).join(" "),
|
||||||
@@ -190,7 +190,8 @@ export default function UserInfoStep({
|
|||||||
privateKey
|
privateKey
|
||||||
});
|
});
|
||||||
|
|
||||||
const userOrgs = await getOrganizations();
|
const userOrgs = await fetchOrganizations();
|
||||||
|
|
||||||
const orgId = userOrgs[0]?._id;
|
const orgId = userOrgs[0]?._id;
|
||||||
const project = await ProjectService.initProject({
|
const project = await ProjectService.initProject({
|
||||||
organizationId: orgId,
|
organizationId: orgId,
|
||||||
|
|||||||
@@ -3,8 +3,9 @@ import crypto from "crypto";
|
|||||||
|
|
||||||
import jsrp from "jsrp";
|
import jsrp from "jsrp";
|
||||||
|
|
||||||
import changePassword2 from "@app/pages/api/auth/ChangePassword2";
|
import {
|
||||||
import SRP1 from "@app/pages/api/auth/SRP1";
|
changePassword,
|
||||||
|
srp1} from "@app/hooks/api/auth/queries";
|
||||||
|
|
||||||
import Aes256Gcm from "./cryptography/aes-256-gcm";
|
import Aes256Gcm from "./cryptography/aes-256-gcm";
|
||||||
import { deriveArgonKey } from "./cryptography/crypto";
|
import { deriveArgonKey } from "./cryptography/crypto";
|
||||||
@@ -27,7 +28,7 @@ const attemptChangePassword = ({ email, currentPassword, newPassword }: Params):
|
|||||||
try {
|
try {
|
||||||
const clientPublicKey = clientOldPassword.getPublicKey();
|
const clientPublicKey = clientOldPassword.getPublicKey();
|
||||||
|
|
||||||
const res = await SRP1({ clientPublicKey });
|
const res = await srp1({ clientPublicKey });
|
||||||
|
|
||||||
serverPublicKey = res.serverPublicKey;
|
serverPublicKey = res.serverPublicKey;
|
||||||
salt = res.salt;
|
salt = res.salt;
|
||||||
@@ -71,7 +72,7 @@ const attemptChangePassword = ({ email, currentPassword, newPassword }: Params):
|
|||||||
secret: Buffer.from(derivedKey.hash)
|
secret: Buffer.from(derivedKey.hash)
|
||||||
});
|
});
|
||||||
|
|
||||||
await changePassword2({
|
await changePassword({
|
||||||
clientProof,
|
clientProof,
|
||||||
protectedKey,
|
protectedKey,
|
||||||
protectedKeyIV,
|
protectedKeyIV,
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
/* eslint-disable prefer-destructuring */
|
/* eslint-disable prefer-destructuring */
|
||||||
import jsrp from "jsrp";
|
import jsrp from "jsrp";
|
||||||
|
|
||||||
import login1 from "@app/pages/api/auth/Login1";
|
import { login1, login2 } from "@app/hooks/api/auth/queries";
|
||||||
import login2 from "@app/pages/api/auth/Login2";
|
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
|
||||||
import getOrganizations from "@app/pages/api/organization/getOrgs";
|
import { fetchMyOrganizationProjects } from "@app/hooks/api/users/queries";
|
||||||
import getOrganizationUserProjects from "@app/pages/api/organization/GetOrgUserProjects";
|
|
||||||
import KeyService from "@app/services/KeyService";
|
import KeyService from "@app/services/KeyService";
|
||||||
|
|
||||||
import Telemetry from "./telemetry/Telemetry";
|
import Telemetry from "./telemetry/Telemetry";
|
||||||
@@ -125,13 +124,11 @@ const attemptLogin = async (
|
|||||||
privateKey
|
privateKey
|
||||||
});
|
});
|
||||||
|
|
||||||
const userOrgs = await getOrganizations();
|
const userOrgs = await fetchOrganizations();
|
||||||
const orgId = userOrgs[0]._id;
|
const orgId = userOrgs[0]._id;
|
||||||
localStorage.setItem("orgData.id", orgId);
|
localStorage.setItem("orgData.id", orgId);
|
||||||
|
|
||||||
const orgUserProjects = await getOrganizationUserProjects({
|
const orgUserProjects = await fetchMyOrganizationProjects(orgId);
|
||||||
orgId
|
|
||||||
});
|
|
||||||
|
|
||||||
if (orgUserProjects.length > 0) {
|
if (orgUserProjects.length > 0) {
|
||||||
localStorage.setItem("projectData.id", orgUserProjects[0]._id);
|
localStorage.setItem("projectData.id", orgUserProjects[0]._id);
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
/* eslint-disable prefer-destructuring */
|
/* eslint-disable prefer-destructuring */
|
||||||
import jsrp from "jsrp";
|
import jsrp from "jsrp";
|
||||||
|
|
||||||
import login1 from "@app/pages/api/auth/Login1";
|
import { login1 , verifyMfaToken } from "@app/hooks/api/auth/queries";
|
||||||
import verifyMfaToken from "@app/pages/api/auth/verifyMfaToken";
|
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
|
||||||
import getOrganizations from "@app/pages/api/organization/getOrgs";
|
import { fetchMyOrganizationProjects } from "@app/hooks/api/users/queries";
|
||||||
import getOrganizationUserProjects from "@app/pages/api/organization/GetOrgUserProjects";
|
// import verifyMfaToken from "@app/pages/api/auth/verifyMfaToken";
|
||||||
import KeyService from "@app/services/KeyService";
|
import KeyService from "@app/services/KeyService";
|
||||||
|
|
||||||
import { saveTokenToLocalStorage } from "./saveTokenToLocalStorage";
|
import { saveTokenToLocalStorage } from "./saveTokenToLocalStorage";
|
||||||
@@ -65,7 +65,7 @@ const attemptLoginMfa = async ({
|
|||||||
tag
|
tag
|
||||||
} = await verifyMfaToken({
|
} = await verifyMfaToken({
|
||||||
email,
|
email,
|
||||||
mfaToken
|
mfaCode: mfaToken
|
||||||
});
|
});
|
||||||
|
|
||||||
// unset temporary (MFA) JWT token and set JWT token
|
// unset temporary (MFA) JWT token and set JWT token
|
||||||
@@ -96,13 +96,11 @@ const attemptLoginMfa = async ({
|
|||||||
// TODO: in the future - move this logic elsewhere
|
// TODO: in the future - move this logic elsewhere
|
||||||
// because this function is about logging the user in
|
// because this function is about logging the user in
|
||||||
// and not initializing the login details
|
// and not initializing the login details
|
||||||
const userOrgs = await getOrganizations();
|
const userOrgs = await fetchOrganizations();
|
||||||
const orgId = userOrgs[0]._id;
|
const orgId = userOrgs[0]._id;
|
||||||
localStorage.setItem("orgData.id", orgId);
|
localStorage.setItem("orgData.id", orgId);
|
||||||
|
|
||||||
const orgUserProjects = await getOrganizationUserProjects({
|
const orgUserProjects = await fetchMyOrganizationProjects(orgId);
|
||||||
orgId
|
|
||||||
});
|
|
||||||
localStorage.setItem("projectData.id", orgUserProjects[0]._id);
|
localStorage.setItem("projectData.id", orgUserProjects[0]._id);
|
||||||
|
|
||||||
resolve({
|
resolve({
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
/* eslint-disable prefer-destructuring */
|
/* eslint-disable prefer-destructuring */
|
||||||
import jsrp from "jsrp";
|
import jsrp from "jsrp";
|
||||||
|
|
||||||
import login1 from "@app/pages/api/auth/Login1";
|
import { login1, login2 } from "@app/hooks/api/auth/queries";
|
||||||
import login2 from "@app/pages/api/auth/Login2";
|
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
|
||||||
import getOrganizations from "@app/pages/api/organization/getOrgs";
|
import { fetchMyOrganizationProjects } from "@app/hooks/api/users/queries";
|
||||||
import getOrganizationUserProjects from "@app/pages/api/organization/GetOrgUserProjects";
|
|
||||||
import KeyService from "@app/services/KeyService";
|
import KeyService from "@app/services/KeyService";
|
||||||
|
|
||||||
import Telemetry from "./telemetry/Telemetry";
|
import Telemetry from "./telemetry/Telemetry";
|
||||||
@@ -36,7 +35,6 @@ const attemptLogin = async (
|
|||||||
providerAuthToken?: string;
|
providerAuthToken?: string;
|
||||||
}
|
}
|
||||||
): Promise<IsLoginSuccessful> => {
|
): Promise<IsLoginSuccessful> => {
|
||||||
|
|
||||||
const telemetry = new Telemetry().getInstance();
|
const telemetry = new Telemetry().getInstance();
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
client.init(
|
client.init(
|
||||||
@@ -47,6 +45,7 @@ const attemptLogin = async (
|
|||||||
async () => {
|
async () => {
|
||||||
try {
|
try {
|
||||||
const clientPublicKey = client.getPublicKey();
|
const clientPublicKey = client.getPublicKey();
|
||||||
|
|
||||||
const { serverPublicKey, salt } = await login1({
|
const { serverPublicKey, salt } = await login1({
|
||||||
email,
|
email,
|
||||||
clientPublicKey,
|
clientPublicKey,
|
||||||
@@ -124,14 +123,12 @@ const attemptLogin = async (
|
|||||||
// TODO: in the future - move this logic elsewhere
|
// TODO: in the future - move this logic elsewhere
|
||||||
// because this function is about logging the user in
|
// because this function is about logging the user in
|
||||||
// and not initializing the login details
|
// and not initializing the login details
|
||||||
const userOrgs = await getOrganizations();
|
const userOrgs = await fetchOrganizations();
|
||||||
|
|
||||||
const orgId = userOrgs[0]._id;
|
const orgId = userOrgs[0]._id;
|
||||||
localStorage.setItem("orgData.id", orgId);
|
localStorage.setItem("orgData.id", orgId);
|
||||||
|
|
||||||
const orgUserProjects = await getOrganizationUserProjects({
|
const orgUserProjects = await fetchMyOrganizationProjects(orgId);
|
||||||
orgId
|
|
||||||
});
|
|
||||||
|
|
||||||
if (orgUserProjects.length > 0) {
|
if (orgUserProjects.length > 0) {
|
||||||
localStorage.setItem("projectData.id", orgUserProjects[0]._id);
|
localStorage.setItem("projectData.id", orgUserProjects[0]._id);
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
/* eslint-disable prefer-destructuring */
|
/* eslint-disable prefer-destructuring */
|
||||||
import jsrp from "jsrp";
|
import jsrp from "jsrp";
|
||||||
|
|
||||||
import login1 from "@app/pages/api/auth/Login1";
|
import { login1 , verifyMfaToken } from "@app/hooks/api/auth/queries";
|
||||||
import verifyMfaToken from "@app/pages/api/auth/verifyMfaToken";
|
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
|
||||||
import getOrganizations from "@app/pages/api/organization/getOrgs";
|
import { fetchMyOrganizationProjects } from "@app/hooks/api/users/queries";
|
||||||
import getOrganizationUserProjects from "@app/pages/api/organization/GetOrgUserProjects";
|
|
||||||
import KeyService from "@app/services/KeyService";
|
import KeyService from "@app/services/KeyService";
|
||||||
|
|
||||||
import { saveTokenToLocalStorage } from "./saveTokenToLocalStorage";
|
import { saveTokenToLocalStorage } from "./saveTokenToLocalStorage";
|
||||||
@@ -56,7 +55,7 @@ const attemptLoginMfa = async ({
|
|||||||
tag
|
tag
|
||||||
} = await verifyMfaToken({
|
} = await verifyMfaToken({
|
||||||
email,
|
email,
|
||||||
mfaToken
|
mfaCode: mfaToken
|
||||||
});
|
});
|
||||||
|
|
||||||
// unset temporary (MFA) JWT token and set JWT token
|
// unset temporary (MFA) JWT token and set JWT token
|
||||||
@@ -87,13 +86,11 @@ const attemptLoginMfa = async ({
|
|||||||
// TODO: in the future - move this logic elsewhere
|
// TODO: in the future - move this logic elsewhere
|
||||||
// because this function is about logging the user in
|
// because this function is about logging the user in
|
||||||
// and not initializing the login details
|
// and not initializing the login details
|
||||||
const userOrgs = await getOrganizations();
|
const userOrgs = await fetchOrganizations();
|
||||||
const orgId = userOrgs[0]._id;
|
const orgId = userOrgs[0]._id;
|
||||||
localStorage.setItem("orgData.id", orgId);
|
localStorage.setItem("orgData.id", orgId);
|
||||||
|
|
||||||
const orgUserProjects = await getOrganizationUserProjects({
|
const orgUserProjects = await fetchMyOrganizationProjects(orgId);
|
||||||
orgId
|
|
||||||
});
|
|
||||||
localStorage.setItem("projectData.id", orgUserProjects[0]._id);
|
localStorage.setItem("projectData.id", orgUserProjects[0]._id);
|
||||||
|
|
||||||
resolve(true);
|
resolve(true);
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import getOrganizationUsers from "@app/pages/api/organization/GetOrgUsers";
|
import { fetchOrgUsers,fetchUserAction } from "@app/hooks/api/users/queries";
|
||||||
import checkUserAction from "@app/pages/api/userActions/checkUserAction";
|
|
||||||
|
|
||||||
interface OnboardingCheckProps {
|
interface OnboardingCheckProps {
|
||||||
setTotalOnboardingActionsDone?: (value: number) => void;
|
setTotalOnboardingActionsDone?: (value: number) => void;
|
||||||
@@ -20,34 +19,31 @@ const onboardingCheck = async ({
|
|||||||
setUsersInOrg
|
setUsersInOrg
|
||||||
}: OnboardingCheckProps) => {
|
}: OnboardingCheckProps) => {
|
||||||
let countActions = 0;
|
let countActions = 0;
|
||||||
const userActionSlack = await checkUserAction({
|
const userActionSlack = await fetchUserAction(
|
||||||
action: "slack_cta_clicked"
|
"slack_cta_clicked"
|
||||||
});
|
);
|
||||||
|
|
||||||
if (userActionSlack) {
|
if (userActionSlack) {
|
||||||
countActions += 1;
|
countActions += 1;
|
||||||
}
|
}
|
||||||
if (setHasUserClickedSlack) setHasUserClickedSlack(!!userActionSlack);
|
if (setHasUserClickedSlack) setHasUserClickedSlack(!!userActionSlack);
|
||||||
|
|
||||||
const userActionSecrets = await checkUserAction({
|
const userActionSecrets = await fetchUserAction("first_time_secrets_pushed");
|
||||||
action: "first_time_secrets_pushed"
|
|
||||||
});
|
|
||||||
if (userActionSecrets) {
|
if (userActionSecrets) {
|
||||||
countActions += 1;
|
countActions += 1;
|
||||||
}
|
}
|
||||||
if (setHasUserPushedSecrets) setHasUserPushedSecrets(!!userActionSecrets);
|
if (setHasUserPushedSecrets) setHasUserPushedSecrets(!!userActionSecrets);
|
||||||
|
|
||||||
const userActionIntro = await checkUserAction({
|
const userActionIntro = await fetchUserAction("intro_cta_clicked");
|
||||||
action: "intro_cta_clicked"
|
|
||||||
});
|
|
||||||
if (userActionIntro) {
|
if (userActionIntro) {
|
||||||
countActions += 1;
|
countActions += 1;
|
||||||
}
|
}
|
||||||
if (setHasUserClickedIntro) setHasUserClickedIntro(!!userActionIntro);
|
if (setHasUserClickedIntro) setHasUserClickedIntro(!!userActionIntro);
|
||||||
|
|
||||||
const orgId = localStorage.getItem("orgData.id");
|
const orgId = localStorage.getItem("orgData.id");
|
||||||
const orgUsers = await getOrganizationUsers({
|
const orgUsers = await fetchOrgUsers(orgId || "");
|
||||||
orgId: orgId || ""
|
|
||||||
});
|
|
||||||
if (orgUsers.length > 1) {
|
if (orgUsers.length > 1) {
|
||||||
countActions += 1;
|
countActions += 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,147 +0,0 @@
|
|||||||
/* eslint-disable new-cap */
|
|
||||||
import crypto from "crypto";
|
|
||||||
|
|
||||||
import jsrp from "jsrp";
|
|
||||||
|
|
||||||
import changePassword2 from "@app/pages/api/auth/ChangePassword2";
|
|
||||||
import SRP1 from "@app/pages/api/auth/SRP1";
|
|
||||||
|
|
||||||
import { saveTokenToLocalStorage } from "../saveTokenToLocalStorage";
|
|
||||||
import Aes256Gcm from "./aes-256-gcm";
|
|
||||||
import { deriveArgonKey } from "./crypto";
|
|
||||||
|
|
||||||
const clientOldPassword = new jsrp.client();
|
|
||||||
const clientNewPassword = new jsrp.client();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function loggs in the user (whether it's right after signup, or a normal login)
|
|
||||||
* @param {*} email
|
|
||||||
* @param {*} password
|
|
||||||
* @param {*} setErrorLogin
|
|
||||||
* @param {*} router
|
|
||||||
* @param {*} isSignUp
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const changePassword = async (
|
|
||||||
email: string,
|
|
||||||
currentPassword: string,
|
|
||||||
newPassword: string,
|
|
||||||
setCurrentPasswordError: (arg: boolean) => void,
|
|
||||||
setPasswordChanged: (arg: boolean) => void,
|
|
||||||
setCurrentPassword: (arg: string) => void,
|
|
||||||
setNewPassword: (arg: string) => void
|
|
||||||
) => {
|
|
||||||
try {
|
|
||||||
setPasswordChanged(false);
|
|
||||||
setCurrentPasswordError(false);
|
|
||||||
|
|
||||||
clientOldPassword.init(
|
|
||||||
{
|
|
||||||
username: email,
|
|
||||||
password: currentPassword
|
|
||||||
},
|
|
||||||
async () => {
|
|
||||||
const clientPublicKey = clientOldPassword.getPublicKey();
|
|
||||||
|
|
||||||
let serverPublicKey;
|
|
||||||
let salt;
|
|
||||||
try {
|
|
||||||
const res = await SRP1({
|
|
||||||
clientPublicKey
|
|
||||||
});
|
|
||||||
serverPublicKey = res.serverPublicKey;
|
|
||||||
salt = res.salt;
|
|
||||||
} catch (err) {
|
|
||||||
setCurrentPasswordError(true);
|
|
||||||
console.log("Wrong current password", err, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
clientOldPassword.setSalt(salt);
|
|
||||||
clientOldPassword.setServerPublicKey(serverPublicKey);
|
|
||||||
const clientProof = clientOldPassword.getProof(); // called M1
|
|
||||||
|
|
||||||
clientNewPassword.init(
|
|
||||||
{
|
|
||||||
username: email,
|
|
||||||
password: newPassword
|
|
||||||
},
|
|
||||||
async () => {
|
|
||||||
clientNewPassword.createVerifier(async (err, result) => {
|
|
||||||
|
|
||||||
const derivedKey = await deriveArgonKey({
|
|
||||||
password: newPassword,
|
|
||||||
salt: result.salt,
|
|
||||||
mem: 65536,
|
|
||||||
time: 3,
|
|
||||||
parallelism: 1,
|
|
||||||
hashLen: 32
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!derivedKey) throw new Error("Failed to derive key from password");
|
|
||||||
|
|
||||||
const key = crypto.randomBytes(32);
|
|
||||||
|
|
||||||
// create encrypted private key by encrypting the private
|
|
||||||
// key with the symmetric key [key]
|
|
||||||
const {
|
|
||||||
ciphertext: encryptedPrivateKey,
|
|
||||||
iv: encryptedPrivateKeyIV,
|
|
||||||
tag: encryptedPrivateKeyTag
|
|
||||||
} = Aes256Gcm.encrypt({
|
|
||||||
text: localStorage.getItem("PRIVATE_KEY") as string,
|
|
||||||
secret: key
|
|
||||||
});
|
|
||||||
|
|
||||||
// create the protected key by encrypting the symmetric key
|
|
||||||
// [key] with the derived key
|
|
||||||
const {
|
|
||||||
ciphertext: protectedKey,
|
|
||||||
iv: protectedKeyIV,
|
|
||||||
tag: protectedKeyTag
|
|
||||||
} = Aes256Gcm.encrypt({
|
|
||||||
text: key.toString("hex"),
|
|
||||||
secret: Buffer.from(derivedKey.hash)
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
await changePassword2({
|
|
||||||
clientProof,
|
|
||||||
protectedKey,
|
|
||||||
protectedKeyIV,
|
|
||||||
protectedKeyTag,
|
|
||||||
encryptedPrivateKey,
|
|
||||||
encryptedPrivateKeyIV,
|
|
||||||
encryptedPrivateKeyTag,
|
|
||||||
salt: result.salt,
|
|
||||||
verifier: result.verifier
|
|
||||||
});
|
|
||||||
|
|
||||||
saveTokenToLocalStorage({
|
|
||||||
encryptedPrivateKey,
|
|
||||||
iv: encryptedPrivateKeyIV,
|
|
||||||
tag: encryptedPrivateKeyTag
|
|
||||||
});
|
|
||||||
|
|
||||||
setPasswordChanged(true);
|
|
||||||
setCurrentPassword("");
|
|
||||||
setNewPassword("");
|
|
||||||
|
|
||||||
window.location.href = "/login";
|
|
||||||
|
|
||||||
// move to login page
|
|
||||||
} catch (error) {
|
|
||||||
setCurrentPasswordError(true);
|
|
||||||
console.log(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
console.log("Something went wrong during changing the password");
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default changePassword;
|
|
||||||
@@ -3,8 +3,9 @@ import crypto from "crypto";
|
|||||||
|
|
||||||
import jsrp from "jsrp";
|
import jsrp from "jsrp";
|
||||||
|
|
||||||
import issueBackupPrivateKey from "@app/pages/api/auth/IssueBackupPrivateKey";
|
import { issueBackupPrivateKey ,
|
||||||
import SRP1 from "@app/pages/api/auth/SRP1";
|
srp1
|
||||||
|
} from "@app/hooks/api/auth/queries";
|
||||||
|
|
||||||
import generateBackupPDF from "../generateBackupPDF";
|
import generateBackupPDF from "../generateBackupPDF";
|
||||||
import Aes256Gcm from "./aes-256-gcm";
|
import Aes256Gcm from "./aes-256-gcm";
|
||||||
@@ -51,7 +52,7 @@ const issueBackupKey = async ({
|
|||||||
let serverPublicKey;
|
let serverPublicKey;
|
||||||
let salt;
|
let salt;
|
||||||
try {
|
try {
|
||||||
const res = await SRP1({
|
const res = await srp1({
|
||||||
clientPublicKey
|
clientPublicKey
|
||||||
});
|
});
|
||||||
serverPublicKey = res.serverPublicKey;
|
serverPublicKey = res.serverPublicKey;
|
||||||
@@ -61,8 +62,8 @@ const issueBackupKey = async ({
|
|||||||
console.log("Wrong current password", err, 1);
|
console.log("Wrong current password", err, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
clientPassword.setSalt(salt);
|
clientPassword.setSalt(salt as string);
|
||||||
clientPassword.setServerPublicKey(serverPublicKey);
|
clientPassword.setServerPublicKey(serverPublicKey as string);
|
||||||
const clientProof = clientPassword.getProof(); // called M1
|
const clientProof = clientPassword.getProof(); // called M1
|
||||||
|
|
||||||
const generatedKey = crypto.randomBytes(16).toString("hex");
|
const generatedKey = crypto.randomBytes(16).toString("hex");
|
||||||
@@ -80,7 +81,8 @@ const issueBackupKey = async ({
|
|||||||
secret: generatedKey
|
secret: generatedKey
|
||||||
});
|
});
|
||||||
|
|
||||||
const res = await issueBackupPrivateKey({
|
try {
|
||||||
|
await issueBackupPrivateKey({
|
||||||
encryptedPrivateKey: ciphertext,
|
encryptedPrivateKey: ciphertext,
|
||||||
iv,
|
iv,
|
||||||
tag,
|
tag,
|
||||||
@@ -89,15 +91,15 @@ const issueBackupKey = async ({
|
|||||||
clientProof
|
clientProof
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res?.status === 400) {
|
|
||||||
setBackupKeyError(true);
|
|
||||||
} else if (res?.status === 200) {
|
|
||||||
generateBackupPDF({
|
generateBackupPDF({
|
||||||
personalName,
|
personalName,
|
||||||
personalEmail: email,
|
personalEmail: email,
|
||||||
generatedKey
|
generatedKey
|
||||||
});
|
});
|
||||||
setBackupKeyIssued(true);
|
setBackupKeyIssued(true);
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
setBackupKeyError(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import crypto from "crypto";
|
|||||||
|
|
||||||
import { SecretDataProps, Tag } from "public/data/frequentInterfaces";
|
import { SecretDataProps, Tag } from "public/data/frequentInterfaces";
|
||||||
|
|
||||||
import getLatestFileKey from "@app/pages/api/workspace/getLatestFileKey";
|
import { fetchUserWsKey } from "@app/hooks/api/keys/queries";
|
||||||
|
|
||||||
import { decryptAssymmetric, encryptSymmetric } from "../cryptography/crypto";
|
import { decryptAssymmetric, encryptSymmetric } from "../cryptography/crypto";
|
||||||
|
|
||||||
@@ -42,19 +42,21 @@ const encryptSecrets = async ({
|
|||||||
}) => {
|
}) => {
|
||||||
let secrets;
|
let secrets;
|
||||||
try {
|
try {
|
||||||
const sharedKey = await getLatestFileKey({ workspaceId });
|
// const sharedKey = await getLatestFileKey({ workspaceId });
|
||||||
|
const wsKey = await fetchUserWsKey(workspaceId);
|
||||||
|
|
||||||
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string;
|
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string;
|
||||||
|
|
||||||
let randomBytes: string;
|
let randomBytes: string;
|
||||||
if (Object.keys(sharedKey).length > 0) {
|
if (wsKey) {
|
||||||
// case: a (shared) key exists for the workspace
|
// case: a (shared) key exists for the workspace
|
||||||
randomBytes = decryptAssymmetric({
|
randomBytes = decryptAssymmetric({
|
||||||
ciphertext: sharedKey.latestKey.encryptedKey,
|
ciphertext: wsKey.encryptedKey,
|
||||||
nonce: sharedKey.latestKey.nonce,
|
nonce: wsKey.nonce,
|
||||||
publicKey: sharedKey.latestKey.sender.publicKey,
|
publicKey: wsKey.sender.publicKey,
|
||||||
privateKey: PRIVATE_KEY
|
privateKey: PRIVATE_KEY
|
||||||
});
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// case: a (shared) key does not exist for the workspace
|
// case: a (shared) key does not exist for the workspace
|
||||||
randomBytes = crypto.randomBytes(16).toString("hex");
|
randomBytes = crypto.randomBytes(16).toString("hex");
|
||||||
@@ -114,6 +116,7 @@ const encryptSecrets = async ({
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error while encrypting secrets");
|
console.log("Error while encrypting secrets");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,160 +0,0 @@
|
|||||||
import { Tag } from "public/data/frequentInterfaces";
|
|
||||||
|
|
||||||
import getSecrets from "@app/pages/api/files/GetSecrets";
|
|
||||||
import getLatestFileKey from "@app/pages/api/workspace/getLatestFileKey";
|
|
||||||
|
|
||||||
import { decryptAssymmetric, decryptSymmetric } from "../cryptography/crypto";
|
|
||||||
|
|
||||||
interface EncryptedSecretProps {
|
|
||||||
_id: string;
|
|
||||||
createdAt: string;
|
|
||||||
environment: string;
|
|
||||||
secretCommentCiphertext: string;
|
|
||||||
secretCommentIV: string;
|
|
||||||
secretCommentTag: string;
|
|
||||||
secretKeyCiphertext: string;
|
|
||||||
secretKeyIV: string;
|
|
||||||
secretKeyTag: string;
|
|
||||||
secretValueCiphertext: string;
|
|
||||||
secretValueIV: string;
|
|
||||||
secretValueTag: string;
|
|
||||||
type: "personal" | "shared";
|
|
||||||
tags: Tag[];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SecretProps {
|
|
||||||
key: string;
|
|
||||||
value: string | undefined;
|
|
||||||
type: "personal" | "shared";
|
|
||||||
comment: string;
|
|
||||||
id: string;
|
|
||||||
tags: Tag[];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface FunctionProps {
|
|
||||||
env: string;
|
|
||||||
setIsKeyAvailable?: any;
|
|
||||||
setData?: any;
|
|
||||||
workspaceId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the secrets for a certain project
|
|
||||||
* @param {object} obj
|
|
||||||
* @param {string} obj.env - environment for which we are getting secrets
|
|
||||||
* @param {boolean} obj.isKeyAvailable - if a person is able to create new key pairs
|
|
||||||
* @param {function} obj.setData - state function that manages the state of secrets in the dashboard
|
|
||||||
* @param {string} obj.workspaceId - id of a workspace for which we are getting secrets
|
|
||||||
*/
|
|
||||||
const getSecretsForProject = async ({
|
|
||||||
env,
|
|
||||||
setIsKeyAvailable,
|
|
||||||
setData,
|
|
||||||
workspaceId
|
|
||||||
}: FunctionProps) => {
|
|
||||||
try {
|
|
||||||
let encryptedSecrets;
|
|
||||||
try {
|
|
||||||
encryptedSecrets = await getSecrets(workspaceId, env);
|
|
||||||
} catch (error) {
|
|
||||||
console.log("ERROR: Not able to access the latest version of secrets");
|
|
||||||
}
|
|
||||||
|
|
||||||
const latestKey = await getLatestFileKey({ workspaceId });
|
|
||||||
// This is called isKeyAvailable but what it really means is if a person is able to create new key pairs
|
|
||||||
if (typeof setIsKeyAvailable === "function") {
|
|
||||||
setIsKeyAvailable(!latestKey ? encryptedSecrets.length === 0 : true);
|
|
||||||
}
|
|
||||||
|
|
||||||
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string;
|
|
||||||
|
|
||||||
const tempDecryptedSecrets: SecretProps[] = [];
|
|
||||||
if (latestKey) {
|
|
||||||
// assymmetrically decrypt symmetric key with local private key
|
|
||||||
const key = decryptAssymmetric({
|
|
||||||
ciphertext: latestKey.latestKey.encryptedKey,
|
|
||||||
nonce: latestKey.latestKey.nonce,
|
|
||||||
publicKey: latestKey.latestKey.sender.publicKey,
|
|
||||||
privateKey: PRIVATE_KEY
|
|
||||||
});
|
|
||||||
|
|
||||||
// decrypt secret keys, values, and comments
|
|
||||||
encryptedSecrets.forEach((secret: EncryptedSecretProps) => {
|
|
||||||
const plainTextKey = decryptSymmetric({
|
|
||||||
ciphertext: secret.secretKeyCiphertext,
|
|
||||||
iv: secret.secretKeyIV,
|
|
||||||
tag: secret.secretKeyTag,
|
|
||||||
key
|
|
||||||
});
|
|
||||||
|
|
||||||
let plainTextValue;
|
|
||||||
if (secret.secretValueCiphertext !== undefined) {
|
|
||||||
plainTextValue = decryptSymmetric({
|
|
||||||
ciphertext: secret.secretValueCiphertext,
|
|
||||||
iv: secret.secretValueIV,
|
|
||||||
tag: secret.secretValueTag,
|
|
||||||
key
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
plainTextValue = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
let plainTextComment;
|
|
||||||
if (secret.secretCommentCiphertext) {
|
|
||||||
plainTextComment = decryptSymmetric({
|
|
||||||
ciphertext: secret.secretCommentCiphertext,
|
|
||||||
iv: secret.secretCommentIV,
|
|
||||||
tag: secret.secretCommentTag,
|
|
||||||
key
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
plainTextComment = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
tempDecryptedSecrets.push({
|
|
||||||
id: secret._id,
|
|
||||||
key: plainTextKey,
|
|
||||||
value: plainTextValue,
|
|
||||||
type: secret.type,
|
|
||||||
comment: plainTextComment,
|
|
||||||
tags: secret.tags
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const secretKeys = [...new Set(tempDecryptedSecrets.map((secret) => secret.key))];
|
|
||||||
|
|
||||||
const result = secretKeys.map((key, index) => ({
|
|
||||||
id: tempDecryptedSecrets.filter((secret) => secret.key === key && secret.type === "shared")[0]
|
|
||||||
?.id,
|
|
||||||
idOverride: tempDecryptedSecrets.filter(
|
|
||||||
(secret) => secret.key === key && secret.type === "personal"
|
|
||||||
)[0]?.id,
|
|
||||||
pos: index,
|
|
||||||
key,
|
|
||||||
value: tempDecryptedSecrets.filter(
|
|
||||||
(secret) => secret.key === key && secret.type === "shared"
|
|
||||||
)[0]?.value,
|
|
||||||
valueOverride: tempDecryptedSecrets.filter(
|
|
||||||
(secret) => secret.key === key && secret.type === "personal"
|
|
||||||
)[0]?.value,
|
|
||||||
comment: tempDecryptedSecrets.filter(
|
|
||||||
(secret) => secret.key === key && secret.type === "shared"
|
|
||||||
)[0]?.comment,
|
|
||||||
tags: tempDecryptedSecrets.filter(
|
|
||||||
(secret) => secret.key === key && secret.type === "shared"
|
|
||||||
)[0]?.tags
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (typeof setData === "function") {
|
|
||||||
setData(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
} catch (error) {
|
|
||||||
console.log("Something went wrong during accessing or decripting secrets.");
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
};
|
|
||||||
|
|
||||||
export default getSecretsForProject;
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { createContext, ReactNode, useContext, useMemo } from "react";
|
import { createContext, ReactNode, useContext, useMemo } from "react";
|
||||||
|
|
||||||
import { useGetOrganization } from "@app/hooks/api";
|
import { useGetOrganizations } from "@app/hooks/api";
|
||||||
import { Organization } from "@app/hooks/api/types";
|
import { Organization } from "@app/hooks/api/types";
|
||||||
|
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const OrgProvider = ({ children }: Props): JSX.Element => {
|
export const OrgProvider = ({ children }: Props): JSX.Element => {
|
||||||
const { data: userOrgs, isLoading } = useGetOrganization();
|
const { data: userOrgs, isLoading } = useGetOrganizations();
|
||||||
|
|
||||||
// const currentWsOrgID = currentWorkspace?.organization;
|
// const currentWsOrgID = currentWorkspace?.organization;
|
||||||
const currentWsOrgID = localStorage.getItem("orgData.id");
|
const currentWsOrgID = localStorage.getItem("orgData.id");
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|||||||
|
|
||||||
import getActionData from "@app/ee/api/secrets/GetActionData";
|
import getActionData from "@app/ee/api/secrets/GetActionData";
|
||||||
import patienceDiff from "@app/ee/utilities/findTextDifferences";
|
import patienceDiff from "@app/ee/utilities/findTextDifferences";
|
||||||
import getLatestFileKey from "@app/pages/api/workspace/getLatestFileKey";
|
import {
|
||||||
|
useGetUserWsKey
|
||||||
|
} from "@app/hooks/api";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
decryptAssymmetric,
|
decryptAssymmetric,
|
||||||
@@ -59,25 +61,24 @@ const ActivitySideBar = ({ toggleSidebar, currentAction }: SideBarProps) => {
|
|||||||
const [actionData, setActionData] = useState<DecryptedSecretProps[]>();
|
const [actionData, setActionData] = useState<DecryptedSecretProps[]>();
|
||||||
const [actionMetaData, setActionMetaData] = useState<ActionProps>();
|
const [actionMetaData, setActionMetaData] = useState<ActionProps>();
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const { data: wsKey } = useGetUserWsKey(String(router.query.id));
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const getLogData = async () => {
|
const getLogData = async () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
const tempActionData = await getActionData({ actionId: currentAction });
|
const tempActionData = await getActionData({ actionId: currentAction });
|
||||||
const latestKey = await getLatestFileKey({ workspaceId: String(router.query.id) });
|
|
||||||
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
|
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
|
||||||
|
|
||||||
// #TODO: make this a separate function and reuse across the app
|
// #TODO: make this a separate function and reuse across the app
|
||||||
let decryptedLatestKey: string;
|
let decryptedLatestKey: string;
|
||||||
if (latestKey) {
|
if (wsKey) {
|
||||||
// assymmetrically decrypt symmetric key with local private key
|
// assymmetrically decrypt symmetric key with local private key
|
||||||
decryptedLatestKey = decryptAssymmetric({
|
decryptedLatestKey = decryptAssymmetric({
|
||||||
ciphertext: latestKey.latestKey.encryptedKey,
|
ciphertext: wsKey.encryptedKey,
|
||||||
nonce: latestKey.latestKey.nonce,
|
nonce: wsKey.nonce,
|
||||||
publicKey: latestKey.latestKey.sender.publicKey,
|
publicKey: wsKey.sender.publicKey,
|
||||||
privateKey: String(PRIVATE_KEY)
|
privateKey: String(PRIVATE_KEY)
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
const decryptedSecretVersions = tempActionData.payload.secretVersions.map(
|
const decryptedSecretVersions = tempActionData.payload.secretVersions.map(
|
||||||
(encryptedSecretVersion: {
|
(encryptedSecretVersion: {
|
||||||
@@ -122,9 +123,10 @@ const ActivitySideBar = ({ toggleSidebar, currentAction }: SideBarProps) => {
|
|||||||
setActionData(decryptedSecretVersions);
|
setActionData(decryptedSecretVersions);
|
||||||
setActionMetaData({ name: tempActionData.name });
|
setActionMetaData({ name: tempActionData.name });
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
getLogData();
|
getLogData();
|
||||||
}, [currentAction]);
|
}, [currentAction, wsKey]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -1,287 +0,0 @@
|
|||||||
/* eslint-disable no-nested-ternary */
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import Image from "next/image";
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import { faXmark } from "@fortawesome/free-solid-svg-icons";
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
||||||
import { Tag } from "public/data/frequentInterfaces";
|
|
||||||
|
|
||||||
import Button from "@app/components/basic/buttons/Button";
|
|
||||||
import {
|
|
||||||
decryptAssymmetric,
|
|
||||||
decryptSymmetric
|
|
||||||
} from "@app/components/utilities/cryptography/crypto";
|
|
||||||
import getProjectSecretShanpshots from "@app/ee/api/secrets/GetProjectSercetShanpshots";
|
|
||||||
import getSecretSnapshotData from "@app/ee/api/secrets/GetSecretSnapshotData";
|
|
||||||
import timeSince from "@app/ee/utilities/timeSince";
|
|
||||||
import getLatestFileKey from "@app/pages/api/workspace/getLatestFileKey";
|
|
||||||
|
|
||||||
export interface SecretDataProps {
|
|
||||||
pos: number;
|
|
||||||
key: string;
|
|
||||||
value: string;
|
|
||||||
type: string;
|
|
||||||
id: string;
|
|
||||||
environment: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SideBarProps {
|
|
||||||
toggleSidebar: (value: boolean) => void;
|
|
||||||
setSnapshotData: (value: any) => void;
|
|
||||||
chosenSnapshot: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SnaphotProps {
|
|
||||||
_id: string;
|
|
||||||
createdAt: string;
|
|
||||||
secretVersions: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface EncrypetedSecretVersionListProps {
|
|
||||||
_id: string;
|
|
||||||
createdAt: string;
|
|
||||||
secretValueCiphertext: string;
|
|
||||||
secretValueIV: string;
|
|
||||||
secretValueTag: string;
|
|
||||||
secretKeyCiphertext: string;
|
|
||||||
secretKeyIV: string;
|
|
||||||
secretKeyTag: string;
|
|
||||||
environment: string;
|
|
||||||
type: "personal" | "shared";
|
|
||||||
tags: Tag[];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {object} obj
|
|
||||||
* @param {function} obj.toggleSidebar - function that opens or closes the sidebar
|
|
||||||
* @param {function} obj.setSnapshotData - state manager for snapshot data
|
|
||||||
* @param {string} obj.chosenSnaphshot - the snapshot id which is currently selected
|
|
||||||
* @returns the sidebar with the options for point-in-time recovery (commits)
|
|
||||||
*/
|
|
||||||
const PITRecoverySidebar = ({ toggleSidebar, setSnapshotData, chosenSnapshot }: SideBarProps) => {
|
|
||||||
const router = useRouter();
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
|
||||||
const [secretSnapshotsMetadata, setSecretSnapshotsMetadata] = useState<SnaphotProps[]>([]);
|
|
||||||
const [currentOffset, setCurrentOffset] = useState(0);
|
|
||||||
const currentLimit = 15;
|
|
||||||
|
|
||||||
const loadMoreSnapshots = () => {
|
|
||||||
setCurrentOffset(currentOffset + currentLimit);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const getLogData = async () => {
|
|
||||||
setIsLoading(true);
|
|
||||||
const results = await getProjectSecretShanpshots({
|
|
||||||
workspaceId: String(router.query.id),
|
|
||||||
limit: currentLimit,
|
|
||||||
offset: currentOffset
|
|
||||||
});
|
|
||||||
setSecretSnapshotsMetadata(secretSnapshotsMetadata.concat(results));
|
|
||||||
setIsLoading(false);
|
|
||||||
};
|
|
||||||
getLogData();
|
|
||||||
}, [currentOffset]);
|
|
||||||
|
|
||||||
const exploreSnapshot = async ({ snapshotId }: { snapshotId: string }) => {
|
|
||||||
const secretSnapshotData = await getSecretSnapshotData({ secretSnapshotId: snapshotId });
|
|
||||||
|
|
||||||
const latestKey = await getLatestFileKey({ workspaceId: String(router.query.id) });
|
|
||||||
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
|
|
||||||
|
|
||||||
let decryptedLatestKey: string;
|
|
||||||
if (latestKey) {
|
|
||||||
// assymmetrically decrypt symmetric key with local private key
|
|
||||||
decryptedLatestKey = decryptAssymmetric({
|
|
||||||
ciphertext: latestKey.latestKey.encryptedKey,
|
|
||||||
nonce: latestKey.latestKey.nonce,
|
|
||||||
publicKey: latestKey.latestKey.sender.publicKey,
|
|
||||||
privateKey: String(PRIVATE_KEY)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const decryptedSecretVersions = secretSnapshotData.secretVersions
|
|
||||||
.filter(
|
|
||||||
(sv: EncrypetedSecretVersionListProps) =>
|
|
||||||
sv.type !== undefined && sv.environment !== undefined
|
|
||||||
)
|
|
||||||
.map((encryptedSecretVersion: EncrypetedSecretVersionListProps, pos: number) => ({
|
|
||||||
id: encryptedSecretVersion._id,
|
|
||||||
pos,
|
|
||||||
type: encryptedSecretVersion.type,
|
|
||||||
environment: encryptedSecretVersion.environment,
|
|
||||||
tags: encryptedSecretVersion.tags,
|
|
||||||
key: decryptSymmetric({
|
|
||||||
ciphertext: encryptedSecretVersion.secretKeyCiphertext,
|
|
||||||
iv: encryptedSecretVersion.secretKeyIV,
|
|
||||||
tag: encryptedSecretVersion.secretKeyTag,
|
|
||||||
key: decryptedLatestKey
|
|
||||||
}),
|
|
||||||
value: decryptSymmetric({
|
|
||||||
ciphertext: encryptedSecretVersion.secretValueCiphertext,
|
|
||||||
iv: encryptedSecretVersion.secretValueIV,
|
|
||||||
tag: encryptedSecretVersion.secretValueTag,
|
|
||||||
key: decryptedLatestKey
|
|
||||||
})
|
|
||||||
}));
|
|
||||||
|
|
||||||
const secretKeys = [
|
|
||||||
...new Set(
|
|
||||||
decryptedSecretVersions
|
|
||||||
.filter((dsv: any) => dsv.type !== undefined || dsv.environemnt !== undefined)
|
|
||||||
.map((secret: SecretDataProps) => secret.key)
|
|
||||||
)
|
|
||||||
];
|
|
||||||
|
|
||||||
const result = secretKeys.map((key, index) =>
|
|
||||||
decryptedSecretVersions.filter(
|
|
||||||
(secret: SecretDataProps) => secret.key === key && secret.type === "shared"
|
|
||||||
)[0]?.id
|
|
||||||
? {
|
|
||||||
id: decryptedSecretVersions.filter(
|
|
||||||
(secret: SecretDataProps) => secret.key === key && secret.type === "shared"
|
|
||||||
)[0].id,
|
|
||||||
pos: index,
|
|
||||||
key,
|
|
||||||
environment: decryptedSecretVersions.filter(
|
|
||||||
(secret: SecretDataProps) => secret.key === key && secret.type === "shared"
|
|
||||||
)[0].environment,
|
|
||||||
tags: decryptedSecretVersions.filter(
|
|
||||||
(secret: SecretDataProps) => secret.key === key && secret.type === "shared"
|
|
||||||
)[0].tags,
|
|
||||||
value: decryptedSecretVersions.filter(
|
|
||||||
(secret: SecretDataProps) => secret.key === key && secret.type === "shared"
|
|
||||||
)[0]?.value,
|
|
||||||
valueOverride: decryptedSecretVersions.filter(
|
|
||||||
(secret: SecretDataProps) => secret.key === key && secret.type === "personal"
|
|
||||||
)[0]?.value
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
id: decryptedSecretVersions.filter(
|
|
||||||
(secret: SecretDataProps) => secret.key === key && secret.type === "personal"
|
|
||||||
)[0].id,
|
|
||||||
pos: index,
|
|
||||||
key,
|
|
||||||
environment: decryptedSecretVersions.filter(
|
|
||||||
(secret: SecretDataProps) => secret.key === key && secret.type === "personal"
|
|
||||||
)[0].environment,
|
|
||||||
tags: decryptedSecretVersions.filter(
|
|
||||||
(secret: SecretDataProps) => secret.key === key && secret.type === "personal"
|
|
||||||
)[0].tags,
|
|
||||||
value: decryptedSecretVersions.filter(
|
|
||||||
(secret: SecretDataProps) => secret.key === key && secret.type === "shared"
|
|
||||||
)[0]?.value,
|
|
||||||
valueOverride: decryptedSecretVersions.filter(
|
|
||||||
(secret: SecretDataProps) => secret.key === key && secret.type === "personal"
|
|
||||||
)[0]?.value
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
setSnapshotData({
|
|
||||||
id: secretSnapshotData._id,
|
|
||||||
version: secretSnapshotData.version,
|
|
||||||
createdAt: secretSnapshotData.createdAt,
|
|
||||||
secretVersions: result,
|
|
||||||
comment: ""
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={`min-w-sm absolute w-full max-w-sm border-l border-mineshaft-500 ${
|
|
||||||
isLoading ? "bg-bunker-800" : "bg-bunker"
|
|
||||||
} fixed sticky right-0 top-0 z-[40] flex h-full flex-col justify-between shadow-xl`}
|
|
||||||
>
|
|
||||||
{isLoading ? (
|
|
||||||
<div className="mb-8 flex h-full items-center justify-center">
|
|
||||||
<Image
|
|
||||||
src="/images/loading/loading.gif"
|
|
||||||
height={60}
|
|
||||||
width={100}
|
|
||||||
alt="infisical loading indicator"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="h-min">
|
|
||||||
<div className="flex flex-row items-center justify-between border-b border-mineshaft-500 px-4 py-3">
|
|
||||||
<p className="text-lg font-semibold text-bunker-200">Point In Recovery</p>
|
|
||||||
<div
|
|
||||||
onKeyDown={() => null}
|
|
||||||
role="button"
|
|
||||||
tabIndex={0}
|
|
||||||
className="p-1"
|
|
||||||
onClick={() => toggleSidebar(false)}
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon icon={faXmark} className="h-4 w-4 cursor-pointer text-bunker-300" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex h-[calc(100vh-115px)] w-96 flex-col overflow-y-auto border-l border-mineshaft-600 bg-bunker px-2 py-2">
|
|
||||||
<span className="px-2 pb-2 text-sm text-bunker-200">
|
|
||||||
Note: This will recover secrets for all enviroments in this project.
|
|
||||||
</span>
|
|
||||||
{secretSnapshotsMetadata?.map((snapshot: SnaphotProps, id: number) => (
|
|
||||||
<div
|
|
||||||
onKeyDown={() => null}
|
|
||||||
role="button"
|
|
||||||
tabIndex={0}
|
|
||||||
key={snapshot._id}
|
|
||||||
onClick={() => exploreSnapshot({ snapshotId: snapshot._id })}
|
|
||||||
className={`${
|
|
||||||
chosenSnapshot === snapshot._id || (id === 0 && chosenSnapshot === "")
|
|
||||||
? "pointer-events-none bg-primary text-black"
|
|
||||||
: "cursor-pointer bg-mineshaft-700 duration-200 hover:bg-mineshaft-500"
|
|
||||||
} mb-2 flex flex-row items-center justify-between rounded-md py-3 px-4`}
|
|
||||||
>
|
|
||||||
<div className="flex flex-row items-start">
|
|
||||||
<div
|
|
||||||
className={`${
|
|
||||||
chosenSnapshot === snapshot._id || (id === 0 && chosenSnapshot === "")
|
|
||||||
? "text-bunker-800"
|
|
||||||
: "text-bunker-200"
|
|
||||||
} mr-1.5 text-sm`}
|
|
||||||
>
|
|
||||||
{timeSince(new Date(snapshot.createdAt))}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={`${
|
|
||||||
chosenSnapshot === snapshot._id || (id === 0 && chosenSnapshot === "")
|
|
||||||
? "text-bunker-900"
|
|
||||||
: "text-bunker-300"
|
|
||||||
} text-sm `}
|
|
||||||
>{` - ${snapshot.secretVersions.length} Secrets`}</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={`${
|
|
||||||
chosenSnapshot === snapshot._id || (id === 0 && chosenSnapshot === "")
|
|
||||||
? "pointer-events-none text-bunker-800"
|
|
||||||
: "cursor-pointer text-bunker-200 duration-200 hover:text-primary"
|
|
||||||
} text-sm`}
|
|
||||||
>
|
|
||||||
{id === 0
|
|
||||||
? "Current Version"
|
|
||||||
: chosenSnapshot === snapshot._id
|
|
||||||
? "Currently Viewing"
|
|
||||||
: "Explore"}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
<div className="mb-14 flex w-full justify-center">
|
|
||||||
<div className="w-40 items-center">
|
|
||||||
<Button
|
|
||||||
text="View More"
|
|
||||||
textDisabled="End of History"
|
|
||||||
active={secretSnapshotsMetadata.length % 15 === 0}
|
|
||||||
onButtonPressed={loadMoreSnapshots}
|
|
||||||
size="md"
|
|
||||||
color="mineshaft"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default PITRecoverySidebar;
|
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
import { useEffect, useState } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import Image from "next/image";
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import { faCircle, faDotCircle } from "@fortawesome/free-solid-svg-icons";
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
||||||
|
|
||||||
import {
|
|
||||||
decryptAssymmetric,
|
|
||||||
decryptSymmetric
|
|
||||||
} from "@app/components/utilities/cryptography/crypto";
|
|
||||||
import getSecretVersions from "@app/ee/api/secrets/GetSecretVersions";
|
|
||||||
import getLatestFileKey from "@app/pages/api/workspace/getLatestFileKey";
|
|
||||||
|
|
||||||
interface DecryptedSecretVersionListProps {
|
|
||||||
createdAt: string;
|
|
||||||
value: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface EncrypetedSecretVersionListProps {
|
|
||||||
createdAt: string;
|
|
||||||
secretValueCiphertext: string;
|
|
||||||
secretValueIV: string;
|
|
||||||
secretValueTag: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} secretId - the id of a secret for which are querying version history
|
|
||||||
* @returns a list of versions for a specific secret
|
|
||||||
*/
|
|
||||||
const SecretVersionList = ({ secretId }: { secretId: string }) => {
|
|
||||||
const router = useRouter();
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [secretVersions, setSecretVersions] = useState<DecryptedSecretVersionListProps[]>([]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const getSecretVersionHistory = async () => {
|
|
||||||
setIsLoading(true);
|
|
||||||
try {
|
|
||||||
const encryptedSecretVersions = await getSecretVersions({ secretId, offset: 0, limit: 10 });
|
|
||||||
const latestKey = await getLatestFileKey({ workspaceId: String(router.query.id) });
|
|
||||||
|
|
||||||
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
|
|
||||||
|
|
||||||
let decryptedLatestKey: string;
|
|
||||||
if (latestKey) {
|
|
||||||
// assymmetrically decrypt symmetric key with local private key
|
|
||||||
decryptedLatestKey = decryptAssymmetric({
|
|
||||||
ciphertext: latestKey.latestKey.encryptedKey,
|
|
||||||
nonce: latestKey.latestKey.nonce,
|
|
||||||
publicKey: latestKey.latestKey.sender.publicKey,
|
|
||||||
privateKey: String(PRIVATE_KEY)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const decryptedSecretVersions = encryptedSecretVersions?.secretVersions.map(
|
|
||||||
(encryptedSecretVersion: EncrypetedSecretVersionListProps) => ({
|
|
||||||
createdAt: encryptedSecretVersion.createdAt,
|
|
||||||
value: decryptSymmetric({
|
|
||||||
ciphertext: encryptedSecretVersion.secretValueCiphertext,
|
|
||||||
iv: encryptedSecretVersion.secretValueIV,
|
|
||||||
tag: encryptedSecretVersion.secretValueTag,
|
|
||||||
key: decryptedLatestKey
|
|
||||||
})
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
setSecretVersions(decryptedSecretVersions);
|
|
||||||
setIsLoading(false);
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
getSecretVersionHistory();
|
|
||||||
}, [secretId]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="min-w-40 overflow-x-none dark mt-4 h-[12.4rem] w-full px-4 text-sm text-bunker-300">
|
|
||||||
<p className="">{t("dashboard.sidebar.version-history")}</p>
|
|
||||||
<div className="overflow-x-none h-full rounded-md border border-mineshaft-500 bg-bunker-800 py-0.5 pl-1">
|
|
||||||
{isLoading ? (
|
|
||||||
<div className="flex h-full items-center justify-center">
|
|
||||||
<Image
|
|
||||||
src="/images/loading/loading.gif"
|
|
||||||
height={60}
|
|
||||||
width={100}
|
|
||||||
alt="infisical loading indicator"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="overflow-x-none h-48 overflow-y-auto dark:[color-scheme:dark]">
|
|
||||||
{secretVersions ? (
|
|
||||||
secretVersions
|
|
||||||
?.sort((a, b) => b.createdAt.localeCompare(a.createdAt))
|
|
||||||
.map((version: DecryptedSecretVersionListProps, index: number) => (
|
|
||||||
<div key={`${version.createdAt}.${index + 1}`} className="flex flex-row">
|
|
||||||
<div className="flex flex-col items-center pr-1">
|
|
||||||
<div className="p-1">
|
|
||||||
<FontAwesomeIcon icon={index === 0 ? faDotCircle : faCircle} />
|
|
||||||
</div>
|
|
||||||
<div className="mt-1 h-full w-0 border-l border-bunker-300" />
|
|
||||||
</div>
|
|
||||||
<div className="flex w-full max-w-[calc(100%-2.3rem)] flex-col">
|
|
||||||
<div className="pr-2 text-bunker-300/90">
|
|
||||||
{new Date(version.createdAt).toLocaleDateString("en-US", {
|
|
||||||
year: "numeric",
|
|
||||||
month: "2-digit",
|
|
||||||
day: "2-digit",
|
|
||||||
hour: "2-digit",
|
|
||||||
minute: "2-digit",
|
|
||||||
second: "2-digit"
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
<div className="">
|
|
||||||
<p className="ph-no-capture break-words">
|
|
||||||
<span className="mr-1.5 rounded-sm bg-primary-500/30 py-0.5 px-1">
|
|
||||||
Value:
|
|
||||||
</span>
|
|
||||||
<span className="font-mono">{version.value}</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<div className="flex h-full w-full items-center justify-center text-bunker-400">
|
|
||||||
No version history yet.
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SecretVersionList;
|
|
||||||
@@ -2,10 +2,10 @@ import crypto from "crypto";
|
|||||||
|
|
||||||
import { encryptAssymmetric } from "@app/components/utilities/cryptography/crypto";
|
import { encryptAssymmetric } from "@app/components/utilities/cryptography/crypto";
|
||||||
import encryptSecrets from "@app/components/utilities/secrets/encryptSecrets";
|
import encryptSecrets from "@app/components/utilities/secrets/encryptSecrets";
|
||||||
import addSecrets from "@app/pages/api/files/AddSecrets";
|
import { uploadWsKey } from "@app/hooks/api/keys/queries";
|
||||||
import getUser from "@app/pages/api/user/getUser";
|
import { createSecret } from "@app/hooks/api/secrets/queries";
|
||||||
import createWorkspace from "@app/pages/api/workspace/createWorkspace";
|
import { fetchUserDetails } from "@app/hooks/api/users/queries";
|
||||||
import uploadKeys from "@app/pages/api/workspace/uploadKeys";
|
import { createWorkspace } from "@app/hooks/api/workspace/queries";
|
||||||
|
|
||||||
const secretsToBeAdded = [
|
const secretsToBeAdded = [
|
||||||
{
|
{
|
||||||
@@ -91,11 +91,8 @@ const initProjectHelper = async ({
|
|||||||
organizationId: string;
|
organizationId: string;
|
||||||
projectName: string;
|
projectName: string;
|
||||||
}) => {
|
}) => {
|
||||||
let project;
|
|
||||||
try {
|
|
||||||
|
|
||||||
// create new project
|
// create new project
|
||||||
project = await createWorkspace({
|
const { data: { workspace } } = await createWorkspace({
|
||||||
workspaceName: projectName,
|
workspaceName: projectName,
|
||||||
organizationId
|
organizationId
|
||||||
});
|
});
|
||||||
@@ -106,7 +103,7 @@ const initProjectHelper = async ({
|
|||||||
|
|
||||||
if (!PRIVATE_KEY) throw new Error("Failed to find private key");
|
if (!PRIVATE_KEY) throw new Error("Failed to find private key");
|
||||||
|
|
||||||
const user = await getUser();
|
const user = await fetchUserDetails();
|
||||||
|
|
||||||
const { ciphertext, nonce } = encryptAssymmetric({
|
const { ciphertext, nonce } = encryptAssymmetric({
|
||||||
plaintext: randomBytes,
|
plaintext: randomBytes,
|
||||||
@@ -114,26 +111,40 @@ const initProjectHelper = async ({
|
|||||||
privateKey: PRIVATE_KEY
|
privateKey: PRIVATE_KEY
|
||||||
});
|
});
|
||||||
|
|
||||||
await uploadKeys(project._id, user._id, ciphertext, nonce);
|
await uploadWsKey({
|
||||||
|
workspaceId: workspace._id,
|
||||||
|
userId: user._id,
|
||||||
|
encryptedKey: ciphertext,
|
||||||
|
nonce
|
||||||
|
});
|
||||||
|
|
||||||
// encrypt and upload secrets to new project
|
// encrypt and upload secrets to new project
|
||||||
const secrets = await encryptSecrets({
|
const secrets = await encryptSecrets({
|
||||||
secretsToEncrypt: secretsToBeAdded,
|
secretsToEncrypt: secretsToBeAdded,
|
||||||
workspaceId: project._id,
|
workspaceId: workspace._id,
|
||||||
env: "dev"
|
env: "dev"
|
||||||
});
|
});
|
||||||
|
|
||||||
await addSecrets({
|
secrets?.forEach((secret) => {
|
||||||
secrets: secrets ?? [],
|
createSecret({
|
||||||
env: "dev",
|
workspaceId: workspace._id,
|
||||||
workspaceId: project._id
|
environment: secret.environment,
|
||||||
|
type: secret.type,
|
||||||
|
secretKey: secret.secretName,
|
||||||
|
secretKeyCiphertext: secret.secretKeyCiphertext,
|
||||||
|
secretKeyIV: secret.secretKeyIV,
|
||||||
|
secretKeyTag: secret.secretKeyTag,
|
||||||
|
secretValueCiphertext: secret.secretValueCiphertext,
|
||||||
|
secretValueIV: secret.secretValueIV,
|
||||||
|
secretValueTag: secret.secretValueTag,
|
||||||
|
secretCommentCiphertext: secret.secretCommentCiphertext,
|
||||||
|
secretCommentIV: secret.secretCommentIV,
|
||||||
|
secretCommentTag: secret.secretCommentTag,
|
||||||
|
secretPath: "/"
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (err) {
|
return workspace;
|
||||||
console.error("Failed to init project in organization", err);
|
|
||||||
}
|
|
||||||
|
|
||||||
return project;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
export {
|
export {
|
||||||
useGetAuthToken,
|
useGetAuthToken,
|
||||||
useGetCommonPasswords,
|
useGetCommonPasswords,
|
||||||
|
useResetPassword,
|
||||||
useSendMfaToken,
|
useSendMfaToken,
|
||||||
useVerifyMfaToken
|
useSendPasswordResetEmail,
|
||||||
} from "./queries"
|
useSendVerificationEmail,
|
||||||
|
useVerifyEmailVerificationCode,
|
||||||
|
useVerifyMfaToken,
|
||||||
|
useVerifyPasswordResetCode} from "./queries"
|
||||||
|
|||||||
@@ -4,16 +4,86 @@ import { apiRequest } from "@app/config/request";
|
|||||||
import { setAuthToken } from "@app/reactQuery";
|
import { setAuthToken } from "@app/reactQuery";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
ChangePasswordDTO,
|
||||||
|
CompleteAccountDTO,
|
||||||
|
CompleteAccountSignupDTO,
|
||||||
GetAuthTokenAPI,
|
GetAuthTokenAPI,
|
||||||
|
GetBackupEncryptedPrivateKeyDTO,
|
||||||
|
IssueBackupPrivateKeyDTO,
|
||||||
|
Login1DTO,
|
||||||
|
Login1Res,
|
||||||
|
Login2DTO,
|
||||||
|
Login2Res,
|
||||||
|
ResetPasswordDTO,
|
||||||
SendMfaTokenDTO,
|
SendMfaTokenDTO,
|
||||||
|
SRP1DTO,
|
||||||
|
SRPR1Res,
|
||||||
VerifyMfaTokenDTO,
|
VerifyMfaTokenDTO,
|
||||||
VerifyMfaTokenRes} from "./types";
|
VerifyMfaTokenRes,
|
||||||
|
VerifySignupInviteDTO} from "./types";
|
||||||
|
|
||||||
const authKeys = {
|
const authKeys = {
|
||||||
getAuthToken: ["token"] as const,
|
getAuthToken: ["token"] as const,
|
||||||
commonPasswords: ["common-passwords"] as const
|
commonPasswords: ["common-passwords"] as const
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const login1 = async (loginDetails: Login1DTO) => {
|
||||||
|
const { data } = await apiRequest.post<Login1Res>("/api/v3/auth/login1", loginDetails);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const login2 = async (loginDetails: Login2DTO) => {
|
||||||
|
const { data } = await apiRequest.post<Login2Res>("/api/v3/auth/login2", loginDetails);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useLogin1 = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async (details: {
|
||||||
|
email: string;
|
||||||
|
clientPublicKey: string;
|
||||||
|
providerAuthToken?: string;
|
||||||
|
}) => {
|
||||||
|
return login1(details);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useLogin2 = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async (details: {
|
||||||
|
email: string;
|
||||||
|
clientProof: string;
|
||||||
|
providerAuthToken?: string;
|
||||||
|
}) => {
|
||||||
|
return login2(details);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const srp1 = async (details: SRP1DTO) => {
|
||||||
|
const { data } = await apiRequest.post<SRPR1Res>("/api/v1/password/srp1", details);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const completeAccountSignup = async (details: CompleteAccountSignupDTO) => {
|
||||||
|
const { data } = await apiRequest.post("/api/v3/signup/complete-account/signup", details);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const completeAccountSignupInvite = async (details: CompleteAccountDTO) => {
|
||||||
|
const { data } = await apiRequest.post("/api/v2/signup/complete-account/invite", details);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useCompleteAccountSignup = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async (details: CompleteAccountSignupDTO) => {
|
||||||
|
return completeAccountSignup(details);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export const useSendMfaToken = () => {
|
export const useSendMfaToken = () => {
|
||||||
return useMutation<{}, {}, SendMfaTokenDTO>({
|
return useMutation<{}, {}, SendMfaTokenDTO>({
|
||||||
mutationFn: async ({ email }) => {
|
mutationFn: async ({ email }) => {
|
||||||
@@ -23,14 +93,157 @@ export const useSendMfaToken = () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useVerifyMfaToken = () => {
|
export const verifyMfaToken = async ({
|
||||||
return useMutation<VerifyMfaTokenRes, {}, VerifyMfaTokenDTO>({
|
email,
|
||||||
mutationFn: async ({ email, mfaCode }) => {
|
mfaCode
|
||||||
|
}: {
|
||||||
|
email: string;
|
||||||
|
mfaCode: string;
|
||||||
|
}) => {
|
||||||
const { data } = await apiRequest.post("/api/v2/auth/mfa/verify", {
|
const { data } = await apiRequest.post("/api/v2/auth/mfa/verify", {
|
||||||
email,
|
email,
|
||||||
mfaToken: mfaCode
|
mfaToken: mfaCode
|
||||||
});
|
});
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useVerifyMfaToken = () => {
|
||||||
|
return useMutation<VerifyMfaTokenRes, {}, VerifyMfaTokenDTO>({
|
||||||
|
mutationFn: async ({ email, mfaCode }) => {
|
||||||
|
return verifyMfaToken({
|
||||||
|
email,
|
||||||
|
mfaCode
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const verifySignupInvite = async (details: VerifySignupInviteDTO) => {
|
||||||
|
const { data } = await apiRequest.post("/api/v1/invite-org/verify", details);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useSendVerificationEmail = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async ({
|
||||||
|
email
|
||||||
|
}: {
|
||||||
|
email: string;
|
||||||
|
}) => {
|
||||||
|
const { data } = await apiRequest.post("/api/v1/signup/email/signup", {
|
||||||
|
email
|
||||||
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useVerifyEmailVerificationCode = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async ({
|
||||||
|
email,
|
||||||
|
code
|
||||||
|
}: {
|
||||||
|
email: string;
|
||||||
|
code: string;
|
||||||
|
}) => {
|
||||||
|
const { data } = await apiRequest.post("/api/v1/signup/email/verify", {
|
||||||
|
email,
|
||||||
|
code
|
||||||
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useSendPasswordResetEmail = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async ({
|
||||||
|
email
|
||||||
|
}: {
|
||||||
|
email: string;
|
||||||
|
}) => {
|
||||||
|
const { data } = await apiRequest.post("/api/v1/password/email/password-reset", {
|
||||||
|
email
|
||||||
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useVerifyPasswordResetCode = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async ({
|
||||||
|
email,
|
||||||
|
code
|
||||||
|
}: {
|
||||||
|
email: string;
|
||||||
|
code: string;
|
||||||
|
}) => {
|
||||||
|
const { data } = await apiRequest.post("/api/v1/password/email/password-reset-verify", {
|
||||||
|
email,
|
||||||
|
code
|
||||||
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const issueBackupPrivateKey = async (details: IssueBackupPrivateKeyDTO) => {
|
||||||
|
const { data } = await apiRequest.post("/api/v1/password/backup-private-key", details);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getBackupEncryptedPrivateKey = async ({
|
||||||
|
verificationToken
|
||||||
|
}: GetBackupEncryptedPrivateKeyDTO) => {
|
||||||
|
const { data } = await apiRequest.get("/api/v1/password/backup-private-key", {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${verificationToken}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return data.backupPrivateKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useResetPassword = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async (details: ResetPasswordDTO) => {
|
||||||
|
const { data } = await apiRequest.post("/api/v1/password/password-reset", {
|
||||||
|
protectedKey: details.protectedKey,
|
||||||
|
protectedKeyIV: details.protectedKeyIV,
|
||||||
|
protectedKeyTag: details.protectedKeyTag,
|
||||||
|
encryptedPrivateKey: details.encryptedPrivateKey,
|
||||||
|
encryptedPrivateKeyIV: details.encryptedPrivateKeyIV,
|
||||||
|
encryptedPrivateKeyTag: details.encryptedPrivateKeyTag,
|
||||||
|
salt: details.salt,
|
||||||
|
verifier: details.verifier
|
||||||
|
}, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${details.verificationToken}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const changePassword = async (details: ChangePasswordDTO) => {
|
||||||
|
const { data } = await apiRequest.post("/api/v1/password/change-password", details);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useChangePassword = () => {
|
||||||
|
// note: use after srp1
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async (details: ChangePasswordDTO) => {
|
||||||
|
return changePassword(details);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,3 +22,106 @@ export type VerifyMfaTokenRes = {
|
|||||||
iv: string;
|
iv: string;
|
||||||
tag: string;
|
tag: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Login1DTO = {
|
||||||
|
email: string;
|
||||||
|
clientPublicKey: string;
|
||||||
|
providerAuthToken?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Login2DTO = {
|
||||||
|
email: string;
|
||||||
|
clientProof: string;
|
||||||
|
providerAuthToken?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Login1Res = {
|
||||||
|
serverPublicKey: string;
|
||||||
|
salt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Login2Res = {
|
||||||
|
mfaEnabled: boolean;
|
||||||
|
token: string;
|
||||||
|
encryptionVersion?: number;
|
||||||
|
protectedKey?: string;
|
||||||
|
protectedKeyIV?: string;
|
||||||
|
protectedKeyTag?: string;
|
||||||
|
publicKey?: string;
|
||||||
|
encryptedPrivateKey?: string;
|
||||||
|
iv?: string;
|
||||||
|
tag?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SRP1DTO = {
|
||||||
|
clientPublicKey: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SRPR1Res = {
|
||||||
|
serverPublicKey: string;
|
||||||
|
salt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CompleteAccountDTO = {
|
||||||
|
email: string;
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
protectedKey: string;
|
||||||
|
protectedKeyIV: string;
|
||||||
|
protectedKeyTag: string;
|
||||||
|
publicKey: string;
|
||||||
|
encryptedPrivateKey: string;
|
||||||
|
encryptedPrivateKeyIV: string;
|
||||||
|
encryptedPrivateKeyTag: string;
|
||||||
|
salt: string;
|
||||||
|
verifier: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CompleteAccountSignupDTO = CompleteAccountDTO & {
|
||||||
|
providerAuthToken?: string;
|
||||||
|
attributionSource?: string;
|
||||||
|
organizationName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type VerifySignupInviteDTO = {
|
||||||
|
email: string;
|
||||||
|
code: string;
|
||||||
|
organizationId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ChangePasswordDTO = {
|
||||||
|
clientProof: string;
|
||||||
|
protectedKey: string;
|
||||||
|
protectedKeyIV: string;
|
||||||
|
protectedKeyTag: string;
|
||||||
|
encryptedPrivateKey: string;
|
||||||
|
encryptedPrivateKeyIV: string;
|
||||||
|
encryptedPrivateKeyTag: string;
|
||||||
|
salt: string;
|
||||||
|
verifier: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ResetPasswordDTO = {
|
||||||
|
protectedKey: string;
|
||||||
|
protectedKeyIV: string;
|
||||||
|
protectedKeyTag: string;
|
||||||
|
encryptedPrivateKey: string;
|
||||||
|
encryptedPrivateKeyIV: string;
|
||||||
|
encryptedPrivateKeyTag: string;
|
||||||
|
salt: string;
|
||||||
|
verifier: string;
|
||||||
|
verificationToken: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IssueBackupPrivateKeyDTO = {
|
||||||
|
encryptedPrivateKey: string;
|
||||||
|
iv: string;
|
||||||
|
tag: string;
|
||||||
|
salt: string;
|
||||||
|
verifier: string;
|
||||||
|
clientProof: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GetBackupEncryptedPrivateKeyDTO = {
|
||||||
|
verificationToken: string;
|
||||||
|
}
|
||||||
@@ -8,29 +8,26 @@ const queryKeys = {
|
|||||||
getBot: (workspaceId: string) => [{ workspaceId }, "bot"] as const
|
getBot: (workspaceId: string) => [{ workspaceId }, "bot"] as const
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchWorkspaceBot = async (workspaceId: string) => {
|
|
||||||
const { data } = await apiRequest.get<{ bot: TBot }>(`/api/v1/bot/${workspaceId}`);
|
|
||||||
return data.bot;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useGetWorkspaceBot = (workspaceId: string) =>
|
export const useGetWorkspaceBot = (workspaceId: string) =>
|
||||||
useQuery({
|
useQuery({
|
||||||
queryKey: queryKeys.getBot(workspaceId),
|
queryKey: queryKeys.getBot(workspaceId),
|
||||||
queryFn: () => fetchWorkspaceBot(workspaceId),
|
queryFn: async () => {
|
||||||
|
const { data: { bot } } = await apiRequest.get<{ bot: TBot }>(`/api/v1/bot/${workspaceId}`);
|
||||||
|
return bot;
|
||||||
|
},
|
||||||
enabled: Boolean(workspaceId)
|
enabled: Boolean(workspaceId)
|
||||||
});
|
});
|
||||||
|
|
||||||
// mutation
|
|
||||||
|
|
||||||
export const useUpdateBotActiveStatus = () => {
|
export const useUpdateBotActiveStatus = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation<{}, {}, TSetBotActiveStatusDto>({
|
return useMutation<{}, {}, TSetBotActiveStatusDto>({
|
||||||
mutationFn: ({ botId, isActive, botKey }) =>
|
mutationFn: ({ botId, isActive, botKey }) => {
|
||||||
apiRequest.patch(`/api/v1/bot/${botId}/active`, {
|
return apiRequest.patch(`/api/v1/bot/${botId}/active`, {
|
||||||
isActive,
|
isActive,
|
||||||
botKey
|
botKey
|
||||||
}),
|
});
|
||||||
|
},
|
||||||
onSuccess: (_, { workspaceId }) => {
|
onSuccess: (_, { workspaceId }) => {
|
||||||
queryClient.invalidateQueries(queryKeys.getBot(workspaceId));
|
queryClient.invalidateQueries(queryKeys.getBot(workspaceId));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,18 +8,16 @@ const incidentContactKeys = {
|
|||||||
getAllContact: (orgId: string) => ["org-incident-contacts", { orgId }] as const
|
getAllContact: (orgId: string) => ["org-incident-contacts", { orgId }] as const
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchOrgIncidentContacts = async (orgId: string) => {
|
export const useGetOrgIncidentContact = (orgId: string) =>
|
||||||
|
useQuery({
|
||||||
|
queryKey: incidentContactKeys.getAllContact(orgId),
|
||||||
|
queryFn: async () => {
|
||||||
const { data } = await apiRequest.get<{ incidentContactsOrg: IncidentContact[] }>(
|
const { data } = await apiRequest.get<{ incidentContactsOrg: IncidentContact[] }>(
|
||||||
`/api/v1/organization/${orgId}/incidentContactOrg`
|
`/api/v1/organization/${orgId}/incidentContactOrg`
|
||||||
);
|
);
|
||||||
|
|
||||||
return data.incidentContactsOrg;
|
return data.incidentContactsOrg;
|
||||||
};
|
},
|
||||||
|
|
||||||
export const useGetOrgIncidentContact = (orgId: string) =>
|
|
||||||
useQuery({
|
|
||||||
queryKey: incidentContactKeys.getAllContact(orgId),
|
|
||||||
queryFn: () => fetchOrgIncidentContacts(orgId),
|
|
||||||
enabled: Boolean(orgId)
|
enabled: Boolean(orgId)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export {
|
export {
|
||||||
|
useAuthorizeIntegration,
|
||||||
useDeleteIntegrationAuth,
|
useDeleteIntegrationAuth,
|
||||||
useGetIntegrationAuthApps,
|
useGetIntegrationAuthApps,
|
||||||
useGetIntegrationAuthBitBucketWorkspaces,
|
useGetIntegrationAuthBitBucketWorkspaces,
|
||||||
@@ -7,4 +8,6 @@ export {
|
|||||||
useGetIntegrationAuthRailwayEnvironments,
|
useGetIntegrationAuthRailwayEnvironments,
|
||||||
useGetIntegrationAuthRailwayServices,
|
useGetIntegrationAuthRailwayServices,
|
||||||
useGetIntegrationAuthTeams,
|
useGetIntegrationAuthTeams,
|
||||||
useGetIntegrationAuthVercelBranches} from "./queries";
|
useGetIntegrationAuthVercelBranches,
|
||||||
|
useSaveIntegrationAccessToken
|
||||||
|
} from "./queries";
|
||||||
|
|||||||
@@ -312,6 +312,69 @@ export const useGetIntegrationAuthNorthflankSecretGroups = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useAuthorizeIntegration = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async ({
|
||||||
|
workspaceId,
|
||||||
|
code,
|
||||||
|
integration
|
||||||
|
}: {
|
||||||
|
workspaceId: string;
|
||||||
|
code: string;
|
||||||
|
integration: string;
|
||||||
|
}) => {
|
||||||
|
const { data: { integrationAuth } } = await apiRequest.post("/api/v1/integration-auth/oauth-token", {
|
||||||
|
workspaceId,
|
||||||
|
code,
|
||||||
|
integration
|
||||||
|
});
|
||||||
|
|
||||||
|
return integrationAuth;
|
||||||
|
},
|
||||||
|
onSuccess: (res) => {
|
||||||
|
queryClient.invalidateQueries(workspaceKeys.getWorkspaceAuthorization(res.workspace));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useSaveIntegrationAccessToken = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async ({
|
||||||
|
workspaceId,
|
||||||
|
integration,
|
||||||
|
accessId,
|
||||||
|
accessToken,
|
||||||
|
url,
|
||||||
|
namespace
|
||||||
|
}: {
|
||||||
|
workspaceId: string | null;
|
||||||
|
integration: string | undefined;
|
||||||
|
accessId: string | null;
|
||||||
|
accessToken: string;
|
||||||
|
url: string | null;
|
||||||
|
namespace: string | null;
|
||||||
|
}) => {
|
||||||
|
const { data: { integrationAuth } } = await apiRequest.post("/api/v1/integration-auth/access-token", {
|
||||||
|
workspaceId,
|
||||||
|
integration,
|
||||||
|
accessId,
|
||||||
|
accessToken,
|
||||||
|
url,
|
||||||
|
namespace
|
||||||
|
});
|
||||||
|
|
||||||
|
return integrationAuth;
|
||||||
|
},
|
||||||
|
onSuccess: (res) => {
|
||||||
|
queryClient.invalidateQueries(workspaceKeys.getWorkspaceAuthorization(res.workspace));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const useDeleteIntegrationAuth = () => {
|
export const useDeleteIntegrationAuth = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1,5 @@
|
|||||||
export { useDeleteIntegration,useGetCloudIntegrations } from "./queries";
|
export {
|
||||||
|
useCreateIntegration,
|
||||||
|
useDeleteIntegration,
|
||||||
|
useGetCloudIntegrations
|
||||||
|
} from "./queries";
|
||||||
|
|||||||
@@ -23,6 +23,63 @@ export const useGetCloudIntegrations = () =>
|
|||||||
queryFn: () => fetchIntegrations()
|
queryFn: () => fetchIntegrations()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const useCreateIntegration = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async ({
|
||||||
|
integrationAuthId,
|
||||||
|
isActive,
|
||||||
|
app,
|
||||||
|
appId,
|
||||||
|
sourceEnvironment,
|
||||||
|
targetEnvironment,
|
||||||
|
targetEnvironmentId,
|
||||||
|
targetService,
|
||||||
|
targetServiceId,
|
||||||
|
owner,
|
||||||
|
path,
|
||||||
|
region,
|
||||||
|
secretPath
|
||||||
|
}: {
|
||||||
|
integrationAuthId: string;
|
||||||
|
isActive: boolean;
|
||||||
|
secretPath: string;
|
||||||
|
app: string | null;
|
||||||
|
appId: string | null;
|
||||||
|
sourceEnvironment: string;
|
||||||
|
targetEnvironment: string | null;
|
||||||
|
targetEnvironmentId: string | null;
|
||||||
|
targetService: string | null;
|
||||||
|
targetServiceId: string | null;
|
||||||
|
owner: string | null;
|
||||||
|
path: string | null;
|
||||||
|
region: string | null;
|
||||||
|
}) => {
|
||||||
|
const { data: { integration } } = await apiRequest.post("/api/v1/integration", {
|
||||||
|
integrationAuthId,
|
||||||
|
isActive,
|
||||||
|
app,
|
||||||
|
appId,
|
||||||
|
sourceEnvironment,
|
||||||
|
targetEnvironment,
|
||||||
|
targetEnvironmentId,
|
||||||
|
targetService,
|
||||||
|
targetServiceId,
|
||||||
|
owner,
|
||||||
|
path,
|
||||||
|
region,
|
||||||
|
secretPath
|
||||||
|
});
|
||||||
|
|
||||||
|
return integration;
|
||||||
|
},
|
||||||
|
onSuccess: (res) => {
|
||||||
|
queryClient.invalidateQueries(workspaceKeys.getWorkspaceIntegrations(res.workspace));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const useDeleteIntegration = () => {
|
export const useDeleteIntegration = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ const encKeyKeys = {
|
|||||||
getUserWorkspaceKey: (workspaceID: string) => ["workspace-key-pair", { workspaceID }] as const
|
getUserWorkspaceKey: (workspaceID: string) => ["workspace-key-pair", { workspaceID }] as const
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchUserWsKey = async (workspaceID: string) => {
|
export const fetchUserWsKey = async (workspaceID: string) => {
|
||||||
const { data } = await apiRequest.get<{ latestKey: UserWsKeyPair }>(
|
const { data } = await apiRequest.get<{ latestKey: UserWsKeyPair }>(
|
||||||
`/api/v1/key/${workspaceID}/latest`
|
`/api/v1/key/${workspaceID}/latest`
|
||||||
);
|
);
|
||||||
@@ -24,8 +24,23 @@ export const useGetUserWsKey = (workspaceID: string) =>
|
|||||||
});
|
});
|
||||||
|
|
||||||
// mutations
|
// mutations
|
||||||
|
export const uploadWsKey = async ({
|
||||||
|
workspaceId,
|
||||||
|
userId,
|
||||||
|
encryptedKey,
|
||||||
|
nonce
|
||||||
|
}: UploadWsKeyDTO) => {
|
||||||
|
return apiRequest.post(`/api/v1/key/${workspaceId}`, { key: { userId, encryptedKey, nonce } })
|
||||||
|
}
|
||||||
|
|
||||||
export const useUploadWsKey = () =>
|
export const useUploadWsKey = () =>
|
||||||
useMutation<{}, {}, UploadWsKeyDTO>({
|
useMutation<{}, {}, UploadWsKeyDTO>({
|
||||||
mutationFn: ({ encryptedKey, nonce, userId, workspaceId }) =>
|
mutationFn: async ({ encryptedKey, nonce, userId, workspaceId }) => {
|
||||||
apiRequest.post(`/api/v1/key/${workspaceId}`, { key: { userId, encryptedKey, nonce } })
|
return uploadWsKey({
|
||||||
|
workspaceId,
|
||||||
|
userId,
|
||||||
|
encryptedKey,
|
||||||
|
nonce
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ export {
|
|||||||
useCreateCustomerPortalSession,
|
useCreateCustomerPortalSession,
|
||||||
useDeleteOrgPmtMethod,
|
useDeleteOrgPmtMethod,
|
||||||
useDeleteOrgTaxId,
|
useDeleteOrgTaxId,
|
||||||
useGetOrganization,
|
useGetOrganizations,
|
||||||
useGetOrgBillingDetails,
|
useGetOrgBillingDetails,
|
||||||
useGetOrgInvoices,
|
useGetOrgInvoices,
|
||||||
useGetOrgLicenses,
|
useGetOrgLicenses,
|
||||||
|
|||||||
@@ -12,10 +12,11 @@ import {
|
|||||||
PmtMethod,
|
PmtMethod,
|
||||||
ProductsTable,
|
ProductsTable,
|
||||||
RenameOrgDTO,
|
RenameOrgDTO,
|
||||||
TaxID} from "./types";
|
TaxID
|
||||||
|
} from "./types";
|
||||||
|
|
||||||
const organizationKeys = {
|
const organizationKeys = {
|
||||||
getUserOrganization: ["organization"] as const,
|
getUserOrganizations: ["organization"] as const,
|
||||||
getOrgPlanBillingInfo: (orgId: string) => [{ orgId }, "organization-plan-billing"] as const,
|
getOrgPlanBillingInfo: (orgId: string) => [{ orgId }, "organization-plan-billing"] as const,
|
||||||
getOrgPlanTable: (orgId: string) => [{ orgId }, "organization-plan-table"] as const,
|
getOrgPlanTable: (orgId: string) => [{ orgId }, "organization-plan-table"] as const,
|
||||||
getOrgPlansTable: (orgId: string, billingCycle: "monthly" | "yearly") => [{ orgId, billingCycle }, "organization-plans-table"] as const,
|
getOrgPlansTable: (orgId: string, billingCycle: "monthly" | "yearly") => [{ orgId, billingCycle }, "organization-plans-table"] as const,
|
||||||
@@ -26,13 +27,16 @@ const organizationKeys = {
|
|||||||
getOrgLicenses: (orgId: string) => [{ orgId }, "organization-licenses"] as const
|
getOrgLicenses: (orgId: string) => [{ orgId }, "organization-licenses"] as const
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useGetOrganization = () => {
|
export const fetchOrganizations = async () => {
|
||||||
return useQuery({
|
const { data: { organizations } } = await apiRequest.get<{ organizations: Organization[] }>("/api/v1/organization");
|
||||||
queryKey: organizationKeys.getUserOrganization,
|
return organizations;
|
||||||
queryFn: async () => {
|
}
|
||||||
const { data } = await apiRequest.get<{ organizations: Organization[] }>("/api/v1/organization");
|
|
||||||
|
|
||||||
return data.organizations;
|
export const useGetOrganizations = () => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: organizationKeys.getUserOrganizations,
|
||||||
|
queryFn: async () => {
|
||||||
|
return fetchOrganizations();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -41,10 +45,11 @@ export const useRenameOrg = () => {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation<{}, {}, RenameOrgDTO>({
|
return useMutation<{}, {}, RenameOrgDTO>({
|
||||||
mutationFn: ({ newOrgName, orgId }) =>
|
mutationFn: ({ newOrgName, orgId }) => {
|
||||||
apiRequest.patch(`/api/v1/organization/${orgId}/name`, { name: newOrgName }),
|
return apiRequest.patch(`/api/v1/organization/${orgId}/name`, { name: newOrgName });
|
||||||
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries(organizationKeys.getUserOrganization);
|
queryClient.invalidateQueries(organizationKeys.getUserOrganizations);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,13 +11,13 @@ import { apiRequest } from "@app/config/request";
|
|||||||
import { secretSnapshotKeys } from "../secretSnapshots/queries";
|
import { secretSnapshotKeys } from "../secretSnapshots/queries";
|
||||||
import {
|
import {
|
||||||
BatchSecretDTO,
|
BatchSecretDTO,
|
||||||
|
CreateSecretDTO,
|
||||||
DecryptedSecret,
|
DecryptedSecret,
|
||||||
EncryptedSecret,
|
EncryptedSecret,
|
||||||
EncryptedSecretVersion,
|
EncryptedSecretVersion,
|
||||||
GetProjectSecretsDTO,
|
GetProjectSecretsDTO,
|
||||||
GetSecretVersionsDTO,
|
GetSecretVersionsDTO,
|
||||||
TGetProjectSecretsAllEnvDTO
|
TGetProjectSecretsAllEnvDTO} from "./types";
|
||||||
} from "./types";
|
|
||||||
|
|
||||||
export const secretKeys = {
|
export const secretKeys = {
|
||||||
// this is also used in secretSnapshot part
|
// this is also used in secretSnapshot part
|
||||||
@@ -324,3 +324,24 @@ export const useBatchSecretsOp = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const createSecret = async (dto: CreateSecretDTO) => {
|
||||||
|
const { data } = await apiRequest.post(`/api/v3/secrets/${dto.secretKey}`, dto);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useCreateSecret = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation<{}, {}, CreateSecretDTO>({
|
||||||
|
mutationFn: async (dto) => {
|
||||||
|
const data = createSecret(dto);
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
onSuccess: (_, dto) => {
|
||||||
|
queryClient.invalidateQueries(
|
||||||
|
secretKeys.getProjectSecret(dto.workspaceId, dto.environment)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -147,3 +147,22 @@ export type TDeleteSecretsV3DTO = {
|
|||||||
secretPath: string;
|
secretPath: string;
|
||||||
secretName: string;
|
secretName: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// --- v3
|
||||||
|
|
||||||
|
export type CreateSecretDTO = {
|
||||||
|
workspaceId: string;
|
||||||
|
environment: string;
|
||||||
|
type: "shared" | "personal";
|
||||||
|
secretKey: string;
|
||||||
|
secretKeyCiphertext: string;
|
||||||
|
secretKeyIV: string;
|
||||||
|
secretKeyTag: string;
|
||||||
|
secretValueCiphertext: string;
|
||||||
|
secretValueIV: string;
|
||||||
|
secretValueTag: string;
|
||||||
|
secretCommentCiphertext: string;
|
||||||
|
secretCommentIV: string;
|
||||||
|
secretCommentTag: string;
|
||||||
|
secretPath: string;
|
||||||
|
}
|
||||||
@@ -23,12 +23,13 @@ const fetchWorkspaceServiceTokens = async (workspaceID: string) => {
|
|||||||
|
|
||||||
type UseGetWorkspaceServiceTokensProps = { workspaceID: string };
|
type UseGetWorkspaceServiceTokensProps = { workspaceID: string };
|
||||||
|
|
||||||
export const useGetUserWsServiceTokens = ({ workspaceID }: UseGetWorkspaceServiceTokensProps) =>
|
export const useGetUserWsServiceTokens = ({ workspaceID }: UseGetWorkspaceServiceTokensProps) => {
|
||||||
useQuery({
|
return useQuery({
|
||||||
queryKey: serviceTokenKeys.getAllWorkspaceServiceToken(workspaceID),
|
queryKey: serviceTokenKeys.getAllWorkspaceServiceToken(workspaceID),
|
||||||
queryFn: () => fetchWorkspaceServiceTokens(workspaceID),
|
queryFn: () => fetchWorkspaceServiceTokens(workspaceID),
|
||||||
enabled: Boolean(workspaceID)
|
enabled: Boolean(workspaceID)
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// mutation
|
// mutation
|
||||||
export const useCreateServiceToken = () => {
|
export const useCreateServiceToken = () => {
|
||||||
@@ -36,6 +37,7 @@ export const useCreateServiceToken = () => {
|
|||||||
|
|
||||||
return useMutation<CreateServiceTokenRes, {}, CreateServiceTokenDTO>({
|
return useMutation<CreateServiceTokenRes, {}, CreateServiceTokenDTO>({
|
||||||
mutationFn: async (body) => {
|
mutationFn: async (body) => {
|
||||||
|
console.log("useCreateServiceToken");
|
||||||
const { data } = await apiRequest.post("/api/v2/service-token/", body);
|
const { data } = await apiRequest.post("/api/v2/service-token/", body);
|
||||||
data.serviceToken += `.${body.randomBytes}`;
|
data.serviceToken += `.${body.randomBytes}`;
|
||||||
return data;
|
return data;
|
||||||
@@ -51,6 +53,7 @@ export const useDeleteServiceToken = () => {
|
|||||||
|
|
||||||
return useMutation<DeleteServiceTokenRes, {}, string>({
|
return useMutation<DeleteServiceTokenRes, {}, string>({
|
||||||
mutationFn: async (serviceTokenId) => {
|
mutationFn: async (serviceTokenId) => {
|
||||||
|
console.log("useDeleteServiceToken");
|
||||||
const { data } = await apiRequest.delete(`/api/v2/service-token/${serviceTokenId}`);
|
const { data } = await apiRequest.delete(`/api/v2/service-token/${serviceTokenId}`);
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import {
|
|||||||
UserWsTags
|
UserWsTags
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
|
||||||
|
|
||||||
const workspaceTags = {
|
const workspaceTags = {
|
||||||
getWsTags: (workspaceID: string) => ["workspace-tags", { workspaceID }] as const
|
getWsTags: (workspaceID: string) => ["workspace-tags", { workspaceID }] as const
|
||||||
};
|
};
|
||||||
@@ -23,12 +22,13 @@ const fetchWsTag = async (workspaceID: string) => {
|
|||||||
return data.workspaceTags;
|
return data.workspaceTags;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useGetWsTags = (workspaceID: string) =>
|
export const useGetWsTags = (workspaceID: string) => {
|
||||||
useQuery({
|
return useQuery({
|
||||||
queryKey: workspaceTags.getWsTags(workspaceID),
|
queryKey: workspaceTags.getWsTags(workspaceID),
|
||||||
queryFn: () => fetchWsTag(workspaceID),
|
queryFn: () => fetchWsTag(workspaceID),
|
||||||
enabled: Boolean(workspaceID)
|
enabled: Boolean(workspaceID)
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export const useCreateWsTag = () => {
|
export const useCreateWsTag = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export {
|
|||||||
useDeleteOrgMembership,
|
useDeleteOrgMembership,
|
||||||
useGetMyAPIKeys,
|
useGetMyAPIKeys,
|
||||||
useGetMyIp,
|
useGetMyIp,
|
||||||
|
useGetMyOrganizationProjects,
|
||||||
useGetMySessions,
|
useGetMySessions,
|
||||||
useGetOrgUsers,
|
useGetOrgUsers,
|
||||||
useGetUser,
|
useGetUser,
|
||||||
@@ -14,6 +15,6 @@ export {
|
|||||||
useLogoutUser,
|
useLogoutUser,
|
||||||
useRegisterUserAction,
|
useRegisterUserAction,
|
||||||
useRevokeMySessions,
|
useRevokeMySessions,
|
||||||
|
useUpdateMfaEnabled,
|
||||||
useUpdateOrgUserRole,
|
useUpdateOrgUserRole,
|
||||||
useUpdateUserAuthProvider
|
useUpdateUserAuthProvider} from "./queries";
|
||||||
} from "./queries";
|
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ import {
|
|||||||
RenameUserDTO,
|
RenameUserDTO,
|
||||||
TokenVersion,
|
TokenVersion,
|
||||||
UpdateOrgUserRoleDTO,
|
UpdateOrgUserRoleDTO,
|
||||||
User} from "./types";
|
User
|
||||||
|
} from "./types";
|
||||||
|
|
||||||
const userKeys = {
|
const userKeys = {
|
||||||
getUser: ["user"] as const,
|
getUser: ["user"] as const,
|
||||||
@@ -27,7 +28,8 @@ const userKeys = {
|
|||||||
getOrgUsers: (orgId: string) => [{ orgId }, "user"],
|
getOrgUsers: (orgId: string) => [{ orgId }, "user"],
|
||||||
myIp: ["ip"] as const,
|
myIp: ["ip"] as const,
|
||||||
myAPIKeys: ["api-keys"] as const,
|
myAPIKeys: ["api-keys"] as const,
|
||||||
mySessions: ["sessions"] as const
|
mySessions: ["sessions"] as const,
|
||||||
|
myOrganizationProjects: (orgId: string) => [{ orgId }, "organization-projects"] as const
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchUserDetails = async () => {
|
export const fetchUserDetails = async () => {
|
||||||
@@ -38,7 +40,7 @@ export const fetchUserDetails = async () => {
|
|||||||
|
|
||||||
export const useGetUser = () => useQuery(userKeys.getUser, fetchUserDetails);
|
export const useGetUser = () => useQuery(userKeys.getUser, fetchUserDetails);
|
||||||
|
|
||||||
const fetchUserAction = async (action: string) => {
|
export const fetchUserAction = async (action: string) => {
|
||||||
const { data } = await apiRequest.get<{ userAction: string }>("/api/v1/user-action", {
|
const { data } = await apiRequest.get<{ userAction: string }>("/api/v1/user-action", {
|
||||||
params: {
|
params: {
|
||||||
action
|
action
|
||||||
@@ -146,7 +148,9 @@ export const useAddUserToOrg = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return useMutation<Response, {}, AddUserToOrgDTO>({
|
return useMutation<Response, {}, AddUserToOrgDTO>({
|
||||||
mutationFn: (dto) => apiRequest.post("/api/v1/invite-org/signup", dto),
|
mutationFn: (dto) => {
|
||||||
|
return apiRequest.post("/api/v1/invite-org/signup", dto);
|
||||||
|
},
|
||||||
onSuccess: (_, { organizationId }) => {
|
onSuccess: (_, { organizationId }) => {
|
||||||
queryClient.invalidateQueries(userKeys.getOrgUsers(organizationId));
|
queryClient.invalidateQueries(userKeys.getOrgUsers(organizationId));
|
||||||
}
|
}
|
||||||
@@ -157,8 +161,9 @@ export const useDeleteOrgMembership = () => {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation<{}, {}, DeletOrgMembershipDTO>({
|
return useMutation<{}, {}, DeletOrgMembershipDTO>({
|
||||||
mutationFn: ({ membershipId, orgId }) =>
|
mutationFn: ({ membershipId, orgId }) => {
|
||||||
apiRequest.delete(`/api/v2/organizations/${orgId}/memberships/${membershipId}`),
|
return apiRequest.delete(`/api/v2/organizations/${orgId}/memberships/${membershipId}`)
|
||||||
|
},
|
||||||
onSuccess: (_, { orgId }) => {
|
onSuccess: (_, { orgId }) => {
|
||||||
queryClient.invalidateQueries(userKeys.getOrgUsers(orgId));
|
queryClient.invalidateQueries(userKeys.getOrgUsers(orgId));
|
||||||
}
|
}
|
||||||
@@ -169,10 +174,11 @@ export const useUpdateOrgUserRole = () => {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation<{}, {}, UpdateOrgUserRoleDTO>({
|
return useMutation<{}, {}, UpdateOrgUserRoleDTO>({
|
||||||
mutationFn: ({ organizationId, membershipId, role }) =>
|
mutationFn: ({ organizationId, membershipId, role }) => {
|
||||||
apiRequest.patch(`/api/v2/organizations/${organizationId}/memberships/${membershipId}`, {
|
return apiRequest.patch(`/api/v2/organizations/${organizationId}/memberships/${membershipId}`, {
|
||||||
role
|
role
|
||||||
}),
|
});
|
||||||
|
},
|
||||||
onSuccess: (_, { organizationId }) => {
|
onSuccess: (_, { organizationId }) => {
|
||||||
queryClient.invalidateQueries(userKeys.getOrgUsers(organizationId));
|
queryClient.invalidateQueries(userKeys.getOrgUsers(organizationId));
|
||||||
},
|
},
|
||||||
@@ -195,7 +201,9 @@ export const useRegisterUserAction = () => {
|
|||||||
|
|
||||||
export const useLogoutUser = () =>
|
export const useLogoutUser = () =>
|
||||||
useMutation({
|
useMutation({
|
||||||
mutationFn: () => apiRequest.post("/api/v1/auth/logout"),
|
mutationFn: async () => {
|
||||||
|
await apiRequest.post("/api/v1/auth/logout");
|
||||||
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
setAuthToken("");
|
setAuthToken("");
|
||||||
// Delete the cookie by not setting a value; Alternatively clear the local storage
|
// Delete the cookie by not setting a value; Alternatively clear the local storage
|
||||||
@@ -304,3 +312,44 @@ export const useRevokeMySessions = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const useUpdateMfaEnabled = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async ({
|
||||||
|
isMfaEnabled
|
||||||
|
}: {
|
||||||
|
isMfaEnabled: boolean;
|
||||||
|
}) => {
|
||||||
|
const { data: { user } } = await apiRequest.patch(
|
||||||
|
"/api/v2/users/me/mfa",
|
||||||
|
{
|
||||||
|
isMfaEnabled
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return user;
|
||||||
|
},
|
||||||
|
onSuccess() {
|
||||||
|
queryClient.invalidateQueries(userKeys.getUser);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fetchMyOrganizationProjects = async (orgId: string) => {
|
||||||
|
const { data: { workspaces } } = await apiRequest.get(
|
||||||
|
`/api/v1/organization/${orgId}/my-workspaces`
|
||||||
|
);
|
||||||
|
|
||||||
|
return workspaces;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useGetMyOrganizationProjects = (orgId: string) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: userKeys.myOrganizationProjects(orgId),
|
||||||
|
queryFn: async () => {
|
||||||
|
return fetchMyOrganizationProjects(orgId);
|
||||||
|
},
|
||||||
|
enabled: true
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ export enum AuthProvider {
|
|||||||
export type User = {
|
export type User = {
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
email?: string;
|
email: string;
|
||||||
firstName?: string;
|
firstName?: string;
|
||||||
lastName?: string;
|
lastName?: string;
|
||||||
authProvider?: AuthProvider;
|
authProvider?: AuthProvider;
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
export {
|
export {
|
||||||
|
useAddUserToWorkspace,
|
||||||
useCreateWorkspace,
|
useCreateWorkspace,
|
||||||
useCreateWsEnvironment,
|
useCreateWsEnvironment,
|
||||||
|
useDeleteUserFromWorkspace,
|
||||||
useDeleteWorkspace,
|
useDeleteWorkspace,
|
||||||
useDeleteWsEnvironment,
|
useDeleteWsEnvironment,
|
||||||
useGetUserWorkspaceMemberships,
|
useGetUserWorkspaceMemberships,
|
||||||
@@ -11,8 +13,9 @@ export {
|
|||||||
useGetWorkspaceIndexStatus,
|
useGetWorkspaceIndexStatus,
|
||||||
useGetWorkspaceIntegrations,
|
useGetWorkspaceIntegrations,
|
||||||
useGetWorkspaceSecrets,
|
useGetWorkspaceSecrets,
|
||||||
|
useGetWorkspaceUsers,
|
||||||
useNameWorkspaceSecrets,
|
useNameWorkspaceSecrets,
|
||||||
useRenameWorkspace,
|
useRenameWorkspace,
|
||||||
useToggleAutoCapitalization,
|
useToggleAutoCapitalization,
|
||||||
useUpdateWsEnvironment
|
useUpdateUserWorkspaceRole,
|
||||||
} from "./queries";
|
useUpdateWsEnvironment} from "./queries";
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ export const workspaceKeys = {
|
|||||||
getWorkspaceIntegrations: (workspaceId: string) => [{ workspaceId }, "workspace-integrations"],
|
getWorkspaceIntegrations: (workspaceId: string) => [{ workspaceId }, "workspace-integrations"],
|
||||||
getAllUserWorkspace: ["workspaces"] as const,
|
getAllUserWorkspace: ["workspaces"] as const,
|
||||||
getUserWsEnvironments: (workspaceId: string) => ["workspace-env", { workspaceId }] as const,
|
getUserWsEnvironments: (workspaceId: string) => ["workspace-env", { workspaceId }] as const,
|
||||||
getWorkspaceAuditLogs: (workspaceId: string) => [{ workspaceId }] as const
|
getWorkspaceAuditLogs: (workspaceId: string) => [{ workspaceId }] as const,
|
||||||
|
getWorkspaceUsers: (workspaceId: string) => [{ workspaceId }] as const
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchWorkspaceById = async (workspaceId: string) => {
|
const fetchWorkspaceById = async (workspaceId: string) => {
|
||||||
@@ -166,13 +167,21 @@ export const useGetWorkspaceIntegrations = (workspaceId: string) =>
|
|||||||
enabled: Boolean(workspaceId)
|
enabled: Boolean(workspaceId)
|
||||||
});
|
});
|
||||||
|
|
||||||
// mutation
|
export const createWorkspace = ({
|
||||||
|
organizationId,
|
||||||
|
workspaceName
|
||||||
|
}: CreateWorkspaceDTO): Promise<{ data: { workspace: Workspace } }> => {
|
||||||
|
return apiRequest.post("/api/v1/workspace", { workspaceName, organizationId });
|
||||||
|
}
|
||||||
|
|
||||||
export const useCreateWorkspace = () => {
|
export const useCreateWorkspace = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation<{ data: { workspace: Workspace } }, {}, CreateWorkspaceDTO>({
|
return useMutation<{ data: { workspace: Workspace } }, {}, CreateWorkspaceDTO>({
|
||||||
mutationFn: async ({ organizationId, workspaceName }) =>
|
mutationFn: async ({ organizationId, workspaceName }) => createWorkspace({
|
||||||
apiRequest.post("/api/v1/workspace", { workspaceName, organizationId }),
|
organizationId,
|
||||||
|
workspaceName
|
||||||
|
}),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
||||||
}
|
}
|
||||||
@@ -183,8 +192,9 @@ export const useRenameWorkspace = () => {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation<{}, {}, RenameWorkspaceDTO>({
|
return useMutation<{}, {}, RenameWorkspaceDTO>({
|
||||||
mutationFn: ({ workspaceID, newWorkspaceName }) =>
|
mutationFn: ({ workspaceID, newWorkspaceName }) => {
|
||||||
apiRequest.post(`/api/v1/workspace/${workspaceID}/name`, { name: newWorkspaceName }),
|
return apiRequest.post(`/api/v1/workspace/${workspaceID}/name`, { name: newWorkspaceName });
|
||||||
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
||||||
}
|
}
|
||||||
@@ -209,7 +219,9 @@ export const useDeleteWorkspace = () => {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation<{}, {}, DeleteWorkspaceDTO>({
|
return useMutation<{}, {}, DeleteWorkspaceDTO>({
|
||||||
mutationFn: ({ workspaceID }) => apiRequest.delete(`/api/v1/workspace/${workspaceID}`),
|
mutationFn: ({ workspaceID }) => {
|
||||||
|
return apiRequest.delete(`/api/v1/workspace/${workspaceID}`);
|
||||||
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
||||||
}
|
}
|
||||||
@@ -220,11 +232,12 @@ export const useCreateWsEnvironment = () => {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation<{}, {}, CreateEnvironmentDTO>({
|
return useMutation<{}, {}, CreateEnvironmentDTO>({
|
||||||
mutationFn: ({ workspaceID, environmentName, environmentSlug }) =>
|
mutationFn: ({ workspaceID, environmentName, environmentSlug }) => {
|
||||||
apiRequest.post(`/api/v2/workspace/${workspaceID}/environments`, {
|
return apiRequest.post(`/api/v2/workspace/${workspaceID}/environments`, {
|
||||||
environmentName,
|
environmentName,
|
||||||
environmentSlug
|
environmentSlug
|
||||||
}),
|
});
|
||||||
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
||||||
}
|
}
|
||||||
@@ -235,12 +248,13 @@ export const useUpdateWsEnvironment = () => {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation<{}, {}, UpdateEnvironmentDTO>({
|
return useMutation<{}, {}, UpdateEnvironmentDTO>({
|
||||||
mutationFn: ({ workspaceID, environmentName, environmentSlug, oldEnvironmentSlug }) =>
|
mutationFn: ({ workspaceID, environmentName, environmentSlug, oldEnvironmentSlug }) => {
|
||||||
apiRequest.put(`/api/v2/workspace/${workspaceID}/environments`, {
|
return apiRequest.put(`/api/v2/workspace/${workspaceID}/environments`, {
|
||||||
environmentName,
|
environmentName,
|
||||||
environmentSlug,
|
environmentSlug,
|
||||||
oldEnvironmentSlug
|
oldEnvironmentSlug
|
||||||
}),
|
});
|
||||||
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
||||||
}
|
}
|
||||||
@@ -251,13 +265,85 @@ export const useDeleteWsEnvironment = () => {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation<{}, {}, DeleteEnvironmentDTO>({
|
return useMutation<{}, {}, DeleteEnvironmentDTO>({
|
||||||
mutationFn: ({ workspaceID, environmentSlug }) =>
|
mutationFn: ({ workspaceID, environmentSlug }) => {
|
||||||
apiRequest.delete(`/api/v2/workspace/${workspaceID}/environments`, {
|
return apiRequest.delete(`/api/v2/workspace/${workspaceID}/environments`, {
|
||||||
data: { environmentSlug }
|
data: { environmentSlug }
|
||||||
}),
|
});
|
||||||
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useGetWorkspaceUsers = (workspaceId: string) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: workspaceKeys.getWorkspaceUsers(workspaceId),
|
||||||
|
queryFn: async () => {
|
||||||
|
const { data: { users } } = await apiRequest.get(
|
||||||
|
`/api/v1/workspace/${workspaceId}/users`
|
||||||
|
);
|
||||||
|
return users;
|
||||||
|
},
|
||||||
|
enabled: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useAddUserToWorkspace = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async ({
|
||||||
|
email,
|
||||||
|
workspaceId
|
||||||
|
}: {
|
||||||
|
email: string;
|
||||||
|
workspaceId: string;
|
||||||
|
}) => {
|
||||||
|
const { data: { invitee, latestKey } } = await apiRequest.post(`/api/v1/workspace/${workspaceId}/invite-signup`, { email });
|
||||||
|
|
||||||
|
return ({
|
||||||
|
invitee,
|
||||||
|
latestKey
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onSuccess: (_, dto) => {
|
||||||
|
queryClient.invalidateQueries(workspaceKeys.getWorkspaceUsers(dto.workspaceId));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useDeleteUserFromWorkspace = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async (membershipId: string) => {
|
||||||
|
const { data: { deletedMembership } } = await apiRequest.delete(`/api/v1/membership/${membershipId}`);
|
||||||
|
return deletedMembership;
|
||||||
|
},
|
||||||
|
onSuccess: (res) => {
|
||||||
|
queryClient.invalidateQueries(workspaceKeys.getWorkspaceUsers(res.workspace));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useUpdateUserWorkspaceRole = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async ({
|
||||||
|
membershipId,
|
||||||
|
role
|
||||||
|
}: {
|
||||||
|
membershipId: string;
|
||||||
|
role: string;
|
||||||
|
}) => {
|
||||||
|
const { data: { membership } } = await apiRequest.post(`/api/v1/membership/${membershipId}/change-role`, {
|
||||||
|
role
|
||||||
|
});
|
||||||
|
return membership;
|
||||||
|
},
|
||||||
|
onSuccess: (res) => {
|
||||||
|
queryClient.invalidateQueries(workspaceKeys.getWorkspaceUsers(res.workspace));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
import { apiRequest } from "@app/config/request";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
clientProof: string;
|
|
||||||
protectedKey: string;
|
|
||||||
protectedKeyIV: string;
|
|
||||||
protectedKeyTag: string;
|
|
||||||
encryptedPrivateKey: string;
|
|
||||||
encryptedPrivateKeyIV: string;
|
|
||||||
encryptedPrivateKeyTag: string;
|
|
||||||
salt: string;
|
|
||||||
verifier: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is the second step of the change password process (pake)
|
|
||||||
* @param {*} clientPublicKey
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const changePassword2 = async ({
|
|
||||||
clientProof,
|
|
||||||
protectedKey,
|
|
||||||
protectedKeyIV,
|
|
||||||
protectedKeyTag,
|
|
||||||
encryptedPrivateKey,
|
|
||||||
encryptedPrivateKeyIV,
|
|
||||||
encryptedPrivateKeyTag,
|
|
||||||
salt,
|
|
||||||
verifier
|
|
||||||
}: Props) => {
|
|
||||||
const { data } = await apiRequest.post("/api/v1/password/change-password", {
|
|
||||||
clientProof,
|
|
||||||
protectedKey,
|
|
||||||
protectedKeyIV,
|
|
||||||
protectedKeyTag,
|
|
||||||
encryptedPrivateKey,
|
|
||||||
encryptedPrivateKeyIV,
|
|
||||||
encryptedPrivateKeyTag,
|
|
||||||
salt,
|
|
||||||
verifier
|
|
||||||
});
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default changePassword2;
|
|
||||||
@@ -4,12 +4,13 @@ import SecurityClient from "@app/components/utilities/SecurityClient";
|
|||||||
* This function is used to check if the user is authenticated.
|
* This function is used to check if the user is authenticated.
|
||||||
* To do that, we get their tokens from cookies, and verify if they are good.
|
* To do that, we get their tokens from cookies, and verify if they are good.
|
||||||
*/
|
*/
|
||||||
const checkAuth = async () =>
|
const checkAuth = async () => {
|
||||||
SecurityClient.fetchCall("/api/v1/auth/checkAuth", {
|
return SecurityClient.fetchCall("/api/v1/auth/checkAuth", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json"
|
||||||
}
|
}
|
||||||
}).then((res) => res);
|
}).then((res) => res);
|
||||||
|
}
|
||||||
|
|
||||||
export default checkAuth;
|
export default checkAuth;
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
interface Props {
|
|
||||||
email: string;
|
|
||||||
code: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This route check the verification code from the email that user just recieved
|
|
||||||
* @param {object} obj
|
|
||||||
* @param {string} obj.email
|
|
||||||
* @param {string} obj.code
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const checkEmailVerificationCode = ({ email, code }: Props) => fetch("/api/v1/signup/email/verify", {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
email,
|
|
||||||
code
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
export default checkEmailVerificationCode;
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
|
|
||||||
import { apiRequest } from "@app/config/request";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
email: string;
|
|
||||||
firstName: string;
|
|
||||||
lastName: string;
|
|
||||||
protectedKey: string;
|
|
||||||
protectedKeyIV: string;
|
|
||||||
protectedKeyTag: string;
|
|
||||||
providerAuthToken?: string;
|
|
||||||
publicKey: string;
|
|
||||||
encryptedPrivateKey: string;
|
|
||||||
encryptedPrivateKeyIV: string;
|
|
||||||
encryptedPrivateKeyTag: string;
|
|
||||||
organizationName: string;
|
|
||||||
salt: string;
|
|
||||||
verifier: string;
|
|
||||||
attributionSource?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function is called in the end of the signup process.
|
|
||||||
* It sends all the necessary nformation to the server.
|
|
||||||
* @param {object} obj
|
|
||||||
* @param {string} obj.email - email of the user completing signup
|
|
||||||
* @param {string} obj.firstName - first name of the user completing signup
|
|
||||||
* @param {string} obj.lastName - last name of the user completing sign up
|
|
||||||
* @param {string} obj.protectedKey - protected key in encryption version 2
|
|
||||||
* @param {string} obj.protectedKeyIV - IV of protected key in encryption version 2
|
|
||||||
* @param {string} obj.protectedKeyTag - tag of protected key in encryption version 2
|
|
||||||
* @param {string} obj.organizationName - organization name for this user (usually, [FIRST_NAME]'s organization)
|
|
||||||
* @param {string} obj.publicKey - public key of the user completing signup
|
|
||||||
* @param {string} obj.ciphertext
|
|
||||||
* @param {string} obj.iv
|
|
||||||
* @param {string} obj.tag
|
|
||||||
* @param {string} obj.salt
|
|
||||||
* @param {string} obj.verifier
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const completeAccountInformationSignup = async ({
|
|
||||||
email,
|
|
||||||
firstName,
|
|
||||||
lastName,
|
|
||||||
protectedKey,
|
|
||||||
protectedKeyIV,
|
|
||||||
protectedKeyTag,
|
|
||||||
publicKey,
|
|
||||||
encryptedPrivateKey,
|
|
||||||
encryptedPrivateKeyIV,
|
|
||||||
encryptedPrivateKeyTag,
|
|
||||||
salt,
|
|
||||||
verifier,
|
|
||||||
organizationName,
|
|
||||||
providerAuthToken,
|
|
||||||
attributionSource
|
|
||||||
}: Props) => {
|
|
||||||
const { data } = await apiRequest.post("/api/v3/signup/complete-account/signup", {
|
|
||||||
email,
|
|
||||||
firstName,
|
|
||||||
lastName,
|
|
||||||
protectedKey,
|
|
||||||
protectedKeyIV,
|
|
||||||
protectedKeyTag,
|
|
||||||
publicKey,
|
|
||||||
encryptedPrivateKey,
|
|
||||||
encryptedPrivateKeyIV,
|
|
||||||
encryptedPrivateKeyTag,
|
|
||||||
salt,
|
|
||||||
verifier,
|
|
||||||
organizationName,
|
|
||||||
providerAuthToken,
|
|
||||||
...(attributionSource ? { attributionSource } : {})
|
|
||||||
});
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default completeAccountInformationSignup;
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
import { apiRequest } from "@app/config/request";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
email: string;
|
|
||||||
firstName: string;
|
|
||||||
lastName: string;
|
|
||||||
protectedKey: string;
|
|
||||||
protectedKeyIV: string;
|
|
||||||
protectedKeyTag: string;
|
|
||||||
publicKey: string;
|
|
||||||
encryptedPrivateKey: string;
|
|
||||||
encryptedPrivateKeyIV: string;
|
|
||||||
encryptedPrivateKeyTag: string;
|
|
||||||
salt: string;
|
|
||||||
verifier: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// missing token?
|
|
||||||
// TODO: add to SecurityClient
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function is called in the end of the signup process.
|
|
||||||
* It sends all the necessary nformation to the server.
|
|
||||||
* @param {object} obj
|
|
||||||
* @param {string} obj.email - email of the user completing signupinvite flow
|
|
||||||
* @param {string} obj.firstName - first name of the user completing signupinvite flow
|
|
||||||
* @param {string} obj.lastName - last name of the user completing signupinvite flow
|
|
||||||
* @param {string} obj.publicKey - public key of the user completing signupinvite flow
|
|
||||||
* @param {string} obj.ciphertext
|
|
||||||
* @param {string} obj.iv
|
|
||||||
* @param {string} obj.tag
|
|
||||||
* @param {string} obj.salt
|
|
||||||
* @param {string} obj.verifier
|
|
||||||
* @param {string} obj.token - token that confirms a user's identity
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const completeAccountInformationSignupInvite = async ({
|
|
||||||
email,
|
|
||||||
firstName,
|
|
||||||
lastName,
|
|
||||||
protectedKey,
|
|
||||||
protectedKeyIV,
|
|
||||||
protectedKeyTag,
|
|
||||||
publicKey,
|
|
||||||
encryptedPrivateKey,
|
|
||||||
encryptedPrivateKeyIV,
|
|
||||||
encryptedPrivateKeyTag,
|
|
||||||
salt,
|
|
||||||
verifier
|
|
||||||
}: Props) => {
|
|
||||||
const { data } = await apiRequest.post("/api/v2/signup/complete-account/invite", {
|
|
||||||
email,
|
|
||||||
firstName,
|
|
||||||
lastName,
|
|
||||||
protectedKey,
|
|
||||||
protectedKeyIV,
|
|
||||||
protectedKeyTag,
|
|
||||||
publicKey,
|
|
||||||
encryptedPrivateKey,
|
|
||||||
encryptedPrivateKeyIV,
|
|
||||||
encryptedPrivateKeyTag,
|
|
||||||
salt,
|
|
||||||
verifier
|
|
||||||
});
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default completeAccountInformationSignupInvite;
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
interface Props {
|
|
||||||
email: string;
|
|
||||||
code: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is the second part of the account recovery step (a user needs to verify their email).
|
|
||||||
* A user need to click on a button in a magic link page
|
|
||||||
* @param {object} obj
|
|
||||||
* @param {object} obj.email - email of a user that is trying to recover access to their account
|
|
||||||
* @param {object} obj.code - token that a use received via the magic link
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const EmailVerifyOnPasswordReset = async ({ email, code }: Props) => {
|
|
||||||
const response = await fetch("/api/v1/password/email/password-reset-verify", {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
email,
|
|
||||||
code
|
|
||||||
})
|
|
||||||
});
|
|
||||||
if (response?.status === 200) {
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(
|
|
||||||
"Something went wrong during email verification on password reset."
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default EmailVerifyOnPasswordReset;
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
import SecurityClient from "@app/components/utilities/SecurityClient";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
encryptedPrivateKey: string;
|
|
||||||
iv: string;
|
|
||||||
tag: string;
|
|
||||||
salt: string;
|
|
||||||
verifier: string;
|
|
||||||
clientProof: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is the route that issues a backup private key that will afterwards be added into a pdf
|
|
||||||
* @param {object} obj
|
|
||||||
* @param {string} obj.encryptedPrivateKey
|
|
||||||
* @param {string} obj.iv
|
|
||||||
* @param {string} obj.tag
|
|
||||||
* @param {string} obj.salt
|
|
||||||
* @param {string} obj.verifier
|
|
||||||
* @param {string} obj.clientProof
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const issueBackupPrivateKey = ({
|
|
||||||
encryptedPrivateKey,
|
|
||||||
iv,
|
|
||||||
tag,
|
|
||||||
salt,
|
|
||||||
verifier,
|
|
||||||
clientProof
|
|
||||||
}: Props) =>
|
|
||||||
SecurityClient.fetchCall("/api/v1/password/backup-private-key", {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
clientProof,
|
|
||||||
encryptedPrivateKey,
|
|
||||||
iv,
|
|
||||||
tag,
|
|
||||||
salt,
|
|
||||||
verifier
|
|
||||||
})
|
|
||||||
}).then((res) => {
|
|
||||||
if (res?.status !== 200) {
|
|
||||||
console.log("Failed to issue the backup key");
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
});
|
|
||||||
|
|
||||||
export default issueBackupPrivateKey;
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
interface Login1 {
|
|
||||||
serverPublicKey: string;
|
|
||||||
salt: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is the first step of the login process (pake)
|
|
||||||
* @param {*} email
|
|
||||||
* @param {*} clientPublicKey
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const login1 = async (loginDetails: {
|
|
||||||
email: string;
|
|
||||||
clientPublicKey: string;
|
|
||||||
providerAuthToken?: string;
|
|
||||||
}) => {
|
|
||||||
const response = await fetch("/api/v3/auth/login1", {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(loginDetails),
|
|
||||||
});
|
|
||||||
// need precise error handling about the status code
|
|
||||||
if (response?.status === 200) {
|
|
||||||
const data = (await response.json()) as unknown as Login1;
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error("Wrong password");
|
|
||||||
};
|
|
||||||
|
|
||||||
export default login1;
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
interface Login2Response {
|
|
||||||
mfaEnabled: boolean;
|
|
||||||
token: string;
|
|
||||||
encryptionVersion?: number;
|
|
||||||
protectedKey?: string;
|
|
||||||
protectedKeyIV?: string;
|
|
||||||
protectedKeyTag?: string;
|
|
||||||
publicKey?: string;
|
|
||||||
encryptedPrivateKey?: string;
|
|
||||||
iv?: string;
|
|
||||||
tag?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is the second step of the login process
|
|
||||||
* @param {*} email
|
|
||||||
* @param {*} clientPublicKey
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const login2 = async (loginDetails: {
|
|
||||||
email: string;
|
|
||||||
clientProof: string;
|
|
||||||
providerAuthToken?: string;
|
|
||||||
}) => {
|
|
||||||
const response = await fetch("/api/v3/auth/login2", {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
},
|
|
||||||
body: JSON.stringify(loginDetails),
|
|
||||||
credentials: "include"
|
|
||||||
});
|
|
||||||
// need precise error handling about the status code
|
|
||||||
if (response.status === 200) {
|
|
||||||
const data = (await response.json()) as unknown as Login2Response;
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error("Password verification failed");
|
|
||||||
};
|
|
||||||
|
|
||||||
export default login2;
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
import SecurityClient from "@app/components/utilities/SecurityClient";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This route logs the user out. Note: the user should authorized to do this.
|
|
||||||
* We first try to log out - if the authorization fails (response.status = 401), we refetch the new token, and then retry
|
|
||||||
*/
|
|
||||||
const logout = async () => {
|
|
||||||
try {
|
|
||||||
const res = await SecurityClient.fetchCall("/api/v1/auth/logout", {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
},
|
|
||||||
credentials: "include"
|
|
||||||
});
|
|
||||||
|
|
||||||
if (res?.status === 200) {
|
|
||||||
SecurityClient.setToken("");
|
|
||||||
// Delete the cookie by not setting a value; Alternatively clear the local storage
|
|
||||||
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");
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.log("Error logging out", error);
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default logout;
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
import SecurityClient from "@app/components/utilities/SecurityClient";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
clientPublicKey: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is the first step of the change password process (pake)
|
|
||||||
* @param {string} clientPublicKey
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const SRP1 = ({ clientPublicKey }: Props) =>
|
|
||||||
SecurityClient.fetchCall("/api/v1/password/srp1", {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
clientPublicKey
|
|
||||||
})
|
|
||||||
}).then(async (res) => {
|
|
||||||
if (res && res.status === 200) {
|
|
||||||
return res.json();
|
|
||||||
}
|
|
||||||
console.log("Failed to do the first step of SRP");
|
|
||||||
return undefined;
|
|
||||||
});
|
|
||||||
|
|
||||||
export default SRP1;
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
interface Props {
|
|
||||||
email: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is the first of the account recovery step (a user needs to verify their email).
|
|
||||||
* It will send an email containing a magic link to start the account recovery flow.
|
|
||||||
* @param {object} obj
|
|
||||||
* @param {object} obj.email - email of a user that is trying to recover access to their account
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const SendEmailOnPasswordReset = async ({ email }: Props) => {
|
|
||||||
const response = await fetch("/api/v1/password/email/password-reset", {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
email
|
|
||||||
})
|
|
||||||
});
|
|
||||||
// need precise error handling about the status code
|
|
||||||
if (response?.status === 200) {
|
|
||||||
const data = await response.json();
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(
|
|
||||||
"Something went wrong while sending the email verification for password reset."
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SendEmailOnPasswordReset;
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
/**
|
|
||||||
* This route send the verification email to the user's email (contains a 6-digit verification code)
|
|
||||||
* @param {*} email
|
|
||||||
*/
|
|
||||||
const sendVerificationEmail = (email: string) => {
|
|
||||||
fetch("/api/v1/signup/email/signup", {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
email
|
|
||||||
})
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export default sendVerificationEmail;
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
const token = async () =>
|
|
||||||
fetch("/api/v1/auth/token", {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
},
|
|
||||||
credentials: "include"
|
|
||||||
}).then(async (res) => {
|
|
||||||
if (res.status === 200) {
|
|
||||||
return (await res.json()).token;
|
|
||||||
}
|
|
||||||
console.log("Getting a new token failed");
|
|
||||||
return undefined;
|
|
||||||
});
|
|
||||||
|
|
||||||
export default token;
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
interface Props {
|
|
||||||
email: string;
|
|
||||||
code: string;
|
|
||||||
organizationId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This route verifies the signup invite link
|
|
||||||
* @param {object} obj
|
|
||||||
* @param {string} obj.email - email that a user is trying to verify
|
|
||||||
* @param {string} obj.organizationId - id of organization that a user is trying to verify for
|
|
||||||
* @param {string} obj.code - code that a user received to the abovementioned email
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const verifySignupInvite = ({ email, organizationId, code }: Props) => fetch("/api/v1/invite-org/verify", {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
email,
|
|
||||||
organizationId,
|
|
||||||
code
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
export default verifySignupInvite;
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
/**
|
|
||||||
* This is the route that get an encrypted private key (will be decrypted with a backup key)
|
|
||||||
* @param {object} obj
|
|
||||||
* @param {object} obj.verificationToken - this is the token that confirms that a user is the right one
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const getBackupEncryptedPrivateKey = ({
|
|
||||||
verificationToken
|
|
||||||
}: {
|
|
||||||
verificationToken: string;
|
|
||||||
}) => fetch("/api/v1/password/backup-private-key", {
|
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: `Bearer ${ verificationToken}`
|
|
||||||
}
|
|
||||||
}).then(async (res) => {
|
|
||||||
if (res?.status !== 200) {
|
|
||||||
console.log("Failed to get the backup key");
|
|
||||||
}
|
|
||||||
return (await res?.json())?.backupPrivateKey;
|
|
||||||
});
|
|
||||||
|
|
||||||
export default getBackupEncryptedPrivateKey;
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
const publicKeyInfisical = () => fetch("/api/v1/key/publicKey/infisical", {
|
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default publicKeyInfisical;
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
interface Props {
|
|
||||||
protectedKey: string;
|
|
||||||
protectedKeyIV: string;
|
|
||||||
protectedKeyTag: string;
|
|
||||||
encryptedPrivateKey: string;
|
|
||||||
encryptedPrivateKeyIV: string;
|
|
||||||
encryptedPrivateKeyTag: string;
|
|
||||||
salt: string;
|
|
||||||
verifier: string;
|
|
||||||
verificationToken: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is the route that resets the account password if all the previus steps were passed
|
|
||||||
* @param {object} obj
|
|
||||||
* @param {object} obj.verificationToken - this is the token that confirms that a user is the right one
|
|
||||||
* @param {object} obj.encryptedPrivateKey - the new encrypted private key (encrypted using the new password)
|
|
||||||
* @param {object} obj.iv
|
|
||||||
* @param {object} obj.tag
|
|
||||||
* @param {object} obj.salt
|
|
||||||
* @param {object} obj.verifier
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const resetPasswordOnAccountRecovery = ({
|
|
||||||
protectedKey,
|
|
||||||
protectedKeyIV,
|
|
||||||
protectedKeyTag,
|
|
||||||
encryptedPrivateKey,
|
|
||||||
encryptedPrivateKeyIV,
|
|
||||||
encryptedPrivateKeyTag,
|
|
||||||
salt,
|
|
||||||
verifier,
|
|
||||||
verificationToken,
|
|
||||||
}: Props) => fetch("/api/v1/password/password-reset", {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: `Bearer ${verificationToken}`
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
protectedKey,
|
|
||||||
protectedKeyIV,
|
|
||||||
protectedKeyTag,
|
|
||||||
encryptedPrivateKey,
|
|
||||||
encryptedPrivateKeyIV,
|
|
||||||
encryptedPrivateKeyTag,
|
|
||||||
salt,
|
|
||||||
verifier
|
|
||||||
})
|
|
||||||
}).then(async (res) => {
|
|
||||||
if (res?.status !== 200) {
|
|
||||||
console.log("Failed to get the backup key");
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
});
|
|
||||||
|
|
||||||
export default resetPasswordOnAccountRecovery;
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import { apiRequest } from "@app/config/request";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verify MFA token [mfaToken] for user with email [email]
|
|
||||||
* @param {object} obj
|
|
||||||
* @param {string} obj.email - email of user
|
|
||||||
* @param {string} obj.mfaToken - MFA cod/token to verify
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const verifyMfaToken = async ({
|
|
||||||
email,
|
|
||||||
mfaToken
|
|
||||||
}: {
|
|
||||||
email: string;
|
|
||||||
mfaToken: string;
|
|
||||||
}) => {
|
|
||||||
const { data } = await apiRequest.post("/api/v2/auth/mfa/verify", {
|
|
||||||
email,
|
|
||||||
mfaToken
|
|
||||||
});
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default verifyMfaToken;
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import SecurityClient from "@app/components/utilities/SecurityClient";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
workspaceId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function fetches the bot for a project
|
|
||||||
* @param {Object} obj
|
|
||||||
* @param {String} obj.workspaceId
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const getBot = async ({ workspaceId }: Props) =>
|
|
||||||
SecurityClient.fetchCall(`/api/v1/bot/${workspaceId}`, {
|
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
}
|
|
||||||
}).then(async (res) => {
|
|
||||||
if (res && res.status === 200) {
|
|
||||||
return (await res.json()).bot;
|
|
||||||
}
|
|
||||||
console.log("Failed to get bot for project");
|
|
||||||
return undefined;
|
|
||||||
});
|
|
||||||
|
|
||||||
export default getBot;
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
import SecurityClient from "@app/components/utilities/SecurityClient";
|
|
||||||
|
|
||||||
interface BotKey {
|
|
||||||
encryptedKey: string;
|
|
||||||
nonce: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
botId: string;
|
|
||||||
isActive: boolean;
|
|
||||||
botKey?: BotKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function sets the active status of a bot and shares a copy of
|
|
||||||
* the project key (encrypted under the bot's public key) with the
|
|
||||||
* project's bot
|
|
||||||
* @param {Object} obj
|
|
||||||
* @param {String} obj.botId
|
|
||||||
* @param {String} obj.isActive
|
|
||||||
* @param {Object} obj.botKey
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const setBotActiveStatus = async ({ botId, isActive, botKey }: Props) =>
|
|
||||||
SecurityClient.fetchCall(`/api/v1/bot/${botId}/active`, {
|
|
||||||
method: "PATCH",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
isActive,
|
|
||||||
botKey
|
|
||||||
})
|
|
||||||
}).then(async (res) => {
|
|
||||||
if (res && res.status === 200) {
|
|
||||||
return res.json();
|
|
||||||
}
|
|
||||||
console.log("Failed to get bot for project");
|
|
||||||
return undefined;
|
|
||||||
});
|
|
||||||
|
|
||||||
export default setBotActiveStatus;
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import SecurityClient from "@app/components/utilities/SecurityClient";
|
|
||||||
|
|
||||||
type NewEnvironmentInfo = {
|
|
||||||
environmentSlug: string;
|
|
||||||
environmentName: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This route deletes a specified workspace.
|
|
||||||
* @param {*} workspaceId
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const createEnvironment = (workspaceId: string, newEnv: NewEnvironmentInfo) =>
|
|
||||||
SecurityClient.fetchCall(`/api/v2/workspace/${workspaceId}/environments`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
},
|
|
||||||
body: JSON.stringify(newEnv)
|
|
||||||
}).then(async (res) => {
|
|
||||||
if (res && res.status === 200) {
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
console.log("Failed to create environment");
|
|
||||||
return undefined;
|
|
||||||
});
|
|
||||||
|
|
||||||
export default createEnvironment;
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import SecurityClient from "@app/components/utilities/SecurityClient";
|
|
||||||
/**
|
|
||||||
* This route deletes a specified env.
|
|
||||||
* @param {*} workspaceId
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const deleteEnvironment = (workspaceId: string, environmentSlug: string) =>
|
|
||||||
SecurityClient.fetchCall(`/api/v2/workspace/${workspaceId}/environments`, {
|
|
||||||
method: "DELETE",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ environmentSlug })
|
|
||||||
}).then(async (res) => {
|
|
||||||
if (res && res.status === 200) {
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
console.log("Failed to delete environment");
|
|
||||||
return undefined;
|
|
||||||
});
|
|
||||||
|
|
||||||
export default deleteEnvironment;
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user