From 6af34335a82b2d9e963a108ff5cf81020493870c Mon Sep 17 00:00:00 2001 From: Sojan Jose Date: Fri, 23 May 2025 02:22:16 -0700 Subject: [PATCH] test: add spec for profile access token reset --- app/controllers/api/v1/profiles_controller.rb | 5 ++++ app/javascript/dashboard/api/auth.js | 4 +++ app/javascript/dashboard/api/endPoints.js | 3 ++ .../dashboard/i18n/locale/en/settings.json | 5 +++- .../settings/profile/AccessToken.vue | 16 ++++++++++- .../dashboard/settings/profile/Index.vue | 14 +++++++++- .../dashboard/store/modules/auth.js | 10 +++++++ .../profiles/reset_access_token.json.jbuilder | 1 + config/database.yml | 3 -- config/routes.rb | 1 + .../api/v1/profiles_controller_spec.rb | 28 +++++++++++++++++++ 11 files changed, 84 insertions(+), 6 deletions(-) create mode 100644 app/views/api/v1/profiles/reset_access_token.json.jbuilder diff --git a/app/controllers/api/v1/profiles_controller.rb b/app/controllers/api/v1/profiles_controller.rb index ae1a1fe30..141253d0d 100644 --- a/app/controllers/api/v1/profiles_controller.rb +++ b/app/controllers/api/v1/profiles_controller.rb @@ -38,6 +38,11 @@ class Api::V1::ProfilesController < Api::BaseController head :ok end + def reset_access_token + @user.access_token.regenerate_token + @user.reload + end + private def set_user diff --git a/app/javascript/dashboard/api/auth.js b/app/javascript/dashboard/api/auth.js index dde817866..75e7e2953 100644 --- a/app/javascript/dashboard/api/auth.js +++ b/app/javascript/dashboard/api/auth.js @@ -102,4 +102,8 @@ export default { const urlData = endPoints('resendConfirmation'); return axios.post(urlData.url); }, + resetAccessToken() { + const urlData = endPoints('resetAccessToken'); + return axios.post(urlData.url); + }, }; diff --git a/app/javascript/dashboard/api/endPoints.js b/app/javascript/dashboard/api/endPoints.js index 31337b7fc..5409aac60 100644 --- a/app/javascript/dashboard/api/endPoints.js +++ b/app/javascript/dashboard/api/endPoints.js @@ -51,6 +51,9 @@ const endPoints = { resendConfirmation: { url: '/api/v1/profile/resend_confirmation', }, + resetAccessToken: { + url: '/api/v1/profile/reset_access_token', + }, }; export default page => { diff --git a/app/javascript/dashboard/i18n/locale/en/settings.json b/app/javascript/dashboard/i18n/locale/en/settings.json index daced76dd..46dde1deb 100644 --- a/app/javascript/dashboard/i18n/locale/en/settings.json +++ b/app/javascript/dashboard/i18n/locale/en/settings.json @@ -76,7 +76,10 @@ "ACCESS_TOKEN": { "TITLE": "Access Token", "NOTE": "This token can be used if you are building an API based integration", - "COPY": "Copy" + "COPY": "Copy", + "RESET": "Reset", + "RESET_SUCCESS": "Access token regenerated", + "RESET_ERROR": "Could not reset token" }, "AUDIO_NOTIFICATIONS_SECTION": { "TITLE": "Audio Alerts", diff --git a/app/javascript/dashboard/routes/dashboard/settings/profile/AccessToken.vue b/app/javascript/dashboard/routes/dashboard/settings/profile/AccessToken.vue index abf547b69..ba16a7dc0 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/profile/AccessToken.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/profile/AccessToken.vue @@ -7,7 +7,7 @@ const props = defineProps({ default: '', }, }); -const emit = defineEmits(['onCopy']); +const emit = defineEmits(['onCopy', 'onReset']); const inputType = ref('password'); const toggleMasked = () => { inputType.value = inputType.value === 'password' ? 'text' : 'password'; @@ -20,6 +20,10 @@ const maskIcon = computed(() => { const onClick = () => { emit('onCopy', props.value); }; + +const onReset = () => { + emit('onReset'); +}; diff --git a/app/javascript/dashboard/routes/dashboard/settings/profile/Index.vue b/app/javascript/dashboard/routes/dashboard/settings/profile/Index.vue index 45a060e05..eedcd21f1 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/profile/Index.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/profile/Index.vue @@ -181,6 +181,14 @@ export default { await copyTextToClipboard(value); useAlert(this.$t('COMPONENTS.CODE.COPY_SUCCESSFUL')); }, + async resetAccessToken() { + const success = await this.$store.dispatch('resetAccessToken'); + if (success) { + useAlert(this.$t('PROFILE_SETTINGS.FORM.ACCESS_TOKEN.RESET_SUCCESS')); + } else { + useAlert(this.$t('PROFILE_SETTINGS.FORM.ACCESS_TOKEN.RESET_ERROR')); + } + }, }, }; @@ -281,7 +289,11 @@ export default { ) " > - + diff --git a/app/javascript/dashboard/store/modules/auth.js b/app/javascript/dashboard/store/modules/auth.js index b790b2a80..f329fb009 100644 --- a/app/javascript/dashboard/store/modules/auth.js +++ b/app/javascript/dashboard/store/modules/auth.js @@ -213,6 +213,16 @@ export const actions = { } }, + resetAccessToken: async ({ commit }) => { + try { + const response = await authAPI.resetAccessToken(); + commit(types.SET_CURRENT_USER, response.data); + return true; + } catch (error) { + return false; + } + }, + resendConfirmation: async () => { try { await authAPI.resendConfirmation(); diff --git a/app/views/api/v1/profiles/reset_access_token.json.jbuilder b/app/views/api/v1/profiles/reset_access_token.json.jbuilder new file mode 100644 index 000000000..0a4b4f9fa --- /dev/null +++ b/app/views/api/v1/profiles/reset_access_token.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'api/v1/models/user', formats: [:json], resource: @user diff --git a/config/database.yml b/config/database.yml index 0577e3ee6..7a28adb4a 100644 --- a/config/database.yml +++ b/config/database.yml @@ -16,16 +16,13 @@ development: <<: *default database: "<%= ENV.fetch('POSTGRES_DATABASE', 'chatwoot_dev') %>" username: "<%= ENV.fetch('POSTGRES_USERNAME', 'postgres') %>" - password: "<%= ENV.fetch('POSTGRES_PASSWORD', '') %>" test: <<: *default database: "<%= ENV.fetch('POSTGRES_DATABASE', 'chatwoot_test') %>" username: "<%= ENV.fetch('POSTGRES_USERNAME', 'postgres') %>" - password: "<%= ENV.fetch('POSTGRES_PASSWORD', '') %>" production: <<: *default database: "<%= ENV.fetch('POSTGRES_DATABASE', 'chatwoot_production') %>" username: "<%= ENV.fetch('POSTGRES_USERNAME', 'chatwoot_prod') %>" - password: "<%= ENV.fetch('POSTGRES_PASSWORD', 'chatwoot_prod') %>" diff --git a/config/routes.rb b/config/routes.rb index 4b4db7b6d..df345b540 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -295,6 +295,7 @@ Rails.application.routes.draw do post :auto_offline put :set_active_account post :resend_confirmation + post :reset_access_token end end diff --git a/spec/controllers/api/v1/profiles_controller_spec.rb b/spec/controllers/api/v1/profiles_controller_spec.rb index 50404ad55..1d40e4853 100644 --- a/spec/controllers/api/v1/profiles_controller_spec.rb +++ b/spec/controllers/api/v1/profiles_controller_spec.rb @@ -296,4 +296,32 @@ RSpec.describe 'Profile API', type: :request do end end end + + describe 'POST /api/v1/profile/reset_access_token' do + context 'when it is an unauthenticated user' do + it 'returns unauthorized' do + post '/api/v1/profile/reset_access_token' + + expect(response).to have_http_status(:unauthorized) + end + end + + context 'when it is an authenticated user' do + let(:agent) { create(:user, account: account, role: :agent) } + + it 'regenerates the access token' do + old_token = agent.access_token.token + + post '/api/v1/profile/reset_access_token', + headers: agent.create_new_auth_token, + as: :json + + expect(response).to have_http_status(:success) + agent.reload + json_response = response.parsed_body + expect(json_response['access_token']).to eq(agent.access_token.token) + expect(agent.access_token.token).not_to eq(old_token) + end + end + end end