Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cli/api): more descriptive api errors & CLI warning when using token auth while being logged in #2445

Merged
merged 9 commits into from
Sep 19, 2024
28 changes: 28 additions & 0 deletions backend/src/server/plugins/error-handler.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ForbiddenError } from "@casl/ability";
import fastifyPlugin from "fastify-plugin";
import { JsonWebTokenError } from "jsonwebtoken";
import { ZodError } from "zod";

import {
Expand All @@ -11,6 +12,12 @@ import {
UnauthorizedError
} from "@app/lib/errors";

enum JWTErrors {
JwtExpired = "jwt expired",
JwtMalformed = "jwt malformed",
InvalidAlgorithm = "invalid algorithm"
}

export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider) => {
server.setErrorHandler((error, req, res) => {
req.log.error(error);
Expand All @@ -36,6 +43,27 @@ export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider
status: error.status,
detail: error.detail
});
// Handle JWT errors and make them more human-readable for the end-user.
} else if (error instanceof JsonWebTokenError) {
const message = (() => {
if (error.message === JWTErrors.JwtExpired) {
return "Your token has expired. Please re-authenticate.";
}
if (error.message === JWTErrors.JwtMalformed) {
return "The provided access token is malformed. Please use a valid token or generate a new one and try again.";
}
if (error.message === JWTErrors.InvalidAlgorithm) {
return "The access token is signed with an invalid algorithm. Please provide a valid token and try again.";
}

return error.message;
})();

void res.status(401).send({
statusCode: 401,
error: "TokenError",
message
});
} else {
void res.send(error);
}
Expand Down
5 changes: 4 additions & 1 deletion backend/src/services/project-bot/project-bot-fns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ export const getBotKeyFnFactory = (
) => {
const getBotKeyFn = async (projectId: string) => {
const project = await projectDAL.findById(projectId);
if (!project) throw new BadRequestError({ message: "Project not found during bot lookup." });
if (!project)
throw new BadRequestError({
message: "Project not found during bot lookup. Are you sure you are using the correct project ID?"
});

if (project.version === 3) {
return { project, shouldUseSecretV2Bridge: true };
Expand Down
6 changes: 5 additions & 1 deletion backend/src/services/secret-import/secret-import-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -512,7 +512,11 @@ export const secretImportServiceFactory = ({
return importedSecrets;
}

if (!botKey) throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" });
if (!botKey)
throw new BadRequestError({
message: "Project bot not found. Please upgrade your project.",
name: "bot_not_found_error"
});

const importedSecrets = await fnSecretsFromImports({ allowedImports, folderDAL, secretDAL, secretImportDAL });
return importedSecrets.map((el) => ({
Expand Down
12 changes: 10 additions & 2 deletions backend/src/services/secret/secret-fns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -832,7 +832,11 @@ export const createManySecretsRawFnFactory = ({
secretDAL
});

if (!botKey) throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" });
if (!botKey)
throw new BadRequestError({
message: "Project bot not found. Please upgrade your project.",
name: "bot_not_found_error"
});
const inputSecrets = secrets.map((secret) => {
const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8(secret.secretName, botKey);
const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8(secret.secretValue || "", botKey);
Expand Down Expand Up @@ -993,7 +997,11 @@ export const updateManySecretsRawFnFactory = ({
return updatedSecrets;
}

if (!botKey) throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" });
if (!botKey)
throw new BadRequestError({
message: "Project bot not found. Please upgrade your project.",
name: "bot_not_found_error"
});
const blindIndexCfg = await secretBlindIndexDAL.findOne({ projectId });
if (!blindIndexCfg) throw new BadRequestError({ message: "Blind index not found", name: "Update secret" });

Expand Down
57 changes: 47 additions & 10 deletions backend/src/services/secret/secret-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -985,7 +985,11 @@ export const secretServiceFactory = ({
return { secrets, imports };
}

if (!botKey) throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" });
if (!botKey)
throw new BadRequestError({
message: "Project bot not found. Please upgrade your project.",
name: "bot_not_found_error"
});

const { secrets, imports } = await getSecrets({
actorId,
Expand Down Expand Up @@ -1146,7 +1150,10 @@ export const secretServiceFactory = ({
});

if (!botKey)
throw new BadRequestError({ message: "Please upgrade your project first", name: "bot_not_found_error" });
throw new BadRequestError({
message: "Project bot not found. Please upgrade your project.",
name: "bot_not_found_error"
});
const decryptedSecret = decryptSecretRaw(encryptedSecret, botKey);

if (expandSecretReferences) {
Expand Down Expand Up @@ -1238,7 +1245,11 @@ export const secretServiceFactory = ({
return { secret, type: SecretProtectionType.Direct as const };
}

if (!botKey) throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" });
if (!botKey)
throw new BadRequestError({
message: "Project bot not found. Please upgrade your project.",
name: "bot_not_found_error"
});
const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8(secretName, botKey);
const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8(secretValue || "", botKey);
const secretCommentEncrypted = encryptSymmetric128BitHexKeyUTF8(secretComment || "", botKey);
Expand Down Expand Up @@ -1376,7 +1387,11 @@ export const secretServiceFactory = ({
return { type: SecretProtectionType.Direct as const, secret };
}

if (!botKey) throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" });
if (!botKey)
throw new BadRequestError({
message: "Project bot not found. Please upgrade your project.",
name: "bot_not_found_error"
});

const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8(secretValue || "", botKey);
const secretCommentEncrypted = encryptSymmetric128BitHexKeyUTF8(secretComment || "", botKey);
Expand Down Expand Up @@ -1498,7 +1513,11 @@ export const secretServiceFactory = ({
});
return { type: SecretProtectionType.Direct as const, secret };
}
if (!botKey) throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" });
if (!botKey)
throw new BadRequestError({
message: "Project bot not found. Please upgrade your project.",
name: "bot_not_found_error"
});
if (policy) {
const approval = await secretApprovalRequestService.generateSecretApprovalRequest({
policy,
Expand Down Expand Up @@ -1598,7 +1617,11 @@ export const secretServiceFactory = ({
return { secrets, type: SecretProtectionType.Direct as const };
}

if (!botKey) throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" });
if (!botKey)
throw new BadRequestError({
message: "Project bot not found. Please upgrade your project.",
name: "bot_not_found_error"
});
const sanitizedSecrets = inputSecrets.map(
({ secretComment, secretKey, metadata, tagIds, secretValue, skipMultilineEncoding }) => {
const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8(secretKey, botKey);
Expand Down Expand Up @@ -1720,7 +1743,11 @@ export const secretServiceFactory = ({
return { type: SecretProtectionType.Direct as const, secrets };
}

if (!botKey) throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" });
if (!botKey)
throw new BadRequestError({
message: "Project bot not found. Please upgrade your project.",
name: "bot_not_found_error"
});
const sanitizedSecrets = inputSecrets.map(
({
secretComment,
Expand Down Expand Up @@ -1848,7 +1875,11 @@ export const secretServiceFactory = ({
return { type: SecretProtectionType.Direct as const, secrets };
}

if (!botKey) throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" });
if (!botKey)
throw new BadRequestError({
message: "Project bot not found. Please upgrade your project.",
name: "bot_not_found_error"
});

if (policy) {
const approval = await secretApprovalRequestService.generateSecretApprovalRequest({
Expand Down Expand Up @@ -2182,7 +2213,10 @@ export const secretServiceFactory = ({
}

if (!botKey)
throw new BadRequestError({ message: "Please upgrade your project first", name: "bot_not_found_error" });
throw new BadRequestError({
message: "Project bot not found. Please upgrade your project.",
name: "bot_not_found_error"
});

await secretDAL.transaction(async (tx) => {
const secrets = await secretDAL.findAllProjectSecretValues(projectId, tx);
Expand Down Expand Up @@ -2265,7 +2299,10 @@ export const secretServiceFactory = ({

const { botKey } = await projectBotService.getBotKey(project.id);
if (!botKey) {
throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" });
throw new BadRequestError({
message: "Project bot not found. Please upgrade your project.",
name: "bot_not_found_error"
});
}

const sourceFolder = await folderDAL.findBySecretPath(project.id, sourceEnvironment, sourceSecretPath);
Expand Down
8 changes: 4 additions & 4 deletions cli/packages/cmd/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,9 @@ var exportCmd = &cobra.Command{
IncludeImport: includeImports,
}

if token != nil && token.Type == util.SERVICE_TOKEN_IDENTIFIER {
if util.ShouldUseInfisicalToken(token, []string{util.SERVICE_TOKEN_IDENTIFIER}) {
request.InfisicalToken = token.Token
} else if token != nil && token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER {
} else if util.ShouldUseInfisicalToken(token, []string{util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER}) {
request.UniversalAuthAccessToken = token.Token
}

Expand Down Expand Up @@ -141,9 +141,9 @@ var exportCmd = &cobra.Command{

authParams := models.ExpandSecretsAuthentication{}

if token != nil && token.Type == util.SERVICE_TOKEN_IDENTIFIER {
if util.ShouldUseInfisicalToken(token, []string{util.SERVICE_TOKEN_IDENTIFIER}) {
authParams.InfisicalToken = token.Token
} else if token != nil && token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER {
} else if util.ShouldUseInfisicalToken(token, []string{util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER}) {
authParams.UniversalAuthAccessToken = token.Token
}

Expand Down
8 changes: 4 additions & 4 deletions cli/packages/cmd/folder.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ var getCmd = &cobra.Command{
FoldersPath: foldersPath,
}

if token != nil && token.Type == util.SERVICE_TOKEN_IDENTIFIER {
if util.ShouldUseInfisicalToken(token, []string{util.SERVICE_TOKEN_IDENTIFIER}) {
request.InfisicalToken = token.Token
} else if token != nil && token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER {
} else if util.ShouldUseInfisicalToken(token, []string{util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER}) {
request.UniversalAuthAccessToken = token.Token
}

Expand Down Expand Up @@ -125,7 +125,7 @@ var createCmd = &cobra.Command{
WorkspaceId: projectId,
}

if token != nil && (token.Type == util.SERVICE_TOKEN_IDENTIFIER || token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER) {
if util.ShouldUseInfisicalToken(token, nil) {
params.InfisicalToken = token.Token
}

Expand Down Expand Up @@ -193,7 +193,7 @@ var deleteCmd = &cobra.Command{
FolderPath: folderPath,
}

if token != nil && (token.Type == util.SERVICE_TOKEN_IDENTIFIER || token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER) {
if util.ShouldUseInfisicalToken(token, nil) {
params.InfisicalToken = token.Token
}

Expand Down
3 changes: 3 additions & 0 deletions cli/packages/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,16 @@ func init() {
rootCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) {
silent, err := cmd.Flags().GetBool("silent")
config.INFISICAL_URL = util.AppendAPIEndpoint(config.INFISICAL_URL)

if err != nil {
util.HandleError(err)
}

if !util.IsRunningInDocker() && !silent {
util.CheckForUpdate()
}

config.INFISICAL_SILENT_MODE = silent
}

// if config.INFISICAL_URL is set to the default value, check if INFISICAL_URL is set in the environment
Expand Down
8 changes: 4 additions & 4 deletions cli/packages/cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -439,9 +439,9 @@ func executeCommandWithWatchMode(commandFlag string, args []string, watchModeInt

func fetchAndFormatSecretsForShell(request models.GetAllSecretsParameters, projectConfigDir string, secretOverriding bool, shouldExpandSecrets bool, token *models.TokenDetails) (models.InjectableEnvironmentResult, error) {

if token != nil && token.Type == util.SERVICE_TOKEN_IDENTIFIER {
if util.ShouldUseInfisicalToken(token, []string{util.SERVICE_TOKEN_IDENTIFIER}) {
DanielHougaard marked this conversation as resolved.
Show resolved Hide resolved
request.InfisicalToken = token.Token
} else if token != nil && token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER {
} else if util.ShouldUseInfisicalToken(token, []string{util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER}) {
request.UniversalAuthAccessToken = token.Token
}

Expand All @@ -461,9 +461,9 @@ func fetchAndFormatSecretsForShell(request models.GetAllSecretsParameters, proje

authParams := models.ExpandSecretsAuthentication{}

if token != nil && token.Type == util.SERVICE_TOKEN_IDENTIFIER {
if util.ShouldUseInfisicalToken(token, []string{util.SERVICE_TOKEN_IDENTIFIER}) {
authParams.InfisicalToken = token.Token
} else if token != nil && token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER {
} else if util.ShouldUseInfisicalToken(token, []string{util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER}) {
authParams.UniversalAuthAccessToken = token.Token
}

Expand Down
Loading
Loading