fix: validate OpenAI API key format and improve error handling

This commit is contained in:
Tanmay Deep Sharma
2026-01-19 17:46:15 +00:00
committed by root
parent e13e3c873a
commit 4c36cea89d
4 changed files with 63 additions and 9 deletions
+48 -6
View File
@@ -8,6 +8,22 @@ import { useAlert, useTrack } from 'dashboard/composables';
import { useI18n } from 'vue-i18n';
import { OPEN_AI_EVENTS } from 'dashboard/helper/AnalyticsHelper/events';
import OpenAPI from 'dashboard/api/integrations/openapi';
import { useRoute } from 'vue-router';
/**
* Checks if an error message indicates an invalid API key.
* @param {string} errorMessage - The error message to check.
* @returns {boolean} True if the error is related to an invalid API key.
*/
const isInvalidApiKeyError = errorMessage => {
if (!errorMessage) return false;
const lowerMessage = errorMessage.toLowerCase();
return (
lowerMessage.includes('incorrect api key') ||
lowerMessage.includes('invalid api key') ||
lowerMessage.includes('unauthorized')
);
};
/**
* Cleans and normalizes a list of labels.
@@ -31,6 +47,7 @@ export function useAI() {
const store = useStore();
const getters = useStoreGetters();
const { t } = useI18n();
const route = useRoute();
/**
* Computed property for UI flags.
@@ -131,6 +148,18 @@ export function useAI() {
}
};
/**
* Shows an error toast for invalid API key with a link to settings.
*/
const showInvalidApiKeyError = () => {
const accountId = route.params?.accountId;
useAlert(t('INTEGRATION_SETTINGS.OPEN_AI.INVALID_API_KEY_ERROR'), {
type: 'link',
to: `/app/accounts/${accountId}/settings/integrations/openai`,
message: t('INTEGRATION_SETTINGS.OPEN_AI.GO_TO_SETTINGS'),
});
};
/**
* Fetches label suggestions for the current conversation.
* @returns {Promise<string[]>} An array of suggested labels.
@@ -150,7 +179,14 @@ export function useAI() {
} = result;
return cleanLabels(labels);
} catch {
} catch (error) {
const errorData = error.response?.data?.error;
const errorMessage = errorData || '';
// Show toast for invalid API key errors
if (isInvalidApiKeyError(errorMessage)) {
showInvalidApiKeyError();
}
return [];
}
};
@@ -173,11 +209,17 @@ export function useAI() {
} = result;
return generatedMessage;
} catch (error) {
const errorData = error.response.data.error;
const errorMessage =
errorData?.error?.message ||
t('INTEGRATION_SETTINGS.OPEN_AI.GENERATE_ERROR');
useAlert(errorMessage);
const errorData = error.response?.data?.error;
const errorMessage = errorData || '';
// Check if this is an invalid API key error
if (isInvalidApiKeyError(errorMessage)) {
showInvalidApiKeyError();
} else {
useAlert(
errorMessage || t('INTEGRATION_SETTINGS.OPEN_AI.GENERATE_ERROR')
);
}
return '';
}
};
@@ -184,7 +184,9 @@
"GENERATING": "Generating...",
"CANCEL": "Cancel"
},
"GENERATE_ERROR": "There was an error processing the content, please verify your OpenAI API key and try again"
"GENERATE_ERROR": "There was an error processing the content, please verify your OpenAI API key and try again",
"INVALID_API_KEY_ERROR": "Your OpenAI API key is invalid.",
"GO_TO_SETTINGS": "Update key"
},
"DELETE": {
"BUTTON_TEXT": "Delete",
+2 -1
View File
@@ -46,7 +46,8 @@ openai:
'label': 'API Key',
'type': 'text',
'name': 'api_key',
'validation': 'required',
'validation': 'required|matches:/^sk-/',
'validation-messages': { 'matches': 'API Key must start with sk-. Please enter a valid OpenAI API key.' },
},
{
'label': 'Show label suggestions',
+10 -1
View File
@@ -96,6 +96,10 @@ class Integrations::LlmBaseService
end
end
# User configuration errors that should not be reported to Sentry
# Only UnauthorizedError is included as it clearly indicates an invalid API key
USER_ERRORS = %w[RubyLLM::UnauthorizedError].freeze
def execute_ruby_llm_request(parsed_body)
messages = parsed_body['messages']
model = parsed_body['model']
@@ -105,10 +109,15 @@ class Integrations::LlmBaseService
setup_chat_with_messages(chat, messages)
end
rescue StandardError => e
ChatwootExceptionTracker.new(e, account: hook.account).capture_exception
# Don't report user configuration errors (invalid API key, etc.) to Sentry
ChatwootExceptionTracker.new(e, account: hook.account).capture_exception unless user_configuration_error?(e)
build_error_response_from_exception(e, messages)
end
def user_configuration_error?(error)
USER_ERRORS.include?(error.class.name)
end
def setup_chat_with_messages(chat, messages)
apply_system_instructions(chat, messages)
response = send_conversation_messages(chat, messages)