1
0
mirror of https://github.com/Infisical/infisical.git synced 2025-03-29 22:02:57 +00:00

Complete v1 API reference docs, pre-launch

This commit is contained in:
Tuan Dang
2023-01-14 19:02:12 +07:00
parent b63360813a
commit 315810bd74
28 changed files with 2216 additions and 222 deletions

@ -18,7 +18,8 @@
"paths": { "paths": {
"/api/v1/secret/{secretId}/secret-versions": { "/api/v1/secret/{secretId}/secret-versions": {
"get": { "get": {
"description": "", "summary": "Return secret versions",
"description": "Return secret versions",
"parameters": [ "parameters": [
{ {
"name": "secretId", "name": "secretId",
@ -26,10 +27,13 @@
"required": true, "required": true,
"schema": { "schema": {
"type": "string" "type": "string"
} },
"description": "ID of secret"
}, },
{ {
"name": "offset", "name": "offset",
"description": "Number of versions to skip",
"required": false,
"in": "query", "in": "query",
"schema": { "schema": {
"type": "string" "type": "string"
@ -37,6 +41,8 @@
}, },
{ {
"name": "limit", "name": "limit",
"description": "Maximum number of versions to return",
"required": false,
"in": "query", "in": "query",
"schema": { "schema": {
"type": "string" "type": "string"
@ -45,17 +51,39 @@
], ],
"responses": { "responses": {
"200": { "200": {
"description": "OK" "description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"secretVersions": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SecretVersion"
},
"description": "Secret versions"
}
}
}
}
}
}, },
"400": { "400": {
"description": "Bad Request" "description": "Bad Request"
} }
} },
"security": [
{
"apiKeyAuth": []
}
]
} }
}, },
"/api/v1/secret/{secretId}/secret-versions/rollback": { "/api/v1/secret/{secretId}/secret-versions/rollback": {
"post": { "post": {
"description": "", "summary": "Roll back secret to a version.",
"description": "Roll back secret to a version.",
"parameters": [ "parameters": [
{ {
"name": "secretId", "name": "secretId",
@ -63,25 +91,47 @@
"required": true, "required": true,
"schema": { "schema": {
"type": "string" "type": "string"
} },
"description": "ID of secret"
} }
], ],
"responses": { "responses": {
"200": { "200": {
"description": "OK" "description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"secret": {
"type": "object",
"$ref": "#/components/schemas/Secret",
"description": "Secret rolled back to"
}
}
}
}
}
}, },
"400": { "400": {
"description": "Bad Request" "description": "Bad Request"
} }
}, },
"security": [
{
"apiKeyAuth": []
}
],
"requestBody": { "requestBody": {
"required": true,
"content": { "content": {
"application/json": { "application/json": {
"schema": { "schema": {
"type": "object", "type": "object",
"properties": { "properties": {
"version": { "version": {
"example": "any" "type": "integer",
"description": "Version of secret to roll back to"
} }
} }
} }
@ -115,7 +165,8 @@
}, },
"/api/v1/workspace/{workspaceId}/secret-snapshots": { "/api/v1/workspace/{workspaceId}/secret-snapshots": {
"get": { "get": {
"description": "", "summary": "Return project secret snapshot ids",
"description": "Return project secret snapshots ids",
"parameters": [ "parameters": [
{ {
"name": "workspaceId", "name": "workspaceId",
@ -123,10 +174,13 @@
"required": true, "required": true,
"schema": { "schema": {
"type": "string" "type": "string"
} },
"description": "ID of project"
}, },
{ {
"name": "offset", "name": "offset",
"description": "Number of secret snapshots to skip",
"required": false,
"in": "query", "in": "query",
"schema": { "schema": {
"type": "string" "type": "string"
@ -134,6 +188,8 @@
}, },
{ {
"name": "limit", "name": "limit",
"description": "Maximum number of secret snapshots to return",
"required": false,
"in": "query", "in": "query",
"schema": { "schema": {
"type": "string" "type": "string"
@ -142,12 +198,33 @@
], ],
"responses": { "responses": {
"200": { "200": {
"description": "OK" "description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"secretSnapshots": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SecretSnapshot"
},
"description": "Project secret snapshots"
}
}
}
}
}
}, },
"400": { "400": {
"description": "Bad Request" "description": "Bad Request"
} }
} },
"security": [
{
"apiKeyAuth": []
}
]
} }
}, },
"/api/v1/workspace/{workspaceId}/secret-snapshots/count": { "/api/v1/workspace/{workspaceId}/secret-snapshots/count": {
@ -175,8 +252,8 @@
}, },
"/api/v1/workspace/{workspaceId}/secret-snapshots/rollback": { "/api/v1/workspace/{workspaceId}/secret-snapshots/rollback": {
"post": { "post": {
"summary": "Roll back project secrets to those captured in a secret snapshot version", "summary": "Roll back project secrets to those captured in a secret snapshot version.",
"description": "Roll back project secrets to those captured in a secret snapshot version", "description": "Roll back project secrets to those captured in a secret snapshot version.",
"parameters": [ "parameters": [
{ {
"name": "workspaceId", "name": "workspaceId",
@ -194,11 +271,16 @@
"content": { "content": {
"application/json": { "application/json": {
"schema": { "schema": {
"type": "array", "type": "object",
"items": { "properties": {
"$ref": "#/components/schemas/Secret" "secrets": {
}, "type": "array",
"description": "Array of secrets captured in the secret snapshot" "items": {
"$ref": "#/components/schemas/Secret"
},
"description": "Secrets rolled back to"
}
}
} }
} }
} }
@ -232,18 +314,31 @@
}, },
"/api/v1/workspace/{workspaceId}/logs": { "/api/v1/workspace/{workspaceId}/logs": {
"get": { "get": {
"description": "", "summary": "Return project (audit) logs",
"description": "Return project (audit) logs",
"parameters": [ "parameters": [
{ {
"name": "workspaceId", "name": "workspaceId",
"in": "path", "in": "path",
"required": true, "required": true,
"schema": {
"type": "string"
},
"description": "ID of project"
},
{
"name": "userId",
"description": "ID of project member",
"required": false,
"in": "query",
"schema": { "schema": {
"type": "string" "type": "string"
} }
}, },
{ {
"name": "offset", "name": "offset",
"description": "Number of logs to skip",
"required": false,
"in": "query", "in": "query",
"schema": { "schema": {
"type": "string" "type": "string"
@ -251,6 +346,8 @@
}, },
{ {
"name": "limit", "name": "limit",
"description": "Maximum number of logs to return",
"required": false,
"in": "query", "in": "query",
"schema": { "schema": {
"type": "string" "type": "string"
@ -258,20 +355,21 @@
}, },
{ {
"name": "sortBy", "name": "sortBy",
"in": "query", "description": "Order to sort the logs by",
"schema": { "schema": {
"type": "string" "type": "string",
} "enum": [
}, "oldest",
{ "recent"
"name": "userId", ]
"in": "query", },
"schema": { "required": false,
"type": "string" "in": "query"
}
}, },
{ {
"name": "actionNames", "name": "actionNames",
"description": "Names of log actions (comma-separated)",
"required": false,
"in": "query", "in": "query",
"schema": { "schema": {
"type": "string" "type": "string"
@ -280,12 +378,33 @@
], ],
"responses": { "responses": {
"200": { "200": {
"description": "OK" "description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"logs": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Log"
},
"description": "Project logs"
}
}
}
}
}
}, },
"400": { "400": {
"description": "Bad Request" "description": "Bad Request"
} }
} },
"security": [
{
"apiKeyAuth": []
}
]
} }
}, },
"/api/v1/action/{actionId}": { "/api/v1/action/{actionId}": {
@ -2117,8 +2236,13 @@
"application/json": { "application/json": {
"schema": { "schema": {
"type": "object", "type": "object",
"$ref": "#/components/schemas/CurrentUser", "properties": {
"description": "Current user on request" "user": {
"type": "object",
"$ref": "#/components/schemas/CurrentUser",
"description": "Current user on request"
}
}
} }
} }
} }
@ -2279,6 +2403,170 @@
} }
} }
}, },
"/api/v2/workspace/{workspaceId}/memberships": {
"get": {
"summary": "Return project memberships",
"description": "Return project memberships",
"parameters": [
{
"name": "workspaceId",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "ID of project"
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"memberships": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Membership"
},
"description": "Memberships of project"
}
}
}
}
}
},
"400": {
"description": "Bad Request"
}
},
"security": [
{
"apiKeyAuth": []
}
]
}
},
"/api/v2/workspace/{workspaceId}/memberships/{membershipId}": {
"delete": {
"summary": "Delete project membership",
"description": "Delete project membership",
"parameters": [
{
"name": "workspaceId",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "ID of project"
},
{
"name": "membershipId",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "ID of membership"
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"membership": {
"$ref": "#/components/schemas/Membership",
"description": "Deleted membership"
}
}
}
}
}
},
"400": {
"description": "Bad Request"
}
},
"security": [
{
"apiKeyAuth": []
}
]
},
"patch": {
"summary": "Update project membership",
"description": "Update project membership",
"parameters": [
{
"name": "workspaceId",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "ID of project"
},
{
"name": "membershipId",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "ID of membership"
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"membership": {
"$ref": "#/components/schemas/Membership",
"description": "Updated membership"
}
}
}
}
}
},
"400": {
"description": "Bad Request"
}
},
"security": [
{
"apiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"role": {
"type": "string",
"description": "Role of membership - either admin or member"
}
}
}
}
}
}
}
},
"/api/v2/secret/batch-create/workspace/{workspaceId}/environment/{environment}": { "/api/v2/secret/batch-create/workspace/{workspaceId}/environment/{environment}": {
"post": { "post": {
"description": "", "description": "",
@ -2565,11 +2853,16 @@
"content": { "content": {
"application/json": { "application/json": {
"schema": { "schema": {
"type": "array", "type": "object",
"items": { "properties": {
"$ref": "#/components/schemas/Secret" "secrets": {
}, "type": "array",
"description": "Array of newly-created secrets for the given project and environment" "items": {
"$ref": "#/components/schemas/Secret"
},
"description": "Newly-created secrets for the given project and environment"
}
}
} }
} }
} }
@ -2608,18 +2901,49 @@
"get": { "get": {
"summary": "Read secrets", "summary": "Read secrets",
"description": "Read secrets from a project and environment", "description": "Read secrets from a project and environment",
"parameters": [], "parameters": [
{
"name": "workspaceId",
"description": "ID of project",
"required": true,
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "environment",
"description": "Environment within project",
"required": true,
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "content",
"in": "query",
"schema": {
"type": "string"
}
}
],
"responses": { "responses": {
"200": { "200": {
"description": "OK", "description": "OK",
"content": { "content": {
"application/json": { "application/json": {
"schema": { "schema": {
"type": "array", "type": "object",
"items": { "properties": {
"$ref": "#/components/schemas/Secret" "secrets": {
}, "type": "array",
"description": "Array of secrets for the given project and environment" "items": {
"$ref": "#/components/schemas/Secret"
},
"description": "Secrets for the given project and environment"
}
}
} }
} }
} }
@ -2629,27 +2953,7 @@
{ {
"apiKeyAuth": [] "apiKeyAuth": []
} }
], ]
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"workspaceId": {
"type": "string",
"description": "ID of project"
},
"environment": {
"type": "string",
"description": "Environment within project"
}
}
}
}
}
}
}, },
"patch": { "patch": {
"summary": "Update secret(s)", "summary": "Update secret(s)",
@ -2661,11 +2965,16 @@
"content": { "content": {
"application/json": { "application/json": {
"schema": { "schema": {
"type": "array", "type": "object",
"items": { "properties": {
"$ref": "#/components/schemas/Secret" "secrets": {
}, "type": "array",
"description": "Array of newly-updated secrets for the given project and environment" "items": {
"$ref": "#/components/schemas/Secret"
},
"description": "Updated secrets"
}
}
} }
} }
} }
@ -2703,11 +3012,16 @@
"content": { "content": {
"application/json": { "application/json": {
"schema": { "schema": {
"type": "array", "type": "object",
"items": { "properties": {
"$ref": "#/components/schemas/Secret" "secrets": {
}, "type": "array",
"description": "Array of deleted secrets" "items": {
"$ref": "#/components/schemas/Secret"
},
"description": "Deleted secrets"
}
}
} }
} }
} }
@ -2942,6 +3256,52 @@
} }
} }
}, },
"Membership": {
"type": "object",
"properties": {
"user": {
"type": "object",
"properties": {
"_id": {
"type": "string",
"example": ""
},
"email": {
"type": "string",
"example": ""
},
"firstName": {
"type": "string",
"example": ""
},
"lastName": {
"type": "string",
"example": ""
},
"publicKey": {
"type": "string",
"example": ""
},
"updatedAt": {
"type": "string",
"example": ""
},
"createdAt": {
"type": "string",
"example": ""
}
}
},
"workspace": {
"type": "string",
"example": ""
},
"role": {
"type": "string",
"example": "admin"
}
}
},
"ProjectKey": { "ProjectKey": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -3127,6 +3487,187 @@
"example": "" "example": ""
} }
} }
},
"Log": {
"type": "object",
"properties": {
"_id": {
"type": "string",
"example": ""
},
"user": {
"type": "object",
"properties": {
"_id": {
"type": "string",
"example": ""
},
"email": {
"type": "string",
"example": ""
},
"firstName": {
"type": "string",
"example": ""
},
"lastName": {
"type": "string",
"example": ""
}
}
},
"workspace": {
"type": "string",
"example": ""
},
"actionNames": {
"type": "array",
"example": [
"addSecrets"
],
"items": {
"type": "string"
}
},
"actions": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"example": "addSecrets"
},
"user": {
"type": "string",
"example": ""
},
"workspace": {
"type": "string",
"example": ""
},
"payload": {
"type": "array",
"items": {
"type": "object",
"properties": {
"oldSecretVersion": {
"type": "string",
"example": ""
},
"newSecretVersion": {
"type": "string",
"example": ""
}
}
}
}
}
}
},
"channel": {
"type": "string",
"example": "cli"
},
"ipAddress": {
"type": "string",
"example": "192.168.0.1"
},
"updatedAt": {
"type": "string",
"example": ""
},
"createdAt": {
"type": "string",
"example": ""
}
}
},
"SecretSnapshot": {
"type": "object",
"properties": {
"workspace": {
"type": "string",
"example": ""
},
"version": {
"type": "number",
"example": 1
},
"secretVersions": {
"type": "array",
"items": {
"type": "object",
"properties": {
"_id": {
"type": "string",
"example": ""
}
}
}
}
}
},
"SecretVersion": {
"type": "object",
"properties": {
"_id": {
"type": "string",
"example": ""
},
"secret": {
"type": "string",
"example": ""
},
"version": {
"type": "number",
"example": 1
},
"workspace": {
"type": "string",
"example": ""
},
"type": {
"type": "string",
"example": ""
},
"user": {
"type": "string",
"example": ""
},
"environment": {
"type": "string",
"example": ""
},
"isDeleted": {
"type": "string",
"example": ""
},
"secretKeyCiphertext": {
"type": "string",
"example": ""
},
"secretKeyIV": {
"type": "string",
"example": ""
},
"secretKeyTag": {
"type": "string",
"example": ""
},
"secretValueCiphertext": {
"type": "string",
"example": ""
},
"secretValueIV": {
"type": "string",
"example": ""
},
"secretValueTag": {
"type": "string",
"example": ""
}
}
} }
}, },
"securitySchemes": { "securitySchemes": {

@ -58,11 +58,16 @@ export const createSecrets = async (req: Request, res: Response) => {
content: { content: {
"application/json": { "application/json": {
"schema": { "schema": {
"type": "array", "type": "object",
"items": { "properties": {
$ref: "#/components/schemas/Secret" "secrets": {
}, "type": "array",
"description": "Array of newly-created secrets for the given project and environment" "items": {
$ref: "#/components/schemas/Secret"
},
"description": "Newly-created secrets for the given project and environment"
}
}
} }
} }
} }
@ -205,36 +210,32 @@ export const getSecrets = async (req: Request, res: Response) => {
"apiKeyAuth": [] "apiKeyAuth": []
}] }]
#swagger.requestBody = { #swagger.parameters['workspaceId'] = {
"required": true, "description": "ID of project",
"content": { "required": true,
"application/json": { "type": "string"
"schema": { }
"type": "object",
"properties": { #swagger.parameters['environment'] = {
"workspaceId": { "description": "Environment within project",
"type": "string", "required": true,
"description": "ID of project" "type": "string"
}, }
"environment": {
"type": "string",
"description": "Environment within project"
}
}
}
}
}
}
#swagger.responses[200] = { #swagger.responses[200] = {
content: { content: {
"application/json": { "application/json": {
"schema": { "schema": {
"type": "array", "type": "object",
"items": { "properties": {
$ref: "#/components/schemas/Secret" "secrets": {
}, "type": "array",
"description": "Array of secrets for the given project and environment" "items": {
$ref: "#/components/schemas/Secret"
},
"description": "Secrets for the given project and environment"
}
}
} }
} }
} }
@ -307,9 +308,6 @@ 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) => {
// TODO: fix update secret schema
/* /*
#swagger.summary = 'Update secret(s)' #swagger.summary = 'Update secret(s)'
#swagger.description = 'Update secret(s)' #swagger.description = 'Update secret(s)'
@ -339,15 +337,20 @@ export const updateSecrets = async (req: Request, res: Response) => {
content: { content: {
"application/json": { "application/json": {
"schema": { "schema": {
"type": "array", "type": "object",
"items": { "properties": {
$ref: "#/components/schemas/Secret" "secrets": {
}, "type": "array",
"description": "Array of newly-updated secrets for the given project and environment" "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';
@ -544,12 +547,17 @@ export const deleteSecrets = async (req: Request, res: Response) => {
#swagger.responses[200] = { #swagger.responses[200] = {
content: { content: {
"application/json": { "application/json": {
schema: { "schema": {
"type": "array", "type": "object",
"items": { "properties": {
$ref: "#/components/schemas/Secret" "secrets": {
}, "type": "array",
"description": "Array of deleted secrets" "items": {
$ref: "#/components/schemas/Secret"
},
"description": "Deleted secrets"
}
}
} }
} }
} }

@ -18,8 +18,13 @@ export const getMe = async (req: Request, res: Response) => {
"application/json": { "application/json": {
"schema": { "schema": {
"type": "object", "type": "object",
$ref: "#/components/schemas/CurrentUser", "properties": {
"description": "Current user on request" "user": {
"type": "object",
$ref: "#/components/schemas/CurrentUser",
"description": "Current user on request"
}
}
} }
} }
} }

@ -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,
@ -242,4 +244,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 members'
});
}
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;
@ -79,8 +124,8 @@ export const getWorkspaceSecretSnapshotsCount = async (req: Request, res: Respon
*/ */
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.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.description = 'Roll back project secrets to those captured in a secret snapshot version.'
#swagger.security = [{ #swagger.security = [{
"apiKeyAuth": [] "apiKeyAuth": []
@ -113,11 +158,16 @@ export const rollbackWorkspaceSecretSnapshot = async (req: Request, res: Respons
content: { content: {
"application/json": { "application/json": {
schema: { schema: {
"type": "array", "type": "object",
"items": { "properties": {
$ref: "#/components/schemas/Secret" "secrets": {
}, "type": "array",
"description": "Array of secrets captured in the secret snapshot" "items": {
$ref: "#/components/schemas/Secret"
},
"description": "Secrets rolled back to"
}
}
} }
} }
} }
@ -276,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,57 @@
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;
} 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']

@ -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
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;

@ -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;

@ -53,6 +53,19 @@ const generateOpenAPISpec = async () => {
updatedAt: '', updatedAt: '',
createdAt: '' createdAt: ''
}, },
Membership: {
user: {
_id: '',
email: '',
firstName: '',
lastName: '',
publicKey: '',
updatedAt: '',
createdAt: ''
},
workspace: '',
role: 'admin'
},
ProjectKey: { ProjectKey: {
encryptedkey: '', encryptedkey: '',
nonce: '', nonce: '',
@ -103,6 +116,61 @@ const generateOpenAPISpec = async () => {
secretCommentTag: '', secretCommentTag: '',
updatedAt: '', updatedAt: '',
createdAt: '' 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: '',
} }
} }
}; };

@ -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: "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}"
---

@ -1,4 +1,4 @@
--- ---
title: "Get Project Key" title: "Get Key"
openapi: "GET /api/v2/workspace/{workspaceId}/encrypted-key" openapi: "GET /api/v2/workspace/{workspaceId}/encrypted-key"
--- ---

@ -13,52 +13,140 @@ Prerequisites:
1. Get your (encrypted) private key. 1. Get your (encrypted) private key.
2. Decrypt your (encrypted) private key with your password. 2. Decrypt your (encrypted) private key with your password.
3. Get the project key for the project. 3. Get the (encrypted) project key for the project.
4. Decrypt the project key with your private key. 4. Decrypt the (encrypted) project key with your private key.
5. Encrypt your secrets with the project key. 5. Encrypt your secret(s) with the project key.
6. Send (encrypted) secrets to the Infical API 6. Send (encrypted) secret(s) to the Infical API
## Example ## Example
```js ```js
const axios = require("axios"); const crypto = require('crypto');
const aes = require("aes-256-gcm"); const axios = require('axios');
const nacl = require("tweetnacl");
nacl.util = require("tweetnacl-util");
const WORKSPACE_KEY = "3a7a243eb62078c13f09203e75e8cb32"; const ALGORITHM = 'aes-256-gcm';
const BLOCK_SIZE_BYTES = 16;
const secretKey = "SOME_KEY"; const encrypt = (
const secretValue = "SOME_VALUE"; text,
secret
) => {
const iv = crypto.randomBytes(BLOCK_SIZE_BYTES);
const cipher = crypto.createCipheriv(ALGORITHM, secret, iv);
// encrypt key of secret let ciphertext = cipher.update(text, 'utf8', 'base64');
const { ciphertext += cipher.final('base64');
ciphertext: secretKeyCiphertext, return {
iv: secretKeyIV, ciphertext,
tag: secretKeyTag, iv: iv.toString('base64'),
} = aes.encrypt(secretKey, WORKSPACE_KEY); tag: cipher.getAuthTag().toString('base64')
};
}
// encrypt value of secret const decrypt = (ciphertext, iv, tag, secret) => {
const { const decipher = crypto.createDecipheriv(
ciphertext: secretValueCiphertext, ALGORITHM,
iv: secretValueIV, secret,
tag: secretValueTag, Buffer.from(iv, 'base64')
} = aes.encrypt(secretKey, WORKSPACE_KEY); );
decipher.setAuthTag(Buffer.from(tag, 'base64'));
// construct request body let cleartext = decipher.update(ciphertext, 'base64', 'utf8');
const secret = { cleartext += decipher.final('utf8');
secretKeyCiphertext,
secretKeyIV, return cleartext;
secretKeyTag, }
secretValueCiphertext,
secretValueIV, const createSecrets = async () => {
secretValueTag, 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> <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 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>

@ -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();
```

@ -2,9 +2,141 @@
title: "Retrieve secrets" 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. 1. Get your (encrypted) private key.
2. Decrypt your (encrypted) private key with your password. 2. Decrypt your (encrypted) private key with your password.
3. Get the project key for the project. 3. Get the (encrypted) project key for the project.
4. Decrypt the project key with your private key. 4. Decrypt the (encrypted) project key with your private key.
5. Get secrets for a project and environment. 5. Get secrets for a project and environment.
6. Decrypt the secrets in your project. 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>

@ -2,9 +2,151 @@
title: "Update secrets" 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. 1. Get your (encrypted) private key.
2. Decrypt your (encrypted) private key with your password. 2. Decrypt your (encrypted) private key with your password.
3. Get the project key for the project. 3. Get the project key for the project.
4. Decrypt the project key with your private key. 4. Decrypt the project key with your private key.
5. Encrypt your secrets with the project key. 5. Encrypt your secret(s) with the project key.
6. Send (encrypted) updated secrets to the Infical API 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>

@ -2,8 +2,13 @@
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 Infisical's REST API provides users an alternative way to programmatically access and manage
secrets via HTTP requests. This can be useful for automating tasks, such as secrets via HTTPS requests. This can be useful for automating tasks, such as
rotating credentials, or for integrating secret management into a larger system. 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. With the REST API, users can create, read, update, and delete secrets, as well as manage access control, query audit logs, and more.
@ -17,3 +22,10 @@ Using Infisical's API to manage secrets requires a basic understanding of the sy
- 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 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>
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>

@ -150,7 +150,8 @@
"pages": [ "pages": [
"api-reference/overview/examples/create-secrets", "api-reference/overview/examples/create-secrets",
"api-reference/overview/examples/retrieve-secrets", "api-reference/overview/examples/retrieve-secrets",
"api-reference/overview/examples/update-secrets" "api-reference/overview/examples/update-secrets",
"api-reference/overview/examples/delete-secrets"
] ]
} }
] ]
@ -167,7 +168,13 @@
{ {
"group": "Projects", "group": "Projects",
"pages": [ "pages": [
"api-reference/endpoints/workspaces/workspace-key" "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"
] ]
}, },
{ {
@ -176,7 +183,9 @@
"api-reference/endpoints/secrets/create", "api-reference/endpoints/secrets/create",
"api-reference/endpoints/secrets/read", "api-reference/endpoints/secrets/read",
"api-reference/endpoints/secrets/update", "api-reference/endpoints/secrets/update",
"api-reference/endpoints/secrets/delete" "api-reference/endpoints/secrets/delete",
"api-reference/endpoints/secrets/versions",
"api-reference/endpoints/secrets/rollback-version"
] ]
} }
] ]

@ -11,48 +11,81 @@ servers:
paths: paths:
/api/v1/secret/{secretId}/secret-versions: /api/v1/secret/{secretId}/secret-versions:
get: get:
description: '' summary: Return secret versions
description: Return secret versions
parameters: parameters:
- name: secretId - name: secretId
in: path in: path
required: true required: true
schema: schema:
type: string type: string
description: ID of secret
- name: offset - name: offset
description: Number of versions to skip
required: false
in: query in: query
schema: schema:
type: string type: string
- name: limit - name: limit
description: Maximum number of versions to return
required: false
in: query in: query
schema: schema:
type: string type: string
responses: responses:
'200': '200':
description: OK description: OK
content:
application/json:
schema:
type: object
properties:
secretVersions:
type: array
items:
$ref: '#/components/schemas/SecretVersion'
description: Secret versions
'400': '400':
description: Bad Request description: Bad Request
security:
- apiKeyAuth: []
/api/v1/secret/{secretId}/secret-versions/rollback: /api/v1/secret/{secretId}/secret-versions/rollback:
post: post:
description: '' summary: Roll back secret to a version.
description: Roll back secret to a version.
parameters: parameters:
- name: secretId - name: secretId
in: path in: path
required: true required: true
schema: schema:
type: string type: string
description: ID of secret
responses: responses:
'200': '200':
description: OK description: OK
content:
application/json:
schema:
type: object
properties:
secret:
type: object
$ref: '#/components/schemas/Secret'
description: Secret rolled back to
'400': '400':
description: Bad Request description: Bad Request
security:
- apiKeyAuth: []
requestBody: requestBody:
required: true
content: content:
application/json: application/json:
schema: schema:
type: object type: object
properties: properties:
version: version:
example: any type: integer
description: Version of secret to roll back to
/api/v1/secret-snapshot/{secretSnapshotId}: /api/v1/secret-snapshot/{secretSnapshotId}:
get: get:
description: '' description: ''
@ -69,26 +102,44 @@ paths:
description: Bad Request description: Bad Request
/api/v1/workspace/{workspaceId}/secret-snapshots: /api/v1/workspace/{workspaceId}/secret-snapshots:
get: get:
description: '' summary: Return project secret snapshot ids
description: Return project secret snapshots ids
parameters: parameters:
- name: workspaceId - name: workspaceId
in: path in: path
required: true required: true
schema: schema:
type: string type: string
description: ID of project
- name: offset - name: offset
description: Number of secret snapshots to skip
required: false
in: query in: query
schema: schema:
type: string type: string
- name: limit - name: limit
description: Maximum number of secret snapshots to return
required: false
in: query in: query
schema: schema:
type: string type: string
responses: responses:
'200': '200':
description: OK description: OK
content:
application/json:
schema:
type: object
properties:
secretSnapshots:
type: array
items:
$ref: '#/components/schemas/SecretSnapshot'
description: Project secret snapshots
'400': '400':
description: Bad Request description: Bad Request
security:
- apiKeyAuth: []
/api/v1/workspace/{workspaceId}/secret-snapshots/count: /api/v1/workspace/{workspaceId}/secret-snapshots/count:
get: get:
description: '' description: ''
@ -105,60 +156,107 @@ paths:
description: Bad Request description: Bad Request
/api/v1/workspace/{workspaceId}/secret-snapshots/rollback: /api/v1/workspace/{workspaceId}/secret-snapshots/rollback:
post: post:
description: '' summary: >-
Roll back project secrets to those captured in a secret snapshot
version.
description: >-
Roll back project secrets to those captured in a secret snapshot
version.
parameters: parameters:
- name: workspaceId - name: workspaceId
in: path in: path
required: true required: true
schema: schema:
type: string type: string
description: ID of project
responses: responses:
'200': '200':
description: OK description: OK
content:
application/json:
schema:
type: object
properties:
secrets:
type: array
items:
$ref: '#/components/schemas/Secret'
description: Secrets rolled back to
'400': '400':
description: Bad Request description: Bad Request
security:
- apiKeyAuth: []
requestBody: requestBody:
required: true
content: content:
application/json: application/json:
schema: schema:
type: object type: object
properties: properties:
version: version:
example: any type: integer
description: Version of secret snapshot to roll back to
/api/v1/workspace/{workspaceId}/logs: /api/v1/workspace/{workspaceId}/logs:
get: get:
description: '' summary: Return project (audit) logs
description: Return project (audit) logs
parameters: parameters:
- name: workspaceId - name: workspaceId
in: path in: path
required: true required: true
schema: schema:
type: string type: string
description: ID of project
- name: userId
description: ID of project member
required: false
in: query
schema:
type: string
- name: offset - name: offset
description: Number of logs to skip
required: false
in: query in: query
schema: schema:
type: string type: string
- name: limit - name: limit
description: Maximum number of logs to return
required: false
in: query in: query
schema: schema:
type: string type: string
- name: sortBy - name: sortBy
in: query description: Order to sort the logs by
schema: schema:
type: string type: string
- name: userId enum:
- oldest
- recent
required: false
in: query in: query
schema:
type: string
- name: actionNames - name: actionNames
description: Names of log actions (comma-separated)
required: false
in: query in: query
schema: schema:
type: string type: string
responses: responses:
'200': '200':
description: OK description: OK
content:
application/json:
schema:
type: object
properties:
logs:
type: array
items:
$ref: '#/components/schemas/Log'
description: Project logs
'400': '400':
description: Bad Request description: Bad Request
security:
- apiKeyAuth: []
/api/v1/action/{actionId}: /api/v1/action/{actionId}:
get: get:
description: '' description: ''
@ -1285,8 +1383,11 @@ paths:
application/json: application/json:
schema: schema:
type: object type: object
$ref: '#/components/schemas/CurrentUser' properties:
description: Current user on request user:
type: object
$ref: '#/components/schemas/CurrentUser'
description: Current user on request
'400': '400':
description: Bad Request description: Bad Request
security: security:
@ -1379,6 +1480,107 @@ paths:
description: OK description: OK
'400': '400':
description: Bad Request description: Bad Request
/api/v2/workspace/{workspaceId}/memberships:
get:
summary: Return project memberships
description: Return project memberships
parameters:
- name: workspaceId
in: path
required: true
schema:
type: string
description: ID of project
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
properties:
memberships:
type: array
items:
$ref: '#/components/schemas/Membership'
description: Memberships of project
'400':
description: Bad Request
security:
- apiKeyAuth: []
/api/v2/workspace/{workspaceId}/memberships/{membershipId}:
delete:
summary: Delete project membership
description: Delete project membership
parameters:
- name: workspaceId
in: path
required: true
schema:
type: string
description: ID of project
- name: membershipId
in: path
required: true
schema:
type: string
description: ID of membership
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
properties:
membership:
$ref: '#/components/schemas/Membership'
description: Deleted membership
'400':
description: Bad Request
security:
- apiKeyAuth: []
patch:
summary: Update project membership
description: Update project membership
parameters:
- name: workspaceId
in: path
required: true
schema:
type: string
description: ID of project
- name: membershipId
in: path
required: true
schema:
type: string
description: ID of membership
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
properties:
membership:
$ref: '#/components/schemas/Membership'
description: Updated membership
'400':
description: Bad Request
security:
- apiKeyAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
role:
type: string
description: Role of membership - either admin or member
/api/v2/secret/batch-create/workspace/{workspaceId}/environment/{environment}: /api/v2/secret/batch-create/workspace/{workspaceId}/environment/{environment}:
post: post:
description: '' description: ''
@ -1554,12 +1756,15 @@ paths:
content: content:
application/json: application/json:
schema: schema:
type: array type: object
items: properties:
$ref: '#/components/schemas/Secret' secrets:
description: >- type: array
Array of newly-created secrets for the given project and items:
environment $ref: '#/components/schemas/Secret'
description: >-
Newly-created secrets for the given project and
environment
security: security:
- apiKeyAuth: [] - apiKeyAuth: []
requestBody: requestBody:
@ -1581,32 +1786,38 @@ paths:
get: get:
summary: Read secrets summary: Read secrets
description: Read secrets from a project and environment description: Read secrets from a project and environment
parameters: [] parameters:
- name: workspaceId
description: ID of project
required: true
in: query
schema:
type: string
- name: environment
description: Environment within project
required: true
in: query
schema:
type: string
- name: content
in: query
schema:
type: string
responses: responses:
'200': '200':
description: OK description: OK
content: content:
application/json: application/json:
schema: schema:
type: array type: object
items: properties:
$ref: '#/components/schemas/Secret' secrets:
description: Array of secrets for the given project and environment type: array
items:
$ref: '#/components/schemas/Secret'
description: Secrets for the given project and environment
security: security:
- apiKeyAuth: [] - apiKeyAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
workspaceId:
type: string
description: ID of project
environment:
type: string
description: Environment within project
patch: patch:
summary: Update secret(s) summary: Update secret(s)
description: Update secret(s) description: Update secret(s)
@ -1617,12 +1828,13 @@ paths:
content: content:
application/json: application/json:
schema: schema:
type: array type: object
items: properties:
$ref: '#/components/schemas/Secret' secrets:
description: >- type: array
Array of newly-updated secrets for the given project and items:
environment $ref: '#/components/schemas/Secret'
description: Updated secrets
security: security:
- apiKeyAuth: [] - apiKeyAuth: []
requestBody: requestBody:
@ -1645,10 +1857,13 @@ paths:
content: content:
application/json: application/json:
schema: schema:
type: array type: object
items: properties:
$ref: '#/components/schemas/Secret' secrets:
description: Array of deleted secrets type: array
items:
$ref: '#/components/schemas/Secret'
description: Deleted secrets
security: security:
- apiKeyAuth: [] - apiKeyAuth: []
requestBody: requestBody:
@ -1783,12 +1998,51 @@ components:
encryptedPrivateKey: encryptedPrivateKey:
type: string type: string
example: '' example: ''
iv:
type: string
example: ''
tag:
type: string
example: ''
updatedAt: updatedAt:
type: string type: string
example: '' example: ''
createdAt: createdAt:
type: string type: string
example: '' example: ''
Membership:
type: object
properties:
user:
type: object
properties:
_id:
type: string
example: ''
email:
type: string
example: ''
firstName:
type: string
example: ''
lastName:
type: string
example: ''
publicKey:
type: string
example: ''
updatedAt:
type: string
example: ''
createdAt:
type: string
example: ''
workspace:
type: string
example: ''
role:
type: string
example: admin
ProjectKey: ProjectKey:
type: object type: object
properties: properties:
@ -1925,6 +2179,135 @@ components:
createdAt: createdAt:
type: string type: string
example: '' example: ''
Log:
type: object
properties:
_id:
type: string
example: ''
user:
type: object
properties:
_id:
type: string
example: ''
email:
type: string
example: ''
firstName:
type: string
example: ''
lastName:
type: string
example: ''
workspace:
type: string
example: ''
actionNames:
type: array
example:
- addSecrets
items:
type: string
actions:
type: array
items:
type: object
properties:
name:
type: string
example: addSecrets
user:
type: string
example: ''
workspace:
type: string
example: ''
payload:
type: array
items:
type: object
properties:
oldSecretVersion:
type: string
example: ''
newSecretVersion:
type: string
example: ''
channel:
type: string
example: cli
ipAddress:
type: string
example: 192.168.0.1
updatedAt:
type: string
example: ''
createdAt:
type: string
example: ''
SecretSnapshot:
type: object
properties:
workspace:
type: string
example: ''
version:
type: number
example: 1
secretVersions:
type: array
items:
type: object
properties:
_id:
type: string
example: ''
SecretVersion:
type: object
properties:
_id:
type: string
example: ''
secret:
type: string
example: ''
version:
type: number
example: 1
workspace:
type: string
example: ''
type:
type: string
example: ''
user:
type: string
example: ''
environment:
type: string
example: ''
isDeleted:
type: string
example: ''
secretKeyCiphertext:
type: string
example: ''
secretKeyIV:
type: string
example: ''
secretKeyTag:
type: string
example: ''
secretValueCiphertext:
type: string
example: ''
secretValueIV:
type: string
example: ''
secretValueTag:
type: string
example: ''
securitySchemes: securitySchemes:
bearerAuth: bearerAuth:
type: http type: http