From 9ff0b7bc1885e2d05e64bda2947903154488cb26 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Wed, 11 Jan 2023 10:03:40 +0700 Subject: [PATCH 1/5] Minor changes to README --- README.md | 10 +- .../endpoints/secrets/create.mdx | 4 + .../endpoints/secrets/delete.mdx | 4 + docs/api-reference/endpoints/secrets/read.mdx | 4 + .../endpoints/secrets/update.mdx | 4 + .../api-reference/overview/authentication.mdx | 3 + docs/api-reference/overview/introduction.mdx | 3 + docs/mint.json | 26 + docs/spec.yaml | 1690 +++++++++++++++++ 9 files changed, 1743 insertions(+), 5 deletions(-) create mode 100644 docs/api-reference/endpoints/secrets/create.mdx create mode 100644 docs/api-reference/endpoints/secrets/delete.mdx create mode 100644 docs/api-reference/endpoints/secrets/read.mdx create mode 100644 docs/api-reference/endpoints/secrets/update.mdx create mode 100644 docs/api-reference/overview/authentication.mdx create mode 100644 docs/api-reference/overview/introduction.mdx create mode 100644 docs/spec.yaml diff --git a/README.md b/README.md index aaafca43a..0312669b7 100644 --- a/README.md +++ b/README.md @@ -42,9 +42,9 @@ - **Navigate Multiple Environments** per project (e.g. development, staging, production, etc.) - **Personal overrides** for environment variables - **[Integrations](https://infisical.com/docs/integrations/overview)** with CI/CD and production infrastructure -- **[Secret Versioning](https://infisical.com/docs/getting-started/dashboard/versioning)** - check the history of change for any secret -- **[Activity Logs](https://infisical.com/docs/getting-started/dashboard/audit-logs)** - check what user in the project is performing what actions with secrets -- **[Point-in-time Secrets Recovery](https://infisical.com/docs/getting-started/dashboard/pit-recovery)** - roll back to any snapshot of you secrets +- **[Secret Versioning](https://infisical.com/docs/getting-started/dashboard/versioning)** to view the change history for any secret +- **[Activity Logs](https://infisical.com/docs/getting-started/dashboard/audit-logs)** to record every action taken in a project. +- **[Point-in-time Secrets Recovery](https://infisical.com/docs/getting-started/dashboard/pit-recovery)** for rolling back to any snapshot of your secrets - 🔜 **1-Click Deploy** to Digital Ocean and Heroku - 🔜 **Authentication/Authorization** for projects (read/write controls soon) - 🔜 **Automatic Secret Rotation** @@ -337,6 +337,6 @@ Infisical officially launched as v.1.0 on November 21st, 2022. There are a lot o ## 🌎 Translations -Infisical is currently aviable in English and Korean. Help us translate Infisical to your language! +Infisical is currently aviable in English and Korean. Help us translate Infisical to your language! -You can find all the info in [this issue](https://github.com/Infisical/infisical/issues/181). \ No newline at end of file +You can find all the info in [this issue](https://github.com/Infisical/infisical/issues/181). diff --git a/docs/api-reference/endpoints/secrets/create.mdx b/docs/api-reference/endpoints/secrets/create.mdx new file mode 100644 index 000000000..27c167f76 --- /dev/null +++ b/docs/api-reference/endpoints/secrets/create.mdx @@ -0,0 +1,4 @@ +--- +title: "Create" +openapi: "POST /api/v2/secrets/" +--- diff --git a/docs/api-reference/endpoints/secrets/delete.mdx b/docs/api-reference/endpoints/secrets/delete.mdx new file mode 100644 index 000000000..af87e7120 --- /dev/null +++ b/docs/api-reference/endpoints/secrets/delete.mdx @@ -0,0 +1,4 @@ +--- +title: "Delete" +openapi: "DELETE /api/v2/secrets/" +--- diff --git a/docs/api-reference/endpoints/secrets/read.mdx b/docs/api-reference/endpoints/secrets/read.mdx new file mode 100644 index 000000000..4305f192c --- /dev/null +++ b/docs/api-reference/endpoints/secrets/read.mdx @@ -0,0 +1,4 @@ +--- +title: "Read" +openapi: "GET /api/v2/secrets/" +--- diff --git a/docs/api-reference/endpoints/secrets/update.mdx b/docs/api-reference/endpoints/secrets/update.mdx new file mode 100644 index 000000000..2193fccc3 --- /dev/null +++ b/docs/api-reference/endpoints/secrets/update.mdx @@ -0,0 +1,4 @@ +--- +title: "Update" +openapi: "PATCH /api/v2/secrets/" +--- diff --git a/docs/api-reference/overview/authentication.mdx b/docs/api-reference/overview/authentication.mdx new file mode 100644 index 000000000..27a2dc134 --- /dev/null +++ b/docs/api-reference/overview/authentication.mdx @@ -0,0 +1,3 @@ +--- +title: "Authentication" +--- diff --git a/docs/api-reference/overview/introduction.mdx b/docs/api-reference/overview/introduction.mdx new file mode 100644 index 000000000..9632e3788 --- /dev/null +++ b/docs/api-reference/overview/introduction.mdx @@ -0,0 +1,3 @@ +--- +title: "Introduction" +--- diff --git a/docs/mint.json b/docs/mint.json index 4b83b5c17..363b27a3e 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -39,6 +39,11 @@ "icon": "server", "url": "self-hosting" }, + { + "name": "API Reference", + "icon": "cloud", + "url": "api-reference" + }, { "name": "Integrations", "icon": "plug", @@ -125,6 +130,27 @@ "self-hosting/configuration/email" ] }, + { + "group": "Overview", + "pages": [ + "api-reference/overview/introduction", + "api-reference/overview/authentication" + ] + }, + { + "group": "Endpoints", + "pages": [ + { + "group": "Secrets", + "pages": [ + "api-reference/endpoints/secrets/create", + "api-reference/endpoints/secrets/read", + "api-reference/endpoints/secrets/update", + "api-reference/endpoints/secrets/delete" + ] + } + ] + }, { "group": "Integrations", "pages": ["integrations/overview"] diff --git a/docs/spec.yaml b/docs/spec.yaml new file mode 100644 index 000000000..a0271395b --- /dev/null +++ b/docs/spec.yaml @@ -0,0 +1,1690 @@ +openapi: 3.0.0 +info: + title: Infisical API + description: List of all available APIs that can be consumed + version: 1.0.0 +servers: + - url: http://localhost:8080 +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/{secretId}/secret-versions/rollback: + post: + description: '' + parameters: + - name: secretId + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + '400': + description: Bad Request + requestBody: + content: + application/json: + schema: + type: object + properties: + version: + example: any + /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}/secret-snapshots/rollback: + 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: + version: + example: any + /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: + '200': + description: OK + /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/{environment}: + post: + description: '' + parameters: + - name: workspaceId + in: path + required: true + schema: + type: string + - name: environment + 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/{environment}: + post: + description: '' + parameters: + - name: workspaceId + in: path + required: true + schema: + type: string + - name: environment + 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/secret/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: + secret: + example: any + /api/v2/secrets/: + post: + description: '' + parameters: [] + responses: + '200': + description: OK + requestBody: + content: + application/json: + schema: + type: object + properties: + secrets: + example: any + workspaceId: + example: any + environment: + example: any + get: + description: '' + parameters: + - name: workspaceId + in: query + schema: + type: string + - name: environment + in: query + schema: + type: string + responses: + '200': + description: OK + patch: + description: '' + parameters: [] + responses: + '200': + description: OK + requestBody: + content: + application/json: + schema: + type: object + properties: + secrets: + example: any + delete: + description: '' + parameters: [] + responses: + '200': + description: OK + /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: + schemas: + secret: + type: object + properties: + type: + type: string + example: object + properties: + type: object + properties: + test: + type: object + properties: + type: + type: string + example: integer + description: + type: string + example: '123' + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT From 9d4ea2dcda6e6c399e1a743de974c570ec2cc386 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Fri, 13 Jan 2023 15:04:46 +0700 Subject: [PATCH 2/5] Continue api-reference docs --- backend/package-lock.json | 9 +- backend/package.json | 3 +- backend/{api-documentation.json => spec.json} | 651 ++++++++++++++++-- backend/src/app.ts | 6 +- backend/src/controllers/v1/authController.ts | 5 +- backend/src/controllers/v2/index.ts | 2 + .../src/controllers/v2/secretsController.ts | 171 +++++ backend/src/controllers/v2/usersController.ts | 44 ++ .../src/controllers/v2/workspaceController.ts | 28 + backend/src/routes/v2/index.ts | 2 + backend/src/routes/v2/users.ts | 16 + backend/swagger.ts | 22 - backend/swagger/index.ts | 116 ++++ backend/swagger/schemas/index.ts | 6 + backend/swagger/schemas/secretSchema.ts | 11 + docs/api-reference/endpoints/secrets/read.mdx | 2 +- docs/api-reference/endpoints/users/me.mdx | 4 + .../endpoints/workspaces/workspace-key.mdx | 4 + .../api-reference/overview/authentication.mdx | 8 + .../overview/examples/create-secrets.mdx | 64 ++ .../overview/examples/retrieve-secrets.mdx | 10 + .../overview/examples/update-secrets.mdx | 10 + docs/api-reference/overview/introduction.mdx | 16 + docs/api-reference/overview/usage.mdx | 18 + docs/mint.json | 32 +- docs/spec.yaml | 328 ++++++++- 26 files changed, 1466 insertions(+), 122 deletions(-) rename backend/{api-documentation.json => spec.json} (79%) create mode 100644 backend/src/controllers/v2/usersController.ts create mode 100644 backend/src/routes/v2/users.ts delete mode 100644 backend/swagger.ts create mode 100644 backend/swagger/index.ts create mode 100644 backend/swagger/schemas/index.ts create mode 100644 backend/swagger/schemas/secretSchema.ts create mode 100644 docs/api-reference/endpoints/users/me.mdx create mode 100644 docs/api-reference/endpoints/workspaces/workspace-key.mdx create mode 100644 docs/api-reference/overview/examples/create-secrets.mdx create mode 100644 docs/api-reference/overview/examples/retrieve-secrets.mdx create mode 100644 docs/api-reference/overview/examples/update-secrets.mdx create mode 100644 docs/api-reference/overview/usage.mdx diff --git a/backend/package-lock.json b/backend/package-lock.json index b51aa7cf0..38c1126cc 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -28,6 +28,7 @@ "express-validator": "^6.14.2", "handlebars": "^4.7.7", "helmet": "^5.1.1", + "js-yaml": "^4.1.0", "jsonwebtoken": "^9.0.0", "jsrp": "^0.2.4", "libsodium-wrappers": "^0.7.10", @@ -3698,8 +3699,7 @@ "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "node_modules/array-flatten": { "version": "1.1.1", @@ -6638,7 +6638,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, "dependencies": { "argparse": "^2.0.1" }, @@ -14980,8 +14979,7 @@ "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "array-flatten": { "version": "1.1.1", @@ -17197,7 +17195,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, "requires": { "argparse": "^2.0.1" } diff --git a/backend/package.json b/backend/package.json index 08f5a815c..b9359f680 100644 --- a/backend/package.json +++ b/backend/package.json @@ -5,7 +5,7 @@ "scripts": { "start": "npm run build && node build/index.js", "dev": "nodemon", - "swagger-autogen": "node ./swagger.ts", + "swagger-autogen": "node ./swagger/index.ts", "build": "rimraf ./build && tsc && cp -R ./src/templates ./build", "lint": "eslint . --ext .ts", "lint-and-fix": "eslint . --ext .ts --fix", @@ -94,6 +94,7 @@ "express-validator": "^6.14.2", "handlebars": "^4.7.7", "helmet": "^5.1.1", + "js-yaml": "^4.1.0", "jsonwebtoken": "^9.0.0", "jsrp": "^0.2.4", "libsodium-wrappers": "^0.7.10", diff --git a/backend/api-documentation.json b/backend/spec.json similarity index 79% rename from backend/api-documentation.json rename to backend/spec.json index 79cf0eb3b..188365290 100644 --- a/backend/api-documentation.json +++ b/backend/spec.json @@ -5,6 +5,16 @@ "description": "List of all available APIs that can be consumed", "version": "1.0.0" }, + "servers": [ + { + "url": "https://infisical.com", + "description": "Production server" + }, + { + "url": "http://localhost:8080", + "description": "Local server" + } + ], "paths": { "/api/v1/secret/{secretId}/secret-versions": { "get": { @@ -43,6 +53,43 @@ } } }, + "/api/v1/secret/{secretId}/secret-versions/rollback": { + "post": { + "description": "", + "parameters": [ + { + "name": "secretId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "version": { + "example": "any" + } + } + } + } + } + } + } + }, "/api/v1/secret-snapshot/{secretSnapshotId}": { "get": { "description": "", @@ -126,6 +173,43 @@ } } }, + "/api/v1/workspace/{workspaceId}/secret-snapshots/rollback": { + "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": { + "version": { + "example": "any" + } + } + } + } + } + } + } + }, "/api/v1/workspace/{workspaceId}/logs": { "get": { "description": "", @@ -476,7 +560,11 @@ "post": { "description": "", "parameters": [], - "responses": {} + "responses": { + "200": { + "description": "OK" + } + } } }, "/api/v1/bot/{workspaceId}": { @@ -1997,6 +2085,35 @@ } } }, + "/api/v2/users/me": { + "get": { + "summary": "Retrieve the current user on the request", + "description": "Retrieve the current user on the request", + "parameters": [], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "$ref": "#/components/schemas/CurrentUser", + "description": "Current user on request" + } + } + } + }, + "400": { + "description": "Bad Request" + } + }, + "security": [ + { + "apiKeyAuth": [] + } + ] + } + }, "/api/v2/workspace/{workspaceId}/secrets": { "post": { "description": "", @@ -2080,7 +2197,8 @@ }, "/api/v2/workspace/{workspaceId}/encrypted-key": { "get": { - "description": "", + "summary": "Return encrypted project key", + "description": "Return encrypted project key", "parameters": [ { "name": "workspaceId", @@ -2088,17 +2206,34 @@ "required": true, "schema": { "type": "string" - } + }, + "description": "ID of project" } ], "responses": { "200": { - "description": "OK" + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProjectKey" + }, + "description": "Encrypted project key for the given project" + } + } + } }, "400": { "description": "Bad Request" } - } + }, + "security": [ + { + "apiKeyAuth": [] + } + ] } }, "/api/v2/workspace/{workspaceId}/service-token-data": { @@ -2124,7 +2259,7 @@ } } }, - "/api/v2/secret/batch-create/workspace/{workspaceId}/environment/{environmentName}": { + "/api/v2/secret/batch-create/workspace/{workspaceId}/environment/{environment}": { "post": { "description": "", "parameters": [ @@ -2137,7 +2272,7 @@ } }, { - "name": "environmentName", + "name": "environment", "in": "path", "required": true, "schema": { @@ -2166,7 +2301,7 @@ } } }, - "/api/v2/secret/workspace/{workspaceId}/environment/{environmentName}": { + "/api/v2/secret/workspace/{workspaceId}/environment/{environment}": { "post": { "description": "", "parameters": [ @@ -2179,47 +2314,7 @@ } }, { - "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", + "name": "environment", "in": "path", "required": true, "schema": { @@ -2397,6 +2492,230 @@ } } }, + "/api/v2/secret/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": { + "secret": { + "example": "any" + } + } + } + } + } + } + } + }, + "/api/v2/secrets/": { + "post": { + "summary": "Create new secret(s)", + "description": "Create one or many secrets for a given project and environment.", + "parameters": [], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Secret" + }, + "description": "Array of newly-created 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" + }, + "secrets": { + "$ref": "#/components/schemas/CreateSecret", + "description": "Secret(s) to create - object or array of objects" + } + } + } + } + } + } + }, + "get": { + "summary": "Read secrets", + "description": "Read secrets from a project and environment", + "parameters": [], + "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" + } + } + } + } + }, + "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)", + "parameters": [], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Secret" + }, + "description": "Array of newly-updated secrets for the given project and environment" + } + } + } + } + }, + "security": [ + { + "apiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "secrets": { + "$ref": "#/components/schemas/UpdateSecret", + "description": "Secret(s) to update - object or array of objects" + } + } + } + } + } + } + }, + "delete": { + "summary": "Delete secret(s)", + "description": "Delete one or many secrets by their ID(s)", + "parameters": [], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Secret" + }, + "description": "Array of deleted secrets" + } + } + } + } + }, + "security": [ + { + "apiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "secretIds": { + "type": "string", + "description": "ID(s) of secrets - string or array of strings" + } + } + } + } + } + } + } + }, "/api/v2/service-token/": { "get": { "description": "", @@ -2557,11 +2876,243 @@ } }, "components": { + "schemas": { + "CurrentUser": { + "type": "object", + "properties": { + "_id": { + "type": "string", + "example": "" + }, + "email": { + "type": "string", + "example": "" + }, + "firstName": { + "type": "string", + "example": "" + }, + "lastName": { + "type": "string", + "example": "" + }, + "publicKey": { + "type": "string", + "example": "" + }, + "encryptedPrivateKey": { + "type": "string", + "example": "" + }, + "updatedAt": { + "type": "string", + "example": "" + }, + "createdAt": { + "type": "string", + "example": "" + } + } + }, + "ProjectKey": { + "type": "object", + "properties": { + "encryptedkey": { + "type": "string", + "example": "" + }, + "nonce": { + "type": "string", + "example": "" + }, + "sender": { + "type": "object", + "properties": { + "publicKey": { + "type": "string", + "example": "" + } + } + }, + "receiver": { + "type": "string", + "example": "" + }, + "workspace": { + "type": "string", + "example": "" + } + } + }, + "CreateSecret": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "shared" + }, + "secretKeyCiphertext": { + "type": "string", + "example": "" + }, + "secretKeyIV": { + "type": "string", + "example": "" + }, + "secretKeyTag": { + "type": "string", + "example": "" + }, + "secretValueCiphertext": { + "type": "string", + "example": "" + }, + "secretValueIV": { + "type": "string", + "example": "" + }, + "secretValueTag": { + "type": "string", + "example": "" + }, + "secretCommentCiphertext": { + "type": "string", + "example": "" + }, + "secretCommentIV": { + "type": "string", + "example": "" + }, + "secretCommentTag": { + "type": "string", + "example": "" + } + } + }, + "UpdateSecret": { + "type": "object", + "properties": { + "id": { + "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": "" + }, + "secretCommentCiphertext": { + "type": "string", + "example": "" + }, + "secretCommentIV": { + "type": "string", + "example": "" + }, + "secretCommentTag": { + "type": "string", + "example": "" + } + } + }, + "Secret": { + "type": "object", + "properties": { + "_id": { + "type": "string", + "example": "" + }, + "version": { + "type": "number", + "example": 1 + }, + "workspace": { + "type": "string", + "example": "" + }, + "type": { + "type": "string", + "example": "shared" + }, + "user": {}, + "secretKeyCiphertext": { + "type": "string", + "example": "" + }, + "secretKeyIV": { + "type": "string", + "example": "" + }, + "secretKeyTag": { + "type": "string", + "example": "" + }, + "secretValueCiphertext": { + "type": "string", + "example": "" + }, + "secretValueIV": { + "type": "string", + "example": "" + }, + "secretValueTag": { + "type": "string", + "example": "" + }, + "secretCommentCiphertext": { + "type": "string", + "example": "" + }, + "secretCommentIV": { + "type": "string", + "example": "" + }, + "secretCommentTag": { + "type": "string", + "example": "" + }, + "updatedAt": { + "type": "string", + "example": "" + }, + "createdAt": { + "type": "string", + "example": "" + } + } + } + }, "securitySchemes": { "bearerAuth": { "type": "http", "scheme": "bearer", - "bearerFormat": "JWT" + "bearerFormat": "JWT", + "description": "This security definition uses the HTTP 'bearer' scheme, which allows the client to authenticate using a JSON Web Token (JWT) that is passed in the Authorization header of the request." + }, + "apiKeyAuth": { + "type": "apiKey", + "in": "header", + "name": "X-API-Key", + "description": "This security definition uses an API key, which is passed in the header of the request as the value of the \"X-API-Key\" header. The client must provide a valid key in order to access the API." } } } diff --git a/backend/src/app.ts b/backend/src/app.ts index aa7ac9b28..8cefd31ac 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -8,7 +8,7 @@ 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') +const swaggerFile = require('../spec.json') dotenv.config(); @@ -41,7 +41,8 @@ import { integrationAuth as v1IntegrationAuthRouter } from './routes/v1'; import { - secret as v2SecretRouter, + users as v2UsersRouter, + secret as v2SecretRouter, // begin to phase out secrets as v2SecretsRouter, workspace as v2WorkspaceRouter, serviceTokenData as v2ServiceTokenDataRouter, @@ -103,6 +104,7 @@ app.use('/api/v1/integration', v1IntegrationRouter); app.use('/api/v1/integration-auth', v1IntegrationAuthRouter); // v2 routes +app.use('/api/v2/users', v2UsersRouter); app.use('/api/v2/workspace', v2WorkspaceRouter); // TODO: turn into plural route app.use('/api/v2/secret', v2SecretRouter); // stop supporting, TODO: revise app.use('/api/v2/secrets', v2SecretsRouter); diff --git a/backend/src/controllers/v1/authController.ts b/backend/src/controllers/v1/authController.ts index defd03d8a..882db688f 100644 --- a/backend/src/controllers/v1/authController.ts +++ b/backend/src/controllers/v1/authController.ts @@ -170,10 +170,11 @@ export const logout = async (req: Request, res: Response) => { * @param res * @returns */ -export const checkAuth = async (req: Request, res: Response) => - res.status(200).send({ +export const checkAuth = async (req: Request, res: Response) => { + return res.status(200).send({ message: 'Authenticated' }); +} /** * Return new token by redeeming refresh token diff --git a/backend/src/controllers/v2/index.ts b/backend/src/controllers/v2/index.ts index 1651c09ee..bc6848590 100644 --- a/backend/src/controllers/v2/index.ts +++ b/backend/src/controllers/v2/index.ts @@ -1,3 +1,4 @@ +import * as usersController from './usersController'; import * as workspaceController from './workspaceController'; import * as serviceTokenDataController from './serviceTokenDataController'; import * as apiKeyDataController from './apiKeyDataController'; @@ -5,6 +6,7 @@ import * as secretController from './secretController'; import * as secretsController from './secretsController'; export { + usersController, workspaceController, serviceTokenDataController, apiKeyDataController, diff --git a/backend/src/controllers/v2/secretsController.ts b/backend/src/controllers/v2/secretsController.ts index 69cacd75c..31ad0d94c 100644 --- a/backend/src/controllers/v2/secretsController.ts +++ b/backend/src/controllers/v2/secretsController.ts @@ -21,6 +21,53 @@ import { BadRequestError } from '../../utils/errors'; * @param res */ export const createSecrets = async (req: Request, res: Response) => { + /* + #swagger.summary = 'Create new secret(s)' + #swagger.description = 'Create one or many secrets for a given project and environment.' + + #swagger.security = [{ + "apiKeyAuth": [] + }] + + #swagger.requestBody = { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "workspaceId": { + "type": "string", + "description": "ID of project", + }, + "environment": { + "type": "string", + "description": "Environment within project" + }, + "secrets": { + $ref: "#/components/schemas/CreateSecret", + "description": "Secret(s) to create - object or array of objects" + } + } + } + } + } + } + + #swagger.responses[200] = { + content: { + "application/json": { + "schema": { + "type": "array", + "items": { + $ref: "#/components/schemas/Secret" + }, + "description": "Array of newly-created secrets for the given project and environment" + } + } + } + } + */ const channel = req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli'; const { workspaceId, environment } = req.body; @@ -150,6 +197,49 @@ export const createSecrets = async (req: Request, res: Response) => { * @returns */ export const getSecrets = async (req: Request, res: Response) => { + /* + #swagger.summary = 'Read secrets' + #swagger.description = 'Read secrets from a project and environment' + + #swagger.security = [{ + "apiKeyAuth": [] + }] + + #swagger.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.responses[200] = { + content: { + "application/json": { + "schema": { + "type": "array", + "items": { + $ref: "#/components/schemas/Secret" + }, + "description": "Array of secrets for the given project and environment" + } + } + } + } + */ const { workspaceId, environment } = req.query; let userId: Types.ObjectId | undefined = undefined // used for getting personal secrets for user @@ -217,6 +307,48 @@ 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)' + + #swagger.security = [{ + "apiKeyAuth": [] + }] + + #swagger.requestBody = { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "secrets": { + $ref: "#/components/schemas/UpdateSecret", + "description": "Secret(s) to update - object or array of objects" + } + } + } + } + } + } + + #swagger.responses[200] = { + content: { + "application/json": { + "schema": { + "type": "array", + "items": { + $ref: "#/components/schemas/Secret" + }, + "description": "Array of newly-updated secrets for the given project and environment" + } + } + } + } + */ const channel = req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli'; // TODO: move type @@ -384,6 +516,45 @@ export const updateSecrets = async (req: Request, res: Response) => { * @param res */ export const deleteSecrets = async (req: Request, res: Response) => { + /* + #swagger.summary = 'Delete secret(s)' + #swagger.description = 'Delete one or many secrets by their ID(s)' + + #swagger.security = [{ + "apiKeyAuth": [] + }] + + #swagger.requestBody = { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "secretIds": { + "type": "string", + "description": "ID(s) of secrets - string or array of strings" + }, + } + } + } + } + } + + #swagger.responses[200] = { + content: { + "application/json": { + schema: { + "type": "array", + "items": { + $ref: "#/components/schemas/Secret" + }, + "description": "Array of deleted secrets" + } + } + } + } + */ const channel = req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli'; const toDelete = req.secrets.map((s: any) => s._id); diff --git a/backend/src/controllers/v2/usersController.ts b/backend/src/controllers/v2/usersController.ts new file mode 100644 index 000000000..faac8f646 --- /dev/null +++ b/backend/src/controllers/v2/usersController.ts @@ -0,0 +1,44 @@ +import { Request, Response } from 'express'; +import * as Sentry from '@sentry/node'; +import { + User +} from '../../models'; + +export const getMe = async (req: Request, res: Response) => { + /* + #swagger.summary = "Retrieve the current user on the request" + #swagger.description = "Retrieve the current user on the request" + + #swagger.security = [{ + "apiKeyAuth": [] + }] + + #swagger.responses[200] = { + content: { + "application/json": { + "schema": { + "type": "object", + $ref: "#/components/schemas/CurrentUser", + "description": "Current user on request" + } + } + } + } + */ + let user; + try { + user = await User + .findById(req.user._id) + .select('+publicKey +encryptedPrivateKey'); + } catch (err) { + Sentry.setUser({ email: req.user.email }); + Sentry.captureException(err); + return res.status(400).send({ + message: 'Failed to get user' + }); + } + + return res.status(200).send({ + user + }); +} \ No newline at end of file diff --git a/backend/src/controllers/v2/workspaceController.ts b/backend/src/controllers/v2/workspaceController.ts index 0dbdfa076..6acec644d 100644 --- a/backend/src/controllers/v2/workspaceController.ts +++ b/backend/src/controllers/v2/workspaceController.ts @@ -169,6 +169,34 @@ export const pullSecrets = async (req: Request, res: Response) => { }; export const getWorkspaceKey = async (req: Request, res: Response) => { + /* + #swagger.summary = 'Return encrypted project key' + #swagger.description = 'Return encrypted project key' + + #swagger.security = [{ + "apiKeyAuth": [] + }] + + #swagger.parameters['workspaceId'] = { + "description": "ID of project", + "required": true, + "type": "string" + } + + #swagger.responses[200] = { + content: { + "application/json": { + "schema": { + "type": "array", + "items": { + $ref: "#/components/schemas/ProjectKey" + }, + "description": "Encrypted project key for the given project" + } + } + } + } + */ let key; try { const { workspaceId } = req.params; diff --git a/backend/src/routes/v2/index.ts b/backend/src/routes/v2/index.ts index 8bea42620..a30c72642 100644 --- a/backend/src/routes/v2/index.ts +++ b/backend/src/routes/v2/index.ts @@ -1,3 +1,4 @@ +import users from './users'; import secret from './secret'; // stop-supporting import secrets from './secrets'; import workspace from './workspace'; @@ -5,6 +6,7 @@ import serviceTokenData from './serviceTokenData'; import apiKeyData from './apiKeyData'; export { + users, secret, secrets, workspace, diff --git a/backend/src/routes/v2/users.ts b/backend/src/routes/v2/users.ts new file mode 100644 index 000000000..dba107b15 --- /dev/null +++ b/backend/src/routes/v2/users.ts @@ -0,0 +1,16 @@ +import express from 'express'; +const router = express.Router(); +import { + requireAuth +} from '../../middleware'; +import { usersController } from '../../controllers/v2'; + +router.get( + '/me', + requireAuth({ + acceptedAuthModes: ['jwt'] + }), + usersController.getMe +); + +export default router; \ No newline at end of file diff --git a/backend/swagger.ts b/backend/swagger.ts deleted file mode 100644 index 0505bb813..000000000 --- a/backend/swagger.ts +++ /dev/null @@ -1,22 +0,0 @@ -// eslint-disable-next-line @typescript-eslint/no-var-requires -const swaggerAutogen = require('swagger-autogen')({ openapi: '3.0.0' }); - -const doc = { - info: { - title: 'Infisical API', - description: 'List of all available APIs that can be consumed', - }, - host: ['https://infisical.com'], - securityDefinitions: { - bearerAuth: { - type: 'http', - scheme: 'bearer', - bearerFormat: 'JWT' - } - } -}; - -const outputFile = './api-documentation.json'; -const endpointsFiles = ['./src/app.ts']; - -swaggerAutogen(outputFile, endpointsFiles, doc); \ No newline at end of file diff --git a/backend/swagger/index.ts b/backend/swagger/index.ts new file mode 100644 index 000000000..2b8bc4f62 --- /dev/null +++ b/backend/swagger/index.ts @@ -0,0 +1,116 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +const swaggerAutogen = require('swagger-autogen')({ openapi: '3.0.0' }); +const fs = require('fs').promises; +const yaml = require('js-yaml'); +const { secretSchema } = require('./schemas/index.ts'); + +/** + * Generates OpenAPI specs for all Infisical API endpoints: + * - spec.json in /backend for api-serving + * - spec.yaml in /docs for API reference + */ +const generateOpenAPISpec = async () => { + const doc = { + info: { + title: 'Infisical API', + description: 'List of all available APIs that can be consumed', + }, + host: ['https://infisical.com'], + servers: [ + { + url: 'https://infisical.com', + description: 'Production server' + }, + { + url: 'http://localhost:8080', + description: 'Local server' + } + ], + securityDefinitions: { + bearerAuth: { + type: 'http', + scheme: 'bearer', + bearerFormat: 'JWT', + description: "This security definition uses the HTTP 'bearer' scheme, which allows the client to authenticate using a JSON Web Token (JWT) that is passed in the Authorization header of the request." + }, + apiKeyAuth: { + type: 'apiKey', + in: 'header', + name: 'X-API-Key', + description: 'This security definition uses an API key, which is passed in the header of the request as the value of the "X-API-Key" header. The client must provide a valid key in order to access the API.' + } + }, + definitions: { + CurrentUser: { + _id: '', + email: '', + firstName: '', + lastName: '', + publicKey: '', + encryptedPrivateKey: '', + updatedAt: '', + createdAt: '' + }, + ProjectKey: { + encryptedkey: '', + nonce: '', + sender: { + publicKey: '' + }, + receiver: '', + workspace: '' + }, + CreateSecret: { + type: 'shared', + secretKeyCiphertext: '', + secretKeyIV: '', + secretKeyTag: '', + secretValueCiphertext: '', + secretValueIV: '', + secretValueTag: '', + secretCommentCiphertext: '', + secretCommentIV: '', + secretCommentTag: '' + }, + UpdateSecret: { + id: '', + secretKeyCiphertext: '', + secretKeyIV: '', + secretKeyTag: '', + secretValueCiphertext: '', + secretValueIV: '', + secretValueTag: '', + secretCommentCiphertext: '', + secretCommentIV: '', + secretCommentTag: '' + }, + Secret: { + _id: '', + version: 1, + workspace : '', + type: 'shared', + user: null, + secretKeyCiphertext: '', + secretKeyIV: '', + secretKeyTag: '', + secretValueCiphertext: '', + secretValueIV: '', + secretValueTag: '', + secretCommentCiphertext: '', + secretCommentIV: '', + secretCommentTag: '', + updatedAt: '', + createdAt: '' + } + } + }; + + const outputJSONFile = '../spec.json'; + const outputYAMLFile = '../docs/spec.yaml'; + const endpointsFiles = ['../src/app.ts']; + + const spec = await swaggerAutogen(outputJSONFile, endpointsFiles, doc); + await fs.writeFile(outputYAMLFile, yaml.dump(spec.data)); +} + +generateOpenAPISpec(); diff --git a/backend/swagger/schemas/index.ts b/backend/swagger/schemas/index.ts new file mode 100644 index 000000000..b0b427fce --- /dev/null +++ b/backend/swagger/schemas/index.ts @@ -0,0 +1,6 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +const secretSchema = require('./secretSchema.ts'); + +module.exports = { + secretSchema +} \ No newline at end of file diff --git a/backend/swagger/schemas/secretSchema.ts b/backend/swagger/schemas/secretSchema.ts new file mode 100644 index 000000000..85c6ce017 --- /dev/null +++ b/backend/swagger/schemas/secretSchema.ts @@ -0,0 +1,11 @@ +const secretSchema = { + _id: { + type: 'string', + format: 'objectId' + }, + version: { + type: 'number' + } +} + +module.exports = secretSchema; \ No newline at end of file diff --git a/docs/api-reference/endpoints/secrets/read.mdx b/docs/api-reference/endpoints/secrets/read.mdx index 4305f192c..c88fb0ec7 100644 --- a/docs/api-reference/endpoints/secrets/read.mdx +++ b/docs/api-reference/endpoints/secrets/read.mdx @@ -1,4 +1,4 @@ --- -title: "Read" +title: "Retrieve" openapi: "GET /api/v2/secrets/" --- diff --git a/docs/api-reference/endpoints/users/me.mdx b/docs/api-reference/endpoints/users/me.mdx new file mode 100644 index 000000000..9273f0ca6 --- /dev/null +++ b/docs/api-reference/endpoints/users/me.mdx @@ -0,0 +1,4 @@ +--- +title: "Get Current User" +openapi: "GET /api/v2/users/me" +--- diff --git a/docs/api-reference/endpoints/workspaces/workspace-key.mdx b/docs/api-reference/endpoints/workspaces/workspace-key.mdx new file mode 100644 index 000000000..2658676a3 --- /dev/null +++ b/docs/api-reference/endpoints/workspaces/workspace-key.mdx @@ -0,0 +1,4 @@ +--- +title: "Get Project Key" +openapi: "GET /api/v2/workspace/{workspaceId}/encrypted-key" +--- diff --git a/docs/api-reference/overview/authentication.mdx b/docs/api-reference/overview/authentication.mdx index 27a2dc134..8cc218253 100644 --- a/docs/api-reference/overview/authentication.mdx +++ b/docs/api-reference/overview/authentication.mdx @@ -1,3 +1,11 @@ --- title: "Authentication" --- + +To authenticate requests with Infisical, you must include an API key in the `X-API-KEY` header of HTTP requests made to the platform. You can obtain an API key from your user settings. + + + It's important to keep your API key secure, as it grants access to your + secrets in Infisical. For added security, consider rotating your API key on a + regular basis. + diff --git a/docs/api-reference/overview/examples/create-secrets.mdx b/docs/api-reference/overview/examples/create-secrets.mdx new file mode 100644 index 000000000..94f830094 --- /dev/null +++ b/docs/api-reference/overview/examples/create-secrets.mdx @@ -0,0 +1,64 @@ +--- +title: "Create secrets" +--- + +In this example, we demonstrate how to add secrets to a project and environment. + +Prerequisites: + +- Set up and add envars to [Infisical Cloud](https://app.infisical.com) +- Grasp a basic understanding of the system and its underlying cryptography [here](/api-reference/overview/introduction). + +## Flow + +1. Get your (encrypted) private key. +2. Decrypt your (encrypted) private key with your password. +3. Get the 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 + +## Example + +```js +const axios = require("axios"); +const aes = require("aes-256-gcm"); +const nacl = require("tweetnacl"); +nacl.util = require("tweetnacl-util"); + +const WORKSPACE_KEY = "3a7a243eb62078c13f09203e75e8cb32"; + +const secretKey = "SOME_KEY"; +const secretValue = "SOME_VALUE"; + +// encrypt key of secret +const { + ciphertext: secretKeyCiphertext, + iv: secretKeyIV, + tag: secretKeyTag, +} = aes.encrypt(secretKey, WORKSPACE_KEY); + +// encrypt value of secret +const { + ciphertext: secretValueCiphertext, + iv: secretValueIV, + tag: secretValueTag, +} = aes.encrypt(secretKey, WORKSPACE_KEY); + +// construct request body +const secret = { + secretKeyCiphertext, + secretKeyIV, + secretKeyTag, + secretValueCiphertext, + secretValueIV, + secretValueTag, +}; +``` + + + 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. + + diff --git a/docs/api-reference/overview/examples/retrieve-secrets.mdx b/docs/api-reference/overview/examples/retrieve-secrets.mdx new file mode 100644 index 000000000..2a314e65d --- /dev/null +++ b/docs/api-reference/overview/examples/retrieve-secrets.mdx @@ -0,0 +1,10 @@ +--- +title: "Retrieve secrets" +--- + +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. Get secrets for a project and environment. +6. Decrypt the secrets in your project. diff --git a/docs/api-reference/overview/examples/update-secrets.mdx b/docs/api-reference/overview/examples/update-secrets.mdx new file mode 100644 index 000000000..b302710a3 --- /dev/null +++ b/docs/api-reference/overview/examples/update-secrets.mdx @@ -0,0 +1,10 @@ +--- +title: "Update secrets" +--- + +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 diff --git a/docs/api-reference/overview/introduction.mdx b/docs/api-reference/overview/introduction.mdx index 9632e3788..f93ba91fe 100644 --- a/docs/api-reference/overview/introduction.mdx +++ b/docs/api-reference/overview/introduction.mdx @@ -1,3 +1,19 @@ --- title: "Introduction" --- + +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 +rotating credentials, or for integrating secret management into a larger system. + +With the REST API, users can create, read, update, and delete secrets, as well as manage access control, query audit logs, and more. + +## Concepts + +Using Infisical's API to manage secrets requires a basic understanding of the system and its underlying cryptography detailed [here](/security/overview). + +- Each user has a public/private key pair that is stored with the platform; private keys are encrypted locally by the user's password before being sent off to the server during the account signup process. +- Each (encrypted) secret belongs to a project and environment. +- Each project has an (encrypted) project key used to encrypt the secrets within that project; Infisical stores copies of the project key, for each member of that project, encrypted under each member's public key. +- Secrets are encrypted symmetrically by your copy of the project key belonging to the project containing. +- Infisical uses AES256-GCM and [TweetNaCl.js](https://tweetnacl.js.org/#/) for symmetric and asymmetric encryption/decryption operations. diff --git a/docs/api-reference/overview/usage.mdx b/docs/api-reference/overview/usage.mdx new file mode 100644 index 000000000..9f23080c7 --- /dev/null +++ b/docs/api-reference/overview/usage.mdx @@ -0,0 +1,18 @@ +--- +title: "Usage" +--- + +Prerequisites: + +- Set up and add envars to [Infisical Cloud](https://app.infisical.com) or your self-hosted instance. +- Obtain an API Key in your user settings to be included in requests to the Infisical API. + +Using Infisical's API to manage secrets requires a basic understanding of the system and its underlying cryptography detailed [here](/security/overview). + +## Concepts + +- Each user has a public/private key pair that is stored with the platform; private keys are encrypted locally by the user's password before being sent off to the server during the account signup process. +- Each (encrypted) secret belongs to a project and environment. +- Each project has an (encrypted) project key used to encrypt the secrets within that project; Infisical stores copies of the project key, for each member of that project, encrypted under each member's public key. +- Secrets are encrypted symmetrically by your copy of the project key belonging to the project containing. +- Infisical uses AES256-GCM and [TweetNaCl.js](https://tweetnacl.js.org/#/) for symmetric and asymmetric encryption/decryption operations. diff --git a/docs/mint.json b/docs/mint.json index 363b27a3e..39a3726d9 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -21,6 +21,16 @@ "to": "#F8B7BD" } }, + "api": { + "baseUrl": [ + "https://app.infisical.com", + "http://localhost:8080" + ], + "auth": { + "method": "api-key", + "name": "X-API-KEY" + } + }, "topbarLinks": [ { "name": "Log In", "url": "https://app.infisical.com/login" } ], @@ -134,12 +144,32 @@ "group": "Overview", "pages": [ "api-reference/overview/introduction", - "api-reference/overview/authentication" + "api-reference/overview/authentication", + { + "group": "Examples", + "pages": [ + "api-reference/overview/examples/create-secrets", + "api-reference/overview/examples/retrieve-secrets", + "api-reference/overview/examples/update-secrets" + ] + } ] }, { "group": "Endpoints", "pages": [ + { + "group": "Users", + "pages": [ + "api-reference/endpoints/users/me" + ] + }, + { + "group": "Projects", + "pages": [ + "api-reference/endpoints/workspaces/workspace-key" + ] + }, { "group": "Secrets", "pages": [ diff --git a/docs/spec.yaml b/docs/spec.yaml index a0271395b..dd62f167b 100644 --- a/docs/spec.yaml +++ b/docs/spec.yaml @@ -4,7 +4,10 @@ info: description: List of all available APIs that can be consumed version: 1.0.0 servers: + - url: https://infisical.com + description: Production server - url: http://localhost:8080 + description: Local server paths: /api/v1/secret/{secretId}/secret-versions: get: @@ -1270,6 +1273,24 @@ paths: description: OK '400': description: Bad Request + /api/v2/users/me: + get: + summary: Retrieve the current user on the request + description: Retrieve the current user on the request + parameters: [] + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + $ref: '#/components/schemas/CurrentUser' + description: Current user on request + '400': + description: Bad Request + security: + - apiKeyAuth: [] /api/v2/workspace/{workspaceId}/secrets: post: description: '' @@ -1321,18 +1342,29 @@ paths: description: Bad Request /api/v2/workspace/{workspaceId}/encrypted-key: get: - description: '' + summary: Return encrypted project key + description: Return encrypted project key parameters: - name: workspaceId in: path required: true schema: type: string + description: ID of project responses: '200': description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ProjectKey' + description: Encrypted project key for the given project '400': description: Bad Request + security: + - apiKeyAuth: [] /api/v2/workspace/{workspaceId}/service-token-data: get: description: '' @@ -1513,57 +1545,122 @@ paths: example: any /api/v2/secrets/: post: - description: '' + summary: Create new secret(s) + description: Create one or many secrets for a given project and environment. parameters: [] responses: '200': description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Secret' + description: >- + Array of newly-created secrets for the given project and + environment + security: + - apiKeyAuth: [] requestBody: + required: true content: application/json: schema: type: object properties: - secrets: - example: any workspaceId: - example: any + type: string + description: ID of project environment: - example: any + type: string + description: Environment within project + secrets: + $ref: '#/components/schemas/CreateSecret' + description: Secret(s) to create - object or array of objects get: - description: '' - parameters: - - name: workspaceId - in: query - schema: - type: string - - name: environment - in: query - schema: - type: string - responses: - '200': - description: OK - patch: - description: '' + summary: Read secrets + description: Read secrets from a project and environment parameters: [] 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 + 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) + parameters: [] + responses: + '200': + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Secret' + description: >- + Array of newly-updated secrets for the given project and + environment + security: + - apiKeyAuth: [] + requestBody: + required: true content: application/json: schema: type: object properties: secrets: - example: any + $ref: '#/components/schemas/UpdateSecret' + description: Secret(s) to update - object or array of objects delete: - description: '' + summary: Delete secret(s) + description: Delete one or many secrets by their ID(s) parameters: [] responses: '200': description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Secret' + description: Array of deleted secrets + security: + - apiKeyAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + secretIds: + type: string + description: ID(s) of secrets - string or array of strings /api/v2/service-token/: get: description: '' @@ -1665,26 +1762,183 @@ paths: description: OK components: schemas: - secret: + CurrentUser: + type: object + properties: + _id: + type: string + example: '' + email: + type: string + example: '' + firstName: + type: string + example: '' + lastName: + type: string + example: '' + publicKey: + type: string + example: '' + encryptedPrivateKey: + type: string + example: '' + updatedAt: + type: string + example: '' + createdAt: + type: string + example: '' + ProjectKey: + type: object + properties: + encryptedkey: + type: string + example: '' + nonce: + type: string + example: '' + sender: + type: object + properties: + publicKey: + type: string + example: '' + receiver: + type: string + example: '' + workspace: + type: string + example: '' + CreateSecret: type: object properties: type: type: string - example: object - properties: - type: object - properties: - test: - type: object - properties: - type: - type: string - example: integer - description: - type: string - example: '123' + example: shared + secretKeyCiphertext: + type: string + example: '' + secretKeyIV: + type: string + example: '' + secretKeyTag: + type: string + example: '' + secretValueCiphertext: + type: string + example: '' + secretValueIV: + type: string + example: '' + secretValueTag: + type: string + example: '' + secretCommentCiphertext: + type: string + example: '' + secretCommentIV: + type: string + example: '' + secretCommentTag: + type: string + example: '' + UpdateSecret: + type: object + properties: + id: + 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: '' + secretCommentCiphertext: + type: string + example: '' + secretCommentIV: + type: string + example: '' + secretCommentTag: + type: string + example: '' + Secret: + type: object + properties: + _id: + type: string + example: '' + version: + type: number + example: 1 + workspace: + type: string + example: '' + type: + type: string + example: shared + user: {} + secretKeyCiphertext: + type: string + example: '' + secretKeyIV: + type: string + example: '' + secretKeyTag: + type: string + example: '' + secretValueCiphertext: + type: string + example: '' + secretValueIV: + type: string + example: '' + secretValueTag: + type: string + example: '' + secretCommentCiphertext: + type: string + example: '' + secretCommentIV: + type: string + example: '' + secretCommentTag: + type: string + example: '' + updatedAt: + type: string + example: '' + createdAt: + type: string + example: '' securitySchemes: bearerAuth: type: http scheme: bearer bearerFormat: JWT + description: >- + This security definition uses the HTTP 'bearer' scheme, which allows the + client to authenticate using a JSON Web Token (JWT) that is passed in + the Authorization header of the request. + apiKeyAuth: + type: apiKey + in: header + name: X-API-Key + description: >- + This security definition uses an API key, which is passed in the header + of the request as the value of the "X-API-Key" header. The client must + provide a valid key in order to access the API. From b63360813a8d30846441256472dafd4b4c35145d Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Sat, 14 Jan 2023 09:48:13 +0700 Subject: [PATCH 3/5] Continue API reference development --- backend/spec.json | 36 +++++++++++-- backend/src/controllers/v2/usersController.ts | 2 +- .../ee/controllers/v1/workspaceController.ts | 53 +++++++++++++++++-- backend/swagger/index.ts | 2 + 4 files changed, 84 insertions(+), 9 deletions(-) diff --git a/backend/spec.json b/backend/spec.json index 188365290..baad21e7e 100644 --- a/backend/spec.json +++ b/backend/spec.json @@ -175,7 +175,8 @@ }, "/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", @@ -183,25 +184,44 @@ "required": true, "schema": { "type": "string" - } + }, + "description": "ID of project" } ], "responses": { "200": { - "description": "OK" + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Secret" + }, + "description": "Array of secrets captured in the secret snapshot" + } + } + } }, "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" } } } @@ -2904,6 +2924,14 @@ "type": "string", "example": "" }, + "iv": { + "type": "string", + "example": "" + }, + "tag": { + "type": "string", + "example": "" + }, "updatedAt": { "type": "string", "example": "" diff --git a/backend/src/controllers/v2/usersController.ts b/backend/src/controllers/v2/usersController.ts index faac8f646..194e508b3 100644 --- a/backend/src/controllers/v2/usersController.ts +++ b/backend/src/controllers/v2/usersController.ts @@ -29,7 +29,7 @@ export const getMe = async (req: Request, res: Response) => { try { user = await User .findById(req.user._id) - .select('+publicKey +encryptedPrivateKey'); + .select('+publicKey +encryptedPrivateKey +iv +tag'); } catch (err) { Sentry.setUser({ email: req.user.email }); Sentry.captureException(err); diff --git a/backend/src/ee/controllers/v1/workspaceController.ts b/backend/src/ee/controllers/v1/workspaceController.ts index 8fd7c8746..f29c42861 100644 --- a/backend/src/ee/controllers/v1/workspaceController.ts +++ b/backend/src/ee/controllers/v1/workspaceController.ts @@ -78,16 +78,61 @@ export const getWorkspaceSecretSnapshotsCount = async (req: Request, res: Respon * @returns */ export const rollbackWorkspaceSecretSnapshot = async (req: Request, res: Response) => { + /* + #swagger.summary = 'Roll back project secrets to those captured in a secret snapshot version' + #swagger.description = 'Roll back project secrets to those captured in a secret snapshot version' + + #swagger.security = [{ + "apiKeyAuth": [] + }] + + #swagger.parameters['workspaceId'] = { + "description": "ID of project", + "required": true, + "type": "string" + } + + #swagger.requestBody = { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "version": { + "type": "integer", + "description": "Version of secret snapshot to roll back to", + } + } + } + } + } + } + + #swagger.responses[200] = { + content: { + "application/json": { + schema: { + "type": "array", + "items": { + $ref: "#/components/schemas/Secret" + }, + "description": "Array of secrets captured in the secret snapshot" + } + } + } + } + */ let secrets; try { const { workspaceId } = req.params; const { version } = req.body; // validate secret snapshot - const secretSnapshot = await SecretSnapshot.findOne({ - workspace: workspaceId, - version - }).populate<{ secretVersions: ISecretVersion[]}>('secretVersions'); + const secretSnapshot = await SecretSnapshot.findOne({ + workspace: workspaceId, + version + }).populate<{ secretVersions: ISecretVersion[]}>('secretVersions'); if (!secretSnapshot) throw new Error('Failed to find secret snapshot'); diff --git a/backend/swagger/index.ts b/backend/swagger/index.ts index 2b8bc4f62..91f27da44 100644 --- a/backend/swagger/index.ts +++ b/backend/swagger/index.ts @@ -48,6 +48,8 @@ const generateOpenAPISpec = async () => { lastName: '', publicKey: '', encryptedPrivateKey: '', + iv: '', + tag: '', updatedAt: '', createdAt: '' }, From 315810bd741cb8ab60a766cb4dea9bfbc76488a5 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Sat, 14 Jan 2023 19:02:12 +0700 Subject: [PATCH 4/5] Complete v1 API reference docs, pre-launch --- backend/spec.json | 693 ++++++++++++++++-- .../src/controllers/v2/secretsController.ts | 98 +-- backend/src/controllers/v2/usersController.ts | 9 +- .../src/controllers/v2/workspaceController.ts | 220 ++++++ .../src/ee/controllers/v1/secretController.ts | 93 +++ .../ee/controllers/v1/workspaceController.ts | 130 +++- backend/src/middleware/index.ts | 2 + .../src/middleware/requireMembershipAuth.ts | 57 ++ backend/src/routes/v1/membership.ts | 2 +- backend/src/routes/v2/workspace.ts | 51 ++ backend/src/types/express/index.d.ts | 1 + backend/swagger/index.ts | 68 ++ .../endpoints/secrets/rollback-version.mdx | 4 + .../endpoints/secrets/versions.mdx | 4 + .../workspaces/delete-membership.mdx | 4 + .../endpoints/workspaces/logs.mdx | 4 + .../endpoints/workspaces/memberships.mdx | 4 + .../workspaces/rollback-snapshot.mdx | 4 + .../endpoints/workspaces/secret-snapshots.mdx | 4 + .../workspaces/update-membership.mdx | 4 + .../endpoints/workspaces/workspace-key.mdx | 2 +- .../overview/examples/create-secrets.mdx | 156 +++- .../overview/examples/delete-secrets.mdx | 34 + .../overview/examples/retrieve-secrets.mdx | 138 +++- .../overview/examples/update-secrets.mdx | 146 +++- docs/api-reference/overview/introduction.mdx | 14 +- docs/mint.json | 15 +- docs/spec.yaml | 477 ++++++++++-- 28 files changed, 2216 insertions(+), 222 deletions(-) create mode 100644 backend/src/middleware/requireMembershipAuth.ts create mode 100644 docs/api-reference/endpoints/secrets/rollback-version.mdx create mode 100644 docs/api-reference/endpoints/secrets/versions.mdx create mode 100644 docs/api-reference/endpoints/workspaces/delete-membership.mdx create mode 100644 docs/api-reference/endpoints/workspaces/logs.mdx create mode 100644 docs/api-reference/endpoints/workspaces/memberships.mdx create mode 100644 docs/api-reference/endpoints/workspaces/rollback-snapshot.mdx create mode 100644 docs/api-reference/endpoints/workspaces/secret-snapshots.mdx create mode 100644 docs/api-reference/endpoints/workspaces/update-membership.mdx create mode 100644 docs/api-reference/overview/examples/delete-secrets.mdx diff --git a/backend/spec.json b/backend/spec.json index baad21e7e..e28c66eaa 100644 --- a/backend/spec.json +++ b/backend/spec.json @@ -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": { diff --git a/backend/src/controllers/v2/secretsController.ts b/backend/src/controllers/v2/secretsController.ts index 31ad0d94c..43434df63 100644 --- a/backend/src/controllers/v2/secretsController.ts +++ b/backend/src/controllers/v2/secretsController.ts @@ -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" + } + } } } } diff --git a/backend/src/controllers/v2/usersController.ts b/backend/src/controllers/v2/usersController.ts index 194e508b3..7ad247ff0 100644 --- a/backend/src/controllers/v2/usersController.ts +++ b/backend/src/controllers/v2/usersController.ts @@ -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" + } + } } } } diff --git a/backend/src/controllers/v2/workspaceController.ts b/backend/src/controllers/v2/workspaceController.ts index 6acec644d..2b3aeda1d 100644 --- a/backend/src/controllers/v2/workspaceController.ts +++ b/backend/src/controllers/v2/workspaceController.ts @@ -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 + }); } \ No newline at end of file diff --git a/backend/src/ee/controllers/v1/secretController.ts b/backend/src/ee/controllers/v1/secretController.ts index 751f21611..562c8aa88 100644 --- a/backend/src/ee/controllers/v1/secretController.ts +++ b/backend/src/ee/controllers/v1/secretController.ts @@ -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; diff --git a/backend/src/ee/controllers/v1/workspaceController.ts b/backend/src/ee/controllers/v1/workspaceController.ts index f29c42861..ea9bb7dab 100644 --- a/backend/src/ee/controllers/v1/workspaceController.ts +++ b/backend/src/ee/controllers/v1/workspaceController.ts @@ -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; diff --git a/backend/src/middleware/index.ts b/backend/src/middleware/index.ts index 6a3537076..e9bffac2d 100644 --- a/backend/src/middleware/index.ts +++ b/backend/src/middleware/index.ts @@ -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, diff --git a/backend/src/middleware/requireMembershipAuth.ts b/backend/src/middleware/requireMembershipAuth.ts new file mode 100644 index 000000000..b44d25ceb --- /dev/null +++ b/backend/src/middleware/requireMembershipAuth.ts @@ -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; \ No newline at end of file diff --git a/backend/src/routes/v1/membership.ts b/backend/src/routes/v1/membership.ts index 76bf3e57a..f2c3eb554 100644 --- a/backend/src/routes/v1/membership.ts +++ b/backend/src/routes/v1/membership.ts @@ -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'] diff --git a/backend/src/routes/v2/workspace.ts b/backend/src/routes/v2/workspace.ts index c90834d6d..ebbfe0d4c 100644 --- a/backend/src/routes/v2/workspace.ts +++ b/backend/src/routes/v2/workspace.ts @@ -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; diff --git a/backend/src/types/express/index.d.ts b/backend/src/types/express/index.d.ts index ae9edb4c5..1ce63e609 100644 --- a/backend/src/types/express/index.d.ts +++ b/backend/src/types/express/index.d.ts @@ -8,6 +8,7 @@ declare global { user: any; workspace: any; membership: any; + targetMembership: any; organization: any; membershipOrg: any; integration: any; diff --git a/backend/swagger/index.ts b/backend/swagger/index.ts index 91f27da44..cbf05ed7e 100644 --- a/backend/swagger/index.ts +++ b/backend/swagger/index.ts @@ -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: '', } } }; diff --git a/docs/api-reference/endpoints/secrets/rollback-version.mdx b/docs/api-reference/endpoints/secrets/rollback-version.mdx new file mode 100644 index 000000000..dff577fe1 --- /dev/null +++ b/docs/api-reference/endpoints/secrets/rollback-version.mdx @@ -0,0 +1,4 @@ +--- +title: "Roll Back to Version" +openapi: "POST /api/v1/secret/{secretId}/secret-versions/rollback" +--- diff --git a/docs/api-reference/endpoints/secrets/versions.mdx b/docs/api-reference/endpoints/secrets/versions.mdx new file mode 100644 index 000000000..e8693684c --- /dev/null +++ b/docs/api-reference/endpoints/secrets/versions.mdx @@ -0,0 +1,4 @@ +--- +title: "Get Versions" +openapi: "GET /api/v1/secret/{secretId}/secret-versions" +--- diff --git a/docs/api-reference/endpoints/workspaces/delete-membership.mdx b/docs/api-reference/endpoints/workspaces/delete-membership.mdx new file mode 100644 index 000000000..826eafa83 --- /dev/null +++ b/docs/api-reference/endpoints/workspaces/delete-membership.mdx @@ -0,0 +1,4 @@ +--- +title: "Delete Membership" +openapi: "DELETE /api/v2/workspace/{workspaceId}/memberships/{membershipId}" +--- diff --git a/docs/api-reference/endpoints/workspaces/logs.mdx b/docs/api-reference/endpoints/workspaces/logs.mdx new file mode 100644 index 000000000..d5921c1c0 --- /dev/null +++ b/docs/api-reference/endpoints/workspaces/logs.mdx @@ -0,0 +1,4 @@ +--- +title: "Get Logs" +openapi: "GET /api/v1/workspace/{workspaceId}/logs" +--- diff --git a/docs/api-reference/endpoints/workspaces/memberships.mdx b/docs/api-reference/endpoints/workspaces/memberships.mdx new file mode 100644 index 000000000..38b86d616 --- /dev/null +++ b/docs/api-reference/endpoints/workspaces/memberships.mdx @@ -0,0 +1,4 @@ +--- +title: "Get Memberships" +openapi: "GET /api/v2/workspace/{workspaceId}/memberships" +--- diff --git a/docs/api-reference/endpoints/workspaces/rollback-snapshot.mdx b/docs/api-reference/endpoints/workspaces/rollback-snapshot.mdx new file mode 100644 index 000000000..8b648a400 --- /dev/null +++ b/docs/api-reference/endpoints/workspaces/rollback-snapshot.mdx @@ -0,0 +1,4 @@ +--- +title: "Roll Back to Snapshot" +openapi: "POST /api/v1/workspace/{workspaceId}/secret-snapshots/rollback" +--- diff --git a/docs/api-reference/endpoints/workspaces/secret-snapshots.mdx b/docs/api-reference/endpoints/workspaces/secret-snapshots.mdx new file mode 100644 index 000000000..2d645d4d0 --- /dev/null +++ b/docs/api-reference/endpoints/workspaces/secret-snapshots.mdx @@ -0,0 +1,4 @@ +--- +title: "Get Snapshots" +openapi: "GET /api/v1/workspace/{workspaceId}/secret-snapshots" +--- diff --git a/docs/api-reference/endpoints/workspaces/update-membership.mdx b/docs/api-reference/endpoints/workspaces/update-membership.mdx new file mode 100644 index 000000000..367793847 --- /dev/null +++ b/docs/api-reference/endpoints/workspaces/update-membership.mdx @@ -0,0 +1,4 @@ +--- +title: "Update Membership" +openapi: "PATCH /api/v2/workspace/{workspaceId}/memberships/{membershipId}" +--- diff --git a/docs/api-reference/endpoints/workspaces/workspace-key.mdx b/docs/api-reference/endpoints/workspaces/workspace-key.mdx index 2658676a3..05313eb8f 100644 --- a/docs/api-reference/endpoints/workspaces/workspace-key.mdx +++ b/docs/api-reference/endpoints/workspaces/workspace-key.mdx @@ -1,4 +1,4 @@ --- -title: "Get Project Key" +title: "Get Key" openapi: "GET /api/v2/workspace/{workspaceId}/encrypted-key" --- diff --git a/docs/api-reference/overview/examples/create-secrets.mdx b/docs/api-reference/overview/examples/create-secrets.mdx index 94f830094..b2afe1467 100644 --- a/docs/api-reference/overview/examples/create-secrets.mdx +++ b/docs/api-reference/overview/examples/create-secrets.mdx @@ -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(); ``` 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. + + 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. + diff --git a/docs/api-reference/overview/examples/delete-secrets.mdx b/docs/api-reference/overview/examples/delete-secrets.mdx new file mode 100644 index 000000000..c602cef7e --- /dev/null +++ b/docs/api-reference/overview/examples/delete-secrets.mdx @@ -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(); +``` diff --git a/docs/api-reference/overview/examples/retrieve-secrets.mdx b/docs/api-reference/overview/examples/retrieve-secrets.mdx index 2a314e65d..2f34339c5 100644 --- a/docs/api-reference/overview/examples/retrieve-secrets.mdx +++ b/docs/api-reference/overview/examples/retrieve-secrets.mdx @@ -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(); +``` + + + 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. + + + 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. + diff --git a/docs/api-reference/overview/examples/update-secrets.mdx b/docs/api-reference/overview/examples/update-secrets.mdx index b302710a3..b8566d9b3 100644 --- a/docs/api-reference/overview/examples/update-secrets.mdx +++ b/docs/api-reference/overview/examples/update-secrets.mdx @@ -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(); +``` + + + 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. + + + 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. + diff --git a/docs/api-reference/overview/introduction.mdx b/docs/api-reference/overview/introduction.mdx index f93ba91fe..7babef955 100644 --- a/docs/api-reference/overview/introduction.mdx +++ b/docs/api-reference/overview/introduction.mdx @@ -2,8 +2,13 @@ title: "Introduction" --- + + Infisical's REST API is currently unavailable and scheduled to go live on Jan + 16! + + 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. + + + 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 + diff --git a/docs/mint.json b/docs/mint.json index 39a3726d9..77d17c6c3 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -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" ] } ] diff --git a/docs/spec.yaml b/docs/spec.yaml index dd62f167b..991682871 100644 --- a/docs/spec.yaml +++ b/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 From 5085376f11f8f52ddb16af33c0c85f22770be600 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Sat, 14 Jan 2023 21:07:17 +0700 Subject: [PATCH 5/5] Check v2 workspace membership routes (currently not routed) --- backend/src/controllers/v2/workspaceController.ts | 2 +- backend/src/middleware/requireMembershipAuth.ts | 3 ++- backend/src/routes/v2/workspace.ts | 2 +- frontend/components/utilities/attemptLogin.ts | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/backend/src/controllers/v2/workspaceController.ts b/backend/src/controllers/v2/workspaceController.ts index 3f929ee4a..39a1be5fd 100644 --- a/backend/src/controllers/v2/workspaceController.ts +++ b/backend/src/controllers/v2/workspaceController.ts @@ -302,7 +302,7 @@ export const getWorkspaceMemberships = async (req: Request, res: Response) => { Sentry.setUser({ email: req.user.email }); Sentry.captureException(err); return res.status(400).send({ - message: 'Failed to get workspace members' + message: 'Failed to get workspace memberships' }); } diff --git a/backend/src/middleware/requireMembershipAuth.ts b/backend/src/middleware/requireMembershipAuth.ts index b44d25ceb..06c0980a3 100644 --- a/backend/src/middleware/requireMembershipAuth.ts +++ b/backend/src/middleware/requireMembershipAuth.ts @@ -45,7 +45,8 @@ const requireMembershipAuth = ({ }); req.targetMembership = targetMembership; - + + return next(); } catch (err) { return next(UnauthorizedRequestError({ message: 'Unable to validate workspace membership' diff --git a/backend/src/routes/v2/workspace.ts b/backend/src/routes/v2/workspace.ts index ebbfe0d4c..ca920e15a 100644 --- a/backend/src/routes/v2/workspace.ts +++ b/backend/src/routes/v2/workspace.ts @@ -68,7 +68,7 @@ router.get( workspaceController.getWorkspaceServiceTokenData ); -// TODO: /POST to create membership +// TODO: /POST to create membership and re-route inviting user to workspace there router.get( // new - TODO: rewire dashboard to this route '/:workspaceId/memberships', diff --git a/frontend/components/utilities/attemptLogin.ts b/frontend/components/utilities/attemptLogin.ts index f1aa03ffb..1a960af10 100644 --- a/frontend/components/utilities/attemptLogin.ts +++ b/frontend/components/utilities/attemptLogin.ts @@ -61,7 +61,7 @@ const attemptLogin = async ( // if everything works, go the main dashboard page. const { token, publicKey, encryptedPrivateKey, iv, tag } = await login2(email, clientProof); - + SecurityClient.setToken(token); const privateKey = Aes256Gcm.decrypt({