mirror of
https://github.com/Infisical/infisical.git
synced 2025-03-25 14:05:03 +00:00
Complete v1 API reference docs, pre-launch
This commit is contained in:
@ -18,7 +18,8 @@
|
||||
"paths": {
|
||||
"/api/v1/secret/{secretId}/secret-versions": {
|
||||
"get": {
|
||||
"description": "",
|
||||
"summary": "Return secret versions",
|
||||
"description": "Return secret versions",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "secretId",
|
||||
@ -26,10 +27,13 @@
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"description": "ID of secret"
|
||||
},
|
||||
{
|
||||
"name": "offset",
|
||||
"description": "Number of versions to skip",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
@ -37,6 +41,8 @@
|
||||
},
|
||||
{
|
||||
"name": "limit",
|
||||
"description": "Maximum number of versions to return",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
@ -45,17 +51,39 @@
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
"description": "OK",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"secretVersions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/SecretVersion"
|
||||
},
|
||||
"description": "Secret versions"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request"
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"apiKeyAuth": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v1/secret/{secretId}/secret-versions/rollback": {
|
||||
"post": {
|
||||
"description": "",
|
||||
"summary": "Roll back secret to a version.",
|
||||
"description": "Roll back secret to a version.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "secretId",
|
||||
@ -63,25 +91,47 @@
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"description": "ID of secret"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"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": {
|
||||
"description": "Bad Request"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"apiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"version": {
|
||||
"example": "any"
|
||||
"type": "integer",
|
||||
"description": "Version of secret to roll back to"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -115,7 +165,8 @@
|
||||
},
|
||||
"/api/v1/workspace/{workspaceId}/secret-snapshots": {
|
||||
"get": {
|
||||
"description": "",
|
||||
"summary": "Return project secret snapshot ids",
|
||||
"description": "Return project secret snapshots ids",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "workspaceId",
|
||||
@ -123,10 +174,13 @@
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"description": "ID of project"
|
||||
},
|
||||
{
|
||||
"name": "offset",
|
||||
"description": "Number of secret snapshots to skip",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
@ -134,6 +188,8 @@
|
||||
},
|
||||
{
|
||||
"name": "limit",
|
||||
"description": "Maximum number of secret snapshots to return",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
@ -142,12 +198,33 @@
|
||||
],
|
||||
"responses": {
|
||||
"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": {
|
||||
"description": "Bad Request"
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"apiKeyAuth": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v1/workspace/{workspaceId}/secret-snapshots/count": {
|
||||
@ -175,8 +252,8 @@
|
||||
},
|
||||
"/api/v1/workspace/{workspaceId}/secret-snapshots/rollback": {
|
||||
"post": {
|
||||
"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",
|
||||
"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": [
|
||||
{
|
||||
"name": "workspaceId",
|
||||
@ -194,11 +271,16 @@
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Secret"
|
||||
},
|
||||
"description": "Array of secrets captured in the secret snapshot"
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"secrets": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Secret"
|
||||
},
|
||||
"description": "Secrets rolled back to"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -232,18 +314,31 @@
|
||||
},
|
||||
"/api/v1/workspace/{workspaceId}/logs": {
|
||||
"get": {
|
||||
"description": "",
|
||||
"summary": "Return project (audit) logs",
|
||||
"description": "Return project (audit) logs",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "workspaceId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "ID of project"
|
||||
},
|
||||
{
|
||||
"name": "userId",
|
||||
"description": "ID of project member",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "offset",
|
||||
"description": "Number of logs to skip",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
@ -251,6 +346,8 @@
|
||||
},
|
||||
{
|
||||
"name": "limit",
|
||||
"description": "Maximum number of logs to return",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
@ -258,20 +355,21 @@
|
||||
},
|
||||
{
|
||||
"name": "sortBy",
|
||||
"in": "query",
|
||||
"description": "Order to sort the logs by",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "userId",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"oldest",
|
||||
"recent"
|
||||
]
|
||||
},
|
||||
"required": false,
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"name": "actionNames",
|
||||
"description": "Names of log actions (comma-separated)",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
@ -280,12 +378,33 @@
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
"description": "OK",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"logs": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Log"
|
||||
},
|
||||
"description": "Project logs"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request"
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"apiKeyAuth": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v1/action/{actionId}": {
|
||||
@ -2117,8 +2236,13 @@
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"$ref": "#/components/schemas/CurrentUser",
|
||||
"description": "Current user on request"
|
||||
"properties": {
|
||||
"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}": {
|
||||
"post": {
|
||||
"description": "",
|
||||
@ -2565,11 +2853,16 @@
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Secret"
|
||||
},
|
||||
"description": "Array of newly-created secrets for the given project and environment"
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"secrets": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Secret"
|
||||
},
|
||||
"description": "Newly-created secrets for the given project and environment"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2608,18 +2901,49 @@
|
||||
"get": {
|
||||
"summary": "Read secrets",
|
||||
"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": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Secret"
|
||||
},
|
||||
"description": "Array of secrets for the given project and environment"
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"secrets": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Secret"
|
||||
},
|
||||
"description": "Secrets for the given project and environment"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2629,27 +2953,7 @@
|
||||
{
|
||||
"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": {
|
||||
"summary": "Update secret(s)",
|
||||
@ -2661,11 +2965,16 @@
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Secret"
|
||||
},
|
||||
"description": "Array of newly-updated secrets for the given project and environment"
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"secrets": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Secret"
|
||||
},
|
||||
"description": "Updated secrets"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2703,11 +3012,16 @@
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Secret"
|
||||
},
|
||||
"description": "Array of deleted secrets"
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"secrets": {
|
||||
"type": "array",
|
||||
"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": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@ -3127,6 +3487,187 @@
|
||||
"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": {
|
||||
|
@ -58,11 +58,16 @@ export const createSecrets = async (req: Request, res: Response) => {
|
||||
content: {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
$ref: "#/components/schemas/Secret"
|
||||
},
|
||||
"description": "Array of newly-created secrets for the given project and environment"
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"secrets": {
|
||||
"type": "array",
|
||||
"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": []
|
||||
}]
|
||||
|
||||
#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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#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": "array",
|
||||
"items": {
|
||||
$ref: "#/components/schemas/Secret"
|
||||
},
|
||||
"description": "Array of secrets for the given project and environment"
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"secrets": {
|
||||
"type": "array",
|
||||
"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
|
||||
*/
|
||||
export const updateSecrets = async (req: Request, res: Response) => {
|
||||
|
||||
// TODO: fix update secret schema
|
||||
|
||||
/*
|
||||
#swagger.summary = 'Update secret(s)'
|
||||
#swagger.description = 'Update secret(s)'
|
||||
@ -339,15 +337,20 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
||||
content: {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
$ref: "#/components/schemas/Secret"
|
||||
},
|
||||
"description": "Array of newly-updated secrets for the given project and environment"
|
||||
"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';
|
||||
|
||||
@ -544,12 +547,17 @@ export const deleteSecrets = async (req: Request, res: Response) => {
|
||||
#swagger.responses[200] = {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
"type": "array",
|
||||
"items": {
|
||||
$ref: "#/components/schemas/Secret"
|
||||
},
|
||||
"description": "Array of deleted secrets"
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"secrets": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
$ref: "#/components/schemas/Secret"
|
||||
},
|
||||
"description": "Deleted secrets"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,8 +18,13 @@ export const getMe = async (req: Request, res: Response) => {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
$ref: "#/components/schemas/CurrentUser",
|
||||
"description": "Current user on request"
|
||||
"properties": {
|
||||
"user": {
|
||||
"type": "object",
|
||||
$ref: "#/components/schemas/CurrentUser",
|
||||
"description": "Current user on request"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
import { Request, Response } from 'express';
|
||||
import * as Sentry from '@sentry/node';
|
||||
import { Types } from 'mongoose';
|
||||
import {
|
||||
Workspace,
|
||||
Secret,
|
||||
Membership,
|
||||
MembershipOrg,
|
||||
Integration,
|
||||
@ -242,4 +244,222 @@ export const getWorkspaceServiceTokenData = async (
|
||||
return res.status(200).send({
|
||||
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
|
||||
*/
|
||||
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;
|
||||
try {
|
||||
const { secretId } = req.params;
|
||||
@ -44,6 +89,54 @@ import { EESecretService } from '../../services';
|
||||
* @returns
|
||||
*/
|
||||
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;
|
||||
try {
|
||||
const { secretId } = req.params;
|
||||
|
@ -19,6 +19,51 @@ import { getLatestSecretVersionIds } from '../../helpers/secretVersion';
|
||||
* @param res
|
||||
*/
|
||||
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;
|
||||
try {
|
||||
const { workspaceId } = req.params;
|
||||
@ -79,8 +124,8 @@ export const getWorkspaceSecretSnapshotsCount = async (req: Request, res: Respon
|
||||
*/
|
||||
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.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": []
|
||||
@ -113,11 +158,16 @@ export const rollbackWorkspaceSecretSnapshot = async (req: Request, res: Respons
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
"type": "array",
|
||||
"items": {
|
||||
$ref: "#/components/schemas/Secret"
|
||||
},
|
||||
"description": "Array of secrets captured in the secret snapshot"
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"secrets": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
$ref: "#/components/schemas/Secret"
|
||||
},
|
||||
"description": "Secrets rolled back to"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -276,6 +326,72 @@ export const rollbackWorkspaceSecretSnapshot = async (req: Request, res: Respons
|
||||
* @returns
|
||||
*/
|
||||
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
|
||||
try {
|
||||
const { workspaceId } = req.params;
|
||||
|
@ -2,6 +2,7 @@ import requireAuth from './requireAuth';
|
||||
import requireBotAuth from './requireBotAuth';
|
||||
import requireSignupAuth from './requireSignupAuth';
|
||||
import requireWorkspaceAuth from './requireWorkspaceAuth';
|
||||
import requireMembershipAuth from './requireMembershipAuth';
|
||||
import requireOrganizationAuth from './requireOrganizationAuth';
|
||||
import requireIntegrationAuth from './requireIntegrationAuth';
|
||||
import requireIntegrationAuthorizationAuth from './requireIntegrationAuthorizationAuth';
|
||||
@ -16,6 +17,7 @@ export {
|
||||
requireBotAuth,
|
||||
requireSignupAuth,
|
||||
requireWorkspaceAuth,
|
||||
requireMembershipAuth,
|
||||
requireOrganizationAuth,
|
||||
requireIntegrationAuth,
|
||||
requireIntegrationAuthorizationAuth,
|
||||
|
57
backend/src/middleware/requireMembershipAuth.ts
Normal file
57
backend/src/middleware/requireMembershipAuth.ts
Normal file
@ -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 { membershipController } from '../../controllers/v1';
|
||||
|
||||
router.get( // used for CLI (deprecate)
|
||||
router.get( // used for old CLI (deprecate)
|
||||
'/:workspaceId/connect',
|
||||
requireAuth({
|
||||
acceptedAuthModes: ['jwt']
|
||||
|
@ -3,6 +3,7 @@ const router = express.Router();
|
||||
import { body, param, query } from 'express-validator';
|
||||
import {
|
||||
requireAuth,
|
||||
requireMembershipAuth,
|
||||
requireWorkspaceAuth,
|
||||
validateRequest
|
||||
} from '../../middleware';
|
||||
@ -67,4 +68,54 @@ router.get(
|
||||
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;
|
||||
|
1
backend/src/types/express/index.d.ts
vendored
1
backend/src/types/express/index.d.ts
vendored
@ -8,6 +8,7 @@ declare global {
|
||||
user: any;
|
||||
workspace: any;
|
||||
membership: any;
|
||||
targetMembership: any;
|
||||
organization: any;
|
||||
membershipOrg: any;
|
||||
integration: any;
|
||||
|
@ -53,6 +53,19 @@ const generateOpenAPISpec = async () => {
|
||||
updatedAt: '',
|
||||
createdAt: ''
|
||||
},
|
||||
Membership: {
|
||||
user: {
|
||||
_id: '',
|
||||
email: '',
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
publicKey: '',
|
||||
updatedAt: '',
|
||||
createdAt: ''
|
||||
},
|
||||
workspace: '',
|
||||
role: 'admin'
|
||||
},
|
||||
ProjectKey: {
|
||||
encryptedkey: '',
|
||||
nonce: '',
|
||||
@ -103,6 +116,61 @@ const generateOpenAPISpec = async () => {
|
||||
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: '',
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Roll Back to Version"
|
||||
openapi: "POST /api/v1/secret/{secretId}/secret-versions/rollback"
|
||||
---
|
4
docs/api-reference/endpoints/secrets/versions.mdx
Normal file
4
docs/api-reference/endpoints/secrets/versions.mdx
Normal file
@ -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}"
|
||||
---
|
4
docs/api-reference/endpoints/workspaces/logs.mdx
Normal file
4
docs/api-reference/endpoints/workspaces/logs.mdx
Normal file
@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Get Logs"
|
||||
openapi: "GET /api/v1/workspace/{workspaceId}/logs"
|
||||
---
|
4
docs/api-reference/endpoints/workspaces/memberships.mdx
Normal file
4
docs/api-reference/endpoints/workspaces/memberships.mdx
Normal file
@ -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"
|
||||
---
|
||||
|
@ -13,52 +13,140 @@ Prerequisites:
|
||||
|
||||
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 secrets with the project key.
|
||||
6. Send (encrypted) secrets to the Infical API
|
||||
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 axios = require("axios");
|
||||
const aes = require("aes-256-gcm");
|
||||
const nacl = require("tweetnacl");
|
||||
nacl.util = require("tweetnacl-util");
|
||||
const crypto = require('crypto');
|
||||
const axios = require('axios');
|
||||
|
||||
const WORKSPACE_KEY = "3a7a243eb62078c13f09203e75e8cb32";
|
||||
const ALGORITHM = 'aes-256-gcm';
|
||||
const BLOCK_SIZE_BYTES = 16;
|
||||
|
||||
const secretKey = "SOME_KEY";
|
||||
const secretValue = "SOME_VALUE";
|
||||
const encrypt = (
|
||||
text,
|
||||
secret
|
||||
) => {
|
||||
const iv = crypto.randomBytes(BLOCK_SIZE_BYTES);
|
||||
const cipher = crypto.createCipheriv(ALGORITHM, secret, iv);
|
||||
|
||||
// encrypt key of secret
|
||||
const {
|
||||
ciphertext: secretKeyCiphertext,
|
||||
iv: secretKeyIV,
|
||||
tag: secretKeyTag,
|
||||
} = aes.encrypt(secretKey, WORKSPACE_KEY);
|
||||
let ciphertext = cipher.update(text, 'utf8', 'base64');
|
||||
ciphertext += cipher.final('base64');
|
||||
return {
|
||||
ciphertext,
|
||||
iv: iv.toString('base64'),
|
||||
tag: cipher.getAuthTag().toString('base64')
|
||||
};
|
||||
}
|
||||
|
||||
// encrypt value of secret
|
||||
const {
|
||||
ciphertext: secretValueCiphertext,
|
||||
iv: secretValueIV,
|
||||
tag: secretValueTag,
|
||||
} = aes.encrypt(secretKey, WORKSPACE_KEY);
|
||||
const decrypt = (ciphertext, iv, tag, secret) => {
|
||||
const decipher = crypto.createDecipheriv(
|
||||
ALGORITHM,
|
||||
secret,
|
||||
Buffer.from(iv, 'base64')
|
||||
);
|
||||
decipher.setAuthTag(Buffer.from(tag, 'base64'));
|
||||
|
||||
// construct request body
|
||||
const secret = {
|
||||
secretKeyCiphertext,
|
||||
secretKeyIV,
|
||||
secretKeyTag,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
};
|
||||
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 in every major language.
|
||||
|
||||
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>
|
||||
|
34
docs/api-reference/overview/examples/delete-secrets.mdx
Normal file
34
docs/api-reference/overview/examples/delete-secrets.mdx
Normal file
@ -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"
|
||||
---
|
||||
|
||||
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 project key for the project.
|
||||
4. Decrypt the project key with your private key.
|
||||
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 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"
|
||||
---
|
||||
|
||||
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 secrets with the project key.
|
||||
6. Send (encrypted) updated secrets to the Infical API
|
||||
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>
|
||||
|
@ -2,8 +2,13 @@
|
||||
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 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.
|
||||
|
||||
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.
|
||||
- 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>
|
||||
|
@ -150,7 +150,8 @@
|
||||
"pages": [
|
||||
"api-reference/overview/examples/create-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",
|
||||
"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/read",
|
||||
"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"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
477
docs/spec.yaml
477
docs/spec.yaml
@ -11,48 +11,81 @@ servers:
|
||||
paths:
|
||||
/api/v1/secret/{secretId}/secret-versions:
|
||||
get:
|
||||
description: ''
|
||||
summary: Return secret versions
|
||||
description: Return secret versions
|
||||
parameters:
|
||||
- name: secretId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: ID of secret
|
||||
- name: offset
|
||||
description: Number of versions to skip
|
||||
required: false
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
- name: limit
|
||||
description: Maximum number of versions to return
|
||||
required: false
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
secretVersions:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/SecretVersion'
|
||||
description: Secret versions
|
||||
'400':
|
||||
description: Bad Request
|
||||
security:
|
||||
- apiKeyAuth: []
|
||||
/api/v1/secret/{secretId}/secret-versions/rollback:
|
||||
post:
|
||||
description: ''
|
||||
summary: Roll back secret to a version.
|
||||
description: Roll back secret to a version.
|
||||
parameters:
|
||||
- name: secretId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: ID of secret
|
||||
responses:
|
||||
'200':
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
secret:
|
||||
type: object
|
||||
$ref: '#/components/schemas/Secret'
|
||||
description: Secret rolled back to
|
||||
'400':
|
||||
description: Bad Request
|
||||
security:
|
||||
- apiKeyAuth: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
version:
|
||||
example: any
|
||||
type: integer
|
||||
description: Version of secret to roll back to
|
||||
/api/v1/secret-snapshot/{secretSnapshotId}:
|
||||
get:
|
||||
description: ''
|
||||
@ -69,26 +102,44 @@ paths:
|
||||
description: Bad Request
|
||||
/api/v1/workspace/{workspaceId}/secret-snapshots:
|
||||
get:
|
||||
description: ''
|
||||
summary: Return project secret snapshot ids
|
||||
description: Return project secret snapshots ids
|
||||
parameters:
|
||||
- name: workspaceId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: ID of project
|
||||
- name: offset
|
||||
description: Number of secret snapshots to skip
|
||||
required: false
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
- name: limit
|
||||
description: Maximum number of secret snapshots to return
|
||||
required: false
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
secretSnapshots:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/SecretSnapshot'
|
||||
description: Project secret snapshots
|
||||
'400':
|
||||
description: Bad Request
|
||||
security:
|
||||
- apiKeyAuth: []
|
||||
/api/v1/workspace/{workspaceId}/secret-snapshots/count:
|
||||
get:
|
||||
description: ''
|
||||
@ -105,60 +156,107 @@ paths:
|
||||
description: Bad Request
|
||||
/api/v1/workspace/{workspaceId}/secret-snapshots/rollback:
|
||||
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:
|
||||
- name: workspaceId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: ID of project
|
||||
responses:
|
||||
'200':
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
secrets:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Secret'
|
||||
description: Secrets rolled back to
|
||||
'400':
|
||||
description: Bad Request
|
||||
security:
|
||||
- apiKeyAuth: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
version:
|
||||
example: any
|
||||
type: integer
|
||||
description: Version of secret snapshot to roll back to
|
||||
/api/v1/workspace/{workspaceId}/logs:
|
||||
get:
|
||||
description: ''
|
||||
summary: Return project (audit) logs
|
||||
description: Return project (audit) logs
|
||||
parameters:
|
||||
- name: workspaceId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: ID of project
|
||||
- name: userId
|
||||
description: ID of project member
|
||||
required: false
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
- name: offset
|
||||
description: Number of logs to skip
|
||||
required: false
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
- name: limit
|
||||
description: Maximum number of logs to return
|
||||
required: false
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
- name: sortBy
|
||||
in: query
|
||||
description: Order to sort the logs by
|
||||
schema:
|
||||
type: string
|
||||
- name: userId
|
||||
enum:
|
||||
- oldest
|
||||
- recent
|
||||
required: false
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
- name: actionNames
|
||||
description: Names of log actions (comma-separated)
|
||||
required: false
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
logs:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Log'
|
||||
description: Project logs
|
||||
'400':
|
||||
description: Bad Request
|
||||
security:
|
||||
- apiKeyAuth: []
|
||||
/api/v1/action/{actionId}:
|
||||
get:
|
||||
description: ''
|
||||
@ -1285,8 +1383,11 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
$ref: '#/components/schemas/CurrentUser'
|
||||
description: Current user on request
|
||||
properties:
|
||||
user:
|
||||
type: object
|
||||
$ref: '#/components/schemas/CurrentUser'
|
||||
description: Current user on request
|
||||
'400':
|
||||
description: Bad Request
|
||||
security:
|
||||
@ -1379,6 +1480,107 @@ paths:
|
||||
description: OK
|
||||
'400':
|
||||
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}:
|
||||
post:
|
||||
description: ''
|
||||
@ -1554,12 +1756,15 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Secret'
|
||||
description: >-
|
||||
Array of newly-created secrets for the given project and
|
||||
environment
|
||||
type: object
|
||||
properties:
|
||||
secrets:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Secret'
|
||||
description: >-
|
||||
Newly-created secrets for the given project and
|
||||
environment
|
||||
security:
|
||||
- apiKeyAuth: []
|
||||
requestBody:
|
||||
@ -1581,32 +1786,38 @@ paths:
|
||||
get:
|
||||
summary: Read secrets
|
||||
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:
|
||||
'200':
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Secret'
|
||||
description: Array of secrets for the given project and environment
|
||||
type: object
|
||||
properties:
|
||||
secrets:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Secret'
|
||||
description: Secrets for the given project and environment
|
||||
security:
|
||||
- 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:
|
||||
summary: Update secret(s)
|
||||
description: Update secret(s)
|
||||
@ -1617,12 +1828,13 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Secret'
|
||||
description: >-
|
||||
Array of newly-updated secrets for the given project and
|
||||
environment
|
||||
type: object
|
||||
properties:
|
||||
secrets:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Secret'
|
||||
description: Updated secrets
|
||||
security:
|
||||
- apiKeyAuth: []
|
||||
requestBody:
|
||||
@ -1645,10 +1857,13 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Secret'
|
||||
description: Array of deleted secrets
|
||||
type: object
|
||||
properties:
|
||||
secrets:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Secret'
|
||||
description: Deleted secrets
|
||||
security:
|
||||
- apiKeyAuth: []
|
||||
requestBody:
|
||||
@ -1783,12 +1998,51 @@ components:
|
||||
encryptedPrivateKey:
|
||||
type: string
|
||||
example: ''
|
||||
iv:
|
||||
type: string
|
||||
example: ''
|
||||
tag:
|
||||
type: string
|
||||
example: ''
|
||||
updatedAt:
|
||||
type: string
|
||||
example: ''
|
||||
createdAt:
|
||||
type: string
|
||||
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:
|
||||
type: object
|
||||
properties:
|
||||
@ -1925,6 +2179,135 @@ components:
|
||||
createdAt:
|
||||
type: string
|
||||
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:
|
||||
bearerAuth:
|
||||
type: http
|
||||
|
Reference in New Issue
Block a user