mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
checkpoint
This commit is contained in:
44
backend/src/models/secretApproval.ts
Normal file
44
backend/src/models/secretApproval.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { Schema, Types, model } from "mongoose";
|
||||
|
||||
export interface ISecretApproval {
|
||||
_id: Types.ObjectId;
|
||||
workspace: Types.ObjectId;
|
||||
environment: string;
|
||||
secretPath?: string;
|
||||
approvers: Types.ObjectId[];
|
||||
approvals: number;
|
||||
}
|
||||
|
||||
const secretApprovalSchema = new Schema<ISecretApproval>(
|
||||
{
|
||||
workspace: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "Workspace",
|
||||
required: true
|
||||
},
|
||||
approvers: [
|
||||
{
|
||||
// user associated with the personal secret
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "Membership"
|
||||
}
|
||||
],
|
||||
environment: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
secretPath: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
approvals: {
|
||||
type: Number,
|
||||
default: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
timestamps: true
|
||||
}
|
||||
);
|
||||
|
||||
export const SecretApproval = model<ISecretApproval>("SecretApproval", secretApprovalSchema);
|
||||
@@ -1,81 +1,65 @@
|
||||
import mongoose, { Schema, model } from "mongoose";
|
||||
import { ISecret, Secret } from "./secret";
|
||||
import { Schema, Types, model } from "mongoose";
|
||||
import { ISecretVersion, SecretVersion } from "../ee/models/secretVersion";
|
||||
|
||||
interface ISecretApprovalRequest {
|
||||
secret: mongoose.Types.ObjectId;
|
||||
requestedChanges: ISecret;
|
||||
requestedBy: mongoose.Types.ObjectId;
|
||||
approvers: IApprover[];
|
||||
status: ApprovalStatus;
|
||||
timestamp: Date;
|
||||
requestType: RequestType;
|
||||
requestId: string;
|
||||
enum ApprovalStatus {
|
||||
PENDING = "pending",
|
||||
APPROVED = "approved",
|
||||
REJECTED = "rejected"
|
||||
}
|
||||
|
||||
interface IApprover {
|
||||
userId: mongoose.Types.ObjectId;
|
||||
status: ApprovalStatus;
|
||||
enum CommitType {
|
||||
DELETE = "delete",
|
||||
UPDATE = "update",
|
||||
CREATE = "create"
|
||||
}
|
||||
|
||||
export enum ApprovalStatus {
|
||||
PENDING = "pending",
|
||||
APPROVED = "approved",
|
||||
REJECTED = "rejected"
|
||||
export interface ISecretApprovalRequest {
|
||||
_id: Types.ObjectId;
|
||||
committer: Types.ObjectId;
|
||||
approvers: {
|
||||
member: Types.ObjectId;
|
||||
status: ApprovalStatus;
|
||||
}[];
|
||||
approvals: number;
|
||||
hasMerged: boolean;
|
||||
status: ApprovalStatus;
|
||||
commits: {
|
||||
secretVersion: Types.ObjectId;
|
||||
newVersion: ISecretVersion;
|
||||
op: CommitType;
|
||||
}[];
|
||||
}
|
||||
|
||||
export enum RequestType {
|
||||
UPDATE = "update",
|
||||
DELETE = "delete",
|
||||
CREATE = "create"
|
||||
}
|
||||
|
||||
const approverSchema = new mongoose.Schema({
|
||||
user: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: "User",
|
||||
required: true,
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
enum: [ApprovalStatus],
|
||||
default: ApprovalStatus.PENDING,
|
||||
},
|
||||
});
|
||||
|
||||
const secretApprovalRequestSchema = new Schema<ISecretApprovalRequest>(
|
||||
{
|
||||
secret: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: "Secret",
|
||||
},
|
||||
requestedChanges: Secret,
|
||||
requestedBy: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: "User",
|
||||
},
|
||||
approvers: [approverSchema],
|
||||
status: {
|
||||
type: String,
|
||||
enum: ApprovalStatus,
|
||||
default: ApprovalStatus.PENDING,
|
||||
},
|
||||
timestamp: {
|
||||
type: Date,
|
||||
default: Date.now,
|
||||
},
|
||||
requestType: {
|
||||
type: String,
|
||||
enum: RequestType,
|
||||
required: true,
|
||||
},
|
||||
requestId: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
}
|
||||
const secretApprovalSchema = new Schema<ISecretApprovalRequest>(
|
||||
{
|
||||
approvers: [
|
||||
{
|
||||
member: {
|
||||
// user associated with the personal secret
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "Membership"
|
||||
},
|
||||
status: { type: String, enum: ApprovalStatus, default: ApprovalStatus.PENDING }
|
||||
}
|
||||
],
|
||||
approvals: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
hasMerged: { type: Boolean, default: false },
|
||||
status: { type: String, enum: ApprovalStatus, default: ApprovalStatus.PENDING },
|
||||
committer: { type: Schema.Types.ObjectId, ref: "Membership" },
|
||||
commits: [
|
||||
{
|
||||
secretVersion: { type: Types.ObjectId, ref: "SecretVersion" },
|
||||
newVersion: SecretVersion,
|
||||
op: { type: String, enum: [CommitType], required: true }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
timestamps: true
|
||||
}
|
||||
);
|
||||
|
||||
export const SecretApprovalRequest = model<ISecretApprovalRequest>("SecretApprovalRequest", secretApprovalRequestSchema);
|
||||
export const SecretApproval = model<ISecretApprovalRequest>("SecretApproval", secretApprovalSchema);
|
||||
|
||||
@@ -107,6 +107,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"approval": {
|
||||
"title": "Admin Panel",
|
||||
"og-title": "Manage your secret change management",
|
||||
"og-description": "Infisical a simple end-to-end encrypted platform that enables teams to sync and manage their .env files."
|
||||
},
|
||||
"integrations": {
|
||||
"title": "Project Integrations",
|
||||
"description": "Manage your integrations of Infisical with third-party services.",
|
||||
|
||||
@@ -475,6 +475,18 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
||||
</MenuItem>
|
||||
</a>
|
||||
</Link>
|
||||
<Link href={`/project/${currentWorkspace?._id}/approval`} passHref>
|
||||
<a>
|
||||
<MenuItem
|
||||
isSelected={
|
||||
router.asPath === `/project/${currentWorkspace?._id}/allowlist`
|
||||
}
|
||||
icon="system-outline-126-verified"
|
||||
>
|
||||
Secret Change Management
|
||||
</MenuItem>
|
||||
</a>
|
||||
</Link>
|
||||
<Link href={`/project/${currentWorkspace?._id}/allowlist`} passHref>
|
||||
<a>
|
||||
<MenuItem
|
||||
|
||||
27
frontend/src/pages/project/[id]/approval/index.tsx
Normal file
27
frontend/src/pages/project/[id]/approval/index.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Head from "next/head";
|
||||
|
||||
import { SecretApprovalListPage } from "@app/views/SecretApproval/SecretApprovalListPage";
|
||||
|
||||
const SecretApproval = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{t("common.head-title", { title: t("approval.title") })}</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
<meta property="og:image" content="/images/message.png" />
|
||||
<meta property="og:title" content={String(t("approval.og-title"))} />
|
||||
<meta name="og:description" content={String(t("approval.og-description"))} />
|
||||
</Head>
|
||||
<div className="h-full">
|
||||
<SecretApprovalListPage />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SecretApproval;
|
||||
|
||||
SecretApproval.requireAuth = true;
|
||||
@@ -0,0 +1,73 @@
|
||||
import { faCheck, faCodeBranch, faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { Button, Table, TableContainer, TBody, Td, Th, THead, Tr } from "@app/components/v2";
|
||||
|
||||
export const SecretApprovalListPage = () => {
|
||||
return (
|
||||
<div className="container mx-auto bg-bunker-800 text-white w-full h-full">
|
||||
<div className="my-6">
|
||||
<p className="text-3xl font-semibold text-gray-200">Admin Panels</p>
|
||||
</div>
|
||||
<div className="rounded-md bg-mineshaft-800 text-gray-300">
|
||||
<div className="p-4 px-8 flex items-center space-x-8">
|
||||
<div>
|
||||
<FontAwesomeIcon icon={faCodeBranch} className="mr-2" />
|
||||
27 Open
|
||||
</div>
|
||||
<div className="text-gray-500">
|
||||
<FontAwesomeIcon icon={faCheck} className="mr-2" />
|
||||
27 Closed
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col border-t border-mineshaft-600">
|
||||
<div className="flex flex-col px-8 py-4">
|
||||
<div className="mb-1">
|
||||
<FontAwesomeIcon icon={faCodeBranch} className="mr-2" />2 secrets added and 1 deleted
|
||||
</div>
|
||||
<span className="text-xs text-gray-500">
|
||||
Opened 2 hours ago by akhilmhdh - Review required
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="my-4 p-4 rounded-md bg-bunker-600 text-gray-300">
|
||||
<div className="mb-4 text-lg">
|
||||
Request for <span className="text-yellow-600">secret change</span>
|
||||
</div>
|
||||
<div>
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<THead>
|
||||
<Tr>
|
||||
<Th className="min-table-row">Secret</Th>
|
||||
<Th>Value</Th>
|
||||
<Th className="min-table-row">Comment</Th>
|
||||
<Th className="min-table-row">Tags</Th>
|
||||
</Tr>
|
||||
</THead>
|
||||
<TBody>
|
||||
<Tr>
|
||||
<Td>TWILIO_SECRET_TOKEN</Td>
|
||||
<Td>value</Td>
|
||||
<Td>Some values</Td>
|
||||
<Td>-</Td>
|
||||
</Tr>
|
||||
</TBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</div>
|
||||
<div className="flex items-center mt-8 space-x-6">
|
||||
<Button leftIcon={<FontAwesomeIcon icon={faCheck} />}>Approve</Button>
|
||||
<Button
|
||||
variant="outline_bg"
|
||||
leftIcon={<FontAwesomeIcon icon={faXmark} />}
|
||||
colorSchema="danger"
|
||||
>
|
||||
Deny
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export { SecretApprovalListPage } from "./SecretApprovalListPage";
|
||||
Reference in New Issue
Block a user