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:
Shivam Mishra
2024-02-03 02:01:29 +05:30
committed by GitHub
parent 07ea9694a3
commit 0c35a77d4b
11 changed files with 123 additions and 3 deletions

View File

@@ -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

View File

@@ -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],

View File

@@ -98,4 +98,8 @@ export default {
},
});
},
resendConfirmation() {
const urlData = endPoints('resendConfirmation');
return axios.post(urlData.url);
},
};

View File

@@ -47,6 +47,10 @@ const endPoints = {
setActiveAccount: {
url: '/api/v1/profile/set_active_account',
},
resendConfirmation: {
url: '/api/v1/profile/resend_confirmation',
},
};
export default page => {

View File

@@ -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>

View File

@@ -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: '',

View File

@@ -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."

View File

@@ -181,6 +181,14 @@ export const actions = {
// Ignore error
}
},
resendConfirmation: async () => {
try {
await authAPI.resendConfirmation();
} catch (error) {
// Ignore error
}
},
};
// mutations

View File

@@ -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?

View File

@@ -248,6 +248,7 @@ Rails.application.routes.draw do
post :availability
post :auto_offline
put :set_active_account
post :resend_confirmation
end
end

View File

@@ -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