mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 22:27:22 +00:00
Scope switch cases into blocks for secrets v3
This commit is contained in:
@@ -3,10 +3,22 @@ import { Types } from "mongoose";
|
|||||||
import { EventService, SecretService } from "../../services";
|
import { EventService, SecretService } from "../../services";
|
||||||
import { eventPushSecrets } from "../../events";
|
import { eventPushSecrets } from "../../events";
|
||||||
import { BotService } from "../../services";
|
import { BotService } from "../../services";
|
||||||
import { containsGlobPatterns, isValidScope, repackageSecretToRaw } from "../../helpers/secrets";
|
import {
|
||||||
|
containsGlobPatterns,
|
||||||
|
isValidScope,
|
||||||
|
isValidScopeV3,
|
||||||
|
repackageSecretToRaw
|
||||||
|
} from "../../helpers/secrets";
|
||||||
import { encryptSymmetric128BitHexKeyUTF8 } from "../../utils/crypto";
|
import { encryptSymmetric128BitHexKeyUTF8 } from "../../utils/crypto";
|
||||||
import { getAllImportedSecrets } from "../../services/SecretImportService";
|
import { getAllImportedSecrets } from "../../services/SecretImportService";
|
||||||
import { Folder, IServiceTokenData } from "../../models";
|
import {
|
||||||
|
Folder,
|
||||||
|
IServiceTokenData,
|
||||||
|
IServiceTokenDataV3
|
||||||
|
} from "../../models";
|
||||||
|
import {
|
||||||
|
Permission
|
||||||
|
} from "../../models/serviceTokenDataV3";
|
||||||
import { getFolderByPath, getFolderWithPathFromId } from "../../services/FolderService";
|
import { getFolderByPath, getFolderWithPathFromId } from "../../services/FolderService";
|
||||||
import { BadRequestError } from "../../utils/errors";
|
import { BadRequestError } from "../../utils/errors";
|
||||||
import { validateRequest } from "../../helpers/validation";
|
import { validateRequest } from "../../helpers/validation";
|
||||||
@@ -17,8 +29,12 @@ import {
|
|||||||
getUserProjectPermissions
|
getUserProjectPermissions
|
||||||
} from "../../ee/services/ProjectRoleService";
|
} from "../../ee/services/ProjectRoleService";
|
||||||
import { ForbiddenError, subject } from "@casl/ability";
|
import { ForbiddenError, subject } from "@casl/ability";
|
||||||
import { validateServiceTokenDataClientForWorkspace } from "../../validation";
|
import {
|
||||||
|
validateServiceTokenDataClientForWorkspace,
|
||||||
|
validateServiceTokenDataV3ClientForWorkspace
|
||||||
|
} 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";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return secrets for workspace with id [workspaceId] and environment
|
* Return secrets for workspace with id [workspaceId] and environment
|
||||||
@@ -59,30 +75,52 @@ export const getSecretsRaw = async (req: Request, res: Response) => {
|
|||||||
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
|
let permissionCheckFn: (env: string, secPath: string) => boolean; // used to pass as callback function to import secret
|
||||||
if (req.user?._id) {
|
switch (req.authData.actor.type) {
|
||||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
case ActorType.USER: {
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||||
ProjectPermissionActions.Read,
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
|
||||||
);
|
|
||||||
permissionCheckFn = (env: string, secPath: string) =>
|
|
||||||
permission.can(
|
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionActions.Read,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
||||||
environment: env,
|
|
||||||
secretPath: secPath
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
} else {
|
permissionCheckFn = (env: string, secPath: string) =>
|
||||||
await validateServiceTokenDataClientForWorkspace({
|
permission.can(
|
||||||
serviceTokenData: req.authData.authPayload as IServiceTokenData,
|
ProjectPermissionActions.Read,
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment,
|
environment: env,
|
||||||
secretPath,
|
secretPath: secPath
|
||||||
requiredPermissions: [PERMISSION_READ_SECRETS]
|
})
|
||||||
});
|
);
|
||||||
permissionCheckFn = (env: string, secPath: string) =>
|
break;
|
||||||
isValidScope(req.authData.authPayload as IServiceTokenData, env, secPath);
|
}
|
||||||
|
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) =>
|
||||||
|
isValidScope(req.authData.authPayload as IServiceTokenData, env, secPath);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ActorType.SERVICE_V3: {
|
||||||
|
await validateServiceTokenDataV3ClientForWorkspace({
|
||||||
|
serviceTokenData: req.authData.authPayload as IServiceTokenDataV3,
|
||||||
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
acceptedPermissions: [Permission.READ, Permission.READ_WRITE]
|
||||||
|
});
|
||||||
|
permissionCheckFn = (env: string, secPath: string) =>
|
||||||
|
isValidScopeV3({
|
||||||
|
authPayload: req.authData.authPayload as IServiceTokenDataV3,
|
||||||
|
environment: env,
|
||||||
|
secretPath: secPath,
|
||||||
|
acceptedPermissions: [Permission.READ, Permission.READ_WRITE]
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const secrets = await SecretService.getSecrets({
|
const secrets = await SecretService.getSecrets({
|
||||||
@@ -150,20 +188,35 @@ export const getSecretByNameRaw = async (req: Request, res: Response) => {
|
|||||||
params: { secretName }
|
params: { secretName }
|
||||||
} = await validateRequest(reqValidator.GetSecretByNameRawV3, req);
|
} = await validateRequest(reqValidator.GetSecretByNameRawV3, req);
|
||||||
|
|
||||||
if (req.user?._id) {
|
switch (req.authData.actor.type) {
|
||||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
case ActorType.USER: {
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||||
ProjectPermissionActions.Read,
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
ProjectPermissionActions.Read,
|
||||||
);
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
||||||
} else {
|
);
|
||||||
await validateServiceTokenDataClientForWorkspace({
|
break;
|
||||||
serviceTokenData: req.authData.authPayload as IServiceTokenData,
|
}
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
case ActorType.SERVICE: {
|
||||||
environment,
|
await validateServiceTokenDataClientForWorkspace({
|
||||||
secretPath,
|
serviceTokenData: req.authData.authPayload as IServiceTokenData,
|
||||||
requiredPermissions: [PERMISSION_READ_SECRETS]
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
});
|
environment,
|
||||||
|
secretPath,
|
||||||
|
requiredPermissions: [PERMISSION_READ_SECRETS]
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ActorType.SERVICE_V3: {
|
||||||
|
await validateServiceTokenDataV3ClientForWorkspace({
|
||||||
|
serviceTokenData: req.authData.authPayload as IServiceTokenDataV3,
|
||||||
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
acceptedPermissions: [Permission.READ]
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const secret = await SecretService.getSecret({
|
const secret = await SecretService.getSecret({
|
||||||
@@ -198,20 +251,35 @@ export const createSecretRaw = async (req: Request, res: Response) => {
|
|||||||
body: { secretPath, environment, workspaceId, type, secretValue, secretComment }
|
body: { secretPath, environment, workspaceId, type, secretValue, secretComment }
|
||||||
} = await validateRequest(reqValidator.CreateSecretRawV3, req);
|
} = await validateRequest(reqValidator.CreateSecretRawV3, req);
|
||||||
|
|
||||||
if (req.user?._id) {
|
switch (req.authData.actor.type) {
|
||||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
case ActorType.USER: {
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||||
ProjectPermissionActions.Create,
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
ProjectPermissionActions.Create,
|
||||||
);
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
||||||
} else {
|
);
|
||||||
await validateServiceTokenDataClientForWorkspace({
|
break;
|
||||||
serviceTokenData: req.authData.authPayload as IServiceTokenData,
|
}
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
case ActorType.SERVICE: {
|
||||||
environment,
|
await validateServiceTokenDataClientForWorkspace({
|
||||||
secretPath,
|
serviceTokenData: req.authData.authPayload as IServiceTokenData,
|
||||||
requiredPermissions: [PERMISSION_WRITE_SECRETS]
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
});
|
environment,
|
||||||
|
secretPath,
|
||||||
|
requiredPermissions: [PERMISSION_WRITE_SECRETS]
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ActorType.SERVICE_V3: {
|
||||||
|
await validateServiceTokenDataV3ClientForWorkspace({
|
||||||
|
serviceTokenData: req.authData.authPayload as IServiceTokenDataV3,
|
||||||
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
acceptedPermissions: [Permission.READ_WRITE]
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const key = await BotService.getWorkspaceKeyWithBot({
|
const key = await BotService.getWorkspaceKeyWithBot({
|
||||||
@@ -281,20 +349,35 @@ export const updateSecretByNameRaw = async (req: Request, res: Response) => {
|
|||||||
body: { secretValue, environment, secretPath, type, workspaceId }
|
body: { secretValue, environment, secretPath, type, workspaceId }
|
||||||
} = await validateRequest(reqValidator.UpdateSecretByNameRawV3, req);
|
} = await validateRequest(reqValidator.UpdateSecretByNameRawV3, req);
|
||||||
|
|
||||||
if (req.user?._id) {
|
switch (req.authData.actor.type) {
|
||||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
case ActorType.USER: {
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||||
ProjectPermissionActions.Edit,
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
ProjectPermissionActions.Edit,
|
||||||
);
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
||||||
} else {
|
);
|
||||||
await validateServiceTokenDataClientForWorkspace({
|
break;
|
||||||
serviceTokenData: req.authData.authPayload as IServiceTokenData,
|
}
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
case ActorType.SERVICE: {
|
||||||
environment,
|
await validateServiceTokenDataClientForWorkspace({
|
||||||
secretPath,
|
serviceTokenData: req.authData.authPayload as IServiceTokenData,
|
||||||
requiredPermissions: [PERMISSION_WRITE_SECRETS]
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
});
|
environment,
|
||||||
|
secretPath,
|
||||||
|
requiredPermissions: [PERMISSION_WRITE_SECRETS]
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ActorType.SERVICE_V3: {
|
||||||
|
await validateServiceTokenDataV3ClientForWorkspace({
|
||||||
|
serviceTokenData: req.authData.authPayload as IServiceTokenDataV3,
|
||||||
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
acceptedPermissions: [Permission.READ_WRITE]
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const key = await BotService.getWorkspaceKeyWithBot({
|
const key = await BotService.getWorkspaceKeyWithBot({
|
||||||
@@ -345,20 +428,35 @@ 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);
|
||||||
|
|
||||||
if (req.user?._id) {
|
switch (req.authData.actor.type) {
|
||||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
case ActorType.USER: {
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||||
ProjectPermissionActions.Delete,
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
ProjectPermissionActions.Delete,
|
||||||
);
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
||||||
} else {
|
);
|
||||||
await validateServiceTokenDataClientForWorkspace({
|
break;
|
||||||
serviceTokenData: req.authData.authPayload as IServiceTokenData,
|
}
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
case ActorType.SERVICE: {
|
||||||
environment,
|
await validateServiceTokenDataClientForWorkspace({
|
||||||
secretPath,
|
serviceTokenData: req.authData.authPayload as IServiceTokenData,
|
||||||
requiredPermissions: [PERMISSION_WRITE_SECRETS]
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
});
|
environment,
|
||||||
|
secretPath,
|
||||||
|
requiredPermissions: [PERMISSION_WRITE_SECRETS]
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ActorType.SERVICE_V3: {
|
||||||
|
await validateServiceTokenDataV3ClientForWorkspace({
|
||||||
|
serviceTokenData: req.authData.authPayload as IServiceTokenDataV3,
|
||||||
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
acceptedPermissions: [Permission.READ_WRITE]
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const { secret } = await SecretService.deleteSecret({
|
const { secret } = await SecretService.deleteSecret({
|
||||||
@@ -414,30 +512,53 @@ export const getSecrets = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let permissionCheckFn: (env: string, secPath: string) => boolean; // used to pass as callback function to import secret
|
let permissionCheckFn: (env: string, secPath: string) => boolean; // used to pass as callback function to import secret
|
||||||
if (req.user?._id) {
|
|
||||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
switch (req.authData.actor.type) {
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
case ActorType.USER: {
|
||||||
ProjectPermissionActions.Read,
|
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
);
|
|
||||||
permissionCheckFn = (env: string, secPath: string) =>
|
|
||||||
permission.can(
|
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionActions.Read,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
||||||
environment: env,
|
|
||||||
secretPath: secPath
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
} else {
|
permissionCheckFn = (env: string, secPath: string) =>
|
||||||
await validateServiceTokenDataClientForWorkspace({
|
permission.can(
|
||||||
serviceTokenData: req.authData.authPayload as IServiceTokenData,
|
ProjectPermissionActions.Read,
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment,
|
environment: env,
|
||||||
secretPath,
|
secretPath: secPath
|
||||||
requiredPermissions: [PERMISSION_READ_SECRETS]
|
})
|
||||||
});
|
);
|
||||||
permissionCheckFn = (env: string, secPath: string) =>
|
break;
|
||||||
isValidScope(req.authData.authPayload as IServiceTokenData, env, secPath);
|
}
|
||||||
|
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) =>
|
||||||
|
isValidScope(req.authData.authPayload as IServiceTokenData, env, secPath);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ActorType.SERVICE_V3: {
|
||||||
|
await validateServiceTokenDataV3ClientForWorkspace({
|
||||||
|
serviceTokenData: req.authData.authPayload as IServiceTokenDataV3,
|
||||||
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
acceptedPermissions: [Permission.READ, Permission.READ_WRITE]
|
||||||
|
});
|
||||||
|
permissionCheckFn = (env: string, secPath: string) =>
|
||||||
|
isValidScopeV3({
|
||||||
|
authPayload: req.authData.authPayload as IServiceTokenDataV3,
|
||||||
|
environment: env,
|
||||||
|
secretPath: secPath,
|
||||||
|
acceptedPermissions: [Permission.READ, Permission.READ_WRITE]
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const secrets = await SecretService.getSecrets({
|
const secrets = await SecretService.getSecrets({
|
||||||
@@ -486,21 +607,36 @@ export const getSecretByName = async (req: Request, res: Response) => {
|
|||||||
query: { secretPath, environment, workspaceId, type },
|
query: { secretPath, environment, workspaceId, type },
|
||||||
params: { secretName }
|
params: { secretName }
|
||||||
} = await validateRequest(reqValidator.GetSecretByNameV3, req);
|
} = await validateRequest(reqValidator.GetSecretByNameV3, req);
|
||||||
|
|
||||||
if (req.user?._id) {
|
switch (req.authData.actor.type) {
|
||||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
case ActorType.USER: {
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||||
ProjectPermissionActions.Read,
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
ProjectPermissionActions.Read,
|
||||||
);
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
||||||
} else {
|
);
|
||||||
await validateServiceTokenDataClientForWorkspace({
|
break;
|
||||||
serviceTokenData: req.authData.authPayload as IServiceTokenData,
|
}
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
case ActorType.SERVICE: {
|
||||||
environment,
|
await validateServiceTokenDataClientForWorkspace({
|
||||||
secretPath,
|
serviceTokenData: req.authData.authPayload as IServiceTokenData,
|
||||||
requiredPermissions: [PERMISSION_READ_SECRETS]
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
});
|
environment,
|
||||||
|
secretPath,
|
||||||
|
requiredPermissions: [PERMISSION_READ_SECRETS]
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ActorType.SERVICE_V3: {
|
||||||
|
await validateServiceTokenDataV3ClientForWorkspace({
|
||||||
|
serviceTokenData: req.authData.authPayload as IServiceTokenDataV3,
|
||||||
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
acceptedPermissions: [Permission.READ]
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const secret = await SecretService.getSecret({
|
const secret = await SecretService.getSecret({
|
||||||
@@ -543,20 +679,35 @@ export const createSecret = async (req: Request, res: Response) => {
|
|||||||
params: { secretName }
|
params: { secretName }
|
||||||
} = await validateRequest(reqValidator.CreateSecretV3, req);
|
} = await validateRequest(reqValidator.CreateSecretV3, req);
|
||||||
|
|
||||||
if (req.user?._id) {
|
switch (req.authData.actor.type) {
|
||||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
case ActorType.USER: {
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||||
ProjectPermissionActions.Create,
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
ProjectPermissionActions.Create,
|
||||||
);
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
||||||
} else {
|
);
|
||||||
await validateServiceTokenDataClientForWorkspace({
|
break;
|
||||||
serviceTokenData: req.authData.authPayload as IServiceTokenData,
|
}
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
case ActorType.SERVICE: {
|
||||||
environment,
|
await validateServiceTokenDataClientForWorkspace({
|
||||||
secretPath,
|
serviceTokenData: req.authData.authPayload as IServiceTokenData,
|
||||||
requiredPermissions: [PERMISSION_WRITE_SECRETS]
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
});
|
environment,
|
||||||
|
secretPath,
|
||||||
|
requiredPermissions: [PERMISSION_WRITE_SECRETS]
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ActorType.SERVICE_V3: {
|
||||||
|
await validateServiceTokenDataV3ClientForWorkspace({
|
||||||
|
serviceTokenData: req.authData.authPayload as IServiceTokenDataV3,
|
||||||
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
acceptedPermissions: [Permission.READ_WRITE]
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const secret = await SecretService.createSecret({
|
const secret = await SecretService.createSecret({
|
||||||
@@ -613,22 +764,37 @@ export const updateSecretByName = async (req: Request, res: Response) => {
|
|||||||
params: { secretName }
|
params: { secretName }
|
||||||
} = await validateRequest(reqValidator.UpdateSecretByNameV3, req);
|
} = await validateRequest(reqValidator.UpdateSecretByNameV3, req);
|
||||||
|
|
||||||
if (req.user?._id) {
|
switch (req.authData.actor.type) {
|
||||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
case ActorType.USER: {
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||||
ProjectPermissionActions.Edit,
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
ProjectPermissionActions.Edit,
|
||||||
);
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
||||||
} else {
|
);
|
||||||
await validateServiceTokenDataClientForWorkspace({
|
break;
|
||||||
serviceTokenData: req.authData.authPayload as IServiceTokenData,
|
}
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
case ActorType.SERVICE: {
|
||||||
environment,
|
await validateServiceTokenDataClientForWorkspace({
|
||||||
secretPath,
|
serviceTokenData: req.authData.authPayload as IServiceTokenData,
|
||||||
requiredPermissions: [PERMISSION_WRITE_SECRETS]
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
});
|
environment,
|
||||||
|
secretPath,
|
||||||
|
requiredPermissions: [PERMISSION_WRITE_SECRETS]
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ActorType.SERVICE_V3: {
|
||||||
|
await validateServiceTokenDataV3ClientForWorkspace({
|
||||||
|
serviceTokenData: req.authData.authPayload as IServiceTokenDataV3,
|
||||||
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
acceptedPermissions: [Permission.READ_WRITE]
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const secret = await SecretService.updateSecret({
|
const secret = await SecretService.updateSecret({
|
||||||
secretName,
|
secretName,
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
@@ -665,20 +831,35 @@ export const deleteSecretByName = async (req: Request, res: Response) => {
|
|||||||
params: { secretName }
|
params: { secretName }
|
||||||
} = await validateRequest(reqValidator.DeleteSecretByNameV3, req);
|
} = await validateRequest(reqValidator.DeleteSecretByNameV3, req);
|
||||||
|
|
||||||
if (req.user?._id) {
|
switch (req.authData.actor.type) {
|
||||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
case ActorType.USER: {
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||||
ProjectPermissionActions.Delete,
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
ProjectPermissionActions.Delete,
|
||||||
);
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
||||||
} else {
|
);
|
||||||
await validateServiceTokenDataClientForWorkspace({
|
break;
|
||||||
serviceTokenData: req.authData.authPayload as IServiceTokenData,
|
}
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
case ActorType.SERVICE: {
|
||||||
environment,
|
await validateServiceTokenDataClientForWorkspace({
|
||||||
secretPath,
|
serviceTokenData: req.authData.authPayload as IServiceTokenData,
|
||||||
requiredPermissions: [PERMISSION_WRITE_SECRETS]
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
});
|
environment,
|
||||||
|
secretPath,
|
||||||
|
requiredPermissions: [PERMISSION_WRITE_SECRETS]
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ActorType.SERVICE_V3: {
|
||||||
|
await validateServiceTokenDataV3ClientForWorkspace({
|
||||||
|
serviceTokenData: req.authData.authPayload as IServiceTokenDataV3,
|
||||||
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
acceptedPermissions: [Permission.READ_WRITE]
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const { secret } = await SecretService.deleteSecret({
|
const { secret } = await SecretService.deleteSecret({
|
||||||
|
|||||||
@@ -8,10 +8,11 @@ import {
|
|||||||
Scope
|
Scope
|
||||||
} from "../../models/serviceTokenDataV3";
|
} from "../../models/serviceTokenDataV3";
|
||||||
import {
|
import {
|
||||||
|
ActorType,
|
||||||
EventType
|
EventType
|
||||||
} from "../../ee/models";
|
} from "../../ee/models";
|
||||||
import { validateRequest } from "../../helpers/validation";
|
import { validateRequest } from "../../helpers/validation";
|
||||||
import * as reqValidator from "../../validation/serviceTokenV3";
|
import * as reqValidator from "../../validation/serviceTokenDataV3";
|
||||||
import { createToken } from "../../helpers/auth";
|
import { createToken } from "../../helpers/auth";
|
||||||
import {
|
import {
|
||||||
ProjectPermissionActions,
|
ProjectPermissionActions,
|
||||||
@@ -52,9 +53,15 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
|
|||||||
expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn);
|
expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let user;
|
||||||
|
if (req.authData.actor.type === ActorType.USER) {
|
||||||
|
user = req.authData.authPayload._id;
|
||||||
|
}
|
||||||
|
|
||||||
const isActive = false;
|
const isActive = false;
|
||||||
const serviceTokenData = await new ServiceTokenDataV3({
|
const serviceTokenData = await new ServiceTokenDataV3({
|
||||||
name,
|
name,
|
||||||
|
user,
|
||||||
workspace: new Types.ObjectId(workspaceId),
|
workspace: new Types.ObjectId(workspaceId),
|
||||||
publicKey,
|
publicKey,
|
||||||
scopes,
|
scopes,
|
||||||
@@ -161,7 +168,7 @@ export const updateServiceTokenData = async (req: Request, res: Response) => {
|
|||||||
{
|
{
|
||||||
type: EventType.UPDATE_SERVICE_TOKEN_V3,
|
type: EventType.UPDATE_SERVICE_TOKEN_V3,
|
||||||
metadata: {
|
metadata: {
|
||||||
name,
|
name: serviceTokenData.name,
|
||||||
isActive,
|
isActive,
|
||||||
scopes: scopes as Array<Scope>,
|
scopes: scopes as Array<Scope>,
|
||||||
expiresAt
|
expiresAt
|
||||||
|
|||||||
+24
-10
@@ -70,6 +70,7 @@ export const validateAuthMode = ({
|
|||||||
if (typeof authHeader === "string") {
|
if (typeof authHeader === "string") {
|
||||||
// case: treat request authentication type as via Authorization header (i.e. either JWT or service token)
|
// case: treat request authentication type as via Authorization header (i.e. either JWT or service token)
|
||||||
const [tokenType, tokenValue] = <[string, string]>authHeader.split(" ", 2) ?? [null, null]
|
const [tokenType, tokenValue] = <[string, string]>authHeader.split(" ", 2) ?? [null, null]
|
||||||
|
|
||||||
if (tokenType === null)
|
if (tokenType === null)
|
||||||
throw BadRequestError({ message: "Missing Authorization Header in the request header." });
|
throw BadRequestError({ message: "Missing Authorization Header in the request header." });
|
||||||
if (tokenType.toLowerCase() !== "bearer")
|
if (tokenType.toLowerCase() !== "bearer")
|
||||||
@@ -77,18 +78,21 @@ export const validateAuthMode = ({
|
|||||||
if (tokenValue === null)
|
if (tokenValue === null)
|
||||||
throw BadRequestError({ message: "Missing Authorization Body in the request header." });
|
throw BadRequestError({ message: "Missing Authorization Body in the request header." });
|
||||||
|
|
||||||
switch (tokenValue.split(".", 1)[0]) {
|
const parts = tokenValue.split(".");
|
||||||
|
|
||||||
|
switch (parts[0]) {
|
||||||
case "st":
|
case "st":
|
||||||
authMode = AuthMode.SERVICE_TOKEN;
|
authMode = AuthMode.SERVICE_TOKEN;
|
||||||
|
authTokenValue = tokenValue;
|
||||||
break;
|
break;
|
||||||
case "proj_token":
|
case "proj_token":
|
||||||
authMode = AuthMode.SERVICE_TOKEN_V3;
|
authMode = AuthMode.SERVICE_TOKEN_V3;
|
||||||
|
authTokenValue = parts.slice(1).join('.');
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
authMode = AuthMode.JWT;
|
authMode = AuthMode.JWT;
|
||||||
|
authTokenValue = tokenValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
authTokenValue = tokenValue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!authMode || !authTokenValue) throw BadRequestError({ message: "Missing valid Authorization or X-API-KEY in request header." });
|
if (!authMode || !authTokenValue) throw BadRequestError({ message: "Missing valid Authorization or X-API-KEY in request header." });
|
||||||
@@ -235,29 +239,39 @@ export const getAuthSTDPayload = async ({
|
|||||||
authTokenValue: string;
|
authTokenValue: string;
|
||||||
}): Promise<ServiceTokenV3AuthData> => {
|
}): Promise<ServiceTokenV3AuthData> => {
|
||||||
const decodedToken = <jwt.UserIDJwtPayload>(
|
const decodedToken = <jwt.UserIDJwtPayload>(
|
||||||
jwt.verify(authTokenValue, "hello") // TODO: change this
|
jwt.verify(authTokenValue, "hello") // TODO: replace with real secret
|
||||||
);
|
);
|
||||||
|
|
||||||
// perhaps turn this one into a find one and update call?
|
|
||||||
const serviceTokenData = await ServiceTokenDataV3.findOne({
|
const serviceTokenData = await ServiceTokenDataV3.findOne({
|
||||||
_id: new Types.ObjectId(decodedToken.serviceTokenDataId),
|
_id: new Types.ObjectId(decodedToken._id),
|
||||||
|
isActive: true
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!serviceTokenData) {
|
if (!serviceTokenData) {
|
||||||
throw UnauthorizedRequestError({
|
throw UnauthorizedRequestError({
|
||||||
message: "Failed to authenticate" // standardize auth error messages
|
message: "Failed to authenticate" // standardize auth error messages
|
||||||
});
|
});
|
||||||
} else if (serviceTokenData?.expiresAt && new Date(serviceTokenData.expiresAt) < new Date()) {
|
} else if (serviceTokenData?.expiresAt && new Date(serviceTokenData.expiresAt) < new Date()) {
|
||||||
// case: service token expired
|
// case: service token expired
|
||||||
await ServiceTokenDataV3.findByIdAndDelete(serviceTokenData._id);
|
// TODO: test expired token
|
||||||
|
await ServiceTokenDataV3.findByIdAndUpdate(
|
||||||
|
serviceTokenData._id,
|
||||||
|
{
|
||||||
|
isActive: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
new: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
throw UnauthorizedRequestError({
|
throw UnauthorizedRequestError({
|
||||||
message: "Failed to authenticate",
|
message: "Failed to authenticate",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
actor: {
|
actor: {
|
||||||
type: ActorType.SERVICE_V3, // should this be servicev3 bc the shape of it is different?
|
type: ActorType.SERVICE_V3,
|
||||||
metadata: {
|
metadata: {
|
||||||
serviceId: serviceTokenData._id.toString(),
|
serviceId: serviceTokenData._id.toString(),
|
||||||
name: serviceTokenData.name
|
name: serviceTokenData.name
|
||||||
|
|||||||
@@ -10,11 +10,16 @@ import {
|
|||||||
Folder,
|
Folder,
|
||||||
ISecret,
|
ISecret,
|
||||||
IServiceTokenData,
|
IServiceTokenData,
|
||||||
|
IServiceTokenDataV3,
|
||||||
Secret,
|
Secret,
|
||||||
SecretBlindIndexData,
|
SecretBlindIndexData,
|
||||||
ServiceTokenData,
|
ServiceTokenData,
|
||||||
TFolderRootSchema
|
TFolderRootSchema
|
||||||
} from "../models";
|
} from "../models";
|
||||||
|
import {
|
||||||
|
Scope,
|
||||||
|
Permission
|
||||||
|
} from "../models/serviceTokenDataV3";
|
||||||
import { EventType, SecretVersion } from "../ee/models";
|
import { EventType, SecretVersion } from "../ee/models";
|
||||||
import {
|
import {
|
||||||
BadRequestError,
|
BadRequestError,
|
||||||
@@ -49,10 +54,50 @@ import { getFolderByPath, getFolderIdFromServiceToken } from "../services/Folder
|
|||||||
import picomatch from "picomatch";
|
import picomatch from "picomatch";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate scope for service token v3
|
||||||
|
* @param authPayload
|
||||||
|
* @param environment
|
||||||
|
* @param secretPath
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const isValidScopeV3 = ({
|
||||||
|
authPayload,
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
acceptedPermissions
|
||||||
|
}: {
|
||||||
|
authPayload: IServiceTokenDataV3,
|
||||||
|
environment: string,
|
||||||
|
secretPath: string,
|
||||||
|
acceptedPermissions: Permission[]
|
||||||
|
}) => {
|
||||||
|
const { scopes } = authPayload;
|
||||||
|
|
||||||
|
const validScope = scopes.find(
|
||||||
|
(scope) =>
|
||||||
|
picomatch.isMatch(secretPath, scope.secretPath, { strictSlashes: false }) &&
|
||||||
|
scope.environment === environment
|
||||||
|
);
|
||||||
|
|
||||||
|
if (validScope && !acceptedPermissions.includes(validScope.permission)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Boolean(validScope);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate scope for service token v2
|
||||||
|
* @param authPayload
|
||||||
|
* @param environment
|
||||||
|
* @param secretPath
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
export const isValidScope = (
|
export const isValidScope = (
|
||||||
authPayload: IServiceTokenData,
|
authPayload: IServiceTokenData,
|
||||||
environment: string,
|
environment: string,
|
||||||
secretPath: string
|
secretPath: string,
|
||||||
) => {
|
) => {
|
||||||
const { scopes: tkScopes } = authPayload;
|
const { scopes: tkScopes } = authPayload;
|
||||||
const validScope = tkScopes.find(
|
const validScope = tkScopes.find(
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Document, Schema, Types, model } from "mongoose";
|
import { Document, Schema, Types, model } from "mongoose";
|
||||||
|
|
||||||
enum Permission {
|
export enum Permission {
|
||||||
READ = "read",
|
READ = "read",
|
||||||
READ_WRITE = "readWrite"
|
READ_WRITE = "readWrite"
|
||||||
}
|
}
|
||||||
@@ -15,6 +15,7 @@ export interface IServiceTokenDataV3 extends Document {
|
|||||||
_id: Types.ObjectId;
|
_id: Types.ObjectId;
|
||||||
name: string;
|
name: string;
|
||||||
workspace: Types.ObjectId;
|
workspace: Types.ObjectId;
|
||||||
|
user: Types.ObjectId;
|
||||||
publicKey: string;
|
publicKey: string;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
lastUsed?: Date;
|
lastUsed?: Date;
|
||||||
@@ -33,6 +34,11 @@ const serviceTokenDataV3Schema = new Schema(
|
|||||||
ref: "Workspace",
|
ref: "Workspace",
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
user: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: "User",
|
||||||
|
required: true
|
||||||
|
},
|
||||||
publicKey: {
|
publicKey: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
router.get(
|
router.get(
|
||||||
"/raw",
|
"/raw",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_TOKEN_V3]
|
||||||
}),
|
}),
|
||||||
secretsController.getSecretsRaw
|
secretsController.getSecretsRaw
|
||||||
);
|
);
|
||||||
@@ -21,7 +21,7 @@ router.get(
|
|||||||
router.get(
|
router.get(
|
||||||
"/raw/:secretName",
|
"/raw/:secretName",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_TOKEN_V3]
|
||||||
}),
|
}),
|
||||||
requireBlindIndicesEnabled({
|
requireBlindIndicesEnabled({
|
||||||
locationWorkspaceId: "query"
|
locationWorkspaceId: "query"
|
||||||
@@ -35,7 +35,7 @@ router.get(
|
|||||||
router.post(
|
router.post(
|
||||||
"/raw/:secretName",
|
"/raw/:secretName",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_TOKEN_V3]
|
||||||
}),
|
}),
|
||||||
requireBlindIndicesEnabled({
|
requireBlindIndicesEnabled({
|
||||||
locationWorkspaceId: "body"
|
locationWorkspaceId: "body"
|
||||||
@@ -49,7 +49,7 @@ router.post(
|
|||||||
router.patch(
|
router.patch(
|
||||||
"/raw/:secretName",
|
"/raw/:secretName",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_TOKEN_V3]
|
||||||
}),
|
}),
|
||||||
requireBlindIndicesEnabled({
|
requireBlindIndicesEnabled({
|
||||||
locationWorkspaceId: "body"
|
locationWorkspaceId: "body"
|
||||||
@@ -63,7 +63,7 @@ router.patch(
|
|||||||
router.delete(
|
router.delete(
|
||||||
"/raw/:secretName",
|
"/raw/:secretName",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_TOKEN_V3]
|
||||||
}),
|
}),
|
||||||
requireBlindIndicesEnabled({
|
requireBlindIndicesEnabled({
|
||||||
locationWorkspaceId: "body"
|
locationWorkspaceId: "body"
|
||||||
@@ -77,7 +77,7 @@ router.delete(
|
|||||||
router.get(
|
router.get(
|
||||||
"/",
|
"/",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_TOKEN_V3]
|
||||||
}),
|
}),
|
||||||
requireBlindIndicesEnabled({
|
requireBlindIndicesEnabled({
|
||||||
locationWorkspaceId: "query"
|
locationWorkspaceId: "query"
|
||||||
@@ -88,7 +88,7 @@ router.get(
|
|||||||
router.post(
|
router.post(
|
||||||
"/:secretName",
|
"/:secretName",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_TOKEN_V3]
|
||||||
}),
|
}),
|
||||||
requireBlindIndicesEnabled({
|
requireBlindIndicesEnabled({
|
||||||
locationWorkspaceId: "body"
|
locationWorkspaceId: "body"
|
||||||
@@ -99,7 +99,7 @@ router.post(
|
|||||||
router.get(
|
router.get(
|
||||||
"/:secretName",
|
"/:secretName",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_TOKEN_V3]
|
||||||
}),
|
}),
|
||||||
requireBlindIndicesEnabled({
|
requireBlindIndicesEnabled({
|
||||||
locationWorkspaceId: "query"
|
locationWorkspaceId: "query"
|
||||||
@@ -110,7 +110,7 @@ router.get(
|
|||||||
router.patch(
|
router.patch(
|
||||||
"/:secretName",
|
"/:secretName",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_TOKEN_V3]
|
||||||
}),
|
}),
|
||||||
requireBlindIndicesEnabled({
|
requireBlindIndicesEnabled({
|
||||||
locationWorkspaceId: "body"
|
locationWorkspaceId: "body"
|
||||||
@@ -121,7 +121,7 @@ router.patch(
|
|||||||
router.delete(
|
router.delete(
|
||||||
"/:secretName",
|
"/:secretName",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_TOKEN_V3]
|
||||||
}),
|
}),
|
||||||
requireBlindIndicesEnabled({
|
requireBlindIndicesEnabled({
|
||||||
locationWorkspaceId: "body"
|
locationWorkspaceId: "body"
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
Organization,
|
Organization,
|
||||||
ServiceAccount,
|
ServiceAccount,
|
||||||
ServiceTokenData,
|
ServiceTokenData,
|
||||||
|
ServiceTokenDataV3,
|
||||||
User
|
User
|
||||||
} from "../models";
|
} from "../models";
|
||||||
import { createToken } from "../helpers/auth";
|
import { createToken } from "../helpers/auth";
|
||||||
@@ -49,6 +50,10 @@ const getAuthDataPayloadIdObj = (authData: AuthData) => {
|
|||||||
if (authData.authPayload instanceof ServiceTokenData) {
|
if (authData.authPayload instanceof ServiceTokenData) {
|
||||||
return { serviceTokenDataId: authData.authPayload._id };
|
return { serviceTokenDataId: authData.authPayload._id };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (authData.authPayload instanceof ServiceTokenDataV3) {
|
||||||
|
return { serviceTokenDataId: authData.authPayload._id };
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -57,7 +62,6 @@ const getAuthDataPayloadIdObj = (authData: AuthData) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const getAuthDataPayloadUserObj = (authData: AuthData) => {
|
const getAuthDataPayloadUserObj = (authData: AuthData) => {
|
||||||
|
|
||||||
if (authData.authPayload instanceof User) {
|
if (authData.authPayload instanceof User) {
|
||||||
return { user: authData.authPayload._id };
|
return { user: authData.authPayload._id };
|
||||||
}
|
}
|
||||||
@@ -67,7 +71,11 @@ const getAuthDataPayloadUserObj = (authData: AuthData) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (authData.authPayload instanceof ServiceTokenData) {
|
if (authData.authPayload instanceof ServiceTokenData) {
|
||||||
return { user: authData.authPayload.user };0
|
return { user: authData.authPayload.user };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authPayload instanceof ServiceTokenDataV3) {
|
||||||
|
return { user: authData.authPayload.user };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,4 +9,4 @@ export * from "./organization";
|
|||||||
export * from "./secrets";
|
export * from "./secrets";
|
||||||
export * from "./serviceAccount";
|
export * from "./serviceAccount";
|
||||||
export * from "./serviceTokenData";
|
export * from "./serviceTokenData";
|
||||||
export * from "./serviceTokenV3";
|
export * from "./serviceTokenDataV3";
|
||||||
|
|||||||
@@ -254,7 +254,7 @@ export const CreateSecretRawV3 = z.object({
|
|||||||
environment: z.string().trim(),
|
environment: z.string().trim(),
|
||||||
secretPath: z.string().trim().default("/"),
|
secretPath: z.string().trim().default("/"),
|
||||||
secretValue: z.string().trim(),
|
secretValue: z.string().trim(),
|
||||||
secretComment: z.string().trim(),
|
secretComment: z.string().trim().optional().default(""),
|
||||||
type: z.enum([SECRET_SHARED, SECRET_PERSONAL])
|
type: z.enum([SECRET_SHARED, SECRET_PERSONAL])
|
||||||
}),
|
}),
|
||||||
params: z.object({
|
params: z.object({
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
import { Types } from "mongoose";
|
||||||
|
import { IServiceTokenDataV3 } from "../models";
|
||||||
|
import { Permission } from "../models/serviceTokenDataV3";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { UnauthorizedRequestError } from "../utils/errors";
|
||||||
|
import { isValidScopeV3 } from "../helpers";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate that service token (client) can access workspace
|
||||||
|
* with id [workspaceId] and its environment [environment] with required permissions
|
||||||
|
* [requiredPermissions]
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {ServiceTokenData} obj.serviceTokenData - service token client
|
||||||
|
* @param {Types.ObjectId} obj.workspaceId - id of workspace to validate against
|
||||||
|
* @param {String} environment - (optional) environment in workspace to validate against
|
||||||
|
* @param {String[]} acceptedPermissions - accepted permissions as part of the endpoint
|
||||||
|
*/
|
||||||
|
export const validateServiceTokenDataV3ClientForWorkspace = async ({
|
||||||
|
serviceTokenData,
|
||||||
|
workspaceId,
|
||||||
|
environment,
|
||||||
|
secretPath = "/",
|
||||||
|
acceptedPermissions
|
||||||
|
}: {
|
||||||
|
serviceTokenData: IServiceTokenDataV3;
|
||||||
|
workspaceId: Types.ObjectId;
|
||||||
|
environment?: string;
|
||||||
|
secretPath?: string;
|
||||||
|
acceptedPermissions: Permission[];
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
if (!serviceTokenData.workspace.equals(workspaceId)) {
|
||||||
|
// case: invalid workspaceId passed
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: "Failed service token authorization for the given workspace"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (environment) {
|
||||||
|
const isValid = isValidScopeV3({
|
||||||
|
authPayload: serviceTokenData,
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
acceptedPermissions
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!isValid) throw UnauthorizedRequestError({
|
||||||
|
message: "Failed service token authorization for the given workspace"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CreateServiceTokenV3 = z.object({
|
||||||
|
body: z.object({
|
||||||
|
name: z.string().trim(),
|
||||||
|
workspaceId: z.string().trim(),
|
||||||
|
publicKey: z.string().trim(),
|
||||||
|
scopes: z
|
||||||
|
.object({
|
||||||
|
permission: z.enum(["read", "readWrite"]),
|
||||||
|
environment: z.string().trim(),
|
||||||
|
secretPath: z.string().trim()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
.min(1),
|
||||||
|
expiresIn: z.number().optional(),
|
||||||
|
encryptedKey: z.string().trim(),
|
||||||
|
nonce: z.string().trim()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
export const UpdateServiceTokenV3 = z.object({
|
||||||
|
params: z.object({
|
||||||
|
serviceTokenDataId: z.string()
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
name: z.string().trim().optional(),
|
||||||
|
isActive: z.boolean().optional(),
|
||||||
|
scopes: z
|
||||||
|
.object({
|
||||||
|
permission: z.enum(["read", "readWrite"]),
|
||||||
|
environment: z.string().trim(),
|
||||||
|
secretPath: z.string().trim()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
.min(1)
|
||||||
|
.optional(),
|
||||||
|
expiresIn: z.number().optional()
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const DeleteServiceTokenV3 = z.object({
|
||||||
|
params: z.object({
|
||||||
|
serviceTokenDataId: z.string()
|
||||||
|
}),
|
||||||
|
});
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
export const CreateServiceTokenV3 = z.object({
|
|
||||||
body: z.object({
|
|
||||||
name: z.string().trim(),
|
|
||||||
workspaceId: z.string().trim(),
|
|
||||||
publicKey: z.string().trim(),
|
|
||||||
scopes: z
|
|
||||||
.object({
|
|
||||||
permission: z.enum(["read", "readWrite"]),
|
|
||||||
environment: z.string().trim(),
|
|
||||||
secretPath: z.string().trim()
|
|
||||||
})
|
|
||||||
.array()
|
|
||||||
.min(1),
|
|
||||||
expiresIn: z.number().optional(),
|
|
||||||
encryptedKey: z.string().trim(),
|
|
||||||
nonce: z.string().trim()
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
export const UpdateServiceTokenV3 = z.object({
|
|
||||||
params: z.object({
|
|
||||||
serviceTokenDataId: z.string()
|
|
||||||
}),
|
|
||||||
body: z.object({
|
|
||||||
name: z.string().trim().optional(),
|
|
||||||
isActive: z.boolean().optional(),
|
|
||||||
scopes: z
|
|
||||||
.object({
|
|
||||||
permission: z.enum(["read", "readWrite"]),
|
|
||||||
environment: z.string().trim(),
|
|
||||||
secretPath: z.string().trim()
|
|
||||||
})
|
|
||||||
.array()
|
|
||||||
.min(1)
|
|
||||||
.optional(),
|
|
||||||
expiresIn: z.number().optional()
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const DeleteServiceTokenV3 = z.object({
|
|
||||||
params: z.object({
|
|
||||||
serviceTokenDataId: z.string()
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
@@ -57,7 +57,7 @@ export const validateClientForWorkspace = async ({
|
|||||||
environment,
|
environment,
|
||||||
requiredPermissions
|
requiredPermissions
|
||||||
});
|
});
|
||||||
break;
|
return { membership, workspace};
|
||||||
case ActorType.SERVICE_V3:
|
case ActorType.SERVICE_V3:
|
||||||
throw UnauthorizedRequestError({
|
throw UnauthorizedRequestError({
|
||||||
message: "Failed service token authorization for organization"
|
message: "Failed service token authorization for organization"
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ export const eventToNameMap: { [K in EventType]: string } = {
|
|||||||
[EventType.DELETE_TRUSTED_IP]: "Delete trusted IP",
|
[EventType.DELETE_TRUSTED_IP]: "Delete trusted IP",
|
||||||
[EventType.CREATE_SERVICE_TOKEN]: "Create service token",
|
[EventType.CREATE_SERVICE_TOKEN]: "Create service token",
|
||||||
[EventType.DELETE_SERVICE_TOKEN]: "Delete service token",
|
[EventType.DELETE_SERVICE_TOKEN]: "Delete service token",
|
||||||
|
[EventType.CREATE_SERVICE_TOKEN_V3]: "Create (new) service token",
|
||||||
|
[EventType.UPDATE_SERVICE_TOKEN_V3]: "Update (new) service token",
|
||||||
|
[EventType.DELETE_SERVICE_TOKEN_V3]: "Delete (new) service token",
|
||||||
[EventType.CREATE_ENVIRONMENT]: "Create environment",
|
[EventType.CREATE_ENVIRONMENT]: "Create environment",
|
||||||
[EventType.UPDATE_ENVIRONMENT]: "Update environment",
|
[EventType.UPDATE_ENVIRONMENT]: "Update environment",
|
||||||
[EventType.DELETE_ENVIRONMENT]: "Delete environment",
|
[EventType.DELETE_ENVIRONMENT]: "Delete environment",
|
||||||
|
|||||||
@@ -24,8 +24,11 @@ export enum EventType {
|
|||||||
ADD_TRUSTED_IP = "add-trusted-ip",
|
ADD_TRUSTED_IP = "add-trusted-ip",
|
||||||
UPDATE_TRUSTED_IP = "update-trusted-ip",
|
UPDATE_TRUSTED_IP = "update-trusted-ip",
|
||||||
DELETE_TRUSTED_IP = "delete-trusted-ip",
|
DELETE_TRUSTED_IP = "delete-trusted-ip",
|
||||||
CREATE_SERVICE_TOKEN = "create-service-token",
|
CREATE_SERVICE_TOKEN = "create-service-token", // v2
|
||||||
DELETE_SERVICE_TOKEN = "delete-service-token",
|
DELETE_SERVICE_TOKEN = "delete-service-token", // v2
|
||||||
|
CREATE_SERVICE_TOKEN_V3 = "create-service-token-v3", // v3
|
||||||
|
UPDATE_SERVICE_TOKEN_V3 = "update-service-token-v3", // v3
|
||||||
|
DELETE_SERVICE_TOKEN_V3 = "delete-service-token-v3", // v3
|
||||||
CREATE_ENVIRONMENT = "create-environment",
|
CREATE_ENVIRONMENT = "create-environment",
|
||||||
UPDATE_ENVIRONMENT = "update-environment",
|
UPDATE_ENVIRONMENT = "update-environment",
|
||||||
DELETE_ENVIRONMENT = "delete-environment",
|
DELETE_ENVIRONMENT = "delete-environment",
|
||||||
|
|||||||
@@ -4,6 +4,17 @@ import {
|
|||||||
UserAgentType
|
UserAgentType
|
||||||
} from "./enums";
|
} from "./enums";
|
||||||
|
|
||||||
|
enum Permission {
|
||||||
|
READ = "read",
|
||||||
|
READ_WRITE = "readWrite"
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Scope {
|
||||||
|
environment: string;
|
||||||
|
secretPath: string;
|
||||||
|
permission: Permission;
|
||||||
|
}
|
||||||
|
|
||||||
interface UserActorMetadata {
|
interface UserActorMetadata {
|
||||||
userId: string;
|
userId: string;
|
||||||
email: string;
|
email: string;
|
||||||
@@ -14,7 +25,6 @@ interface ServiceActorMetadata {
|
|||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
interface UserActor {
|
interface UserActor {
|
||||||
type: ActorType.USER;
|
type: ActorType.USER;
|
||||||
metadata: UserActorMetadata;
|
metadata: UserActorMetadata;
|
||||||
@@ -190,6 +200,36 @@ interface DeleteServiceTokenEvent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface CreateServiceTokenV3Event {
|
||||||
|
type: EventType.CREATE_SERVICE_TOKEN_V3;
|
||||||
|
metadata: {
|
||||||
|
name: string;
|
||||||
|
isActive: boolean;
|
||||||
|
scopes: Array<Scope>;
|
||||||
|
expiresAt?: Date;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdateServiceTokenV3Event {
|
||||||
|
type: EventType.UPDATE_SERVICE_TOKEN_V3;
|
||||||
|
metadata: {
|
||||||
|
name?: string;
|
||||||
|
isActive?: boolean;
|
||||||
|
scopes?: Array<Scope>;
|
||||||
|
expiresAt?: Date;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DeleteServiceTokenV3Event {
|
||||||
|
type: EventType.DELETE_SERVICE_TOKEN_V3;
|
||||||
|
metadata: {
|
||||||
|
name: string;
|
||||||
|
isActive: boolean;
|
||||||
|
scopes: Array<Scope>;
|
||||||
|
expiresAt?: Date;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
interface CreateEnvironmentEvent {
|
interface CreateEnvironmentEvent {
|
||||||
type: EventType.CREATE_ENVIRONMENT;
|
type: EventType.CREATE_ENVIRONMENT;
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -387,6 +427,9 @@ export type Event =
|
|||||||
| DeleteTrustedIPEvent
|
| DeleteTrustedIPEvent
|
||||||
| CreateServiceTokenEvent
|
| CreateServiceTokenEvent
|
||||||
| DeleteServiceTokenEvent
|
| DeleteServiceTokenEvent
|
||||||
|
| CreateServiceTokenV3Event
|
||||||
|
| UpdateServiceTokenV3Event
|
||||||
|
| DeleteServiceTokenV3Event
|
||||||
| CreateEnvironmentEvent
|
| CreateEnvironmentEvent
|
||||||
| UpdateEnvironmentEvent
|
| UpdateEnvironmentEvent
|
||||||
| DeleteEnvironmentEvent
|
| DeleteEnvironmentEvent
|
||||||
|
|||||||
@@ -93,6 +93,4 @@ export const LogsTable = ({
|
|||||||
)}
|
)}
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: retrieve count
|
|
||||||
@@ -160,6 +160,24 @@ export const LogsTableRow = ({
|
|||||||
<p>{`Name: ${event.metadata.name}`}</p>
|
<p>{`Name: ${event.metadata.name}`}</p>
|
||||||
</Td>
|
</Td>
|
||||||
);
|
);
|
||||||
|
case EventType.CREATE_SERVICE_TOKEN_V3:
|
||||||
|
return (
|
||||||
|
<Td>
|
||||||
|
<p>{`Name: ${event.metadata.name}`}</p>
|
||||||
|
</Td>
|
||||||
|
);
|
||||||
|
case EventType.UPDATE_SERVICE_TOKEN_V3:
|
||||||
|
return (
|
||||||
|
<Td>
|
||||||
|
<p>{`Name: ${event.metadata.name}`}</p>
|
||||||
|
</Td>
|
||||||
|
);
|
||||||
|
case EventType.DELETE_SERVICE_TOKEN_V3:
|
||||||
|
return (
|
||||||
|
<Td>
|
||||||
|
<p>{`Name: ${event.metadata.name}`}</p>
|
||||||
|
</Td>
|
||||||
|
);
|
||||||
case EventType.CREATE_ENVIRONMENT:
|
case EventType.CREATE_ENVIRONMENT:
|
||||||
return (
|
return (
|
||||||
<Td>
|
<Td>
|
||||||
|
|||||||
+1
-1
@@ -420,7 +420,7 @@ export const AddServiceTokenV3Modal = ({
|
|||||||
isLoading={isSubmitting}
|
isLoading={isSubmitting}
|
||||||
isDisabled={isSubmitting}
|
isDisabled={isSubmitting}
|
||||||
>
|
>
|
||||||
Create
|
{popUp?.serviceTokenV3?.data ? "Update" : "Create"}
|
||||||
</Button>
|
</Button>
|
||||||
<Button colorSchema="secondary" variant="plain">
|
<Button colorSchema="secondary" variant="plain">
|
||||||
Cancel
|
Cancel
|
||||||
|
|||||||
+1
-1
@@ -64,7 +64,7 @@ export const ServiceTokenV3Section = withProjectPermission(
|
|||||||
onClick={() => handlePopUpOpen("serviceTokenV3")}
|
onClick={() => handlePopUpOpen("serviceTokenV3")}
|
||||||
isDisabled={!isAllowed}
|
isDisabled={!isAllowed}
|
||||||
>
|
>
|
||||||
Create ST V3
|
Create token
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</ProjectPermissionCan>
|
</ProjectPermissionCan>
|
||||||
|
|||||||
Reference in New Issue
Block a user