mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 10:27:26 +00:00
Finish preliminary CRUD ops for service token v3, ServiceTokenV3Key structure
This commit is contained in:
@@ -1,51 +1,99 @@
|
|||||||
import { Request, Response } from "express";
|
import { Request, Response } from "express";
|
||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { ServiceTokenDataV3 } from "../../models";
|
import {
|
||||||
|
ServiceTokenDataV3,
|
||||||
|
ServiceTokenDataV3Key
|
||||||
|
} from "../../models";
|
||||||
import { validateRequest } from "../../helpers/validation";
|
import { validateRequest } from "../../helpers/validation";
|
||||||
import * as reqValidator from "../../validation/serviceTokenV3";
|
import * as reqValidator from "../../validation/serviceTokenV3";
|
||||||
import { createToken } from "../../helpers/auth";
|
import { createToken } from "../../helpers/auth";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create service token data
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
export const createServiceTokenData = async (req: Request, res: Response) => {
|
export const createServiceTokenData = async (req: Request, res: Response) => {
|
||||||
const {
|
const {
|
||||||
body: { name, workspaceId, publicKey }
|
body: {
|
||||||
|
name,
|
||||||
|
workspaceId,
|
||||||
|
publicKey,
|
||||||
|
scopes,
|
||||||
|
expiresIn,
|
||||||
|
encryptedKey, // for ServiceTokenDataV3Key
|
||||||
|
nonce // for ServiceTokenDataV3Key
|
||||||
|
}
|
||||||
} = await validateRequest(reqValidator.CreateServiceTokenV3, req);
|
} = await validateRequest(reqValidator.CreateServiceTokenV3, req);
|
||||||
|
|
||||||
|
let expiresAt;
|
||||||
|
if (expiresIn) {
|
||||||
|
expiresAt = new Date();
|
||||||
|
expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn);
|
||||||
|
}
|
||||||
|
|
||||||
const serviceTokenData = await new ServiceTokenDataV3({
|
const serviceTokenData = await new ServiceTokenDataV3({
|
||||||
name,
|
name,
|
||||||
workspace: new Types.ObjectId(workspaceId),
|
workspace: new Types.ObjectId(workspaceId),
|
||||||
publicKey,
|
publicKey,
|
||||||
isActive: false
|
scopes,
|
||||||
|
isActive: false,
|
||||||
|
expiresAt
|
||||||
}).save();
|
}).save();
|
||||||
|
|
||||||
console.log("the newly created serviceTokenDataV3: ", serviceTokenData);
|
await new ServiceTokenDataV3Key({
|
||||||
|
encryptedKey,
|
||||||
|
nonce,
|
||||||
|
sender: req.user._id,
|
||||||
|
serviceTokenData: serviceTokenData._id,
|
||||||
|
workspace: new Types.ObjectId(workspaceId)
|
||||||
|
}).save();
|
||||||
|
|
||||||
const token = createToken({
|
const token = createToken({
|
||||||
payload: {
|
payload: {
|
||||||
_id: serviceTokenData._id.toString()
|
_id: serviceTokenData._id.toString()
|
||||||
},
|
},
|
||||||
expiresIn: "5d",
|
expiresIn,
|
||||||
secret: "hello" // TODO: replace with real secret
|
secret: "hello" // TODO: replace with real secret
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("jwt token: ", token);
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
serviceTokenData,
|
serviceTokenData,
|
||||||
serviceToken: `proj_token.${token}`
|
serviceToken: `proj_token.${token}`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update service token data with id [serviceTokenDataId]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
export const updateServiceTokenData = async (req: Request, res: Response) => {
|
export const updateServiceTokenData = async (req: Request, res: Response) => {
|
||||||
const {
|
const {
|
||||||
params: { serviceTokenDataId },
|
params: { serviceTokenDataId },
|
||||||
body: { name, isActive }
|
body: {
|
||||||
|
name,
|
||||||
|
isActive,
|
||||||
|
scopes,
|
||||||
|
expiresIn
|
||||||
|
}
|
||||||
} = await validateRequest(reqValidator.UpdateServiceTokenV3, req);
|
} = await validateRequest(reqValidator.UpdateServiceTokenV3, req);
|
||||||
|
|
||||||
|
let expiresAt;
|
||||||
|
if (expiresIn) {
|
||||||
|
expiresAt = new Date();
|
||||||
|
expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn);
|
||||||
|
}
|
||||||
|
|
||||||
const serviceTokenData = await ServiceTokenDataV3.findByIdAndUpdate(
|
const serviceTokenData = await ServiceTokenDataV3.findByIdAndUpdate(
|
||||||
serviceTokenDataId,
|
serviceTokenDataId,
|
||||||
{
|
{
|
||||||
name,
|
name,
|
||||||
isActive
|
isActive,
|
||||||
|
scopes,
|
||||||
|
expiresAt
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
new: true
|
new: true
|
||||||
@@ -57,6 +105,12 @@ export const updateServiceTokenData = async (req: Request, res: Response) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete service token data with id [serviceTokenDataId]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
export const deleteServiceTokenData = async (req: Request, res: Response) => {
|
export const deleteServiceTokenData = async (req: Request, res: Response) => {
|
||||||
const {
|
const {
|
||||||
params: { serviceTokenDataId }
|
params: { serviceTokenDataId }
|
||||||
@@ -64,6 +118,12 @@ export const deleteServiceTokenData = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
const serviceTokenData = await ServiceTokenDataV3.findByIdAndDelete(serviceTokenDataId);
|
const serviceTokenData = await ServiceTokenDataV3.findByIdAndDelete(serviceTokenDataId);
|
||||||
|
|
||||||
|
if (serviceTokenData) {
|
||||||
|
await ServiceTokenDataV3Key.findOneAndDelete({
|
||||||
|
serviceTokenData: serviceTokenData._id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
serviceTokenData
|
serviceTokenData
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -382,11 +382,15 @@ export const createToken = ({
|
|||||||
secret,
|
secret,
|
||||||
}: {
|
}: {
|
||||||
payload: any;
|
payload: any;
|
||||||
expiresIn: string | number;
|
expiresIn?: string | number;
|
||||||
secret: string;
|
secret: string;
|
||||||
}) => {
|
}) => {
|
||||||
return jwt.sign(payload, secret, {
|
return jwt.sign(payload, secret, {
|
||||||
expiresIn,
|
...(
|
||||||
|
(expiresIn !== undefined && expiresIn !== null)
|
||||||
|
? { expiresIn }
|
||||||
|
: {}
|
||||||
|
)
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -29,3 +29,4 @@ export * from "./loginSRPDetail";
|
|||||||
export * from "./tokenVersion";
|
export * from "./tokenVersion";
|
||||||
export * from "./webhooks";
|
export * from "./webhooks";
|
||||||
export * from "./serviceTokenDataV3";
|
export * from "./serviceTokenDataV3";
|
||||||
|
export * from "./serviceTokenDataV3Key";
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// TODO: deprecate
|
||||||
import { Schema, Types, model } from "mongoose";
|
import { Schema, Types, model } from "mongoose";
|
||||||
export interface IServiceToken {
|
export interface IServiceToken {
|
||||||
_id: Types.ObjectId;
|
_id: Types.ObjectId;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// TODO: deprecate
|
||||||
import { Document, Schema, Types, model } from "mongoose";
|
import { Document, Schema, Types, model } from "mongoose";
|
||||||
|
|
||||||
export interface IServiceTokenData extends Document {
|
export interface IServiceTokenData extends Document {
|
||||||
|
|||||||
@@ -1,5 +1,16 @@
|
|||||||
import { Document, Schema, Types, model } from "mongoose";
|
import { Document, Schema, Types, model } from "mongoose";
|
||||||
|
|
||||||
|
enum Permission {
|
||||||
|
READ = "read",
|
||||||
|
READ_WRITE = "readWrite"
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Scope {
|
||||||
|
environment: string;
|
||||||
|
secretPath: string;
|
||||||
|
permission: Permission;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IServiceTokenDataV3 extends Document {
|
export interface IServiceTokenDataV3 extends Document {
|
||||||
_id: Types.ObjectId;
|
_id: Types.ObjectId;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -7,6 +18,7 @@ export interface IServiceTokenDataV3 extends Document {
|
|||||||
publicKey: string;
|
publicKey: string;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
lastUsed: Date;
|
lastUsed: Date;
|
||||||
|
scopes: Array<Scope>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const serviceTokenDataV3Schema = new Schema(
|
const serviceTokenDataV3Schema = new Schema(
|
||||||
@@ -31,6 +43,32 @@ const serviceTokenDataV3Schema = new Schema(
|
|||||||
lastUsed: {
|
lastUsed: {
|
||||||
type: Date,
|
type: Date,
|
||||||
required: false
|
required: false
|
||||||
|
},
|
||||||
|
expiresAt: {
|
||||||
|
type: Date,
|
||||||
|
required: false,
|
||||||
|
expires: 0
|
||||||
|
},
|
||||||
|
scopes: {
|
||||||
|
type: [
|
||||||
|
{
|
||||||
|
environment: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
secretPath: {
|
||||||
|
type: String,
|
||||||
|
default: "/",
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
permission: {
|
||||||
|
type: String,
|
||||||
|
enum: [Permission.READ, Permission.READ_WRITE],
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
required: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { Document, Schema, Types, model } from "mongoose";
|
||||||
|
|
||||||
|
export interface IServiceTokenDataV3Key extends Document {
|
||||||
|
_id: Types.ObjectId;
|
||||||
|
encryptedKey: string;
|
||||||
|
nonce: string;
|
||||||
|
sender: Types.ObjectId;
|
||||||
|
serviceTokenData: Types.ObjectId;
|
||||||
|
workspace: Types.ObjectId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const serviceTokenDataV3KeySchema = new Schema(
|
||||||
|
{
|
||||||
|
encryptedKey: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
nonce: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
sender: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: "User",
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
serviceTokenData: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: "ServiceTokenDataV3",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
workspace: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: "Workspace",
|
||||||
|
required: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timestamps: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const ServiceTokenDataV3Key = model<IServiceTokenDataV3Key>("ServiceTokenDataV3Key", serviceTokenDataV3KeySchema);
|
||||||
@@ -5,6 +5,17 @@ export const CreateServiceTokenV3 = z.object({
|
|||||||
name: z.string().trim(),
|
name: z.string().trim(),
|
||||||
workspaceId: z.string().trim(),
|
workspaceId: z.string().trim(),
|
||||||
publicKey: z.string().trim(),
|
publicKey: z.string().trim(),
|
||||||
|
scopes: z
|
||||||
|
.object({
|
||||||
|
permission: z.enum(["read", "readWrite"]),
|
||||||
|
environment: z.string().trim(),
|
||||||
|
secretPath: z.string().trim()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
.min(1),
|
||||||
|
expiresIn: z.number().optional(),
|
||||||
|
encryptedKey: z.string().trim(),
|
||||||
|
nonce: z.string().trim()
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -14,8 +25,18 @@ export const UpdateServiceTokenV3 = z.object({
|
|||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
name: z.string().trim().optional(),
|
name: z.string().trim().optional(),
|
||||||
isActive: z.boolean().optional()
|
isActive: z.boolean().optional(),
|
||||||
})
|
scopes: z
|
||||||
|
.object({
|
||||||
|
permission: z.enum(["read", "readWrite"]),
|
||||||
|
environment: z.string().trim(),
|
||||||
|
secretPath: z.string().trim()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
.min(1)
|
||||||
|
.optional(),
|
||||||
|
expiresIn: z.number().optional()
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const DeleteServiceTokenV3 = z.object({
|
export const DeleteServiceTokenV3 = z.object({
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export const ModalContent = forwardRef<HTMLDivElement, ModalContentProps>(
|
|||||||
<Card
|
<Card
|
||||||
isRounded
|
isRounded
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
"fixed top-1/2 left-1/2 z-[90] dark:[color-scheme:dark] max-h-screen overflow-y-auto thin-scrollbar max-w-lg -translate-y-2/4 -translate-x-2/4 animate-popIn border border-mineshaft-600 drop-shadow-2xl",
|
"fixed top-1/2 left-1/2 z-[90] dark:[color-scheme:dark] max-h-screen overflow-y-auto thin-scrollbar max-w-xl -translate-y-2/4 -translate-x-2/4 animate-popIn border border-mineshaft-600 drop-shadow-2xl",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -2,18 +2,17 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|||||||
|
|
||||||
import { apiRequest } from "@app/config/request";
|
import { apiRequest } from "@app/config/request";
|
||||||
|
|
||||||
|
import { workspaceKeys } from "../workspace/queries";
|
||||||
import {
|
import {
|
||||||
|
CreateServiceTokenDataV3DTO,
|
||||||
|
CreateServiceTokenDataV3Res,
|
||||||
CreateServiceTokenDTO,
|
CreateServiceTokenDTO,
|
||||||
CreateServiceTokenRes,
|
CreateServiceTokenRes,
|
||||||
|
DeleteServiceTokenDataV3DTO,
|
||||||
DeleteServiceTokenRes,
|
DeleteServiceTokenRes,
|
||||||
ServiceToken,
|
ServiceToken,
|
||||||
ServiceTokenDataV3,
|
ServiceTokenDataV3,
|
||||||
CreateServiceTokenDataV3DTO,
|
UpdateServiceTokenDataV3DTO} from "./types";
|
||||||
CreateServiceTokenDataV3Res,
|
|
||||||
UpdateServiceTokenDataV3DTO,
|
|
||||||
DeleteServiceTokenDataV3DTO
|
|
||||||
} from "./types";
|
|
||||||
import { workspaceKeys } from "../workspace/queries";
|
|
||||||
|
|
||||||
const serviceTokenKeys = {
|
const serviceTokenKeys = {
|
||||||
getAllWorkspaceServiceToken: (workspaceID: string) => [{ workspaceID }, "service-tokens"] as const
|
getAllWorkspaceServiceToken: (workspaceID: string) => [{ workspaceID }, "service-tokens"] as const
|
||||||
@@ -58,7 +57,6 @@ 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;
|
||||||
},
|
},
|
||||||
@@ -87,11 +85,15 @@ export const useUpdateServiceTokenV3 = () => {
|
|||||||
mutationFn: async ({
|
mutationFn: async ({
|
||||||
serviceTokenDataId,
|
serviceTokenDataId,
|
||||||
name,
|
name,
|
||||||
isActive
|
isActive,
|
||||||
|
scopes,
|
||||||
|
expiresIn
|
||||||
}) => {
|
}) => {
|
||||||
const { data: { serviceTokenData } } = await apiRequest.patch(`/api/v3/service-token/${serviceTokenDataId}`, {
|
const { data: { serviceTokenData } } = await apiRequest.patch(`/api/v3/service-token/${serviceTokenDataId}`, {
|
||||||
name,
|
name,
|
||||||
isActive
|
isActive,
|
||||||
|
scopes,
|
||||||
|
expiresIn
|
||||||
});
|
});
|
||||||
|
|
||||||
return serviceTokenData;
|
return serviceTokenData;
|
||||||
@@ -108,13 +110,10 @@ export const useDeleteServiceTokenV3 = () => {
|
|||||||
mutationFn: async ({
|
mutationFn: async ({
|
||||||
serviceTokenDataId
|
serviceTokenDataId
|
||||||
}) => {
|
}) => {
|
||||||
console.log("useDeleteServiceTokenV3");
|
|
||||||
const { data: { serviceTokenData } } = await apiRequest.delete(`/api/v3/service-token/${serviceTokenDataId}`);
|
const { data: { serviceTokenData } } = await apiRequest.delete(`/api/v3/service-token/${serviceTokenDataId}`);
|
||||||
console.log("useDeleteServiceTokenV3 serviceTokenData: ", serviceTokenData);
|
|
||||||
return serviceTokenData;
|
return serviceTokenData;
|
||||||
},
|
},
|
||||||
onSuccess: ({ workspace }) => {
|
onSuccess: ({ workspace }) => {
|
||||||
console.log("useDeleteServiceTokenV3 onSuccess: ", workspace);
|
|
||||||
queryClient.invalidateQueries(workspaceKeys.getWorkspaceServiceTokenDataV3(workspace));
|
queryClient.invalidateQueries(workspaceKeys.getWorkspaceServiceTokenDataV3(workspace));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -36,22 +36,32 @@ export type DeleteServiceTokenRes = { serviceTokenData: ServiceToken };
|
|||||||
|
|
||||||
// --- v3
|
// --- v3
|
||||||
|
|
||||||
|
export type ServiceTokenV3Scope = {
|
||||||
|
permission: string;
|
||||||
|
environment: string;
|
||||||
|
secretPath: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type ServiceTokenDataV3 = {
|
export type ServiceTokenDataV3 = {
|
||||||
_id: string;
|
_id: string;
|
||||||
name: string;
|
name: string;
|
||||||
workspace: string;
|
workspace: string;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
lastUsed?: string;
|
lastUsed?: string;
|
||||||
|
scopes: ServiceTokenV3Scope[];
|
||||||
|
expiresAt?: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: add scopes
|
|
||||||
// TODO: encrypted key info
|
|
||||||
export type CreateServiceTokenDataV3DTO = {
|
export type CreateServiceTokenDataV3DTO = {
|
||||||
name: string;
|
name: string;
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
publicKey: string;
|
publicKey: string;
|
||||||
|
scopes: ServiceTokenV3Scope[];
|
||||||
|
expiresIn?: number;
|
||||||
|
encryptedKey: string;
|
||||||
|
nonce: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CreateServiceTokenDataV3Res = {
|
export type CreateServiceTokenDataV3Res = {
|
||||||
@@ -61,8 +71,10 @@ export type CreateServiceTokenDataV3Res = {
|
|||||||
|
|
||||||
export type UpdateServiceTokenDataV3DTO = {
|
export type UpdateServiceTokenDataV3DTO = {
|
||||||
serviceTokenDataId: string;
|
serviceTokenDataId: string;
|
||||||
name?: string;
|
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
|
name?: string;
|
||||||
|
scopes?: ServiceTokenV3Scope[];
|
||||||
|
expiresIn?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DeleteServiceTokenDataV3DTO = {
|
export type DeleteServiceTokenDataV3DTO = {
|
||||||
|
|||||||
+4
-1
@@ -88,7 +88,10 @@ export const AddServiceTokenModal = ({ popUp, handlePopUpToggle }: Props) => {
|
|||||||
} = useForm<FormData>({
|
} = useForm<FormData>({
|
||||||
resolver: yupResolver(schema),
|
resolver: yupResolver(schema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
scopes: [{ secretPath: "/", environment: currentWorkspace?.environments?.[0]?.slug }]
|
scopes: [{
|
||||||
|
secretPath: "/",
|
||||||
|
environment: currentWorkspace?.environments?.[0]?.slug
|
||||||
|
}]
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -43,7 +43,7 @@ export const ServiceTokenTable = ({ handlePopUpOpen }: Props) => {
|
|||||||
<THead>
|
<THead>
|
||||||
<Tr>
|
<Tr>
|
||||||
<Th>Token Name</Th>
|
<Th>Token Name</Th>
|
||||||
<Th>Envrionment - Secret Path</Th>
|
<Th>Environment - Secret Path</Th>
|
||||||
<Th>Valid Until</Th>
|
<Th>Valid Until</Th>
|
||||||
<Th aria-label="button" />
|
<Th aria-label="button" />
|
||||||
</Tr>
|
</Tr>
|
||||||
|
|||||||
+332
-84
@@ -1,23 +1,41 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import { Controller, useFieldArray, useForm } from "react-hook-form";
|
||||||
|
import { faPlus,faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { yupResolver } from "@hookform/resolvers/yup";
|
||||||
import nacl from "tweetnacl";
|
import nacl from "tweetnacl";
|
||||||
import { encodeBase64 } from "tweetnacl-util";
|
import { encodeBase64 } from "tweetnacl-util";
|
||||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
import * as yup from "yup";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
|
||||||
import { useWorkspace } from "@app/context";
|
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||||
import {
|
import {
|
||||||
|
decryptAssymmetric,
|
||||||
|
encryptAssymmetric
|
||||||
|
} from "@app/components/utilities/cryptography/crypto";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
FormControl,
|
||||||
|
IconButton,
|
||||||
|
Input,
|
||||||
Modal,
|
Modal,
|
||||||
ModalContent,
|
ModalContent,
|
||||||
FormControl,
|
|
||||||
Select,
|
Select,
|
||||||
SelectItem,
|
SelectItem,
|
||||||
Input,
|
// Accordion,
|
||||||
Button
|
// AccordionItem,
|
||||||
|
// AccordionTrigger,
|
||||||
|
// AccordionContent
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
import { useWorkspace } from "@app/context";
|
||||||
import { yupResolver } from "@hookform/resolvers/yup";
|
import {
|
||||||
import * as yup from "yup";
|
useCreateServiceTokenV3,
|
||||||
import { useCreateServiceTokenV3 } from "@app/hooks/api";
|
useGetUserWsKey,
|
||||||
|
useUpdateServiceTokenV3
|
||||||
|
} from "@app/hooks/api";
|
||||||
|
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
const expirations = [
|
const expirations = [
|
||||||
|
{ label: "Never", value: undefined },
|
||||||
{ label: "1 day", value: "86400" },
|
{ label: "1 day", value: "86400" },
|
||||||
{ label: "7 days", value: "604800" },
|
{ label: "7 days", value: "604800" },
|
||||||
{ label: "1 month", value: "2592000" },
|
{ label: "1 month", value: "2592000" },
|
||||||
@@ -25,88 +43,172 @@ const expirations = [
|
|||||||
{ label: "12 months", value: "31104000" }
|
{ label: "12 months", value: "31104000" }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const permissionValues: Array<"read" | "readWrite"> = ["read", "readWrite"];
|
||||||
|
|
||||||
const schema = yup.object({
|
const schema = yup.object({
|
||||||
name: yup.string().required("ST V3 name is required"),
|
name: yup.string().required("ST V3 name is required"),
|
||||||
expiresIn: yup.string().required("ST V3 expiration window is required")
|
expiresIn: yup.string(),
|
||||||
|
scopes: yup
|
||||||
|
.array(
|
||||||
|
yup.object({
|
||||||
|
permission: yup.string().oneOf(permissionValues, "Invalid permission").required().label("Permission"),
|
||||||
|
environment: yup.string().max(50).required().label("Environment"),
|
||||||
|
secretPath: yup
|
||||||
|
.string()
|
||||||
|
.required()
|
||||||
|
.default("/")
|
||||||
|
.label("Secret Path")
|
||||||
|
.transform((val) =>
|
||||||
|
typeof val === "string" && val.at(-1) === "/" && val.length > 1 ? val.slice(0, -1) : val
|
||||||
|
)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.min(1)
|
||||||
|
.required()
|
||||||
|
.label("Scope")
|
||||||
}).required();
|
}).required();
|
||||||
|
|
||||||
export type FormData = yup.InferType<typeof schema>;
|
export type FormData = yup.InferType<typeof schema>;
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
popUp: UsePopUpState<["createServiceTokenV3"]>;
|
popUp: UsePopUpState<["serviceTokenV3"]>;
|
||||||
handlePopUpToggle: (popUpName: keyof UsePopUpState<["createServiceTokenV3"]>, state?: boolean) => void;
|
handlePopUpToggle: (popUpName: keyof UsePopUpState<["serviceTokenV3"]>, state?: boolean) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Will download a JSON
|
|
||||||
// Maybe you can set a timer at which point service token is no longer active!
|
|
||||||
// Maybe you can also set IP allowlist for it too
|
|
||||||
|
|
||||||
export const AddServiceTokenV3Modal = ({
|
export const AddServiceTokenV3Modal = ({
|
||||||
popUp,
|
popUp,
|
||||||
handlePopUpToggle
|
handlePopUpToggle
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const { currentWorkspace } = useWorkspace();
|
const { currentWorkspace } = useWorkspace();
|
||||||
const { mutateAsync } = useCreateServiceTokenV3();
|
|
||||||
|
const { data: latestFileKey } = useGetUserWsKey(currentWorkspace?._id ?? "");
|
||||||
|
const { mutateAsync: createMutateAsync } = useCreateServiceTokenV3();
|
||||||
|
const { mutateAsync: updateMutateAsync } = useUpdateServiceTokenV3();
|
||||||
const { createNotification } = useNotificationContext();
|
const { createNotification } = useNotificationContext();
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
reset
|
reset,
|
||||||
|
formState: { isSubmitting }
|
||||||
} = useForm<FormData>({
|
} = useForm<FormData>({
|
||||||
resolver: yupResolver(schema)
|
resolver: yupResolver(schema),
|
||||||
|
defaultValues: {
|
||||||
|
name: "",
|
||||||
|
scopes: [{
|
||||||
|
permission: "read",
|
||||||
|
environment: currentWorkspace?.environments?.[0]?.slug,
|
||||||
|
secretPath: "/",
|
||||||
|
}]
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const serviceTokenData = popUp?.serviceTokenV3?.data as {
|
||||||
|
serviceTokenDataId: string;
|
||||||
|
name: string;
|
||||||
|
scopes: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (serviceTokenData) {
|
||||||
|
reset({
|
||||||
|
name: serviceTokenData.name,
|
||||||
|
scopes: serviceTokenData.scopes
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
reset({
|
||||||
|
name: "",
|
||||||
|
scopes: [{
|
||||||
|
permission: "read",
|
||||||
|
environment: currentWorkspace?.environments?.[0]?.slug,
|
||||||
|
secretPath: "/",
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [popUp?.serviceTokenV3?.data]);
|
||||||
|
|
||||||
|
const { fields: tokenScopes, append, remove } = useFieldArray({ control, name: "scopes" });
|
||||||
|
|
||||||
const onFormSubmit = async ({
|
const onFormSubmit = async ({
|
||||||
name,
|
name,
|
||||||
expiresIn
|
expiresIn,
|
||||||
|
scopes
|
||||||
}: FormData) => {
|
}: FormData) => {
|
||||||
try {
|
try {
|
||||||
if (!currentWorkspace?._id) return;
|
const serviceTokenData = popUp?.serviceTokenV3?.data as {
|
||||||
|
serviceTokenDataId: string;
|
||||||
console.log("onFormSubmit name: ", name);
|
name: string;
|
||||||
console.log("onFormSubmit expiresIn: ", expiresIn);
|
scopes: any;
|
||||||
|
|
||||||
const pair = nacl.box.keyPair();
|
|
||||||
const secretKeyUint8Array = pair.secretKey;
|
|
||||||
const publicKeyUint8Array = pair.publicKey;
|
|
||||||
const privateKey = encodeBase64(secretKeyUint8Array);
|
|
||||||
const publicKey = encodeBase64(publicKeyUint8Array);
|
|
||||||
|
|
||||||
console.log("pair: ", pair);
|
|
||||||
console.log("privateKey: ", privateKey);
|
|
||||||
console.log("publicKey: ", publicKey );
|
|
||||||
const { serviceToken } = await mutateAsync({
|
|
||||||
name,
|
|
||||||
workspaceId: currentWorkspace._id,
|
|
||||||
publicKey
|
|
||||||
});
|
|
||||||
|
|
||||||
const downloadData = {
|
|
||||||
publicKey,
|
|
||||||
privateKey,
|
|
||||||
serviceToken
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const blob = new Blob([JSON.stringify(downloadData, null, 2)], { type: 'application/json' });
|
if (serviceTokenData) {
|
||||||
const href = URL.createObjectURL(blob);
|
// update
|
||||||
const link = document.createElement('a');
|
await updateMutateAsync({
|
||||||
link.href = href;
|
serviceTokenDataId: serviceTokenData.serviceTokenDataId,
|
||||||
link.download = `infisical_${name}.json`;
|
name,
|
||||||
document.body.appendChild(link);
|
scopes,
|
||||||
link.click();
|
expiresIn: expiresIn === "" ? undefined : Number(expiresIn)
|
||||||
document.body.removeChild(link);
|
});
|
||||||
|
} else {
|
||||||
|
// create
|
||||||
|
if (!currentWorkspace?._id) return;
|
||||||
|
if (!latestFileKey) return;
|
||||||
|
|
||||||
|
const pair = nacl.box.keyPair();
|
||||||
|
const secretKeyUint8Array = pair.secretKey;
|
||||||
|
const publicKeyUint8Array = pair.publicKey;
|
||||||
|
const privateKey = encodeBase64(secretKeyUint8Array);
|
||||||
|
const publicKey = encodeBase64(publicKeyUint8Array);
|
||||||
|
|
||||||
|
const key = decryptAssymmetric({
|
||||||
|
ciphertext: latestFileKey.encryptedKey,
|
||||||
|
nonce: latestFileKey.nonce,
|
||||||
|
publicKey: latestFileKey.sender.publicKey,
|
||||||
|
privateKey: localStorage.getItem("PRIVATE_KEY") as string
|
||||||
|
});
|
||||||
|
|
||||||
|
const { ciphertext, nonce } = encryptAssymmetric({
|
||||||
|
plaintext: key,
|
||||||
|
publicKey,
|
||||||
|
privateKey: localStorage.getItem("PRIVATE_KEY") as string
|
||||||
|
});
|
||||||
|
|
||||||
|
const { serviceToken } = await createMutateAsync({
|
||||||
|
name,
|
||||||
|
workspaceId: currentWorkspace._id,
|
||||||
|
publicKey,
|
||||||
|
scopes,
|
||||||
|
expiresIn: expiresIn === "" ? undefined : Number(expiresIn),
|
||||||
|
encryptedKey: ciphertext,
|
||||||
|
nonce
|
||||||
|
});
|
||||||
|
|
||||||
|
const downloadData = {
|
||||||
|
publicKey,
|
||||||
|
privateKey,
|
||||||
|
serviceToken
|
||||||
|
};
|
||||||
|
|
||||||
|
const blob = new Blob([JSON.stringify(downloadData, null, 2)], { type: "application/json" });
|
||||||
|
const href = URL.createObjectURL(blob);
|
||||||
|
const link = document.createElement("a");
|
||||||
|
link.href = href;
|
||||||
|
link.download = `infisical_${name}.json`;
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
}
|
||||||
|
|
||||||
createNotification({
|
createNotification({
|
||||||
text: "Successfully created ST V3",
|
text: `Successfully ${popUp?.serviceTokenV3?.data ? "updated" : "created"} ST V3`,
|
||||||
type: "success"
|
type: "success"
|
||||||
});
|
});
|
||||||
|
|
||||||
reset();
|
reset();
|
||||||
handlePopUpToggle("createServiceTokenV3", false);
|
handlePopUpToggle("serviceTokenV3", false);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
createNotification({
|
createNotification({
|
||||||
text: "Failed to create ST V3",
|
text: `Failed to ${popUp?.serviceTokenV3?.data ? "updated" : "created"} ST V3`,
|
||||||
type: "error"
|
type: "error"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -114,13 +216,13 @@ export const AddServiceTokenV3Modal = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={popUp?.createServiceTokenV3?.isOpen}
|
isOpen={popUp?.serviceTokenV3?.isOpen}
|
||||||
onOpenChange={(isOpen) => {
|
onOpenChange={(isOpen) => {
|
||||||
handlePopUpToggle("createServiceTokenV3", isOpen);
|
handlePopUpToggle("serviceTokenV3", isOpen);
|
||||||
reset();
|
reset();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ModalContent title="Create Service Token V3">
|
<ModalContent title={`${popUp?.serviceTokenV3?.data ? "Update" : "Create"} Service Token V3`}>
|
||||||
<form onSubmit={handleSubmit(onFormSubmit)}>
|
<form onSubmit={handleSubmit(onFormSubmit)}>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
@@ -139,38 +241,184 @@ export const AddServiceTokenV3Modal = ({
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<Controller
|
{tokenScopes.map(({ id }, index) => (
|
||||||
control={control}
|
<div className="flex items-end space-x-2 mb-3" key={id}>
|
||||||
name="expiresIn"
|
<Controller
|
||||||
defaultValue="15552000"
|
control={control}
|
||||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
name={`scopes.${index}.permission`}
|
||||||
<FormControl
|
defaultValue={currentWorkspace?.environments?.[0]?.slug}
|
||||||
label="Expiration"
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||||
errorText={error?.message}
|
<FormControl
|
||||||
isError={Boolean(error)}
|
className="mb-0"
|
||||||
|
label={index === 0 ? "Permission" : undefined}
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
defaultValue={field.value}
|
||||||
|
{...field}
|
||||||
|
onValueChange={(e) => onChange(e)}
|
||||||
|
className="w-36"
|
||||||
|
>
|
||||||
|
<SelectItem value="read" key="st-v3-read">
|
||||||
|
Read
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="readWrite" key="st-v3-write">
|
||||||
|
Read & Write
|
||||||
|
</SelectItem>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name={`scopes.${index}.environment`}
|
||||||
|
defaultValue={currentWorkspace?.environments?.[0]?.slug}
|
||||||
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
className="mb-0"
|
||||||
|
label={index === 0 ? "Environment" : undefined}
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
defaultValue={field.value}
|
||||||
|
{...field}
|
||||||
|
onValueChange={(e) => onChange(e)}
|
||||||
|
className="w-36"
|
||||||
|
>
|
||||||
|
{currentWorkspace?.environments.map(({ name, slug }) => (
|
||||||
|
<SelectItem value={slug} key={slug}>
|
||||||
|
{name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name={`scopes.${index}.secretPath`}
|
||||||
|
defaultValue="/"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
className="mb-0 flex-grow"
|
||||||
|
label={index === 0 ? "Secrets Path" : undefined}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input {...field} placeholder="can be /, /nested/**, /**/deep" />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => remove(index)}
|
||||||
|
size="lg"
|
||||||
|
colorSchema="danger"
|
||||||
|
variant="plain"
|
||||||
|
ariaLabel="update"
|
||||||
|
className="p-3"
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faXmark} />
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<div className="my-4 ml-1">
|
||||||
|
<Button
|
||||||
|
variant="outline_bg"
|
||||||
|
onClick={() =>
|
||||||
|
append({
|
||||||
|
permission: "read",
|
||||||
|
environment: currentWorkspace?.environments?.[0]?.slug || "",
|
||||||
|
secretPath: "/"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||||
|
size="xs"
|
||||||
>
|
>
|
||||||
<Select
|
Add Scope
|
||||||
defaultValue={field.value}
|
</Button>
|
||||||
{...field}
|
</div>
|
||||||
onValueChange={(e) => onChange(e)}
|
<Controller
|
||||||
className="w-full"
|
control={control}
|
||||||
|
name="expiresIn"
|
||||||
|
defaultValue="15552000"
|
||||||
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label={`${popUp?.serviceTokenV3?.data ? "Update" : ""} Expire In`}
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
className="mt-4"
|
||||||
>
|
>
|
||||||
{expirations.map(({ label, value }) => (
|
<Select
|
||||||
<SelectItem value={String(value || "")} key={`api-key-expiration-${label}`}>
|
defaultValue={field.value}
|
||||||
{label}
|
{...field}
|
||||||
</SelectItem>
|
onValueChange={(e) => onChange(e)}
|
||||||
))}
|
className="w-full"
|
||||||
</Select>
|
>
|
||||||
</FormControl>
|
{expirations.map(({ label, value }) => (
|
||||||
)}
|
<SelectItem value={String(value || "")} key={`api-key-expiration-${label}`}>
|
||||||
/>
|
{label}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{/* <Accordion
|
||||||
|
type="multiple"
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
<AccordionItem value="section-1">
|
||||||
|
<AccordionTrigger>Scopes</AccordionTrigger>
|
||||||
|
<AccordionContent>Description of Section 1</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion> */}
|
||||||
|
{/* <h3 className="text-mineshaft-400 text-sm mb-2">Temporariness</h3>
|
||||||
|
<Switch
|
||||||
|
id={`enable-ephemerality`}
|
||||||
|
onCheckedChange={(value) => setIsTemporary(value)}
|
||||||
|
isChecked={isTemporary}
|
||||||
|
>
|
||||||
|
<div className="w-96 mr-4">
|
||||||
|
<p className="text-gray-400 text-md">This token will be deactivated after your specified duration.</p>
|
||||||
|
</div>
|
||||||
|
</Switch>
|
||||||
|
{isTemporary && (
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="expiresIn"
|
||||||
|
defaultValue="15552000"
|
||||||
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Duration"
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
className="mt-4"
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
defaultValue={field.value}
|
||||||
|
{...field}
|
||||||
|
onValueChange={(e) => onChange(e)}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
{expirations.map(({ label, value }) => (
|
||||||
|
<SelectItem value={String(value || "")} key={`api-key-expiration-${label}`}>
|
||||||
|
{label}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)} */}
|
||||||
<div className="mt-8 flex items-center">
|
<div className="mt-8 flex items-center">
|
||||||
<Button
|
<Button
|
||||||
className="mr-4"
|
className="mr-4"
|
||||||
size="sm"
|
size="sm"
|
||||||
type="submit"
|
type="submit"
|
||||||
// isLoading={isLoading}
|
isLoading={isSubmitting}
|
||||||
// isDisabled={isLoading}
|
isDisabled={isSubmitting}
|
||||||
>
|
>
|
||||||
Create
|
Create
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
+55
-7
@@ -1,14 +1,47 @@
|
|||||||
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { AddServiceTokenV3Modal } from "./AddServiceTokenV3Modal";
|
|
||||||
import { ServiceTokenV3Table } from "./ServiceTokenV3Table";
|
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||||
import { Button } from "@app/components/v2";
|
import {
|
||||||
|
Button,
|
||||||
|
DeleteActionModal
|
||||||
|
} from "@app/components/v2";
|
||||||
|
import {
|
||||||
|
useDeleteServiceTokenV3
|
||||||
|
} from "@app/hooks/api";
|
||||||
import { usePopUp } from "@app/hooks/usePopUp";
|
import { usePopUp } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
|
import { AddServiceTokenV3Modal } from "./AddServiceTokenV3Modal";
|
||||||
|
import { ServiceTokenV3Table } from "./ServiceTokenV3Table";
|
||||||
|
|
||||||
export const ServiceTokenV3Section = () => {
|
export const ServiceTokenV3Section = () => {
|
||||||
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([
|
const { createNotification } = useNotificationContext();
|
||||||
"createServiceTokenV3"
|
const { mutateAsync: deleteMutateAsync } = useDeleteServiceTokenV3();
|
||||||
|
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||||
|
"serviceTokenV3",
|
||||||
|
"deleteServiceTokenV3"
|
||||||
] as const);
|
] as const);
|
||||||
|
|
||||||
|
const onDeleteServiceTokenDataSubmit = async (serviceTokenDataId: string) => {
|
||||||
|
try {
|
||||||
|
await deleteMutateAsync({
|
||||||
|
serviceTokenDataId
|
||||||
|
});
|
||||||
|
createNotification({
|
||||||
|
text: "Successfully deleted service token v3",
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
|
||||||
|
handlePopUpClose("deleteServiceTokenV3");
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
createNotification({
|
||||||
|
text: "Failed to delete service token v3",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
<div className="flex justify-between mb-8">
|
<div className="flex justify-between mb-8">
|
||||||
@@ -19,16 +52,31 @@ export const ServiceTokenV3Section = () => {
|
|||||||
colorSchema="secondary"
|
colorSchema="secondary"
|
||||||
type="submit"
|
type="submit"
|
||||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||||
onClick={() => handlePopUpOpen("createServiceTokenV3")}
|
onClick={() => handlePopUpOpen("serviceTokenV3")}
|
||||||
>
|
>
|
||||||
Create ST V3
|
Create ST V3
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<ServiceTokenV3Table />
|
<ServiceTokenV3Table
|
||||||
|
handlePopUpOpen={handlePopUpOpen}
|
||||||
|
/>
|
||||||
<AddServiceTokenV3Modal
|
<AddServiceTokenV3Modal
|
||||||
popUp={popUp}
|
popUp={popUp}
|
||||||
handlePopUpToggle={handlePopUpToggle}
|
handlePopUpToggle={handlePopUpToggle}
|
||||||
/>
|
/>
|
||||||
|
<DeleteActionModal
|
||||||
|
isOpen={popUp.deleteServiceTokenV3.isOpen}
|
||||||
|
title={`Are you sure want to delete ${
|
||||||
|
(popUp?.deleteServiceTokenV3?.data as { name: string })?.name || ""
|
||||||
|
}?`}
|
||||||
|
onChange={(isOpen) => handlePopUpToggle("deleteServiceTokenV3", isOpen)}
|
||||||
|
deleteKey="confirm"
|
||||||
|
onDeleteApproved={() =>
|
||||||
|
onDeleteServiceTokenDataSubmit(
|
||||||
|
(popUp?.deleteServiceTokenV3?.data as { serviceTokenDataId: string })?.serviceTokenDataId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
+62
-44
@@ -1,6 +1,6 @@
|
|||||||
|
import { faKey, faPencil,faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { faKey, faXmark, faPencil } from "@fortawesome/free-solid-svg-icons";
|
|
||||||
import { useWorkspace } from "@app/context";
|
|
||||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||||
import {
|
import {
|
||||||
EmptyState,
|
EmptyState,
|
||||||
@@ -15,38 +15,34 @@ import {
|
|||||||
THead,
|
THead,
|
||||||
Tr
|
Tr
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
|
import { useWorkspace } from "@app/context";
|
||||||
import {
|
import {
|
||||||
useGetWorkspaceServiceTokenDataV3,
|
useGetWorkspaceServiceTokenDataV3,
|
||||||
useUpdateServiceTokenV3,
|
useUpdateServiceTokenV3
|
||||||
useDeleteServiceTokenV3
|
|
||||||
} from "@app/hooks/api";
|
} from "@app/hooks/api";
|
||||||
|
import {
|
||||||
|
ServiceTokenV3Scope
|
||||||
|
} from "@app/hooks/api/serviceTokens/types"
|
||||||
|
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
export const ServiceTokenV3Table = () => {
|
type Props = {
|
||||||
|
handlePopUpOpen: (
|
||||||
|
popUpName: keyof UsePopUpState<["deleteServiceTokenV3", "serviceTokenV3"]>,
|
||||||
|
data?: {
|
||||||
|
serviceTokenDataId?: string;
|
||||||
|
name?: string;
|
||||||
|
scopes?: ServiceTokenV3Scope[];
|
||||||
|
}
|
||||||
|
) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ServiceTokenV3Table = ({
|
||||||
|
handlePopUpOpen
|
||||||
|
}: Props) => {
|
||||||
const { createNotification } = useNotificationContext();
|
const { createNotification } = useNotificationContext();
|
||||||
const { currentWorkspace } = useWorkspace();
|
const { currentWorkspace } = useWorkspace();
|
||||||
const { data, isLoading } = useGetWorkspaceServiceTokenDataV3(currentWorkspace?._id || "");
|
const { data, isLoading } = useGetWorkspaceServiceTokenDataV3(currentWorkspace?._id || "");
|
||||||
const { mutateAsync: updateMutateAsync } = useUpdateServiceTokenV3();
|
const { mutateAsync: updateMutateAsync } = useUpdateServiceTokenV3();
|
||||||
const { mutateAsync: deleteMutateAsync } = useDeleteServiceTokenV3();
|
|
||||||
|
|
||||||
console.log("data1: ", data);
|
|
||||||
|
|
||||||
const handleDeleteServiceTokenData = async (serviceTokenDataId: string) => {
|
|
||||||
try {
|
|
||||||
await deleteMutateAsync({
|
|
||||||
serviceTokenDataId
|
|
||||||
});
|
|
||||||
createNotification({
|
|
||||||
text: "Successfully deleted service token v3",
|
|
||||||
type: "success"
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
createNotification({
|
|
||||||
text: "Failed to delete service token v3",
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleToggleServiceTokenDataStatus = async ({
|
const handleToggleServiceTokenDataStatus = async ({
|
||||||
serviceTokenDataId,
|
serviceTokenDataId,
|
||||||
@@ -92,10 +88,11 @@ export const ServiceTokenV3Table = () => {
|
|||||||
<Tr>
|
<Tr>
|
||||||
<Th>Name</Th>
|
<Th>Name</Th>
|
||||||
<Th>Status</Th>
|
<Th>Status</Th>
|
||||||
<Th>Last Active</Th>
|
<Th>Scopes</Th>
|
||||||
<Th>Created</Th>
|
<Th>Last Used</Th>
|
||||||
<Th>Expiration</Th>
|
<Th>Created At</Th>
|
||||||
<Th className="w-5"></Th>
|
<Th>Expires At</Th>
|
||||||
|
<Th className="w-5" />
|
||||||
</Tr>
|
</Tr>
|
||||||
</THead>
|
</THead>
|
||||||
<TBody>
|
<TBody>
|
||||||
@@ -108,32 +105,48 @@ export const ServiceTokenV3Table = () => {
|
|||||||
name,
|
name,
|
||||||
isActive,
|
isActive,
|
||||||
lastUsed,
|
lastUsed,
|
||||||
|
scopes,
|
||||||
createdAt,
|
createdAt,
|
||||||
// expiresAt
|
expiresAt
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<Tr className="h-10" key={`st-v3-${_id}`}>
|
<Tr className="h-10" key={`st-v3-${_id}`}>
|
||||||
<Td>{name}</Td>
|
<Td>{name}</Td>
|
||||||
<Td>
|
<Td>
|
||||||
<Switch
|
<Switch
|
||||||
id="test"
|
id={`enable-service-token-${_id}`}
|
||||||
// id={`enable-${authMethodOpt.value}-auth`}
|
onCheckedChange={(value) => handleToggleServiceTokenDataStatus({
|
||||||
onCheckedChange={(value) => handleToggleServiceTokenDataStatus({
|
serviceTokenDataId: _id,
|
||||||
serviceTokenDataId: _id,
|
isActive: value
|
||||||
isActive: value
|
})}
|
||||||
|
isChecked={isActive}
|
||||||
|
>
|
||||||
|
<p className="w-12 mr-4">{isActive ? "Active" : "Inactive"}</p>
|
||||||
|
</Switch>
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
{scopes.map((scope) => {
|
||||||
|
return (
|
||||||
|
<p key={`service-token-${_id}-scope-${scope.environment}-${scope.secretPath}`}>
|
||||||
|
<span className="font-bold">
|
||||||
|
{scope.permission}
|
||||||
|
</span>
|
||||||
|
{` @${scope.environment} - ${scope.secretPath}`}
|
||||||
|
</p>
|
||||||
|
);
|
||||||
})}
|
})}
|
||||||
isChecked={isActive}
|
|
||||||
>
|
|
||||||
<p className="w-12 mr-4">{isActive ? "Active" : "Inactive"}</p>
|
|
||||||
</Switch>
|
|
||||||
</Td>
|
</Td>
|
||||||
<Td>{lastUsed ? formatDate(lastUsed) : "-"}</Td>
|
<Td>{lastUsed ? formatDate(lastUsed) : "-"}</Td>
|
||||||
<Td>{formatDate(createdAt)}</Td>
|
<Td>{formatDate(createdAt)}</Td>
|
||||||
<Td>{formatDate(createdAt)}</Td>
|
<Td>{expiresAt ? formatDate(expiresAt) : "-"}</Td>
|
||||||
<Td className="flex justify-end">
|
<Td className="flex justify-end">
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
console.log("edit");
|
handlePopUpOpen("serviceTokenV3", {
|
||||||
|
serviceTokenDataId: _id,
|
||||||
|
name,
|
||||||
|
scopes,
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
size="lg"
|
size="lg"
|
||||||
colorSchema="primary"
|
colorSchema="primary"
|
||||||
@@ -143,7 +156,12 @@ export const ServiceTokenV3Table = () => {
|
|||||||
<FontAwesomeIcon icon={faPencil} />
|
<FontAwesomeIcon icon={faPencil} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={() => handleDeleteServiceTokenData(_id)}
|
onClick={() => {
|
||||||
|
handlePopUpOpen("deleteServiceTokenV3", {
|
||||||
|
serviceTokenDataId: _id,
|
||||||
|
name
|
||||||
|
});
|
||||||
|
}}
|
||||||
size="lg"
|
size="lg"
|
||||||
colorSchema="danger"
|
colorSchema="danger"
|
||||||
variant="plain"
|
variant="plain"
|
||||||
|
|||||||
Reference in New Issue
Block a user