mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-10-31 02:57:57 +00:00 
			
		
		
		
	feat: Show a confirmation banner if the email is not verified (#8808)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
This commit is contained in:
		| @@ -31,6 +31,11 @@ class Api::V1::ProfilesController < Api::BaseController | ||||
|     head :ok | ||||
|   end | ||||
|  | ||||
|   def resend_confirmation | ||||
|     @user.send_confirmation_instructions unless @user.confirmed? | ||||
|     head :ok | ||||
|   end | ||||
|  | ||||
|   private | ||||
|  | ||||
|   def set_user | ||||
|   | ||||
| @@ -2,12 +2,13 @@ | ||||
|   <div | ||||
|     v-if="!authUIFlags.isFetching && !accountUIFlags.isFetchingItem" | ||||
|     id="app" | ||||
|     class="app-wrapper h-full flex-grow-0 min-h-0 w-full" | ||||
|     class="flex-grow-0 w-full h-full min-h-0 app-wrapper" | ||||
|     :class="{ 'app-rtl--wrapper': isRTLView }" | ||||
|     :dir="isRTLView ? 'rtl' : 'ltr'" | ||||
|   > | ||||
|     <update-banner :latest-chatwoot-version="latestChatwootVersion" /> | ||||
|     <template v-if="currentAccountId"> | ||||
|       <pending-email-verification-banner /> | ||||
|       <payment-pending-banner /> | ||||
|       <upgrade-banner /> | ||||
|     </template> | ||||
| @@ -32,6 +33,7 @@ import NetworkNotification from './components/NetworkNotification.vue'; | ||||
| import UpdateBanner from './components/app/UpdateBanner.vue'; | ||||
| import UpgradeBanner from './components/app/UpgradeBanner.vue'; | ||||
| import PaymentPendingBanner from './components/app/PaymentPendingBanner.vue'; | ||||
| import PendingEmailVerificationBanner from './components/app/PendingEmailVerificationBanner.vue'; | ||||
| import vueActionCable from './helper/actionCable'; | ||||
| import WootSnackbarBox from './components/SnackbarContainer.vue'; | ||||
| import rtlMixin from 'shared/mixins/rtlMixin'; | ||||
| @@ -52,6 +54,7 @@ export default { | ||||
|     PaymentPendingBanner, | ||||
|     WootSnackbarBox, | ||||
|     UpgradeBanner, | ||||
|     PendingEmailVerificationBanner, | ||||
|   }, | ||||
|  | ||||
|   mixins: [rtlMixin], | ||||
|   | ||||
| @@ -98,4 +98,8 @@ export default { | ||||
|       }, | ||||
|     }); | ||||
|   }, | ||||
|   resendConfirmation() { | ||||
|     const urlData = endPoints('resendConfirmation'); | ||||
|     return axios.post(urlData.url); | ||||
|   }, | ||||
| }; | ||||
|   | ||||
| @@ -47,6 +47,10 @@ const endPoints = { | ||||
|   setActiveAccount: { | ||||
|     url: '/api/v1/profile/set_active_account', | ||||
|   }, | ||||
|  | ||||
|   resendConfirmation: { | ||||
|     url: '/api/v1/profile/resend_confirmation', | ||||
|   }, | ||||
| }; | ||||
|  | ||||
| export default page => { | ||||
|   | ||||
| @@ -0,0 +1,43 @@ | ||||
| <template> | ||||
|   <banner | ||||
|     v-if="shouldShowBanner" | ||||
|     color-scheme="alert" | ||||
|     :banner-message="bannerMessage" | ||||
|     :action-button-label="actionButtonMessage" | ||||
|     action-button-icon="mail" | ||||
|     has-action-button | ||||
|     @click="resendVerificationEmail" | ||||
|   /> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import Banner from 'dashboard/components/ui/Banner.vue'; | ||||
| import { mapGetters } from 'vuex'; | ||||
| import accountMixin from 'dashboard/mixins/account'; | ||||
| import alertMixin from 'shared/mixins/alertMixin'; | ||||
|  | ||||
| export default { | ||||
|   components: { Banner }, | ||||
|   mixins: [accountMixin, alertMixin], | ||||
|   computed: { | ||||
|     ...mapGetters({ | ||||
|       currentUser: 'getCurrentUser', | ||||
|     }), | ||||
|     bannerMessage() { | ||||
|       return this.$t('APP_GLOBAL.EMAIL_VERIFICATION_PENDING'); | ||||
|     }, | ||||
|     actionButtonMessage() { | ||||
|       return this.$t('APP_GLOBAL.RESEND_VERIFICATION_MAIL'); | ||||
|     }, | ||||
|     shouldShowBanner() { | ||||
|       return !this.currentUser.confirmed; | ||||
|     }, | ||||
|   }, | ||||
|   methods: { | ||||
|     resendVerificationEmail() { | ||||
|       this.$store.dispatch('resendConfirmation'); | ||||
|       this.showAlert(this.$t('APP_GLOBAL.EMAIL_VERIFICATION_SENT')); | ||||
|     }, | ||||
|   }, | ||||
| }; | ||||
| </script> | ||||
| @@ -1,6 +1,6 @@ | ||||
| <template> | ||||
|   <div | ||||
|     class="banner flex items-center h-12 gap-4 text-white dark:text-white text-xs py-3 px-4 justify-center" | ||||
|     class="flex items-center justify-center h-12 gap-4 px-4 py-3 text-xs text-white banner dark:text-white" | ||||
|     :class="bannerClasses" | ||||
|   > | ||||
|     <span class="banner-message"> | ||||
| @@ -18,7 +18,7 @@ | ||||
|       <woot-button | ||||
|         v-if="hasActionButton" | ||||
|         size="tiny" | ||||
|         icon="arrow-right" | ||||
|         :icon="actionButtonIcon" | ||||
|         :variant="actionButtonVariant" | ||||
|         color-scheme="primary" | ||||
|         class-names="banner-action__button" | ||||
| @@ -67,6 +67,10 @@ export default { | ||||
|       type: String, | ||||
|       default: '', | ||||
|     }, | ||||
|     actionButtonIcon: { | ||||
|       type: String, | ||||
|       default: 'arrow-right', | ||||
|     }, | ||||
|     colorScheme: { | ||||
|       type: String, | ||||
|       default: '', | ||||
|   | ||||
| @@ -156,6 +156,9 @@ | ||||
|     "TRIAL_MESSAGE": "days trial remaining.", | ||||
|     "TRAIL_BUTTON": "Buy Now", | ||||
|     "DELETED_USER": "Deleted User", | ||||
|     "EMAIL_VERIFICATION_PENDING": "It seems that you haven't verified your email address yet. Please check your inbox for the verification email.", | ||||
|     "RESEND_VERIFICATION_MAIL": "Resend verification email", | ||||
|     "EMAIL_VERIFICATION_SENT": "Verification email has been sent. Please check your inbox.", | ||||
|     "ACCOUNT_SUSPENDED": { | ||||
|       "TITLE": "Account Suspended", | ||||
|       "MESSAGE": "Your account is suspended. Please reach out to the support team for more information." | ||||
|   | ||||
| @@ -181,6 +181,14 @@ export const actions = { | ||||
|       // Ignore error | ||||
|     } | ||||
|   }, | ||||
|  | ||||
|   resendConfirmation: async () => { | ||||
|     try { | ||||
|       await authAPI.resendConfirmation(); | ||||
|     } catch (error) { | ||||
|       // Ignore error | ||||
|     } | ||||
|   }, | ||||
| }; | ||||
|  | ||||
| // mutations | ||||
|   | ||||
| @@ -94,6 +94,11 @@ class Rack::Attack | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   ## Resend confirmation throttling | ||||
|   throttle('resend_confirmation/ip', limit: 5, period: 30.minutes) do |req| | ||||
|     req.ip if req.path_without_extentions == '/api/v1/profile/resend_confirmation' && req.post? | ||||
|   end | ||||
|  | ||||
|   ## Prevent Brute-Force Signup Attacks ### | ||||
|   throttle('accounts/ip', limit: 5, period: 30.minutes) do |req| | ||||
|     req.ip if req.path_without_extentions == '/api/v1/accounts' && req.post? | ||||
|   | ||||
| @@ -248,6 +248,7 @@ Rails.application.routes.draw do | ||||
|           post :availability | ||||
|           post :auto_offline | ||||
|           put :set_active_account | ||||
|           post :resend_confirmation | ||||
|         end | ||||
|       end | ||||
|  | ||||
|   | ||||
| @@ -242,4 +242,44 @@ RSpec.describe 'Profile API', type: :request do | ||||
|       end | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   describe 'POST /api/v1/profile/resend_confirmation' do | ||||
|     context 'when it is an unauthenticated user' do | ||||
|       it 'returns unauthorized' do | ||||
|         post '/api/v1/profile/resend_confirmation' | ||||
|  | ||||
|         expect(response).to have_http_status(:unauthorized) | ||||
|       end | ||||
|     end | ||||
|  | ||||
|     context 'when it is an authenticated user' do | ||||
|       let(:agent) do | ||||
|         create(:user, password: 'Test123!', email: 'test-unconfirmed@email.com', account: account, role: :agent, | ||||
|                       unconfirmed_email: 'test-unconfirmed@email.com') | ||||
|       end | ||||
|  | ||||
|       it 'does not send the confirmation email if the user is already confirmed' do | ||||
|         expect do | ||||
|           post '/api/v1/profile/resend_confirmation', | ||||
|                headers: agent.create_new_auth_token, | ||||
|                as: :json | ||||
|         end.not_to have_enqueued_mail(Devise::Mailer, :confirmation_instructions) | ||||
|  | ||||
|         expect(response).to have_http_status(:success) | ||||
|       end | ||||
|  | ||||
|       it 'resends the confirmation email if the user is unconfirmed' do | ||||
|         agent.confirmed_at = nil | ||||
|         agent.save! | ||||
|  | ||||
|         expect do | ||||
|           post '/api/v1/profile/resend_confirmation', | ||||
|                headers: agent.create_new_auth_token, | ||||
|                as: :json | ||||
|         end.to have_enqueued_mail(Devise::Mailer, :confirmation_instructions) | ||||
|  | ||||
|         expect(response).to have_http_status(:success) | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Shivam Mishra
					Shivam Mishra