diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 1e836a178..db182c24a 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -10,6 +10,9 @@ jobs: steps: - name: ☁️ Checkout source uses: actions/checkout@v3 + - name: Save commit hashes for tag + id: commit + uses: pr-mpt/actions-commit-hash@v2 - name: 🔧 Set up QEMU uses: docker/setup-qemu-action@v2 - name: 🔧 Set up Docker Buildx @@ -39,7 +42,8 @@ jobs: with: push: true context: backend - tags: infisical/backend:latest + tags: infisical/backend:${{ steps.commit.outputs.short }}, + infisical/backend:latest platforms: linux/amd64,linux/arm64 frontend-image: @@ -49,6 +53,9 @@ jobs: steps: - name: ☁️ Checkout source uses: actions/checkout@v3 + - name: Save commit hashes for tag + id: commit + uses: pr-mpt/actions-commit-hash@v2 - name: 🔧 Set up QEMU uses: docker/setup-qemu-action@v2 - name: 🔧 Set up Docker Buildx @@ -80,7 +87,8 @@ jobs: with: push: true context: frontend - tags: infisical/frontend:latest + tags: infisical/frontend:${{ steps.commit.outputs.short }}, + infisical/frontend:latest platforms: linux/amd64,linux/arm64 build-args: | POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }} diff --git a/README.md b/README.md index d0f040bb0..aaafca43a 100644 --- a/README.md +++ b/README.md @@ -333,7 +333,7 @@ Infisical officially launched as v.1.0 on November 21st, 2022. There are a lot o - + ## 🌎 Translations diff --git a/backend/api-documentation.json b/backend/api-documentation.json new file mode 100644 index 000000000..79cf0eb3b --- /dev/null +++ b/backend/api-documentation.json @@ -0,0 +1,2568 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Infisical API", + "description": "List of all available APIs that can be consumed", + "version": "1.0.0" + }, + "paths": { + "/api/v1/secret/{secretId}/secret-versions": { + "get": { + "description": "", + "parameters": [ + { + "name": "secretId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/api/v1/secret-snapshot/{secretSnapshotId}": { + "get": { + "description": "", + "parameters": [ + { + "name": "secretSnapshotId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/api/v1/workspace/{workspaceId}/secret-snapshots": { + "get": { + "description": "", + "parameters": [ + { + "name": "workspaceId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/api/v1/workspace/{workspaceId}/secret-snapshots/count": { + "get": { + "description": "", + "parameters": [ + { + "name": "workspaceId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/api/v1/workspace/{workspaceId}/logs": { + "get": { + "description": "", + "parameters": [ + { + "name": "workspaceId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "sortBy", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "userId", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "actionNames", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/api/v1/action/{actionId}": { + "get": { + "description": "", + "parameters": [ + { + "name": "actionId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/v1/signup/email/signup": { + "post": { + "description": "", + "parameters": [], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + }, + "403": { + "description": "Forbidden" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "email": { + "example": "any" + } + } + } + } + } + } + } + }, + "/api/v1/signup/email/verify": { + "post": { + "description": "", + "parameters": [], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + }, + "403": { + "description": "Forbidden" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "email": { + "example": "any" + }, + "code": { + "example": "any" + } + } + } + } + } + } + } + }, + "/api/v1/signup/complete-account/signup": { + "post": { + "description": "", + "parameters": [], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + }, + "403": { + "description": "Forbidden" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "email": { + "example": "any" + }, + "firstName": { + "example": "any" + }, + "lastName": { + "example": "any" + }, + "publicKey": { + "example": "any" + }, + "encryptedPrivateKey": { + "example": "any" + }, + "iv": { + "example": "any" + }, + "tag": { + "example": "any" + }, + "salt": { + "example": "any" + }, + "verifier": { + "example": "any" + }, + "organizationName": { + "example": "any" + } + } + } + } + } + } + } + }, + "/api/v1/signup/complete-account/invite": { + "post": { + "description": "", + "parameters": [], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + }, + "403": { + "description": "Forbidden" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "email": { + "example": "any" + }, + "firstName": { + "example": "any" + }, + "lastName": { + "example": "any" + }, + "publicKey": { + "example": "any" + }, + "encryptedPrivateKey": { + "example": "any" + }, + "iv": { + "example": "any" + }, + "tag": { + "example": "any" + }, + "salt": { + "example": "any" + }, + "verifier": { + "example": "any" + } + } + } + } + } + } + } + }, + "/api/v1/auth/token": { + "post": { + "description": "", + "parameters": [], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/api/v1/auth/login1": { + "post": { + "description": "", + "parameters": [], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "email": { + "example": "any" + }, + "clientPublicKey": { + "example": "any" + } + } + } + } + } + } + } + }, + "/api/v1/auth/login2": { + "post": { + "description": "", + "parameters": [], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "email": { + "example": "any" + }, + "clientProof": { + "example": "any" + } + } + } + } + } + } + } + }, + "/api/v1/auth/logout": { + "post": { + "description": "", + "parameters": [], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/api/v1/auth/checkAuth": { + "post": { + "description": "", + "parameters": [], + "responses": {} + } + }, + "/api/v1/bot/{workspaceId}": { + "get": { + "description": "", + "parameters": [ + { + "name": "workspaceId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/api/v1/bot/{botId}/active": { + "patch": { + "description": "", + "parameters": [ + { + "name": "botId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "isActive": { + "example": "any" + }, + "botKey": { + "example": "any" + } + } + } + } + } + } + } + }, + "/api/v1/user/": { + "get": { + "description": "", + "parameters": [], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/v1/user-action/": { + "post": { + "description": "", + "parameters": [], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "action": { + "example": "any" + } + } + } + } + } + } + }, + "get": { + "description": "", + "parameters": [ + { + "name": "action", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/api/v1/organization/": { + "get": { + "description": "", + "parameters": [], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + }, + "post": { + "description": "", + "parameters": [], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "organizationName": { + "example": "any" + } + } + } + } + } + } + } + }, + "/api/v1/organization/{organizationId}": { + "get": { + "description": "", + "parameters": [ + { + "name": "organizationId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/api/v1/organization/{organizationId}/users": { + "get": { + "description": "", + "parameters": [ + { + "name": "organizationId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/api/v1/organization/{organizationId}/my-workspaces": { + "get": { + "description": "", + "parameters": [ + { + "name": "organizationId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/api/v1/organization/{organizationId}/name": { + "patch": { + "description": "", + "parameters": [ + { + "name": "organizationId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "example": "any" + } + } + } + } + } + } + } + }, + "/api/v1/organization/{organizationId}/incidentContactOrg": { + "get": { + "description": "", + "parameters": [ + { + "name": "organizationId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + }, + "post": { + "description": "", + "parameters": [ + { + "name": "organizationId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "email": { + "example": "any" + } + } + } + } + } + } + }, + "delete": { + "description": "", + "parameters": [ + { + "name": "organizationId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "email": { + "example": "any" + } + } + } + } + } + } + } + }, + "/api/v1/organization/{organizationId}/customer-portal-session": { + "post": { + "description": "", + "parameters": [ + { + "name": "organizationId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/api/v1/organization/{organizationId}/subscriptions": { + "get": { + "description": "", + "parameters": [ + { + "name": "organizationId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/api/v1/workspace/{workspaceId}/keys": { + "get": { + "description": "", + "parameters": [ + { + "name": "workspaceId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/api/v1/workspace/{workspaceId}/users": { + "get": { + "description": "", + "parameters": [ + { + "name": "workspaceId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/api/v1/workspace/": { + "get": { + "description": "", + "parameters": [], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + }, + "post": { + "description": "", + "parameters": [], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "workspaceName": { + "example": "any" + }, + "organizationId": { + "example": "any" + } + } + } + } + } + } + } + }, + "/api/v1/workspace/{workspaceId}": { + "get": { + "description": "", + "parameters": [ + { + "name": "workspaceId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + }, + "delete": { + "description": "", + "parameters": [ + { + "name": "workspaceId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/api/v1/workspace/{workspaceId}/name": { + "post": { + "description": "", + "parameters": [ + { + "name": "workspaceId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "example": "any" + } + } + } + } + } + } + } + }, + "/api/v1/workspace/{workspaceId}/invite-signup": { + "post": { + "description": "", + "parameters": [ + { + "name": "workspaceId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "email": { + "example": "any" + } + } + } + } + } + } + } + }, + "/api/v1/workspace/{workspaceId}/integrations": { + "get": { + "description": "", + "parameters": [ + { + "name": "workspaceId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/api/v1/workspace/{workspaceId}/authorizations": { + "get": { + "description": "", + "parameters": [ + { + "name": "workspaceId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/api/v1/workspace/{workspaceId}/service-tokens": { + "get": { + "description": "", + "parameters": [ + { + "name": "workspaceId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/api/v1/membership-org/membershipOrg/{membershipOrgId}/change-role": { + "post": { + "description": "", + "parameters": [ + { + "name": "membershipOrgId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/v1/membership-org/{membershipOrgId}": { + "delete": { + "description": "", + "parameters": [ + { + "name": "membershipOrgId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "400": { + "description": "Bad Request" + } + } + } + }, + "/api/v1/membership/{workspaceId}/connect": { + "get": { + "description": "", + "parameters": [ + { + "name": "workspaceId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/api/v1/membership/{membershipId}": { + "delete": { + "description": "", + "parameters": [ + { + "name": "membershipId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/api/v1/membership/{membershipId}/change-role": { + "post": { + "description": "", + "parameters": [ + { + "name": "membershipId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "role": { + "example": "any" + } + } + } + } + } + } + } + }, + "/api/v1/key/{workspaceId}": { + "post": { + "description": "", + "parameters": [ + { + "name": "workspaceId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "key": { + "example": "any" + } + } + } + } + } + } + } + }, + "/api/v1/key/{workspaceId}/latest": { + "get": { + "description": "", + "parameters": [ + { + "name": "workspaceId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/api/v1/invite-org/signup": { + "post": { + "description": "", + "parameters": [], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "organizationId": { + "example": "any" + }, + "inviteeEmail": { + "example": "any" + } + } + } + } + } + } + } + }, + "/api/v1/invite-org/verify": { + "post": { + "description": "", + "parameters": [], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "email": { + "example": "any" + }, + "code": { + "example": "any" + } + } + } + } + } + } + } + }, + "/api/v1/secret/{workspaceId}": { + "post": { + "description": "", + "parameters": [ + { + "name": "workspaceId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "secrets": { + "example": "any" + }, + "keys": { + "example": "any" + }, + "environment": { + "example": "any" + }, + "channel": { + "example": "any" + } + } + } + } + } + } + }, + "get": { + "description": "", + "parameters": [ + { + "name": "workspaceId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "environment", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "channel", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/api/v1/secret/{workspaceId}/service-token": { + "get": { + "description": "", + "parameters": [ + { + "name": "workspaceId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "environment", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "channel", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/api/v1/service-token/": { + "get": { + "description": "", + "parameters": [], + "responses": { + "200": { + "description": "OK" + } + } + }, + "post": { + "description": "", + "parameters": [], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "example": "any" + }, + "workspaceId": { + "example": "any" + }, + "environment": { + "example": "any" + }, + "expiresIn": { + "example": "any" + }, + "publicKey": { + "example": "any" + }, + "encryptedKey": { + "example": "any" + }, + "nonce": { + "example": "any" + } + } + } + } + } + } + } + }, + "/api/v1/password/srp1": { + "post": { + "description": "", + "parameters": [], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "clientPublicKey": { + "example": "any" + } + } + } + } + } + } + } + }, + "/api/v1/password/change-password": { + "post": { + "description": "", + "parameters": [], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "clientProof": { + "example": "any" + }, + "encryptedPrivateKey": { + "example": "any" + }, + "iv": { + "example": "any" + }, + "tag": { + "example": "any" + }, + "salt": { + "example": "any" + }, + "verifier": { + "example": "any" + } + } + } + } + } + } + } + }, + "/api/v1/password/email/password-reset": { + "post": { + "description": "", + "parameters": [], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + }, + "403": { + "description": "Forbidden" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "email": { + "example": "any" + } + } + } + } + } + } + } + }, + "/api/v1/password/email/password-reset-verify": { + "post": { + "description": "", + "parameters": [], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + }, + "403": { + "description": "Forbidden" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "email": { + "example": "any" + }, + "code": { + "example": "any" + } + } + } + } + } + } + } + }, + "/api/v1/password/backup-private-key": { + "get": { + "description": "", + "parameters": [], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + }, + "post": { + "description": "", + "parameters": [], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "clientProof": { + "example": "any" + }, + "encryptedPrivateKey": { + "example": "any" + }, + "iv": { + "example": "any" + }, + "tag": { + "example": "any" + }, + "salt": { + "example": "any" + }, + "verifier": { + "example": "any" + } + } + } + } + } + } + } + }, + "/api/v1/password/password-reset": { + "post": { + "description": "", + "parameters": [], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "encryptedPrivateKey": { + "example": "any" + }, + "iv": { + "example": "any" + }, + "tag": { + "example": "any" + }, + "salt": { + "example": "any" + }, + "verifier": { + "example": "any" + } + } + } + } + } + } + } + }, + "/api/v1/stripe/webhook": { + "post": { + "description": "", + "parameters": [ + { + "name": "stripe-signature", + "in": "header", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/api/v1/integration/{integrationId}": { + "patch": { + "description": "", + "parameters": [ + { + "name": "integrationId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "app": { + "example": "any" + }, + "environment": { + "example": "any" + }, + "isActive": { + "example": "any" + }, + "target": { + "example": "any" + }, + "context": { + "example": "any" + }, + "siteId": { + "example": "any" + } + } + } + } + } + } + }, + "delete": { + "description": "", + "parameters": [ + { + "name": "integrationId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/api/v1/integration-auth/integration-options": { + "get": { + "description": "", + "parameters": [], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/v1/integration-auth/oauth-token": { + "post": { + "description": "", + "parameters": [], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "workspaceId": { + "example": "any" + }, + "code": { + "example": "any" + }, + "integration": { + "example": "any" + } + } + } + } + } + } + } + }, + "/api/v1/integration-auth/{integrationAuthId}/apps": { + "get": { + "description": "", + "parameters": [ + { + "name": "integrationAuthId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/api/v1/integration-auth/{integrationAuthId}": { + "delete": { + "description": "", + "parameters": [ + { + "name": "integrationAuthId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/api/v2/workspace/{workspaceId}/secrets": { + "post": { + "description": "", + "parameters": [ + { + "name": "workspaceId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "secrets": { + "example": "any" + }, + "keys": { + "example": "any" + }, + "environment": { + "example": "any" + }, + "channel": { + "example": "any" + } + } + } + } + } + } + }, + "get": { + "description": "", + "parameters": [ + { + "name": "workspaceId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "environment", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "channel", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/api/v2/workspace/{workspaceId}/encrypted-key": { + "get": { + "description": "", + "parameters": [ + { + "name": "workspaceId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/api/v2/workspace/{workspaceId}/service-token-data": { + "get": { + "description": "", + "parameters": [ + { + "name": "workspaceId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/api/v2/secret/batch-create/workspace/{workspaceId}/environment/{environmentName}": { + "post": { + "description": "", + "parameters": [ + { + "name": "workspaceId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "environmentName", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "secrets": { + "example": "any" + } + } + } + } + } + } + } + }, + "/api/v2/secret/workspace/{workspaceId}/environment/{environmentName}": { + "post": { + "description": "", + "parameters": [ + { + "name": "workspaceId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "environmentName", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "secret": { + "example": "any" + } + } + } + } + } + } + }, + "patch": { + "description": "", + "parameters": [ + { + "name": "workspaceId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "environmentName", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "secret": { + "example": "any" + } + } + } + } + } + } + } + }, + "/api/v2/secret/workspace/{workspaceId}": { + "get": { + "description": "", + "parameters": [ + { + "name": "workspaceId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "environment", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/v2/secret/{secretId}": { + "get": { + "description": "", + "parameters": [ + { + "name": "secretId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + }, + "delete": { + "description": "", + "parameters": [ + { + "name": "secretId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/v2/secret/batch/workspace/{workspaceId}/environment/{environmentName}": { + "delete": { + "description": "", + "parameters": [ + { + "name": "workspaceId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "environmentName", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "secretIds": { + "example": "any" + } + } + } + } + } + } + } + }, + "/api/v2/secret/batch-modify/workspace/{workspaceId}/environment/{environmentName}": { + "patch": { + "description": "", + "parameters": [ + { + "name": "workspaceId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "environmentName", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "secrets": { + "example": "any" + } + } + } + } + } + } + } + }, + "/api/v2/service-token/": { + "get": { + "description": "", + "parameters": [], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + }, + "post": { + "description": "", + "parameters": [], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "example": "any" + }, + "workspaceId": { + "example": "any" + }, + "environment": { + "example": "any" + }, + "encryptedKey": { + "example": "any" + }, + "iv": { + "example": "any" + }, + "tag": { + "example": "any" + }, + "expiresIn": { + "example": "any" + } + } + } + } + } + } + } + }, + "/api/v2/service-token/{serviceTokenDataId}": { + "delete": { + "description": "", + "parameters": [ + { + "name": "serviceTokenDataId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/api/v2/api-key-data/": { + "get": { + "description": "", + "parameters": [], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + }, + "post": { + "description": "", + "parameters": [], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "example": "any" + }, + "expiresIn": { + "example": "any" + } + } + } + } + } + } + } + }, + "/api/v2/api-key-data/{apiKeyDataId}": { + "delete": { + "description": "", + "parameters": [ + { + "name": "apiKeyDataId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/api/status": { + "get": { + "description": "", + "parameters": [], + "responses": { + "200": { + "description": "OK" + } + } + } + } + }, + "components": { + "securitySchemes": { + "bearerAuth": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT" + } + } + } +} \ No newline at end of file diff --git a/backend/package-lock.json b/backend/package-lock.json index a9bb83e6b..b51aa7cf0 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -37,6 +37,8 @@ "query-string": "^7.1.3", "rimraf": "^3.0.2", "stripe": "^10.7.0", + "swagger-autogen": "^2.22.0", + "swagger-ui-express": "^4.6.0", "tweetnacl": "^1.0.3", "tweetnacl-util": "^0.15.1", "typescript": "^4.9.3", @@ -4549,7 +4551,6 @@ "version": "4.2.2", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -6690,7 +6691,6 @@ "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, "bin": { "json5": "lib/cli.js" }, @@ -11174,6 +11174,47 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swagger-autogen": { + "version": "2.22.0", + "resolved": "https://registry.npmjs.org/swagger-autogen/-/swagger-autogen-2.22.0.tgz", + "integrity": "sha512-MPdtwgx/RL3og0RjFVV9hPoQv3x+c3ZRhS0Vjp9k94DLV7iUgIuCg8H+uAT8oD5w48ATTRT1VjcOHlCGH62pdA==", + "dependencies": { + "acorn": "^7.4.1", + "deepmerge": "^4.2.2", + "glob": "^7.1.7", + "json5": "^2.2.1" + } + }, + "node_modules/swagger-autogen/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/swagger-ui-dist": { + "version": "4.15.5", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-4.15.5.tgz", + "integrity": "sha512-V3eIa28lwB6gg7/wfNvAbjwJYmDXy1Jo1POjyTzlB6wPcHiGlRxq39TSjYGVjQrUSAzpv+a7nzp7mDxgNy57xA==" + }, + "node_modules/swagger-ui-express": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.6.0.tgz", + "integrity": "sha512-ZxpQFp1JR2RF8Ar++CyJzEDdvufa08ujNUJgMVTMWPi86CuQeVdBtvaeO/ysrz6dJAYXf9kbVNhWD7JWocwqsA==", + "dependencies": { + "swagger-ui-dist": ">=4.11.0" + }, + "engines": { + "node": ">= v0.10.32" + }, + "peerDependencies": { + "express": ">=4.0.0" + } + }, "node_modules/tar": { "version": "6.1.13", "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.13.tgz", @@ -15582,8 +15623,7 @@ "deepmerge": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", - "dev": true + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==" }, "delayed-stream": { "version": "1.0.0", @@ -17200,8 +17240,7 @@ "json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==" }, "jsonwebtoken": { "version": "9.0.0", @@ -20424,6 +20463,37 @@ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true }, + "swagger-autogen": { + "version": "2.22.0", + "resolved": "https://registry.npmjs.org/swagger-autogen/-/swagger-autogen-2.22.0.tgz", + "integrity": "sha512-MPdtwgx/RL3og0RjFVV9hPoQv3x+c3ZRhS0Vjp9k94DLV7iUgIuCg8H+uAT8oD5w48ATTRT1VjcOHlCGH62pdA==", + "requires": { + "acorn": "^7.4.1", + "deepmerge": "^4.2.2", + "glob": "^7.1.7", + "json5": "^2.2.1" + }, + "dependencies": { + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==" + } + } + }, + "swagger-ui-dist": { + "version": "4.15.5", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-4.15.5.tgz", + "integrity": "sha512-V3eIa28lwB6gg7/wfNvAbjwJYmDXy1Jo1POjyTzlB6wPcHiGlRxq39TSjYGVjQrUSAzpv+a7nzp7mDxgNy57xA==" + }, + "swagger-ui-express": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.6.0.tgz", + "integrity": "sha512-ZxpQFp1JR2RF8Ar++CyJzEDdvufa08ujNUJgMVTMWPi86CuQeVdBtvaeO/ysrz6dJAYXf9kbVNhWD7JWocwqsA==", + "requires": { + "swagger-ui-dist": ">=4.11.0" + } + }, "tar": { "version": "6.1.13", "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.13.tgz", diff --git a/backend/package.json b/backend/package.json index 56009e3e9..08f5a815c 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,46 +1,11 @@ { - "dependencies": { - "@godaddy/terminus": "^4.11.2", - "@octokit/rest": "^19.0.5", - "@sentry/node": "^7.14.0", - "@sentry/tracing": "^7.19.0", - "@types/crypto-js": "^4.1.1", - "@types/libsodium-wrappers": "^0.7.10", - "await-to-js": "^3.0.0", - "axios": "^1.1.3", - "bcrypt": "^5.1.0", - "bigint-conversion": "^2.2.2", - "cookie-parser": "^1.4.6", - "cors": "^2.8.5", - "crypto-js": "^4.1.1", - "dotenv": "^16.0.1", - "express": "^4.18.1", - "express-rate-limit": "^6.7.0", - "express-validator": "^6.14.2", - "handlebars": "^4.7.7", - "helmet": "^5.1.1", - "jsonwebtoken": "^9.0.0", - "jsrp": "^0.2.4", - "libsodium-wrappers": "^0.7.10", - "mongoose": "^6.7.2", - "nodemailer": "^6.8.0", - "posthog-node": "^2.2.2", - "query-string": "^7.1.3", - "rimraf": "^3.0.2", - "stripe": "^10.7.0", - "tweetnacl": "^1.0.3", - "tweetnacl-util": "^0.15.1", - "typescript": "^4.9.3", - "utility-types": "^3.10.0", - "winston": "^3.8.2", - "winston-loki": "^6.0.6" - }, "name": "infisical-api", "version": "1.0.0", "main": "src/index.js", "scripts": { "start": "npm run build && node build/index.js", "dev": "nodemon", + "swagger-autogen": "node ./swagger.ts", "build": "rimraf ./build && tsc && cp -R ./src/templates ./build", "lint": "eslint . --ext .ts", "lint-and-fix": "eslint . --ext .ts --fix", @@ -108,5 +73,43 @@ "suiteNameTemplate": "{filepath}", "classNameTemplate": "{classname}", "titleTemplate": "{title}" + }, + "dependencies": { + "@godaddy/terminus": "^4.11.2", + "@octokit/rest": "^19.0.5", + "@sentry/node": "^7.14.0", + "@sentry/tracing": "^7.19.0", + "@types/crypto-js": "^4.1.1", + "@types/libsodium-wrappers": "^0.7.10", + "await-to-js": "^3.0.0", + "axios": "^1.1.3", + "bcrypt": "^5.1.0", + "bigint-conversion": "^2.2.2", + "cookie-parser": "^1.4.6", + "cors": "^2.8.5", + "crypto-js": "^4.1.1", + "dotenv": "^16.0.1", + "express": "^4.18.1", + "express-rate-limit": "^6.7.0", + "express-validator": "^6.14.2", + "handlebars": "^4.7.7", + "helmet": "^5.1.1", + "jsonwebtoken": "^9.0.0", + "jsrp": "^0.2.4", + "libsodium-wrappers": "^0.7.10", + "mongoose": "^6.7.2", + "nodemailer": "^6.8.0", + "posthog-node": "^2.2.2", + "query-string": "^7.1.3", + "rimraf": "^3.0.2", + "stripe": "^10.7.0", + "swagger-autogen": "^2.22.0", + "swagger-ui-express": "^4.6.0", + "tweetnacl": "^1.0.3", + "tweetnacl-util": "^0.15.1", + "typescript": "^4.9.3", + "utility-types": "^3.10.0", + "winston": "^3.8.2", + "winston-loki": "^6.0.6" } } diff --git a/backend/src/app.ts b/backend/src/app.ts index 06f0c7a0b..aa7ac9b28 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -6,6 +6,10 @@ import helmet from 'helmet'; import cors from 'cors'; import cookieParser from 'cookie-parser'; import dotenv from 'dotenv'; +import swaggerUi = require('swagger-ui-express'); +// eslint-disable-next-line @typescript-eslint/no-var-requires +const swaggerFile = require('../api-documentation.json') + dotenv.config(); import { PORT, NODE_ENV, SITE_URL } from './config'; @@ -105,6 +109,8 @@ app.use('/api/v2/secrets', v2SecretsRouter); app.use('/api/v2/service-token', v2ServiceTokenDataRouter); // TODO: turn into plural route app.use('/api/v2/api-key-data', v2APIKeyDataRouter); +// api docs +app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerFile)) // Server status app.use('/api', healthCheck) diff --git a/backend/swagger.ts b/backend/swagger.ts new file mode 100644 index 000000000..0505bb813 --- /dev/null +++ b/backend/swagger.ts @@ -0,0 +1,22 @@ +// eslint-disable-next-line @typescript-eslint/no-var-requires +const swaggerAutogen = require('swagger-autogen')({ openapi: '3.0.0' }); + +const doc = { + info: { + title: 'Infisical API', + description: 'List of all available APIs that can be consumed', + }, + host: ['https://infisical.com'], + securityDefinitions: { + bearerAuth: { + type: 'http', + scheme: 'bearer', + bearerFormat: 'JWT' + } + } +}; + +const outputFile = './api-documentation.json'; +const endpointsFiles = ['./src/app.ts']; + +swaggerAutogen(outputFile, endpointsFiles, doc); \ No newline at end of file diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 8ad4ae956..014d858c4 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -28,6 +28,8 @@ services: - ./backend/src:/app/src - ./backend/nodemon.json:/app/nodemon.json - /app/node_modules + - ./backend/api-documentation.json:/app/api-documentation.json + - ./backend/swagger.ts:/app/swagger.ts command: npm run dev env_file: .env environment: diff --git a/docs/mint.json b/docs/mint.json index 05ae42747..d4146ff9b 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -39,6 +39,11 @@ "icon": "server", "url": "self-hosting" }, + { + "name": "API Reference", + "icon": "cloud", + "url": "self-hosting" + }, { "name": "Integrations", "icon": "plug", diff --git a/frontend/components/basic/Error.tsx b/frontend/components/basic/Error.tsx index 79cc6e5f1..b5f0b7a4c 100644 --- a/frontend/components/basic/Error.tsx +++ b/frontend/components/basic/Error.tsx @@ -4,13 +4,13 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; export default function Error({ text }: { text: string }): JSX.Element { return ( -
+
{text && ( -

{text}

+

{text}

)}
); diff --git a/frontend/components/basic/EventFilter.tsx b/frontend/components/basic/EventFilter.tsx index c9b31fd43..dc5ffe109 100644 --- a/frontend/components/basic/EventFilter.tsx +++ b/frontend/components/basic/EventFilter.tsx @@ -55,7 +55,7 @@ export default function EventFilter({ {selected != '' ? (

{t("activity:event." + selected)}

) : ( -

Select an event

+

{String(t("common:select-event"))}

)} {selected != '' ? ( )}
diff --git a/frontend/components/basic/dialog/DeleteEnvVar.tsx b/frontend/components/basic/dialog/DeleteEnvVar.tsx new file mode 100644 index 000000000..4bb584039 --- /dev/null +++ b/frontend/components/basic/dialog/DeleteEnvVar.tsx @@ -0,0 +1,77 @@ +import { Fragment } from "react"; +import { useTranslation } from "react-i18next"; +import { Dialog, Transition } from "@headlessui/react"; + +// #TODO: USE THIS. Currently it's not. Kinda complicated to set up because of state. + +type Props = { + isOpen: boolean + onClose: () => void + onSubmit: () => void +} + +export const DeleteEnvVar = ({ isOpen, onClose, onSubmit }: Props) => { + const { t } = useTranslation() + return ( +
+ + {}}> +
+ +
+ +
+ + + + {t('dashboard:sidebar.delete-key-dialog.title')} + +
+

+ {t('dashboard:sidebar.delete-key-dialog.confirm-delete-message')} +

+
+
+ + +
+
+
+
+
+
+
+
+ ); +}; diff --git a/frontend/components/basic/table/UserTable.js b/frontend/components/basic/table/UserTable.js index 449d23cc6..677b0d978 100644 --- a/frontend/components/basic/table/UserTable.js +++ b/frontend/components/basic/table/UserTable.js @@ -117,13 +117,13 @@ const UserTable = ({ return (
-
- - +
+
+ - - - + + + diff --git a/frontend/components/dashboard/DeleteActionButton.tsx b/frontend/components/dashboard/DeleteActionButton.tsx new file mode 100644 index 000000000..db595ff03 --- /dev/null +++ b/frontend/components/dashboard/DeleteActionButton.tsx @@ -0,0 +1,33 @@ +import React, { useState } from 'react' +import { useTranslation } from 'react-i18next'; + +import Button from '../basic/buttons/Button'; +import { DeleteEnvVar } from '../basic/dialog/DeleteEnvVar'; + +type Props = { + onSubmit: () => void +} + +export function DeleteActionButton({ onSubmit }: Props) { + const { t } = useTranslation(); + const [open, setOpen] = useState(false) + + return ( +
+
+ ) +} diff --git a/frontend/components/dashboard/DropZone.tsx b/frontend/components/dashboard/DropZone.tsx index 66be964cc..ffde34566 100644 --- a/frontend/components/dashboard/DropZone.tsx +++ b/frontend/components/dashboard/DropZone.tsx @@ -3,10 +3,11 @@ import Image from "next/image"; import { useTranslation } from "next-i18next"; import { faUpload } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { parseDocument, Scalar, YAMLMap } from 'yaml'; import Button from "../basic/buttons/Button"; import Error from "../basic/Error"; -import parse from "../utilities/file"; +import { parseDotEnv } from '../utilities/parseDotEnv'; import guidGenerator from "../utilities/randomId"; interface DropZoneProps { @@ -51,6 +52,53 @@ const DropZone = ({ const [loading, setLoading] = useState(false); + const getSecrets = (file: ArrayBuffer, fileType: string) => { + let secrets; + switch (fileType) { + case 'env': { + const keyPairs = parseDotEnv(file); + secrets = Object.keys(keyPairs).map((key, index) => { + return { + id: guidGenerator(), + pos: numCurrentRows + index, + key: key, + value: keyPairs[key as keyof typeof keyPairs].value, + comment: keyPairs[key as keyof typeof keyPairs].comments.join('\n'), + type: 'shared', + }; + }); + break; + } + case 'yml': { + const parsedFile = parseDocument(file.toString()); + const keyPairs = parsedFile.contents!.toJSON(); + + secrets = Object.keys(keyPairs).map((key, index) => { + const fileContent = parsedFile.contents as YAMLMap; + const comment = + fileContent!.items + .find((item) => item.key.value === key) + ?.key?.commentBefore?.split('\n') + .map((comment) => comment.trim()) + .join('\n') ?? ''; + return { + id: guidGenerator(), + pos: numCurrentRows + index, + key: key, + value: keyPairs[key as keyof typeof keyPairs]?.toString() ?? '', + comment, + type: 'shared', + }; + }); + break; + } + default: + secrets = ''; + break; + } + return secrets; + }; + // This function function immediately parses the file after it is dropped const handleDrop = async (e: DragEvent) => { setLoading(true); @@ -61,20 +109,12 @@ const DropZone = ({ const file = e.dataTransfer.files[0]; const reader = new FileReader(); + const fileType = file.name.split('.')[1]; reader.onload = (event) => { if (event.target === null || event.target.result === null) return; // parse function's argument looks like to be ArrayBuffer - const keyPairs = parse(event.target.result as Buffer); - const newData = Object.keys(keyPairs).map((key, index) => { - return { - id: guidGenerator(), - pos: numCurrentRows + index, - key: key, - value: keyPairs[key as keyof typeof keyPairs], - type: "shared", - }; - }); + const newData = getSecrets(event.target.result as ArrayBuffer, fileType); setData(newData); setButtonReady(true); }; @@ -95,25 +135,14 @@ const DropZone = ({ setTimeout(() => setLoading(false), 5000); if (e.currentTarget.files === null) return; const file = e.currentTarget.files[0]; + const fileType = file.name.split('.')[1]; const reader = new FileReader(); reader.onload = (event) => { if (event.target === null || event.target.result === null) return; const { result } = event.target; - if (typeof result === "string") { - const newData = result - .split("\n") - .map((line: string, index: number) => { - return { - id: guidGenerator(), - pos: numCurrentRows + index, - key: line.split("=")[0], - value: line.split("=").slice(1, line.split("=").length).join("="), - type: "shared", - }; - }); - setData(newData); - setButtonReady(true); - } + const newData = getSecrets(result as ArrayBuffer, fileType); + setData(newData); + setButtonReady(true); }; reader.readAsText(file); }; @@ -139,7 +168,7 @@ const DropZone = ({ id="fileSelect" type="file" className="opacity-0 absolute w-full h-full" - accept=".txt,.env" + accept=".txt,.env,.yml" onChange={handleFileSelect} /> {errorDragAndDrop ? ( @@ -176,7 +205,7 @@ const DropZone = ({ id="fileSelect" type="file" className="opacity-0 absolute w-full h-full" - accept=".txt,.env" + accept=".txt,.env,.yml" onChange={handleFileSelect} />
@@ -187,7 +216,7 @@ const DropZone = ({
+ deleteRow({ ids: overrideEnabled ? data.map(secret => secret.id) : [data.filter(secret => secret.type == "shared")[0]?.id], secretName: data[0]?.key })} + />
}; diff --git a/frontend/components/navigation/NavBarDashboard.tsx b/frontend/components/navigation/NavBarDashboard.tsx index 824614819..b2752f3ed 100644 --- a/frontend/components/navigation/NavBarDashboard.tsx +++ b/frontend/components/navigation/NavBarDashboard.tsx @@ -13,6 +13,7 @@ import { faGear, faPlus, faRightFromBracket, + faUpRightFromSquare, } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Menu, Transition } from "@headlessui/react"; @@ -107,6 +108,14 @@ export default function Navbar() {
+ + Docs + +
diff --git a/frontend/components/signup/CodeInputStep.tsx b/frontend/components/signup/CodeInputStep.tsx new file mode 100644 index 000000000..05c4973a8 --- /dev/null +++ b/frontend/components/signup/CodeInputStep.tsx @@ -0,0 +1,136 @@ +import React, { useState } from "react"; +import ReactCodeInput from "react-code-input"; +import { useTranslation } from "next-i18next"; + +import sendVerificationEmail from "~/pages/api/auth/SendVerificationEmail"; + +import Button from "../basic/buttons/Button"; +import Error from "../basic/Error"; + + +// The style for the verification code input +const props = { + inputStyle: { + fontFamily: "monospace", + margin: "4px", + MozAppearance: "textfield", + width: "55px", + borderRadius: "5px", + fontSize: "24px", + height: "55px", + paddingLeft: "7", + backgroundColor: "#0d1117", + color: "white", + border: "1px solid #2d2f33", + textAlign: "center", + outlineColor: "#8ca542", + borderColor: "#2d2f33" + }, +} as const; +const propsPhone = { + inputStyle: { + fontFamily: "monospace", + margin: "4px", + MozAppearance: "textfield", + width: "40px", + borderRadius: "5px", + fontSize: "24px", + height: "40px", + paddingLeft: "7", + backgroundColor: "#0d1117", + color: "white", + border: "1px solid #2d2f33", + textAlign: "center", + outlineColor: "#8ca542", + borderColor: "#2d2f33" + }, +} as const; + +interface CodeInputStepProps { + email: string; + incrementStep: () => void; + setCode: (value: string) => void; + codeError: boolean; +} + +/** + * This is the second step of sign up where users need to verify their email + * @param {object} obj + * @param {string} obj.email - user's email to which we just sent a verification email + * @param {function} obj.incrementStep - goes to the next step of signup + * @param {function} obj.setCode - state updating function that set the current value of the emai verification code + * @param {boolean} obj.codeError - whether the code was inputted wrong or now + * @returns + */ +export default function CodeInputStep({ email, incrementStep, setCode, codeError }: CodeInputStepProps): JSX.Element { + const [isLoading, setIsLoading] = useState(false); + const [isResendingVerificationEmail, setIsResendingVerificationEmail] = + useState(false); + const { t } = useTranslation(); + + const resendVerificationEmail = async () => { + setIsResendingVerificationEmail(true); + setIsLoading(true); + sendVerificationEmail(email); + setTimeout(() => { + setIsLoading(false); + setIsResendingVerificationEmail(false); + }, 2000); + }; + + return ( +
+

+ {"We've"} sent a verification email to{" "} +

+

+ {email}{" "} +

+
+ +
+
+ +
+ {codeError && } +
+
+
+
+ + Not seeing an email? + + + + +
+

+ {t("signup:step2-spam-alert")} +

+
+
+ ); +} diff --git a/frontend/components/signup/DonwloadBackupPDFStep.tsx b/frontend/components/signup/DonwloadBackupPDFStep.tsx new file mode 100644 index 000000000..786c49fd2 --- /dev/null +++ b/frontend/components/signup/DonwloadBackupPDFStep.tsx @@ -0,0 +1,60 @@ +import React from "react"; +import { useTranslation } from "next-i18next"; +import { faWarning } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +import Button from "../basic/buttons/Button"; +import issueBackupKey from "../utilities/cryptography/issueBackupKey"; + + +interface DownloadBackupPDFStepProps { + incrementStep: () => void; + email: string; + password: string; + name: string; +} + +/** + * This is the step of the signup flow where the user downloads the backup pdf + * @param {object} obj + * @param {function} obj.incrementStep - function that moves the user on to the next stage of signup + * @param {string} obj.email - user's email + * @param {string} obj.password - user's password + * @param {string} obj.name - user's name + * @returns + */ +export default function DonwloadBackupPDFStep({ incrementStep, email, password, name }: DownloadBackupPDFStepProps): JSX.Element { + const { t } = useTranslation(); + + return ( +
+

+ {t("signup:step4-message")} +

+
+
{t("signup:step4-description1")}
+
{t("signup:step4-description2")}
+
+
+ + {t("signup:step4-description3")} +
+
+
+
+ ); +} diff --git a/frontend/components/signup/EnterEmailStep.tsx b/frontend/components/signup/EnterEmailStep.tsx new file mode 100644 index 000000000..5f98710b7 --- /dev/null +++ b/frontend/components/signup/EnterEmailStep.tsx @@ -0,0 +1,97 @@ +import React, { useState } from "react"; +import Link from "next/link"; +import { useTranslation } from "next-i18next"; + +import sendVerificationEmail from "~/pages/api/auth/SendVerificationEmail"; + +import Button from "../basic/buttons/Button"; +import InputField from "../basic/InputField"; + + +interface DownloadBackupPDFStepProps { + incrementStep: () => void; + email: string; + setEmail: (value: string) => void; +} + +/** + * This is the first step of the sign up process - users need to enter their email + * @param {object} obj + * @param {string} obj.email - email of a user signing up + * @param {function} obj.setEmail - funciton that manages the state of the email variable + * @param {function} obj.incrementStep - function to go to the next step of the signup flow + * @returns + */ +export default function EnterEmailStep({ email, setEmail, incrementStep }: DownloadBackupPDFStepProps): JSX.Element { + const [emailError, setEmailError] = useState(false); + const [emailErrorMessage, setEmailErrorMessage] = useState(""); + const { t } = useTranslation(); + + /** + * Verifies if the entered email "looks" correct + */ + const emailCheck = () => { + let emailCheckBool = false; + if (!email) { + setEmailError(true); + setEmailErrorMessage("Please enter your email."); + emailCheckBool = true; + } else if ( + !email.includes("@") || + !email.includes(".") || + !/[a-z]/.test(email) + ) { + setEmailError(true); + setEmailErrorMessage("Please enter a valid email."); + emailCheckBool = true; + } else { + setEmailError(false); + } + + // If everything is correct, go to the next step + if (!emailCheckBool) { + sendVerificationEmail(email); + incrementStep(); + } + }; + + return ( +
+
+

+ {'Let\''}s get started +

+
+ +
+
+

+ {t("signup:step1-privacy")} +

+
+
+
+
+
+ + + +
+
+ ); +} diff --git a/frontend/components/signup/TeamInviteStep.tsx b/frontend/components/signup/TeamInviteStep.tsx new file mode 100644 index 000000000..e2639b88f --- /dev/null +++ b/frontend/components/signup/TeamInviteStep.tsx @@ -0,0 +1,72 @@ +import React, { useState } from "react"; +import { useRouter } from "next/router"; +import { useTranslation } from "next-i18next"; + +import addUserToOrg from "~/pages/api/organization/addUserToOrg"; +import getWorkspaces from "~/pages/api/workspace/getWorkspaces"; + +import Button from "../basic/buttons/Button"; + + +/** + * This is the last step of the signup flow. People can optionally invite their teammates here. + */ +export default function TeamInviteStep(): JSX.Element { + const [emails, setEmails] = useState(""); + const { t } = useTranslation(); + const router = useRouter(); + + // Redirect user to the getting started page + const redirectToHome = async () => { + const userWorkspaces = await getWorkspaces(); + const userWorkspace = userWorkspaces[0]._id; + router.push("/home/" + userWorkspace); + + } + + const inviteUsers = async ({ emails }: { emails: string; }) => { + emails + .split(',') + .map(email => email.trim()) + .map(async (email) => await addUserToOrg(email, String(localStorage.getItem('orgData.id')))); + + await redirectToHome(); + } + + return ( +
+

+ {t("signup:step5-invite-team")} +

+

+ {t("signup:step5-subtitle")} +

+
+
+
+ +
+
+
First NameLast NameEmailFIRST NAMELAST NAMEEMAIL