diff --git a/.github/workflows/build-docker-image-to-prod.yml b/.github/workflows/build-docker-image-to-prod.yml index 116ca0cf9..bea66e3fc 100644 --- a/.github/workflows/build-docker-image-to-prod.yml +++ b/.github/workflows/build-docker-image-to-prod.yml @@ -93,6 +93,7 @@ jobs: tags: infisical/frontend:test build-args: | POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }} + NEXT_INFISICAL_PLATFORM_VERSION=${{ steps.extract_version.outputs.version }} - name: ⏻ Spawn frontend container run: | docker run -d --rm --name infisical-frontend-test infisical/frontend:test @@ -116,3 +117,4 @@ jobs: platforms: linux/amd64,linux/arm64 build-args: | POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }} + NEXT_INFISICAL_PLATFORM_VERSION=${{ steps.extract_version.outputs.version }} diff --git a/.github/workflows/build-staging-img.yml b/.github/workflows/build-staging-img.yml index 806e89b87..f4233c713 100644 --- a/.github/workflows/build-staging-img.yml +++ b/.github/workflows/build-staging-img.yml @@ -83,6 +83,7 @@ jobs: tags: infisical/staging_deployment_frontend:test build-args: | POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }} + NEXT_INFISICAL_PLATFORM_VERSION=${{ steps.extract_version.outputs.version }} - name: ⏻ Spawn frontend container run: | docker run -d --rm --name infisical-frontend-test infisical/staging_deployment_frontend:test @@ -105,6 +106,7 @@ jobs: platforms: linux/amd64,linux/arm64 build-args: | POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }} + NEXT_INFISICAL_PLATFORM_VERSION=${{ steps.extract_version.outputs.version }} gamma-deployment: name: Deploy to gamma runs-on: ubuntu-latest diff --git a/README.md b/README.md index 499f534a1..900e70625 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,8 @@
-
Open-source, end-to-end encrypted secret management platform: distribute secrets/configs across your team/infrastructure and prevent secret leaks.
+The open-source secret management platform: Sync secrets/configs across your team/infrastructure and prevent secret leaks.
+
## 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.
diff --git a/backend/package-lock.json b/backend/package-lock.json
index 66aeceee2..3429181a2 100644
--- a/backend/package-lock.json
+++ b/backend/package-lock.json
@@ -50,6 +50,7 @@
"nodemailer": "^6.8.0",
"passport": "^0.6.0",
"passport-github": "^1.1.0",
+ "passport-gitlab2": "^5.0.0",
"passport-google-oauth20": "^2.0.0",
"posthog-node": "^2.6.0",
"probot": "^12.3.1",
@@ -63,7 +64,7 @@
"utility-types": "^3.10.0",
"winston": "^3.8.2",
"winston-loki": "^6.0.6",
- "zod": "^3.21.4"
+ "zod": "^3.22.3"
},
"devDependencies": {
"@jest/globals": "^29.3.1",
@@ -13727,6 +13728,17 @@
"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": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/passport-google-oauth20/-/passport-google-oauth20-2.0.0.tgz",
@@ -16684,9 +16696,9 @@
}
},
"node_modules/zod": {
- "version": "3.21.4",
- "resolved": "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz",
- "integrity": "sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==",
+ "version": "3.22.3",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.3.tgz",
+ "integrity": "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
@@ -27163,6 +27175,14 @@
"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": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/passport-google-oauth20/-/passport-google-oauth20-2.0.0.tgz",
@@ -29384,9 +29404,9 @@
"dev": true
},
"zod": {
- "version": "3.21.4",
- "resolved": "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz",
- "integrity": "sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw=="
+ "version": "3.22.3",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.3.tgz",
+ "integrity": "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug=="
}
}
}
diff --git a/backend/package.json b/backend/package.json
index e8ecb384f..cd38aa20f 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -41,6 +41,7 @@
"nodemailer": "^6.8.0",
"passport": "^0.6.0",
"passport-github": "^1.1.0",
+ "passport-gitlab2": "^5.0.0",
"passport-google-oauth20": "^2.0.0",
"posthog-node": "^2.6.0",
"probot": "^12.3.1",
@@ -54,7 +55,7 @@
"utility-types": "^3.10.0",
"winston": "^3.8.2",
"winston-loki": "^6.0.6",
- "zod": "^3.21.4"
+ "zod": "^3.22.3"
},
"name": "infisical-api",
"version": "1.0.0",
diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts
index d84d2ff67..5c3e2f819 100644
--- a/backend/src/config/index.ts
+++ b/backend/src/config/index.ts
@@ -1,3 +1,5 @@
+import { GITLAB_URL } from "../variables";
+
import InfisicalClient from "infisical-node";
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 getClientIdGitHubLogin = async () => (await client.getSecret("CLIENT_ID_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 getPostHogProjectApiKey = async () => (await client.getSecret("POSTHOG_PROJECT_API_KEY")).secretValue || "phc_nSin8j5q2zdhpFDI1ETmFNUIuTG4DwKVyIigrY10XiE";
diff --git a/backend/src/controllers/v2/environmentController.ts b/backend/src/controllers/v2/environmentController.ts
index b111d4566..9441734f9 100644
--- a/backend/src/controllers/v2/environmentController.ts
+++ b/backend/src/controllers/v2/environmentController.ts
@@ -34,10 +34,10 @@ import { Webhook } from "../../models";
* @returns
*/
export const createWorkspaceEnvironment = async (req: Request, res: Response) => {
- /*
+ /*
#swagger.summary = 'Create environment'
#swagger.description = 'Create environment'
-
+
#swagger.security = [{
"apiKeyAuth": []
}]
@@ -46,12 +46,12 @@ export const createWorkspaceEnvironment = async (req: Request, res: Response) =>
"description": "ID of project",
"required": true,
"type": "string"
- }
+ }
- /*
+ /*
#swagger.summary = 'Create environment'
#swagger.description = 'Create environment'
-
+
#swagger.security = [{
"apiKeyAuth": []
}]
@@ -60,7 +60,7 @@ export const createWorkspaceEnvironment = async (req: Request, res: Response) =>
"description": "ID of project",
"required": true,
"type": "string"
- }
+ }
#swagger.requestBody = {
content: {
@@ -88,7 +88,7 @@ export const createWorkspaceEnvironment = async (req: Request, res: Response) =>
#swagger.responses[200] = {
content: {
"application/json": {
- "schema": {
+ "schema": {
"type": "object",
"properties": {
"message": {
@@ -115,7 +115,7 @@ export const createWorkspaceEnvironment = async (req: Request, res: Response) =>
},
"description": "Response after creating a new environment"
}
- }
+ }
}
}
*/
@@ -246,7 +246,7 @@ export const reorderWorkspaceEnvironments = async (req: Request, res: Response)
* @returns
*/
export const renameWorkspaceEnvironment = async (req: Request, res: Response) => {
- /*
+ /*
#swagger.summary = 'Rename workspace environment'
#swagger.description = 'Rename a specific environment within a workspace'
@@ -317,7 +317,7 @@ export const renameWorkspaceEnvironment = async (req: Request, res: Response) =>
}
}
}
- }
+ }
*/
const {
params: { workspaceId },
@@ -394,6 +394,11 @@ export const renameWorkspaceEnvironment = async (req: Request, res: Response) =>
{ workspace: workspaceId, environment: oldEnvironmentSlug },
{ environment: environmentSlug }
);
+ await SecretImport.updateMany(
+ { workspace: workspaceId, "imports.environment": oldEnvironmentSlug },
+ { $set: { "imports.$[element].environment": environmentSlug } },
+ { arrayFilters: [{ "element.environment": oldEnvironmentSlug }] },
+ );
await ServiceAccountWorkspacePermission.updateMany(
{ workspace: workspaceId, environment: oldEnvironmentSlug },
@@ -447,10 +452,10 @@ export const renameWorkspaceEnvironment = async (req: Request, res: Response) =>
* @returns
*/
export const deleteWorkspaceEnvironment = async (req: Request, res: Response) => {
- /*
+ /*
#swagger.summary = 'Delete workspace environment'
#swagger.description = 'Delete a specific environment from a workspace'
-
+
#swagger.security = [{
"apiKeyAuth": []
}]
@@ -483,7 +488,7 @@ export const deleteWorkspaceEnvironment = async (req: Request, res: Response) =>
#swagger.responses[200] = {
content: {
"application/json": {
- "schema": {
+ "schema": {
"type": "object",
"properties": {
"message": {
@@ -501,9 +506,9 @@ export const deleteWorkspaceEnvironment = async (req: Request, res: Response) =>
},
"description": "Response after deleting an environment from a workspace"
}
- }
+ }
}
- }
+ }
*/
const {
params: { workspaceId },
@@ -590,10 +595,10 @@ export const deleteWorkspaceEnvironment = async (req: Request, res: Response) =>
// TODO(akhilmhdh) after rbac this can be completely removed
export const getAllAccessibleEnvironmentsOfWorkspace = async (req: Request, res: Response) => {
- /*
+ /*
#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.security = [{
"apiKeyAuth": []
}]
@@ -640,7 +645,7 @@ export const getAllAccessibleEnvironmentsOfWorkspace = async (req: Request, res:
}
}
}
- }
+ }
*/
const {
params: { workspaceId }
diff --git a/backend/src/controllers/v3/secretsController.ts b/backend/src/controllers/v3/secretsController.ts
index f8e5e8702..482e5baa6 100644
--- a/backend/src/controllers/v3/secretsController.ts
+++ b/backend/src/controllers/v3/secretsController.ts
@@ -34,6 +34,93 @@ import {
} from "../../validation";
import { PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS } from "../../variables";
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
@@ -73,54 +160,13 @@ export const getSecretsRaw = async (req: Request, res: Response) => {
if (!environment || !workspaceId)
throw BadRequestError({ message: "Missing environment or workspace id" });
- let permissionCheckFn: (env: string, secPath: string) => boolean; // used to pass as callback function to import secret
- switch (req.authData.actor.type) {
- case ActorType.USER: {
- const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
- ForbiddenError.from(permission).throwUnlessCan(
- 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 permissionCheckFn = await checkSecretsPermission({
+ authData: req.authData,
+ workspaceId,
+ environment,
+ secretPath,
+ secretAction: ProjectPermissionActions.Read
+ });
const secrets = await SecretService.getSecrets({
workspaceId: new Types.ObjectId(workspaceId),
@@ -186,38 +232,14 @@ export const getSecretByNameRaw = async (req: Request, res: Response) => {
query: { secretPath, environment, workspaceId, type, include_imports },
params: { secretName }
} = await validateRequest(reqValidator.GetSecretByNameRawV3, req);
-
- switch (req.authData.actor.type) {
- case ActorType.USER: {
- const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
- ForbiddenError.from(permission).throwUnlessCan(
- ProjectPermissionActions.Read,
- subject(ProjectPermissionSub.Secrets, { environment, secretPath })
- );
- 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;
- }
- }
+
+ await checkSecretsPermission({
+ authData: req.authData,
+ workspaceId,
+ environment,
+ secretPath,
+ secretAction: ProjectPermissionActions.Read
+ });
const secret = await SecretService.getSecret({
secretName,
@@ -260,37 +282,13 @@ export const createSecretRaw = async (req: Request, res: Response) => {
}
} = await validateRequest(reqValidator.CreateSecretRawV3, req);
- switch (req.authData.actor.type) {
- case ActorType.USER: {
- const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
- ForbiddenError.from(permission).throwUnlessCan(
- ProjectPermissionActions.Create,
- subject(ProjectPermissionSub.Secrets, { environment, secretPath })
- );
- 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;
- }
- }
+ await checkSecretsPermission({
+ authData: req.authData,
+ workspaceId,
+ environment,
+ secretPath,
+ secretAction: ProjectPermissionActions.Create
+ });
const key = await BotService.getWorkspaceKeyWithBot({
workspaceId: new Types.ObjectId(workspaceId)
@@ -360,37 +358,13 @@ export const updateSecretByNameRaw = async (req: Request, res: Response) => {
body: { secretValue, environment, secretPath, type, workspaceId, skipMultilineEncoding }
} = await validateRequest(reqValidator.UpdateSecretByNameRawV3, req);
- switch (req.authData.actor.type) {
- case ActorType.USER: {
- const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
- ForbiddenError.from(permission).throwUnlessCan(
- ProjectPermissionActions.Edit,
- subject(ProjectPermissionSub.Secrets, { environment, secretPath })
- );
- 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;
- }
- }
+ await checkSecretsPermission({
+ authData: req.authData,
+ workspaceId,
+ environment,
+ secretPath,
+ secretAction: ProjectPermissionActions.Edit
+ });
const key = await BotService.getWorkspaceKeyWithBot({
workspaceId: new Types.ObjectId(workspaceId)
@@ -441,37 +415,13 @@ export const deleteSecretByNameRaw = async (req: Request, res: Response) => {
body: { environment, secretPath, type, workspaceId }
} = await validateRequest(reqValidator.DeleteSecretByNameRawV3, req);
- switch (req.authData.actor.type) {
- case ActorType.USER: {
- const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
- ForbiddenError.from(permission).throwUnlessCan(
- ProjectPermissionActions.Delete,
- subject(ProjectPermissionSub.Secrets, { environment, secretPath })
- );
- 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;
- }
- }
+ await checkSecretsPermission({
+ authData: req.authData,
+ workspaceId,
+ environment,
+ secretPath,
+ secretAction: ProjectPermissionActions.Delete
+ });
const { secret } = await SecretService.deleteSecret({
secretName,
@@ -525,55 +475,13 @@ export const getSecrets = async (req: Request, res: Response) => {
secretPath = getFolderWithPathFromId(folder.nodes, folderId).folderPath;
}
- let permissionCheckFn: (env: string, secPath: string) => boolean; // used to pass as callback function to import secret
-
- switch (req.authData.actor.type) {
- case ActorType.USER: {
- const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
- ForbiddenError.from(permission).throwUnlessCan(
- 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 permissionCheckFn = await checkSecretsPermission({
+ authData: req.authData,
+ workspaceId,
+ environment,
+ secretPath,
+ secretAction: ProjectPermissionActions.Read
+ });
const secrets = await SecretService.getSecrets({
workspaceId: new Types.ObjectId(workspaceId),
@@ -622,37 +530,13 @@ export const getSecretByName = async (req: Request, res: Response) => {
params: { secretName }
} = await validateRequest(reqValidator.GetSecretByNameV3, req);
- switch (req.authData.actor.type) {
- case ActorType.USER: {
- const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
- ForbiddenError.from(permission).throwUnlessCan(
- ProjectPermissionActions.Read,
- subject(ProjectPermissionSub.Secrets, { environment, secretPath })
- );
- 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;
- }
- }
+ await checkSecretsPermission({
+ authData: req.authData,
+ workspaceId,
+ environment,
+ secretPath,
+ secretAction: ProjectPermissionActions.Read
+ });
const secret = await SecretService.getSecret({
secretName,
@@ -695,38 +579,14 @@ export const createSecret = async (req: Request, res: Response) => {
},
params: { secretName }
} = await validateRequest(reqValidator.CreateSecretV3, req);
-
- switch (req.authData.actor.type) {
- case ActorType.USER: {
- const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
- ForbiddenError.from(permission).throwUnlessCan(
- ProjectPermissionActions.Create,
- subject(ProjectPermissionSub.Secrets, { environment, secretPath })
- );
- 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;
- }
- }
+
+ await checkSecretsPermission({
+ authData: req.authData,
+ workspaceId,
+ environment,
+ secretPath,
+ secretAction: ProjectPermissionActions.Create
+ });
const secret = await SecretService.createSecret({
secretName,
@@ -796,38 +656,14 @@ export const updateSecretByName = async (req: Request, res: Response) => {
throw BadRequestError({ message: "Missing encrypted key" });
}
- switch (req.authData.actor.type) {
- case ActorType.USER: {
- const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
- ForbiddenError.from(permission).throwUnlessCan(
- ProjectPermissionActions.Edit,
- subject(ProjectPermissionSub.Secrets, { environment, secretPath })
- );
- 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;
- }
- }
-
+ await checkSecretsPermission({
+ authData: req.authData,
+ workspaceId,
+ environment,
+ secretPath,
+ secretAction: ProjectPermissionActions.Edit
+ });
+
const secret = await SecretService.updateSecret({
secretName,
workspaceId: new Types.ObjectId(workspaceId),
@@ -873,37 +709,13 @@ export const deleteSecretByName = async (req: Request, res: Response) => {
params: { secretName }
} = await validateRequest(reqValidator.DeleteSecretByNameV3, req);
- switch (req.authData.actor.type) {
- case ActorType.USER: {
- const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
- ForbiddenError.from(permission).throwUnlessCan(
- ProjectPermissionActions.Delete,
- subject(ProjectPermissionSub.Secrets, { environment, secretPath })
- );
- 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;
- }
- }
+ await checkSecretsPermission({
+ authData: req.authData,
+ workspaceId,
+ environment,
+ secretPath,
+ secretAction: ProjectPermissionActions.Delete
+ });
const { secret } = await SecretService.deleteSecret({
secretName,
@@ -931,38 +743,14 @@ export const createSecretByNameBatch = async (req: Request, res: Response) => {
const {
body: { secrets, secretPath, environment, workspaceId }
} = await validateRequest(reqValidator.CreateSecretByNameBatchV3, req);
-
- switch (req.authData.actor.type) {
- case ActorType.USER: {
- const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
- ForbiddenError.from(permission).throwUnlessCan(
- ProjectPermissionActions.Create,
- subject(ProjectPermissionSub.Secrets, { environment, secretPath })
- );
- 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;
- }
- }
+
+ await checkSecretsPermission({
+ authData: req.authData,
+ workspaceId,
+ environment,
+ secretPath,
+ secretAction: ProjectPermissionActions.Create
+ });
const createdSecrets = await SecretService.createSecretBatch({
secretPath,
@@ -982,37 +770,13 @@ export const updateSecretByNameBatch = async (req: Request, res: Response) => {
body: { secrets, secretPath, environment, workspaceId }
} = await validateRequest(reqValidator.UpdateSecretByNameBatchV3, req);
- switch (req.authData.actor.type) {
- case ActorType.USER: {
- const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
- ForbiddenError.from(permission).throwUnlessCan(
- ProjectPermissionActions.Edit,
- subject(ProjectPermissionSub.Secrets, { environment, secretPath })
- );
- 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;
- }
- }
+ await checkSecretsPermission({
+ authData: req.authData,
+ workspaceId,
+ environment,
+ secretPath,
+ secretAction: ProjectPermissionActions.Edit
+ });
const updatedSecrets = await SecretService.updateSecretBatch({
secretPath,
@@ -1032,37 +796,13 @@ export const deleteSecretByNameBatch = async (req: Request, res: Response) => {
body: { secrets, secretPath, environment, workspaceId }
} = await validateRequest(reqValidator.DeleteSecretByNameBatchV3, req);
- switch (req.authData.actor.type) {
- case ActorType.USER: {
- const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
- ForbiddenError.from(permission).throwUnlessCan(
- ProjectPermissionActions.Delete,
- subject(ProjectPermissionSub.Secrets, { environment, secretPath })
- );
- 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;
- }
- }
+ await checkSecretsPermission({
+ authData: req.authData,
+ workspaceId,
+ environment,
+ secretPath,
+ secretAction: ProjectPermissionActions.Delete
+ });
const deletedSecrets = await SecretService.deleteSecretBatch({
secretPath,
@@ -1075,4 +815,4 @@ export const deleteSecretByNameBatch = async (req: Request, res: Response) => {
return res.status(200).send({
secrets: deletedSecrets
});
-};
+};
\ No newline at end of file
diff --git a/backend/src/ee/controllers/v3/serviceTokenDataController.ts b/backend/src/ee/controllers/v3/serviceTokenDataController.ts
index 2c5d6d3d4..3f57602f9 100644
--- a/backend/src/ee/controllers/v3/serviceTokenDataController.ts
+++ b/backend/src/ee/controllers/v3/serviceTokenDataController.ts
@@ -112,7 +112,7 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
user = req.authData.authPayload._id;
}
- const isActive = false;
+ const isActive = true;
const serviceTokenData = await new ServiceTokenDataV3({
name,
user,
@@ -160,7 +160,7 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
return res.status(200).send({
serviceTokenData,
- serviceToken: `proj_token.${token}`
+ serviceToken: `stv3.${token}`
});
}
diff --git a/backend/src/ee/routes/v1/sso.ts b/backend/src/ee/routes/v1/sso.ts
index 90d25cd45..baa75d505 100644
--- a/backend/src/ee/routes/v1/sso.ts
+++ b/backend/src/ee/routes/v1/sso.ts
@@ -6,60 +6,20 @@ import { ssoController } from "../../controllers/v1";
import { authLimiter } from "../../../helpers/rateLimiter";
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(
- "/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,
- scope: [ 'user:email' ],
- ...(req.query.callback_port
- ? {
- state: req.query.callback_port as string
- }
- : {})
- })(req, res, next);
-});
-
-router.get(
- "/github",
+ "/redirect/saml2/:ssoIdentifier",
authLimiter,
- passport.authenticate("github", {
- failureRedirect: "/login/provider/error",
- session: false,
- scope: [ 'user:email' ]
- }),
- ssoController.redirectSSO
+ (req, res, next) => {
+ const options = {
+ failureRedirect: "/",
+ additionalParams: {
+ RelayState: req.query.callback_port ?? ""
+ },
+ };
+ 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(
"/saml2/:ssoIdentifier",
passport.authenticate("saml", {
diff --git a/backend/src/helpers/auth.ts b/backend/src/helpers/auth.ts
index 861301fb6..31e584fe5 100644
--- a/backend/src/helpers/auth.ts
+++ b/backend/src/helpers/auth.ts
@@ -86,7 +86,7 @@ export const validateAuthMode = ({
authMode = AuthMode.SERVICE_TOKEN;
authTokenValue = tokenValue;
break;
- case "proj_token":
+ case "stv3":
authMode = AuthMode.SERVICE_TOKEN_V3;
authTokenValue = parts.slice(1).join(".");
break;
diff --git a/backend/src/index.ts b/backend/src/index.ts
index fbe85c3c1..602da13ec 100644
--- a/backend/src/index.ts
+++ b/backend/src/index.ts
@@ -41,6 +41,7 @@ import {
membership as v1MembershipRouter,
organization as v1OrganizationRouter,
password as v1PasswordRouter,
+ sso as v1SSORouter,
secretApprovalPolicy as v1SecretApprovalPolicy,
secretImps as v1SecretImpsRouter,
secret as v1SecretRouter,
@@ -181,6 +182,7 @@ const main = async () => {
app.use("/api/v1/secret-imports", v1SecretImpsRouter);
app.use("/api/v1/roles", v1RoleRouter);
app.use("/api/v1/secret-approvals", v1SecretApprovalPolicy);
+ app.use("/api/v1/sso", v1SSORouter);
// v2 routes (improvements)
app.use("/api/v2/signup", v2SignupRouter);
diff --git a/backend/src/models/user.ts b/backend/src/models/user.ts
index c85c0936b..911776a63 100644
--- a/backend/src/models/user.ts
+++ b/backend/src/models/user.ts
@@ -4,6 +4,7 @@ export enum AuthMethod {
EMAIL = "email",
GOOGLE = "google",
GITHUB = "github",
+ GITLAB = "gitlab",
OKTA_SAML = "okta-saml",
AZURE_SAML = "azure-saml",
JUMPCLOUD_SAML = "jumpcloud-saml",
diff --git a/backend/src/routes/v1/index.ts b/backend/src/routes/v1/index.ts
index cfdccdc92..50275d00f 100644
--- a/backend/src/routes/v1/index.ts
+++ b/backend/src/routes/v1/index.ts
@@ -11,6 +11,7 @@ import key from "./key";
import inviteOrg from "./inviteOrg";
import secret from "./secret";
import serviceToken from "./serviceToken";
+import sso from "./sso";
import password from "./password";
import integration from "./integration";
import integrationAuth from "./integrationAuth";
@@ -39,5 +40,6 @@ export {
secretsFolder,
webhooks,
secretImps,
+ sso,
secretApprovalPolicy
};
diff --git a/backend/src/routes/v1/sso.ts b/backend/src/routes/v1/sso.ts
new file mode 100644
index 000000000..b06ba9986
--- /dev/null
+++ b/backend/src/routes/v1/sso.ts
@@ -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;
\ No newline at end of file
diff --git a/backend/src/routes/v2/users.ts b/backend/src/routes/v2/users.ts
index c093be21c..41d4c8e6b 100644
--- a/backend/src/routes/v2/users.ts
+++ b/backend/src/routes/v2/users.ts
@@ -29,11 +29,11 @@ router.patch(
);
router.put(
- "/me/auth-methods",
- requireAuth({
- acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
- }),
- usersController.updateAuthMethods
+ "/me/auth-methods",
+ requireAuth({
+ acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY],
+ }),
+ usersController.updateAuthMethods,
);
router.get(
diff --git a/backend/src/utils/auth.ts b/backend/src/utils/auth.ts
index 45e8e47f5..6f46824d4 100644
--- a/backend/src/utils/auth.ts
+++ b/backend/src/utils/auth.ts
@@ -14,16 +14,19 @@ import {
import { createToken } from "../helpers/auth";
import {
getClientIdGitHubLogin,
+ getClientIdGitLabLogin,
getClientIdGoogleLogin,
getClientSecretGitHubLogin,
+ getClientSecretGitLabLogin,
getClientSecretGoogleLogin,
getJwtProviderAuthLifetime,
getJwtProviderAuthSecret,
+ getSiteURL,
+ getUrlGitLabLogin
} from "../config";
import { getSSOConfigHelper } from "../ee/helpers/organizations";
import { InternalServerError, OrganizationNotFoundError } from "./errors";
import { ACCEPTED, INTEGRATION_GITHUB_API_URL, INVITED, MEMBER } from "../variables";
-import { getSiteURL } from "../config";
import { standardRequest } from "../config/request";
// 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
const GitHubStrategy = require("passport-github").Strategy;
// 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");
/**
@@ -84,6 +89,9 @@ const initializePassport = async () => {
const clientSecretGoogleLogin = await getClientSecretGoogleLogin();
const clientIdGitHubLogin = await getClientIdGitHubLogin();
const clientSecretGitHubLogin = await getClientSecretGitHubLogin();
+ const urlGitLab = await getUrlGitLabLogin();
+ const clientIdGitLabLogin = await getClientIdGitLabLogin();
+ const clientSecretGitLabLogin = await getClientSecretGitLabLogin();
if (clientIdGoogleLogin && clientSecretGoogleLogin) {
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(
{
diff --git a/backend/src/variables/integration.ts b/backend/src/variables/integration.ts
index 3adfad4a8..e6e20e73c 100644
--- a/backend/src/variables/integration.ts
+++ b/backend/src/variables/integration.ts
@@ -84,7 +84,8 @@ export const INTEGRATION_BITBUCKET_TOKEN_URL = "https://bitbucket.org/site/oauth
// integration apps endpoints
export const INTEGRATION_GCP_API_URL = "https://cloudresourcemanager.googleapis.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_VERCEL_API_URL = "https://api.vercel.com";
export const INTEGRATION_NETLIFY_API_URL = "https://api.netlify.com";
diff --git a/docs/documentation/platform/sso/gitlab.mdx b/docs/documentation/platform/sso/gitlab.mdx
new file mode 100644
index 000000000..cac946280
--- /dev/null
+++ b/docs/documentation/platform/sso/gitlab.mdx
@@ -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.
+
+
+
+| {name} | -{formatDate(lastUsed)} | -{formatDate(createdAt)} | -{formatDate(expiresAt)} | +{format(new Date(lastUsed), "yyyy-MM-dd")} | +{format(new Date(createdAt), "yyyy-MM-dd")} | +{format(new Date(expiresAt), "yyyy-MM-dd")} |
-
+ }, [user]);
+
+ const onAuthMethodToggle = async (value: boolean, authMethodOpt: AuthMethodOption) => {
+ const hasSamlEnabled = user.authMethods.some((authMethod: AuthMethod) =>
+ samlProviders.includes(authMethod)
);
-}
+
+ 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 (
+ - Authentication methods --- 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. - -
- {user && authMethodOpts.map((authMethodOpt) => {
- return (
-
-
-
- );
- })}
-
-
- {authMethodOpt.label} -
+
+ );
+};
diff --git a/frontend/src/views/Settings/ProjectSettingsPage/components/ServiceTokenV3Section/AddServiceTokenV3Modal.tsx b/frontend/src/views/Settings/ProjectSettingsPage/components/ServiceTokenV3Section/AddServiceTokenV3Modal.tsx
index ea8ab6f4c..cc3c81bab 100644
--- a/frontend/src/views/Settings/ProjectSettingsPage/components/ServiceTokenV3Section/AddServiceTokenV3Modal.tsx
+++ b/frontend/src/views/Settings/ProjectSettingsPage/components/ServiceTokenV3Section/AddServiceTokenV3Modal.tsx
@@ -140,15 +140,19 @@ export const AddServiceTokenV3Modal = ({
if (serviceTokenData) {
reset({
name: serviceTokenData.name,
- scopes: serviceTokenData.scopes.map((scope: ServiceTokenV3Scope) => {
+ scopes: serviceTokenData.scopes.map(({
+ environment,
+ secretPath,
+ permissions
+ }: ServiceTokenV3Scope) => {
let permission = "read";
- if (scope.permissions.includes(Permission.WRITE)) {
+ if (permissions.includes(Permission.WRITE)) {
permission = "readWrite";
}
-
+
return ({
- environment: "dev",
- secretPath: "/",
+ environment,
+ secretPath,
permission
})
}),
diff --git a/frontend/src/views/Settings/ProjectSettingsPage/components/ServiceTokenV3Section/ServiceTokenV3Table.tsx b/frontend/src/views/Settings/ProjectSettingsPage/components/ServiceTokenV3Section/ServiceTokenV3Table.tsx
index e30d83eca..dd9268bf6 100644
--- a/frontend/src/views/Settings/ProjectSettingsPage/components/ServiceTokenV3Section/ServiceTokenV3Table.tsx
+++ b/frontend/src/views/Settings/ProjectSettingsPage/components/ServiceTokenV3Section/ServiceTokenV3Table.tsx
@@ -1,5 +1,6 @@
import { faKey, faPencil,faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { format } from "date-fns";
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
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 (
+ Authentication methods +++ 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. + +
+ {user &&
+ authMethodOpts.map((authMethodOpt) => {
+ return (
+
+
+
+ );
+ })}
+
+
+ {authMethodOpt.label} + |
{/* {usageCount} | */} -{lastUsed ? formatDate(lastUsed) : "-"} | -{formatDate(createdAt)} | -{expiresAt ? formatDate(expiresAt) : "-"} | +{lastUsed ? format(new Date(lastUsed), "yyyy-MM-dd") : "-"} | +{format(new Date(createdAt), "yyyy-MM-dd")} | +{expiresAt ? format(new Date(expiresAt), "yyyy-MM-dd") : "-"} |
|