mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-05 00:26:24 +00:00
@@ -42,9 +42,9 @@
|
|||||||
- **Navigate Multiple Environments** per project (e.g. development, staging, production, etc.)
|
- **Navigate Multiple Environments** per project (e.g. development, staging, production, etc.)
|
||||||
- **Personal overrides** for secrets and configs
|
- **Personal overrides** for secrets and configs
|
||||||
- **[Integrations](https://infisical.com/docs/integrations/overview)** with CI/CD and production infrastructure
|
- **[Integrations](https://infisical.com/docs/integrations/overview)** with CI/CD and production infrastructure
|
||||||
- **[Secret Versioning](https://infisical.com/docs/getting-started/dashboard/versioning)** - check the history of change for any secret
|
- **[Secret Versioning](https://infisical.com/docs/getting-started/dashboard/versioning)** to view the change history for any secret
|
||||||
- **[Activity Logs](https://infisical.com/docs/getting-started/dashboard/audit-logs)** - check what user in the project is performing what actions with secrets
|
- **[Activity Logs](https://infisical.com/docs/getting-started/dashboard/audit-logs)** to record every action taken in a project.
|
||||||
- **[Point-in-time Secrets Recovery](https://infisical.com/docs/getting-started/dashboard/pit-recovery)** - roll back to any snapshot of you secrets
|
- **[Point-in-time Secrets Recovery](https://infisical.com/docs/getting-started/dashboard/pit-recovery)** for rolling back to any snapshot of your secrets
|
||||||
- 🔜 **1-Click Deploy** to Digital Ocean and Heroku
|
- 🔜 **1-Click Deploy** to Digital Ocean and Heroku
|
||||||
- 🔜 **Authentication/Authorization** for projects (read/write controls soon)
|
- 🔜 **Authentication/Authorization** for projects (read/write controls soon)
|
||||||
- 🔜 **Automatic Secret Rotation**
|
- 🔜 **Automatic Secret Rotation**
|
||||||
@@ -338,6 +338,10 @@ Infisical officially launched as v.1.0 on November 21st, 2022. There are a lot o
|
|||||||
|
|
||||||
## 🌎 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).
|
||||||
|
|||||||
Generated
+3
-6
@@ -28,6 +28,7 @@
|
|||||||
"express-validator": "^6.14.2",
|
"express-validator": "^6.14.2",
|
||||||
"handlebars": "^4.7.7",
|
"handlebars": "^4.7.7",
|
||||||
"helmet": "^5.1.1",
|
"helmet": "^5.1.1",
|
||||||
|
"js-yaml": "^4.1.0",
|
||||||
"jsonwebtoken": "^9.0.0",
|
"jsonwebtoken": "^9.0.0",
|
||||||
"jsrp": "^0.2.4",
|
"jsrp": "^0.2.4",
|
||||||
"libsodium-wrappers": "^0.7.10",
|
"libsodium-wrappers": "^0.7.10",
|
||||||
@@ -3698,8 +3699,7 @@
|
|||||||
"node_modules/argparse": {
|
"node_modules/argparse": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/array-flatten": {
|
"node_modules/array-flatten": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
@@ -6638,7 +6638,6 @@
|
|||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"argparse": "^2.0.1"
|
"argparse": "^2.0.1"
|
||||||
},
|
},
|
||||||
@@ -14980,8 +14979,7 @@
|
|||||||
"argparse": {
|
"argparse": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"array-flatten": {
|
"array-flatten": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
@@ -17197,7 +17195,6 @@
|
|||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"argparse": "^2.0.1"
|
"argparse": "^2.0.1"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "npm run build && node build/index.js",
|
"start": "npm run build && node build/index.js",
|
||||||
"dev": "nodemon",
|
"dev": "nodemon",
|
||||||
"swagger-autogen": "node ./swagger.ts",
|
"swagger-autogen": "node ./swagger/index.ts",
|
||||||
"build": "rimraf ./build && tsc && cp -R ./src/templates ./build",
|
"build": "rimraf ./build && tsc && cp -R ./src/templates ./build",
|
||||||
"lint": "eslint . --ext .ts",
|
"lint": "eslint . --ext .ts",
|
||||||
"lint-and-fix": "eslint . --ext .ts --fix",
|
"lint-and-fix": "eslint . --ext .ts --fix",
|
||||||
@@ -94,6 +94,7 @@
|
|||||||
"express-validator": "^6.14.2",
|
"express-validator": "^6.14.2",
|
||||||
"handlebars": "^4.7.7",
|
"handlebars": "^4.7.7",
|
||||||
"helmet": "^5.1.1",
|
"helmet": "^5.1.1",
|
||||||
|
"js-yaml": "^4.1.0",
|
||||||
"jsonwebtoken": "^9.0.0",
|
"jsonwebtoken": "^9.0.0",
|
||||||
"jsrp": "^0.2.4",
|
"jsrp": "^0.2.4",
|
||||||
"libsodium-wrappers": "^0.7.10",
|
"libsodium-wrappers": "^0.7.10",
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
+4
-2
@@ -8,7 +8,7 @@ import cookieParser from 'cookie-parser';
|
|||||||
import dotenv from 'dotenv';
|
import dotenv from 'dotenv';
|
||||||
import swaggerUi = require('swagger-ui-express');
|
import swaggerUi = require('swagger-ui-express');
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
const swaggerFile = require('../api-documentation.json')
|
const swaggerFile = require('../spec.json')
|
||||||
|
|
||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
@@ -41,7 +41,8 @@ import {
|
|||||||
integrationAuth as v1IntegrationAuthRouter
|
integrationAuth as v1IntegrationAuthRouter
|
||||||
} from './routes/v1';
|
} from './routes/v1';
|
||||||
import {
|
import {
|
||||||
secret as v2SecretRouter,
|
users as v2UsersRouter,
|
||||||
|
secret as v2SecretRouter, // begin to phase out
|
||||||
secrets as v2SecretsRouter,
|
secrets as v2SecretsRouter,
|
||||||
workspace as v2WorkspaceRouter,
|
workspace as v2WorkspaceRouter,
|
||||||
serviceTokenData as v2ServiceTokenDataRouter,
|
serviceTokenData as v2ServiceTokenDataRouter,
|
||||||
@@ -104,6 +105,7 @@ app.use('/api/v1/integration', v1IntegrationRouter);
|
|||||||
app.use('/api/v1/integration-auth', v1IntegrationAuthRouter);
|
app.use('/api/v1/integration-auth', v1IntegrationAuthRouter);
|
||||||
|
|
||||||
// v2 routes
|
// v2 routes
|
||||||
|
app.use('/api/v2/users', v2UsersRouter);
|
||||||
app.use('/api/v2/workspace', v2EnvironmentRouter);
|
app.use('/api/v2/workspace', v2EnvironmentRouter);
|
||||||
app.use('/api/v2/workspace', v2WorkspaceRouter); // TODO: turn into plural route
|
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
|
||||||
|
|||||||
@@ -170,10 +170,11 @@ export const logout = async (req: Request, res: Response) => {
|
|||||||
* @param res
|
* @param res
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const checkAuth = async (req: Request, res: Response) =>
|
export const checkAuth = async (req: Request, res: Response) => {
|
||||||
res.status(200).send({
|
return res.status(200).send({
|
||||||
message: 'Authenticated'
|
message: 'Authenticated'
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return new token by redeeming refresh token
|
* Return new token by redeeming refresh token
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import * as usersController from './usersController';
|
||||||
import * as workspaceController from './workspaceController';
|
import * as workspaceController from './workspaceController';
|
||||||
import * as serviceTokenDataController from './serviceTokenDataController';
|
import * as serviceTokenDataController from './serviceTokenDataController';
|
||||||
import * as apiKeyDataController from './apiKeyDataController';
|
import * as apiKeyDataController from './apiKeyDataController';
|
||||||
@@ -6,6 +7,7 @@ import * as secretsController from './secretsController';
|
|||||||
import * as environmentController from './environmentController';
|
import * as environmentController from './environmentController';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
usersController,
|
||||||
workspaceController,
|
workspaceController,
|
||||||
serviceTokenDataController,
|
serviceTokenDataController,
|
||||||
apiKeyDataController,
|
apiKeyDataController,
|
||||||
|
|||||||
@@ -23,6 +23,58 @@ import { BadRequestError } from '../../utils/errors';
|
|||||||
* @param res
|
* @param res
|
||||||
*/
|
*/
|
||||||
export const createSecrets = async (req: Request, res: Response) => {
|
export const createSecrets = async (req: Request, res: Response) => {
|
||||||
|
/*
|
||||||
|
#swagger.summary = 'Create new secret(s)'
|
||||||
|
#swagger.description = 'Create one or many secrets for a given project and environment.'
|
||||||
|
|
||||||
|
#swagger.security = [{
|
||||||
|
"apiKeyAuth": []
|
||||||
|
}]
|
||||||
|
|
||||||
|
#swagger.requestBody = {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"workspaceId": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "ID of project",
|
||||||
|
},
|
||||||
|
"environment": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Environment within project"
|
||||||
|
},
|
||||||
|
"secrets": {
|
||||||
|
$ref: "#/components/schemas/CreateSecret",
|
||||||
|
"description": "Secret(s) to create - object or array of objects"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#swagger.responses[200] = {
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"secrets": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
$ref: "#/components/schemas/Secret"
|
||||||
|
},
|
||||||
|
"description": "Newly-created secrets for the given project and environment"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
const channel = req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli';
|
const channel = req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli';
|
||||||
const { workspaceId, environment } = req.body;
|
const { workspaceId, environment } = req.body;
|
||||||
|
|
||||||
@@ -161,6 +213,45 @@ export const createSecrets = async (req: Request, res: Response) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const getSecrets = async (req: Request, res: Response) => {
|
export const getSecrets = async (req: Request, res: Response) => {
|
||||||
|
/*
|
||||||
|
#swagger.summary = 'Read secrets'
|
||||||
|
#swagger.description = 'Read secrets from a project and environment'
|
||||||
|
|
||||||
|
#swagger.security = [{
|
||||||
|
"apiKeyAuth": []
|
||||||
|
}]
|
||||||
|
|
||||||
|
#swagger.parameters['workspaceId'] = {
|
||||||
|
"description": "ID of project",
|
||||||
|
"required": true,
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
|
||||||
|
#swagger.parameters['environment'] = {
|
||||||
|
"description": "Environment within project",
|
||||||
|
"required": true,
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
|
||||||
|
#swagger.responses[200] = {
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"secrets": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
$ref: "#/components/schemas/Secret"
|
||||||
|
},
|
||||||
|
"description": "Secrets for the given project and environment"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
const { workspaceId, environment } = req.query;
|
const { workspaceId, environment } = req.query;
|
||||||
|
|
||||||
let userId: Types.ObjectId | undefined = undefined // used for getting personal secrets for user
|
let userId: Types.ObjectId | undefined = undefined // used for getting personal secrets for user
|
||||||
@@ -231,6 +322,50 @@ export const getSecrets = async (req: Request, res: Response) => {
|
|||||||
* @param res
|
* @param res
|
||||||
*/
|
*/
|
||||||
export const updateSecrets = async (req: Request, res: Response) => {
|
export const updateSecrets = async (req: Request, res: Response) => {
|
||||||
|
/*
|
||||||
|
#swagger.summary = 'Update secret(s)'
|
||||||
|
#swagger.description = 'Update secret(s)'
|
||||||
|
|
||||||
|
#swagger.security = [{
|
||||||
|
"apiKeyAuth": []
|
||||||
|
}]
|
||||||
|
|
||||||
|
#swagger.requestBody = {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"secrets": {
|
||||||
|
$ref: "#/components/schemas/UpdateSecret",
|
||||||
|
"description": "Secret(s) to update - object or array of objects"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#swagger.responses[200] = {
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"secrets": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
$ref: "#/components/schemas/Secret"
|
||||||
|
},
|
||||||
|
"description": "Updated secrets"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
const channel = req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli';
|
const channel = req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli';
|
||||||
|
|
||||||
// TODO: move type
|
// TODO: move type
|
||||||
@@ -403,6 +538,50 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
|||||||
* @param res
|
* @param res
|
||||||
*/
|
*/
|
||||||
export const deleteSecrets = async (req: Request, res: Response) => {
|
export const deleteSecrets = async (req: Request, res: Response) => {
|
||||||
|
/*
|
||||||
|
#swagger.summary = 'Delete secret(s)'
|
||||||
|
#swagger.description = 'Delete one or many secrets by their ID(s)'
|
||||||
|
|
||||||
|
#swagger.security = [{
|
||||||
|
"apiKeyAuth": []
|
||||||
|
}]
|
||||||
|
|
||||||
|
#swagger.requestBody = {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"secretIds": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "ID(s) of secrets - string or array of strings"
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#swagger.responses[200] = {
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"secrets": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
$ref: "#/components/schemas/Secret"
|
||||||
|
},
|
||||||
|
"description": "Deleted secrets"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
const channel = req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli';
|
const channel = req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli';
|
||||||
const toDelete = req.secrets.map((s: any) => s._id);
|
const toDelete = req.secrets.map((s: any) => s._id);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import { Request, Response } from 'express';
|
||||||
|
import * as Sentry from '@sentry/node';
|
||||||
|
import {
|
||||||
|
User
|
||||||
|
} from '../../models';
|
||||||
|
|
||||||
|
export const getMe = async (req: Request, res: Response) => {
|
||||||
|
/*
|
||||||
|
#swagger.summary = "Retrieve the current user on the request"
|
||||||
|
#swagger.description = "Retrieve the current user on the request"
|
||||||
|
|
||||||
|
#swagger.security = [{
|
||||||
|
"apiKeyAuth": []
|
||||||
|
}]
|
||||||
|
|
||||||
|
#swagger.responses[200] = {
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"user": {
|
||||||
|
"type": "object",
|
||||||
|
$ref: "#/components/schemas/CurrentUser",
|
||||||
|
"description": "Current user on request"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
let user;
|
||||||
|
try {
|
||||||
|
user = await User
|
||||||
|
.findById(req.user._id)
|
||||||
|
.select('+publicKey +encryptedPrivateKey +iv +tag');
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser({ email: req.user.email });
|
||||||
|
Sentry.captureException(err);
|
||||||
|
return res.status(400).send({
|
||||||
|
message: 'Failed to get user'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
user
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
|
import { Types } from 'mongoose';
|
||||||
import {
|
import {
|
||||||
Workspace,
|
Workspace,
|
||||||
|
Secret,
|
||||||
Membership,
|
Membership,
|
||||||
MembershipOrg,
|
MembershipOrg,
|
||||||
Integration,
|
Integration,
|
||||||
@@ -174,6 +176,34 @@ export const pullSecrets = async (req: Request, res: Response) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getWorkspaceKey = async (req: Request, res: Response) => {
|
export const getWorkspaceKey = async (req: Request, res: Response) => {
|
||||||
|
/*
|
||||||
|
#swagger.summary = 'Return encrypted project key'
|
||||||
|
#swagger.description = 'Return encrypted project key'
|
||||||
|
|
||||||
|
#swagger.security = [{
|
||||||
|
"apiKeyAuth": []
|
||||||
|
}]
|
||||||
|
|
||||||
|
#swagger.parameters['workspaceId'] = {
|
||||||
|
"description": "ID of project",
|
||||||
|
"required": true,
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
|
||||||
|
#swagger.responses[200] = {
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
$ref: "#/components/schemas/ProjectKey"
|
||||||
|
},
|
||||||
|
"description": "Encrypted project key for the given project"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
let key;
|
let key;
|
||||||
try {
|
try {
|
||||||
const { workspaceId } = req.params;
|
const { workspaceId } = req.params;
|
||||||
@@ -219,4 +249,222 @@ export const getWorkspaceServiceTokenData = async (
|
|||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
serviceTokenData
|
serviceTokenData
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return memberships for workspace with id [workspaceId]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getWorkspaceMemberships = async (req: Request, res: Response) => {
|
||||||
|
/*
|
||||||
|
#swagger.summary = 'Return project memberships'
|
||||||
|
#swagger.description = 'Return project memberships'
|
||||||
|
|
||||||
|
#swagger.security = [{
|
||||||
|
"apiKeyAuth": []
|
||||||
|
}]
|
||||||
|
|
||||||
|
#swagger.parameters['workspaceId'] = {
|
||||||
|
"description": "ID of project",
|
||||||
|
"required": true,
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
|
||||||
|
#swagger.responses[200] = {
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"memberships": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
$ref: "#/components/schemas/Membership"
|
||||||
|
},
|
||||||
|
"description": "Memberships of project"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
let memberships;
|
||||||
|
try {
|
||||||
|
const { workspaceId } = req.params;
|
||||||
|
|
||||||
|
memberships = await Membership.find({
|
||||||
|
workspace: workspaceId
|
||||||
|
}).populate('user', '+publicKey');
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser({ email: req.user.email });
|
||||||
|
Sentry.captureException(err);
|
||||||
|
return res.status(400).send({
|
||||||
|
message: 'Failed to get workspace memberships'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
memberships
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete workspace membership with id [membershipId]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const deleteWorkspaceMembership = async (req: Request, res: Response) => {
|
||||||
|
/*
|
||||||
|
#swagger.summary = 'Delete project membership'
|
||||||
|
#swagger.description = 'Delete project membership'
|
||||||
|
|
||||||
|
#swagger.security = [{
|
||||||
|
"apiKeyAuth": []
|
||||||
|
}]
|
||||||
|
|
||||||
|
#swagger.parameters['workspaceId'] = {
|
||||||
|
"description": "ID of project",
|
||||||
|
"required": true,
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
|
||||||
|
#swagger.parameters['membershipId'] = {
|
||||||
|
"description": "ID of membership",
|
||||||
|
"required": true,
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
|
||||||
|
#swagger.responses[200] = {
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"membership": {
|
||||||
|
$ref: "#/components/schemas/Membership",
|
||||||
|
"description": "Deleted membership"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
let membership;
|
||||||
|
try {
|
||||||
|
const {
|
||||||
|
membershipId
|
||||||
|
} = req.params;
|
||||||
|
|
||||||
|
membership = await Membership.findByIdAndDelete(membershipId);
|
||||||
|
|
||||||
|
if (!membership) throw new Error('Failed to delete workspace membership');
|
||||||
|
|
||||||
|
await Key.deleteMany({
|
||||||
|
receiver: membership.user,
|
||||||
|
workspace: membership.workspace
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser({ email: req.user.email });
|
||||||
|
Sentry.captureException(err);
|
||||||
|
return res.status(400).send({
|
||||||
|
message: 'Failed to delete workspace membership'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
membership
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update role of membership with id [membershipId] to role [role]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const updateWorkspaceMembership = async (req: Request, res: Response) => {
|
||||||
|
/*
|
||||||
|
#swagger.summary = 'Update project membership'
|
||||||
|
#swagger.description = 'Update project membership'
|
||||||
|
|
||||||
|
#swagger.security = [{
|
||||||
|
"apiKeyAuth": []
|
||||||
|
}]
|
||||||
|
|
||||||
|
#swagger.parameters['workspaceId'] = {
|
||||||
|
"description": "ID of project",
|
||||||
|
"required": true,
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
|
||||||
|
#swagger.parameters['membershipId'] = {
|
||||||
|
"description": "ID of membership",
|
||||||
|
"required": true,
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
|
||||||
|
#swagger.requestBody = {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"role": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Role of membership - either admin or member",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#swagger.responses[200] = {
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"membership": {
|
||||||
|
$ref: "#/components/schemas/Membership",
|
||||||
|
"description": "Updated membership"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
let membership;
|
||||||
|
try {
|
||||||
|
const {
|
||||||
|
membershipId
|
||||||
|
} = req.params;
|
||||||
|
const { role } = req.body;
|
||||||
|
|
||||||
|
membership = await Membership.findByIdAndUpdate(
|
||||||
|
membershipId,
|
||||||
|
{
|
||||||
|
role
|
||||||
|
}, {
|
||||||
|
new: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser({ email: req.user.email });
|
||||||
|
Sentry.captureException(err);
|
||||||
|
return res.status(400).send({
|
||||||
|
message: 'Failed to update workspace membership'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
membership
|
||||||
|
});
|
||||||
}
|
}
|
||||||
@@ -10,6 +10,51 @@ import { EESecretService } from '../../services';
|
|||||||
* @param res
|
* @param res
|
||||||
*/
|
*/
|
||||||
export const getSecretVersions = async (req: Request, res: Response) => {
|
export const getSecretVersions = async (req: Request, res: Response) => {
|
||||||
|
/*
|
||||||
|
#swagger.summary = 'Return secret versions'
|
||||||
|
#swagger.description = 'Return secret versions'
|
||||||
|
|
||||||
|
#swagger.security = [{
|
||||||
|
"apiKeyAuth": []
|
||||||
|
}]
|
||||||
|
|
||||||
|
#swagger.parameters['secretId'] = {
|
||||||
|
"description": "ID of secret",
|
||||||
|
"required": true,
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
|
||||||
|
#swagger.parameters['offset'] = {
|
||||||
|
"description": "Number of versions to skip",
|
||||||
|
"required": false,
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
|
||||||
|
#swagger.parameters['limit'] = {
|
||||||
|
"description": "Maximum number of versions to return",
|
||||||
|
"required": false,
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
|
||||||
|
#swagger.responses[200] = {
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"secretVersions": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
$ref: "#/components/schemas/SecretVersion"
|
||||||
|
},
|
||||||
|
"description": "Secret versions"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
let secretVersions;
|
let secretVersions;
|
||||||
try {
|
try {
|
||||||
const { secretId } = req.params;
|
const { secretId } = req.params;
|
||||||
@@ -44,6 +89,54 @@ import { EESecretService } from '../../services';
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const rollbackSecretVersion = async (req: Request, res: Response) => {
|
export const rollbackSecretVersion = async (req: Request, res: Response) => {
|
||||||
|
/*
|
||||||
|
#swagger.summary = 'Roll back secret to a version.'
|
||||||
|
#swagger.description = 'Roll back secret to a version.'
|
||||||
|
|
||||||
|
#swagger.security = [{
|
||||||
|
"apiKeyAuth": []
|
||||||
|
}]
|
||||||
|
|
||||||
|
#swagger.parameters['secretId'] = {
|
||||||
|
"description": "ID of secret",
|
||||||
|
"required": true,
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
|
||||||
|
#swagger.requestBody = {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"version": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Version of secret to roll back to"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#swagger.responses[200] = {
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"secret": {
|
||||||
|
"type": "object",
|
||||||
|
$ref: "#/components/schemas/Secret",
|
||||||
|
"description": "Secret rolled back to"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
let secret;
|
let secret;
|
||||||
try {
|
try {
|
||||||
const { secretId } = req.params;
|
const { secretId } = req.params;
|
||||||
|
|||||||
@@ -19,6 +19,51 @@ import { getLatestSecretVersionIds } from '../../helpers/secretVersion';
|
|||||||
* @param res
|
* @param res
|
||||||
*/
|
*/
|
||||||
export const getWorkspaceSecretSnapshots = async (req: Request, res: Response) => {
|
export const getWorkspaceSecretSnapshots = async (req: Request, res: Response) => {
|
||||||
|
/*
|
||||||
|
#swagger.summary = 'Return project secret snapshot ids'
|
||||||
|
#swagger.description = 'Return project secret snapshots ids'
|
||||||
|
|
||||||
|
#swagger.security = [{
|
||||||
|
"apiKeyAuth": []
|
||||||
|
}]
|
||||||
|
|
||||||
|
#swagger.parameters['workspaceId'] = {
|
||||||
|
"description": "ID of project",
|
||||||
|
"required": true,
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
|
||||||
|
#swagger.parameters['offset'] = {
|
||||||
|
"description": "Number of secret snapshots to skip",
|
||||||
|
"required": false,
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
|
||||||
|
#swagger.parameters['limit'] = {
|
||||||
|
"description": "Maximum number of secret snapshots to return",
|
||||||
|
"required": false,
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
|
||||||
|
#swagger.responses[200] = {
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"secretSnapshots": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
$ref: "#/components/schemas/SecretSnapshot"
|
||||||
|
},
|
||||||
|
"description": "Project secret snapshots"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
let secretSnapshots;
|
let secretSnapshots;
|
||||||
try {
|
try {
|
||||||
const { workspaceId } = req.params;
|
const { workspaceId } = req.params;
|
||||||
@@ -78,16 +123,66 @@ export const getWorkspaceSecretSnapshotsCount = async (req: Request, res: Respon
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const rollbackWorkspaceSecretSnapshot = async (req: Request, res: Response) => {
|
export const rollbackWorkspaceSecretSnapshot = async (req: Request, res: Response) => {
|
||||||
|
/*
|
||||||
|
#swagger.summary = 'Roll back project secrets to those captured in a secret snapshot version.'
|
||||||
|
#swagger.description = 'Roll back project secrets to those captured in a secret snapshot version.'
|
||||||
|
|
||||||
|
#swagger.security = [{
|
||||||
|
"apiKeyAuth": []
|
||||||
|
}]
|
||||||
|
|
||||||
|
#swagger.parameters['workspaceId'] = {
|
||||||
|
"description": "ID of project",
|
||||||
|
"required": true,
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
|
||||||
|
#swagger.requestBody = {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"version": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Version of secret snapshot to roll back to",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#swagger.responses[200] = {
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"secrets": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
$ref: "#/components/schemas/Secret"
|
||||||
|
},
|
||||||
|
"description": "Secrets rolled back to"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
let secrets;
|
let secrets;
|
||||||
try {
|
try {
|
||||||
const { workspaceId } = req.params;
|
const { workspaceId } = req.params;
|
||||||
const { version } = req.body;
|
const { version } = req.body;
|
||||||
|
|
||||||
// validate secret snapshot
|
// validate secret snapshot
|
||||||
const secretSnapshot = await SecretSnapshot.findOne({
|
const secretSnapshot = await SecretSnapshot.findOne({
|
||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
version
|
version
|
||||||
}).populate<{ secretVersions: ISecretVersion[]}>('secretVersions');
|
}).populate<{ secretVersions: ISecretVersion[]}>('secretVersions');
|
||||||
|
|
||||||
if (!secretSnapshot) throw new Error('Failed to find secret snapshot');
|
if (!secretSnapshot) throw new Error('Failed to find secret snapshot');
|
||||||
|
|
||||||
@@ -231,6 +326,72 @@ export const rollbackWorkspaceSecretSnapshot = async (req: Request, res: Respons
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const getWorkspaceLogs = async (req: Request, res: Response) => {
|
export const getWorkspaceLogs = async (req: Request, res: Response) => {
|
||||||
|
/*
|
||||||
|
#swagger.summary = 'Return project (audit) logs'
|
||||||
|
#swagger.description = 'Return project (audit) logs'
|
||||||
|
|
||||||
|
#swagger.security = [{
|
||||||
|
"apiKeyAuth": []
|
||||||
|
}]
|
||||||
|
|
||||||
|
#swagger.parameters['workspaceId'] = {
|
||||||
|
"description": "ID of project",
|
||||||
|
"required": true,
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
|
||||||
|
#swagger.parameters['userId'] = {
|
||||||
|
"description": "ID of project member",
|
||||||
|
"required": false,
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
|
||||||
|
#swagger.parameters['offset'] = {
|
||||||
|
"description": "Number of logs to skip",
|
||||||
|
"required": false,
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
|
||||||
|
#swagger.parameters['limit'] = {
|
||||||
|
"description": "Maximum number of logs to return",
|
||||||
|
"required": false,
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
|
||||||
|
#swagger.parameters['sortBy'] = {
|
||||||
|
"description": "Order to sort the logs by",
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"@enum": ["oldest", "recent"]
|
||||||
|
},
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
|
||||||
|
#swagger.parameters['actionNames'] = {
|
||||||
|
"description": "Names of log actions (comma-separated)",
|
||||||
|
"required": false,
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
|
||||||
|
#swagger.responses[200] = {
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"logs": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
$ref: "#/components/schemas/Log"
|
||||||
|
},
|
||||||
|
"description": "Project logs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
let logs
|
let logs
|
||||||
try {
|
try {
|
||||||
const { workspaceId } = req.params;
|
const { workspaceId } = req.params;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import requireAuth from './requireAuth';
|
|||||||
import requireBotAuth from './requireBotAuth';
|
import requireBotAuth from './requireBotAuth';
|
||||||
import requireSignupAuth from './requireSignupAuth';
|
import requireSignupAuth from './requireSignupAuth';
|
||||||
import requireWorkspaceAuth from './requireWorkspaceAuth';
|
import requireWorkspaceAuth from './requireWorkspaceAuth';
|
||||||
|
import requireMembershipAuth from './requireMembershipAuth';
|
||||||
import requireOrganizationAuth from './requireOrganizationAuth';
|
import requireOrganizationAuth from './requireOrganizationAuth';
|
||||||
import requireIntegrationAuth from './requireIntegrationAuth';
|
import requireIntegrationAuth from './requireIntegrationAuth';
|
||||||
import requireIntegrationAuthorizationAuth from './requireIntegrationAuthorizationAuth';
|
import requireIntegrationAuthorizationAuth from './requireIntegrationAuthorizationAuth';
|
||||||
@@ -16,6 +17,7 @@ export {
|
|||||||
requireBotAuth,
|
requireBotAuth,
|
||||||
requireSignupAuth,
|
requireSignupAuth,
|
||||||
requireWorkspaceAuth,
|
requireWorkspaceAuth,
|
||||||
|
requireMembershipAuth,
|
||||||
requireOrganizationAuth,
|
requireOrganizationAuth,
|
||||||
requireIntegrationAuth,
|
requireIntegrationAuth,
|
||||||
requireIntegrationAuthorizationAuth,
|
requireIntegrationAuthorizationAuth,
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import { Request, Response, NextFunction } from 'express';
|
||||||
|
import { UnauthorizedRequestError } from '../utils/errors';
|
||||||
|
import {
|
||||||
|
Membership,
|
||||||
|
} from '../models';
|
||||||
|
import { validateMembership } from '../helpers/membership';
|
||||||
|
|
||||||
|
type req = 'params' | 'body' | 'query';
|
||||||
|
/**
|
||||||
|
* Validate membership with id [membershipId] and that user with id
|
||||||
|
* [req.user._id] can modify that membership.
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {String[]} obj.acceptedRoles - accepted workspace roles for JWT auth
|
||||||
|
* @param {String[]} obj.location - location of [workspaceId] on request (e.g. params, body) for parsing
|
||||||
|
*/
|
||||||
|
const requireMembershipAuth = ({
|
||||||
|
acceptedRoles,
|
||||||
|
location = 'params'
|
||||||
|
}: {
|
||||||
|
acceptedRoles: string[];
|
||||||
|
location?: req;
|
||||||
|
}) => {
|
||||||
|
return async (
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
const { membershipId } = req[location];
|
||||||
|
|
||||||
|
const membership = await Membership.findById(membershipId);
|
||||||
|
|
||||||
|
if (!membership) throw new Error('Failed to find target membership');
|
||||||
|
|
||||||
|
const userMembership = await Membership.findOne({
|
||||||
|
workspace: membership.workspace
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!userMembership) throw new Error('Failed to validate own membership')
|
||||||
|
|
||||||
|
const targetMembership = await validateMembership({
|
||||||
|
userId: req.user._id.toString(),
|
||||||
|
workspaceId: membership.workspace.toString(),
|
||||||
|
acceptedRoles
|
||||||
|
});
|
||||||
|
|
||||||
|
req.targetMembership = targetMembership;
|
||||||
|
|
||||||
|
return next();
|
||||||
|
} catch (err) {
|
||||||
|
return next(UnauthorizedRequestError({
|
||||||
|
message: 'Unable to validate workspace membership'
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default requireMembershipAuth;
|
||||||
@@ -4,7 +4,7 @@ import { body, param } from 'express-validator';
|
|||||||
import { requireAuth, validateRequest } from '../../middleware';
|
import { requireAuth, validateRequest } from '../../middleware';
|
||||||
import { membershipController } from '../../controllers/v1';
|
import { membershipController } from '../../controllers/v1';
|
||||||
|
|
||||||
router.get( // used for CLI (deprecate)
|
router.get( // used for old CLI (deprecate)
|
||||||
'/:workspaceId/connect',
|
'/:workspaceId/connect',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: ['jwt']
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import users from './users';
|
||||||
import secret from './secret'; // stop-supporting
|
import secret from './secret'; // stop-supporting
|
||||||
import secrets from './secrets';
|
import secrets from './secrets';
|
||||||
import workspace from './workspace';
|
import workspace from './workspace';
|
||||||
@@ -6,6 +7,7 @@ import apiKeyData from './apiKeyData';
|
|||||||
import environment from "./environment"
|
import environment from "./environment"
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
users,
|
||||||
secret,
|
secret,
|
||||||
secrets,
|
secrets,
|
||||||
workspace,
|
workspace,
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import express from 'express';
|
||||||
|
const router = express.Router();
|
||||||
|
import {
|
||||||
|
requireAuth
|
||||||
|
} from '../../middleware';
|
||||||
|
import { usersController } from '../../controllers/v2';
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/me',
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
|
usersController.getMe
|
||||||
|
);
|
||||||
|
|
||||||
|
export default router;
|
||||||
@@ -3,6 +3,7 @@ const router = express.Router();
|
|||||||
import { body, param, query } from 'express-validator';
|
import { body, param, query } from 'express-validator';
|
||||||
import {
|
import {
|
||||||
requireAuth,
|
requireAuth,
|
||||||
|
requireMembershipAuth,
|
||||||
requireWorkspaceAuth,
|
requireWorkspaceAuth,
|
||||||
validateRequest
|
validateRequest
|
||||||
} from '../../middleware';
|
} from '../../middleware';
|
||||||
@@ -67,4 +68,54 @@ router.get(
|
|||||||
workspaceController.getWorkspaceServiceTokenData
|
workspaceController.getWorkspaceServiceTokenData
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// TODO: /POST to create membership and re-route inviting user to workspace there
|
||||||
|
|
||||||
|
router.get( // new - TODO: rewire dashboard to this route
|
||||||
|
'/:workspaceId/memberships',
|
||||||
|
param('workspaceId').exists().trim(),
|
||||||
|
validateRequest,
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
|
requireWorkspaceAuth({
|
||||||
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
}),
|
||||||
|
workspaceController.getWorkspaceMemberships
|
||||||
|
);
|
||||||
|
|
||||||
|
router.delete( // TODO - rewire dashboard to this route
|
||||||
|
'/:workspaceId/memberships/:membershipId',
|
||||||
|
param('workspaceId').exists().trim(),
|
||||||
|
param('membershipId').exists().trim(),
|
||||||
|
validateRequest,
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
|
requireWorkspaceAuth({
|
||||||
|
acceptedRoles: [ADMIN],
|
||||||
|
}),
|
||||||
|
requireMembershipAuth({
|
||||||
|
acceptedRoles: [ADMIN]
|
||||||
|
}),
|
||||||
|
workspaceController.deleteWorkspaceMembership
|
||||||
|
);
|
||||||
|
|
||||||
|
router.patch( // TODO - rewire dashboard to this route
|
||||||
|
'/:workspaceId/memberships/:membershipId',
|
||||||
|
param('workspaceId').exists().trim(),
|
||||||
|
param('membershipId').exists().trim(),
|
||||||
|
body('role').exists().isString().trim().isIn([ADMIN, MEMBER]),
|
||||||
|
validateRequest,
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
|
requireWorkspaceAuth({
|
||||||
|
acceptedRoles: [ADMIN],
|
||||||
|
}),
|
||||||
|
requireMembershipAuth({
|
||||||
|
acceptedRoles: [ADMIN]
|
||||||
|
}),
|
||||||
|
workspaceController.updateWorkspaceMembership
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
Vendored
+1
@@ -8,6 +8,7 @@ declare global {
|
|||||||
user: any;
|
user: any;
|
||||||
workspace: any;
|
workspace: any;
|
||||||
membership: any;
|
membership: any;
|
||||||
|
targetMembership: any;
|
||||||
organization: any;
|
organization: any;
|
||||||
membershipOrg: any;
|
membershipOrg: any;
|
||||||
integration: any;
|
integration: any;
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
||||||
const swaggerAutogen = require('swagger-autogen')({ openapi: '3.0.0' });
|
|
||||||
|
|
||||||
const doc = {
|
|
||||||
info: {
|
|
||||||
title: 'Infisical API',
|
|
||||||
description: 'List of all available APIs that can be consumed',
|
|
||||||
},
|
|
||||||
host: ['https://infisical.com'],
|
|
||||||
securityDefinitions: {
|
|
||||||
bearerAuth: {
|
|
||||||
type: 'http',
|
|
||||||
scheme: 'bearer',
|
|
||||||
bearerFormat: 'JWT'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const outputFile = './api-documentation.json';
|
|
||||||
const endpointsFiles = ['./src/app.ts'];
|
|
||||||
|
|
||||||
swaggerAutogen(outputFile, endpointsFiles, doc);
|
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||||
|
const swaggerAutogen = require('swagger-autogen')({ openapi: '3.0.0' });
|
||||||
|
const fs = require('fs').promises;
|
||||||
|
const yaml = require('js-yaml');
|
||||||
|
const { secretSchema } = require('./schemas/index.ts');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates OpenAPI specs for all Infisical API endpoints:
|
||||||
|
* - spec.json in /backend for api-serving
|
||||||
|
* - spec.yaml in /docs for API reference
|
||||||
|
*/
|
||||||
|
const generateOpenAPISpec = async () => {
|
||||||
|
const doc = {
|
||||||
|
info: {
|
||||||
|
title: 'Infisical API',
|
||||||
|
description: 'List of all available APIs that can be consumed',
|
||||||
|
},
|
||||||
|
host: ['https://infisical.com'],
|
||||||
|
servers: [
|
||||||
|
{
|
||||||
|
url: 'https://infisical.com',
|
||||||
|
description: 'Production server'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: 'http://localhost:8080',
|
||||||
|
description: 'Local server'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
securityDefinitions: {
|
||||||
|
bearerAuth: {
|
||||||
|
type: 'http',
|
||||||
|
scheme: 'bearer',
|
||||||
|
bearerFormat: 'JWT',
|
||||||
|
description: "This security definition uses the HTTP 'bearer' scheme, which allows the client to authenticate using a JSON Web Token (JWT) that is passed in the Authorization header of the request."
|
||||||
|
},
|
||||||
|
apiKeyAuth: {
|
||||||
|
type: 'apiKey',
|
||||||
|
in: 'header',
|
||||||
|
name: 'X-API-Key',
|
||||||
|
description: 'This security definition uses an API key, which is passed in the header of the request as the value of the "X-API-Key" header. The client must provide a valid key in order to access the API.'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
definitions: {
|
||||||
|
CurrentUser: {
|
||||||
|
_id: '',
|
||||||
|
email: '',
|
||||||
|
firstName: '',
|
||||||
|
lastName: '',
|
||||||
|
publicKey: '',
|
||||||
|
encryptedPrivateKey: '',
|
||||||
|
iv: '',
|
||||||
|
tag: '',
|
||||||
|
updatedAt: '',
|
||||||
|
createdAt: ''
|
||||||
|
},
|
||||||
|
Membership: {
|
||||||
|
user: {
|
||||||
|
_id: '',
|
||||||
|
email: '',
|
||||||
|
firstName: '',
|
||||||
|
lastName: '',
|
||||||
|
publicKey: '',
|
||||||
|
updatedAt: '',
|
||||||
|
createdAt: ''
|
||||||
|
},
|
||||||
|
workspace: '',
|
||||||
|
role: 'admin'
|
||||||
|
},
|
||||||
|
ProjectKey: {
|
||||||
|
encryptedkey: '',
|
||||||
|
nonce: '',
|
||||||
|
sender: {
|
||||||
|
publicKey: ''
|
||||||
|
},
|
||||||
|
receiver: '',
|
||||||
|
workspace: ''
|
||||||
|
},
|
||||||
|
CreateSecret: {
|
||||||
|
type: 'shared',
|
||||||
|
secretKeyCiphertext: '',
|
||||||
|
secretKeyIV: '',
|
||||||
|
secretKeyTag: '',
|
||||||
|
secretValueCiphertext: '',
|
||||||
|
secretValueIV: '',
|
||||||
|
secretValueTag: '',
|
||||||
|
secretCommentCiphertext: '',
|
||||||
|
secretCommentIV: '',
|
||||||
|
secretCommentTag: ''
|
||||||
|
},
|
||||||
|
UpdateSecret: {
|
||||||
|
id: '',
|
||||||
|
secretKeyCiphertext: '',
|
||||||
|
secretKeyIV: '',
|
||||||
|
secretKeyTag: '',
|
||||||
|
secretValueCiphertext: '',
|
||||||
|
secretValueIV: '',
|
||||||
|
secretValueTag: '',
|
||||||
|
secretCommentCiphertext: '',
|
||||||
|
secretCommentIV: '',
|
||||||
|
secretCommentTag: ''
|
||||||
|
},
|
||||||
|
Secret: {
|
||||||
|
_id: '',
|
||||||
|
version: 1,
|
||||||
|
workspace : '',
|
||||||
|
type: 'shared',
|
||||||
|
user: null,
|
||||||
|
secretKeyCiphertext: '',
|
||||||
|
secretKeyIV: '',
|
||||||
|
secretKeyTag: '',
|
||||||
|
secretValueCiphertext: '',
|
||||||
|
secretValueIV: '',
|
||||||
|
secretValueTag: '',
|
||||||
|
secretCommentCiphertext: '',
|
||||||
|
secretCommentIV: '',
|
||||||
|
secretCommentTag: '',
|
||||||
|
updatedAt: '',
|
||||||
|
createdAt: ''
|
||||||
|
},
|
||||||
|
Log: {
|
||||||
|
_id: '',
|
||||||
|
user: {
|
||||||
|
_id: '',
|
||||||
|
email: '',
|
||||||
|
firstName: '',
|
||||||
|
lastName: ''
|
||||||
|
},
|
||||||
|
workspace: '',
|
||||||
|
actionNames: [
|
||||||
|
'addSecrets'
|
||||||
|
],
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
name: 'addSecrets',
|
||||||
|
user: '',
|
||||||
|
workspace: '',
|
||||||
|
payload: [
|
||||||
|
{
|
||||||
|
oldSecretVersion: '',
|
||||||
|
newSecretVersion: ''
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
channel: 'cli',
|
||||||
|
ipAddress: '192.168.0.1',
|
||||||
|
updatedAt: '',
|
||||||
|
createdAt: ''
|
||||||
|
},
|
||||||
|
SecretSnapshot: {
|
||||||
|
workspace: '',
|
||||||
|
version: 1,
|
||||||
|
secretVersions: [
|
||||||
|
{
|
||||||
|
_id: ''
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
SecretVersion: {
|
||||||
|
_id: '',
|
||||||
|
secret: '',
|
||||||
|
version: 1,
|
||||||
|
workspace: '',
|
||||||
|
type: '',
|
||||||
|
user: '',
|
||||||
|
environment: '',
|
||||||
|
isDeleted: '',
|
||||||
|
secretKeyCiphertext: '',
|
||||||
|
secretKeyIV: '',
|
||||||
|
secretKeyTag: '',
|
||||||
|
secretValueCiphertext: '',
|
||||||
|
secretValueIV: '',
|
||||||
|
secretValueTag: '',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const outputJSONFile = '../spec.json';
|
||||||
|
const outputYAMLFile = '../docs/spec.yaml';
|
||||||
|
const endpointsFiles = ['../src/app.ts'];
|
||||||
|
|
||||||
|
const spec = await swaggerAutogen(outputJSONFile, endpointsFiles, doc);
|
||||||
|
await fs.writeFile(outputYAMLFile, yaml.dump(spec.data));
|
||||||
|
}
|
||||||
|
|
||||||
|
generateOpenAPISpec();
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||||
|
const secretSchema = require('./secretSchema.ts');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
secretSchema
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
const secretSchema = {
|
||||||
|
_id: {
|
||||||
|
type: 'string',
|
||||||
|
format: 'objectId'
|
||||||
|
},
|
||||||
|
version: {
|
||||||
|
type: 'number'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = secretSchema;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
---
|
---
|
||||||
title: "Read"
|
title: "Retrieve"
|
||||||
openapi: "GET /api/v2/secrets/"
|
openapi: "GET /api/v2/secrets/"
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Roll Back to Version"
|
||||||
|
openapi: "POST /api/v1/secret/{secretId}/secret-versions/rollback"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get Versions"
|
||||||
|
openapi: "GET /api/v1/secret/{secretId}/secret-versions"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get Current User"
|
||||||
|
openapi: "GET /api/v2/users/me"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Delete Membership"
|
||||||
|
openapi: "DELETE /api/v2/workspace/{workspaceId}/memberships/{membershipId}"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get Logs"
|
||||||
|
openapi: "GET /api/v1/workspace/{workspaceId}/logs"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get Memberships"
|
||||||
|
openapi: "GET /api/v2/workspace/{workspaceId}/memberships"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Roll Back to Snapshot"
|
||||||
|
openapi: "POST /api/v1/workspace/{workspaceId}/secret-snapshots/rollback"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get Snapshots"
|
||||||
|
openapi: "GET /api/v1/workspace/{workspaceId}/secret-snapshots"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Update Membership"
|
||||||
|
openapi: "PATCH /api/v2/workspace/{workspaceId}/memberships/{membershipId}"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get Key"
|
||||||
|
openapi: "GET /api/v2/workspace/{workspaceId}/encrypted-key"
|
||||||
|
---
|
||||||
@@ -1,3 +1,11 @@
|
|||||||
---
|
---
|
||||||
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 from your user settings.
|
||||||
|
|
||||||
|
<Info>
|
||||||
|
It's important to keep your API key secure, as it grants access to your
|
||||||
|
secrets in Infisical. For added security, consider rotating your API key on a
|
||||||
|
regular basis.
|
||||||
|
</Info>
|
||||||
|
|||||||
@@ -0,0 +1,152 @@
|
|||||||
|
---
|
||||||
|
title: "Create secrets"
|
||||||
|
---
|
||||||
|
|
||||||
|
In this example, we demonstrate how to add secrets to a project and environment.
|
||||||
|
|
||||||
|
Prerequisites:
|
||||||
|
|
||||||
|
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
||||||
|
- Grasp a basic understanding of the system and its underlying cryptography [here](/api-reference/overview/introduction).
|
||||||
|
|
||||||
|
## Flow
|
||||||
|
|
||||||
|
1. Get your (encrypted) private key.
|
||||||
|
2. Decrypt your (encrypted) private key with your password.
|
||||||
|
3. Get the (encrypted) project key for the project.
|
||||||
|
4. Decrypt the (encrypted) project key with your private key.
|
||||||
|
5. Encrypt your secret(s) with the project key.
|
||||||
|
6. Send (encrypted) secret(s) to the Infical API
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```js
|
||||||
|
const crypto = require('crypto');
|
||||||
|
const axios = require('axios');
|
||||||
|
|
||||||
|
const ALGORITHM = 'aes-256-gcm';
|
||||||
|
const BLOCK_SIZE_BYTES = 16;
|
||||||
|
|
||||||
|
const encrypt = (
|
||||||
|
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(
|
||||||
|
ALGORITHM,
|
||||||
|
secret,
|
||||||
|
Buffer.from(iv, 'base64')
|
||||||
|
);
|
||||||
|
decipher.setAuthTag(Buffer.from(tag, 'base64'));
|
||||||
|
|
||||||
|
let cleartext = decipher.update(ciphertext, 'base64', 'utf8');
|
||||||
|
cleartext += decipher.final('utf8');
|
||||||
|
|
||||||
|
return cleartext;
|
||||||
|
}
|
||||||
|
|
||||||
|
const createSecrets = async () => {
|
||||||
|
const API_KEY = 'your_api_key';
|
||||||
|
const PSWD = 'your_pswd';
|
||||||
|
const WORKSPACE_ID = 'your_workspace_id';
|
||||||
|
|
||||||
|
const SECRET_KEY = 'SOME_KEY';
|
||||||
|
const SECRET_VALUE = 'SOME_VALUE';
|
||||||
|
|
||||||
|
// 1. get (encrypted) private key
|
||||||
|
const user = await axios.get(
|
||||||
|
'https://api.infisical.com/api/v2/users/me', {
|
||||||
|
headers: {
|
||||||
|
'X-API-KEY': API_KEY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 2. decrypt your (encrypted) private key with your password
|
||||||
|
const privateKey = decrypt({
|
||||||
|
ciphertext: user.encryptedPrivateKey,
|
||||||
|
iv: user.iv,
|
||||||
|
tag: user.tag,
|
||||||
|
secret: PSWD.slice(0, 32).padStart(32, '0');
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3. get the (encrypted) project key for the project
|
||||||
|
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
|
||||||
|
const projectKey = nacl.box.open(
|
||||||
|
util.decodeBase64(encryptedProjectKey),
|
||||||
|
util.decodeBase64(projectKey.nonce),
|
||||||
|
util.decodeBase64(projectKey.sender.publicKey),
|
||||||
|
util.decodeBase64(privateKey)
|
||||||
|
);
|
||||||
|
|
||||||
|
// 5. encrypt your secret(s) with the project key
|
||||||
|
const {
|
||||||
|
ciphertext: secretKeyCiphertext,
|
||||||
|
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();
|
||||||
|
```
|
||||||
|
|
||||||
|
<Info>
|
||||||
|
This example uses [TweetNaCl.js](https://tweetnacl.js.org/#/), a port of
|
||||||
|
TweetNacl/Nacl, to perform asymmeric decryption of the project key but there
|
||||||
|
are ports of NaCl available in every major language.
|
||||||
|
</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>
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
title: "Delete secrets"
|
||||||
|
---
|
||||||
|
|
||||||
|
In this example, we demonstrate how to delete secrets
|
||||||
|
|
||||||
|
Prerequisites:
|
||||||
|
|
||||||
|
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
||||||
|
- Grasp a basic understanding of the system and its underlying cryptography [here](/api-reference/overview/introduction).
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```js
|
||||||
|
const deleteSecrets = async () => {
|
||||||
|
const API_KEY = "your_api_key";
|
||||||
|
const SECRET_ID = "ID"; // ID of secret to delete
|
||||||
|
|
||||||
|
// 6. Send ID(s) of secret(s) to delete to the Infisical API
|
||||||
|
await axios.delete(
|
||||||
|
`https://api.infisical.com/api/v2/secrets`,
|
||||||
|
{
|
||||||
|
secretIds: SECRET_ID,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"X-API-KEY": API_KEY,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
deleteSecrets();
|
||||||
|
```
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
---
|
||||||
|
title: "Retrieve secrets"
|
||||||
|
---
|
||||||
|
|
||||||
|
In this example, we demonstrate how to retrieve secrets from a project and environment.
|
||||||
|
|
||||||
|
Prerequisites:
|
||||||
|
|
||||||
|
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
||||||
|
- Grasp a basic understanding of the system and its underlying cryptography [here](/api-reference/overview/introduction).
|
||||||
|
|
||||||
|
## Flow
|
||||||
|
|
||||||
|
1. Get your (encrypted) private key.
|
||||||
|
2. Decrypt your (encrypted) private key with your password.
|
||||||
|
3. Get the (encrypted) project key for the project.
|
||||||
|
4. Decrypt the (encrypted) project key with your private key.
|
||||||
|
5. Get secrets for a project and environment.
|
||||||
|
6. Decrypt the (encrypted) secrets
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```js
|
||||||
|
const crypto = require('crypto');
|
||||||
|
const axios = require('axios');
|
||||||
|
|
||||||
|
const ALGORITHM = 'aes-256-gcm';
|
||||||
|
const BLOCK_SIZE_BYTES = 16;
|
||||||
|
|
||||||
|
const encrypt = (
|
||||||
|
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(
|
||||||
|
ALGORITHM,
|
||||||
|
secret,
|
||||||
|
Buffer.from(iv, 'base64')
|
||||||
|
);
|
||||||
|
decipher.setAuthTag(Buffer.from(tag, 'base64'));
|
||||||
|
|
||||||
|
let cleartext = decipher.update(ciphertext, 'base64', 'utf8');
|
||||||
|
cleartext += decipher.final('utf8');
|
||||||
|
|
||||||
|
return cleartext;
|
||||||
|
}
|
||||||
|
|
||||||
|
const retrieveSecrets = async () => {
|
||||||
|
const API_KEY = 'your_api_key';
|
||||||
|
const PSWD = 'your_pswd';
|
||||||
|
const WORKSPACE_ID = 'your_workspace_id';
|
||||||
|
|
||||||
|
// 1. get (encrypted) private key
|
||||||
|
const user = await axios.get(
|
||||||
|
'https://api.infisical.com/api/v2/users/me', {
|
||||||
|
headers: {
|
||||||
|
'X-API-KEY': API_KEY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 2. decrypt your (encrypted) private key with your password
|
||||||
|
const privateKey = decrypt({
|
||||||
|
ciphertext: user.encryptedPrivateKey,
|
||||||
|
iv: user.iv,
|
||||||
|
tag: user.tag,
|
||||||
|
secret: PSWD.slice(0, 32).padStart(32, '0');
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3. get the (encrypted) project key for the project
|
||||||
|
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
|
||||||
|
const projectKey = nacl.box.open(
|
||||||
|
util.decodeBase64(encryptedProjectKey),
|
||||||
|
util.decodeBase64(projectKey.nonce),
|
||||||
|
util.decodeBase64(projectKey.sender.publicKey),
|
||||||
|
util.decodeBase64(privateKey)
|
||||||
|
);
|
||||||
|
|
||||||
|
// 5. get (encrypted) secrets for a project and environment.
|
||||||
|
const encryptedSecrets = await axios.get(
|
||||||
|
'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({
|
||||||
|
ciphertext: encryptedSecret.secretKeyCiphertext,
|
||||||
|
iv: encryptedSecret.secretKeyIV,
|
||||||
|
tag: encryptedSecret.secretKeyTag
|
||||||
|
secret: projectKey
|
||||||
|
});
|
||||||
|
const secretValue = decrypt({
|
||||||
|
ciphertext: encryptedSecret.secretValueCiphertext,
|
||||||
|
iv: encryptedSecret.secretValueIV,
|
||||||
|
tag: encryptedSecret.secretValueTag
|
||||||
|
secret: projectKey
|
||||||
|
});
|
||||||
|
|
||||||
|
return ({
|
||||||
|
secretKey,
|
||||||
|
secretValue
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
retrieveSecrets();
|
||||||
|
```
|
||||||
|
|
||||||
|
<Info>
|
||||||
|
This example uses [TweetNaCl.js](https://tweetnacl.js.org/#/), a port of
|
||||||
|
TweetNacl/Nacl, to perform asymmeric decryption of the project key but there
|
||||||
|
are ports of NaCl available in every major language.
|
||||||
|
</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>
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
---
|
||||||
|
title: "Update secrets"
|
||||||
|
---
|
||||||
|
|
||||||
|
In this example, we demonstrate how to update secrets
|
||||||
|
|
||||||
|
Prerequisites:
|
||||||
|
|
||||||
|
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
||||||
|
- Grasp a basic understanding of the system and its underlying cryptography [here](/api-reference/overview/introduction).
|
||||||
|
|
||||||
|
## Flow
|
||||||
|
|
||||||
|
1. Get your (encrypted) private key.
|
||||||
|
2. Decrypt your (encrypted) private key with your password.
|
||||||
|
3. Get the project key for the project.
|
||||||
|
4. Decrypt the project key with your private key.
|
||||||
|
5. Encrypt your secret(s) with the project key.
|
||||||
|
6. Send (encrypted) updated secret(s) to the Infical API
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```js
|
||||||
|
const crypto = require('crypto');
|
||||||
|
const axios = require('axios');
|
||||||
|
|
||||||
|
const ALGORITHM = 'aes-256-gcm';
|
||||||
|
const BLOCK_SIZE_BYTES = 16;
|
||||||
|
|
||||||
|
const encrypt = (
|
||||||
|
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(
|
||||||
|
ALGORITHM,
|
||||||
|
secret,
|
||||||
|
Buffer.from(iv, 'base64')
|
||||||
|
);
|
||||||
|
decipher.setAuthTag(Buffer.from(tag, 'base64'));
|
||||||
|
|
||||||
|
let cleartext = decipher.update(ciphertext, 'base64', 'utf8');
|
||||||
|
cleartext += decipher.final('utf8');
|
||||||
|
|
||||||
|
return cleartext;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateSecrets = async () => {
|
||||||
|
const API_KEY = 'your_api_key';
|
||||||
|
const PSWD = 'your_pswd';
|
||||||
|
const WORKSPACE_ID = 'your_workspace_id';
|
||||||
|
|
||||||
|
const SECRET_ID = 'ID' // ID of secret to update
|
||||||
|
const SECRET_KEY = 'SOME_KEY';
|
||||||
|
const SECRET_VALUE = 'SOME_VALUE';
|
||||||
|
|
||||||
|
// 1. get (encrypted) private key
|
||||||
|
const user = await axios.get(
|
||||||
|
'https://api.infisical.com/api/v2/users/me', {
|
||||||
|
headers: {
|
||||||
|
'X-API-KEY': API_KEY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 2. decrypt your (encrypted) private key with your password
|
||||||
|
const privateKey = decrypt({
|
||||||
|
ciphertext: user.encryptedPrivateKey,
|
||||||
|
iv: user.iv,
|
||||||
|
tag: user.tag,
|
||||||
|
secret: PSWD.slice(0, 32).padStart(32, '0');
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3. get the (encrypted) project key for the project
|
||||||
|
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
|
||||||
|
const projectKey = nacl.box.open(
|
||||||
|
util.decodeBase64(encryptedProjectKey),
|
||||||
|
util.decodeBase64(projectKey.nonce),
|
||||||
|
util.decodeBase64(projectKey.sender.publicKey),
|
||||||
|
util.decodeBase64(privateKey)
|
||||||
|
);
|
||||||
|
|
||||||
|
// 5. encrypt your secret(s) with the project key
|
||||||
|
const {
|
||||||
|
ciphertext: secretKeyCiphertext,
|
||||||
|
iv: secretKeyIV,
|
||||||
|
tag: secretKeyTag
|
||||||
|
} = encrypt(SECRET_KEY, projectKey);
|
||||||
|
|
||||||
|
const {
|
||||||
|
ciphertext: secretValueCiphertext,
|
||||||
|
iv: secretValueIV,
|
||||||
|
tag: secretValueTag
|
||||||
|
} = encrypt(SECRET_VALUE, projectKey);
|
||||||
|
|
||||||
|
const secret = {
|
||||||
|
id: SECRET_ID,
|
||||||
|
secretKeyCiphertext,
|
||||||
|
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();
|
||||||
|
```
|
||||||
|
|
||||||
|
<Info>
|
||||||
|
This example uses [TweetNaCl.js](https://tweetnacl.js.org/#/), a port of
|
||||||
|
TweetNacl/Nacl, to perform asymmeric decryption of the project key but there
|
||||||
|
are ports of NaCl available in every major language.
|
||||||
|
</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>
|
||||||
@@ -1,3 +1,31 @@
|
|||||||
---
|
---
|
||||||
title: "Introduction"
|
title: "Introduction"
|
||||||
---
|
---
|
||||||
|
|
||||||
|
<Warning>
|
||||||
|
Infisical's REST API is currently unavailable and scheduled to go live on Jan
|
||||||
|
16!
|
||||||
|
</Warning>
|
||||||
|
|
||||||
|
Infisical's REST API provides users an alternative way to programmatically access and manage
|
||||||
|
secrets via HTTPS requests. This can be useful for automating tasks, such as
|
||||||
|
rotating credentials, or for integrating secret management into a larger system.
|
||||||
|
|
||||||
|
With the REST API, users can create, read, update, and delete secrets, as well as manage access control, query audit logs, and more.
|
||||||
|
|
||||||
|
## Concepts
|
||||||
|
|
||||||
|
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 (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.
|
||||||
|
- Secrets are encrypted symmetrically by your copy of the project key belonging to the project containing.
|
||||||
|
- Infisical uses AES256-GCM and [TweetNaCl.js](https://tweetnacl.js.org/#/) for symmetric and asymmetric encryption/decryption operations.
|
||||||
|
|
||||||
|
<Info>
|
||||||
|
Infisical's system ensures greater security such that secrets are
|
||||||
|
encrypted/decrypted on the client-side but requires users to properly
|
||||||
|
implement cryptographic operations to maintain end-to-end encryption (E2EE).
|
||||||
|
We're
|
||||||
|
</Info>
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
title: "Usage"
|
||||||
|
---
|
||||||
|
|
||||||
|
Prerequisites:
|
||||||
|
|
||||||
|
- Set up and add envars to [Infisical Cloud](https://app.infisical.com) or your self-hosted instance.
|
||||||
|
- Obtain an API Key in your user settings to be included in requests to the Infisical API.
|
||||||
|
|
||||||
|
Using Infisical's API to manage secrets requires a basic understanding of the system and its underlying cryptography detailed [here](/security/overview).
|
||||||
|
|
||||||
|
## Concepts
|
||||||
|
|
||||||
|
- 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 (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.
|
||||||
|
- Secrets are encrypted symmetrically by your copy of the project key belonging to the project containing.
|
||||||
|
- Infisical uses AES256-GCM and [TweetNaCl.js](https://tweetnacl.js.org/#/) for symmetric and asymmetric encryption/decryption operations.
|
||||||
@@ -21,6 +21,16 @@
|
|||||||
"to": "#F8B7BD"
|
"to": "#F8B7BD"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"api": {
|
||||||
|
"baseUrl": [
|
||||||
|
"https://app.infisical.com",
|
||||||
|
"http://localhost:8080"
|
||||||
|
],
|
||||||
|
"auth": {
|
||||||
|
"method": "api-key",
|
||||||
|
"name": "X-API-KEY"
|
||||||
|
}
|
||||||
|
},
|
||||||
"topbarLinks": [
|
"topbarLinks": [
|
||||||
{ "name": "Log In", "url": "https://app.infisical.com/login" }
|
{ "name": "Log In", "url": "https://app.infisical.com/login" }
|
||||||
],
|
],
|
||||||
@@ -39,6 +49,11 @@
|
|||||||
"icon": "server",
|
"icon": "server",
|
||||||
"url": "self-hosting"
|
"url": "self-hosting"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "API Reference",
|
||||||
|
"icon": "cloud",
|
||||||
|
"url": "api-reference"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Integrations",
|
"name": "Integrations",
|
||||||
"icon": "plug",
|
"icon": "plug",
|
||||||
@@ -125,6 +140,56 @@
|
|||||||
"self-hosting/configuration/email"
|
"self-hosting/configuration/email"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"group": "Overview",
|
||||||
|
"pages": [
|
||||||
|
"api-reference/overview/introduction",
|
||||||
|
"api-reference/overview/authentication",
|
||||||
|
{
|
||||||
|
"group": "Examples",
|
||||||
|
"pages": [
|
||||||
|
"api-reference/overview/examples/create-secrets",
|
||||||
|
"api-reference/overview/examples/retrieve-secrets",
|
||||||
|
"api-reference/overview/examples/update-secrets",
|
||||||
|
"api-reference/overview/examples/delete-secrets"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": "Endpoints",
|
||||||
|
"pages": [
|
||||||
|
{
|
||||||
|
"group": "Users",
|
||||||
|
"pages": [
|
||||||
|
"api-reference/endpoints/users/me"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": "Projects",
|
||||||
|
"pages": [
|
||||||
|
"api-reference/endpoints/workspaces/memberships",
|
||||||
|
"api-reference/endpoints/workspaces/update-membership",
|
||||||
|
"api-reference/endpoints/workspaces/delete-membership",
|
||||||
|
"api-reference/endpoints/workspaces/workspace-key",
|
||||||
|
"api-reference/endpoints/workspaces/logs",
|
||||||
|
"api-reference/endpoints/workspaces/secret-snapshots",
|
||||||
|
"api-reference/endpoints/workspaces/rollback-snapshot"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": "Secrets",
|
||||||
|
"pages": [
|
||||||
|
"api-reference/endpoints/secrets/create",
|
||||||
|
"api-reference/endpoints/secrets/read",
|
||||||
|
"api-reference/endpoints/secrets/update",
|
||||||
|
"api-reference/endpoints/secrets/delete",
|
||||||
|
"api-reference/endpoints/secrets/versions",
|
||||||
|
"api-reference/endpoints/secrets/rollback-version"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"group": "Integrations",
|
"group": "Integrations",
|
||||||
"pages": ["integrations/overview"]
|
"pages": ["integrations/overview"]
|
||||||
|
|||||||
+2327
File diff suppressed because it is too large
Load Diff
@@ -61,7 +61,7 @@ const attemptLogin = async (
|
|||||||
// if everything works, go the main dashboard page.
|
// if everything works, go the main dashboard page.
|
||||||
const { token, publicKey, encryptedPrivateKey, iv, tag } =
|
const { token, publicKey, encryptedPrivateKey, iv, tag } =
|
||||||
await login2(email, clientProof);
|
await login2(email, clientProof);
|
||||||
|
|
||||||
SecurityClient.setToken(token);
|
SecurityClient.setToken(token);
|
||||||
|
|
||||||
const privateKey = Aes256Gcm.decrypt({
|
const privateKey = Aes256Gcm.decrypt({
|
||||||
|
|||||||
Reference in New Issue
Block a user