mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 10:28:22 +00:00
Adjust ST V3
This commit is contained in:
@@ -93,6 +93,7 @@ jobs:
|
|||||||
tags: infisical/frontend:test
|
tags: infisical/frontend:test
|
||||||
build-args: |
|
build-args: |
|
||||||
POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }}
|
POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }}
|
||||||
|
NEXT_INFISICAL_PLATFORM_VERSION=${{ steps.extract_version.outputs.version }}
|
||||||
- name: ⏻ Spawn frontend container
|
- name: ⏻ Spawn frontend container
|
||||||
run: |
|
run: |
|
||||||
docker run -d --rm --name infisical-frontend-test infisical/frontend:test
|
docker run -d --rm --name infisical-frontend-test infisical/frontend:test
|
||||||
@@ -116,3 +117,4 @@ jobs:
|
|||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
build-args: |
|
build-args: |
|
||||||
POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }}
|
POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }}
|
||||||
|
NEXT_INFISICAL_PLATFORM_VERSION=${{ steps.extract_version.outputs.version }}
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ jobs:
|
|||||||
tags: infisical/staging_deployment_frontend:test
|
tags: infisical/staging_deployment_frontend:test
|
||||||
build-args: |
|
build-args: |
|
||||||
POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }}
|
POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }}
|
||||||
|
NEXT_INFISICAL_PLATFORM_VERSION=${{ steps.extract_version.outputs.version }}
|
||||||
- name: ⏻ Spawn frontend container
|
- name: ⏻ Spawn frontend container
|
||||||
run: |
|
run: |
|
||||||
docker run -d --rm --name infisical-frontend-test infisical/staging_deployment_frontend:test
|
docker run -d --rm --name infisical-frontend-test infisical/staging_deployment_frontend:test
|
||||||
@@ -105,6 +106,7 @@ jobs:
|
|||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
build-args: |
|
build-args: |
|
||||||
POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }}
|
POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }}
|
||||||
|
NEXT_INFISICAL_PLATFORM_VERSION=${{ steps.extract_version.outputs.version }}
|
||||||
gamma-deployment:
|
gamma-deployment:
|
||||||
name: Deploy to gamma
|
name: Deploy to gamma
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
<h1 align="center">
|
<h1 align="center">
|
||||||
<img width="300" src="/img/logoname-black.svg#gh-light-mode-only" alt="infisical">
|
|
||||||
<img width="300" src="/img/logoname-white.svg#gh-dark-mode-only" alt="infisical">
|
<img width="300" src="/img/logoname-white.svg#gh-dark-mode-only" alt="infisical">
|
||||||
</h1>
|
</h1>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<p align="center"><b>Open-source, end-to-end encrypted secret management platform</b>: distribute secrets/configs across your team/infrastructure and prevent secret leaks.</p>
|
<p align="center"><b>The open-source secret management platform</b>: Sync secrets/configs across your team/infrastructure and prevent secret leaks.</p>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h4 align="center">
|
<h4 align="center">
|
||||||
@@ -44,11 +43,11 @@
|
|||||||
</a>
|
</a>
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
<img src="/img/infisical_github_repo.png" width="100%" alt="Dashboard" />
|
<img src="/img/infisical_github_repo2.png" width="100%" alt="Dashboard" />
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
**[Infisical](https://infisical.com)** is an open source, end-to-end encrypted secret management platform that teams use to centralize their secrets like API keys, database credentials, and configurations.
|
**[Infisical](https://infisical.com)** is the open source secret management platform that teams use to centralize their secrets like API keys, database credentials, and configurations.
|
||||||
|
|
||||||
We're on a mission to make secret management more accessible to everyone, not just security teams, and that means redesigning the entire developer experience from ground up.
|
We're on a mission to make secret management more accessible to everyone, not just security teams, and that means redesigning the entire developer experience from ground up.
|
||||||
|
|
||||||
|
|||||||
Generated
+27
-7
@@ -50,6 +50,7 @@
|
|||||||
"nodemailer": "^6.8.0",
|
"nodemailer": "^6.8.0",
|
||||||
"passport": "^0.6.0",
|
"passport": "^0.6.0",
|
||||||
"passport-github": "^1.1.0",
|
"passport-github": "^1.1.0",
|
||||||
|
"passport-gitlab2": "^5.0.0",
|
||||||
"passport-google-oauth20": "^2.0.0",
|
"passport-google-oauth20": "^2.0.0",
|
||||||
"posthog-node": "^2.6.0",
|
"posthog-node": "^2.6.0",
|
||||||
"probot": "^12.3.1",
|
"probot": "^12.3.1",
|
||||||
@@ -63,7 +64,7 @@
|
|||||||
"utility-types": "^3.10.0",
|
"utility-types": "^3.10.0",
|
||||||
"winston": "^3.8.2",
|
"winston": "^3.8.2",
|
||||||
"winston-loki": "^6.0.6",
|
"winston-loki": "^6.0.6",
|
||||||
"zod": "^3.21.4"
|
"zod": "^3.22.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@jest/globals": "^29.3.1",
|
"@jest/globals": "^29.3.1",
|
||||||
@@ -13727,6 +13728,17 @@
|
|||||||
"node": ">= 0.4.0"
|
"node": ">= 0.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/passport-gitlab2": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/passport-gitlab2/-/passport-gitlab2-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-cXQMgM6JQx9wHVh7JLH30D8fplfwjsDwRz+zS0pqC8JS+4bNmc1J04NGp5g2M4yfwylH9kQRrMN98GxMw7q7cg==",
|
||||||
|
"dependencies": {
|
||||||
|
"passport-oauth2": "^1.4.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/passport-google-oauth20": {
|
"node_modules/passport-google-oauth20": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/passport-google-oauth20/-/passport-google-oauth20-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/passport-google-oauth20/-/passport-google-oauth20-2.0.0.tgz",
|
||||||
@@ -16684,9 +16696,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/zod": {
|
"node_modules/zod": {
|
||||||
"version": "3.21.4",
|
"version": "3.22.3",
|
||||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz",
|
"resolved": "https://registry.npmjs.org/zod/-/zod-3.22.3.tgz",
|
||||||
"integrity": "sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==",
|
"integrity": "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==",
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
}
|
}
|
||||||
@@ -27163,6 +27175,14 @@
|
|||||||
"passport-oauth2": "1.x.x"
|
"passport-oauth2": "1.x.x"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"passport-gitlab2": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/passport-gitlab2/-/passport-gitlab2-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-cXQMgM6JQx9wHVh7JLH30D8fplfwjsDwRz+zS0pqC8JS+4bNmc1J04NGp5g2M4yfwylH9kQRrMN98GxMw7q7cg==",
|
||||||
|
"requires": {
|
||||||
|
"passport-oauth2": "^1.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"passport-google-oauth20": {
|
"passport-google-oauth20": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/passport-google-oauth20/-/passport-google-oauth20-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/passport-google-oauth20/-/passport-google-oauth20-2.0.0.tgz",
|
||||||
@@ -29384,9 +29404,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"zod": {
|
"zod": {
|
||||||
"version": "3.21.4",
|
"version": "3.22.3",
|
||||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz",
|
"resolved": "https://registry.npmjs.org/zod/-/zod-3.22.3.tgz",
|
||||||
"integrity": "sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw=="
|
"integrity": "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,7 @@
|
|||||||
"nodemailer": "^6.8.0",
|
"nodemailer": "^6.8.0",
|
||||||
"passport": "^0.6.0",
|
"passport": "^0.6.0",
|
||||||
"passport-github": "^1.1.0",
|
"passport-github": "^1.1.0",
|
||||||
|
"passport-gitlab2": "^5.0.0",
|
||||||
"passport-google-oauth20": "^2.0.0",
|
"passport-google-oauth20": "^2.0.0",
|
||||||
"posthog-node": "^2.6.0",
|
"posthog-node": "^2.6.0",
|
||||||
"probot": "^12.3.1",
|
"probot": "^12.3.1",
|
||||||
@@ -54,7 +55,7 @@
|
|||||||
"utility-types": "^3.10.0",
|
"utility-types": "^3.10.0",
|
||||||
"winston": "^3.8.2",
|
"winston": "^3.8.2",
|
||||||
"winston-loki": "^6.0.6",
|
"winston-loki": "^6.0.6",
|
||||||
"zod": "^3.21.4"
|
"zod": "^3.22.3"
|
||||||
},
|
},
|
||||||
"name": "infisical-api",
|
"name": "infisical-api",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { GITLAB_URL } from "../variables";
|
||||||
|
|
||||||
import InfisicalClient from "infisical-node";
|
import InfisicalClient from "infisical-node";
|
||||||
|
|
||||||
export const client = new InfisicalClient({
|
export const client = new InfisicalClient({
|
||||||
@@ -53,6 +55,9 @@ export const getClientIdGoogleLogin = async () => (await client.getSecret("CLIEN
|
|||||||
export const getClientSecretGoogleLogin = async () => (await client.getSecret("CLIENT_SECRET_GOOGLE_LOGIN")).secretValue;
|
export const getClientSecretGoogleLogin = async () => (await client.getSecret("CLIENT_SECRET_GOOGLE_LOGIN")).secretValue;
|
||||||
export const getClientIdGitHubLogin = async () => (await client.getSecret("CLIENT_ID_GITHUB_LOGIN")).secretValue;
|
export const getClientIdGitHubLogin = async () => (await client.getSecret("CLIENT_ID_GITHUB_LOGIN")).secretValue;
|
||||||
export const getClientSecretGitHubLogin = async () => (await client.getSecret("CLIENT_SECRET_GITHUB_LOGIN")).secretValue;
|
export const getClientSecretGitHubLogin = async () => (await client.getSecret("CLIENT_SECRET_GITHUB_LOGIN")).secretValue;
|
||||||
|
export const getClientIdGitLabLogin = async () => (await client.getSecret("CLIENT_ID_GITLAB_LOGIN")).secretValue;
|
||||||
|
export const getClientSecretGitLabLogin = async () => (await client.getSecret("CLIENT_SECRET_GITLAB_LOGIN")).secretValue;
|
||||||
|
export const getUrlGitLabLogin = async () => (await client.getSecret("URL_GITLAB_LOGIN")).secretValue || GITLAB_URL;
|
||||||
|
|
||||||
export const getPostHogHost = async () => (await client.getSecret("POSTHOG_HOST")).secretValue || "https://app.posthog.com";
|
export const getPostHogHost = async () => (await client.getSecret("POSTHOG_HOST")).secretValue || "https://app.posthog.com";
|
||||||
export const getPostHogProjectApiKey = async () => (await client.getSecret("POSTHOG_PROJECT_API_KEY")).secretValue || "phc_nSin8j5q2zdhpFDI1ETmFNUIuTG4DwKVyIigrY10XiE";
|
export const getPostHogProjectApiKey = async () => (await client.getSecret("POSTHOG_PROJECT_API_KEY")).secretValue || "phc_nSin8j5q2zdhpFDI1ETmFNUIuTG4DwKVyIigrY10XiE";
|
||||||
|
|||||||
@@ -34,10 +34,10 @@ import { Webhook } from "../../models";
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const createWorkspaceEnvironment = async (req: Request, res: Response) => {
|
export const createWorkspaceEnvironment = async (req: Request, res: Response) => {
|
||||||
/*
|
/*
|
||||||
#swagger.summary = 'Create environment'
|
#swagger.summary = 'Create environment'
|
||||||
#swagger.description = 'Create environment'
|
#swagger.description = 'Create environment'
|
||||||
|
|
||||||
#swagger.security = [{
|
#swagger.security = [{
|
||||||
"apiKeyAuth": []
|
"apiKeyAuth": []
|
||||||
}]
|
}]
|
||||||
@@ -46,12 +46,12 @@ export const createWorkspaceEnvironment = async (req: Request, res: Response) =>
|
|||||||
"description": "ID of project",
|
"description": "ID of project",
|
||||||
"required": true,
|
"required": true,
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
#swagger.summary = 'Create environment'
|
#swagger.summary = 'Create environment'
|
||||||
#swagger.description = 'Create environment'
|
#swagger.description = 'Create environment'
|
||||||
|
|
||||||
#swagger.security = [{
|
#swagger.security = [{
|
||||||
"apiKeyAuth": []
|
"apiKeyAuth": []
|
||||||
}]
|
}]
|
||||||
@@ -60,7 +60,7 @@ export const createWorkspaceEnvironment = async (req: Request, res: Response) =>
|
|||||||
"description": "ID of project",
|
"description": "ID of project",
|
||||||
"required": true,
|
"required": true,
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
|
|
||||||
#swagger.requestBody = {
|
#swagger.requestBody = {
|
||||||
content: {
|
content: {
|
||||||
@@ -88,7 +88,7 @@ export const createWorkspaceEnvironment = async (req: Request, res: Response) =>
|
|||||||
#swagger.responses[200] = {
|
#swagger.responses[200] = {
|
||||||
content: {
|
content: {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"message": {
|
"message": {
|
||||||
@@ -115,7 +115,7 @@ export const createWorkspaceEnvironment = async (req: Request, res: Response) =>
|
|||||||
},
|
},
|
||||||
"description": "Response after creating a new environment"
|
"description": "Response after creating a new environment"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
@@ -246,7 +246,7 @@ export const reorderWorkspaceEnvironments = async (req: Request, res: Response)
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const renameWorkspaceEnvironment = async (req: Request, res: Response) => {
|
export const renameWorkspaceEnvironment = async (req: Request, res: Response) => {
|
||||||
/*
|
/*
|
||||||
#swagger.summary = 'Rename workspace environment'
|
#swagger.summary = 'Rename workspace environment'
|
||||||
#swagger.description = 'Rename a specific environment within a workspace'
|
#swagger.description = 'Rename a specific environment within a workspace'
|
||||||
|
|
||||||
@@ -317,7 +317,7 @@ export const renameWorkspaceEnvironment = async (req: Request, res: Response) =>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
const {
|
const {
|
||||||
params: { workspaceId },
|
params: { workspaceId },
|
||||||
@@ -394,6 +394,11 @@ export const renameWorkspaceEnvironment = async (req: Request, res: Response) =>
|
|||||||
{ workspace: workspaceId, environment: oldEnvironmentSlug },
|
{ workspace: workspaceId, environment: oldEnvironmentSlug },
|
||||||
{ environment: environmentSlug }
|
{ environment: environmentSlug }
|
||||||
);
|
);
|
||||||
|
await SecretImport.updateMany(
|
||||||
|
{ workspace: workspaceId, "imports.environment": oldEnvironmentSlug },
|
||||||
|
{ $set: { "imports.$[element].environment": environmentSlug } },
|
||||||
|
{ arrayFilters: [{ "element.environment": oldEnvironmentSlug }] },
|
||||||
|
);
|
||||||
|
|
||||||
await ServiceAccountWorkspacePermission.updateMany(
|
await ServiceAccountWorkspacePermission.updateMany(
|
||||||
{ workspace: workspaceId, environment: oldEnvironmentSlug },
|
{ workspace: workspaceId, environment: oldEnvironmentSlug },
|
||||||
@@ -447,10 +452,10 @@ export const renameWorkspaceEnvironment = async (req: Request, res: Response) =>
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const deleteWorkspaceEnvironment = async (req: Request, res: Response) => {
|
export const deleteWorkspaceEnvironment = async (req: Request, res: Response) => {
|
||||||
/*
|
/*
|
||||||
#swagger.summary = 'Delete workspace environment'
|
#swagger.summary = 'Delete workspace environment'
|
||||||
#swagger.description = 'Delete a specific environment from a workspace'
|
#swagger.description = 'Delete a specific environment from a workspace'
|
||||||
|
|
||||||
#swagger.security = [{
|
#swagger.security = [{
|
||||||
"apiKeyAuth": []
|
"apiKeyAuth": []
|
||||||
}]
|
}]
|
||||||
@@ -483,7 +488,7 @@ export const deleteWorkspaceEnvironment = async (req: Request, res: Response) =>
|
|||||||
#swagger.responses[200] = {
|
#swagger.responses[200] = {
|
||||||
content: {
|
content: {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"message": {
|
"message": {
|
||||||
@@ -501,9 +506,9 @@ export const deleteWorkspaceEnvironment = async (req: Request, res: Response) =>
|
|||||||
},
|
},
|
||||||
"description": "Response after deleting an environment from a workspace"
|
"description": "Response after deleting an environment from a workspace"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
const {
|
const {
|
||||||
params: { workspaceId },
|
params: { workspaceId },
|
||||||
@@ -590,10 +595,10 @@ export const deleteWorkspaceEnvironment = async (req: Request, res: Response) =>
|
|||||||
|
|
||||||
// TODO(akhilmhdh) after rbac this can be completely removed
|
// TODO(akhilmhdh) after rbac this can be completely removed
|
||||||
export const getAllAccessibleEnvironmentsOfWorkspace = async (req: Request, res: Response) => {
|
export const getAllAccessibleEnvironmentsOfWorkspace = async (req: Request, res: Response) => {
|
||||||
/*
|
/*
|
||||||
#swagger.summary = 'Get all accessible environments of a workspace'
|
#swagger.summary = 'Get all accessible environments of a workspace'
|
||||||
#swagger.description = 'Fetch all environments that the user has access to in a specified workspace'
|
#swagger.description = 'Fetch all environments that the user has access to in a specified workspace'
|
||||||
|
|
||||||
#swagger.security = [{
|
#swagger.security = [{
|
||||||
"apiKeyAuth": []
|
"apiKeyAuth": []
|
||||||
}]
|
}]
|
||||||
@@ -640,7 +645,7 @@ export const getAllAccessibleEnvironmentsOfWorkspace = async (req: Request, res:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
const {
|
const {
|
||||||
params: { workspaceId }
|
params: { workspaceId }
|
||||||
|
|||||||
@@ -34,6 +34,93 @@ import {
|
|||||||
} from "../../validation";
|
} from "../../validation";
|
||||||
import { PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS } from "../../variables";
|
import { PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS } from "../../variables";
|
||||||
import { ActorType } from "../../ee/models";
|
import { ActorType } from "../../ee/models";
|
||||||
|
import { UnauthorizedRequestError } from "../../utils/errors";
|
||||||
|
import { AuthData } from "../../interfaces/middleware";
|
||||||
|
|
||||||
|
const checkSecretsPermission = async ({
|
||||||
|
authData,
|
||||||
|
workspaceId,
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
secretAction
|
||||||
|
}: {
|
||||||
|
authData: AuthData;
|
||||||
|
workspaceId: string;
|
||||||
|
environment: string;
|
||||||
|
secretPath: string;
|
||||||
|
secretAction: ProjectPermissionActions; // CRUD
|
||||||
|
}): Promise<(env: string, secPath: string) => boolean> => {
|
||||||
|
|
||||||
|
let STV2RequiredPermissions = [];
|
||||||
|
let STV3RequiredPermissions: Permission[] = [];
|
||||||
|
|
||||||
|
switch (secretAction) {
|
||||||
|
case ProjectPermissionActions.Create:
|
||||||
|
STV2RequiredPermissions = [PERMISSION_WRITE_SECRETS];
|
||||||
|
STV3RequiredPermissions = [Permission.WRITE];
|
||||||
|
break;
|
||||||
|
case ProjectPermissionActions.Read:
|
||||||
|
STV2RequiredPermissions = [PERMISSION_READ_SECRETS];
|
||||||
|
STV3RequiredPermissions = [Permission.READ];
|
||||||
|
break;
|
||||||
|
case ProjectPermissionActions.Edit:
|
||||||
|
STV2RequiredPermissions = [PERMISSION_WRITE_SECRETS];
|
||||||
|
STV3RequiredPermissions = [Permission.WRITE];
|
||||||
|
break;
|
||||||
|
case ProjectPermissionActions.Delete:
|
||||||
|
STV2RequiredPermissions = [PERMISSION_WRITE_SECRETS];
|
||||||
|
STV3RequiredPermissions = [Permission.WRITE];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (authData.actor.type) {
|
||||||
|
case ActorType.USER: {
|
||||||
|
const { permission } = await getUserProjectPermissions(authData.actor.metadata.userId, workspaceId);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
secretAction,
|
||||||
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
||||||
|
);
|
||||||
|
return (env: string, secPath: string) =>
|
||||||
|
permission.can(
|
||||||
|
secretAction,
|
||||||
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
|
environment: env,
|
||||||
|
secretPath: secPath
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case ActorType.SERVICE: {
|
||||||
|
await validateServiceTokenDataClientForWorkspace({
|
||||||
|
serviceTokenData: authData.authPayload as IServiceTokenData,
|
||||||
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
requiredPermissions: STV2RequiredPermissions
|
||||||
|
});
|
||||||
|
return () => true;
|
||||||
|
}
|
||||||
|
case ActorType.SERVICE_V3: {
|
||||||
|
await validateServiceTokenDataV3ClientForWorkspace({
|
||||||
|
authData,
|
||||||
|
serviceTokenData: authData.authPayload as IServiceTokenDataV3,
|
||||||
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
requiredPermissions: STV3RequiredPermissions
|
||||||
|
});
|
||||||
|
return (env: string, secPath: string) =>
|
||||||
|
isValidScopeV3({
|
||||||
|
authPayload: authData.authPayload as IServiceTokenDataV3,
|
||||||
|
environment: env,
|
||||||
|
secretPath: secPath,
|
||||||
|
requiredPermissions: STV3RequiredPermissions
|
||||||
|
});
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
throw UnauthorizedRequestError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return secrets for workspace with id [workspaceId] and environment
|
* Return secrets for workspace with id [workspaceId] and environment
|
||||||
@@ -73,54 +160,13 @@ export const getSecretsRaw = async (req: Request, res: Response) => {
|
|||||||
if (!environment || !workspaceId)
|
if (!environment || !workspaceId)
|
||||||
throw BadRequestError({ message: "Missing environment or workspace id" });
|
throw BadRequestError({ message: "Missing environment or workspace id" });
|
||||||
|
|
||||||
let permissionCheckFn: (env: string, secPath: string) => boolean; // used to pass as callback function to import secret
|
const permissionCheckFn = await checkSecretsPermission({
|
||||||
switch (req.authData.actor.type) {
|
authData: req.authData,
|
||||||
case ActorType.USER: {
|
workspaceId,
|
||||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
environment,
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
secretPath,
|
||||||
ProjectPermissionActions.Read,
|
secretAction: ProjectPermissionActions.Read
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
});
|
||||||
);
|
|
||||||
permissionCheckFn = (env: string, secPath: string) =>
|
|
||||||
permission.can(
|
|
||||||
ProjectPermissionActions.Read,
|
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
|
||||||
environment: env,
|
|
||||||
secretPath: secPath
|
|
||||||
})
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ActorType.SERVICE: {
|
|
||||||
await validateServiceTokenDataClientForWorkspace({
|
|
||||||
serviceTokenData: req.authData.authPayload as IServiceTokenData,
|
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
|
||||||
environment,
|
|
||||||
secretPath,
|
|
||||||
requiredPermissions: [PERMISSION_READ_SECRETS]
|
|
||||||
});
|
|
||||||
permissionCheckFn = () => true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ActorType.SERVICE_V3: {
|
|
||||||
await validateServiceTokenDataV3ClientForWorkspace({
|
|
||||||
authData: req.authData,
|
|
||||||
serviceTokenData: req.authData.authPayload as IServiceTokenDataV3,
|
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
|
||||||
environment,
|
|
||||||
secretPath,
|
|
||||||
requiredPermissions: [Permission.READ]
|
|
||||||
});
|
|
||||||
permissionCheckFn = (env: string, secPath: string) =>
|
|
||||||
isValidScopeV3({
|
|
||||||
authPayload: req.authData.authPayload as IServiceTokenDataV3,
|
|
||||||
environment: env,
|
|
||||||
secretPath: secPath,
|
|
||||||
requiredPermissions: [Permission.READ]
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const secrets = await SecretService.getSecrets({
|
const secrets = await SecretService.getSecrets({
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
@@ -186,38 +232,14 @@ export const getSecretByNameRaw = async (req: Request, res: Response) => {
|
|||||||
query: { secretPath, environment, workspaceId, type, include_imports },
|
query: { secretPath, environment, workspaceId, type, include_imports },
|
||||||
params: { secretName }
|
params: { secretName }
|
||||||
} = await validateRequest(reqValidator.GetSecretByNameRawV3, req);
|
} = await validateRequest(reqValidator.GetSecretByNameRawV3, req);
|
||||||
|
|
||||||
switch (req.authData.actor.type) {
|
await checkSecretsPermission({
|
||||||
case ActorType.USER: {
|
authData: req.authData,
|
||||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
workspaceId,
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
environment,
|
||||||
ProjectPermissionActions.Read,
|
secretPath,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
secretAction: ProjectPermissionActions.Read
|
||||||
);
|
});
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ActorType.SERVICE: {
|
|
||||||
await validateServiceTokenDataClientForWorkspace({
|
|
||||||
serviceTokenData: req.authData.authPayload as IServiceTokenData,
|
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
|
||||||
environment,
|
|
||||||
secretPath,
|
|
||||||
requiredPermissions: [PERMISSION_READ_SECRETS]
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ActorType.SERVICE_V3: {
|
|
||||||
await validateServiceTokenDataV3ClientForWorkspace({
|
|
||||||
authData: req.authData,
|
|
||||||
serviceTokenData: req.authData.authPayload as IServiceTokenDataV3,
|
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
|
||||||
environment,
|
|
||||||
secretPath,
|
|
||||||
requiredPermissions: [Permission.READ]
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const secret = await SecretService.getSecret({
|
const secret = await SecretService.getSecret({
|
||||||
secretName,
|
secretName,
|
||||||
@@ -260,37 +282,13 @@ export const createSecretRaw = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
} = await validateRequest(reqValidator.CreateSecretRawV3, req);
|
} = await validateRequest(reqValidator.CreateSecretRawV3, req);
|
||||||
|
|
||||||
switch (req.authData.actor.type) {
|
await checkSecretsPermission({
|
||||||
case ActorType.USER: {
|
authData: req.authData,
|
||||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
workspaceId,
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
environment,
|
||||||
ProjectPermissionActions.Create,
|
secretPath,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
secretAction: ProjectPermissionActions.Create
|
||||||
);
|
});
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ActorType.SERVICE: {
|
|
||||||
await validateServiceTokenDataClientForWorkspace({
|
|
||||||
serviceTokenData: req.authData.authPayload as IServiceTokenData,
|
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
|
||||||
environment,
|
|
||||||
secretPath,
|
|
||||||
requiredPermissions: [PERMISSION_WRITE_SECRETS]
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ActorType.SERVICE_V3: {
|
|
||||||
await validateServiceTokenDataV3ClientForWorkspace({
|
|
||||||
authData: req.authData,
|
|
||||||
serviceTokenData: req.authData.authPayload as IServiceTokenDataV3,
|
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
|
||||||
environment,
|
|
||||||
secretPath,
|
|
||||||
requiredPermissions: [Permission.WRITE]
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const key = await BotService.getWorkspaceKeyWithBot({
|
const key = await BotService.getWorkspaceKeyWithBot({
|
||||||
workspaceId: new Types.ObjectId(workspaceId)
|
workspaceId: new Types.ObjectId(workspaceId)
|
||||||
@@ -360,37 +358,13 @@ export const updateSecretByNameRaw = async (req: Request, res: Response) => {
|
|||||||
body: { secretValue, environment, secretPath, type, workspaceId, skipMultilineEncoding }
|
body: { secretValue, environment, secretPath, type, workspaceId, skipMultilineEncoding }
|
||||||
} = await validateRequest(reqValidator.UpdateSecretByNameRawV3, req);
|
} = await validateRequest(reqValidator.UpdateSecretByNameRawV3, req);
|
||||||
|
|
||||||
switch (req.authData.actor.type) {
|
await checkSecretsPermission({
|
||||||
case ActorType.USER: {
|
authData: req.authData,
|
||||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
workspaceId,
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
environment,
|
||||||
ProjectPermissionActions.Edit,
|
secretPath,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
secretAction: ProjectPermissionActions.Edit
|
||||||
);
|
});
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ActorType.SERVICE: {
|
|
||||||
await validateServiceTokenDataClientForWorkspace({
|
|
||||||
serviceTokenData: req.authData.authPayload as IServiceTokenData,
|
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
|
||||||
environment,
|
|
||||||
secretPath,
|
|
||||||
requiredPermissions: [PERMISSION_WRITE_SECRETS]
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ActorType.SERVICE_V3: {
|
|
||||||
await validateServiceTokenDataV3ClientForWorkspace({
|
|
||||||
authData: req.authData,
|
|
||||||
serviceTokenData: req.authData.authPayload as IServiceTokenDataV3,
|
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
|
||||||
environment,
|
|
||||||
secretPath,
|
|
||||||
requiredPermissions: [Permission.WRITE]
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const key = await BotService.getWorkspaceKeyWithBot({
|
const key = await BotService.getWorkspaceKeyWithBot({
|
||||||
workspaceId: new Types.ObjectId(workspaceId)
|
workspaceId: new Types.ObjectId(workspaceId)
|
||||||
@@ -441,37 +415,13 @@ export const deleteSecretByNameRaw = async (req: Request, res: Response) => {
|
|||||||
body: { environment, secretPath, type, workspaceId }
|
body: { environment, secretPath, type, workspaceId }
|
||||||
} = await validateRequest(reqValidator.DeleteSecretByNameRawV3, req);
|
} = await validateRequest(reqValidator.DeleteSecretByNameRawV3, req);
|
||||||
|
|
||||||
switch (req.authData.actor.type) {
|
await checkSecretsPermission({
|
||||||
case ActorType.USER: {
|
authData: req.authData,
|
||||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
workspaceId,
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
environment,
|
||||||
ProjectPermissionActions.Delete,
|
secretPath,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
secretAction: ProjectPermissionActions.Delete
|
||||||
);
|
});
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ActorType.SERVICE: {
|
|
||||||
await validateServiceTokenDataClientForWorkspace({
|
|
||||||
serviceTokenData: req.authData.authPayload as IServiceTokenData,
|
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
|
||||||
environment,
|
|
||||||
secretPath,
|
|
||||||
requiredPermissions: [PERMISSION_WRITE_SECRETS]
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ActorType.SERVICE_V3: {
|
|
||||||
await validateServiceTokenDataV3ClientForWorkspace({
|
|
||||||
authData: req.authData,
|
|
||||||
serviceTokenData: req.authData.authPayload as IServiceTokenDataV3,
|
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
|
||||||
environment,
|
|
||||||
secretPath,
|
|
||||||
requiredPermissions: [Permission.WRITE]
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const { secret } = await SecretService.deleteSecret({
|
const { secret } = await SecretService.deleteSecret({
|
||||||
secretName,
|
secretName,
|
||||||
@@ -525,55 +475,13 @@ export const getSecrets = async (req: Request, res: Response) => {
|
|||||||
secretPath = getFolderWithPathFromId(folder.nodes, folderId).folderPath;
|
secretPath = getFolderWithPathFromId(folder.nodes, folderId).folderPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
let permissionCheckFn: (env: string, secPath: string) => boolean; // used to pass as callback function to import secret
|
const permissionCheckFn = await checkSecretsPermission({
|
||||||
|
authData: req.authData,
|
||||||
switch (req.authData.actor.type) {
|
workspaceId,
|
||||||
case ActorType.USER: {
|
environment,
|
||||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
secretPath,
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
secretAction: ProjectPermissionActions.Read
|
||||||
ProjectPermissionActions.Read,
|
});
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
|
||||||
);
|
|
||||||
permissionCheckFn = (env: string, secPath: string) =>
|
|
||||||
permission.can(
|
|
||||||
ProjectPermissionActions.Read,
|
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
|
||||||
environment: env,
|
|
||||||
secretPath: secPath
|
|
||||||
})
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ActorType.SERVICE: {
|
|
||||||
await validateServiceTokenDataClientForWorkspace({
|
|
||||||
serviceTokenData: req.authData.authPayload as IServiceTokenData,
|
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
|
||||||
environment,
|
|
||||||
secretPath,
|
|
||||||
requiredPermissions: [PERMISSION_READ_SECRETS]
|
|
||||||
});
|
|
||||||
permissionCheckFn = (env: string, secPath: string) => true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ActorType.SERVICE_V3: {
|
|
||||||
await validateServiceTokenDataV3ClientForWorkspace({
|
|
||||||
authData: req.authData,
|
|
||||||
serviceTokenData: req.authData.authPayload as IServiceTokenDataV3,
|
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
|
||||||
environment,
|
|
||||||
secretPath,
|
|
||||||
requiredPermissions: [Permission.READ]
|
|
||||||
});
|
|
||||||
permissionCheckFn = (env: string, secPath: string) =>
|
|
||||||
isValidScopeV3({
|
|
||||||
authPayload: req.authData.authPayload as IServiceTokenDataV3,
|
|
||||||
environment: env,
|
|
||||||
secretPath: secPath,
|
|
||||||
requiredPermissions: [Permission.READ]
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const secrets = await SecretService.getSecrets({
|
const secrets = await SecretService.getSecrets({
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
@@ -622,37 +530,13 @@ export const getSecretByName = async (req: Request, res: Response) => {
|
|||||||
params: { secretName }
|
params: { secretName }
|
||||||
} = await validateRequest(reqValidator.GetSecretByNameV3, req);
|
} = await validateRequest(reqValidator.GetSecretByNameV3, req);
|
||||||
|
|
||||||
switch (req.authData.actor.type) {
|
await checkSecretsPermission({
|
||||||
case ActorType.USER: {
|
authData: req.authData,
|
||||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
workspaceId,
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
environment,
|
||||||
ProjectPermissionActions.Read,
|
secretPath,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
secretAction: ProjectPermissionActions.Read
|
||||||
);
|
});
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ActorType.SERVICE: {
|
|
||||||
await validateServiceTokenDataClientForWorkspace({
|
|
||||||
serviceTokenData: req.authData.authPayload as IServiceTokenData,
|
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
|
||||||
environment,
|
|
||||||
secretPath,
|
|
||||||
requiredPermissions: [PERMISSION_READ_SECRETS]
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ActorType.SERVICE_V3: {
|
|
||||||
await validateServiceTokenDataV3ClientForWorkspace({
|
|
||||||
authData: req.authData,
|
|
||||||
serviceTokenData: req.authData.authPayload as IServiceTokenDataV3,
|
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
|
||||||
environment,
|
|
||||||
secretPath,
|
|
||||||
requiredPermissions: [Permission.READ]
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const secret = await SecretService.getSecret({
|
const secret = await SecretService.getSecret({
|
||||||
secretName,
|
secretName,
|
||||||
@@ -695,38 +579,14 @@ export const createSecret = async (req: Request, res: Response) => {
|
|||||||
},
|
},
|
||||||
params: { secretName }
|
params: { secretName }
|
||||||
} = await validateRequest(reqValidator.CreateSecretV3, req);
|
} = await validateRequest(reqValidator.CreateSecretV3, req);
|
||||||
|
|
||||||
switch (req.authData.actor.type) {
|
await checkSecretsPermission({
|
||||||
case ActorType.USER: {
|
authData: req.authData,
|
||||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
workspaceId,
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
environment,
|
||||||
ProjectPermissionActions.Create,
|
secretPath,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
secretAction: ProjectPermissionActions.Create
|
||||||
);
|
});
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ActorType.SERVICE: {
|
|
||||||
await validateServiceTokenDataClientForWorkspace({
|
|
||||||
serviceTokenData: req.authData.authPayload as IServiceTokenData,
|
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
|
||||||
environment,
|
|
||||||
secretPath,
|
|
||||||
requiredPermissions: [PERMISSION_WRITE_SECRETS]
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ActorType.SERVICE_V3: {
|
|
||||||
await validateServiceTokenDataV3ClientForWorkspace({
|
|
||||||
authData: req.authData,
|
|
||||||
serviceTokenData: req.authData.authPayload as IServiceTokenDataV3,
|
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
|
||||||
environment,
|
|
||||||
secretPath,
|
|
||||||
requiredPermissions: [Permission.WRITE]
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const secret = await SecretService.createSecret({
|
const secret = await SecretService.createSecret({
|
||||||
secretName,
|
secretName,
|
||||||
@@ -796,38 +656,14 @@ export const updateSecretByName = async (req: Request, res: Response) => {
|
|||||||
throw BadRequestError({ message: "Missing encrypted key" });
|
throw BadRequestError({ message: "Missing encrypted key" });
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (req.authData.actor.type) {
|
await checkSecretsPermission({
|
||||||
case ActorType.USER: {
|
authData: req.authData,
|
||||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
workspaceId,
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
environment,
|
||||||
ProjectPermissionActions.Edit,
|
secretPath,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
secretAction: ProjectPermissionActions.Edit
|
||||||
);
|
});
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ActorType.SERVICE: {
|
|
||||||
await validateServiceTokenDataClientForWorkspace({
|
|
||||||
serviceTokenData: req.authData.authPayload as IServiceTokenData,
|
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
|
||||||
environment,
|
|
||||||
secretPath,
|
|
||||||
requiredPermissions: [PERMISSION_WRITE_SECRETS]
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ActorType.SERVICE_V3: {
|
|
||||||
await validateServiceTokenDataV3ClientForWorkspace({
|
|
||||||
authData: req.authData,
|
|
||||||
serviceTokenData: req.authData.authPayload as IServiceTokenDataV3,
|
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
|
||||||
environment,
|
|
||||||
secretPath,
|
|
||||||
requiredPermissions: [Permission.WRITE]
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const secret = await SecretService.updateSecret({
|
const secret = await SecretService.updateSecret({
|
||||||
secretName,
|
secretName,
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
@@ -873,37 +709,13 @@ export const deleteSecretByName = async (req: Request, res: Response) => {
|
|||||||
params: { secretName }
|
params: { secretName }
|
||||||
} = await validateRequest(reqValidator.DeleteSecretByNameV3, req);
|
} = await validateRequest(reqValidator.DeleteSecretByNameV3, req);
|
||||||
|
|
||||||
switch (req.authData.actor.type) {
|
await checkSecretsPermission({
|
||||||
case ActorType.USER: {
|
authData: req.authData,
|
||||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
workspaceId,
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
environment,
|
||||||
ProjectPermissionActions.Delete,
|
secretPath,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
secretAction: ProjectPermissionActions.Delete
|
||||||
);
|
});
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ActorType.SERVICE: {
|
|
||||||
await validateServiceTokenDataClientForWorkspace({
|
|
||||||
serviceTokenData: req.authData.authPayload as IServiceTokenData,
|
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
|
||||||
environment,
|
|
||||||
secretPath,
|
|
||||||
requiredPermissions: [PERMISSION_WRITE_SECRETS]
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ActorType.SERVICE_V3: {
|
|
||||||
await validateServiceTokenDataV3ClientForWorkspace({
|
|
||||||
authData: req.authData,
|
|
||||||
serviceTokenData: req.authData.authPayload as IServiceTokenDataV3,
|
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
|
||||||
environment,
|
|
||||||
secretPath,
|
|
||||||
requiredPermissions: [Permission.WRITE]
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const { secret } = await SecretService.deleteSecret({
|
const { secret } = await SecretService.deleteSecret({
|
||||||
secretName,
|
secretName,
|
||||||
@@ -931,38 +743,14 @@ export const createSecretByNameBatch = async (req: Request, res: Response) => {
|
|||||||
const {
|
const {
|
||||||
body: { secrets, secretPath, environment, workspaceId }
|
body: { secrets, secretPath, environment, workspaceId }
|
||||||
} = await validateRequest(reqValidator.CreateSecretByNameBatchV3, req);
|
} = await validateRequest(reqValidator.CreateSecretByNameBatchV3, req);
|
||||||
|
|
||||||
switch (req.authData.actor.type) {
|
await checkSecretsPermission({
|
||||||
case ActorType.USER: {
|
authData: req.authData,
|
||||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
workspaceId,
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
environment,
|
||||||
ProjectPermissionActions.Create,
|
secretPath,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
secretAction: ProjectPermissionActions.Create
|
||||||
);
|
});
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ActorType.SERVICE: {
|
|
||||||
await validateServiceTokenDataClientForWorkspace({
|
|
||||||
serviceTokenData: req.authData.authPayload as IServiceTokenData,
|
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
|
||||||
environment,
|
|
||||||
secretPath,
|
|
||||||
requiredPermissions: [PERMISSION_WRITE_SECRETS]
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ActorType.SERVICE_V3: {
|
|
||||||
await validateServiceTokenDataV3ClientForWorkspace({
|
|
||||||
authData: req.authData,
|
|
||||||
serviceTokenData: req.authData.authPayload as IServiceTokenDataV3,
|
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
|
||||||
environment,
|
|
||||||
secretPath,
|
|
||||||
requiredPermissions: [Permission.WRITE]
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const createdSecrets = await SecretService.createSecretBatch({
|
const createdSecrets = await SecretService.createSecretBatch({
|
||||||
secretPath,
|
secretPath,
|
||||||
@@ -982,37 +770,13 @@ export const updateSecretByNameBatch = async (req: Request, res: Response) => {
|
|||||||
body: { secrets, secretPath, environment, workspaceId }
|
body: { secrets, secretPath, environment, workspaceId }
|
||||||
} = await validateRequest(reqValidator.UpdateSecretByNameBatchV3, req);
|
} = await validateRequest(reqValidator.UpdateSecretByNameBatchV3, req);
|
||||||
|
|
||||||
switch (req.authData.actor.type) {
|
await checkSecretsPermission({
|
||||||
case ActorType.USER: {
|
authData: req.authData,
|
||||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
workspaceId,
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
environment,
|
||||||
ProjectPermissionActions.Edit,
|
secretPath,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
secretAction: ProjectPermissionActions.Edit
|
||||||
);
|
});
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ActorType.SERVICE: {
|
|
||||||
await validateServiceTokenDataClientForWorkspace({
|
|
||||||
serviceTokenData: req.authData.authPayload as IServiceTokenData,
|
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
|
||||||
environment,
|
|
||||||
secretPath,
|
|
||||||
requiredPermissions: [PERMISSION_WRITE_SECRETS]
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ActorType.SERVICE_V3: {
|
|
||||||
await validateServiceTokenDataV3ClientForWorkspace({
|
|
||||||
authData: req.authData,
|
|
||||||
serviceTokenData: req.authData.authPayload as IServiceTokenDataV3,
|
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
|
||||||
environment,
|
|
||||||
secretPath,
|
|
||||||
requiredPermissions: [Permission.WRITE]
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const updatedSecrets = await SecretService.updateSecretBatch({
|
const updatedSecrets = await SecretService.updateSecretBatch({
|
||||||
secretPath,
|
secretPath,
|
||||||
@@ -1032,37 +796,13 @@ export const deleteSecretByNameBatch = async (req: Request, res: Response) => {
|
|||||||
body: { secrets, secretPath, environment, workspaceId }
|
body: { secrets, secretPath, environment, workspaceId }
|
||||||
} = await validateRequest(reqValidator.DeleteSecretByNameBatchV3, req);
|
} = await validateRequest(reqValidator.DeleteSecretByNameBatchV3, req);
|
||||||
|
|
||||||
switch (req.authData.actor.type) {
|
await checkSecretsPermission({
|
||||||
case ActorType.USER: {
|
authData: req.authData,
|
||||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
workspaceId,
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
environment,
|
||||||
ProjectPermissionActions.Delete,
|
secretPath,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
secretAction: ProjectPermissionActions.Delete
|
||||||
);
|
});
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ActorType.SERVICE: {
|
|
||||||
await validateServiceTokenDataClientForWorkspace({
|
|
||||||
serviceTokenData: req.authData.authPayload as IServiceTokenData,
|
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
|
||||||
environment,
|
|
||||||
secretPath,
|
|
||||||
requiredPermissions: [PERMISSION_WRITE_SECRETS]
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ActorType.SERVICE_V3: {
|
|
||||||
await validateServiceTokenDataV3ClientForWorkspace({
|
|
||||||
authData: req.authData,
|
|
||||||
serviceTokenData: req.authData.authPayload as IServiceTokenDataV3,
|
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
|
||||||
environment,
|
|
||||||
secretPath,
|
|
||||||
requiredPermissions: [Permission.WRITE]
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const deletedSecrets = await SecretService.deleteSecretBatch({
|
const deletedSecrets = await SecretService.deleteSecretBatch({
|
||||||
secretPath,
|
secretPath,
|
||||||
@@ -1075,4 +815,4 @@ export const deleteSecretByNameBatch = async (req: Request, res: Response) => {
|
|||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
secrets: deletedSecrets
|
secrets: deletedSecrets
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -112,7 +112,7 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
|
|||||||
user = req.authData.authPayload._id;
|
user = req.authData.authPayload._id;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isActive = false;
|
const isActive = true;
|
||||||
const serviceTokenData = await new ServiceTokenDataV3({
|
const serviceTokenData = await new ServiceTokenDataV3({
|
||||||
name,
|
name,
|
||||||
user,
|
user,
|
||||||
@@ -160,7 +160,7 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
serviceTokenData,
|
serviceTokenData,
|
||||||
serviceToken: `proj_token.${token}`
|
serviceToken: `stv3.${token}`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,60 +6,20 @@ import { ssoController } from "../../controllers/v1";
|
|||||||
import { authLimiter } from "../../../helpers/rateLimiter";
|
import { authLimiter } from "../../../helpers/rateLimiter";
|
||||||
import { AuthMode } from "../../../variables";
|
import { AuthMode } from "../../../variables";
|
||||||
|
|
||||||
router.get("/redirect/google", authLimiter, (req, res, next) => {
|
|
||||||
passport.authenticate("google", {
|
|
||||||
scope: ["profile", "email"],
|
|
||||||
session: false,
|
|
||||||
...(req.query.callback_port
|
|
||||||
? {
|
|
||||||
state: req.query.callback_port as string
|
|
||||||
}
|
|
||||||
: {})
|
|
||||||
})(req, res, next);
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/google",
|
"/redirect/saml2/:ssoIdentifier",
|
||||||
passport.authenticate("google", {
|
|
||||||
failureRedirect: "/login/provider/error",
|
|
||||||
session: false
|
|
||||||
}),
|
|
||||||
ssoController.redirectSSO
|
|
||||||
);
|
|
||||||
|
|
||||||
router.get("/redirect/github", authLimiter, (req, res, next) => {
|
|
||||||
passport.authenticate("github", {
|
|
||||||
session: false,
|
|
||||||
scope: [ 'user:email' ],
|
|
||||||
...(req.query.callback_port
|
|
||||||
? {
|
|
||||||
state: req.query.callback_port as string
|
|
||||||
}
|
|
||||||
: {})
|
|
||||||
})(req, res, next);
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get(
|
|
||||||
"/github",
|
|
||||||
authLimiter,
|
authLimiter,
|
||||||
passport.authenticate("github", {
|
(req, res, next) => {
|
||||||
failureRedirect: "/login/provider/error",
|
const options = {
|
||||||
session: false,
|
failureRedirect: "/",
|
||||||
scope: [ 'user:email' ]
|
additionalParams: {
|
||||||
}),
|
RelayState: req.query.callback_port ?? ""
|
||||||
ssoController.redirectSSO
|
},
|
||||||
|
};
|
||||||
|
passport.authenticate("saml", options)(req, res, next);
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get("/redirect/saml2/:ssoIdentifier", authLimiter, (req, res, next) => {
|
|
||||||
const options = {
|
|
||||||
failureRedirect: "/",
|
|
||||||
additionalParams: {
|
|
||||||
RelayState: req.query.callback_port ?? ""
|
|
||||||
}
|
|
||||||
};
|
|
||||||
passport.authenticate("saml", options)(req, res, next);
|
|
||||||
});
|
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/saml2/:ssoIdentifier",
|
"/saml2/:ssoIdentifier",
|
||||||
passport.authenticate("saml", {
|
passport.authenticate("saml", {
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ export const validateAuthMode = ({
|
|||||||
authMode = AuthMode.SERVICE_TOKEN;
|
authMode = AuthMode.SERVICE_TOKEN;
|
||||||
authTokenValue = tokenValue;
|
authTokenValue = tokenValue;
|
||||||
break;
|
break;
|
||||||
case "proj_token":
|
case "stv3":
|
||||||
authMode = AuthMode.SERVICE_TOKEN_V3;
|
authMode = AuthMode.SERVICE_TOKEN_V3;
|
||||||
authTokenValue = parts.slice(1).join(".");
|
authTokenValue = parts.slice(1).join(".");
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ import {
|
|||||||
membership as v1MembershipRouter,
|
membership as v1MembershipRouter,
|
||||||
organization as v1OrganizationRouter,
|
organization as v1OrganizationRouter,
|
||||||
password as v1PasswordRouter,
|
password as v1PasswordRouter,
|
||||||
|
sso as v1SSORouter,
|
||||||
secretApprovalPolicy as v1SecretApprovalPolicy,
|
secretApprovalPolicy as v1SecretApprovalPolicy,
|
||||||
secretImps as v1SecretImpsRouter,
|
secretImps as v1SecretImpsRouter,
|
||||||
secret as v1SecretRouter,
|
secret as v1SecretRouter,
|
||||||
@@ -181,6 +182,7 @@ const main = async () => {
|
|||||||
app.use("/api/v1/secret-imports", v1SecretImpsRouter);
|
app.use("/api/v1/secret-imports", v1SecretImpsRouter);
|
||||||
app.use("/api/v1/roles", v1RoleRouter);
|
app.use("/api/v1/roles", v1RoleRouter);
|
||||||
app.use("/api/v1/secret-approvals", v1SecretApprovalPolicy);
|
app.use("/api/v1/secret-approvals", v1SecretApprovalPolicy);
|
||||||
|
app.use("/api/v1/sso", v1SSORouter);
|
||||||
|
|
||||||
// v2 routes (improvements)
|
// v2 routes (improvements)
|
||||||
app.use("/api/v2/signup", v2SignupRouter);
|
app.use("/api/v2/signup", v2SignupRouter);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ export enum AuthMethod {
|
|||||||
EMAIL = "email",
|
EMAIL = "email",
|
||||||
GOOGLE = "google",
|
GOOGLE = "google",
|
||||||
GITHUB = "github",
|
GITHUB = "github",
|
||||||
|
GITLAB = "gitlab",
|
||||||
OKTA_SAML = "okta-saml",
|
OKTA_SAML = "okta-saml",
|
||||||
AZURE_SAML = "azure-saml",
|
AZURE_SAML = "azure-saml",
|
||||||
JUMPCLOUD_SAML = "jumpcloud-saml",
|
JUMPCLOUD_SAML = "jumpcloud-saml",
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import key from "./key";
|
|||||||
import inviteOrg from "./inviteOrg";
|
import inviteOrg from "./inviteOrg";
|
||||||
import secret from "./secret";
|
import secret from "./secret";
|
||||||
import serviceToken from "./serviceToken";
|
import serviceToken from "./serviceToken";
|
||||||
|
import sso from "./sso";
|
||||||
import password from "./password";
|
import password from "./password";
|
||||||
import integration from "./integration";
|
import integration from "./integration";
|
||||||
import integrationAuth from "./integrationAuth";
|
import integrationAuth from "./integrationAuth";
|
||||||
@@ -39,5 +40,6 @@ export {
|
|||||||
secretsFolder,
|
secretsFolder,
|
||||||
webhooks,
|
webhooks,
|
||||||
secretImps,
|
secretImps,
|
||||||
|
sso,
|
||||||
secretApprovalPolicy
|
secretApprovalPolicy
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import express from "express";
|
||||||
|
const router = express.Router();
|
||||||
|
import passport from "passport";
|
||||||
|
import { authLimiter } from "../../helpers/rateLimiter";
|
||||||
|
import { ssoController } from "../../ee/controllers/v1";
|
||||||
|
|
||||||
|
router.get("/redirect/google", authLimiter, (req, res, next) => {
|
||||||
|
passport.authenticate("google", {
|
||||||
|
scope: ["profile", "email"],
|
||||||
|
session: false,
|
||||||
|
...(req.query.callback_port
|
||||||
|
? {
|
||||||
|
state: req.query.callback_port as string
|
||||||
|
}
|
||||||
|
: {})
|
||||||
|
})(req, res, next);
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
"/google",
|
||||||
|
passport.authenticate("google", {
|
||||||
|
failureRedirect: "/login/provider/error",
|
||||||
|
session: false
|
||||||
|
}),
|
||||||
|
ssoController.redirectSSO
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get("/redirect/github", authLimiter, (req, res, next) => {
|
||||||
|
passport.authenticate("github", {
|
||||||
|
session: false,
|
||||||
|
...(req.query.callback_port
|
||||||
|
? {
|
||||||
|
state: req.query.callback_port as string
|
||||||
|
}
|
||||||
|
: {})
|
||||||
|
})(req, res, next);
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
"/github",
|
||||||
|
authLimiter,
|
||||||
|
passport.authenticate("github", {
|
||||||
|
failureRedirect: "/login/provider/error",
|
||||||
|
session: false
|
||||||
|
}),
|
||||||
|
ssoController.redirectSSO
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
"/redirect/gitlab",
|
||||||
|
authLimiter,
|
||||||
|
(req, res, next) => {
|
||||||
|
passport.authenticate("gitlab", {
|
||||||
|
session: false,
|
||||||
|
...(req.query.callback_port ? {
|
||||||
|
state: req.query.callback_port as string
|
||||||
|
} : {})
|
||||||
|
})(req, res, next);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
"/gitlab",
|
||||||
|
authLimiter,
|
||||||
|
passport.authenticate("gitlab", {
|
||||||
|
failureRedirect: "/login/provider/error",
|
||||||
|
session: false
|
||||||
|
}),
|
||||||
|
ssoController.redirectSSO
|
||||||
|
);
|
||||||
|
|
||||||
|
export default router;
|
||||||
@@ -29,11 +29,11 @@ router.patch(
|
|||||||
);
|
);
|
||||||
|
|
||||||
router.put(
|
router.put(
|
||||||
"/me/auth-methods",
|
"/me/auth-methods",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY],
|
||||||
}),
|
}),
|
||||||
usersController.updateAuthMethods
|
usersController.updateAuthMethods,
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
|
|||||||
@@ -14,16 +14,19 @@ import {
|
|||||||
import { createToken } from "../helpers/auth";
|
import { createToken } from "../helpers/auth";
|
||||||
import {
|
import {
|
||||||
getClientIdGitHubLogin,
|
getClientIdGitHubLogin,
|
||||||
|
getClientIdGitLabLogin,
|
||||||
getClientIdGoogleLogin,
|
getClientIdGoogleLogin,
|
||||||
getClientSecretGitHubLogin,
|
getClientSecretGitHubLogin,
|
||||||
|
getClientSecretGitLabLogin,
|
||||||
getClientSecretGoogleLogin,
|
getClientSecretGoogleLogin,
|
||||||
getJwtProviderAuthLifetime,
|
getJwtProviderAuthLifetime,
|
||||||
getJwtProviderAuthSecret,
|
getJwtProviderAuthSecret,
|
||||||
|
getSiteURL,
|
||||||
|
getUrlGitLabLogin
|
||||||
} from "../config";
|
} from "../config";
|
||||||
import { getSSOConfigHelper } from "../ee/helpers/organizations";
|
import { getSSOConfigHelper } from "../ee/helpers/organizations";
|
||||||
import { InternalServerError, OrganizationNotFoundError } from "./errors";
|
import { InternalServerError, OrganizationNotFoundError } from "./errors";
|
||||||
import { ACCEPTED, INTEGRATION_GITHUB_API_URL, INVITED, MEMBER } from "../variables";
|
import { ACCEPTED, INTEGRATION_GITHUB_API_URL, INVITED, MEMBER } from "../variables";
|
||||||
import { getSiteURL } from "../config";
|
|
||||||
import { standardRequest } from "../config/request";
|
import { standardRequest } from "../config/request";
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
@@ -31,6 +34,8 @@ const GoogleStrategy = require("passport-google-oauth20").Strategy;
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
const GitHubStrategy = require("passport-github").Strategy;
|
const GitHubStrategy = require("passport-github").Strategy;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
const GitLabStrategy = require("passport-gitlab2").Strategy;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
const { MultiSamlStrategy } = require("@node-saml/passport-saml");
|
const { MultiSamlStrategy } = require("@node-saml/passport-saml");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -84,6 +89,9 @@ const initializePassport = async () => {
|
|||||||
const clientSecretGoogleLogin = await getClientSecretGoogleLogin();
|
const clientSecretGoogleLogin = await getClientSecretGoogleLogin();
|
||||||
const clientIdGitHubLogin = await getClientIdGitHubLogin();
|
const clientIdGitHubLogin = await getClientIdGitHubLogin();
|
||||||
const clientSecretGitHubLogin = await getClientSecretGitHubLogin();
|
const clientSecretGitHubLogin = await getClientSecretGitHubLogin();
|
||||||
|
const urlGitLab = await getUrlGitLabLogin();
|
||||||
|
const clientIdGitLabLogin = await getClientIdGitLabLogin();
|
||||||
|
const clientSecretGitLabLogin = await getClientSecretGitLabLogin();
|
||||||
|
|
||||||
if (clientIdGoogleLogin && clientSecretGoogleLogin) {
|
if (clientIdGoogleLogin && clientSecretGoogleLogin) {
|
||||||
passport.use(new GoogleStrategy({
|
passport.use(new GoogleStrategy({
|
||||||
@@ -217,6 +225,60 @@ const initializePassport = async () => {
|
|||||||
}
|
}
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (urlGitLab && clientIdGitLabLogin && clientSecretGitLabLogin) {
|
||||||
|
passport.use(new GitLabStrategy({
|
||||||
|
passReqToCallback: true,
|
||||||
|
clientID: clientIdGitLabLogin,
|
||||||
|
clientSecret: clientSecretGitLabLogin,
|
||||||
|
callbackURL: "/api/v1/sso/gitlab",
|
||||||
|
baseURL: urlGitLab
|
||||||
|
},
|
||||||
|
async (req : express.Request, accessToken : any, refreshToken : any, profile : any, done : any) => {
|
||||||
|
const email = profile.emails[0].value;
|
||||||
|
|
||||||
|
let user = await User.findOne({
|
||||||
|
email
|
||||||
|
}).select("+publicKey");
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
user = await new User({
|
||||||
|
email: email,
|
||||||
|
authMethods: [AuthMethod.GITLAB],
|
||||||
|
firstName: profile.displayName,
|
||||||
|
lastName: ""
|
||||||
|
}).save();
|
||||||
|
}
|
||||||
|
|
||||||
|
let isLinkingRequired = false;
|
||||||
|
if (!user.authMethods.includes(AuthMethod.GITLAB)) {
|
||||||
|
isLinkingRequired = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isUserCompleted = !!user.publicKey;
|
||||||
|
const providerAuthToken = createToken({
|
||||||
|
payload: {
|
||||||
|
userId: user._id.toString(),
|
||||||
|
email: user.email,
|
||||||
|
firstName: user.firstName,
|
||||||
|
lastName: user.lastName,
|
||||||
|
authMethod: AuthMethod.GITLAB,
|
||||||
|
isUserCompleted,
|
||||||
|
isLinkingRequired,
|
||||||
|
...(req.query.state ? {
|
||||||
|
callbackPort: req.query.state as string
|
||||||
|
} : {})
|
||||||
|
},
|
||||||
|
expiresIn: await getJwtProviderAuthLifetime(),
|
||||||
|
secret: await getJwtProviderAuthSecret(),
|
||||||
|
});
|
||||||
|
|
||||||
|
req.isUserCompleted = isUserCompleted;
|
||||||
|
req.providerAuthToken = providerAuthToken;
|
||||||
|
return done(null, profile);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
passport.use("saml", new MultiSamlStrategy(
|
passport.use("saml", new MultiSamlStrategy(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -84,7 +84,8 @@ export const INTEGRATION_BITBUCKET_TOKEN_URL = "https://bitbucket.org/site/oauth
|
|||||||
// integration apps endpoints
|
// integration apps endpoints
|
||||||
export const INTEGRATION_GCP_API_URL = "https://cloudresourcemanager.googleapis.com";
|
export const INTEGRATION_GCP_API_URL = "https://cloudresourcemanager.googleapis.com";
|
||||||
export const INTEGRATION_HEROKU_API_URL = "https://api.heroku.com";
|
export const INTEGRATION_HEROKU_API_URL = "https://api.heroku.com";
|
||||||
export const INTEGRATION_GITLAB_API_URL = "https://gitlab.com/api";
|
export const GITLAB_URL = "https://gitlab.com";
|
||||||
|
export const INTEGRATION_GITLAB_API_URL = `${GITLAB_URL}/api`;
|
||||||
export const INTEGRATION_GITHUB_API_URL = "https://api.github.com";
|
export const INTEGRATION_GITHUB_API_URL = "https://api.github.com";
|
||||||
export const INTEGRATION_VERCEL_API_URL = "https://api.vercel.com";
|
export const INTEGRATION_VERCEL_API_URL = "https://api.vercel.com";
|
||||||
export const INTEGRATION_NETLIFY_API_URL = "https://api.netlify.com";
|
export const INTEGRATION_NETLIFY_API_URL = "https://api.netlify.com";
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
---
|
||||||
|
title: "GitLab SSO"
|
||||||
|
description: "Configure GitLab SSO for Infisical"
|
||||||
|
---
|
||||||
|
|
||||||
|
Using GitLab SSO on a self-hosted instance of Infisical requires configuring an OAuth application in GitLab and registering your instance with it.
|
||||||
|
|
||||||
|
## Create an OAuth application in GitLab
|
||||||
|
|
||||||
|
Navigate to your user Settings > Applications to create a new GitLab application.
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
Create the application. As part of the form, set the **Redirect URI** to `https://your-domain.com/api/v1/sso/gitlab`.
|
||||||
|
Note that only `read_user` is required as part of the **Scopes** configuration.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
<Note>
|
||||||
|
If you have a GitLab group, you can create an OAuth application under it
|
||||||
|
in your group Settings > Applications.
|
||||||
|
</Note>
|
||||||
|
|
||||||
|
## Add your OAuth application credentials to Infisical
|
||||||
|
|
||||||
|
Obtain the **Application ID** and **Secret** for your GitLab application.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Back in your Infisical instance, add 2-3 new environment variables for the credentials of your GitLab application:
|
||||||
|
|
||||||
|
- `CLIENT_ID_GITLAB_LOGIN`: The **Client ID** of your GitLab application.
|
||||||
|
- `CLIENT_SECRET_GITLAB_LOGIN`: The **Secret** of your GitLab application.
|
||||||
|
- (optional) `URL_GITLAB_LOGIN`: The URL of your self-hosted instance of GitLab where the OAuth application is registered. If no URL is passed in, this will default to `https://gitlab.com`.
|
||||||
|
|
||||||
|
Once added, restart your Infisical instance and log in with GitLab.
|
||||||
@@ -19,6 +19,7 @@ your IdP cannot and will not have access to the decryption key needed to decrypt
|
|||||||
|
|
||||||
- [Google SSO](/documentation/platform/sso/google)
|
- [Google SSO](/documentation/platform/sso/google)
|
||||||
- [GitHub SSO](/documentation/platform/sso/github)
|
- [GitHub SSO](/documentation/platform/sso/github)
|
||||||
|
- [GitLab SSO](/documentation/platform/sso/gitlab)
|
||||||
- [Okta SAML](/documentation/platform/sso/okta)
|
- [Okta SAML](/documentation/platform/sso/okta)
|
||||||
- [Azure SAML](/documentation/platform/sso/azure)
|
- [Azure SAML](/documentation/platform/sso/azure)
|
||||||
- [JumpCloud SAML](/documentation/platform/sso/jumpcloud)
|
- [JumpCloud SAML](/documentation/platform/sso/jumpcloud)
|
||||||
@@ -26,7 +26,7 @@ Service Token V3 (ST V3) is a new and improved authentication method that is in
|
|||||||
Here's a few pointers to get you acquainted with it:
|
Here's a few pointers to get you acquainted with it:
|
||||||
|
|
||||||
- When you create a ST V3, you export a `JSON` file containing 3 components: `publicKey`, `privateKey`, and `serviceToken` where
|
- When you create a ST V3, you export a `JSON` file containing 3 components: `publicKey`, `privateKey`, and `serviceToken` where
|
||||||
`serviceToken` is a JWT token prefixed with `proj_token`. The token provides access to the Infisical API and the public-private key
|
`serviceToken` is a JWT token prefixed with `stv3`. The token provides access to the Infisical API and the public-private key
|
||||||
pairs are to support cryptographic operations for the client whenever E2EE is needed.
|
pairs are to support cryptographic operations for the client whenever E2EE is needed.
|
||||||
- ST V3 supports IP allowlisting; this means you can restrict the usage of a ST V3 to a specific IP or CIDR range.
|
- ST V3 supports IP allowlisting; this means you can restrict the usage of a ST V3 to a specific IP or CIDR range.
|
||||||
- ST V3 supports provisioning granular `read` or `readWrite` access down to each path.
|
- ST V3 supports provisioning granular `read` or `readWrite` access down to each path.
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 365 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.5 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 959 KiB |
@@ -107,7 +107,7 @@ build-job:
|
|||||||
Back in your Infisical instance, add two new environment variables for the credentials of your GitLab application:
|
Back in your Infisical instance, add two new environment variables for the credentials of your GitLab application:
|
||||||
|
|
||||||
- `CLIENT_ID_GITLAB`: The **Client ID** of your GitLab application.
|
- `CLIENT_ID_GITLAB`: The **Client ID** of your GitLab application.
|
||||||
- `CLIENT_SECRET_GITLAB`: The **Client Secret** of your GitLab application.
|
- `CLIENT_SECRET_GITLAB`: The **Secret** of your GitLab application.
|
||||||
|
|
||||||
Once added, restart your Infisical instance and use the GitLab integration.
|
Once added, restart your Infisical instance and use the GitLab integration.
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ Consider the following `JSON`:
|
|||||||
{
|
{
|
||||||
"publicKey": "...",
|
"publicKey": "...",
|
||||||
"privateKey": "...",
|
"privateKey": "...",
|
||||||
"serviceToken": "proj_token..."
|
"serviceToken": "stv3..."
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -126,6 +126,7 @@
|
|||||||
"documentation/platform/sso/overview",
|
"documentation/platform/sso/overview",
|
||||||
"documentation/platform/sso/google",
|
"documentation/platform/sso/google",
|
||||||
"documentation/platform/sso/github",
|
"documentation/platform/sso/github",
|
||||||
|
"documentation/platform/sso/gitlab",
|
||||||
"documentation/platform/sso/okta",
|
"documentation/platform/sso/okta",
|
||||||
"documentation/platform/sso/azure",
|
"documentation/platform/sso/azure",
|
||||||
"documentation/platform/sso/jumpcloud"
|
"documentation/platform/sso/jumpcloud"
|
||||||
|
|||||||
@@ -155,6 +155,15 @@ Other environment variables are listed below to increase the functionality of yo
|
|||||||
<ParamField query="CLIENT_SECRET_GITHUB_LOGIN" type="string" default="none" optional>
|
<ParamField query="CLIENT_SECRET_GITHUB_LOGIN" type="string" default="none" optional>
|
||||||
OAuth2 client secret for GitHub login
|
OAuth2 client secret for GitHub login
|
||||||
</ParamField>
|
</ParamField>
|
||||||
|
<ParamField query="CLIENT_ID_GITLAB_LOGIN" type="string" default="none" optional>
|
||||||
|
OAuth2 client ID for GitLab login
|
||||||
|
</ParamField>
|
||||||
|
<ParamField query="CLIENT_SECRET_GITLAB_LOGIN" type="string" default="none" optional>
|
||||||
|
OAuth2 client secret for GitLab login
|
||||||
|
</ParamField>
|
||||||
|
<ParamField query="URL_GITLAB_LOGIN" type="string" default="https://gitlab.com" optional>
|
||||||
|
URL of your self-hosted instance of GitLab where the OAuth application is registered
|
||||||
|
</ParamField>
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab title="Others">
|
<Tab title="Others">
|
||||||
#### JWT
|
#### JWT
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ You can view specific documentation for how to set up each SSO authentication me
|
|||||||
|
|
||||||
- [Google SSO](/documentation/platform/sso/google)
|
- [Google SSO](/documentation/platform/sso/google)
|
||||||
- [GitHub SSO](/documentation/platform/sso/github)
|
- [GitHub SSO](/documentation/platform/sso/github)
|
||||||
|
- [GitLab SSO](/documentation/platform/sso/gitlab)
|
||||||
- [Okta SAML](/documentation/platform/sso/okta)
|
- [Okta SAML](/documentation/platform/sso/okta)
|
||||||
- [Azure SAML](/documentation/platform/sso/azure)
|
- [Azure SAML](/documentation/platform/sso/azure)
|
||||||
- [JumpCloud SAML](/documentation/platform/sso/jumpcloud)
|
- [JumpCloud SAML](/documentation/platform/sso/jumpcloud)
|
||||||
+3
-1
@@ -1,6 +1,7 @@
|
|||||||
ARG POSTHOG_HOST=https://app.posthog.com
|
ARG POSTHOG_HOST=https://app.posthog.com
|
||||||
ARG POSTHOG_API_KEY=posthog-api-key
|
ARG POSTHOG_API_KEY=posthog-api-key
|
||||||
ARG INTERCOM_ID=intercom-id
|
ARG INTERCOM_ID=intercom-id
|
||||||
|
ARG NEXT_INFISICAL_PLATFORM_VERSION=next-infisical-platform-version
|
||||||
|
|
||||||
FROM node:16-alpine AS deps
|
FROM node:16-alpine AS deps
|
||||||
# Install dependencies only when needed. Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
|
# Install dependencies only when needed. Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
|
||||||
@@ -13,7 +14,6 @@ COPY package.json package-lock.json next.config.js ./
|
|||||||
# Install dependencies
|
# Install dependencies
|
||||||
RUN npm ci --only-production --ignore-scripts
|
RUN npm ci --only-production --ignore-scripts
|
||||||
|
|
||||||
|
|
||||||
# Rebuild the source code only when needed
|
# Rebuild the source code only when needed
|
||||||
FROM node:16-alpine AS builder
|
FROM node:16-alpine AS builder
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
@@ -52,6 +52,8 @@ ENV NEXT_PUBLIC_POSTHOG_API_KEY=$POSTHOG_API_KEY \
|
|||||||
ARG INTERCOM_ID
|
ARG INTERCOM_ID
|
||||||
ENV NEXT_PUBLIC_INTERCOM_ID=$INTERCOM_ID \
|
ENV NEXT_PUBLIC_INTERCOM_ID=$INTERCOM_ID \
|
||||||
BAKED_NEXT_PUBLIC_INTERCOM_ID=$INTERCOM_ID
|
BAKED_NEXT_PUBLIC_INTERCOM_ID=$INTERCOM_ID
|
||||||
|
ARG NEXT_INFISICAL_PLATFORM_VERSION
|
||||||
|
ENV NEXT_PUBLIC_INFISICAL_PLATFORM_VERSION=$NEXT_INFISICAL_PLATFORM_VERSION
|
||||||
|
|
||||||
COPY --chown=nextjs:nodejs --chmod=555 scripts ./scripts
|
COPY --chown=nextjs:nodejs --chmod=555 scripts ./scripts
|
||||||
COPY --from=builder /app/public ./public
|
COPY --from=builder /app/public ./public
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { faGithub,faGoogle } from "@fortawesome/free-brands-svg-icons";
|
import { faGithub, faGitlab, faGoogle } from "@fortawesome/free-brands-svg-icons";
|
||||||
import { faEnvelope } from "@fortawesome/free-regular-svg-icons";
|
import { faEnvelope } from "@fortawesome/free-regular-svg-icons";
|
||||||
import { faLock } from "@fortawesome/free-solid-svg-icons";
|
import { faLock } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
@@ -9,74 +9,94 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|||||||
import { Button } from "../v2";
|
import { Button } from "../v2";
|
||||||
|
|
||||||
export default function InitialSignupStep({
|
export default function InitialSignupStep({
|
||||||
setIsSignupWithEmail,
|
setIsSignupWithEmail
|
||||||
}: {
|
}: {
|
||||||
setIsSignupWithEmail: (value: boolean) => void
|
setIsSignupWithEmail: (value: boolean) => void;
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
return <div className='flex flex-col mx-auto w-full justify-center items-center'>
|
return (
|
||||||
<h1 className='text-xl font-medium text-transparent bg-clip-text bg-gradient-to-b from-white to-bunker-200 text-center mb-8' >{t("signup.initial-title")}</h1>
|
<div className="mx-auto flex w-full flex-col items-center justify-center">
|
||||||
<div className='lg:w-1/6 w-1/4 min-w-[20rem] rounded-md'>
|
<h1 className="mb-8 bg-gradient-to-b from-white to-bunker-200 bg-clip-text text-center text-xl font-medium text-transparent">
|
||||||
<Button
|
{t("signup.initial-title")}
|
||||||
colorSchema="primary"
|
</h1>
|
||||||
variant="solid"
|
<div className="w-1/4 min-w-[20rem] rounded-md lg:w-1/6">
|
||||||
onClick={() => {
|
<Button
|
||||||
window.open("/api/v1/sso/redirect/google");
|
colorSchema="primary"
|
||||||
window.close();
|
variant="solid"
|
||||||
}}
|
onClick={() => {
|
||||||
leftIcon={<FontAwesomeIcon icon={faGoogle} className="mr-2" />}
|
window.open("/api/v1/sso/redirect/google");
|
||||||
className="h-12 w-full mx-0"
|
window.close();
|
||||||
>
|
}}
|
||||||
{t("signup.continue-with-google")}
|
leftIcon={<FontAwesomeIcon icon={faGoogle} className="mr-2" />}
|
||||||
</Button>
|
className="mx-0 h-12 w-full"
|
||||||
</div>
|
>
|
||||||
<div className='lg:w-1/6 w-1/4 min-w-[20rem] rounded-md mt-4'>
|
{t("signup.continue-with-google")}
|
||||||
<Button
|
</Button>
|
||||||
colorSchema="primary"
|
</div>
|
||||||
variant="outline_bg"
|
<div className="mt-4 w-1/4 min-w-[20rem] rounded-md lg:w-1/6">
|
||||||
onClick={() => {
|
<Button
|
||||||
window.open("/api/v1/sso/redirect/github");
|
colorSchema="primary"
|
||||||
window.close();
|
variant="outline_bg"
|
||||||
}}
|
onClick={() => {
|
||||||
leftIcon={<FontAwesomeIcon icon={faGithub} className="mr-2" />}
|
window.open("/api/v1/sso/redirect/github");
|
||||||
className="h-12 w-full mx-0"
|
window.close();
|
||||||
>
|
}}
|
||||||
Continue with GitHub
|
leftIcon={<FontAwesomeIcon icon={faGithub} className="mr-2" />}
|
||||||
</Button>
|
className="mx-0 h-12 w-full"
|
||||||
</div>
|
>
|
||||||
<div className='lg:w-1/6 w-1/4 min-w-[20rem] text-center rounded-md mt-4'>
|
Continue with GitHub
|
||||||
<Button
|
</Button>
|
||||||
colorSchema="primary"
|
</div>
|
||||||
variant="outline_bg"
|
<div className="mt-4 w-1/4 min-w-[20rem] rounded-md lg:w-1/6">
|
||||||
onClick={() => {
|
<Button
|
||||||
setIsSignupWithEmail(true);
|
colorSchema="primary"
|
||||||
}}
|
variant="outline_bg"
|
||||||
leftIcon={<FontAwesomeIcon icon={faEnvelope} className="mr-2" />}
|
onClick={() => {
|
||||||
className="h-12 w-full mx-0"
|
window.open("/api/v1/sso/redirect/gitlab");
|
||||||
>
|
window.close();
|
||||||
Continue with Email
|
}}
|
||||||
</Button>
|
leftIcon={<FontAwesomeIcon icon={faGitlab} className="mr-2" />}
|
||||||
</div>
|
className="mx-0 h-12 w-full"
|
||||||
<div className='lg:w-1/6 w-1/4 min-w-[20rem] text-center rounded-md mt-4'>
|
>
|
||||||
<Button
|
Continue with GitLab
|
||||||
colorSchema="primary"
|
</Button>
|
||||||
variant="outline_bg"
|
</div>
|
||||||
onClick={() => router.push("/saml-sso")}
|
<div className="mt-4 w-1/4 min-w-[20rem] rounded-md text-center lg:w-1/6">
|
||||||
leftIcon={<FontAwesomeIcon icon={faLock} className="mr-2" />}
|
<Button
|
||||||
className="h-12 w-full mx-0"
|
colorSchema="primary"
|
||||||
>
|
variant="outline_bg"
|
||||||
Continue with SSO
|
onClick={() => {
|
||||||
</Button>
|
setIsSignupWithEmail(true);
|
||||||
</div>
|
}}
|
||||||
<div className='lg:w-1/6 w-1/4 min-w-[20rem] px-8 text-center mt-6 text-xs text-bunker-400'>
|
leftIcon={<FontAwesomeIcon icon={faEnvelope} className="mr-2" />}
|
||||||
{t("signup.create-policy")}
|
className="mx-0 h-12 w-full"
|
||||||
</div>
|
>
|
||||||
<div className="mt-2 text-bunker-400 text-xs flex flex-row">
|
Continue with Email
|
||||||
<Link href="/login">
|
</Button>
|
||||||
<span className='hover:underline hover:underline-offset-4 hover:decoration-primary-700 hover:text-bunker-200 duration-200 cursor-pointer'>{t("signup.already-have-account")}</span>
|
</div>
|
||||||
</Link>
|
<div className="mt-4 w-1/4 min-w-[20rem] rounded-md text-center lg:w-1/6">
|
||||||
</div>
|
<Button
|
||||||
|
colorSchema="primary"
|
||||||
|
variant="outline_bg"
|
||||||
|
onClick={() => router.push("/saml-sso")}
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faLock} className="mr-2" />}
|
||||||
|
className="mx-0 h-12 w-full"
|
||||||
|
>
|
||||||
|
Continue with SSO
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className="mt-6 w-1/4 min-w-[20rem] px-8 text-center text-xs text-bunker-400 lg:w-1/6">
|
||||||
|
{t("signup.create-policy")}
|
||||||
|
</div>
|
||||||
|
<div className="mt-2 flex flex-row text-xs text-bunker-400">
|
||||||
|
<Link href="/login">
|
||||||
|
<span className="cursor-pointer duration-200 hover:text-bunker-200 hover:underline hover:decoration-primary-700 hover:underline-offset-4">
|
||||||
|
{t("signup.already-have-account")}
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,10 @@ export enum AuthMethod {
|
|||||||
EMAIL = "email",
|
EMAIL = "email",
|
||||||
GOOGLE = "google",
|
GOOGLE = "google",
|
||||||
GITHUB = "github",
|
GITHUB = "github",
|
||||||
OKTA_SAML = "okta-saml",
|
GITLAB = "gitlab",
|
||||||
AZURE_SAML = "azure-saml",
|
OKTA_SAML = "okta-saml",
|
||||||
JUMPCLOUD_SAML = "jumpcloud-saml"
|
AZURE_SAML = "azure-saml",
|
||||||
|
JUMPCLOUD_SAML = "jumpcloud-saml"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type User = {
|
export type User = {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
faCheck,
|
faCheck,
|
||||||
faEnvelope,
|
faEnvelope,
|
||||||
faInfinity,
|
faInfinity,
|
||||||
|
faInfo,
|
||||||
faMobile,
|
faMobile,
|
||||||
faPlus,
|
faPlus,
|
||||||
faQuestion
|
faQuestion
|
||||||
@@ -122,6 +123,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
|||||||
const createWs = useCreateWorkspace();
|
const createWs = useCreateWorkspace();
|
||||||
const uploadWsKey = useUploadWsKey();
|
const uploadWsKey = useUploadWsKey();
|
||||||
const addWsUser = useAddUserToWs();
|
const addWsUser = useAddUserToWs();
|
||||||
|
const infisicalPlatformVersion = process.env.NEXT_PUBLIC_INFISICAL_PLATFORM_VERSION;
|
||||||
|
|
||||||
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||||
"addNewWs",
|
"addNewWs",
|
||||||
@@ -696,6 +698,12 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
|||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
{infisicalPlatformVersion && (
|
||||||
|
<div className="mb-2 w-full pl-5 duration-200 hover:text-mineshaft-200">
|
||||||
|
<FontAwesomeIcon icon={faInfo} className="mr-4 px-[0.1rem]" />
|
||||||
|
Platform Version: {infisicalPlatformVersion}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
</aside>
|
</aside>
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import { FormEvent, useState } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { faGithub,faGoogle } from "@fortawesome/free-brands-svg-icons";
|
import { faGithub, faGitlab, faGoogle } from "@fortawesome/free-brands-svg-icons";
|
||||||
import { faLock } from "@fortawesome/free-solid-svg-icons";
|
import { faLock } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import axios from "axios"
|
import axios from "axios";
|
||||||
|
|
||||||
import Error from "@app/components/basic/Error";
|
import Error from "@app/components/basic/Error";
|
||||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||||
@@ -16,208 +16,234 @@ import { fetchOrganizations } from "@app/hooks/api/organization/queries";
|
|||||||
import { useFetchServerStatus } from "@app/hooks/api/serverDetails";
|
import { useFetchServerStatus } from "@app/hooks/api/serverDetails";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
setStep: (step: number) => void;
|
setStep: (step: number) => void;
|
||||||
email: string;
|
email: string;
|
||||||
setEmail: (email: string) => void;
|
setEmail: (email: string) => void;
|
||||||
password: string;
|
password: string;
|
||||||
setPassword: (email: string) => void;
|
setPassword: (email: string) => void;
|
||||||
}
|
};
|
||||||
|
|
||||||
export const InitialStep = ({
|
export const InitialStep = ({ setStep, email, setEmail, password, setPassword }: Props) => {
|
||||||
setStep,
|
const router = useRouter();
|
||||||
email,
|
const { createNotification } = useNotificationContext();
|
||||||
setEmail,
|
const { t } = useTranslation();
|
||||||
password,
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
setPassword
|
const [loginError, setLoginError] = useState(false);
|
||||||
}: Props) => {
|
const { data: serverDetails } = useFetchServerStatus();
|
||||||
const router = useRouter();
|
const queryParams = new URLSearchParams(window.location.search);
|
||||||
const { createNotification } = useNotificationContext();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
|
||||||
const [loginError, setLoginError] = useState(false);
|
|
||||||
const { data: serverDetails } = useFetchServerStatus();
|
|
||||||
const queryParams = new URLSearchParams(window.location.search);
|
|
||||||
|
|
||||||
const handleLogin = async (e: FormEvent<HTMLFormElement>) => {
|
const handleLogin = async (e: FormEvent<HTMLFormElement>) => {
|
||||||
e.preventDefault()
|
e.preventDefault();
|
||||||
try {
|
try {
|
||||||
if (!email || !password) {
|
if (!email || !password) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
if (queryParams && queryParams.get("callback_port")) {
|
if (queryParams && queryParams.get("callback_port")) {
|
||||||
const callbackPort = queryParams.get("callback_port")
|
const callbackPort = queryParams.get("callback_port");
|
||||||
|
|
||||||
// attemptCliLogin
|
// attemptCliLogin
|
||||||
const isCliLoginSuccessful = await attemptCliLogin({
|
const isCliLoginSuccessful = await attemptCliLogin({
|
||||||
email: email.toLowerCase(),
|
email: email.toLowerCase(),
|
||||||
password,
|
password
|
||||||
})
|
});
|
||||||
|
|
||||||
if (isCliLoginSuccessful && isCliLoginSuccessful.success) {
|
if (isCliLoginSuccessful && isCliLoginSuccessful.success) {
|
||||||
|
if (isCliLoginSuccessful.mfaEnabled) {
|
||||||
|
// case: login requires MFA step
|
||||||
|
setStep(1);
|
||||||
|
setIsLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// case: login was successful
|
||||||
|
const cliUrl = `http://localhost:${callbackPort}`;
|
||||||
|
|
||||||
if (isCliLoginSuccessful.mfaEnabled) {
|
// send request to server endpoint
|
||||||
// case: login requires MFA step
|
const instance = axios.create();
|
||||||
setStep(1);
|
await instance.post(cliUrl, { ...isCliLoginSuccessful.loginResponse });
|
||||||
setIsLoading(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// case: login was successful
|
|
||||||
const cliUrl = `http://localhost:${callbackPort}`
|
|
||||||
|
|
||||||
// send request to server endpoint
|
// cli page
|
||||||
const instance = axios.create()
|
router.push("/cli-redirect");
|
||||||
await instance.post(cliUrl, { ...isCliLoginSuccessful.loginResponse })
|
|
||||||
|
|
||||||
// cli page
|
// on success, router.push to cli Login Successful page
|
||||||
router.push("/cli-redirect");
|
|
||||||
|
|
||||||
// on success, router.push to cli Login Successful page
|
|
||||||
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const isLoginSuccessful = await attemptLogin({
|
|
||||||
email: email.toLowerCase(),
|
|
||||||
password,
|
|
||||||
});
|
|
||||||
if (isLoginSuccessful && isLoginSuccessful.success) {
|
|
||||||
// case: login was successful
|
|
||||||
|
|
||||||
if (isLoginSuccessful.mfaEnabled) {
|
|
||||||
// case: login requires MFA step
|
|
||||||
setStep(1);
|
|
||||||
setIsLoading(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const userOrgs = await fetchOrganizations();
|
|
||||||
const userOrg = userOrgs[0] && userOrgs[0]._id;
|
|
||||||
|
|
||||||
// case: login does not require MFA step
|
|
||||||
createNotification({
|
|
||||||
text: "Successfully logged in",
|
|
||||||
type: "success"
|
|
||||||
});
|
|
||||||
router.push(`/org/${userOrg}/overview`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
setLoginError(true);
|
|
||||||
createNotification({
|
|
||||||
text: "Login unsuccessful. Double-check your credentials and try again.",
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
const isLoginSuccessful = await attemptLogin({
|
||||||
|
email: email.toLowerCase(),
|
||||||
|
password
|
||||||
|
});
|
||||||
|
if (isLoginSuccessful && isLoginSuccessful.success) {
|
||||||
|
// case: login was successful
|
||||||
|
|
||||||
setIsLoading(false);
|
if (isLoginSuccessful.mfaEnabled) {
|
||||||
|
// case: login requires MFA step
|
||||||
|
setStep(1);
|
||||||
|
setIsLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const userOrgs = await fetchOrganizations();
|
||||||
|
const userOrg = userOrgs[0] && userOrgs[0]._id;
|
||||||
|
|
||||||
|
// case: login does not require MFA step
|
||||||
|
createNotification({
|
||||||
|
text: "Successfully logged in",
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
router.push(`/org/${userOrg}/overview`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
setLoginError(true);
|
||||||
|
createNotification({
|
||||||
|
text: "Login unsuccessful. Double-check your credentials and try again.",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
setIsLoading(false);
|
||||||
<form onSubmit={handleLogin} className='flex flex-col mx-auto w-full justify-center items-center'>
|
};
|
||||||
<h1 className='text-xl font-medium text-transparent bg-clip-text bg-gradient-to-b from-white to-bunker-200 text-center mb-8' >Login to Infisical</h1>
|
|
||||||
<div className='lg:w-1/6 w-1/4 min-w-[21.2rem] md:min-w-[20.1rem] text-center rounded-md mt-4'>
|
return (
|
||||||
<Button
|
<form
|
||||||
colorSchema="primary"
|
onSubmit={handleLogin}
|
||||||
variant="outline_bg"
|
className="mx-auto flex w-full flex-col items-center justify-center"
|
||||||
onClick={() => {
|
>
|
||||||
const callbackPort = queryParams.get("callback_port");
|
<h1 className="mb-8 bg-gradient-to-b from-white to-bunker-200 bg-clip-text text-center text-xl font-medium text-transparent">
|
||||||
|
Login to Infisical
|
||||||
window.open(`/api/v1/sso/redirect/google${callbackPort ? `?callback_port=${callbackPort}` : ""}`);
|
</h1>
|
||||||
window.close();
|
<div className="mt-4 w-1/4 min-w-[21.2rem] rounded-md text-center md:min-w-[20.1rem] lg:w-1/6">
|
||||||
}}
|
<Button
|
||||||
leftIcon={<FontAwesomeIcon icon={faGoogle} className="mr-2" />}
|
colorSchema="primary"
|
||||||
className="h-11 w-full mx-0"
|
variant="outline_bg"
|
||||||
>
|
onClick={() => {
|
||||||
{t("login.continue-with-google")}
|
const callbackPort = queryParams.get("callback_port");
|
||||||
</Button>
|
|
||||||
</div>
|
window.open(
|
||||||
<div className='lg:w-1/6 w-1/4 min-w-[21.2rem] md:min-w-[20.1rem] text-center rounded-md mt-4'>
|
`/api/v1/sso/redirect/google${callbackPort ? `?callback_port=${callbackPort}` : ""}`
|
||||||
<Button
|
);
|
||||||
colorSchema="primary"
|
window.close();
|
||||||
variant="outline_bg"
|
}}
|
||||||
onClick={() => {
|
leftIcon={<FontAwesomeIcon icon={faGoogle} className="mr-2" />}
|
||||||
const callbackPort = queryParams.get("callback_port");
|
className="mx-0 h-11 w-full"
|
||||||
|
>
|
||||||
window.open(`/api/v1/sso/redirect/github${callbackPort ? `?callback_port=${callbackPort}` : ""}`);
|
{t("login.continue-with-google")}
|
||||||
|
</Button>
|
||||||
window.close();
|
</div>
|
||||||
}}
|
<div className="mt-4 w-1/4 min-w-[21.2rem] rounded-md text-center md:min-w-[20.1rem] lg:w-1/6">
|
||||||
leftIcon={<FontAwesomeIcon icon={faGithub} className="mr-2" />}
|
<Button
|
||||||
className="h-11 w-full mx-0"
|
colorSchema="primary"
|
||||||
>
|
variant="outline_bg"
|
||||||
Continue with GitHub
|
onClick={() => {
|
||||||
</Button>
|
const callbackPort = queryParams.get("callback_port");
|
||||||
</div>
|
|
||||||
<div className='lg:w-1/6 w-1/4 min-w-[21.2rem] md:min-w-[20.1rem] text-center rounded-md mt-4'>
|
window.open(
|
||||||
<Button
|
`/api/v1/sso/redirect/github${callbackPort ? `?callback_port=${callbackPort}` : ""}`
|
||||||
colorSchema="primary"
|
);
|
||||||
variant="outline_bg"
|
|
||||||
onClick={() => {
|
window.close();
|
||||||
setStep(2);
|
}}
|
||||||
}}
|
leftIcon={<FontAwesomeIcon icon={faGithub} className="mr-2" />}
|
||||||
leftIcon={<FontAwesomeIcon icon={faLock} className="mr-2" />}
|
className="mx-0 h-11 w-full"
|
||||||
className="h-11 w-full mx-0"
|
>
|
||||||
>
|
Continue with GitHub
|
||||||
Continue with SSO
|
</Button>
|
||||||
</Button>
|
</div>
|
||||||
</div>
|
<div className="mt-4 w-1/4 min-w-[21.2rem] rounded-md text-center md:min-w-[20.1rem] lg:w-1/6">
|
||||||
<div className='lg:w-1/6 w-1/4 min-w-[20rem] flex flex-row items-center my-4 py-2'>
|
<Button
|
||||||
<div className='w-full border-t border-mineshaft-400/60' />
|
colorSchema="primary"
|
||||||
<span className="mx-2 text-mineshaft-200 text-xs">or</span>
|
variant="outline_bg"
|
||||||
<div className='w-full border-t border-mineshaft-400/60' />
|
onClick={() => {
|
||||||
</div>
|
const callbackPort = queryParams.get("callback_port");
|
||||||
<div className='lg:w-1/6 w-1/4 min-w-[21.2rem] md:min-w-[20.1rem] text-center rounded-md'>
|
|
||||||
<Input
|
window.open(
|
||||||
value={email}
|
`/api/v1/sso/redirect/gitlab${callbackPort ? `?callback_port=${callbackPort}` : ""}`
|
||||||
onChange={(e) => setEmail(e.target.value)}
|
);
|
||||||
type="email"
|
|
||||||
placeholder="Enter your email..."
|
window.close();
|
||||||
isRequired
|
}}
|
||||||
autoComplete="username"
|
leftIcon={<FontAwesomeIcon icon={faGitlab} className="mr-2" />}
|
||||||
className="h-11"
|
className="mx-0 h-11 w-full"
|
||||||
/>
|
>
|
||||||
</div>
|
Continue with GitLab
|
||||||
<div className='lg:w-1/6 w-1/4 min-w-[21.2rem] md:min-w-[20.1rem] text-center rounded-md mt-4'>
|
</Button>
|
||||||
<Input
|
</div>
|
||||||
value={password}
|
<div className="mt-4 w-1/4 min-w-[21.2rem] rounded-md text-center md:min-w-[20.1rem] lg:w-1/6">
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
<Button
|
||||||
type="password"
|
colorSchema="primary"
|
||||||
placeholder="Enter your password..."
|
variant="outline_bg"
|
||||||
isRequired
|
onClick={() => {
|
||||||
autoComplete="current-password"
|
setStep(2);
|
||||||
id="current-password"
|
}}
|
||||||
className="h-11 select:-webkit-autofill:focus"
|
leftIcon={<FontAwesomeIcon icon={faLock} className="mr-2" />}
|
||||||
/>
|
className="mx-0 h-11 w-full"
|
||||||
</div>
|
>
|
||||||
<div className='lg:w-1/6 w-1/4 min-w-[21.2rem] md:min-w-[20.1rem] text-center rounded-md mt-5'>
|
Continue with SSO
|
||||||
<Button
|
</Button>
|
||||||
type="submit"
|
</div>
|
||||||
size="sm"
|
<div className="my-4 flex w-1/4 min-w-[20rem] flex-row items-center py-2 lg:w-1/6">
|
||||||
isFullWidth
|
<div className="w-full border-t border-mineshaft-400/60" />
|
||||||
className='h-11'
|
<span className="mx-2 text-xs text-mineshaft-200">or</span>
|
||||||
colorSchema="primary"
|
<div className="w-full border-t border-mineshaft-400/60" />
|
||||||
variant="solid"
|
</div>
|
||||||
isLoading={isLoading}
|
<div className="w-1/4 min-w-[21.2rem] rounded-md text-center md:min-w-[20.1rem] lg:w-1/6">
|
||||||
> Continue with Email </Button>
|
<Input
|
||||||
</div>
|
value={email}
|
||||||
{!isLoading && loginError && <Error text={t("login.error-login") ?? ""} />}
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
{
|
type="email"
|
||||||
!serverDetails?.inviteOnlySignup ?
|
placeholder="Enter your email..."
|
||||||
<div className="mt-6 text-bunker-400 text-sm flex flex-row">
|
isRequired
|
||||||
<span className="mr-1">Don't have an acount yet?</span>
|
autoComplete="username"
|
||||||
<Link href="/signup">
|
className="h-11"
|
||||||
<span className='hover:underline hover:underline-offset-4 hover:decoration-primary-700 hover:text-bunker-200 duration-200 cursor-pointer'>{t("login.create-account")}</span>
|
/>
|
||||||
</Link>
|
</div>
|
||||||
</div> : <div />
|
<div className="mt-4 w-1/4 min-w-[21.2rem] rounded-md text-center md:min-w-[20.1rem] lg:w-1/6">
|
||||||
}
|
<Input
|
||||||
<div className="text-bunker-400 text-sm flex flex-row">
|
value={password}
|
||||||
<span className="mr-1">Forgot password?</span>
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
<Link href="/verify-email">
|
type="password"
|
||||||
<span className='hover:underline hover:underline-offset-4 hover:decoration-primary-700 hover:text-bunker-200 duration-200 cursor-pointer'>Recover your account</span>
|
placeholder="Enter your password..."
|
||||||
</Link>
|
isRequired
|
||||||
</div>
|
autoComplete="current-password"
|
||||||
</form>
|
id="current-password"
|
||||||
);
|
className="select:-webkit-autofill:focus h-11"
|
||||||
}
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mt-5 w-1/4 min-w-[21.2rem] rounded-md text-center md:min-w-[20.1rem] lg:w-1/6">
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
size="sm"
|
||||||
|
isFullWidth
|
||||||
|
className="h-11"
|
||||||
|
colorSchema="primary"
|
||||||
|
variant="solid"
|
||||||
|
isLoading={isLoading}
|
||||||
|
>
|
||||||
|
{" "}
|
||||||
|
Continue with Email{" "}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{!isLoading && loginError && <Error text={t("login.error-login") ?? ""} />}
|
||||||
|
{!serverDetails?.inviteOnlySignup ? (
|
||||||
|
<div className="mt-6 flex flex-row text-sm text-bunker-400">
|
||||||
|
<span className="mr-1">Don't have an acount yet?</span>
|
||||||
|
<Link href="/signup">
|
||||||
|
<span className="cursor-pointer duration-200 hover:text-bunker-200 hover:underline hover:decoration-primary-700 hover:underline-offset-4">
|
||||||
|
{t("login.create-account")}
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div />
|
||||||
|
)}
|
||||||
|
<div className="flex flex-row text-sm text-bunker-400">
|
||||||
|
<span className="mr-1">Forgot password?</span>
|
||||||
|
<Link href="/verify-email">
|
||||||
|
<span className="cursor-pointer duration-200 hover:text-bunker-200 hover:underline hover:decoration-primary-700 hover:underline-offset-4">
|
||||||
|
Recover your account
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { faKey, faXmark } from "@fortawesome/free-solid-svg-icons";
|
import { faKey, faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { format } from "date-fns";
|
||||||
|
|
||||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||||
import {
|
import {
|
||||||
@@ -37,17 +38,6 @@ export const APIKeyTable = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatDate = (dateToFormat: string) => {
|
|
||||||
const date = new Date(dateToFormat);
|
|
||||||
const year = date.getFullYear();
|
|
||||||
const month = date.getMonth() + 1;
|
|
||||||
const day = date.getDate();
|
|
||||||
|
|
||||||
const formattedDate = `${day}/${month}/${year}`;
|
|
||||||
|
|
||||||
return formattedDate;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableContainer>
|
<TableContainer>
|
||||||
<Table>
|
<Table>
|
||||||
@@ -69,9 +59,9 @@ export const APIKeyTable = () => {
|
|||||||
return (
|
return (
|
||||||
<Tr className="h-10" key={`api-key-${_id}`}>
|
<Tr className="h-10" key={`api-key-${_id}`}>
|
||||||
<Td>{name}</Td>
|
<Td>{name}</Td>
|
||||||
<Td>{formatDate(lastUsed)}</Td>
|
<Td>{format(new Date(lastUsed), "yyyy-MM-dd")}</Td>
|
||||||
<Td>{formatDate(createdAt)}</Td>
|
<Td>{format(new Date(createdAt), "yyyy-MM-dd")}</Td>
|
||||||
<Td>{formatDate(expiresAt)}</Td>
|
<Td>{format(new Date(expiresAt), "yyyy-MM-dd")}</Td>
|
||||||
<Td>
|
<Td>
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
|
|||||||
+106
-108
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { faGithub, faGoogle, IconDefinition } from "@fortawesome/free-brands-svg-icons";
|
import { faGithub, faGitlab, faGoogle, IconDefinition } from "@fortawesome/free-brands-svg-icons";
|
||||||
import { faEnvelope } from "@fortawesome/free-regular-svg-icons";
|
import { faEnvelope } from "@fortawesome/free-regular-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { yupResolver } from "@hookform/resolvers/yup";
|
import { yupResolver } from "@hookform/resolvers/yup";
|
||||||
@@ -10,129 +10,127 @@ import { useNotificationContext } from "@app/components/context/Notifications/No
|
|||||||
import { Switch } from "@app/components/v2";
|
import { Switch } from "@app/components/v2";
|
||||||
import { useUser } from "@app/context";
|
import { useUser } from "@app/context";
|
||||||
import { useUpdateUserAuthMethods } from "@app/hooks/api";
|
import { useUpdateUserAuthMethods } from "@app/hooks/api";
|
||||||
import {
|
import { AuthMethod } from "@app/hooks/api/users/types";
|
||||||
AuthMethod
|
|
||||||
} from "@app/hooks/api/users/types";
|
|
||||||
|
|
||||||
interface AuthMethodOption {
|
interface AuthMethodOption {
|
||||||
label: string,
|
label: string;
|
||||||
value: AuthMethod,
|
value: AuthMethod;
|
||||||
icon: IconDefinition;
|
icon: IconDefinition;
|
||||||
}
|
}
|
||||||
|
|
||||||
const authMethodOpts: AuthMethodOption[] = [
|
const authMethodOpts: AuthMethodOption[] = [
|
||||||
{ label: "Email", value: AuthMethod.EMAIL, icon: faEnvelope },
|
{ label: "Email", value: AuthMethod.EMAIL, icon: faEnvelope },
|
||||||
{ label: "Google", value: AuthMethod.GOOGLE, icon: faGoogle },
|
{ label: "Google", value: AuthMethod.GOOGLE, icon: faGoogle },
|
||||||
{ label: "GitHub", value: AuthMethod.GITHUB, icon: faGithub }
|
{ label: "GitHub", value: AuthMethod.GITHUB, icon: faGithub },
|
||||||
|
{ label: "GitLab", value: AuthMethod.GITLAB, icon: faGitlab }
|
||||||
];
|
];
|
||||||
|
|
||||||
const samlProviders = [AuthMethod.OKTA_SAML, AuthMethod.JUMPCLOUD_SAML, AuthMethod.AZURE_SAML];
|
const samlProviders = [AuthMethod.OKTA_SAML, AuthMethod.JUMPCLOUD_SAML, AuthMethod.AZURE_SAML];
|
||||||
|
|
||||||
const schema = yup.object({
|
const schema = yup.object({
|
||||||
authMethods: yup.array().required("Auth method is required")
|
authMethods: yup.array().required("Auth method is required")
|
||||||
});
|
});
|
||||||
|
|
||||||
export type FormData = yup.InferType<typeof schema>;
|
export type FormData = yup.InferType<typeof schema>;
|
||||||
|
|
||||||
export const AuthMethodSection = () => {
|
export const AuthMethodSection = () => {
|
||||||
const { createNotification } = useNotificationContext();
|
const { createNotification } = useNotificationContext();
|
||||||
const { user } = useUser();
|
const { user } = useUser();
|
||||||
const { mutateAsync } = useUpdateUserAuthMethods();
|
const { mutateAsync } = useUpdateUserAuthMethods();
|
||||||
|
|
||||||
const {
|
|
||||||
reset,
|
|
||||||
setValue,
|
|
||||||
watch,
|
|
||||||
} = useForm<FormData>({
|
|
||||||
defaultValues: {
|
|
||||||
authMethods: user.authMethods,
|
|
||||||
},
|
|
||||||
resolver: yupResolver(schema)
|
|
||||||
});
|
|
||||||
|
|
||||||
const authMethods = watch("authMethods");
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (user) {
|
|
||||||
reset({
|
|
||||||
authMethods: user.authMethods,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [user]);
|
|
||||||
|
|
||||||
const onAuthMethodToggle = async (value: boolean, authMethodOpt: AuthMethodOption) => {
|
|
||||||
const hasSamlEnabled = user.authMethods
|
|
||||||
.some((authMethod: AuthMethod) => samlProviders.includes(authMethod));
|
|
||||||
|
|
||||||
if (hasSamlEnabled) {
|
const { reset, setValue, watch } = useForm<FormData>({
|
||||||
createNotification({
|
defaultValues: {
|
||||||
text: "SAML authentication can only be configured in your organization settings",
|
authMethods: user.authMethods
|
||||||
type: "error"
|
},
|
||||||
});
|
resolver: yupResolver(schema)
|
||||||
}
|
});
|
||||||
|
|
||||||
const newAuthMethods = value
|
|
||||||
? [...authMethods, authMethodOpt.value]
|
|
||||||
: authMethods.filter(auth => auth !== authMethodOpt.value);
|
|
||||||
|
|
||||||
if (value) {
|
|
||||||
const newUser = await mutateAsync({
|
|
||||||
authMethods: newAuthMethods
|
|
||||||
});
|
|
||||||
|
|
||||||
setValue("authMethods", newUser.authMethods);
|
const authMethods = watch("authMethods");
|
||||||
createNotification({
|
|
||||||
text: "Successfully enabled authentication method",
|
useEffect(() => {
|
||||||
type: "success"
|
if (user) {
|
||||||
});
|
reset({
|
||||||
return;
|
authMethods: user.authMethods
|
||||||
}
|
});
|
||||||
|
|
||||||
if (newAuthMethods.length === 0) {
|
|
||||||
createNotification({
|
|
||||||
text: "You must keep at least 1 authentication method enabled",
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newUser = await mutateAsync({
|
|
||||||
authMethods: newAuthMethods
|
|
||||||
});
|
|
||||||
|
|
||||||
setValue("authMethods", newUser.authMethods);
|
|
||||||
createNotification({
|
|
||||||
text: "Successfully disabled authentication method",
|
|
||||||
type: "success"
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
}, [user]);
|
||||||
return (
|
|
||||||
<div className="p-4 bg-mineshaft-900 mb-6 rounded-lg border border-mineshaft-600">
|
const onAuthMethodToggle = async (value: boolean, authMethodOpt: AuthMethodOption) => {
|
||||||
<h2 className="text-xl font-semibold flex-1 text-mineshaft-100 mb-8">
|
const hasSamlEnabled = user.authMethods.some((authMethod: AuthMethod) =>
|
||||||
Authentication methods
|
samlProviders.includes(authMethod)
|
||||||
</h2>
|
|
||||||
<p className="text-gray-400 mb-4">
|
|
||||||
By enabling a SSO provider, you are allowing an account with that provider which uses the same email address as your existing Infisical account to be able to log in to Infisical.
|
|
||||||
</p>
|
|
||||||
<div className="mb-4">
|
|
||||||
{user && authMethodOpts.map((authMethodOpt) => {
|
|
||||||
return (
|
|
||||||
<div className="flex p-4 items-center" key={`auth-method-${authMethodOpt.value}`}>
|
|
||||||
<div className="flex items-center">
|
|
||||||
<FontAwesomeIcon icon={authMethodOpt.icon} className="mr-4" />
|
|
||||||
</div>
|
|
||||||
<Switch
|
|
||||||
id={`enable-${authMethodOpt.value}-auth`}
|
|
||||||
onCheckedChange={(value) => onAuthMethodToggle(value, authMethodOpt)}
|
|
||||||
isChecked={authMethods?.includes(authMethodOpt.value) ?? false}
|
|
||||||
>
|
|
||||||
<p className="w-12 mr-4">{authMethodOpt.label}</p>
|
|
||||||
</Switch>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
if (hasSamlEnabled) {
|
||||||
|
createNotification({
|
||||||
|
text: "SAML authentication can only be configured in your organization settings",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const newAuthMethods = value
|
||||||
|
? [...authMethods, authMethodOpt.value]
|
||||||
|
: authMethods.filter((auth) => auth !== authMethodOpt.value);
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
const newUser = await mutateAsync({
|
||||||
|
authMethods: newAuthMethods
|
||||||
|
});
|
||||||
|
|
||||||
|
setValue("authMethods", newUser.authMethods);
|
||||||
|
createNotification({
|
||||||
|
text: "Successfully enabled authentication method",
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newAuthMethods.length === 0) {
|
||||||
|
createNotification({
|
||||||
|
text: "You must keep at least 1 authentication method enabled",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newUser = await mutateAsync({
|
||||||
|
authMethods: newAuthMethods
|
||||||
|
});
|
||||||
|
|
||||||
|
setValue("authMethods", newUser.authMethods);
|
||||||
|
createNotification({
|
||||||
|
text: "Successfully disabled authentication method",
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
|
<h2 className="mb-8 flex-1 text-xl font-semibold text-mineshaft-100">
|
||||||
|
Authentication methods
|
||||||
|
</h2>
|
||||||
|
<p className="mb-4 text-gray-400">
|
||||||
|
By enabling a SSO provider, you are allowing an account with that provider which uses the
|
||||||
|
same email address as your existing Infisical account to be able to log in to Infisical.
|
||||||
|
</p>
|
||||||
|
<div className="mb-4">
|
||||||
|
{user &&
|
||||||
|
authMethodOpts.map((authMethodOpt) => {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center p-4" key={`auth-method-${authMethodOpt.value}`}>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<FontAwesomeIcon icon={authMethodOpt.icon} className="mr-4" />
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
id={`enable-${authMethodOpt.value}-auth`}
|
||||||
|
onCheckedChange={(value) => onAuthMethodToggle(value, authMethodOpt)}
|
||||||
|
isChecked={authMethods?.includes(authMethodOpt.value) ?? false}
|
||||||
|
>
|
||||||
|
<p className="mr-4 w-12">{authMethodOpt.label}</p>
|
||||||
|
</Switch>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
+9
-5
@@ -140,15 +140,19 @@ export const AddServiceTokenV3Modal = ({
|
|||||||
if (serviceTokenData) {
|
if (serviceTokenData) {
|
||||||
reset({
|
reset({
|
||||||
name: serviceTokenData.name,
|
name: serviceTokenData.name,
|
||||||
scopes: serviceTokenData.scopes.map((scope: ServiceTokenV3Scope) => {
|
scopes: serviceTokenData.scopes.map(({
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
permissions
|
||||||
|
}: ServiceTokenV3Scope) => {
|
||||||
let permission = "read";
|
let permission = "read";
|
||||||
if (scope.permissions.includes(Permission.WRITE)) {
|
if (permissions.includes(Permission.WRITE)) {
|
||||||
permission = "readWrite";
|
permission = "readWrite";
|
||||||
}
|
}
|
||||||
|
|
||||||
return ({
|
return ({
|
||||||
environment: "dev",
|
environment,
|
||||||
secretPath: "/",
|
secretPath,
|
||||||
permission
|
permission
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
|
|||||||
+4
-14
@@ -1,5 +1,6 @@
|
|||||||
import { faKey, faPencil,faXmark } from "@fortawesome/free-solid-svg-icons";
|
import { faKey, faPencil,faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { format } from "date-fns";
|
||||||
|
|
||||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
@@ -70,17 +71,6 @@ export const ServiceTokenV3Table = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const formatDate = (dateToFormat: string) => {
|
|
||||||
const date = new Date(dateToFormat);
|
|
||||||
const year = date.getFullYear();
|
|
||||||
const month = date.getMonth() + 1;
|
|
||||||
const day = date.getDate();
|
|
||||||
|
|
||||||
const formattedDate = `${day}/${month}/${year}`;
|
|
||||||
|
|
||||||
return formattedDate;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableContainer>
|
<TableContainer>
|
||||||
@@ -171,9 +161,9 @@ export const ServiceTokenV3Table = ({
|
|||||||
})}
|
})}
|
||||||
</Td>
|
</Td>
|
||||||
{/* <Td>{usageCount}</Td> */}
|
{/* <Td>{usageCount}</Td> */}
|
||||||
<Td>{lastUsed ? formatDate(lastUsed) : "-"}</Td>
|
<Td>{lastUsed ? format(new Date(lastUsed), "yyyy-MM-dd") : "-"}</Td>
|
||||||
<Td>{formatDate(createdAt)}</Td>
|
<Td>{format(new Date(createdAt), "yyyy-MM-dd")}</Td>
|
||||||
<Td>{expiresAt ? formatDate(expiresAt) : "-"}</Td>
|
<Td>{expiresAt ? format(new Date(expiresAt), "yyyy-MM-dd") : "-"}</Td>
|
||||||
<Td className="flex justify-end">
|
<Td className="flex justify-end">
|
||||||
<ProjectPermissionCan
|
<ProjectPermissionCan
|
||||||
I={ProjectPermissionActions.Edit}
|
I={ProjectPermissionActions.Edit}
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 913 KiB |
Reference in New Issue
Block a user