Skip to content

Commit 9cf28fe

Browse files
committed
Service tokens update on frontend
1 parent 6c88c4d commit 9cf28fe

File tree

7 files changed

+107
-80
lines changed

7 files changed

+107
-80
lines changed

backend/src/controllers/v2/workspaceController.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ export const getWorkspaceServiceTokenData = async (
190190
) => {
191191
let serviceTokenData;
192192
try {
193-
const { workspaceId } = req.query;
193+
const { workspaceId } = req.params;
194194

195195
serviceTokenData = await ServiceTokenData
196196
.find({

frontend/components/basic/dialog/AddServiceTokenDialog.js

+18-18
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { envMapping } from "../../../public/data/frequentConstants";
1212
import {
1313
decryptAssymmetric,
1414
encryptAssymmetric,
15+
encryptSymmetric,
1516
} from "../../utilities/cryptography/crypto";
1617
import Button from "../buttons/Button";
1718
import InputField from "../InputField";
@@ -25,6 +26,8 @@ const expiryMapping = {
2526
"12 months": 31104000,
2627
};
2728

29+
const crypto = require('crypto');
30+
2831
const AddServiceTokenDialog = ({
2932
isOpen,
3033
closeModal,
@@ -48,30 +51,27 @@ const AddServiceTokenDialog = ({
4851
privateKey: localStorage.getItem("PRIVATE_KEY"),
4952
});
5053

51-
// generate new public/private key pair
52-
const pair = nacl.box.keyPair();
53-
const publicKey = nacl.util.encodeBase64(pair.publicKey);
54-
const privateKey = nacl.util.encodeBase64(pair.secretKey);
55-
56-
// encrypt workspace key under newly-generated public key
57-
const { ciphertext: encryptedKey, nonce } = encryptAssymmetric({
54+
const randomBytes = crypto.randomBytes(16).toString('hex');
55+
const {
56+
ciphertext,
57+
iv,
58+
tag,
59+
} = encryptSymmetric({
5860
plaintext: key,
59-
publicKey,
60-
privateKey,
61+
key: randomBytes,
6162
});
6263

6364
let newServiceToken = await addServiceToken({
6465
name: serviceTokenName,
6566
workspaceId,
6667
environment: envMapping[serviceTokenEnv],
6768
expiresIn: expiryMapping[serviceTokenExpiresIn],
68-
publicKey,
69-
encryptedKey,
70-
nonce,
69+
encryptedKey: ciphertext,
70+
iv,
71+
tag
7172
});
7273

73-
const serviceToken = newServiceToken + "," + privateKey;
74-
setServiceToken(serviceToken);
74+
setServiceToken(newServiceToken);
7575
};
7676

7777
function copyToClipboard() {
@@ -161,7 +161,7 @@ const AddServiceTokenDialog = ({
161161
"Production",
162162
"Testing",
163163
]}
164-
width="full"
164+
isFull={true}
165165
text={`${t("common:environment")}: `}
166166
/>
167167
</div>
@@ -176,7 +176,7 @@ const AddServiceTokenDialog = ({
176176
"6 months",
177177
"12 months",
178178
]}
179-
width="full"
179+
isFull={true}
180180
text={`${t("common:expired-in")}: `}
181181
/>
182182
</div>
@@ -211,7 +211,7 @@ const AddServiceTokenDialog = ({
211211
</div>
212212
</div>
213213
<div className="w-full">
214-
<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">
214+
<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">
215215
<input
216216
type="text"
217217
value={serviceToken}
@@ -236,7 +236,7 @@ const AddServiceTokenDialog = ({
236236
)}
237237
</button>
238238
<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">
239-
{t("common.click-to-copy")}
239+
{t("common:click-to-copy")}
240240
</span>
241241
</div>
242242
</div>

frontend/components/basic/table/ServiceTokenTable.js renamed to frontend/components/basic/table/ServiceTokenTable.tsx

+34-9
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,37 @@
1-
import { useEffect, useState } from 'react';
2-
import { useRouter } from 'next/router';
31
import { faX } from '@fortawesome/free-solid-svg-icons';
42

3+
import { useNotificationContext } from '~/components/context/Notifications/NotificationProvider';
4+
5+
import deleteServiceToken from "../../../pages/api/serviceToken/deleteServiceToken";
56
import { reverseEnvMapping } from '../../../public/data/frequentConstants';
67
import guidGenerator from '../../utilities/randomId';
78
import Button from '../buttons/Button';
89

10+
interface TokenProps {
11+
_id: string;
12+
name: string;
13+
environment: string;
14+
expiresAt: string;
15+
}
16+
17+
interface ServiceTokensProps {
18+
data: TokenProps[];
19+
workspaceName: string;
20+
setServiceTokens: (value: TokenProps[]) => void;
21+
}
22+
923
/**
10-
* This is the component that we utilize for the user table - in future, can reuse it for some other purposes too.
24+
* This is the component that we utilize for the service token table
1125
* #TODO: add the possibility of choosing and doing operations on multiple users.
12-
* @param {*} props
26+
* @param {object} obj
27+
* @param {any[]} obj.data - current state of the service token table
28+
* @param {string} obj.workspaceName - name of the current project
29+
* @param {function} obj.setServiceTokens - updating the state of the service token table
1330
* @returns
1431
*/
15-
const ServiceTokenTable = ({ data, workspaceName }) => {
16-
const router = useRouter();
32+
const ServiceTokenTable = ({ data, workspaceName, setServiceTokens }: ServiceTokensProps) => {
33+
console.log(data)
34+
const { createNotification } = useNotificationContext();
1735

1836
return (
1937
<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 }) => {
3048
</thead>
3149
<tbody>
3250
{data?.length > 0 ? (
33-
data.map((row, index) => {
51+
data.map((row) => {
3452
return (
3553
<tr
3654
key={guidGenerator()}
@@ -51,7 +69,14 @@ const ServiceTokenTable = ({ data, workspaceName }) => {
5169
<td className="py-2 border-mineshaft-700 border-t">
5270
<div className="opacity-50 hover:opacity-100 duration-200 flex items-center">
5371
<Button
54-
onButtonPressed={() => {}}
72+
onButtonPressed={() => {
73+
deleteServiceToken({ serviceTokenId: row._id} );
74+
setServiceTokens(data.filter(token => token._id != row._id));
75+
createNotification({
76+
text: `'${row.name}' token has been revoked.`,
77+
type: 'error'
78+
});
79+
}}
5580
color="red"
5681
size="icon-sm"
5782
icon={faX}
@@ -63,7 +88,7 @@ const ServiceTokenTable = ({ data, workspaceName }) => {
6388
})
6489
) : (
6590
<tr>
66-
<td colSpan="4" className="text-center pt-7 pb-4 text-bunker-400">
91+
<td className="text-center pt-7 pb-4 text-bunker-400 col-span-4">
6792
No service tokens yet
6893
</td>
6994
</tr>

frontend/pages/api/serviceToken/addServiceToken.ts

+16-9
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,33 @@ interface Props {
55
workspaceId: string;
66
environment: string;
77
expiresIn: number;
8-
publicKey: string;
98
encryptedKey: string;
10-
nonce: string;
9+
iv: string;
10+
tag: string;
1111
}
1212

1313
/**
1414
* This route gets service tokens for a specific user in a project
15-
* @param {*} param0
15+
* @param {object} obj
16+
* @param {string} obj.name - name of the service token
17+
* @param {string} obj.workspaceId - workspace for which we are issuing the token
18+
* @param {string} obj.environment - environment for which we are issuing the token
19+
* @param {string} obj.expiresIn - how soon the service token expires in ms
20+
* @param {string} obj.encryptedKey - encrypted project key through random symmetric encryption
21+
* @param {string} obj.iv - obtained through symmetric encryption
22+
* @param {string} obj.tag - obtained through symmetric encryption
1623
* @returns
1724
*/
1825
const addServiceToken = ({
1926
name,
2027
workspaceId,
2128
environment,
2229
expiresIn,
23-
publicKey,
2430
encryptedKey,
25-
nonce
31+
iv,
32+
tag
2633
}: Props) => {
27-
return SecurityClient.fetchCall('/api/v1/service-token/', {
34+
return SecurityClient.fetchCall('/api/v2/service-token-data/', {
2835
method: 'POST',
2936
headers: {
3037
'Content-Type': 'application/json'
@@ -34,13 +41,13 @@ const addServiceToken = ({
3441
workspaceId,
3542
environment,
3643
expiresIn,
37-
publicKey,
3844
encryptedKey,
39-
nonce
45+
iv,
46+
tag
4047
})
4148
}).then(async (res) => {
4249
if (res && res.status == 200) {
43-
return (await res.json()).token;
50+
return (await res.json()).serviceToken;
4451
} else {
4552
console.log('Failed to add service tokens');
4653
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import SecurityClient from '~/utilities/SecurityClient';
2+
3+
interface Props {
4+
serviceTokenId: string;
5+
}
6+
7+
/**
8+
* This route revokes a specific service token
9+
* @param {object} obj
10+
* @param {string} obj.serviceTokenId - id of a cervice token that we want to delete
11+
* @returns
12+
*/
13+
const deleteServiceToken = ({
14+
serviceTokenId,
15+
}: Props) => {
16+
return SecurityClient.fetchCall('/api/v2/service-token-data/' + serviceTokenId, {
17+
method: 'DELETE',
18+
headers: {
19+
'Content-Type': 'application/json'
20+
},
21+
}).then(async (res) => {
22+
if (res && res.status == 200) {
23+
return (await res.json());
24+
} else {
25+
console.log('Failed to delete a service token');
26+
}
27+
});
28+
};
29+
30+
export default deleteServiceToken;

frontend/pages/api/serviceToken/getServiceTokens.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import SecurityClient from '~/utilities/SecurityClient';
77
*/
88
const getServiceTokens = ({ workspaceId }: { workspaceId: string }) => {
99
return SecurityClient.fetchCall(
10-
'/api/v1/workspace/' + workspaceId + '/service-tokens',
10+
'/api/v2/workspace/' + workspaceId + '/service-token-data',
1111
{
1212
method: 'GET',
1313
headers: {
@@ -16,7 +16,7 @@ const getServiceTokens = ({ workspaceId }: { workspaceId: string }) => {
1616
}
1717
).then(async (res) => {
1818
if (res && res.status == 200) {
19-
return (await res.json()).serviceTokens;
19+
return (await res.json()).serviceTokenData;
2020
} else {
2121
console.log('Failed to get service tokens');
2222
}

frontend/pages/settings/project/[id].js

+6-41
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
88
import Button from "~/components/basic/buttons/Button";
99
import AddServiceTokenDialog from "~/components/basic/dialog/AddServiceTokenDialog";
1010
import InputField from "~/components/basic/InputField";
11-
import ServiceTokenTable from "~/components/basic/table/ServiceTokenTable";
11+
import ServiceTokenTable from "~/components/basic/table/ServiceTokenTable.tsx";
1212
import NavHeader from "~/components/navigation/NavHeader";
1313
import { getTranslatedServerSideProps } from "~/utilities/withTranslateProps";
1414

@@ -145,7 +145,7 @@ export default function SettingsBasic() {
145145
<div className="flex flex-col">
146146
<div className="min-w-md mt-2 flex flex-col items-start">
147147
<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">
148-
<p className="text-xl font-semibold mb-4">
148+
<p className="text-xl font-semibold mb-4 mt-2">
149149
{t("common:display-name")}
150150
</p>
151151
<div className="max-h-28 w-full max-w-md mr-auto">
@@ -171,7 +171,7 @@ export default function SettingsBasic() {
171171
</div>
172172
</div>
173173
</div>
174-
<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">
174+
<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">
175175
<p className="text-xl font-semibold self-start">
176176
{t("common:project-id")}
177177
</p>
@@ -219,7 +219,7 @@ export default function SettingsBasic() {
219219
</div>
220220
</div>
221221
</div>
222-
<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">
222+
<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">
223223
<div className="flex flex-row justify-between w-full">
224224
<div className="flex flex-col w-full">
225225
<p className="text-xl font-semibold mb-3">
@@ -244,47 +244,12 @@ export default function SettingsBasic() {
244244
<ServiceTokenTable
245245
data={serviceTokens}
246246
workspaceName={workspaceName}
247+
setServiceTokens={setServiceTokens}
247248
/>
248249
</div>
249-
250-
{/* <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">
251-
<p className="text-xl font-semibold self-start">
252-
Project Environments
253-
</p>
254-
<p className="text-md mr-1 text-gray-400 mt-2 self-start">
255-
Choose which environments will show up
256-
in your Dashboard. Some common ones
257-
include Development, Staging, and
258-
Production. Often, teams choose to add
259-
Testing.
260-
</p>
261-
<p className="text-sm mr-1 text-gray-500 self-start">
262-
Note: the text in brackets shows how
263-
these environmant should be accessed in
264-
CLI.
265-
</p>
266-
<div className="rounded-md h-10 w-full mr-auto mt-4 flex flex-row">
267-
{envOptions.map((env) => (
268-
<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">
269-
{env}
270-
<XIcon
271-
className="h-5 w-5 ml-2 mt-0.5 text-white cursor-pointer"
272-
aria-hidden="true"
273-
/>
274-
</div>
275-
))}
276-
<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">
277-
<PlusIcon
278-
className="h-5 w-5 text-white mr-2 group-hover:text-black"
279-
aria-hidden="true"
280-
/>
281-
Add
282-
</div>
283-
</div>
284-
</div> */}
285250
</div>
286251
</div>
287-
<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">
252+
<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">
288253
<p className="text-xl font-bold text-red">
289254
{t("settings-project:danger-zone")}
290255
</p>

0 commit comments

Comments
 (0)