Adjust ST V3

This commit is contained in:
Tuan Dang
2023-10-05 14:53:59 +01:00
40 changed files with 905 additions and 943 deletions
+5
View File
@@ -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";
@@ -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 }
+183 -443
View File
@@ -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
});
};
};
@@ -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}`
});
}
+10 -50
View File
@@ -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", {
+1 -1
View File
@@ -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;
+2
View File
@@ -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);
+1
View File
@@ -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",
+2
View File
@@ -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
};
+72
View File
@@ -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;
+5 -5
View File
@@ -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(
+63 -1
View File
@@ -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(
{
+2 -1
View File
@@ -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";