diff --git a/backend/src/controllers/v2/workspaceController.ts b/backend/src/controllers/v2/workspaceController.ts
index 62dcebda6..cc289c81d 100644
--- a/backend/src/controllers/v2/workspaceController.ts
+++ b/backend/src/controllers/v2/workspaceController.ts
@@ -190,7 +190,7 @@ export const getWorkspaceServiceTokenData = async (
) => {
let serviceTokenData;
try {
- const { workspaceId } = req.query;
+ const { workspaceId } = req.params;
serviceTokenData = await ServiceTokenData
.find({
diff --git a/frontend/components/basic/dialog/AddServiceTokenDialog.js b/frontend/components/basic/dialog/AddServiceTokenDialog.js
index b02a804b7..d1ed17190 100644
--- a/frontend/components/basic/dialog/AddServiceTokenDialog.js
+++ b/frontend/components/basic/dialog/AddServiceTokenDialog.js
@@ -12,6 +12,7 @@ import { envMapping } from "../../../public/data/frequentConstants";
import {
decryptAssymmetric,
encryptAssymmetric,
+ encryptSymmetric,
} from "../../utilities/cryptography/crypto";
import Button from "../buttons/Button";
import InputField from "../InputField";
@@ -25,6 +26,8 @@ const expiryMapping = {
"12 months": 31104000,
};
+const crypto = require('crypto');
+
const AddServiceTokenDialog = ({
isOpen,
closeModal,
@@ -48,16 +51,14 @@ const AddServiceTokenDialog = ({
privateKey: localStorage.getItem("PRIVATE_KEY"),
});
- // generate new public/private key pair
- const pair = nacl.box.keyPair();
- const publicKey = nacl.util.encodeBase64(pair.publicKey);
- const privateKey = nacl.util.encodeBase64(pair.secretKey);
-
- // encrypt workspace key under newly-generated public key
- const { ciphertext: encryptedKey, nonce } = encryptAssymmetric({
+ const randomBytes = crypto.randomBytes(16).toString('hex');
+ const {
+ ciphertext,
+ iv,
+ tag,
+ } = encryptSymmetric({
plaintext: key,
- publicKey,
- privateKey,
+ key: randomBytes,
});
let newServiceToken = await addServiceToken({
@@ -65,13 +66,12 @@ const AddServiceTokenDialog = ({
workspaceId,
environment: envMapping[serviceTokenEnv],
expiresIn: expiryMapping[serviceTokenExpiresIn],
- publicKey,
- encryptedKey,
- nonce,
+ encryptedKey: ciphertext,
+ iv,
+ tag
});
- const serviceToken = newServiceToken + "," + privateKey;
- setServiceToken(serviceToken);
+ setServiceToken(newServiceToken);
};
function copyToClipboard() {
@@ -161,7 +161,7 @@ const AddServiceTokenDialog = ({
"Production",
"Testing",
]}
- width="full"
+ isFull={true}
text={`${t("common:environment")}: `}
/>
@@ -176,7 +176,7 @@ const AddServiceTokenDialog = ({
"6 months",
"12 months",
]}
- width="full"
+ isFull={true}
text={`${t("common:expired-in")}: `}
/>
@@ -211,7 +211,7 @@ const AddServiceTokenDialog = ({
-
diff --git a/frontend/components/basic/table/ServiceTokenTable.js b/frontend/components/basic/table/ServiceTokenTable.tsx
similarity index 62%
rename from frontend/components/basic/table/ServiceTokenTable.js
rename to frontend/components/basic/table/ServiceTokenTable.tsx
index 6e5cc65ea..a79acd759 100644
--- a/frontend/components/basic/table/ServiceTokenTable.js
+++ b/frontend/components/basic/table/ServiceTokenTable.tsx
@@ -1,19 +1,37 @@
-import { useEffect, useState } from 'react';
-import { useRouter } from 'next/router';
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 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 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.
- * @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
*/
-const ServiceTokenTable = ({ data, workspaceName }) => {
- const router = useRouter();
+const ServiceTokenTable = ({ data, workspaceName, setServiceTokens }: ServiceTokensProps) => {
+ console.log(data)
+ const { createNotification } = useNotificationContext();
return (
@@ -30,7 +48,7 @@ const ServiceTokenTable = ({ data, workspaceName }) => {
{data?.length > 0 ? (
- data.map((row, index) => {
+ data.map((row) => {
return (
{
{}}
+ 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}
@@ -63,7 +88,7 @@ const ServiceTokenTable = ({ data, workspaceName }) => {
})
) : (
-
+
No service tokens yet
diff --git a/frontend/pages/api/serviceToken/addServiceToken.ts b/frontend/pages/api/serviceToken/addServiceToken.ts
index 5dad69f69..f006d6146 100644
--- a/frontend/pages/api/serviceToken/addServiceToken.ts
+++ b/frontend/pages/api/serviceToken/addServiceToken.ts
@@ -5,14 +5,21 @@ interface Props {
workspaceId: string;
environment: string;
expiresIn: number;
- publicKey: string;
encryptedKey: string;
- nonce: string;
+ iv: string;
+ tag: string;
}
/**
* 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
*/
const addServiceToken = ({
@@ -20,11 +27,11 @@ const addServiceToken = ({
workspaceId,
environment,
expiresIn,
- publicKey,
encryptedKey,
- nonce
+ iv,
+ tag
}: Props) => {
- return SecurityClient.fetchCall('/api/v1/service-token/', {
+ return SecurityClient.fetchCall('/api/v2/service-token-data/', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
@@ -34,13 +41,13 @@ const addServiceToken = ({
workspaceId,
environment,
expiresIn,
- publicKey,
encryptedKey,
- nonce
+ iv,
+ tag
})
}).then(async (res) => {
if (res && res.status == 200) {
- return (await res.json()).token;
+ return (await res.json()).serviceToken;
} else {
console.log('Failed to add service tokens');
}
diff --git a/frontend/pages/api/serviceToken/deleteServiceToken.ts b/frontend/pages/api/serviceToken/deleteServiceToken.ts
new file mode 100644
index 000000000..b59ad0b8a
--- /dev/null
+++ b/frontend/pages/api/serviceToken/deleteServiceToken.ts
@@ -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;
diff --git a/frontend/pages/api/serviceToken/getServiceTokens.ts b/frontend/pages/api/serviceToken/getServiceTokens.ts
index d2577cc83..db6b035dd 100644
--- a/frontend/pages/api/serviceToken/getServiceTokens.ts
+++ b/frontend/pages/api/serviceToken/getServiceTokens.ts
@@ -7,7 +7,7 @@ import SecurityClient from '~/utilities/SecurityClient';
*/
const getServiceTokens = ({ workspaceId }: { workspaceId: string }) => {
return SecurityClient.fetchCall(
- '/api/v1/workspace/' + workspaceId + '/service-tokens',
+ '/api/v2/workspace/' + workspaceId + '/service-token-data',
{
method: 'GET',
headers: {
@@ -16,7 +16,7 @@ const getServiceTokens = ({ workspaceId }: { workspaceId: string }) => {
}
).then(async (res) => {
if (res && res.status == 200) {
- return (await res.json()).serviceTokens;
+ return (await res.json()).serviceTokenData;
} else {
console.log('Failed to get service tokens');
}
diff --git a/frontend/pages/settings/project/[id].js b/frontend/pages/settings/project/[id].js
index 8115cbebd..53c60d629 100644
--- a/frontend/pages/settings/project/[id].js
+++ b/frontend/pages/settings/project/[id].js
@@ -8,7 +8,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import Button from "~/components/basic/buttons/Button";
import AddServiceTokenDialog from "~/components/basic/dialog/AddServiceTokenDialog";
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 { getTranslatedServerSideProps } from "~/utilities/withTranslateProps";
@@ -145,7 +145,7 @@ export default function SettingsBasic() {
-
+
{t("common:display-name")}
@@ -171,7 +171,7 @@ export default function SettingsBasic() {
-
+
{t("common:project-id")}
@@ -219,7 +219,7 @@ export default function SettingsBasic() {
-
+
@@ -244,47 +244,12 @@ export default function SettingsBasic() {
-
- {/*
-
- Project Environments
-
-
- Choose which environments will show up
- in your Dashboard. Some common ones
- include Development, Staging, and
- Production. Often, teams choose to add
- Testing.
-
-
- Note: the text in brackets shows how
- these environmant should be accessed in
- CLI.
-
-
- {envOptions.map((env) => (
-
- {env}
-
-
- ))}
-
-
-
*/}
-
+
{t("settings-project:danger-zone")}