mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 13:27:22 +00:00
Service tokens update on frontend
This commit is contained in:
@@ -190,7 +190,7 @@ export const getWorkspaceServiceTokenData = async (
|
|||||||
) => {
|
) => {
|
||||||
let serviceTokenData;
|
let serviceTokenData;
|
||||||
try {
|
try {
|
||||||
const { workspaceId } = req.query;
|
const { workspaceId } = req.params;
|
||||||
|
|
||||||
serviceTokenData = await ServiceTokenData
|
serviceTokenData = await ServiceTokenData
|
||||||
.find({
|
.find({
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { envMapping } from "../../../public/data/frequentConstants";
|
|||||||
import {
|
import {
|
||||||
decryptAssymmetric,
|
decryptAssymmetric,
|
||||||
encryptAssymmetric,
|
encryptAssymmetric,
|
||||||
|
encryptSymmetric,
|
||||||
} from "../../utilities/cryptography/crypto";
|
} from "../../utilities/cryptography/crypto";
|
||||||
import Button from "../buttons/Button";
|
import Button from "../buttons/Button";
|
||||||
import InputField from "../InputField";
|
import InputField from "../InputField";
|
||||||
@@ -25,6 +26,8 @@ const expiryMapping = {
|
|||||||
"12 months": 31104000,
|
"12 months": 31104000,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const crypto = require('crypto');
|
||||||
|
|
||||||
const AddServiceTokenDialog = ({
|
const AddServiceTokenDialog = ({
|
||||||
isOpen,
|
isOpen,
|
||||||
closeModal,
|
closeModal,
|
||||||
@@ -48,16 +51,14 @@ const AddServiceTokenDialog = ({
|
|||||||
privateKey: localStorage.getItem("PRIVATE_KEY"),
|
privateKey: localStorage.getItem("PRIVATE_KEY"),
|
||||||
});
|
});
|
||||||
|
|
||||||
// generate new public/private key pair
|
const randomBytes = crypto.randomBytes(16).toString('hex');
|
||||||
const pair = nacl.box.keyPair();
|
const {
|
||||||
const publicKey = nacl.util.encodeBase64(pair.publicKey);
|
ciphertext,
|
||||||
const privateKey = nacl.util.encodeBase64(pair.secretKey);
|
iv,
|
||||||
|
tag,
|
||||||
// encrypt workspace key under newly-generated public key
|
} = encryptSymmetric({
|
||||||
const { ciphertext: encryptedKey, nonce } = encryptAssymmetric({
|
|
||||||
plaintext: key,
|
plaintext: key,
|
||||||
publicKey,
|
key: randomBytes,
|
||||||
privateKey,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let newServiceToken = await addServiceToken({
|
let newServiceToken = await addServiceToken({
|
||||||
@@ -65,13 +66,12 @@ const AddServiceTokenDialog = ({
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
environment: envMapping[serviceTokenEnv],
|
environment: envMapping[serviceTokenEnv],
|
||||||
expiresIn: expiryMapping[serviceTokenExpiresIn],
|
expiresIn: expiryMapping[serviceTokenExpiresIn],
|
||||||
publicKey,
|
encryptedKey: ciphertext,
|
||||||
encryptedKey,
|
iv,
|
||||||
nonce,
|
tag
|
||||||
});
|
});
|
||||||
|
|
||||||
const serviceToken = newServiceToken + "," + privateKey;
|
setServiceToken(newServiceToken);
|
||||||
setServiceToken(serviceToken);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function copyToClipboard() {
|
function copyToClipboard() {
|
||||||
@@ -161,7 +161,7 @@ const AddServiceTokenDialog = ({
|
|||||||
"Production",
|
"Production",
|
||||||
"Testing",
|
"Testing",
|
||||||
]}
|
]}
|
||||||
width="full"
|
isFull={true}
|
||||||
text={`${t("common:environment")}: `}
|
text={`${t("common:environment")}: `}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -176,7 +176,7 @@ const AddServiceTokenDialog = ({
|
|||||||
"6 months",
|
"6 months",
|
||||||
"12 months",
|
"12 months",
|
||||||
]}
|
]}
|
||||||
width="full"
|
isFull={true}
|
||||||
text={`${t("common:expired-in")}: `}
|
text={`${t("common:expired-in")}: `}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -211,7 +211,7 @@ const AddServiceTokenDialog = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<div className="flex justify-end items-center bg-white/[0.07] text-base mt-2 mr-2 rounded-md text-gray-400 w-full h-44">
|
<div className="flex justify-end items-center bg-white/[0.07] text-base mt-2 mr-2 rounded-md text-gray-400 w-full h-14">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={serviceToken}
|
value={serviceToken}
|
||||||
@@ -236,7 +236,7 @@ const AddServiceTokenDialog = ({
|
|||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
<span className="absolute hidden group-hover:flex group-hover:animate-popup duration-300 w-28 -left-8 -top-20 translate-y-full px-3 py-2 bg-chicago-900 rounded-md text-center text-gray-400 text-sm">
|
<span className="absolute hidden group-hover:flex group-hover:animate-popup duration-300 w-28 -left-8 -top-20 translate-y-full px-3 py-2 bg-chicago-900 rounded-md text-center text-gray-400 text-sm">
|
||||||
{t("common.click-to-copy")}
|
{t("common:click-to-copy")}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+34
-9
@@ -1,19 +1,37 @@
|
|||||||
import { useEffect, useState } from 'react';
|
|
||||||
import { useRouter } from 'next/router';
|
|
||||||
import { faX } from '@fortawesome/free-solid-svg-icons';
|
import { faX } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
|
import { useNotificationContext } from '~/components/context/Notifications/NotificationProvider';
|
||||||
|
|
||||||
|
import deleteServiceToken from "../../../pages/api/serviceToken/deleteServiceToken";
|
||||||
import { reverseEnvMapping } from '../../../public/data/frequentConstants';
|
import { reverseEnvMapping } from '../../../public/data/frequentConstants';
|
||||||
import guidGenerator from '../../utilities/randomId';
|
import guidGenerator from '../../utilities/randomId';
|
||||||
import Button from '../buttons/Button';
|
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 user table - in future, can reuse it for some other purposes too.
|
* This is the component that we utilize for the service token table
|
||||||
* #TODO: add the possibility of choosing and doing operations on multiple users.
|
* #TODO: add the possibility of choosing and doing operations on multiple users.
|
||||||
* @param {*} props
|
* @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
|
* @returns
|
||||||
*/
|
*/
|
||||||
const ServiceTokenTable = ({ data, workspaceName }) => {
|
const ServiceTokenTable = ({ data, workspaceName, setServiceTokens }: ServiceTokensProps) => {
|
||||||
const router = useRouter();
|
console.log(data)
|
||||||
|
const { createNotification } = useNotificationContext();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="table-container w-full bg-bunker rounded-md mb-6 border border-mineshaft-700 relative mt-1">
|
<div className="table-container w-full bg-bunker rounded-md mb-6 border border-mineshaft-700 relative mt-1">
|
||||||
@@ -30,7 +48,7 @@ const ServiceTokenTable = ({ data, workspaceName }) => {
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{data?.length > 0 ? (
|
{data?.length > 0 ? (
|
||||||
data.map((row, index) => {
|
data.map((row) => {
|
||||||
return (
|
return (
|
||||||
<tr
|
<tr
|
||||||
key={guidGenerator()}
|
key={guidGenerator()}
|
||||||
@@ -51,7 +69,14 @@ const ServiceTokenTable = ({ data, workspaceName }) => {
|
|||||||
<td className="py-2 border-mineshaft-700 border-t">
|
<td className="py-2 border-mineshaft-700 border-t">
|
||||||
<div className="opacity-50 hover:opacity-100 duration-200 flex items-center">
|
<div className="opacity-50 hover:opacity-100 duration-200 flex items-center">
|
||||||
<Button
|
<Button
|
||||||
onButtonPressed={() => {}}
|
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"
|
color="red"
|
||||||
size="icon-sm"
|
size="icon-sm"
|
||||||
icon={faX}
|
icon={faX}
|
||||||
@@ -63,7 +88,7 @@ const ServiceTokenTable = ({ data, workspaceName }) => {
|
|||||||
})
|
})
|
||||||
) : (
|
) : (
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan="4" className="text-center pt-7 pb-4 text-bunker-400">
|
<td className="text-center pt-7 pb-4 text-bunker-400 col-span-4">
|
||||||
No service tokens yet
|
No service tokens yet
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -5,14 +5,21 @@ interface Props {
|
|||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
environment: string;
|
environment: string;
|
||||||
expiresIn: number;
|
expiresIn: number;
|
||||||
publicKey: string;
|
|
||||||
encryptedKey: string;
|
encryptedKey: string;
|
||||||
nonce: string;
|
iv: string;
|
||||||
|
tag: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This route gets service tokens for a specific user in a project
|
* This route gets service tokens for a specific user in a project
|
||||||
* @param {*} param0
|
* @param {object} obj
|
||||||
|
* @param {string} obj.name - name of the service token
|
||||||
|
* @param {string} obj.workspaceId - workspace for which we are issuing the token
|
||||||
|
* @param {string} obj.environment - environment for which we are issuing the token
|
||||||
|
* @param {string} obj.expiresIn - how soon the service token expires in ms
|
||||||
|
* @param {string} obj.encryptedKey - encrypted project key through random symmetric encryption
|
||||||
|
* @param {string} obj.iv - obtained through symmetric encryption
|
||||||
|
* @param {string} obj.tag - obtained through symmetric encryption
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const addServiceToken = ({
|
const addServiceToken = ({
|
||||||
@@ -20,11 +27,11 @@ const addServiceToken = ({
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
environment,
|
environment,
|
||||||
expiresIn,
|
expiresIn,
|
||||||
publicKey,
|
|
||||||
encryptedKey,
|
encryptedKey,
|
||||||
nonce
|
iv,
|
||||||
|
tag
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
return SecurityClient.fetchCall('/api/v1/service-token/', {
|
return SecurityClient.fetchCall('/api/v2/service-token-data/', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
@@ -34,13 +41,13 @@ const addServiceToken = ({
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
environment,
|
environment,
|
||||||
expiresIn,
|
expiresIn,
|
||||||
publicKey,
|
|
||||||
encryptedKey,
|
encryptedKey,
|
||||||
nonce
|
iv,
|
||||||
|
tag
|
||||||
})
|
})
|
||||||
}).then(async (res) => {
|
}).then(async (res) => {
|
||||||
if (res && res.status == 200) {
|
if (res && res.status == 200) {
|
||||||
return (await res.json()).token;
|
return (await res.json()).serviceToken;
|
||||||
} else {
|
} else {
|
||||||
console.log('Failed to add service tokens');
|
console.log('Failed to add service tokens');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import SecurityClient from '~/utilities/SecurityClient';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
serviceTokenId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This route revokes a specific service token
|
||||||
|
* @param {object} obj
|
||||||
|
* @param {string} obj.serviceTokenId - id of a cervice token that we want to delete
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const deleteServiceToken = ({
|
||||||
|
serviceTokenId,
|
||||||
|
}: Props) => {
|
||||||
|
return SecurityClient.fetchCall('/api/v2/service-token-data/' + serviceTokenId, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
}).then(async (res) => {
|
||||||
|
if (res && res.status == 200) {
|
||||||
|
return (await res.json());
|
||||||
|
} else {
|
||||||
|
console.log('Failed to delete a service token');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default deleteServiceToken;
|
||||||
@@ -7,7 +7,7 @@ import SecurityClient from '~/utilities/SecurityClient';
|
|||||||
*/
|
*/
|
||||||
const getServiceTokens = ({ workspaceId }: { workspaceId: string }) => {
|
const getServiceTokens = ({ workspaceId }: { workspaceId: string }) => {
|
||||||
return SecurityClient.fetchCall(
|
return SecurityClient.fetchCall(
|
||||||
'/api/v1/workspace/' + workspaceId + '/service-tokens',
|
'/api/v2/workspace/' + workspaceId + '/service-token-data',
|
||||||
{
|
{
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
@@ -16,7 +16,7 @@ const getServiceTokens = ({ workspaceId }: { workspaceId: string }) => {
|
|||||||
}
|
}
|
||||||
).then(async (res) => {
|
).then(async (res) => {
|
||||||
if (res && res.status == 200) {
|
if (res && res.status == 200) {
|
||||||
return (await res.json()).serviceTokens;
|
return (await res.json()).serviceTokenData;
|
||||||
} else {
|
} else {
|
||||||
console.log('Failed to get service tokens');
|
console.log('Failed to get service tokens');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|||||||
import Button from "~/components/basic/buttons/Button";
|
import Button from "~/components/basic/buttons/Button";
|
||||||
import AddServiceTokenDialog from "~/components/basic/dialog/AddServiceTokenDialog";
|
import AddServiceTokenDialog from "~/components/basic/dialog/AddServiceTokenDialog";
|
||||||
import InputField from "~/components/basic/InputField";
|
import InputField from "~/components/basic/InputField";
|
||||||
import ServiceTokenTable from "~/components/basic/table/ServiceTokenTable";
|
import ServiceTokenTable from "~/components/basic/table/ServiceTokenTable.tsx";
|
||||||
import NavHeader from "~/components/navigation/NavHeader";
|
import NavHeader from "~/components/navigation/NavHeader";
|
||||||
import { getTranslatedServerSideProps } from "~/utilities/withTranslateProps";
|
import { getTranslatedServerSideProps } from "~/utilities/withTranslateProps";
|
||||||
|
|
||||||
@@ -145,7 +145,7 @@ export default function SettingsBasic() {
|
|||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className="min-w-md mt-2 flex flex-col items-start">
|
<div className="min-w-md mt-2 flex flex-col items-start">
|
||||||
<div className="bg-white/5 rounded-md px-6 pt-6 pb-4 flex flex-col items-start flex flex-col items-start w-full mb-6 pt-2">
|
<div className="bg-white/5 rounded-md px-6 pt-6 pb-4 flex flex-col items-start flex flex-col items-start w-full mb-6 pt-2">
|
||||||
<p className="text-xl font-semibold mb-4">
|
<p className="text-xl font-semibold mb-4 mt-2">
|
||||||
{t("common:display-name")}
|
{t("common:display-name")}
|
||||||
</p>
|
</p>
|
||||||
<div className="max-h-28 w-full max-w-md mr-auto">
|
<div className="max-h-28 w-full max-w-md mr-auto">
|
||||||
@@ -171,7 +171,7 @@ export default function SettingsBasic() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-white/5 rounded-md px-6 pt-6 pb-2 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4">
|
<div className="bg-white/5 rounded-md px-6 pt-4 pb-2 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4">
|
||||||
<p className="text-xl font-semibold self-start">
|
<p className="text-xl font-semibold self-start">
|
||||||
{t("common:project-id")}
|
{t("common:project-id")}
|
||||||
</p>
|
</p>
|
||||||
@@ -219,7 +219,7 @@ export default function SettingsBasic() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-white/5 rounded-md px-6 pt-6 flex flex-col items-start flex flex-col items-start w-full mt-4 mb-4 pt-2">
|
<div className="bg-white/5 rounded-md px-6 pt-4 flex flex-col items-start flex flex-col items-start w-full mt-4 mb-4 pt-2">
|
||||||
<div className="flex flex-row justify-between w-full">
|
<div className="flex flex-row justify-between w-full">
|
||||||
<div className="flex flex-col w-full">
|
<div className="flex flex-col w-full">
|
||||||
<p className="text-xl font-semibold mb-3">
|
<p className="text-xl font-semibold mb-3">
|
||||||
@@ -244,47 +244,12 @@ export default function SettingsBasic() {
|
|||||||
<ServiceTokenTable
|
<ServiceTokenTable
|
||||||
data={serviceTokens}
|
data={serviceTokens}
|
||||||
workspaceName={workspaceName}
|
workspaceName={workspaceName}
|
||||||
|
setServiceTokens={setServiceTokens}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* <div className="bg-white/5 rounded-md px-6 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4 pb-6 pt-6">
|
|
||||||
<p className="text-xl font-semibold self-start">
|
|
||||||
Project Environments
|
|
||||||
</p>
|
|
||||||
<p className="text-md mr-1 text-gray-400 mt-2 self-start">
|
|
||||||
Choose which environments will show up
|
|
||||||
in your Dashboard. Some common ones
|
|
||||||
include Development, Staging, and
|
|
||||||
Production. Often, teams choose to add
|
|
||||||
Testing.
|
|
||||||
</p>
|
|
||||||
<p className="text-sm mr-1 text-gray-500 self-start">
|
|
||||||
Note: the text in brackets shows how
|
|
||||||
these environmant should be accessed in
|
|
||||||
CLI.
|
|
||||||
</p>
|
|
||||||
<div className="rounded-md h-10 w-full mr-auto mt-4 flex flex-row">
|
|
||||||
{envOptions.map((env) => (
|
|
||||||
<div className="bg-white/5 hover:bg-white/10 duration-200 h-full w-max px-3 flex flex-row items-center justify-between rounded-md mr-1 text-sm">
|
|
||||||
{env}
|
|
||||||
<XIcon
|
|
||||||
className="h-5 w-5 ml-2 mt-0.5 text-white cursor-pointer"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
<div className="group bg-white/5 hover:bg-primary hover:text-black duration-200 h-full w-max py-1 px-3 flex flex-row items-center justify-between rounded-md mr-1 cursor-pointer text-sm font-semibold">
|
|
||||||
<PlusIcon
|
|
||||||
className="h-5 w-5 text-white mr-2 group-hover:text-black"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
Add
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div> */}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-white/5 rounded-md px-6 pt-6 pb-6 border-l border-red pl-6 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4 pb-4 pt-2">
|
<div className="bg-white/5 rounded-md px-6 pt-4 pb-6 border-l border-red pl-6 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4 pb-4 pt-2">
|
||||||
<p className="text-xl font-bold text-red">
|
<p className="text-xl font-bold text-red">
|
||||||
{t("settings-project:danger-zone")}
|
{t("settings-project:danger-zone")}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
Reference in New Issue
Block a user