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
Merged
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
15 changes: 14 additions & 1 deletion cli/packages/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Copyright (c) 2023 Infisical Inc.
package cmd

import (
"fmt"
"os"
"strings"

Expand Down Expand Up @@ -43,14 +44,26 @@ func init() {
rootCmd.PersistentFlags().Bool("silent", false, "Disable output of tip/info messages. Useful when running in scripts or CI/CD pipelines.")
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)
}

config.INFISICAL_URL = util.AppendAPIEndpoint(config.INFISICAL_URL)

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

loggedInDetails, err := util.GetCurrentLoggedInUserDetails()

if !silent && err == nil && loggedInDetails.IsUserLoggedIn && !loggedInDetails.LoginExpired {
token, err := util.GetInfisicalToken(cmd)

if err == nil && token != nil {
util.PrintWarning(fmt.Sprintf("Your logged-in session is being overwritten by the token provided from the %s.", token.Source))
}
}

}

// if config.INFISICAL_URL is set to the default value, check if INFISICAL_URL is set in the environment
Expand Down
6 changes: 3 additions & 3 deletions cli/packages/cmd/secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,19 +160,19 @@ var secretsSetCmd = &cobra.Command{
util.HandleError(err, "Unable to parse flag")
}

if (token == nil) {
if token == nil {
util.RequireLocalWorkspaceFile()
}

environmentName, _ := cmd.Flags().GetString("env")
if !cmd.Flags().Changed("env") {
environmentFromWorkspace := util.GetEnvFromWorkspaceFile()
environmentFromWorkspace := util.GetEnvFromWorkspaceFile()
if environmentFromWorkspace != "" {
environmentName = environmentFromWorkspace
}
}

projectId, err := cmd.Flags().GetString("projectId")
projectId, err := cmd.Flags().GetString("projectId")
if err != nil {
util.HandleError(err, "Unable to parse flag")
}
Expand Down
5 changes: 3 additions & 2 deletions cli/packages/models/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,9 @@ type DynamicSecretLease struct {
}

type TokenDetails struct {
Type string
Token string
Type string
Token string
Source string
}

type SingleFolder struct {
Expand Down
14 changes: 10 additions & 4 deletions cli/packages/util/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,15 @@ func GetInfisicalToken(cmd *cobra.Command) (token *models.TokenDetails, err erro
return nil, err
}

var source = "--token flag"

if infisicalToken == "" { // If no flag is passed, we first check for the universal auth access token env variable.
infisicalToken = os.Getenv(INFISICAL_UNIVERSAL_AUTH_ACCESS_TOKEN_NAME)
source = fmt.Sprintf("%s environment variable", INFISICAL_UNIVERSAL_AUTH_ACCESS_TOKEN_NAME)

if infisicalToken == "" { // If it's still empty after the first env check, we check for the service token env variable.
infisicalToken = os.Getenv(INFISICAL_TOKEN_NAME)
source = fmt.Sprintf("%s environment variable", INFISICAL_TOKEN_NAME)
}
}

Expand All @@ -101,14 +105,16 @@ func GetInfisicalToken(cmd *cobra.Command) (token *models.TokenDetails, err erro

if strings.HasPrefix(infisicalToken, "st.") {
return &models.TokenDetails{
Type: SERVICE_TOKEN_IDENTIFIER,
Token: infisicalToken,
Type: SERVICE_TOKEN_IDENTIFIER,
Token: infisicalToken,
Source: source,
}, nil
}

return &models.TokenDetails{
Type: UNIVERSAL_AUTH_TOKEN_IDENTIFIER,
Token: infisicalToken,
Type: UNIVERSAL_AUTH_TOKEN_IDENTIFIER,
Token: infisicalToken,
Source: source,
}, nil

}
Expand Down
Loading