mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 12:27:31 +00:00
Revise docs for working with CRUD secrets
This commit is contained in:
+790
-138
File diff suppressed because it is too large
Load Diff
@@ -953,6 +953,7 @@ export const deleteSecrets = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const channel = getChannelFromUserAgent(req.headers['user-agent'])
|
const channel = getChannelFromUserAgent(req.headers['user-agent'])
|
||||||
const toDelete = req.secrets.map((s: any) => s._id);
|
const toDelete = req.secrets.map((s: any) => s._id);
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,35 @@ import { ABILITY_READ } from '../../variables/organization';
|
|||||||
* @param res
|
* @param res
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const getServiceTokenData = async (req: Request, res: Response) => res.status(200).json(req.serviceTokenData);
|
export const getServiceTokenData = async (req: Request, res: Response) => {
|
||||||
|
/*
|
||||||
|
#swagger.summary = 'Return Infisical Token data'
|
||||||
|
#swagger.description = 'Return Infisical Token data'
|
||||||
|
|
||||||
|
#swagger.security = [{
|
||||||
|
"bearerAuth": []
|
||||||
|
}]
|
||||||
|
|
||||||
|
#swagger.responses[200] = {
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"serviceTokenData": {
|
||||||
|
"type": "object",
|
||||||
|
$ref: "#/components/schemas/ServiceTokenData",
|
||||||
|
"description": "Details of service token"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
return res.status(200).json(req.serviceTokenData);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create new service token data for workspace with id [workspaceId] and
|
* Create new service token data for workspace with id [workspaceId] and
|
||||||
@@ -28,6 +56,7 @@ export const getServiceTokenData = async (req: Request, res: Response) => res.st
|
|||||||
*/
|
*/
|
||||||
export const createServiceTokenData = async (req: Request, res: Response) => {
|
export const createServiceTokenData = async (req: Request, res: Response) => {
|
||||||
let serviceToken, serviceTokenData;
|
let serviceToken, serviceTokenData;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
name,
|
name,
|
||||||
@@ -36,7 +65,8 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
|
|||||||
encryptedKey,
|
encryptedKey,
|
||||||
iv,
|
iv,
|
||||||
tag,
|
tag,
|
||||||
expiresIn
|
expiresIn,
|
||||||
|
permissions
|
||||||
} = req.body;
|
} = req.body;
|
||||||
|
|
||||||
const hasAccess = await userHasWorkspaceAccess(req.user, workspaceId, environment, ABILITY_READ)
|
const hasAccess = await userHasWorkspaceAccess(req.user, workspaceId, environment, ABILITY_READ)
|
||||||
@@ -59,7 +89,8 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
|
|||||||
secretHash,
|
secretHash,
|
||||||
encryptedKey,
|
encryptedKey,
|
||||||
iv,
|
iv,
|
||||||
tag
|
tag,
|
||||||
|
permissions
|
||||||
}).save();
|
}).save();
|
||||||
|
|
||||||
// return service token data without sensitive data
|
// return service token data without sensitive data
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import jwt from 'jsonwebtoken';
|
|||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import bcrypt from 'bcrypt';
|
import bcrypt from 'bcrypt';
|
||||||
import {
|
import {
|
||||||
|
IUser,
|
||||||
User,
|
User,
|
||||||
ServiceTokenData,
|
ServiceTokenData,
|
||||||
APIKeyData
|
APIKeyData
|
||||||
@@ -148,7 +149,10 @@ const getAuthSTDPayload = async ({
|
|||||||
|
|
||||||
serviceTokenData = await ServiceTokenData
|
serviceTokenData = await ServiceTokenData
|
||||||
.findById(TOKEN_IDENTIFIER)
|
.findById(TOKEN_IDENTIFIER)
|
||||||
.select('+encryptedKey +iv +tag').populate('user');
|
.select('+encryptedKey +iv +tag')
|
||||||
|
.populate<{user: IUser}>('user');
|
||||||
|
|
||||||
|
if (!serviceTokenData) throw ServiceTokenDataNotFoundError({ message: 'Failed to find service token data' });
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw UnauthorizedRequestError({
|
throw UnauthorizedRequestError({
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import jwt from 'jsonwebtoken';
|
import jwt from 'jsonwebtoken';
|
||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
import { User, ServiceTokenData } from '../models';
|
|
||||||
import {
|
import {
|
||||||
validateAuthMode,
|
validateAuthMode,
|
||||||
getAuthUserPayload,
|
getAuthUserPayload,
|
||||||
getAuthSTDPayload,
|
getAuthSTDPayload,
|
||||||
getAuthAPIKeyPayload
|
getAuthAPIKeyPayload
|
||||||
} from '../helpers/auth';
|
} from '../helpers/auth';
|
||||||
|
import {
|
||||||
|
UnauthorizedRequestError
|
||||||
|
} from '../utils/errors';
|
||||||
|
|
||||||
declare module 'jsonwebtoken' {
|
declare module 'jsonwebtoken' {
|
||||||
export interface UserIDJwtPayload extends jwt.JwtPayload {
|
export interface UserIDJwtPayload extends jwt.JwtPayload {
|
||||||
@@ -25,9 +27,11 @@ declare module 'jsonwebtoken' {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const requireAuth = ({
|
const requireAuth = ({
|
||||||
acceptedAuthModes = ['jwt']
|
acceptedAuthModes = ['jwt'],
|
||||||
|
requiredServiceTokenPermissions = []
|
||||||
}: {
|
}: {
|
||||||
acceptedAuthModes: string[];
|
acceptedAuthModes: string[];
|
||||||
|
requiredServiceTokenPermissions?: string[];
|
||||||
}) => {
|
}) => {
|
||||||
return async (req: Request, res: Response, next: NextFunction) => {
|
return async (req: Request, res: Response, next: NextFunction) => {
|
||||||
// validate auth token against accepted auth modes [acceptedAuthModes]
|
// validate auth token against accepted auth modes [acceptedAuthModes]
|
||||||
@@ -38,11 +42,22 @@ const requireAuth = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// attach auth payloads
|
// attach auth payloads
|
||||||
|
let serviceTokenData;
|
||||||
switch (authTokenType) {
|
switch (authTokenType) {
|
||||||
case 'serviceToken':
|
case 'serviceToken':
|
||||||
req.serviceTokenData = await getAuthSTDPayload({
|
serviceTokenData = await getAuthSTDPayload({
|
||||||
authTokenValue
|
authTokenValue
|
||||||
});
|
});
|
||||||
|
|
||||||
|
requiredServiceTokenPermissions.forEach((requiredServiceTokenPermission) => {
|
||||||
|
if (!serviceTokenData.permissions.includes(requiredServiceTokenPermission)) {
|
||||||
|
return next(UnauthorizedRequestError({ message: 'Failed to authorize service token for endpoint' }));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
req.serviceTokenData = serviceTokenData;
|
||||||
|
req.user = serviceTokenData?.user;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case 'apiKey':
|
case 'apiKey':
|
||||||
req.user = await getAuthAPIKeyPayload({
|
req.user = await getAuthAPIKeyPayload({
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ const requireSecretsAuth = ({
|
|||||||
// case: validate 1 secret
|
// case: validate 1 secret
|
||||||
secrets = await validateSecrets({
|
secrets = await validateSecrets({
|
||||||
userId: req.user._id.toString(),
|
userId: req.user._id.toString(),
|
||||||
secretIds: req.body.secrets.id
|
secretIds: [req.body.secrets.id]
|
||||||
});
|
});
|
||||||
} else if (Array.isArray(req.body.secretIds)) {
|
} else if (Array.isArray(req.body.secretIds)) {
|
||||||
secrets = await validateSecrets({
|
secrets = await validateSecrets({
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ const requireWorkspaceAuth = ({
|
|||||||
return async (req: Request, res: Response, next: NextFunction) => {
|
return async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const { workspaceId } = req[location];
|
const { workspaceId } = req[location];
|
||||||
|
|
||||||
if (req.user) {
|
if (req.user) {
|
||||||
// case: jwt auth
|
// case: jwt auth
|
||||||
const membership = await validateMembership({
|
const membership = await validateMembership({
|
||||||
@@ -32,11 +32,11 @@ const requireWorkspaceAuth = ({
|
|||||||
|
|
||||||
req.membership = membership;
|
req.membership = membership;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
req.serviceTokenData
|
req.serviceTokenData
|
||||||
&& req.serviceTokenData.workspace !== workspaceId
|
&& req.serviceTokenData.workspace !== workspaceId
|
||||||
&& req.serviceTokenData.environment !== req.query.environment
|
&& req.serviceTokenData.environment !== req.body.environment
|
||||||
) {
|
) {
|
||||||
next(UnauthorizedRequestError({message: 'Unable to authenticate workspace'}))
|
next(UnauthorizedRequestError({message: 'Unable to authenticate workspace'}))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,13 +3,14 @@ import { Schema, model, Types } from 'mongoose';
|
|||||||
export interface IServiceTokenData {
|
export interface IServiceTokenData {
|
||||||
name: string;
|
name: string;
|
||||||
workspace: Types.ObjectId;
|
workspace: Types.ObjectId;
|
||||||
environment: string; // TODO: adapt to upcoming environment id
|
environment: string;
|
||||||
user: Types.ObjectId;
|
user: Types.ObjectId;
|
||||||
expiresAt: Date;
|
expiresAt: Date;
|
||||||
secretHash: string;
|
secretHash: string;
|
||||||
encryptedKey: string;
|
encryptedKey: string;
|
||||||
iv: string;
|
iv: string;
|
||||||
tag: string;
|
tag: string;
|
||||||
|
permissions: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const serviceTokenDataSchema = new Schema<IServiceTokenData>(
|
const serviceTokenDataSchema = new Schema<IServiceTokenData>(
|
||||||
@@ -51,6 +52,11 @@ const serviceTokenDataSchema = new Schema<IServiceTokenData>(
|
|||||||
tag: {
|
tag: {
|
||||||
type: String,
|
type: String,
|
||||||
select: false
|
select: false
|
||||||
|
},
|
||||||
|
permissions: {
|
||||||
|
type: [String],
|
||||||
|
enum: ['read', 'write'],
|
||||||
|
default: ['read']
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ import {
|
|||||||
router.post(
|
router.post(
|
||||||
'/batch',
|
'/batch',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt', 'apiKey']
|
acceptedAuthModes: ['jwt', 'apiKey', 'serviceToken'],
|
||||||
|
requiredServiceTokenPermissions: ['read', 'write']
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
@@ -99,7 +100,8 @@ router.post(
|
|||||||
}),
|
}),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt', 'apiKey']
|
acceptedAuthModes: ['jwt', 'apiKey', 'serviceToken'],
|
||||||
|
requiredServiceTokenPermissions: ['write']
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
@@ -115,7 +117,8 @@ router.get(
|
|||||||
query('tagSlugs'),
|
query('tagSlugs'),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt', 'apiKey', 'serviceToken']
|
acceptedAuthModes: ['jwt', 'apiKey', 'serviceToken'],
|
||||||
|
requiredServiceTokenPermissions: ['read']
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
@@ -154,7 +157,8 @@ router.patch(
|
|||||||
}),
|
}),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt', 'apiKey']
|
acceptedAuthModes: ['jwt', 'apiKey', 'serviceToken'],
|
||||||
|
requiredServiceTokenPermissions: ['write']
|
||||||
}),
|
}),
|
||||||
requireSecretsAuth({
|
requireSecretsAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER]
|
||||||
@@ -182,7 +186,8 @@ router.delete(
|
|||||||
.isEmpty(),
|
.isEmpty(),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt', 'apiKey']
|
acceptedAuthModes: ['jwt', 'apiKey', 'serviceToken'],
|
||||||
|
requiredServiceTokenPermissions: ['write']
|
||||||
}),
|
}),
|
||||||
requireSecretsAuth({
|
requireSecretsAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER]
|
||||||
@@ -192,5 +197,3 @@ router.delete(
|
|||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -30,13 +30,22 @@ router.post(
|
|||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
location: 'body'
|
location: 'body'
|
||||||
}),
|
}),
|
||||||
body('name').exists().trim(),
|
body('name').exists().isString().trim(),
|
||||||
body('workspaceId'),
|
body('workspaceId').exists().isString().trim(),
|
||||||
body('environment'),
|
body('environment').exists().isString().trim(),
|
||||||
body('encryptedKey'),
|
body('encryptedKey').exists().isString().trim(),
|
||||||
body('iv'),
|
body('iv').exists().isString().trim(),
|
||||||
body('tag'),
|
body('tag').exists().isString().trim(),
|
||||||
body('expiresIn'), // measured in ms
|
body('expiresIn').exists().isNumeric(), // measured in ms
|
||||||
|
body('permissions').isArray({ min: 1 }).custom((value: string[]) => {
|
||||||
|
const allowedPermissions = ['read', 'write'];
|
||||||
|
const invalidValues = value.filter((v) => !allowedPermissions.includes(v));
|
||||||
|
if (invalidValues.length > 0) {
|
||||||
|
throw new Error(`permissions contains invalid values: ${invalidValues.join(', ')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
serviceTokenDataController.createServiceTokenData
|
serviceTokenDataController.createServiceTokenData
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -197,6 +197,23 @@ const generateOpenAPISpec = async () => {
|
|||||||
secretValueCiphertext: '',
|
secretValueCiphertext: '',
|
||||||
secretValueIV: '',
|
secretValueIV: '',
|
||||||
secretValueTag: '',
|
secretValueTag: '',
|
||||||
|
},
|
||||||
|
ServiceTokenData: {
|
||||||
|
_id: '',
|
||||||
|
name: '',
|
||||||
|
workspace: '',
|
||||||
|
environment: '',
|
||||||
|
user: {
|
||||||
|
_id: '',
|
||||||
|
firstName: '',
|
||||||
|
lastName: ''
|
||||||
|
},
|
||||||
|
expiresAt: '2023-01-13T14:16:12.210Z',
|
||||||
|
encryptedKey: '',
|
||||||
|
iv: '',
|
||||||
|
tag: '',
|
||||||
|
updatedAt: '2023-01-13T14:16:12.210Z',
|
||||||
|
createdAt: '2023-01-13T14:16:12.210Z'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get"
|
||||||
|
openapi: "GET /api/v2/service-token/"
|
||||||
|
---
|
||||||
@@ -2,14 +2,24 @@
|
|||||||
title: "Authentication"
|
title: "Authentication"
|
||||||
---
|
---
|
||||||
|
|
||||||
To authenticate requests with Infisical, you must include an API key in the `X-API-KEY` header of HTTP requests made to the platform. You can obtain an API key in User Settings > API Keys
|
To authenticate requests with Infisical, you can either use an API Key or [Infisical Token](../../../getting-started/dashboard/token); certain endpoints will accept either one or both.
|
||||||
|
- API Key: This general-purpose authentication token provides user access to most endpoints in this reference.
|
||||||
|
- [Infisical Token](../../../getting-started/dashboard/token): This authentication token (also referred to as the service token) is scoped to a specific project and environment and used for CRUD secret operations.
|
||||||
|
|
||||||
|
<AccordionGroup>
|
||||||
|
<Accordion title="API Key">
|
||||||
|
To authenticate requests with Infisical using the API Key, you must include an API key in the `X-API-KEY` header of HTTP requests made to the platform.
|
||||||
|
|
||||||
|
You can obtain an API key in User Settings > API Keys
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||

|
</Accordion>
|
||||||
|
<Accordion title="Infisical Token">
|
||||||
|
To authenticate requests with Infisical using the Infisical Token, you must include your Infisical Token in the `Authorization` header of HTTP requests made to the platform with the value `Bearer st.<rest_of_your_infisical_token>`.
|
||||||
|
|
||||||
<Info>
|
You can obtain an Infisical Token in Project Settings > Service Tokens.
|
||||||
It's important to keep your API key secure, as it grants access to your
|
|
||||||
secrets in Infisical. For added security, set a reasonable expiration time and
|

|
||||||
rotate your API key on a regular basis.
|
</Accordion>
|
||||||
</Info>
|
</AccordionGroup>
|
||||||
@@ -2,48 +2,47 @@
|
|||||||
title: "Create secrets"
|
title: "Create secrets"
|
||||||
---
|
---
|
||||||
|
|
||||||
In this example, we demonstrate how to add secrets to a project and environment.
|
In this example, we demonstrate how to add secrets to a project and environment using an Infisical Token.
|
||||||
|
|
||||||
Prerequisites:
|
Prerequisites:
|
||||||
|
|
||||||
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
||||||
|
- Create an [Infisical Token](../../../getting-started/dashboard/token) for your project and environment.
|
||||||
- Grasp a basic understanding of the system and its underlying cryptography [here](/api-reference/overview/introduction).
|
- Grasp a basic understanding of the system and its underlying cryptography [here](/api-reference/overview/introduction).
|
||||||
|
|
||||||
## Flow
|
## Flow
|
||||||
|
|
||||||
1. [Get your (encrypted) private key](/api-reference/endpoints/users/me).
|
1. [Get your Infisical Token data](/api-reference/endpoints/service-tokens/get) including a (encrypted) project key.
|
||||||
2. Decrypt your (encrypted) private key with your password.
|
2. Decrypt the (encrypted) project key with the key from your Infisical Token.
|
||||||
3. [Get the (encrypted) project key for the project.](/api-reference/endpoints/workspaces/workspace-key)
|
3. Encrypt your secret(s) with the project key
|
||||||
4. Decrypt the (encrypted) project key with your private key.
|
4. [Send (encrypted) secret(s) to Infical](/api-reference/endpoints/secrets/create)
|
||||||
5. Encrypt your secret(s) with the project key.
|
|
||||||
6. [Send (encrypted) secret(s) to the Infical API](/api-reference/endpoints/secrets/create)
|
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
<Tab title="Javascript">
|
||||||
```js
|
```js
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
const axios = require('axios');
|
const axios = require('axios');
|
||||||
|
|
||||||
|
const BASE_URL = 'https://app.infisical.com';
|
||||||
const ALGORITHM = 'aes-256-gcm';
|
const ALGORITHM = 'aes-256-gcm';
|
||||||
const BLOCK_SIZE_BYTES = 16;
|
const BLOCK_SIZE_BYTES = 16;
|
||||||
|
|
||||||
const encrypt = (
|
const encrypt = ({ text, secret }) => {
|
||||||
text,
|
const iv = crypto.randomBytes(BLOCK_SIZE_BYTES);
|
||||||
secret
|
const cipher = crypto.createCipheriv(ALGORITHM, secret, iv);
|
||||||
) => {
|
|
||||||
const iv = crypto.randomBytes(BLOCK_SIZE_BYTES);
|
|
||||||
const cipher = crypto.createCipheriv(ALGORITHM, secret, iv);
|
|
||||||
|
|
||||||
let ciphertext = cipher.update(text, 'utf8', 'base64');
|
let ciphertext = cipher.update(text, 'utf8', 'base64');
|
||||||
ciphertext += cipher.final('base64');
|
ciphertext += cipher.final('base64');
|
||||||
return {
|
return {
|
||||||
ciphertext,
|
ciphertext,
|
||||||
iv: iv.toString('base64'),
|
iv: iv.toString('base64'),
|
||||||
tag: cipher.getAuthTag().toString('base64')
|
tag: cipher.getAuthTag().toString('base64')
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const decrypt = (ciphertext, iv, tag, secret) => {
|
const decrypt = ({ ciphertext, iv, tag, secret}) => {
|
||||||
const decipher = crypto.createDecipheriv(
|
const decipher = crypto.createDecipheriv(
|
||||||
ALGORITHM,
|
ALGORITHM,
|
||||||
secret,
|
secret,
|
||||||
@@ -58,95 +57,96 @@ const decrypt = (ciphertext, iv, tag, secret) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const createSecrets = async () => {
|
const createSecrets = async () => {
|
||||||
const API_KEY = 'your_api_key';
|
const serviceToken = '';
|
||||||
const PSWD = 'your_pswd';
|
const serviceTokenSecret = serviceToken.substring(serviceToken.lastIndexOf('.') + 1);
|
||||||
const WORKSPACE_ID = 'your_workspace_id';
|
|
||||||
|
const secretType = 'shared'; // 'shared' or 'personal'
|
||||||
|
const secretKey = 'some_key';
|
||||||
|
const secretValue = 'some_value';
|
||||||
|
const secretComment = 'some_comment';
|
||||||
|
|
||||||
const SECRET_KEY = 'SOME_KEY';
|
// 1. Get your Infisical Token data
|
||||||
const SECRET_VALUE = 'SOME_VALUE';
|
const { data: serviceTokenData } = await axios.get(
|
||||||
|
`${BASE_URL}/api/v2/service-token`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${serviceToken}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// 1. get (encrypted) private key
|
// 2. Decrypt the (encrypted) project key with the key from your Infisical Token
|
||||||
const user = await axios.get(
|
const projectKey = decrypt({
|
||||||
'https://api.infisical.com/api/v2/users/me', {
|
ciphertext: serviceTokenData.encryptedKey,
|
||||||
headers: {
|
iv: serviceTokenData.iv,
|
||||||
'X-API-KEY': API_KEY
|
tag: serviceTokenData.tag,
|
||||||
}
|
secret: serviceTokenSecret
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
// 3. Encrypt your secret(s) with the project key
|
||||||
|
const {
|
||||||
|
ciphertext: secretKeyCiphertext,
|
||||||
|
iv: secretKeyIV,
|
||||||
|
tag: secretKeyTag
|
||||||
|
} = encrypt({
|
||||||
|
text: secretKey,
|
||||||
|
secret: projectKey
|
||||||
|
});
|
||||||
|
|
||||||
// 2. decrypt your (encrypted) private key with your password
|
const {
|
||||||
const privateKey = decrypt({
|
ciphertext: secretValueCiphertext,
|
||||||
ciphertext: user.encryptedPrivateKey,
|
iv: secretValueIV,
|
||||||
iv: user.iv,
|
tag: secretValueTag
|
||||||
tag: user.tag,
|
} = encrypt({
|
||||||
secret: PSWD.slice(0, 32).padStart(32, '0');
|
text: secretValue,
|
||||||
});
|
secret: projectKey
|
||||||
|
});
|
||||||
|
|
||||||
// 3. get the (encrypted) project key for the project
|
const {
|
||||||
const encryptedProjectKey = await axios.get(
|
ciphertext: secretCommentCiphertext,
|
||||||
`https://api.infisical.com/api/v2/workspace/${WORKSPACE_ID}`, {
|
iv: secretCommentIV,
|
||||||
headers: {
|
tag: secretCommentTag
|
||||||
'X-API-KEY': API_KEY
|
} = encrypt({
|
||||||
}
|
text: secretComment,
|
||||||
}
|
secret: projectKey
|
||||||
);
|
});
|
||||||
|
|
||||||
|
const secret = {
|
||||||
|
type: secretType,
|
||||||
|
secretKeyCiphertext,
|
||||||
|
secretKeyIV,
|
||||||
|
secretKeyTag,
|
||||||
|
secretValueCiphertext,
|
||||||
|
secretValueIV,
|
||||||
|
secretValueTag,
|
||||||
|
secretCommentCiphertext,
|
||||||
|
secretCommentIV,
|
||||||
|
secretCommentTag
|
||||||
|
}
|
||||||
|
|
||||||
// 4. decrypt the project key with your private key
|
// 4. Send (encrypted) secret(s) to Infisical
|
||||||
const projectKey = nacl.box.open(
|
await axios.post(
|
||||||
util.decodeBase64(encryptedProjectKey),
|
`${BASE_URL}/api/v2/secrets`,
|
||||||
util.decodeBase64(projectKey.nonce),
|
{
|
||||||
util.decodeBase64(projectKey.sender.publicKey),
|
workspaceId: serviceTokenData.workspace,
|
||||||
util.decodeBase64(privateKey)
|
environment: serviceTokenData.environment,
|
||||||
);
|
secrets: [secret]
|
||||||
|
},
|
||||||
// 5. encrypt your secret(s) with the project key
|
{
|
||||||
const {
|
headers: {
|
||||||
ciphertext: secretKeyCiphertext,
|
Authorization: `Bearer ${serviceToken}`
|
||||||
iv: secretKeyIV,
|
}
|
||||||
tag: secretKeyTag
|
}
|
||||||
} = encrypt(SECRET_KEY, projectKey);
|
);
|
||||||
|
|
||||||
const {
|
|
||||||
ciphertext: secretValueCiphertext,
|
|
||||||
iv: secretValueIV,
|
|
||||||
tag: secretValueTag
|
|
||||||
} = encrypt(SECRET_VALUE, projectKey);
|
|
||||||
|
|
||||||
const secret = {
|
|
||||||
secretKeyCiphertext,
|
|
||||||
secretKeyIV,
|
|
||||||
secretKeyTag,
|
|
||||||
secretValueCiphertext,
|
|
||||||
secretValueIV,
|
|
||||||
secretValueTag
|
|
||||||
}
|
|
||||||
|
|
||||||
// 6. Send (encrypted) secret(s) to the Infisical API
|
|
||||||
await axios.post(
|
|
||||||
`https://api.infisical.com/api/v2/secrets`,
|
|
||||||
{
|
|
||||||
workspaceId: WORKSPACE_ID,
|
|
||||||
environment: 'dev',
|
|
||||||
secrets: secret
|
|
||||||
},
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
'X-API-KEY': API_KEY
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
createSecrets();
|
createSecrets();
|
||||||
```
|
```
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
<Info>
|
<Info>
|
||||||
This example uses [TweetNaCl.js](https://tweetnacl.js.org/#/), a port of
|
This example uses [TweetNaCl.js](https://tweetnacl.js.org/#/), a port of
|
||||||
TweetNacl/Nacl, to perform asymmeric decryption of the project key but there
|
TweetNacl/Nacl, to perform asymmeric decryption of the project key but there
|
||||||
are ports of NaCl available in every major language.
|
are ports of NaCl available in every major language.
|
||||||
</Info>
|
</Info>
|
||||||
<Tip>
|
|
||||||
It can be useful to perform steps 1-4 ahead of time and store away your
|
|
||||||
private key (and even project key) for later use. The Infisical CLI works by
|
|
||||||
securely storing your private key via your OS keyring.
|
|
||||||
</Tip>
|
|
||||||
@@ -7,24 +7,32 @@ In this example, we demonstrate how to delete secrets
|
|||||||
Prerequisites:
|
Prerequisites:
|
||||||
|
|
||||||
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
||||||
|
- Create either an [API Key](/api-reference/overview/authentication) or [Infisical Token](../../../getting-started/dashboard/token) for your project and environment.
|
||||||
- Grasp a basic understanding of the system and its underlying cryptography [here](/api-reference/overview/introduction).
|
- Grasp a basic understanding of the system and its underlying cryptography [here](/api-reference/overview/introduction).
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
<Tab title="Javascript">
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
```js
|
```js
|
||||||
|
const axios = require('axios');
|
||||||
|
const BASE_URL = 'https://app.infisical.com';
|
||||||
|
|
||||||
const deleteSecrets = async () => {
|
const deleteSecrets = async () => {
|
||||||
const API_KEY = "your_api_key";
|
const serviceToken = 'your_service_token';
|
||||||
const SECRET_ID = "ID"; // ID of secret to delete
|
const secretId = 'id_of_secret_to_delete';
|
||||||
|
|
||||||
// 6. Send ID(s) of secret(s) to delete to the Infisical API
|
// 6. Send ID(s) of secret(s) to delete to the Infisical API
|
||||||
await axios.delete(
|
await axios.delete(
|
||||||
`https://api.infisical.com/api/v2/secrets`,
|
`${BASE_URL}/api/v2/secrets`,
|
||||||
{
|
{
|
||||||
secretIds: SECRET_ID,
|
secretIds: [secretId],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
"X-API-KEY": API_KEY,
|
Authorization: `Bearer ${serviceToken}`
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -32,3 +40,8 @@ const deleteSecrets = async () => {
|
|||||||
|
|
||||||
deleteSecrets();
|
deleteSecrets();
|
||||||
```
|
```
|
||||||
|
|
||||||
|
<Info>
|
||||||
|
If using an `API_KEY` to authenticate with the Infisical API, then you should include it in the `X_API_KEY` header.
|
||||||
|
</Info>
|
||||||
|
|
||||||
|
|||||||
@@ -2,48 +2,33 @@
|
|||||||
title: "Retrieve secrets"
|
title: "Retrieve secrets"
|
||||||
---
|
---
|
||||||
|
|
||||||
In this example, we demonstrate how to retrieve secrets from a project and environment.
|
In this example, we demonstrate how to retrieve secrets from a project and environment using an Infisical Token.
|
||||||
|
|
||||||
Prerequisites:
|
Prerequisites:
|
||||||
|
|
||||||
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
- Set up and add envars to [Infisical Cloud](https://app.infisical.com).
|
||||||
|
- Create an [Infisical Token](../../../getting-started/dashboard/token) for your project and environment.
|
||||||
- Grasp a basic understanding of the system and its underlying cryptography [here](/api-reference/overview/introduction).
|
- Grasp a basic understanding of the system and its underlying cryptography [here](/api-reference/overview/introduction).
|
||||||
|
|
||||||
## Flow
|
## Flow
|
||||||
|
|
||||||
1. [Get your (encrypted) private key.](/api-reference/endpoints/users/me)
|
1. [Get your Infisical Token data](/api-reference/endpoints/service-tokens/get) including a (encrypted) project key.
|
||||||
2. Decrypt your (encrypted) private key with your password.
|
2. [Get secrets for your project and environment](/api-reference/endpoints/secrets/read).
|
||||||
3. [Get the (encrypted) project key for the project.](/api-reference/endpoints/workspaces/workspace-key)
|
3. Decrypt the (encrypted) project key with the key from your Infisical Token.
|
||||||
4. Decrypt the (encrypted) project key with your private key.
|
4. Decrypt the (encrypted) secrets
|
||||||
5. [Get secrets for a project and environment.](/api-reference/endpoints/secrets/read)
|
|
||||||
6. Decrypt the (encrypted) secrets
|
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
<Tab title="Javascript">
|
||||||
```js
|
```js
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
const axios = require('axios');
|
const axios = require('axios');
|
||||||
|
|
||||||
|
const BASE_URL = 'https://app.infisical.com';
|
||||||
const ALGORITHM = 'aes-256-gcm';
|
const ALGORITHM = 'aes-256-gcm';
|
||||||
const BLOCK_SIZE_BYTES = 16;
|
|
||||||
|
|
||||||
const encrypt = (
|
const decrypt = ({ ciphertext, iv, tag, secret}) => {
|
||||||
text,
|
|
||||||
secret
|
|
||||||
) => {
|
|
||||||
const iv = crypto.randomBytes(BLOCK_SIZE_BYTES);
|
|
||||||
const cipher = crypto.createCipheriv(ALGORITHM, secret, iv);
|
|
||||||
|
|
||||||
let ciphertext = cipher.update(text, 'utf8', 'base64');
|
|
||||||
ciphertext += cipher.final('base64');
|
|
||||||
return {
|
|
||||||
ciphertext,
|
|
||||||
iv: iv.toString('base64'),
|
|
||||||
tag: cipher.getAuthTag().toString('base64')
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const decrypt = (ciphertext, iv, tag, secret) => {
|
|
||||||
const decipher = crypto.createDecipheriv(
|
const decipher = crypto.createDecipheriv(
|
||||||
ALGORITHM,
|
ALGORITHM,
|
||||||
secret,
|
secret,
|
||||||
@@ -51,73 +36,63 @@ const decrypt = (ciphertext, iv, tag, secret) => {
|
|||||||
);
|
);
|
||||||
decipher.setAuthTag(Buffer.from(tag, 'base64'));
|
decipher.setAuthTag(Buffer.from(tag, 'base64'));
|
||||||
|
|
||||||
let cleartext = decipher.update(ciphertext, 'base64', 'utf8');
|
let cleartext = decipher.update(ciphertext, 'base64', 'utf8');
|
||||||
cleartext += decipher.final('utf8');
|
cleartext += decipher.final('utf8');
|
||||||
|
|
||||||
return cleartext;
|
return cleartext;
|
||||||
}
|
}
|
||||||
|
|
||||||
const retrieveSecrets = async () => {
|
const getSecrets = async () => {
|
||||||
const API_KEY = 'your_api_key';
|
const serviceToken = 'your_service_token';
|
||||||
const PSWD = 'your_pswd';
|
const serviceTokenSecret = serviceToken.substring(serviceToken.lastIndexOf('.') + 1);
|
||||||
const WORKSPACE_ID = 'your_workspace_id';
|
|
||||||
|
|
||||||
// 1. get (encrypted) private key
|
// 1. Get your Infisical Token data
|
||||||
const user = await axios.get(
|
const { data: serviceTokenData } = await axios.get(
|
||||||
'https://api.infisical.com/api/v2/users/me', {
|
`${BASE_URL}/api/v2/service-token`,
|
||||||
headers: {
|
{
|
||||||
'X-API-KEY': API_KEY
|
headers: {
|
||||||
}
|
Authorization: `Bearer ${serviceToken}`
|
||||||
}
|
}
|
||||||
);
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// 2. decrypt your (encrypted) private key with your password
|
// 2. Get secrets for your project and environment
|
||||||
const privateKey = decrypt({
|
const { data } = await axios.get(
|
||||||
ciphertext: user.encryptedPrivateKey,
|
`${BASE_URL}/api/v2/secrets?${new URLSearchParams({
|
||||||
iv: user.iv,
|
environment: serviceTokenData.environment,
|
||||||
tag: user.tag,
|
workspaceId: serviceTokenData.workspace
|
||||||
secret: PSWD.slice(0, 32).padStart(32, '0');
|
})}`,
|
||||||
});
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${serviceToken}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// 3. get the (encrypted) project key for the project
|
const encryptedSecrets = data.secrets;
|
||||||
const encryptedProjectKey = await axios.get(
|
|
||||||
`https://api.infisical.com/api/v2/workspace/${WORKSPACE_ID}`, {
|
|
||||||
headers: {
|
|
||||||
'X-API-KEY': API_KEY
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// 4. decrypt the project key with your private key
|
// 3. Decrypt the (encrypted) project key with the key from your Infisical Token
|
||||||
const projectKey = nacl.box.open(
|
const projectKey = decrypt({
|
||||||
util.decodeBase64(encryptedProjectKey),
|
ciphertext: serviceTokenData.encryptedKey,
|
||||||
util.decodeBase64(projectKey.nonce),
|
iv: serviceTokenData.iv,
|
||||||
util.decodeBase64(projectKey.sender.publicKey),
|
tag: serviceTokenData.tag,
|
||||||
util.decodeBase64(privateKey)
|
secret: serviceTokenSecret
|
||||||
);
|
});
|
||||||
|
|
||||||
// 5. get (encrypted) secrets for a project and environment.
|
// 4. Decrypt the (encrypted) secrets
|
||||||
const encryptedSecrets = await axios.get(
|
const secrets = encryptedSecrets.map((secret) => {
|
||||||
'https://api.infisical.com/api/v2/secrets', {
|
|
||||||
headers: {
|
|
||||||
'X-API-KEY': API_KEY
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// 6. decrypt the (encrypted) secrets
|
|
||||||
const secrets = encryptedSecrets.map((encryptedSecret) => {
|
|
||||||
const secretKey = decrypt({
|
const secretKey = decrypt({
|
||||||
ciphertext: encryptedSecret.secretKeyCiphertext,
|
ciphertext: secret.secretKeyCiphertext,
|
||||||
iv: encryptedSecret.secretKeyIV,
|
iv: secret.secretKeyIV,
|
||||||
tag: encryptedSecret.secretKeyTag
|
tag: secret.secretKeyTag,
|
||||||
secret: projectKey
|
secret: projectKey
|
||||||
});
|
});
|
||||||
|
|
||||||
const secretValue = decrypt({
|
const secretValue = decrypt({
|
||||||
ciphertext: encryptedSecret.secretValueCiphertext,
|
ciphertext: secret.secretValueCiphertext,
|
||||||
iv: encryptedSecret.secretValueIV,
|
iv: secret.secretValueIV,
|
||||||
tag: encryptedSecret.secretValueTag
|
tag: secret.secretValueTag,
|
||||||
secret: projectKey
|
secret: projectKey
|
||||||
});
|
});
|
||||||
|
|
||||||
return ({
|
return ({
|
||||||
@@ -125,18 +100,18 @@ const retrieveSecrets = async () => {
|
|||||||
secretValue
|
secretValue
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log('secrets: ', secrets);
|
||||||
}
|
}
|
||||||
|
|
||||||
retrieveSecrets();
|
getSecrets();
|
||||||
|
|
||||||
```
|
```
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
<Info>
|
<Info>
|
||||||
This example uses [TweetNaCl.js](https://tweetnacl.js.org/#/), a port of
|
This example uses [TweetNaCl.js](https://tweetnacl.js.org/#/), a port of
|
||||||
TweetNacl/Nacl, to perform asymmeric decryption of the project key but there
|
TweetNacl/Nacl, to perform asymmeric decryption of the project key but there
|
||||||
are ports of NaCl available in every major language.
|
are ports of NaCl available in every major language.
|
||||||
</Info>
|
</Info>
|
||||||
<Tip>
|
|
||||||
It can be useful to perform steps 1-4 ahead of time and store away your
|
|
||||||
private key (and even project key) for later use. The Infisical CLI works by
|
|
||||||
securely storing your private key via your OS keyring.
|
|
||||||
</Tip>
|
|
||||||
@@ -2,48 +2,47 @@
|
|||||||
title: "Update secrets"
|
title: "Update secrets"
|
||||||
---
|
---
|
||||||
|
|
||||||
In this example, we demonstrate how to update secrets
|
In this example, we demonstrate how to update secrets using an Infisical Token.
|
||||||
|
|
||||||
Prerequisites:
|
Prerequisites:
|
||||||
|
|
||||||
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
||||||
|
- Create an [Infisical Token](../../../getting-started/dashboard/token) for your project and environment.
|
||||||
- Grasp a basic understanding of the system and its underlying cryptography [here](/api-reference/overview/introduction).
|
- Grasp a basic understanding of the system and its underlying cryptography [here](/api-reference/overview/introduction).
|
||||||
|
|
||||||
## Flow
|
## Flow
|
||||||
|
|
||||||
1. [Get your (encrypted) private key.](/api-reference/endpoints/users/me)
|
1. [Get your Infisical Token data](/api-reference/endpoints/service-tokens/get) including a (encrypted) project key.
|
||||||
2. Decrypt your (encrypted) private key with your password.
|
2. Decrypt the (encrypted) project key with the key from your Infisical Token.
|
||||||
3. [Get the (encrypted) project key for the project.](/api-reference/endpoints/workspaces/workspace-key)
|
3. Encrypt your updated secret(s) with the project key
|
||||||
4. Decrypt the (encrypted) project key with your private key.
|
4. [Send (encrypted) updated secret(s) to Infical](/api-reference/endpoints/secrets/update)
|
||||||
5. Encrypt your secret(s) with the project key.
|
|
||||||
6. [Send (encrypted) updated secret(s) to the Infical API.](/api-reference/endpoints/secrets/update)
|
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
<Tab title="Javascript">
|
||||||
```js
|
```js
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
const axios = require('axios');
|
const axios = require('axios');
|
||||||
|
|
||||||
|
const BASE_URL = 'https://app.infisical.com';
|
||||||
const ALGORITHM = 'aes-256-gcm';
|
const ALGORITHM = 'aes-256-gcm';
|
||||||
const BLOCK_SIZE_BYTES = 16;
|
const BLOCK_SIZE_BYTES = 16;
|
||||||
|
|
||||||
const encrypt = (
|
const encrypt = ({ text, secret }) => {
|
||||||
text,
|
const iv = crypto.randomBytes(BLOCK_SIZE_BYTES);
|
||||||
secret
|
const cipher = crypto.createCipheriv(ALGORITHM, secret, iv);
|
||||||
) => {
|
|
||||||
const iv = crypto.randomBytes(BLOCK_SIZE_BYTES);
|
|
||||||
const cipher = crypto.createCipheriv(ALGORITHM, secret, iv);
|
|
||||||
|
|
||||||
let ciphertext = cipher.update(text, 'utf8', 'base64');
|
let ciphertext = cipher.update(text, 'utf8', 'base64');
|
||||||
ciphertext += cipher.final('base64');
|
ciphertext += cipher.final('base64');
|
||||||
return {
|
return {
|
||||||
ciphertext,
|
ciphertext,
|
||||||
iv: iv.toString('base64'),
|
iv: iv.toString('base64'),
|
||||||
tag: cipher.getAuthTag().toString('base64')
|
tag: cipher.getAuthTag().toString('base64')
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const decrypt = (ciphertext, iv, tag, secret) => {
|
const decrypt = ({ ciphertext, iv, tag, secret}) => {
|
||||||
const decipher = crypto.createDecipheriv(
|
const decipher = crypto.createDecipheriv(
|
||||||
ALGORITHM,
|
ALGORITHM,
|
||||||
secret,
|
secret,
|
||||||
@@ -58,95 +57,96 @@ const decrypt = (ciphertext, iv, tag, secret) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const updateSecrets = async () => {
|
const updateSecrets = async () => {
|
||||||
const API_KEY = 'your_api_key';
|
const serviceToken = 'your_service_token';
|
||||||
const PSWD = 'your_pswd';
|
const serviceTokenSecret = serviceToken.substring(serviceToken.lastIndexOf('.') + 1);
|
||||||
const WORKSPACE_ID = 'your_workspace_id';
|
|
||||||
|
const secretId = 'id_of_secret_to_update';
|
||||||
|
const secretKey = 'some_key';
|
||||||
|
const secretValue = 'updated_value';
|
||||||
|
const secretComment = 'updated_comment';
|
||||||
|
|
||||||
const SECRET_ID = 'ID' // ID of secret to update
|
// 1. Get your Infisical Token data
|
||||||
const SECRET_KEY = 'SOME_KEY';
|
const { data: serviceTokenData } = await axios.get(
|
||||||
const SECRET_VALUE = 'SOME_VALUE';
|
`${BASE_URL}/api/v2/service-token`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${serviceToken}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// 1. get (encrypted) private key
|
// 2. Decrypt the (encrypted) project key with the key from your Infisical Token
|
||||||
const user = await axios.get(
|
const projectKey = decrypt({
|
||||||
'https://api.infisical.com/api/v2/users/me', {
|
ciphertext: serviceTokenData.encryptedKey,
|
||||||
headers: {
|
iv: serviceTokenData.iv,
|
||||||
'X-API-KEY': API_KEY
|
tag: serviceTokenData.tag,
|
||||||
}
|
secret: serviceTokenSecret
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
// 3. Encrypt your updated secret(s) with the project key
|
||||||
|
const {
|
||||||
|
ciphertext: secretKeyCiphertext,
|
||||||
|
iv: secretKeyIV,
|
||||||
|
tag: secretKeyTag
|
||||||
|
} = encrypt({
|
||||||
|
text: secretKey,
|
||||||
|
secret: projectKey
|
||||||
|
});
|
||||||
|
|
||||||
// 2. decrypt your (encrypted) private key with your password
|
const {
|
||||||
const privateKey = decrypt({
|
ciphertext: secretValueCiphertext,
|
||||||
ciphertext: user.encryptedPrivateKey,
|
iv: secretValueIV,
|
||||||
iv: user.iv,
|
tag: secretValueTag
|
||||||
tag: user.tag,
|
} = encrypt({
|
||||||
secret: PSWD.slice(0, 32).padStart(32, '0');
|
text: secretValue,
|
||||||
});
|
secret: projectKey
|
||||||
|
});
|
||||||
|
|
||||||
// 3. get the (encrypted) project key for the project
|
const {
|
||||||
const encryptedProjectKey = await axios.get(
|
ciphertext: secretCommentCiphertext,
|
||||||
`https://api.infisical.com/api/v2/workspace/${WORKSPACE_ID}`, {
|
iv: secretCommentIV,
|
||||||
headers: {
|
tag: secretCommentTag
|
||||||
'X-API-KEY': API_KEY
|
} = encrypt({
|
||||||
}
|
text: secretComment,
|
||||||
}
|
secret: projectKey
|
||||||
);
|
});
|
||||||
|
|
||||||
// 4. decrypt the project key with your private key
|
const secret = {
|
||||||
const projectKey = nacl.box.open(
|
id: secretId,
|
||||||
util.decodeBase64(encryptedProjectKey),
|
workspace: serviceTokenData.workspace,
|
||||||
util.decodeBase64(projectKey.nonce),
|
environment: serviceTokenData.environment,
|
||||||
util.decodeBase64(projectKey.sender.publicKey),
|
secretKeyCiphertext,
|
||||||
util.decodeBase64(privateKey)
|
secretKeyIV,
|
||||||
);
|
secretKeyTag,
|
||||||
|
secretValueCiphertext,
|
||||||
// 5. encrypt your secret(s) with the project key
|
secretValueIV,
|
||||||
const {
|
secretValueTag,
|
||||||
ciphertext: secretKeyCiphertext,
|
secretCommentCiphertext,
|
||||||
iv: secretKeyIV,
|
secretCommentIV,
|
||||||
tag: secretKeyTag
|
secretCommentTag
|
||||||
} = encrypt(SECRET_KEY, projectKey);
|
}
|
||||||
|
|
||||||
const {
|
// 4. Send (encrypted) updated secret(s) to Infisical
|
||||||
ciphertext: secretValueCiphertext,
|
await axios.patch(
|
||||||
iv: secretValueIV,
|
`${BASE_URL}/api/v2/secrets`,
|
||||||
tag: secretValueTag
|
{
|
||||||
} = encrypt(SECRET_VALUE, projectKey);
|
secrets: [secret]
|
||||||
|
},
|
||||||
const secret = {
|
{
|
||||||
id: SECRET_ID,
|
headers: {
|
||||||
secretKeyCiphertext,
|
Authorization: `Bearer ${serviceToken}`
|
||||||
secretKeyIV,
|
}
|
||||||
secretKeyTag,
|
}
|
||||||
secretValueCiphertext,
|
);
|
||||||
secretValueIV,
|
|
||||||
secretValueTag
|
|
||||||
}
|
|
||||||
|
|
||||||
// 6. Send (encrypted) secret(s) to the Infisical API
|
|
||||||
await axios.patch(
|
|
||||||
`https://api.infisical.com/api/v2/secrets`,
|
|
||||||
{
|
|
||||||
secrets: secret
|
|
||||||
},
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
'X-API-KEY': API_KEY
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSecrets();
|
updateSecrets();
|
||||||
```
|
```
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
<Info>
|
<Info>
|
||||||
This example uses [TweetNaCl.js](https://tweetnacl.js.org/#/), a port of
|
This example uses [TweetNaCl.js](https://tweetnacl.js.org/#/), a port of
|
||||||
TweetNacl/Nacl, to perform asymmeric decryption of the project key but there
|
TweetNacl/Nacl, to perform asymmeric decryption of the project key but there
|
||||||
are ports of NaCl available in every major language.
|
are ports of NaCl available in every major language.
|
||||||
</Info>
|
</Info>
|
||||||
<Tip>
|
|
||||||
It can be useful to perform steps 1-4 ahead of time and store away your
|
|
||||||
private key (and even project key) for later use. The Infisical CLI works by
|
|
||||||
securely storing your private key via your OS keyring.
|
|
||||||
</Tip>
|
|
||||||
@@ -12,10 +12,11 @@ With the REST API, users can create, read, update, and delete secrets, as well a
|
|||||||
|
|
||||||
Using Infisical's API to manage secrets requires a basic understanding of the system and its underlying cryptography detailed [here](/security/overview).
|
Using Infisical's API to manage secrets requires a basic understanding of the system and its underlying cryptography detailed [here](/security/overview).
|
||||||
|
|
||||||
- Each user has a public/private key pair that is stored with the platform; private keys are encrypted locally by the user's password before being sent off to the server during the account signup process.
|
- Each user has a public/private key pair that is stored with the platform; private keys are encrypted locally by protected keys that are encrypted by keys derived from Argon2id applied to the user's password before being sent off to the server during the account signup process.
|
||||||
- Each (encrypted) secret belongs to a project and environment.
|
- Each (encrypted) secret belongs to a project and environment.
|
||||||
- Each project has an (encrypted) project key used to encrypt the secrets within that project; Infisical stores copies of the project key, for each member of that project, encrypted under each member's public key.
|
- Each project has an (encrypted) project key used to encrypt the secrets within that project; Infisical stores copies of the project key, for each member of that project, encrypted under each member's public key.
|
||||||
- Secrets are encrypted symmetrically by your copy of the project key belonging to the project containing.
|
- Secrets are encrypted symmetrically by your copy of the project key belonging to the project containing.
|
||||||
|
- Infisical Tokens contain a symmetric key that can be used to decrypt a copy of a project key from the [call to get the Infisical Token data](/api-reference/endpoints/service-tokens/get).
|
||||||
- Infisical uses AES256-GCM and [TweetNaCl.js](https://tweetnacl.js.org/#/) for symmetric and asymmetric encryption/decryption operations.
|
- Infisical uses AES256-GCM and [TweetNaCl.js](https://tweetnacl.js.org/#/) for symmetric and asymmetric encryption/decryption operations.
|
||||||
|
|
||||||
<Info>
|
<Info>
|
||||||
|
|||||||
@@ -200,6 +200,12 @@
|
|||||||
"api-reference/endpoints/secrets/versions",
|
"api-reference/endpoints/secrets/versions",
|
||||||
"api-reference/endpoints/secrets/rollback-version"
|
"api-reference/endpoints/secrets/rollback-version"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": "Service Tokens",
|
||||||
|
"pages": [
|
||||||
|
"api-reference/endpoints/service-tokens/get"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
+493
-96
@@ -309,78 +309,6 @@ paths:
|
|||||||
example: any
|
example: any
|
||||||
code:
|
code:
|
||||||
example: any
|
example: any
|
||||||
/api/v1/signup/complete-account/signup:
|
|
||||||
post:
|
|
||||||
description: ''
|
|
||||||
parameters: []
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: OK
|
|
||||||
'400':
|
|
||||||
description: Bad Request
|
|
||||||
'403':
|
|
||||||
description: Forbidden
|
|
||||||
requestBody:
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
email:
|
|
||||||
example: any
|
|
||||||
firstName:
|
|
||||||
example: any
|
|
||||||
lastName:
|
|
||||||
example: any
|
|
||||||
publicKey:
|
|
||||||
example: any
|
|
||||||
encryptedPrivateKey:
|
|
||||||
example: any
|
|
||||||
iv:
|
|
||||||
example: any
|
|
||||||
tag:
|
|
||||||
example: any
|
|
||||||
salt:
|
|
||||||
example: any
|
|
||||||
verifier:
|
|
||||||
example: any
|
|
||||||
organizationName:
|
|
||||||
example: any
|
|
||||||
/api/v1/signup/complete-account/invite:
|
|
||||||
post:
|
|
||||||
description: ''
|
|
||||||
parameters: []
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: OK
|
|
||||||
'400':
|
|
||||||
description: Bad Request
|
|
||||||
'403':
|
|
||||||
description: Forbidden
|
|
||||||
requestBody:
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
email:
|
|
||||||
example: any
|
|
||||||
firstName:
|
|
||||||
example: any
|
|
||||||
lastName:
|
|
||||||
example: any
|
|
||||||
publicKey:
|
|
||||||
example: any
|
|
||||||
encryptedPrivateKey:
|
|
||||||
example: any
|
|
||||||
iv:
|
|
||||||
example: any
|
|
||||||
tag:
|
|
||||||
example: any
|
|
||||||
salt:
|
|
||||||
example: any
|
|
||||||
verifier:
|
|
||||||
example: any
|
|
||||||
/api/v1/auth/token:
|
/api/v1/auth/token:
|
||||||
post:
|
post:
|
||||||
description: ''
|
description: ''
|
||||||
@@ -412,7 +340,11 @@ paths:
|
|||||||
/api/v1/auth/login2:
|
/api/v1/auth/login2:
|
||||||
post:
|
post:
|
||||||
description: ''
|
description: ''
|
||||||
parameters: []
|
parameters:
|
||||||
|
- name: user-agent
|
||||||
|
in: header
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: OK
|
description: OK
|
||||||
@@ -431,7 +363,11 @@ paths:
|
|||||||
/api/v1/auth/logout:
|
/api/v1/auth/logout:
|
||||||
post:
|
post:
|
||||||
description: ''
|
description: ''
|
||||||
parameters: []
|
parameters:
|
||||||
|
- name: user-agent
|
||||||
|
in: header
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: OK
|
description: OK
|
||||||
@@ -691,6 +627,18 @@ paths:
|
|||||||
description: OK
|
description: OK
|
||||||
'400':
|
'400':
|
||||||
description: Bad Request
|
description: Bad Request
|
||||||
|
/api/v1/organization/{organizationId}/workspace-memberships:
|
||||||
|
get:
|
||||||
|
description: ''
|
||||||
|
parameters:
|
||||||
|
- name: organizationId
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
/api/v1/workspace/{workspaceId}/keys:
|
/api/v1/workspace/{workspaceId}/keys:
|
||||||
get:
|
get:
|
||||||
description: ''
|
description: ''
|
||||||
@@ -933,6 +881,26 @@ paths:
|
|||||||
properties:
|
properties:
|
||||||
role:
|
role:
|
||||||
example: any
|
example: any
|
||||||
|
/api/v1/membership/{membershipId}/deny-permissions:
|
||||||
|
post:
|
||||||
|
description: ''
|
||||||
|
parameters:
|
||||||
|
- name: membershipId
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
permissions:
|
||||||
|
example: any
|
||||||
/api/v1/key/{workspaceId}:
|
/api/v1/key/{workspaceId}:
|
||||||
post:
|
post:
|
||||||
description: ''
|
description: ''
|
||||||
@@ -1147,11 +1115,17 @@ paths:
|
|||||||
properties:
|
properties:
|
||||||
clientProof:
|
clientProof:
|
||||||
example: any
|
example: any
|
||||||
|
protectedKey:
|
||||||
|
example: any
|
||||||
|
protectedKeyIV:
|
||||||
|
example: any
|
||||||
|
protectedKeyTag:
|
||||||
|
example: any
|
||||||
encryptedPrivateKey:
|
encryptedPrivateKey:
|
||||||
example: any
|
example: any
|
||||||
iv:
|
encryptedPrivateKeyIV:
|
||||||
example: any
|
example: any
|
||||||
tag:
|
encryptedPrivateKeyTag:
|
||||||
example: any
|
example: any
|
||||||
salt:
|
salt:
|
||||||
example: any
|
example: any
|
||||||
@@ -1247,11 +1221,17 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
protectedKey:
|
||||||
|
example: any
|
||||||
|
protectedKeyIV:
|
||||||
|
example: any
|
||||||
|
protectedKeyTag:
|
||||||
|
example: any
|
||||||
encryptedPrivateKey:
|
encryptedPrivateKey:
|
||||||
example: any
|
example: any
|
||||||
iv:
|
encryptedPrivateKeyIV:
|
||||||
example: any
|
example: any
|
||||||
tag:
|
encryptedPrivateKeyTag:
|
||||||
example: any
|
example: any
|
||||||
salt:
|
salt:
|
||||||
example: any
|
example: any
|
||||||
@@ -1270,6 +1250,39 @@ paths:
|
|||||||
description: OK
|
description: OK
|
||||||
'400':
|
'400':
|
||||||
description: Bad Request
|
description: Bad Request
|
||||||
|
/api/v1/integration/:
|
||||||
|
post:
|
||||||
|
description: ''
|
||||||
|
parameters: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
'400':
|
||||||
|
description: Bad Request
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
integrationAuthId:
|
||||||
|
example: any
|
||||||
|
app:
|
||||||
|
example: any
|
||||||
|
appId:
|
||||||
|
example: any
|
||||||
|
isActive:
|
||||||
|
example: any
|
||||||
|
sourceEnvironment:
|
||||||
|
example: any
|
||||||
|
targetEnvironment:
|
||||||
|
example: any
|
||||||
|
owner:
|
||||||
|
example: any
|
||||||
|
path:
|
||||||
|
example: any
|
||||||
|
region:
|
||||||
|
example: any
|
||||||
/api/v1/integration/{integrationId}:
|
/api/v1/integration/{integrationId}:
|
||||||
patch:
|
patch:
|
||||||
description: ''
|
description: ''
|
||||||
@@ -1290,17 +1303,17 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
app:
|
|
||||||
example: any
|
|
||||||
environment:
|
environment:
|
||||||
example: any
|
example: any
|
||||||
isActive:
|
isActive:
|
||||||
example: any
|
example: any
|
||||||
target:
|
app:
|
||||||
example: any
|
example: any
|
||||||
context:
|
appId:
|
||||||
example: any
|
example: any
|
||||||
siteId:
|
targetEnvironment:
|
||||||
|
example: any
|
||||||
|
owner:
|
||||||
example: any
|
example: any
|
||||||
delete:
|
delete:
|
||||||
description: ''
|
description: ''
|
||||||
@@ -1322,6 +1335,33 @@ paths:
|
|||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: OK
|
description: OK
|
||||||
|
/api/v1/integration-auth/{integrationAuthId}:
|
||||||
|
get:
|
||||||
|
description: ''
|
||||||
|
parameters:
|
||||||
|
- name: integrationAuthId
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
'400':
|
||||||
|
description: Bad Request
|
||||||
|
delete:
|
||||||
|
description: ''
|
||||||
|
parameters:
|
||||||
|
- name: integrationAuthId
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
'400':
|
||||||
|
description: Bad Request
|
||||||
/api/v1/integration-auth/oauth-token:
|
/api/v1/integration-auth/oauth-token:
|
||||||
post:
|
post:
|
||||||
description: ''
|
description: ''
|
||||||
@@ -1343,6 +1383,29 @@ paths:
|
|||||||
example: any
|
example: any
|
||||||
integration:
|
integration:
|
||||||
example: any
|
example: any
|
||||||
|
/api/v1/integration-auth/access-token:
|
||||||
|
post:
|
||||||
|
description: ''
|
||||||
|
parameters: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
'400':
|
||||||
|
description: Bad Request
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
workspaceId:
|
||||||
|
example: any
|
||||||
|
accessId:
|
||||||
|
example: any
|
||||||
|
accessToken:
|
||||||
|
example: any
|
||||||
|
integration:
|
||||||
|
example: any
|
||||||
/api/v1/integration-auth/{integrationAuthId}/apps:
|
/api/v1/integration-auth/{integrationAuthId}/apps:
|
||||||
get:
|
get:
|
||||||
description: ''
|
description: ''
|
||||||
@@ -1357,13 +1420,115 @@ paths:
|
|||||||
description: OK
|
description: OK
|
||||||
'400':
|
'400':
|
||||||
description: Bad Request
|
description: Bad Request
|
||||||
/api/v1/integration-auth/{integrationAuthId}:
|
/api/v2/signup/complete-account/signup:
|
||||||
delete:
|
post:
|
||||||
|
description: ''
|
||||||
|
parameters: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
'400':
|
||||||
|
description: Bad Request
|
||||||
|
'403':
|
||||||
|
description: Forbidden
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
email:
|
||||||
|
example: any
|
||||||
|
firstName:
|
||||||
|
example: any
|
||||||
|
lastName:
|
||||||
|
example: any
|
||||||
|
protectedKey:
|
||||||
|
example: any
|
||||||
|
protectedKeyIV:
|
||||||
|
example: any
|
||||||
|
protectedKeyTag:
|
||||||
|
example: any
|
||||||
|
publicKey:
|
||||||
|
example: any
|
||||||
|
encryptedPrivateKey:
|
||||||
|
example: any
|
||||||
|
encryptedPrivateKeyIV:
|
||||||
|
example: any
|
||||||
|
encryptedPrivateKeyTag:
|
||||||
|
example: any
|
||||||
|
salt:
|
||||||
|
example: any
|
||||||
|
verifier:
|
||||||
|
example: any
|
||||||
|
organizationName:
|
||||||
|
example: any
|
||||||
|
/api/v2/signup/complete-account/invite:
|
||||||
|
post:
|
||||||
|
description: ''
|
||||||
|
parameters: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
'400':
|
||||||
|
description: Bad Request
|
||||||
|
'403':
|
||||||
|
description: Forbidden
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
email:
|
||||||
|
example: any
|
||||||
|
firstName:
|
||||||
|
example: any
|
||||||
|
lastName:
|
||||||
|
example: any
|
||||||
|
protectedKey:
|
||||||
|
example: any
|
||||||
|
protectedKeyIV:
|
||||||
|
example: any
|
||||||
|
protectedKeyTag:
|
||||||
|
example: any
|
||||||
|
publicKey:
|
||||||
|
example: any
|
||||||
|
encryptedPrivateKey:
|
||||||
|
example: any
|
||||||
|
encryptedPrivateKeyIV:
|
||||||
|
example: any
|
||||||
|
encryptedPrivateKeyTag:
|
||||||
|
example: any
|
||||||
|
salt:
|
||||||
|
example: any
|
||||||
|
verifier:
|
||||||
|
example: any
|
||||||
|
/api/v2/auth/login1:
|
||||||
|
post:
|
||||||
|
description: ''
|
||||||
|
parameters: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
'400':
|
||||||
|
description: Bad Request
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
email:
|
||||||
|
example: any
|
||||||
|
clientPublicKey:
|
||||||
|
example: any
|
||||||
|
/api/v2/auth/login2:
|
||||||
|
post:
|
||||||
description: ''
|
description: ''
|
||||||
parameters:
|
parameters:
|
||||||
- name: integrationAuthId
|
- name: user-agent
|
||||||
in: path
|
in: header
|
||||||
required: true
|
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
responses:
|
responses:
|
||||||
@@ -1371,6 +1536,54 @@ paths:
|
|||||||
description: OK
|
description: OK
|
||||||
'400':
|
'400':
|
||||||
description: Bad Request
|
description: Bad Request
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
email:
|
||||||
|
example: any
|
||||||
|
clientProof:
|
||||||
|
example: any
|
||||||
|
/api/v2/auth/mfa/send:
|
||||||
|
post:
|
||||||
|
description: ''
|
||||||
|
parameters: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
'400':
|
||||||
|
description: Bad Request
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
email:
|
||||||
|
example: any
|
||||||
|
/api/v2/auth/mfa/verify:
|
||||||
|
post:
|
||||||
|
description: ''
|
||||||
|
parameters:
|
||||||
|
- name: user-agent
|
||||||
|
in: header
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
email:
|
||||||
|
example: any
|
||||||
|
mfaToken:
|
||||||
|
example: any
|
||||||
/api/v2/users/me:
|
/api/v2/users/me:
|
||||||
get:
|
get:
|
||||||
summary: Retrieve the current user on the request
|
summary: Retrieve the current user on the request
|
||||||
@@ -1392,6 +1605,23 @@ paths:
|
|||||||
description: Bad Request
|
description: Bad Request
|
||||||
security:
|
security:
|
||||||
- apiKeyAuth: []
|
- apiKeyAuth: []
|
||||||
|
/api/v2/users/me/mfa:
|
||||||
|
patch:
|
||||||
|
description: ''
|
||||||
|
parameters: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
'400':
|
||||||
|
description: Bad Request
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
isMfaEnabled:
|
||||||
|
example: any
|
||||||
/api/v2/users/me/organizations:
|
/api/v2/users/me/organizations:
|
||||||
get:
|
get:
|
||||||
summary: Return organizations that current user is part of
|
summary: Return organizations that current user is part of
|
||||||
@@ -1615,6 +1845,62 @@ paths:
|
|||||||
properties:
|
properties:
|
||||||
environmentSlug:
|
environmentSlug:
|
||||||
example: any
|
example: any
|
||||||
|
get:
|
||||||
|
description: ''
|
||||||
|
parameters:
|
||||||
|
- name: workspaceId
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
/api/v2/workspace/{workspaceId}/tags:
|
||||||
|
get:
|
||||||
|
description: ''
|
||||||
|
parameters:
|
||||||
|
- name: workspaceId
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
post:
|
||||||
|
description: ''
|
||||||
|
parameters:
|
||||||
|
- name: workspaceId
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
example: any
|
||||||
|
slug:
|
||||||
|
example: any
|
||||||
|
/api/v2/workspace/tags/{tagId}:
|
||||||
|
delete:
|
||||||
|
description: ''
|
||||||
|
parameters:
|
||||||
|
- name: tagId
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
/api/v2/workspace/{workspaceId}/secrets:
|
/api/v2/workspace/{workspaceId}/secrets:
|
||||||
post:
|
post:
|
||||||
description: ''
|
description: ''
|
||||||
@@ -1804,6 +2090,28 @@ paths:
|
|||||||
description: Bad Request
|
description: Bad Request
|
||||||
security:
|
security:
|
||||||
- apiKeyAuth: []
|
- apiKeyAuth: []
|
||||||
|
/api/v2/workspace/{workspaceId}/auto-capitalization:
|
||||||
|
patch:
|
||||||
|
description: ''
|
||||||
|
parameters:
|
||||||
|
- name: workspaceId
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
'400':
|
||||||
|
description: Bad Request
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
autoCapitalization:
|
||||||
|
example: any
|
||||||
/api/v2/secret/batch-create/workspace/{workspaceId}/environment/{environment}:
|
/api/v2/secret/batch-create/workspace/{workspaceId}/environment/{environment}:
|
||||||
post:
|
post:
|
||||||
description: ''
|
description: ''
|
||||||
@@ -1968,11 +2276,38 @@ paths:
|
|||||||
properties:
|
properties:
|
||||||
secret:
|
secret:
|
||||||
example: any
|
example: any
|
||||||
|
/api/v2/secrets/batch:
|
||||||
|
post:
|
||||||
|
description: ''
|
||||||
|
parameters:
|
||||||
|
- name: user-agent
|
||||||
|
in: header
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
workspaceId:
|
||||||
|
example: any
|
||||||
|
environment:
|
||||||
|
example: any
|
||||||
|
requests:
|
||||||
|
example: any
|
||||||
/api/v2/secrets/:
|
/api/v2/secrets/:
|
||||||
post:
|
post:
|
||||||
summary: Create new secret(s)
|
summary: Create new secret(s)
|
||||||
description: Create one or many secrets for a given project and environment.
|
description: Create one or many secrets for a given project and environment.
|
||||||
parameters: []
|
parameters:
|
||||||
|
- name: user-agent
|
||||||
|
in: header
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: OK
|
description: OK
|
||||||
@@ -2022,6 +2357,10 @@ paths:
|
|||||||
in: query
|
in: query
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
|
- name: user-agent
|
||||||
|
in: header
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
- name: content
|
- name: content
|
||||||
in: query
|
in: query
|
||||||
schema:
|
schema:
|
||||||
@@ -2073,7 +2412,11 @@ paths:
|
|||||||
delete:
|
delete:
|
||||||
summary: Delete secret(s)
|
summary: Delete secret(s)
|
||||||
description: Delete one or many secrets by their ID(s)
|
description: Delete one or many secrets by their ID(s)
|
||||||
parameters: []
|
parameters:
|
||||||
|
- name: user-agent
|
||||||
|
in: header
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: OK
|
description: OK
|
||||||
@@ -2101,13 +2444,23 @@ paths:
|
|||||||
description: ID(s) of secrets - string or array of strings
|
description: ID(s) of secrets - string or array of strings
|
||||||
/api/v2/service-token/:
|
/api/v2/service-token/:
|
||||||
get:
|
get:
|
||||||
description: ''
|
summary: Return Infisical Token data
|
||||||
|
description: Return Infisical Token data
|
||||||
parameters: []
|
parameters: []
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: OK
|
description: OK
|
||||||
'400':
|
content:
|
||||||
description: Bad Request
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
serviceTokenData:
|
||||||
|
type: object
|
||||||
|
$ref: '#/components/schemas/ServiceTokenData'
|
||||||
|
description: Details of service token
|
||||||
|
security:
|
||||||
|
- bearerAuth: []
|
||||||
post:
|
post:
|
||||||
description: ''
|
description: ''
|
||||||
parameters: []
|
parameters: []
|
||||||
@@ -2136,6 +2489,8 @@ paths:
|
|||||||
example: any
|
example: any
|
||||||
expiresIn:
|
expiresIn:
|
||||||
example: any
|
example: any
|
||||||
|
permissions:
|
||||||
|
example: any
|
||||||
/api/v2/service-token/{serviceTokenDataId}:
|
/api/v2/service-token/{serviceTokenDataId}:
|
||||||
delete:
|
delete:
|
||||||
description: ''
|
description: ''
|
||||||
@@ -2317,9 +2672,6 @@ components:
|
|||||||
Project:
|
Project:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
_id:
|
|
||||||
type: string
|
|
||||||
example: ''
|
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
example: My Project
|
example: My Project
|
||||||
@@ -2602,6 +2954,51 @@ components:
|
|||||||
secretValueTag:
|
secretValueTag:
|
||||||
type: string
|
type: string
|
||||||
example: ''
|
example: ''
|
||||||
|
ServiceTokenData:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
_id:
|
||||||
|
type: string
|
||||||
|
example: ''
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
example: ''
|
||||||
|
workspace:
|
||||||
|
type: string
|
||||||
|
example: ''
|
||||||
|
environment:
|
||||||
|
type: string
|
||||||
|
example: ''
|
||||||
|
user:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
_id:
|
||||||
|
type: string
|
||||||
|
example: ''
|
||||||
|
firstName:
|
||||||
|
type: string
|
||||||
|
example: ''
|
||||||
|
lastName:
|
||||||
|
type: string
|
||||||
|
example: ''
|
||||||
|
expiresAt:
|
||||||
|
type: string
|
||||||
|
example: '2023-01-13T14:16:12.210Z'
|
||||||
|
encryptedKey:
|
||||||
|
type: string
|
||||||
|
example: ''
|
||||||
|
iv:
|
||||||
|
type: string
|
||||||
|
example: ''
|
||||||
|
tag:
|
||||||
|
type: string
|
||||||
|
example: ''
|
||||||
|
updatedAt:
|
||||||
|
type: string
|
||||||
|
example: '2023-01-13T14:16:12.210Z'
|
||||||
|
createdAt:
|
||||||
|
type: string
|
||||||
|
example: '2023-01-13T14:16:12.210Z'
|
||||||
securitySchemes:
|
securitySchemes:
|
||||||
bearerAuth:
|
bearerAuth:
|
||||||
type: http
|
type: http
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export const useCreateServiceToken = () => {
|
|||||||
return useMutation<CreateServiceTokenRes, {}, CreateServiceTokenDTO>({
|
return useMutation<CreateServiceTokenRes, {}, CreateServiceTokenDTO>({
|
||||||
mutationFn: async (body) => {
|
mutationFn: async (body) => {
|
||||||
const { data } = await apiRequest.post('/api/v2/service-token/', body);
|
const { data } = await apiRequest.post('/api/v2/service-token/', body);
|
||||||
data.serviceToken += `.${ body.randomBytes}`;
|
data.serviceToken += `.${body.randomBytes}`;
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
onSuccess: ({ serviceTokenData: { workspace } }) => {
|
onSuccess: ({ serviceTokenData: { workspace } }) => {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export type CreateServiceTokenDTO = {
|
|||||||
iv: string;
|
iv: string;
|
||||||
tag: string;
|
tag: string;
|
||||||
randomBytes: string;
|
randomBytes: string;
|
||||||
|
permissions: ('read' | 'write')[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CreateServiceTokenRes = {
|
export type CreateServiceTokenRes = {
|
||||||
|
|||||||
@@ -203,7 +203,7 @@ export const ProjectSettingsPage = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onCreateServiceToken = async ({ environment, expiresIn, name }: CreateServiceToken) => {
|
const onCreateServiceToken = async ({ environment, expiresIn, name, permissions }: CreateServiceToken) => {
|
||||||
// type guard
|
// type guard
|
||||||
if (!latestFileKey) return '';
|
if (!latestFileKey) return '';
|
||||||
try {
|
try {
|
||||||
@@ -228,9 +228,9 @@ export const ProjectSettingsPage = () => {
|
|||||||
expiresIn: Number(expiresIn),
|
expiresIn: Number(expiresIn),
|
||||||
name,
|
name,
|
||||||
workspaceId: workspaceID,
|
workspaceId: workspaceID,
|
||||||
randomBytes
|
randomBytes,
|
||||||
|
permissions
|
||||||
});
|
});
|
||||||
console.log(res);
|
|
||||||
createNotification({
|
createNotification({
|
||||||
text: 'Successfully created a service token',
|
text: 'Successfully created a service token',
|
||||||
type: 'success'
|
type: 'success'
|
||||||
|
|||||||
+97
-3
@@ -8,6 +8,7 @@ import * as yup from 'yup';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
|
Checkbox,
|
||||||
DeleteActionModal,
|
DeleteActionModal,
|
||||||
EmptyState,
|
EmptyState,
|
||||||
FormControl,
|
FormControl,
|
||||||
@@ -42,7 +43,11 @@ const apiTokenExpiry = [
|
|||||||
const createServiceTokenSchema = yup.object({
|
const createServiceTokenSchema = yup.object({
|
||||||
name: yup.string().required().label('Service Token Name'),
|
name: yup.string().required().label('Service Token Name'),
|
||||||
environment: yup.string().required().label('Environment'),
|
environment: yup.string().required().label('Environment'),
|
||||||
expiresIn: yup.string().required().label('Service Token Name')
|
expiresIn: yup.string().required().label('Service Token Name'),
|
||||||
|
permissions: yup.object().shape({
|
||||||
|
read: yup.boolean().required(),
|
||||||
|
write: yup.boolean().required()
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
export type CreateServiceToken = yup.InferType<typeof createServiceTokenSchema>;
|
export type CreateServiceToken = yup.InferType<typeof createServiceTokenSchema>;
|
||||||
@@ -87,7 +92,7 @@ export const ServiceTokenSection = ({
|
|||||||
'createAPIToken',
|
'createAPIToken',
|
||||||
'deleteAPITokenConfirmation'
|
'deleteAPITokenConfirmation'
|
||||||
] as const);
|
] as const);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
reset,
|
reset,
|
||||||
@@ -100,7 +105,13 @@ export const ServiceTokenSection = ({
|
|||||||
const hasServiceToken = Boolean(newToken);
|
const hasServiceToken = Boolean(newToken);
|
||||||
|
|
||||||
const onFormSubmit = async (data: CreateServiceToken) => {
|
const onFormSubmit = async (data: CreateServiceToken) => {
|
||||||
const token = await onCreateToken(data);
|
// transform permissions object into array
|
||||||
|
const dataWithPermissionsArray = data;
|
||||||
|
dataWithPermissionsArray.permissions = Object.entries(data.permissions)
|
||||||
|
.filter(([, value]) => value)
|
||||||
|
.map(([key]) => key);
|
||||||
|
|
||||||
|
const token = await onCreateToken(dataWithPermissionsArray);
|
||||||
setToken(token);
|
setToken(token);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -216,6 +227,89 @@ export const ServiceTokenSection = ({
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="permissions"
|
||||||
|
defaultValue={{
|
||||||
|
read: true,
|
||||||
|
write: false
|
||||||
|
}}
|
||||||
|
render={({ field: { onChange, value }, fieldState: { error }}) => {
|
||||||
|
const options = [{
|
||||||
|
label: 'Read (default)',
|
||||||
|
value: 'read'
|
||||||
|
}, {
|
||||||
|
label: 'Write (optional)',
|
||||||
|
value: 'write'
|
||||||
|
}];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormControl
|
||||||
|
label="Permissions"
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
>
|
||||||
|
<>
|
||||||
|
{options.map(({ label, value: optionValue }) => {
|
||||||
|
// TODO: refactor
|
||||||
|
return (
|
||||||
|
<Checkbox
|
||||||
|
key={optionValue}
|
||||||
|
className="data-[state=checked]:bg-primary"
|
||||||
|
isChecked={value[optionValue]}
|
||||||
|
isDisabled={ optionValue === 'read'}
|
||||||
|
onCheckedChange={(state) => {
|
||||||
|
onChange({
|
||||||
|
...value,
|
||||||
|
[optionValue]: state
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</Checkbox>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/* <Controller
|
||||||
|
name="isReadEnabled"
|
||||||
|
defaultValue={true}
|
||||||
|
control={control}
|
||||||
|
render={({ field: { onChange, ... field }, fieldState }) => {
|
||||||
|
return (
|
||||||
|
<Checkbox
|
||||||
|
className="data-[state=checked]:bg-primary"
|
||||||
|
isChecked={field.value}
|
||||||
|
onCheckedChange={(state) => {
|
||||||
|
onChange(state);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Read (default)
|
||||||
|
</Checkbox>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
name="isWriteEnabled"
|
||||||
|
defaultValue={false}
|
||||||
|
control={control}
|
||||||
|
render={({ field: { onChange, ... field }, fieldState }) => {
|
||||||
|
return (
|
||||||
|
<Checkbox
|
||||||
|
className="data-[state=checked]:bg-primary"
|
||||||
|
isChecked={field.value}
|
||||||
|
onCheckedChange={(state) => {
|
||||||
|
onChange(state);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Write (optional)
|
||||||
|
</Checkbox>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/> */}
|
||||||
<div className="mt-8 flex items-center">
|
<div className="mt-8 flex items-center">
|
||||||
<Button
|
<Button
|
||||||
className="mr-4"
|
className="mr-4"
|
||||||
|
|||||||
Reference in New Issue
Block a user