feat: handle oauth scope verification and errors

This commit is contained in:
Shivam Mishra
2025-06-09 15:35:42 +05:30
parent 43e55491a3
commit f4b8760e00
6 changed files with 129 additions and 4 deletions
+1 -1
View File
@@ -15,6 +15,6 @@ module GoogleConcern
private
def scope
'email profile https://mail.google.com/'
'https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email https://mail.google.com/'
end
end
@@ -12,6 +12,16 @@ class Google::CallbacksController < OauthCallbackController
private
def verify_scopes
granted_scopes = parsed_body['scope']&.split || []
required_scopes = scope.split
missing_scopes = required_scopes - granted_scopes
return if missing_scopes.empty?
raise CustomExceptions::OAuth::InsufficientScopes.new({ missing_scopes: missing_scopes })
end
def provider_name
'google'
end
@@ -24,4 +34,14 @@ class Google::CallbacksController < OauthCallbackController
# from GoogleConcern
google_client
end
def handle_error(exception)
ChatwootExceptionTracker.new(exception).capture_exception
error_code = exception.respond_to?(:code) ? exception.code : 'OAUTH_ERR'
error_message = exception.message || 'OAuth authorization failed'
redirect_url = "#{ENV.fetch('FRONTEND_URL', 'http://localhost:3000')}/app/accounts/#{account.id}/settings/inboxes/new/email"
redirect_to "#{redirect_url}?error=#{CGI.escape(error_message)}&code=#{error_code}"
end
end
+15 -2
View File
@@ -5,10 +5,10 @@ class OauthCallbackController < ApplicationController
redirect_uri: "#{base_url}/#{provider_name}/callback"
)
verify_scopes
handle_response
rescue StandardError => e
ChatwootExceptionTracker.new(e).capture_exception
redirect_to '/'
handle_error(e)
end
private
@@ -63,6 +63,19 @@ class OauthCallbackController < ApplicationController
raise NotImplementedError
end
def verify_scopes
true
end
def failure_redirect_url
'/'
end
def handle_error(exception)
ChatwootExceptionTracker.new(exception).capture_exception
redirect_to failure_redirect_url
end
def create_channel_with_inbox
ActiveRecord::Base.transaction do
channel_email = Channel::Email.create!(email: users_data['email'], account: account)
@@ -1,17 +1,21 @@
<script setup>
import { ref, computed } from 'vue';
import { ref, computed, onMounted } from 'vue';
import ForwardToOption from './emailChannels/ForwardToOption.vue';
import Microsoft from './emailChannels/Microsoft.vue';
import Google from './emailChannels/Google.vue';
import ChannelSelector from 'dashboard/components/ChannelSelector.vue';
import PageHeader from '../../SettingsSubPageHeader.vue';
import { useAlert } from 'dashboard/composables';
import { useStoreGetters } from 'dashboard/composables/store';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
const provider = ref('');
const getters = useStoreGetters();
const route = useRoute();
const { t } = useI18n();
const globalConfig = getters['globalConfig/get'];
@@ -45,6 +49,15 @@ const emailProviderList = computed(() => {
});
});
const checkForOAuthError = () => {
const { error } = route.query;
if (error) {
useAlert(decodeURIComponent(error));
}
};
onMounted(checkForOAuthError);
function onClick(emailProvider) {
if (emailProvider.isEnabled) {
provider.value = emailProvider.key;
+5
View File
@@ -48,6 +48,11 @@ en:
email_already_exists: 'You have already signed up for an account with %{email}'
invalid_params: 'Invalid, please check the signup paramters and try again'
failed: Signup failed
oauth:
insufficient_scopes: 'Insufficient permissions granted. Missing scopes: %{scopes}'
authorization_failed: 'Authorization failed for %{provider}'
token_exchange_failed: 'Failed to exchange authorization code for %{provider}'
invalid_state: 'Invalid or missing state parameter'
data_import:
data_type:
invalid: Invalid data type
+74
View File
@@ -0,0 +1,74 @@
# frozen_string_literal: true
module CustomExceptions::OAuth
class InsufficientScopes < CustomExceptions::Base
def message
I18n.t('errors.oauth.insufficient_scopes', scopes: @data[:missing_scopes].join(', '))
end
def code
'SCOPE_ERR'
end
def to_hash
{
message: message,
code: code,
missing_scopes: @data[:missing_scopes]
}
end
end
class AuthorizationFailed < CustomExceptions::Base
def message
I18n.t('errors.oauth.authorization_failed', provider: @data[:provider])
end
def code
'AUTH_ERR'
end
def to_hash
{
message: message,
code: code,
provider: @data[:provider]
}
end
end
class TokenExchangeFailed < CustomExceptions::Base
def message
I18n.t('errors.oauth.token_exchange_failed', provider: @data[:provider])
end
def code
'TOKEN_ERR'
end
def to_hash
{
message: message,
code: code,
provider: @data[:provider]
}
end
end
class InvalidState < CustomExceptions::Base
def message
I18n.t('errors.oauth.invalid_state')
end
def code
'STATE_ERR'
end
def to_hash
{
message: message,
code: code
}
end
end
end