Files
infisical/backend/src/validation/secrets.ts

364 lines
9.9 KiB
TypeScript

import { Types } from "mongoose";
import { ISecret, IServiceTokenData, IUser, Secret } from "../models";
import { validateUserClientForSecret, validateUserClientForSecrets } from "./user";
import {
validateServiceTokenDataClientForSecrets,
validateServiceTokenDataClientForWorkspace
} from "./serviceTokenData";
import { BadRequestError, SecretNotFoundError } from "../utils/errors";
import { AuthData } from "../interfaces/middleware";
import { ActorType } from "../ee/models";
import { z } from "zod";
import { SECRET_PERSONAL, SECRET_SHARED } from "../variables";
/**
* Validate authenticated clients for secrets with id [secretId] based
* on any known permissions.
* @param {Object} obj
* @param {Object} obj.authData - authenticated client details
* @param {Types.ObjectId} obj.secretId - id of secret to validate against
* @param {Array<'admin' | 'member'>} obj.acceptedRoles - accepted workspace roles
* @param {String[]} obj.requiredPermissions - required permissions as part of the endpoint
*/
export const validateClientForSecret = async ({
authData,
secretId,
acceptedRoles,
requiredPermissions
}: {
authData: AuthData;
secretId: Types.ObjectId;
acceptedRoles: Array<"admin" | "member">;
requiredPermissions: string[];
}) => {
const secret = await Secret.findById(secretId);
if (!secret)
throw SecretNotFoundError({
message: "Failed to find secret"
});
switch (authData.actor.type) {
case ActorType.USER:
await validateUserClientForSecret({
user: authData.authPayload as IUser,
secret,
acceptedRoles,
requiredPermissions
});
return secret;
case ActorType.SERVICE:
await validateServiceTokenDataClientForWorkspace({
serviceTokenData: authData.authPayload as IServiceTokenData,
workspaceId: secret.workspace,
environment: secret.environment
});
return secret;
}
};
/**
* Validate authenticated clients for secrets with ids [secretIds] based
* on any known permissions.
* @param {Object} obj
* @param {Object} obj.authData - authenticated client details
* @param {Types.ObjectId[]} obj.secretIds - id of workspace to validate against
* @param {String} obj.environment - (optional) environment in workspace to validate against
* @param {Array<'admin' | 'member'>} obj.acceptedRoles - accepted workspace roles
* @param {String[]} obj.requiredPermissions - required permissions as part of the endpoint
*/
export const validateClientForSecrets = async ({
authData,
secretIds,
requiredPermissions
}: {
authData: AuthData;
secretIds: Types.ObjectId[];
requiredPermissions: string[];
}) => {
let secrets: ISecret[] = [];
secrets = await Secret.find({
_id: {
$in: secretIds
}
});
if (secrets.length != secretIds.length) {
throw BadRequestError({ message: "Failed to validate non-existent secrets" });
}
switch (authData.actor.type) {
case ActorType.USER:
await validateUserClientForSecrets({
user: authData.authPayload as IUser,
secrets,
requiredPermissions
});
return secrets;
case ActorType.SERVICE:
await validateServiceTokenDataClientForSecrets({
serviceTokenData: authData.authPayload as IServiceTokenData,
secrets,
requiredPermissions
});
return secrets;
}
};
export const GetSecretVersionsV1 = z.object({
params: z.object({
secretId: z.string().trim()
}),
query: z.object({
offset: z.coerce.number(),
limit: z.coerce.number()
})
});
export const RollbackSecretVersionV1 = z.object({
params: z.object({
secretId: z.string().trim()
}),
body: z.object({
version: z.number()
})
});
export const PushSecretsV1 = z.object({
params: z.object({
workspaceId: z.string().trim()
}),
body: z.object({
secrets: z.object({}).array(),
keys: z.object({}).array(),
environment: z.string().trim(),
channel: z.string().trim()
})
});
export const PullSecretsV1 = z.object({
params: z.object({
workspaceId: z.string().trim()
}),
query: z.object({
channel: z.string().optional(),
environment: z.string().trim()
})
});
export const PullSecretsServiceTokenV1 = z.object({
params: z.object({
workspaceId: z.string().trim()
}),
query: z.object({
channel: z.string().optional(),
environment: z.string().trim()
})
});
const batchUpdateRequestV2 = z.object({
_id: z.string(),
folderId: z.string().trim().optional(),
type: z.enum(["shared", "personal"]),
secretName: z.string().trim(),
secretKeyCiphertext: z.string().trim(),
secretKeyIV: z.string().trim(),
secretKeyTag: z.string().trim(),
secretValueCiphertext: z.string().trim(),
secretValueIV: z.string().trim(),
secretValueTag: z.string().trim(),
secretCommentCiphertext: z.string().trim().optional(),
secretCommentIV: z.string().trim().optional(),
secretCommentTag: z.string().trim().optional(),
tags: z
.object({
_id: z.string().trim(),
name: z.string().trim(),
slug: z.string().trim()
})
.array()
});
export const BatchSecretsV2 = z.object({
body: z.object({
workspaceId: z.string().trim(),
folderId: z.string().trim().default("root"),
environment: z.string().trim(),
secretPath: z.string().trim().default("/"),
requests: z
.discriminatedUnion("method", [
z.object({
method: z.literal("POST"),
secret: batchUpdateRequestV2.omit({ _id: true })
}),
z.object({
method: z.literal("PATCH"),
secret: batchUpdateRequestV2
}),
z.object({
method: z.literal("DELETE"),
secret: z.object({ _id: z.string().trim(), secretName: z.string().trim() })
})
])
.array()
})
});
export const GetSecretsV2 = z.object({
query: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
tagSlugs: z.string().trim().optional(),
folderId: z.string().trim().default("root"),
secretPath: z.string().trim().optional(),
include_imports: z
.enum(["true", "false"])
.default("false")
.transform((value) => value === "true")
})
});
export const GetSecretsRawV3 = z.object({
query: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
secretPath: z.string().trim().default("/"),
folderId: z.string().trim().optional(),
include_imports: z
.enum(["true", "false"])
.default("false")
.transform((value) => value === "true")
})
});
export const GetSecretByNameRawV3 = z.object({
params: z.object({
secretName: z.string().trim()
}),
query: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
secretPath: z.string().trim().default("/"),
type: z.enum([SECRET_SHARED, SECRET_PERSONAL]).optional()
})
});
export const CreateSecretRawV3 = z.object({
body: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
secretPath: z.string().trim().default("/"),
secretValue: z.string().trim(),
secretComment: z.string().trim(),
type: z.enum([SECRET_SHARED, SECRET_PERSONAL])
}),
params: z.object({
secretName: z.string().trim()
})
});
export const UpdateSecretByNameRawV3 = z.object({
params: z.object({
secretName: z.string().trim()
}),
body: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
secretValue: z.string().trim(),
secretName: z.string().trim(),
secretPath: z.string().trim().default("/"),
type: z.enum([SECRET_SHARED, SECRET_PERSONAL]).default(SECRET_SHARED)
})
});
export const DeleteSecretByNameRawV3 = z.object({
params: z.object({
secretName: z.string().trim()
}),
body: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
secretPath: z.string().trim().default("/"),
type: z.enum([SECRET_SHARED, SECRET_PERSONAL]).default(SECRET_SHARED)
})
});
export const GetSecretsV3 = z.object({
query: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
secretPath: z.string().trim().default("/"),
folderId: z.string().trim().optional(),
include_imports: z
.enum(["true", "false"])
.default("false")
.transform((value) => value === "true")
})
});
export const GetSecretByNameV3 = z.object({
query: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
secretPath: z.string().trim().default("/"),
type: z.enum([SECRET_SHARED, SECRET_PERSONAL]).optional()
}),
params: z.object({
secretName: z.string().trim()
})
});
export const CreateSecretV3 = z.object({
body: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
type: z.enum([SECRET_SHARED, SECRET_PERSONAL]),
secretPath: z.string().trim().default("/"),
secretKeyCiphertext: z.string().trim(),
secretKeyIV: z.string().trim(),
secretKeyTag: z.string().trim(),
secretValueCiphertext: z.string().trim(),
secretValueIV: z.string().trim(),
secretValueTag: z.string().trim(),
secretCommentCiphertext: z.string().trim().optional(),
secretCommentIV: z.string().trim().optional(),
secretCommentTag: z.string().trim().optional(),
metadata: z.record(z.string()).optional()
}),
params: z.object({
secretName: z.string().trim()
})
});
export const UpdateSecretByNameV3 = z.object({
body: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
type: z.enum([SECRET_SHARED, SECRET_PERSONAL]),
secretPath: z.string().trim().default("/"),
secretValueCiphertext: z.string().trim(),
secretValueIV: z.string().trim(),
secretValueTag: z.string().trim()
}),
params: z.object({
secretName: z.string()
})
});
export const DeleteSecretByNameV3 = z.object({
body: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
type: z.enum([SECRET_SHARED, SECRET_PERSONAL]),
secretPath: z.string().trim().default("/")
}),
params: z.object({
secretName: z.string()
})
});