mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-08 03:27:38 +00:00
Complete v1 API Key
This commit is contained in:
@@ -335,13 +335,8 @@ Infisical officially launched as v.1.0 on November 21st, 2022. There are a lot o
|
|||||||
|
|
||||||
<a href="https://github.com/dangtony98"><img src="https://avatars.githubusercontent.com/u/25857006?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/mv-turtle"><img src="https://avatars.githubusercontent.com/u/78047717?s=96&v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/maidul98"><img src="https://avatars.githubusercontent.com/u/9300960?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/gangjun06"><img src="https://avatars.githubusercontent.com/u/50910815?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/reginaldbondoc"><img src="https://avatars.githubusercontent.com/u/7693108?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/SH5H"><img src="https://avatars.githubusercontent.com/u/25437192?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/gmgale"><img src="https://avatars.githubusercontent.com/u/62303146?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/asharonbaltazar"><img src="https://avatars.githubusercontent.com/u/58940073?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/JoaoVictor6"><img src="https://avatars.githubusercontent.com/u/68869379?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/mocherfaoui"><img src="https://avatars.githubusercontent.com/u/37941426?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/jon4hz"><img src="https://avatars.githubusercontent.com/u/26183582?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/edgarrmondragon"><img src="https://avatars.githubusercontent.com/u/16805946?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/arjunyel"><img src="https://avatars.githubusercontent.com/u/11153289?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/LemmyMwaura"><img src="https://avatars.githubusercontent.com/u/20738858?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/Zamion101"><img src="https://avatars.githubusercontent.com/u/8071263?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/Grraahaam"><img src="https://avatars.githubusercontent.com/u/72856427?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/Gabriellopes232"><img src="https://avatars.githubusercontent.com/u/74881862?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/akhilmhdh"><img src="https://avatars.githubusercontent.com/u/31166322?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/naorpeled"><img src="https://avatars.githubusercontent.com/u/6171622?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/jonerrr"><img src="https://avatars.githubusercontent.com/u/73760377?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/cerrussell"><img src="https://avatars.githubusercontent.com/u/80227828?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/imakecodes"><img src="https://avatars.githubusercontent.com/u/35536648?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/adrianmarinwork"><img src="https://avatars.githubusercontent.com/u/118568289?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/arthurzenika"><img src="https://avatars.githubusercontent.com/u/445200?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/hanywang2"><img src="https://avatars.githubusercontent.com/u/44352119?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/tobias-mintlify"><img src="https://avatars.githubusercontent.com/u/110702161?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/wjhurley"><img src="https://avatars.githubusercontent.com/u/15939055?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/0xflotus"><img src="https://avatars.githubusercontent.com/u/26602940?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/wanjohiryan"><img src="https://avatars.githubusercontent.com/u/71614375?v=4" width="50" height="50" alt=""/></a>
|
<a href="https://github.com/dangtony98"><img src="https://avatars.githubusercontent.com/u/25857006?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/mv-turtle"><img src="https://avatars.githubusercontent.com/u/78047717?s=96&v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/maidul98"><img src="https://avatars.githubusercontent.com/u/9300960?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/gangjun06"><img src="https://avatars.githubusercontent.com/u/50910815?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/reginaldbondoc"><img src="https://avatars.githubusercontent.com/u/7693108?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/SH5H"><img src="https://avatars.githubusercontent.com/u/25437192?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/gmgale"><img src="https://avatars.githubusercontent.com/u/62303146?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/asharonbaltazar"><img src="https://avatars.githubusercontent.com/u/58940073?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/JoaoVictor6"><img src="https://avatars.githubusercontent.com/u/68869379?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/mocherfaoui"><img src="https://avatars.githubusercontent.com/u/37941426?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/jon4hz"><img src="https://avatars.githubusercontent.com/u/26183582?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/edgarrmondragon"><img src="https://avatars.githubusercontent.com/u/16805946?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/arjunyel"><img src="https://avatars.githubusercontent.com/u/11153289?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/LemmyMwaura"><img src="https://avatars.githubusercontent.com/u/20738858?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/Zamion101"><img src="https://avatars.githubusercontent.com/u/8071263?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/Grraahaam"><img src="https://avatars.githubusercontent.com/u/72856427?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/Gabriellopes232"><img src="https://avatars.githubusercontent.com/u/74881862?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/akhilmhdh"><img src="https://avatars.githubusercontent.com/u/31166322?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/naorpeled"><img src="https://avatars.githubusercontent.com/u/6171622?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/jonerrr"><img src="https://avatars.githubusercontent.com/u/73760377?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/cerrussell"><img src="https://avatars.githubusercontent.com/u/80227828?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/imakecodes"><img src="https://avatars.githubusercontent.com/u/35536648?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/adrianmarinwork"><img src="https://avatars.githubusercontent.com/u/118568289?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/arthurzenika"><img src="https://avatars.githubusercontent.com/u/445200?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/hanywang2"><img src="https://avatars.githubusercontent.com/u/44352119?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/tobias-mintlify"><img src="https://avatars.githubusercontent.com/u/110702161?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/wjhurley"><img src="https://avatars.githubusercontent.com/u/15939055?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/0xflotus"><img src="https://avatars.githubusercontent.com/u/26602940?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/wanjohiryan"><img src="https://avatars.githubusercontent.com/u/71614375?v=4" width="50" height="50" alt=""/></a>
|
||||||
|
|
||||||
|
|
||||||
## 🌎 Translations
|
## 🌎 Translations
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
Infisical is currently aviable in English and Korean. Help us translate Infisical to your language!
|
|
||||||
=======
|
|
||||||
Infisical is currently available in English and Korean. Help us translate Infisical to your language!
|
Infisical is currently available in English and Korean. Help us translate Infisical to your language!
|
||||||
>>>>>>> 9ce4a52b8da0057c2450cd7af93a8c5758c2476b
|
|
||||||
|
|
||||||
You can find all the info in [this issue](https://github.com/Infisical/infisical/issues/181).
|
You can find all the info in [this issue](https://github.com/Infisical/infisical/issues/181).
|
||||||
|
|||||||
+1
-1
@@ -111,7 +111,7 @@ app.use('/api/v2/workspace', v2WorkspaceRouter); // TODO: turn into plural route
|
|||||||
app.use('/api/v2/secret', v2SecretRouter); // stop supporting, TODO: revise
|
app.use('/api/v2/secret', v2SecretRouter); // stop supporting, TODO: revise
|
||||||
app.use('/api/v2/secrets', v2SecretsRouter);
|
app.use('/api/v2/secrets', v2SecretsRouter);
|
||||||
app.use('/api/v2/service-token', v2ServiceTokenDataRouter); // TODO: turn into plural route
|
app.use('/api/v2/service-token', v2ServiceTokenDataRouter); // TODO: turn into plural route
|
||||||
app.use('/api/v2/api-key-data', v2APIKeyDataRouter);
|
app.use('/api/v2/api-key', v2APIKeyDataRouter);
|
||||||
|
|
||||||
// api docs
|
// api docs
|
||||||
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerFile))
|
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerFile))
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { ADMIN, MEMBER } from '../../../variables';
|
|||||||
router.get(
|
router.get(
|
||||||
'/:secretId/secret-versions',
|
'/:secretId/secret-versions',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: ['jwt', 'apiKey']
|
||||||
}),
|
}),
|
||||||
requireSecretAuth({
|
requireSecretAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER]
|
||||||
@@ -27,7 +27,7 @@ router.get(
|
|||||||
router.post(
|
router.post(
|
||||||
'/:secretId/secret-versions/rollback',
|
'/:secretId/secret-versions/rollback',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: ['jwt', 'apiKey']
|
||||||
}),
|
}),
|
||||||
requireSecretAuth({
|
requireSecretAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER]
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { workspaceController } from '../../controllers/v1';
|
|||||||
router.get(
|
router.get(
|
||||||
'/:workspaceId/secret-snapshots',
|
'/:workspaceId/secret-snapshots',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: ['jwt', 'apiKey']
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER]
|
||||||
@@ -40,7 +40,7 @@ router.get(
|
|||||||
router.post(
|
router.post(
|
||||||
'/:workspaceId/secret-snapshots/rollback',
|
'/:workspaceId/secret-snapshots/rollback',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: ['jwt', 'apiKey']
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER]
|
||||||
@@ -54,7 +54,7 @@ router.post(
|
|||||||
router.get(
|
router.get(
|
||||||
'/:workspaceId/logs',
|
'/:workspaceId/logs',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: ['jwt', 'apiKey']
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER]
|
||||||
|
|||||||
+44
-27
@@ -16,49 +16,66 @@ import {
|
|||||||
AccountNotFoundError,
|
AccountNotFoundError,
|
||||||
ServiceTokenDataNotFoundError,
|
ServiceTokenDataNotFoundError,
|
||||||
APIKeyDataNotFoundError,
|
APIKeyDataNotFoundError,
|
||||||
UnauthorizedRequestError
|
UnauthorizedRequestError,
|
||||||
|
BadRequestError
|
||||||
} from '../utils/errors';
|
} from '../utils/errors';
|
||||||
|
|
||||||
// TODO 1: check if API key works
|
|
||||||
// TODO 2: optimize middleware
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate that auth token value [authTokenValue] falls under one of
|
*
|
||||||
* accepted auth modes [acceptedAuthModes].
|
|
||||||
* @param {Object} obj
|
* @param {Object} obj
|
||||||
* @param {String} obj.authTokenValue - auth token value (e.g. JWT or service token value)
|
* @param {Object} obj.headers - HTTP request headers object
|
||||||
* @param {String[]} obj.acceptedAuthModes - accepted auth modes (e.g. jwt, serviceToken)
|
|
||||||
* @returns {String} authMode - auth mode
|
|
||||||
*/
|
*/
|
||||||
const validateAuthMode = ({
|
const validateAuthMode = ({
|
||||||
authTokenValue,
|
headers,
|
||||||
acceptedAuthModes
|
acceptedAuthModes
|
||||||
}: {
|
}: {
|
||||||
authTokenValue: string;
|
headers: { [key: string]: string | string[] | undefined },
|
||||||
acceptedAuthModes: string[];
|
acceptedAuthModes: string[]
|
||||||
}) => {
|
}) => {
|
||||||
let authMode;
|
// TODO: refactor middleware
|
||||||
try {
|
const apiKey = headers['x-api-key'];
|
||||||
switch (authTokenValue.split('.', 1)[0]) {
|
const authHeader = headers['authorization'];
|
||||||
|
|
||||||
|
let authTokenType, authTokenValue;
|
||||||
|
if (apiKey === undefined && authHeader === undefined) {
|
||||||
|
// case: no auth or X-API-KEY header present
|
||||||
|
throw BadRequestError({ message: 'Missing Authorization or X-API-KEY in request header.' });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof apiKey === 'string') {
|
||||||
|
// case: treat request authentication type as via X-API-KEY (i.e. API Key)
|
||||||
|
authTokenType = 'apiKey';
|
||||||
|
authTokenValue = apiKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof authHeader === 'string') {
|
||||||
|
// case: treat request authentication type as via Authorization header (i.e. either JWT or service token)
|
||||||
|
const [tokenType, tokenValue] = <[string, string]>authHeader.split(' ', 2) ?? [null, null]
|
||||||
|
if (tokenType === null)
|
||||||
|
throw BadRequestError({ message: `Missing Authorization Header in the request header.` });
|
||||||
|
if (tokenType.toLowerCase() !== 'bearer')
|
||||||
|
throw BadRequestError({ message: `The provided authentication type '${tokenType}' is not supported.` });
|
||||||
|
if (tokenValue === null)
|
||||||
|
throw BadRequestError({ message: 'Missing Authorization Body in the request header.' });
|
||||||
|
|
||||||
|
switch (tokenValue.split('.', 1)[0]) {
|
||||||
case 'st':
|
case 'st':
|
||||||
authMode = 'serviceToken';
|
authTokenType = 'serviceToken';
|
||||||
break;
|
|
||||||
case 'ak':
|
|
||||||
authMode = 'apiKey';
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
authMode = 'jwt';
|
authTokenType = 'jwt';
|
||||||
break;
|
}
|
||||||
|
authTokenValue = tokenValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!acceptedAuthModes.includes(authMode))
|
if (!authTokenType || !authTokenValue) throw BadRequestError({ message: 'Missing valid Authorization or X-API-KEY in request header.' });
|
||||||
throw UnauthorizedRequestError({ message: 'Failed to authenticated auth mode' });
|
|
||||||
|
|
||||||
} catch (err) {
|
if (!acceptedAuthModes.includes(authTokenType)) throw BadRequestError({ message: 'The provided authentication type is not supported.' });
|
||||||
throw UnauthorizedRequestError({ message: 'Failed to authenticated auth mode' });
|
|
||||||
}
|
|
||||||
|
|
||||||
return authMode;
|
return ({
|
||||||
|
authTokenType,
|
||||||
|
authTokenValue
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import {
|
|||||||
getAuthSTDPayload,
|
getAuthSTDPayload,
|
||||||
getAuthAPIKeyPayload
|
getAuthAPIKeyPayload
|
||||||
} from '../helpers/auth';
|
} from '../helpers/auth';
|
||||||
import { BadRequestError } from '../utils/errors';
|
|
||||||
|
|
||||||
declare module 'jsonwebtoken' {
|
declare module 'jsonwebtoken' {
|
||||||
export interface UserIDJwtPayload extends jwt.JwtPayload {
|
export interface UserIDJwtPayload extends jwt.JwtPayload {
|
||||||
@@ -31,37 +30,28 @@ const requireAuth = ({
|
|||||||
acceptedAuthModes: string[];
|
acceptedAuthModes: string[];
|
||||||
}) => {
|
}) => {
|
||||||
return async (req: Request, res: Response, next: NextFunction) => {
|
return async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const [AUTH_TOKEN_TYPE, AUTH_TOKEN_VALUE] = <[string, string]>req.headers['authorization']?.split(' ', 2) ?? [null, null]
|
// validate auth token against accepted auth modes [acceptedAuthModes]
|
||||||
if (AUTH_TOKEN_TYPE === null)
|
// and return token type [authTokenType] and value [authTokenValue]
|
||||||
return next(BadRequestError({ message: `Missing Authorization Header in the request header.` }))
|
const { authTokenType, authTokenValue } = validateAuthMode({
|
||||||
if (AUTH_TOKEN_TYPE.toLowerCase() !== 'bearer')
|
headers: req.headers,
|
||||||
return next(BadRequestError({ message: `The provided authentication type '${AUTH_TOKEN_TYPE}' is not supported.` }))
|
|
||||||
if (AUTH_TOKEN_VALUE === null)
|
|
||||||
return next(BadRequestError({ message: 'Missing Authorization Body in the request header' }))
|
|
||||||
|
|
||||||
// validate auth token against
|
|
||||||
const authMode = validateAuthMode({
|
|
||||||
authTokenValue: AUTH_TOKEN_VALUE,
|
|
||||||
acceptedAuthModes
|
acceptedAuthModes
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!acceptedAuthModes.includes(authMode)) throw new Error('Failed to validate auth mode');
|
|
||||||
|
|
||||||
// attach auth payloads
|
// attach auth payloads
|
||||||
switch (authMode) {
|
switch (authTokenType) {
|
||||||
case 'serviceToken':
|
case 'serviceToken':
|
||||||
req.serviceTokenData = await getAuthSTDPayload({
|
req.serviceTokenData = await getAuthSTDPayload({
|
||||||
authTokenValue: AUTH_TOKEN_VALUE
|
authTokenValue
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case 'apiKey':
|
case 'apiKey':
|
||||||
req.user = await getAuthAPIKeyPayload({
|
req.user = await getAuthAPIKeyPayload({
|
||||||
authTokenValue: AUTH_TOKEN_VALUE
|
authTokenValue
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
req.user = await getAuthUserPayload({
|
req.user = await getAuthUserPayload({
|
||||||
authTokenValue: AUTH_TOKEN_VALUE
|
authTokenValue
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ router.post(
|
|||||||
}),
|
}),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: ['jwt', 'apiKey']
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
@@ -76,7 +76,7 @@ router.get(
|
|||||||
query('environment').exists().trim(),
|
query('environment').exists().trim(),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt', 'serviceToken']
|
acceptedAuthModes: ['jwt', 'apiKey', 'serviceToken']
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
@@ -115,7 +115,7 @@ router.patch(
|
|||||||
}),
|
}),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: ['jwt', 'apiKey']
|
||||||
}),
|
}),
|
||||||
requireSecretsAuth({
|
requireSecretsAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER]
|
||||||
@@ -143,7 +143,7 @@ router.delete(
|
|||||||
.isEmpty(),
|
.isEmpty(),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: ['jwt', 'apiKey']
|
||||||
}),
|
}),
|
||||||
requireSecretsAuth({
|
requireSecretsAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER]
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { usersController } from '../../controllers/v2';
|
|||||||
router.get(
|
router.get(
|
||||||
'/me',
|
'/me',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: ['jwt', 'apiKey']
|
||||||
}),
|
}),
|
||||||
usersController.getMe
|
usersController.getMe
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ router.get(
|
|||||||
router.get(
|
router.get(
|
||||||
'/:workspaceId/encrypted-key',
|
'/:workspaceId/encrypted-key',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: ['jwt', 'apiKey']
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER]
|
||||||
@@ -75,7 +75,7 @@ router.get( // new - TODO: rewire dashboard to this route
|
|||||||
param('workspaceId').exists().trim(),
|
param('workspaceId').exists().trim(),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: ['jwt', 'apiKey']
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
@@ -89,7 +89,7 @@ router.delete( // TODO - rewire dashboard to this route
|
|||||||
param('membershipId').exists().trim(),
|
param('membershipId').exists().trim(),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: ['jwt', 'apiKey']
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN],
|
acceptedRoles: [ADMIN],
|
||||||
@@ -107,7 +107,7 @@ router.patch( // TODO - rewire dashboard to this route
|
|||||||
body('role').exists().isString().trim().isIn([ADMIN, MEMBER]),
|
body('role').exists().isString().trim().isIn([ADMIN, MEMBER]),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: ['jwt', 'apiKey']
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN],
|
acceptedRoles: [ADMIN],
|
||||||
|
|||||||
@@ -4,9 +4,10 @@ import { faCheck, faCopy } from "@fortawesome/free-solid-svg-icons";
|
|||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
|
|
||||||
import addServiceToken from "~/pages/api/serviceToken/addServiceToken";
|
import addAPIKey from "~/pages/api/apiKey/addAPIKey";
|
||||||
import getLatestFileKey from "~/pages/api/workspace/getLatestFileKey";
|
|
||||||
|
|
||||||
|
// import addServiceToken from "~/pages/api/serviceToken/addServiceToken";
|
||||||
|
// import getLatestFileKey from "~/pages/api/workspace/getLatestFileKey";
|
||||||
import { envMapping } from "../../../public/data/frequentConstants";
|
import { envMapping } from "../../../public/data/frequentConstants";
|
||||||
import {
|
import {
|
||||||
decryptAssymmetric,
|
decryptAssymmetric,
|
||||||
@@ -26,58 +27,33 @@ const expiryMapping = {
|
|||||||
|
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
|
|
||||||
|
// TODO: convert to TS
|
||||||
const AddApiKeyDialog = ({
|
const AddApiKeyDialog = ({
|
||||||
isOpen,
|
isOpen,
|
||||||
closeModal,
|
closeModal,
|
||||||
workspaceId,
|
|
||||||
workspaceName,
|
workspaceName,
|
||||||
serviceTokens,
|
apiKeys,
|
||||||
setServiceTokens
|
setApiKeys
|
||||||
}) => {
|
}) => {
|
||||||
const [serviceToken, setServiceToken] = useState("");
|
const [apiKey, setApiKey] = useState("");
|
||||||
const [serviceTokenName, setServiceTokenName] = useState("");
|
const [apiKeyName, setApiKeyName] = useState("");
|
||||||
const [serviceTokenEnv, setServiceTokenEnv] = useState("Development");
|
const [apiKeyExpiresIn, setApiKeyExpiresIn] = useState("1 day");
|
||||||
const [serviceTokenExpiresIn, setServiceTokenExpiresIn] = useState("1 day");
|
const [apiKeyCopied, setApiKeyCopied] = useState(false);
|
||||||
const [serviceTokenCopied, setServiceTokenCopied] = useState(false);
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const generateServiceToken = async () => {
|
const generateAPIKey = async () => {
|
||||||
const latestFileKey = await getLatestFileKey({ workspaceId });
|
const newApiKey = await addAPIKey({
|
||||||
|
name: apiKeyName,
|
||||||
const key = decryptAssymmetric({
|
expiresIn: expiryMapping[apiKeyExpiresIn]
|
||||||
ciphertext: latestFileKey.latestKey.encryptedKey,
|
|
||||||
nonce: latestFileKey.latestKey.nonce,
|
|
||||||
publicKey: latestFileKey.latestKey.sender.publicKey,
|
|
||||||
privateKey: localStorage.getItem("PRIVATE_KEY"),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const randomBytes = crypto.randomBytes(16).toString('hex');
|
setApiKeys([...apiKeys, newApiKey.apiKeyData])
|
||||||
const {
|
setApiKey(newApiKey.apiKey);
|
||||||
ciphertext,
|
|
||||||
iv,
|
|
||||||
tag,
|
|
||||||
} = encryptSymmetric({
|
|
||||||
plaintext: key,
|
|
||||||
key: randomBytes,
|
|
||||||
});
|
|
||||||
|
|
||||||
let newServiceToken = await addServiceToken({
|
|
||||||
name: serviceTokenName,
|
|
||||||
workspaceId,
|
|
||||||
environment: envMapping[serviceTokenEnv],
|
|
||||||
expiresIn: expiryMapping[serviceTokenExpiresIn],
|
|
||||||
encryptedKey: ciphertext,
|
|
||||||
iv,
|
|
||||||
tag
|
|
||||||
});
|
|
||||||
|
|
||||||
setServiceTokens(serviceTokens.concat([newServiceToken.serviceTokenData]));
|
|
||||||
setServiceToken(newServiceToken.serviceToken + "." + randomBytes);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function copyToClipboard() {
|
function copyToClipboard() {
|
||||||
// Get the text field
|
// Get the text field
|
||||||
var copyText = document.getElementById("serviceToken");
|
var copyText = document.getElementById("apiKey");
|
||||||
|
|
||||||
// Select the text field
|
// Select the text field
|
||||||
copyText.select();
|
copyText.select();
|
||||||
@@ -86,16 +62,16 @@ const AddApiKeyDialog = ({
|
|||||||
// Copy the text inside the text field
|
// Copy the text inside the text field
|
||||||
navigator.clipboard.writeText(copyText.value);
|
navigator.clipboard.writeText(copyText.value);
|
||||||
|
|
||||||
setServiceTokenCopied(true);
|
setApiKeyCopied(true);
|
||||||
setTimeout(() => setServiceTokenCopied(false), 2000);
|
setTimeout(() => setApiKeyCopied(false), 2000);
|
||||||
// Alert the copied text
|
// Alert the copied text
|
||||||
// alert("Copied the text: " + copyText.value);
|
// alert("Copied the text: " + copyText.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
const closeAddServiceTokenModal = () => {
|
const closeAddApiKeyModal = () => {
|
||||||
closeModal();
|
closeModal();
|
||||||
setServiceTokenName("");
|
setApiKeyName("");
|
||||||
setServiceToken("");
|
setApiKey("");
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -125,51 +101,37 @@ const AddApiKeyDialog = ({
|
|||||||
leaveFrom="opacity-100 scale-100"
|
leaveFrom="opacity-100 scale-100"
|
||||||
leaveTo="opacity-0 scale-95"
|
leaveTo="opacity-0 scale-95"
|
||||||
>
|
>
|
||||||
{serviceToken == "" ? (
|
{apiKey == "" ? (
|
||||||
<Dialog.Panel className="w-full max-w-md transform rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
|
<Dialog.Panel className="w-full max-w-md transform rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
|
||||||
<Dialog.Title
|
<Dialog.Title
|
||||||
as="h3"
|
as="h3"
|
||||||
className="text-lg font-medium leading-6 text-gray-400 z-50"
|
className="text-lg font-medium leading-6 text-gray-400 z-50"
|
||||||
>
|
>
|
||||||
{t("section-token:add-dialog.title", {
|
{t("section-api-key:add-dialog.title", {
|
||||||
target: workspaceName,
|
target: workspaceName,
|
||||||
})}
|
})}
|
||||||
</Dialog.Title>
|
</Dialog.Title>
|
||||||
<div className="mt-2 mb-4">
|
<div className="mt-2 mb-4">
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<p className="text-sm text-gray-500">
|
<p className="text-sm text-gray-500">
|
||||||
{t("section-token:add-dialog.description")}
|
{t("section-api-key:add-dialog.description")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="max-h-28 mb-2">
|
<div className="max-h-28 mb-2">
|
||||||
<InputField
|
<InputField
|
||||||
label={t("section-token:add-dialog.name")}
|
label={t("section-api-key:add-dialog.name")}
|
||||||
onChangeHandler={setServiceTokenName}
|
onChangeHandler={setApiKeyName}
|
||||||
type="varName"
|
type="varName"
|
||||||
value={serviceTokenName}
|
value={apiKeyName}
|
||||||
placeholder=""
|
placeholder=""
|
||||||
isRequired
|
isRequired
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="max-h-28 mb-2">
|
|
||||||
<ListBox
|
|
||||||
selected={serviceTokenEnv}
|
|
||||||
onChange={setServiceTokenEnv}
|
|
||||||
data={[
|
|
||||||
"Development",
|
|
||||||
"Staging",
|
|
||||||
"Production",
|
|
||||||
"Testing",
|
|
||||||
]}
|
|
||||||
isFull={true}
|
|
||||||
text={`${t("common:environment")}: `}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="max-h-28">
|
<div className="max-h-28">
|
||||||
<ListBox
|
<ListBox
|
||||||
selected={serviceTokenExpiresIn}
|
selected={apiKeyExpiresIn}
|
||||||
onChange={setServiceTokenExpiresIn}
|
onChange={setApiKeyExpiresIn}
|
||||||
data={[
|
data={[
|
||||||
"1 day",
|
"1 day",
|
||||||
"7 days",
|
"7 days",
|
||||||
@@ -184,12 +146,12 @@ const AddApiKeyDialog = ({
|
|||||||
<div className="max-w-max">
|
<div className="max-w-max">
|
||||||
<div className="mt-6 flex flex-col justify-start w-max">
|
<div className="mt-6 flex flex-col justify-start w-max">
|
||||||
<Button
|
<Button
|
||||||
onButtonPressed={() => generateServiceToken()}
|
onButtonPressed={() => generateAPIKey()}
|
||||||
color="mineshaft"
|
color="mineshaft"
|
||||||
text={t("section-token:add-dialog.add")}
|
text={t("section-api-key:add-dialog.add")}
|
||||||
textDisabled={t("section-token:add-dialog.add")}
|
textDisabled={t("section-api-key:add-dialog.add")}
|
||||||
size="md"
|
size="md"
|
||||||
active={serviceTokenName == "" ? false : true}
|
active={apiKeyName == "" ? false : true}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -200,13 +162,13 @@ const AddApiKeyDialog = ({
|
|||||||
as="h3"
|
as="h3"
|
||||||
className="text-lg font-medium leading-6 text-gray-400 z-50"
|
className="text-lg font-medium leading-6 text-gray-400 z-50"
|
||||||
>
|
>
|
||||||
{t("section-token:add-dialog.copy-service-token")}
|
{t("section-api-key:add-dialog.copy-service-token")}
|
||||||
</Dialog.Title>
|
</Dialog.Title>
|
||||||
<div className="mt-2 mb-4">
|
<div className="mt-2 mb-4">
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<p className="text-sm text-gray-500">
|
<p className="text-sm text-gray-500">
|
||||||
{t(
|
{t(
|
||||||
"section-token:add-dialog.copy-service-token-description"
|
"section-api-key:add-dialog.copy-service-token-description"
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -215,19 +177,20 @@ const AddApiKeyDialog = ({
|
|||||||
<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-20">
|
<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-20">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={serviceToken}
|
value={apiKey}
|
||||||
id="serviceToken"
|
disabled={true}
|
||||||
|
id="apiKey"
|
||||||
className="invisible bg-white/0 text-gray-400 py-2 w-full px-2 min-w-full outline-none"
|
className="invisible bg-white/0 text-gray-400 py-2 w-full px-2 min-w-full outline-none"
|
||||||
></input>
|
></input>
|
||||||
<div className="bg-white/0 max-w-md text-sm text-gray-400 py-2 w-full pl-14 pr-2 break-words outline-none">
|
<div className="bg-white/0 max-w-md text-sm text-gray-400 py-2 w-full pl-14 pr-2 break-words outline-none">
|
||||||
{serviceToken}
|
{apiKey}
|
||||||
</div>
|
</div>
|
||||||
<div className="group font-normal h-full relative inline-block text-gray-400 underline hover:text-primary duration-200">
|
<div className="group font-normal h-full relative inline-block text-gray-400 underline hover:text-primary duration-200">
|
||||||
<button
|
<button
|
||||||
onClick={copyToClipboard}
|
onClick={copyToClipboard}
|
||||||
className="h-full pl-3.5 pr-4 border-l border-white/20 py-2 hover:bg-white/[0.12] duration-200"
|
className="h-full pl-3.5 pr-4 border-l border-white/20 py-2 hover:bg-white/[0.12] duration-200"
|
||||||
>
|
>
|
||||||
{serviceTokenCopied ? (
|
{apiKeyCopied ? (
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={faCheck}
|
icon={faCheck}
|
||||||
className="pr-0.5"
|
className="pr-0.5"
|
||||||
@@ -244,7 +207,7 @@ const AddApiKeyDialog = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className="mt-6 flex flex-col justify-start w-max">
|
<div className="mt-6 flex flex-col justify-start w-max">
|
||||||
<Button
|
<Button
|
||||||
onButtonPressed={() => closeAddServiceTokenModal()}
|
onButtonPressed={() => closeAddApiKeyModal()}
|
||||||
color="mineshaft"
|
color="mineshaft"
|
||||||
text="Close"
|
text="Close"
|
||||||
size="md"
|
size="md"
|
||||||
|
|||||||
@@ -2,8 +2,7 @@ import { faX } from '@fortawesome/free-solid-svg-icons';
|
|||||||
|
|
||||||
import { useNotificationContext } from '~/components/context/Notifications/NotificationProvider';
|
import { useNotificationContext } from '~/components/context/Notifications/NotificationProvider';
|
||||||
|
|
||||||
import deleteServiceToken from "../../../pages/api/serviceToken/deleteServiceToken";
|
import deleteAPIKey from "../../../pages/api/apiKey/deleteAPIKey";
|
||||||
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';
|
||||||
|
|
||||||
@@ -16,19 +15,18 @@ interface TokenProps {
|
|||||||
|
|
||||||
interface ServiceTokensProps {
|
interface ServiceTokensProps {
|
||||||
data: TokenProps[];
|
data: TokenProps[];
|
||||||
setServiceTokens: (value: TokenProps[]) => void;
|
setApiKeys: (value: TokenProps[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the component that we utilize for the api key table
|
* This is the component that we utilize for the api key table
|
||||||
* @param {object} obj
|
* @param {object} obj
|
||||||
* @param {any[]} obj.data - current state of the api key table
|
* @param {any[]} obj.data - current state of the api key table
|
||||||
* @param {function} obj.setServiceTokens - updating the state of the api key table
|
* @param {function} obj.setApiKeys - updating the state of the api key table
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const ApiKeyTable = ({ data, setServiceTokens }: ServiceTokensProps) => {
|
const ApiKeyTable = ({ data, setApiKeys }: ServiceTokensProps) => {
|
||||||
const { createNotification } = useNotificationContext();
|
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">
|
||||||
<div className="absolute rounded-t-md w-full h-12 bg-white/5"></div>
|
<div className="absolute rounded-t-md w-full h-12 bg-white/5"></div>
|
||||||
@@ -36,8 +34,7 @@ const ApiKeyTable = ({ data, setServiceTokens }: ServiceTokensProps) => {
|
|||||||
<thead className="text-bunker-300 text-sm font-light">
|
<thead className="text-bunker-300 text-sm font-light">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="text-left pl-6 pt-2.5 pb-2">API KEY NAME</th>
|
<th className="text-left pl-6 pt-2.5 pb-2">API KEY NAME</th>
|
||||||
<th className="text-left pl-6 pt-2.5 pb-2">ENVIRONMENT</th>
|
<th className="text-left pl-6 pt-2.5 pb-2">VALID UNTIL</th>
|
||||||
<th className="text-left pl-6 pt-2.5 pb-2">VAILD UNTIL</th>
|
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -52,9 +49,6 @@ const ApiKeyTable = ({ data, setServiceTokens }: ServiceTokensProps) => {
|
|||||||
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
||||||
{row.name}
|
{row.name}
|
||||||
</td>
|
</td>
|
||||||
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
|
||||||
{reverseEnvMapping[row.environment]}
|
|
||||||
</td>
|
|
||||||
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
||||||
{new Date(row.expiresAt).toUTCString()}
|
{new Date(row.expiresAt).toUTCString()}
|
||||||
</td>
|
</td>
|
||||||
@@ -62,10 +56,10 @@ const ApiKeyTable = ({ data, setServiceTokens }: ServiceTokensProps) => {
|
|||||||
<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} );
|
deleteAPIKey({ apiKeyId: row._id} );
|
||||||
setServiceTokens(data.filter(token => token._id != row._id));
|
setApiKeys(data.filter(token => token._id != row._id));
|
||||||
createNotification({
|
createNotification({
|
||||||
text: `'${row.name}' token has been revoked.`,
|
text: `'${row.name}' API key has been revoked.`,
|
||||||
type: 'error'
|
type: 'error'
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import SecurityClient from '~/utilities/SecurityClient';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
name: string;
|
||||||
|
expiresIn: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This route adds an API key for the user
|
||||||
|
* @param {object} obj
|
||||||
|
* @param {string} obj.name - name of the API key
|
||||||
|
* @param {string} obj.expiresIn - how soon the API key expires in ms
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const addAPIKey = ({
|
||||||
|
name,
|
||||||
|
expiresIn,
|
||||||
|
}: Props) => {
|
||||||
|
return SecurityClient.fetchCall('/api/v2/api-key/', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
name,
|
||||||
|
expiresIn
|
||||||
|
})
|
||||||
|
}).then(async (res) => {
|
||||||
|
if (res && res.status == 200) {
|
||||||
|
return (await res.json());
|
||||||
|
} else {
|
||||||
|
console.log('Failed to add API key');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default addAPIKey;
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import SecurityClient from '~/utilities/SecurityClient';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
apiKeyId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This route revokes the API key with id [apiKeyId]
|
||||||
|
* @param {object} obj
|
||||||
|
* @param {string} obj.apiKeyId - id of the API key to delete
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const deleteAPIKey = ({
|
||||||
|
apiKeyId
|
||||||
|
}: Props) => {
|
||||||
|
return SecurityClient.fetchCall('/api/v2/api-key/' + apiKeyId, {
|
||||||
|
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 API key');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default deleteAPIKey;
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import SecurityClient from '~/utilities/SecurityClient';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This route gets API keys for the user
|
||||||
|
* @param {*} param0
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const getAPIKeys = () => {
|
||||||
|
return SecurityClient.fetchCall(
|
||||||
|
'/api/v2/api-key',
|
||||||
|
{
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
).then(async (res) => {
|
||||||
|
if (res && res.status == 200) {
|
||||||
|
return (await res.json()).apiKeyData;
|
||||||
|
} else {
|
||||||
|
console.log('Failed to get API keys');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getAPIKeys;
|
||||||
@@ -11,7 +11,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This route gets service tokens for a specific user in a project
|
* This route adds a service token for a specific user in a project
|
||||||
* @param {object} obj
|
* @param {object} obj
|
||||||
* @param {string} obj.name - name of the service token
|
* @param {string} obj.name - name of the service token
|
||||||
* @param {string} obj.workspaceId - workspace for which we are issuing the token
|
* @param {string} obj.workspaceId - workspace for which we are issuing the token
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ import passwordCheck from "~/utilities/checks/PasswordCheck";
|
|||||||
import { getTranslatedServerSideProps } from "~/utilities/withTranslateProps";
|
import { getTranslatedServerSideProps } from "~/utilities/withTranslateProps";
|
||||||
|
|
||||||
import AddApiKeyDialog from "../../../components/basic/dialog/AddApiKeyDialog";
|
import AddApiKeyDialog from "../../../components/basic/dialog/AddApiKeyDialog";
|
||||||
|
import deleteAPIKey from "../../api/apiKey/deleteAPIKey";
|
||||||
|
import getAPIKeys from "../../api/apiKey/getAPIKeys";
|
||||||
import getUser from "../../api/user/getUser";
|
import getUser from "../../api/user/getUser";
|
||||||
|
|
||||||
export default function PersonalSettings() {
|
export default function PersonalSettings() {
|
||||||
@@ -43,10 +45,21 @@ export default function PersonalSettings() {
|
|||||||
localStorage.setItem("lang", to);
|
localStorage.setItem("lang", to);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(async () => {
|
useEffect(() => {
|
||||||
|
const load = async () => {
|
||||||
|
try {
|
||||||
let user = await getUser();
|
let user = await getUser();
|
||||||
|
setApiKeys(
|
||||||
|
await getAPIKeys()
|
||||||
|
);
|
||||||
setPersonalEmail(user.email);
|
setPersonalEmail(user.email);
|
||||||
setPersonalName(user.firstName + " " + user.lastName);
|
setPersonalName(user.firstName + " " + user.lastName);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
load();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const closeAddApiKeyModal = () => {
|
const closeAddApiKeyModal = () => {
|
||||||
@@ -63,10 +76,9 @@ export default function PersonalSettings() {
|
|||||||
</Head>
|
</Head>
|
||||||
<AddApiKeyDialog
|
<AddApiKeyDialog
|
||||||
isOpen={isAddApiKeyDialogOpen}
|
isOpen={isAddApiKeyDialogOpen}
|
||||||
workspaceId={router.query.id}
|
|
||||||
closeModal={closeAddApiKeyModal}
|
closeModal={closeAddApiKeyModal}
|
||||||
serviceTokens={apiKeys}
|
apiKeys={apiKeys}
|
||||||
setServiceTokens={setApiKeys}
|
setApiKeys={setApiKeys}
|
||||||
/>
|
/>
|
||||||
<div className="flex flex-row">
|
<div className="flex flex-row">
|
||||||
<div className="w-full max-h-screen pb-2 overflow-y-auto">
|
<div className="w-full max-h-screen pb-2 overflow-y-auto">
|
||||||
@@ -108,17 +120,6 @@ export default function PersonalSettings() {
|
|||||||
<p className="text-sm text-gray-400">
|
<p className="text-sm text-gray-400">
|
||||||
{t("settings-personal:api-keys.description")}
|
{t("settings-personal:api-keys.description")}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm text-gray-400 mb-4">
|
|
||||||
Please, make sure you are on the
|
|
||||||
<a
|
|
||||||
className="text-primary underline underline-offset-2 ml-1"
|
|
||||||
href="https://infisical.com/docs/cli/overview"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
latest version of CLI
|
|
||||||
</a>.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="w-48 mt-2">
|
<div className="w-48 mt-2">
|
||||||
<Button
|
<Button
|
||||||
@@ -134,7 +135,7 @@ export default function PersonalSettings() {
|
|||||||
</div>
|
</div>
|
||||||
<ApiKeyTable
|
<ApiKeyTable
|
||||||
data={apiKeys}
|
data={apiKeys}
|
||||||
setServiceTokens={setApiKeys}
|
setApiKeys={setApiKeys}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -365,4 +366,5 @@ export const getServerSideProps = getTranslatedServerSideProps([
|
|||||||
"settings",
|
"settings",
|
||||||
"settings-personal",
|
"settings-personal",
|
||||||
"section-password",
|
"section-password",
|
||||||
|
"section-api-key"
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"api-keys": "Service Tokens",
|
||||||
|
"api-keys-description": "Every service token is specific to you, a certain project and a certain environment within this project.",
|
||||||
|
"add-new": "Add New Token",
|
||||||
|
"add-dialog": {
|
||||||
|
"title": "Add an API Key",
|
||||||
|
"description": "Specify the name and expiry period. When an API key is generated, you will only be able to see it once before it disappears. Make sure to save it somewhere.",
|
||||||
|
"name": "API Key Name",
|
||||||
|
"add": "Add API Key",
|
||||||
|
"copy-service-token": "Copy your API key",
|
||||||
|
"copy-service-token-description": "Once you close this popup, you will never see your API key again"
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user