Skip to content

Comments

feat: add email verification on signup#1644

Open
rakshityadav1868 wants to merge 20 commits intoruxailab:developfrom
rakshityadav1868:verify-email
Open

feat: add email verification on signup#1644
rakshityadav1868 wants to merge 20 commits intoruxailab:developfrom
rakshityadav1868:verify-email

Conversation

@rakshityadav1868
Copy link
Contributor

@rakshityadav1868 rakshityadav1868 commented Feb 7, 2026

fixes #334
Reference #1419

Overview

Added a complete email verification feature to the signup process. Users are now required to verify their email address before completing registration.


Detailed Changes

1. Frontend - New Email Verification View

  • File: src/features/auth/views/VerifyEmailView.vue
  • New Vue component for email verification page
  • Features:
    • Display the user's email address
    • Resend verification email button

2. Routing - Add Verify Email Route

  • File: src/router/modules/public.js
  • Added /verify-email route to public routes
  • Route includes VerifyEmailView component

3. Authentication Controller - Email Verification Method

  • File: src/features/auth/controllers/AuthController.js
  • New method: sendVerificationEmail(email, userName)
  • Sends verification email using EmailController
  • Uses a custom email template for verification

4. Auth Store - Verification State Management

  • File: src/features/auth/store/Auth.js
  • New state properties for managing the verification process
  • Actions and mutations for handling email verification flow

5. Email Templates - New Verification Email Template

  • File: functions/src/templates/mails/emailVerification.html
  • Includes:
    • Header with logo
    • Verification instructions

6. Email Function - Updated Email Handler

  • File: functions/src/https/email.js
  • Enhanced email sending functionality
  • Support for email verification template

7. Internationalization - New Translations

  • File: src/app/plugins/locales/en.json

8. Sign-Up View Updates

  • File: src/features/auth/views/SignUpView.vue
  • Integrated email verification into the signup flow
  • Redirects to the verification page after signup

9. Sign-In View Updates

  • File: src/features/auth/views/SignInView.vue
  • Minor updates to support new email verification flow

10. Router Configuration Updates

  • File: src/app/router/index.js
  • Updated router configuration for the new verify-email route

11. Email Controller Updates

  • File: src/shared/controllers/EmailController.js
  • The change modernizes the email sending to use Firebase's recommended approach (httpsCallable) instead of raw HTTP requests. This makes the code more secure and maintainable!

DIRECT SIGNUP WITH VERIFICATION:

Screen.Recording.2026-02-07.at.11.09.06.PM.1.mp4

TRYING TO SIGNIN WITHOUT VERIFICATION:

Screen.Recording.2026-02-07.at.11.13.49.PM.mp4

Signed-off-by: Rakshit Yadav <yadavrakshit60@gmail.com>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an email-verification gate to the auth flow so newly created accounts must verify their email before accessing authenticated areas. This spans the Vue frontend (new verify page + routing), Vuex auth enforcement, and the Firebase Functions email sender + new template.

Changes:

  • Added a new /verify-email public route and VerifyEmailView for resend/change-email UX.
  • Enforced emailVerified checks during sign-in/auto-sign-in and redirected unverified users to /verify-email.
  • Added a new emailVerification email template and updated the Cloud Function + frontend email sender to use httpsCallable.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
src/shared/controllers/EmailController.js Switches email sending from raw HTTP to Firebase httpsCallable.
src/router/modules/public.js Adds the /verify-email public route.
src/features/auth/views/VerifyEmailView.vue New verification UI (status, resend, change email).
src/features/auth/views/SignUpView.vue Redirects new signups to /verify-email.
src/features/auth/views/SignInView.vue Redirects to /verify-email when sign-in fails due to unverified email.
src/features/auth/store/Auth.js Sends verification email on signup; blocks sign-in if not verified; adds resend action.
src/features/auth/controllers/AuthController.js Adds sendVerificationEmail helper using EmailController/template.
src/app/router/index.js Updates global route guard to route unverified users to /verify-email.
src/app/plugins/locales/en.json Adds i18n strings for the verification UI.
functions/src/templates/mails/emailVerification.html Adds verification email HTML template.
functions/src/https/email.js Adds backend support for verification template + callable payload handling.
Comments suppressed due to low confidence (1)

functions/src/https/email.js:74

  • In a callable function, returning err from the catch block will resolve successfully on the client, so the frontend may report “Email sent successfully” even when sending fails. Throw a functions.https.HttpsError instead so httpsCallable rejects and the client can handle the failure properly.
    } catch (err) {
      logger.error('Error sending email:', { error: err });
      return err;
    }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +213 to +225
async updateEmail() {
try {
if (!this.newEmail || !this.newEmail.includes('@')) {
this.modalError = this.$t('auth.invalidEmail')
return
}

this.isUpdatingEmail = true
this.modalError = null

// Update email in Firebase
await updateEmail(auth.currentUser, this.newEmail)

Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The updateEmail method name shadows the imported Firebase updateEmail function. Inside this method, updateEmail(...) will resolve to the method itself (recursive call) and can cause a stack overflow instead of updating Firebase. Rename the component method (e.g., onUpdateEmail) or alias the import.

Copilot uses AI. Check for mistakes.

async logout() {
try {
await this.logout()
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logout method overrides the Vuex-mapped logout action (added by ...mapActions) and then calls this.logout() recursively, causing infinite recursion. Rename one of them or call the store action explicitly (e.g., this.$store.dispatch('logout')).

Suggested change
await this.logout()
await this.$store.dispatch('Auth/logout')

Copilot uses AI. Check for mistakes.
Comment on lines 274 to 283
mounted() {
// Ensure user is logged in
onAuthStateChanged(auth, (user) => {
if (!user) {
this.$router.push('/signin')
return
}
this.initializeVerification()
})
},
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The verification flow currently only polls auth.currentUser.reload(); it never processes the email action link (mode=verifyEmail, oobCode) that Firebase generates when handleCodeInApp is enabled. Without calling applyActionCode, users who open the link may never get verified. Handle the action link parameters on mount and update UI accordingly.

Copilot uses AI. Check for mistakes.
Comment on lines 265 to 268
// Check verification status every 3 seconds
this.verificationCheckInterval = setInterval(() => {
this.checkEmailVerificationStatus()
}, 3000)
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Polling auth.currentUser.reload() every 3 seconds can generate a lot of network traffic and may hit Firebase rate limits in real usage. Consider using a longer interval, adding a guard to prevent overlapping reloads, and/or switching to a user-triggered “I’ve verified” check.

Suggested change
// Check verification status every 3 seconds
this.verificationCheckInterval = setInterval(() => {
this.checkEmailVerificationStatus()
}, 3000)
// Avoid creating multiple verification check intervals
if (this.verificationCheckInterval) {
return
}
// Check verification status every 15 seconds
this.verificationCheckInterval = setInterval(() => {
this.checkEmailVerificationStatus()
}, 15000)

Copilot uses AI. Check for mistakes.
const actionCodeSettings = {
url: `${process.env.SITE_URL}/verify-email`,
handleCodeInApp: true,
}
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid automated semicolon insertion (90% of all statements in the enclosing function have an explicit semicolon).

Copilot uses AI. Check for mistakes.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings February 8, 2026 08:48
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 7 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 270 to 276
mounted() {
// Ensure user is logged in
onAuthStateChanged(auth, (user) => {
if (!user) {
this.$router.push('/signin')
return
}
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This view forces a redirect to /signin when onAuthStateChanged reports no user. That prevents users from verifying via the email link in cases where they open the link in a different browser/device (i.e., not already signed in). If you intend to use Firebase action links (oobCode), handle them here (e.g., applyActionCode) and avoid requiring an authenticated session just to verify.

Copilot uses AI. Check for mistakes.
Comment on lines +189 to +194

// Show success message using Vuex toast
this.$store.commit('SET_TOAST', {
message: this.$t('auth.verificationEmailSent'),
type: 'success',
})
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sendVerificationEmail Vuex action already commits a success toast (auth.verificationEmailSent). This method commits the same toast again after awaiting the action, which will likely show duplicate notifications. Consider removing the local SET_TOAST commit here or adding a silent option to the action.

Suggested change
// Show success message using Vuex toast
this.$store.commit('SET_TOAST', {
message: this.$t('auth.verificationEmailSent'),
type: 'success',
})

Copilot uses AI. Check for mistakes.
Comment on lines 270 to 279
mounted() {
// Ensure user is logged in
onAuthStateChanged(auth, (user) => {
if (!user) {
this.$router.push('/signin')
return
}
this.initializeVerification()
})
},
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

onAuthStateChanged returns an unsubscribe function, but it's not stored/called. Navigating away and back to this page can register multiple listeners and cause duplicate redirects/interval setup. Save the unsubscribe callback and invoke it in beforeUnmount.

Copilot uses AI. Check for mistakes.

async logout() {
try {
await this.logout()
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...mapActions(['sendVerificationEmail', 'logout']) maps an action named logout, but this component also declares a logout() method. The local method overrides the mapped action and then calls this.logout() recursively, causing infinite recursion/stack overflow. Rename the local method (e.g. onLogout) or map the action under a different name.

Suggested change
await this.logout()
// Dispatch the Vuex logout action directly to avoid recursive self-calls
await this.$store.dispatch('Auth/logout')

Copilot uses AI. Check for mistakes.
Comment on lines 1 to 23
@@ -13,10 +15,11 @@ export default class EmailController {
* @returns {Promise<{success: boolean, message: string}>} Result of email send operation
*/
async send(payload) {
try {
await axios.post(process.env.VUE_APP_CLOUD_FUNCTIONS_URL + '/sendEmail', {
data: payload,
})
try {
// Use Firebase SDK callable function instead of HTTP
// Firebase callable wraps the argument automatically, so pass payload directly
const sendEmailFunction = httpsCallable(fbFunctions, 'sendEmail')
const response = await sendEmailFunction(payload)
return { success: true, message: 'Email sent successfully.' }
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

axios is no longer used in this file, and response is assigned but never read. This will typically fail linting/CI in Vue projects; remove the unused import/variable or use the callable response (e.g., for error messages).

Copilot uses AI. Check for mistakes.
// Use Firebase SDK callable function instead of HTTP
// Firebase callable wraps the argument automatically, so pass payload directly
const sendEmailFunction = httpsCallable(fbFunctions, 'sendEmail')
const response = await sendEmailFunction(payload)
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused variable response.

Suggested change
const response = await sendEmailFunction(payload)
await sendEmailFunction(payload)

Copilot uses AI. Check for mistakes.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings February 8, 2026 08:53
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.

Comments suppressed due to low confidence (1)

functions/src/https/email.js:74

  • On error, this callable returns the raw err object instead of throwing an HttpsError. With httpsCallable, returning an error object will often serialize poorly and the client may treat the call as a success. Throw an HttpsError (and avoid leaking internal SMTP errors) so callers can reliably handle failures.
    } catch (err) {
      logger.error('Error sending email:', { error: err });
      return err;
    }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 6 to 10
export const sendEmail = functions.onCall({
handler: async (data) => {
const content = data.data;
// Firebase callable passes the argument directly
const content = data.data || data;

Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Firebase Functions v2 onCall, the handler receives a request object with a .data field. Using const content = data.data || data can set content to the full request object when .data is missing/empty, which will break later reads like content.to/content.template. Use const content = data.data (and validate required fields) instead of falling back to the request object.

Copilot uses AI. Check for mistakes.
Comment on lines 270 to 285
},

mounted() {
// Ensure user is logged in
onAuthStateChanged(auth, (user) => {
if (!user) {
this.$router.push('/signin')
return
}
this.initializeVerification()
})
},

beforeUnmount() {
if (this.verificationCheckInterval) {
clearInterval(this.verificationCheckInterval)
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

onAuthStateChanged returns an unsubscribe function, but it isn't captured/cleaned up. Since this component also starts an interval, leaving the auth listener active after unmount can cause memory leaks and unexpected callbacks; store the unsubscribe and call it in beforeUnmount() (alongside clearing the interval).

Copilot uses AI. Check for mistakes.
Comment on lines +191 to +196

// Show success message using Vuex toast
this.$store.commit('SET_TOAST', {
message: this.$t('auth.verificationEmailSent'),
type: 'success',
})
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resendVerificationEmail() commits a success toast after dispatching sendVerificationEmail, but the Vuex action already commits its own success toast. This will likely show duplicate notifications; keep the toast in one place (store or view).

Suggested change
// Show success message using Vuex toast
this.$store.commit('SET_TOAST', {
message: this.$t('auth.verificationEmailSent'),
type: 'success',
})

Copilot uses AI. Check for mistakes.
Comment on lines +151 to +153
computed: {
...mapState({
currentUser: state => state.user,
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mapState('Auth', ['currentUser']) maps a currentUser state field that doesn't exist in the Auth module (it uses state.user). This computed prop is also unused in the component; remove it or map the correct field to avoid confusion.

Copilot uses AI. Check for mistakes.
… verification redirects. Prevents authenticated users from seeing signin page after refresh.

Signed-off-by: Rakshit Yadav <yadavrakshit60@gmail.com>
@github-actions github-actions bot added size/XL and removed size/L labels Feb 8, 2026
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings February 8, 2026 09:13
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 7 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 283 to 287
beforeUnmount() {
if (this.verificationCheckInterval) {
clearInterval(this.verificationCheckInterval)
}
},
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

beforeUnmount() clears the verification polling interval, but the onAuthStateChanged listener created in mounted() is never unsubscribed. Store the unsubscribe function and invoke it here to avoid leaking listeners across navigations.

Copilot uses AI. Check for mistakes.
@@ -2,11 +2,11 @@ import { admin, functions } from "../f.firebase.js";
import nodemailer from "nodemailer";
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logger is referenced later in this file but is not imported anymore, which will throw ReferenceError: logger is not defined at runtime and break email sending. Re-add import logger from "../utils/logger.js"; (as done in other functions files) or remove/replace the logging calls.

Suggested change
import nodemailer from "nodemailer";
import nodemailer from "nodemailer";
import logger from "../utils/logger.js";

Copilot uses AI. Check for mistakes.
// Use Firebase SDK callable function instead of HTTP
// Firebase callable wraps the argument automatically, so pass payload directly
const sendEmailFunction = httpsCallable(fbFunctions, 'sendEmail')
const response = await sendEmailFunction(payload)
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The callable result is not inspected; send() always returns { success: true } after awaiting the function. If the backend returns an error payload (or you later return a structured response), this will still report success. Use response.data (or rely on the function throwing HttpsError) to determine success/failure.

Suggested change
const response = await sendEmailFunction(payload)
const response = await sendEmailFunction(payload)
const data = response && response.data ? response.data : null
// If the backend returns a structured result (e.g. { success, message }),
// respect it; otherwise fall back to the default success response.
if (data && typeof data.success === 'boolean') {
return {
success: data.success,
message: typeof data.message === 'string'
? data.message
: data.success
? 'Email sent successfully.'
: 'Failed to send email.'
}
}

Copilot uses AI. Check for mistakes.
Comment on lines +40 to +44
"verifyEmailTitle": "Verify Your Email",
"verifyEmailSubtitle": "We've sent a verification link to your email",
"emailLabel": "Email",
"verifyingEmail": "Verifying...",
"checkEmailTitle": "Check Your Email",
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The auth store references i18n keys that are not defined in this file (e.g., auth.emailNotVerified and auth.errorSendingVerification). Add these translations under the auth section so users don’t see raw keys in toasts.

Copilot uses AI. Check for mistakes.
Comment on lines 272 to 276
mounted() {
// Ensure user is logged in
onAuthStateChanged(auth, (user) => {
if (!user) {
this.$router.push('/signin')
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

onAuthStateChanged returns an unsubscribe function, but it is not stored. If the user navigates away/back, this can accumulate listeners and duplicate callbacks. Capture the unsubscribe returned by onAuthStateChanged in mounted() and call it from beforeUnmount().

Copilot uses AI. Check for mistakes.
return 'Email sent successfully.';
} catch (err) {
logger.error('Error sending email:', { error: err });
return err;
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Cloud Function returns err from the catch block instead of throwing an HttpsError. That means the callable can resolve successfully even when sending fails, and the frontend may show success incorrectly. Prefer throwing functions.https.HttpsError (or returning a structured { success: false, message } and handling it client-side).

Suggested change
return err;
throw new functions.https.HttpsError(
"internal",
"Failed to send email.",
err
);

Copilot uses AI. Check for mistakes.

// Special case: /verify-email only accessible if user email not verified
if (to.path === '/verify-email') {
if (!user || (user && user.emailVerified === true)) {
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This use of variable 'user' always evaluates to true.

Suggested change
if (!user || (user && user.emailVerified === true)) {
if (!user || user.emailVerified === true) {

Copilot uses AI. Check for mistakes.
Refactor router logic to allow access to public pages for logged-in users with unverified emails.
@github-actions github-actions bot added size/L and removed size/XL labels Feb 9, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 21 out of 21 changed files in this pull request and generated 10 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

htmlTemplate = fs.readFileSync(templatePath, "utf-8");
htmlTemplate = htmlTemplate
.replace("{{verificationLink}}", link)
.replace("{{userName}}", content.data.userName || 'User');
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential null/undefined access issue: If content.data is undefined, accessing content.data.userName will throw an error. Consider adding a null check like content.data?.userName or (content.data && content.data.userName) to make this more defensive.

Suggested change
.replace("{{userName}}", content.data.userName || 'User');
.replace("{{userName}}", content.data?.userName || 'User');

Copilot uses AI. Check for mistakes.
Comment on lines +154 to +169
async sendVerificationEmail(email, userName) {
try {
const emailController = new EmailController()
await emailController.send({
to: email,
subject: 'Verify Your Email Address',
template: 'emailVerification',
data: {
userName: userName || email,
},
})
} catch (err) {
console.error('Error sending verification email:', err)
throw err
}
}
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new sendVerificationEmail method lacks test coverage. Since this codebase has comprehensive test coverage for AuthController methods (as seen in tests/unit/AuthController.spec.js), a test should be added for this new method to ensure it correctly calls EmailController with the proper parameters and handles errors appropriately.

Copilot uses AI. Check for mistakes.
Comment on lines +211 to +213
async updateEmail() {
try {
if (!this.newEmail || !this.newEmail.includes('@')) {
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The email validation is too simplistic. Using !this.newEmail.includes('@') only checks for the presence of an @ symbol, which will allow invalid emails like '@example.com', 'test@', or 'test@@example'. Consider using a proper email validation regex or the browser's built-in email validation through the input type attribute, which is already set to type="email".

Suggested change
async updateEmail() {
try {
if (!this.newEmail || !this.newEmail.includes('@')) {
isValidEmail(email) {
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
return emailPattern.test(email)
},
async updateEmail() {
try {
if (!this.isValidEmail(this.newEmail)) {

Copilot uses AI. Check for mistakes.
await authController.sendVerificationEmail(user.email, user.email)
} catch (emailErr) {
console.warn('Failed to send verification email:', emailErr)
// Don't fail signup if email sending fails
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the signup flow, when sending the verification email fails, the error is only logged with a console.warn and doesn't propagate to the user. While the comment says "Don't fail signup if email sending fails", users should still be notified that the verification email wasn't sent so they can request a resend. Consider showing a toast notification to inform the user.

Suggested change
// Don't fail signup if email sending fails
// Don't fail signup if email sending fails, but inform the user
showError(i18n.global.t('auth.verificationEmailFailed'))

Copilot uses AI. Check for mistakes.

async logout() {
try {
await this.logout()
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method has an infinite recursion bug. The method is calling itself (await this.logout()) instead of calling the Vuex action. Since logout is already mapped from Vuex actions using ...mapActions(['sendVerificationEmail', 'logout']), this should directly call the action without the await and this prefix, or you should rename this method to avoid the conflict.

Suggested change
await this.logout()
await this.$store.dispatch('logout')

Copilot uses AI. Check for mistakes.
// Check if email is verified
if (!user.emailVerified) {
commit('SET_TOAST', {
message: i18n.global.t('auth.emailNotVerified'),
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The translation key 'auth.emailNotVerified' is used in the code but is not defined in the locale file. This will cause the translation to display the key itself instead of the translated message. Add this key to the locale file with an appropriate message like "Please verify your email address to continue".

Suggested change
message: i18n.global.t('auth.emailNotVerified'),
message: 'Please verify your email address to continue',

Copilot uses AI. Check for mistakes.
})
} catch (err) {
commit('SET_TOAST', {
message: i18n.global.t('auth.errorSendingVerification'),
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The translation key 'auth.errorSendingVerification' is used but not defined in the locale file. Add this key with an appropriate error message like "Failed to send verification email. Please try again later."

Suggested change
message: i18n.global.t('auth.errorSendingVerification'),
message: i18n.global.t('errors.globalError'),

Copilot uses AI. Check for mistakes.
Comment on lines +172 to +174
if (err.message === 'EMAIL_NOT_VERIFIED') {
throw err
}
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error handling for the EMAIL_NOT_VERIFIED case is added here for Google OAuth, but the actual email verification check is missing in the sign-in flow above. Google OAuth users typically have pre-verified emails, but there's no explicit check to ensure they don't need email verification. This creates an inconsistency with the regular sign-in flow where email verification is enforced.

Copilot uses AI. Check for mistakes.
Comment on lines 179 to 191
const onGoogleSignInSuccess = async () => {
if (store.getters.user) router.push('/admin')
store.commit('setLoading', false)
try {
if (store.getters.user) router.push('/admin')
} catch (error) {
if (error.message === 'EMAIL_NOT_VERIFIED') {
await router.push('/verify-email')
return
}
throw error
} finally {
store.commit('setLoading', false)
}
}
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error handling for EMAIL_NOT_VERIFIED is present but the actual signInWithGoogle action is never called in this function. The code just checks if a user exists and redirects, but doesn't trigger authentication. This function should dispatch the signInWithGoogle action and await its completion before checking the user state.

Copilot uses AI. Check for mistakes.
Comment on lines +69 to 72
logger.info('Email sent successfully to', { to: content.to });
return 'Email sent successfully.';
} catch (err) {
logger.error('Error sending email:', { error: err });
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logger import is missing from this file. The code uses logger.info and logger.error but doesn't import the logger module. Add import logger from "../utils/logger.js"; at the top of the file to fix this issue.

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings February 13, 2026 13:37
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 21 out of 21 changed files in this pull request and generated 12 comments.

Comments suppressed due to low confidence (1)

src/features/auth/store/Auth.js:180

  • Consider adding explicit documentation or comment explaining that Google sign-in users bypass email verification. While Google accounts are typically verified, it's good practice to document this assumption. Also consider checking if the Google user's email is verified using the Firebase user.emailVerified property, as Firebase provides this information even for Google sign-ins.
    async signInWithGoogle({ commit }, payload) {
      try {
        const { user } = await authController.signInWithGoogle(
          payload.rememberMe,
        )

        // Check if user already exists in database
        let dbUser = null
        try {
          dbUser = await userController.getById(user.uid)
        } catch (error) {
          // User doesn't exist in DB, will be created below
          return error
        }

        // Create user if they don't exist yet
        if (!dbUser || !dbUser.email) {
          await userController.create({
            id: user.uid,
            email: user.email,
            displayName: user.displayName || '',
            profileImage: user.photoURL || '',
            createdAt: new Date().toISOString(),
            authProvider: 'google',
          })
          dbUser = await userController.getById(user.uid)
        }

        commit('SET_USER', dbUser)
        commit('SET_TOAST', {
          message: i18n.global.t('auth.loginSuccess'),
          type: 'success',
        })
      } catch (err) {
        if (err.message === 'EMAIL_NOT_VERIFIED') {
          throw err
        }
        commit('SET_TOAST', {
          message: i18n.global.t('errors.globalError'),
          type: 'error',
        })
        throw err
      }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

"user": "用户",
"id": "ID",
"itemsPerPage": "每页项目数:",
"saving": "保存中..."
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing comma after line 141. This will cause a JSON parsing error. Add a comma after "saving": "保存中..." on line 141.

Suggested change
"saving": "保存中..."
"saving": "保存中...",

Copilot uses AI. Check for mistakes.
"user": "Пользователь",
"id": "ID",
"itemsPerPage": "Элементов на странице:",
"saving": "Сохранение..."
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing comma after line 141. This will cause a JSON parsing error. Add a comma after "saving": "Сохранение..." on line 141.

Suggested change
"saving": "Сохранение..."
"saving": "Сохранение...",

Copilot uses AI. Check for mistakes.
"user": "Usuário",
"id": "ID",
"itemsPerPage": "Itens por página:",
"saving": "Salvando..."
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing comma after line 141. This will cause a JSON parsing error. Add a comma after "saving": "Salvando..." on line 141.

Suggested change
"saving": "Salvando..."
"saving": "Salvando...",

Copilot uses AI. Check for mistakes.
"user": "Benutzer",
"id": "ID",
"itemsPerPage": "Elemente pro Seite:",
"saving": "Wird gespeichert..."
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing comma after line 141. This will cause a JSON parsing error. Add a comma after "saving": "Wird gespeichert..." on line 141.

Suggested change
"saving": "Wird gespeichert..."
"saving": "Wird gespeichert...",

Copilot uses AI. Check for mistakes.
Comment on lines 44 to +48
if (!user) {
await store.dispatch('autoSignIn')
const authUser = await store.dispatch('autoSignIn')
user = store.state.Auth.user
// If user is logged in but email not verified, redirect to verify-email
if (authUser && authUser.emailVerified === false && !publicPages.includes(to.path)) {
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential logic issue: When an unverified user is auto-signed in, the autoSignIn action returns the Firebase user object but doesn't commit it to the store. In the router guard (line 46), 'user' is then reassigned from 'store.state.Auth.user', which will be null. The check on line 48 for 'authUser.emailVerified === false' will work, but subsequent checks that rely on 'user' being set may fail. Consider if this is the intended behavior or if there should be a separate state variable to track unverified users.

Copilot uses AI. Check for mistakes.
Comment on lines 86 to 139
.link-text {
font-size: 13px;
color: #666;
margin-top: 20px;
word-break: break-all;
background-color: #f8f9fa;
padding: 12px;
border-radius: 4px;
border-left: 3px solid #1e3a8a;
}
.link-text strong {
display: block;
margin-bottom: 8px;
color: #1e3a8a;
font-weight: 600;
}
.expiry {
background-color: #eff6ff;
padding: 15px;
border-left: 4px solid #1e3a8a;
margin: 30px 0;
font-size: 14px;
color: #1e3a8a;
border-radius: 4px;
}
.expiry strong {
color: #1e3a8a;
font-weight: 600;
}
.expiry p {
margin-top: 8px;
color: #334155;
}
.footer {
background-color: #f8f9fa;
padding: 20px 30px;
text-align: center;
font-size: 12px;
color: #666;
border-top: 1px solid #e0e7ff;
}
.footer a {
color: #1e3a8a;
text-decoration: none;
font-weight: 500;
}
.footer a:hover {
text-decoration: underline;
}
.divider {
height: 1px;
background-color: #e0e7ff;
margin: 30px 0;
}
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused CSS styles: The template defines styles for '.link-text', '.expiry', and '.divider' classes (lines 86-139) but these elements are not present in the HTML. Consider removing these unused styles to keep the code clean.

Copilot uses AI. Check for mistakes.
Comment on lines +213 to +215
if (!this.newEmail || !this.newEmail.includes('@')) {
this.modalError = this.$t('auth.invalidEmail')
return
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Weak email validation: The email validation on line 213 only checks if the email contains an '@' symbol. This is insufficient and will accept invalid emails like '@example' or 'test@@example.com'. Consider using a proper email validation regex or a validation library to ensure the email format is valid.

Copilot uses AI. Check for mistakes.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings February 13, 2026 13:48
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
rakshityadav1868 and others added 2 commits February 13, 2026 19:18
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 21 out of 21 changed files in this pull request and generated 8 comments.

Comments suppressed due to low confidence (1)

src/features/auth/store/Auth.js:180

  • Google sign-in users are not checked for email verification, but they should be auto-verified since Google already verifies emails. However, the code in SignInView.vue tries to catch 'EMAIL_NOT_VERIFIED' error for Google sign-in (lines 183-186), but this error will never be thrown by the signInWithGoogle action, potentially causing confusion. Google-authenticated users typically have their emailVerified property set to true by Firebase, but there's no explicit check or handling for this in the auth flow.
    async signInWithGoogle({ commit }, payload) {
      try {
        const { user } = await authController.signInWithGoogle(
          payload.rememberMe,
        )

        // Check if user already exists in database
        let dbUser = null
        try {
          dbUser = await userController.getById(user.uid)
        } catch (error) {
          // User doesn't exist in DB, will be created below
          return error
        }

        // Create user if they don't exist yet
        if (!dbUser || !dbUser.email) {
          await userController.create({
            id: user.uid,
            email: user.email,
            displayName: user.displayName || '',
            profileImage: user.photoURL || '',
            createdAt: new Date().toISOString(),
            authProvider: 'google',
          })
          dbUser = await userController.getById(user.uid)
        }

        commit('SET_USER', dbUser)
        commit('SET_TOAST', {
          message: i18n.global.t('auth.loginSuccess'),
          type: 'success',
        })
      } catch (err) {
        if (err.message === 'EMAIL_NOT_VERIFIED') {
          throw err
        }
        commit('SET_TOAST', {
          message: i18n.global.t('errors.globalError'),
          type: 'error',
        })
        throw err
      }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


<!-- Verify Button -->
<div class="button-container">
<a href="{{verificationLink}}" class="verify-button" style="color: white !important;">Verify Email Address</a>
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The email verification implementation is incomplete. When users click the verification link in the email, there's no code to handle the action code and actually verify the email. Firebase generates an email verification link with an action code, but the app needs to use Firebase's applyActionCode or handle the link properly to complete the verification. The current implementation only redirects to /verify-email, but doesn't process the verification action code from the link parameters.

Copilot uses AI. Check for mistakes.
"user": "Пользователь",
"id": "ID",
"itemsPerPage": "Элементов на странице:",
"saving": "Сохранение..."
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing comma after the "saving" translation entry. This will cause a JSON parsing error and break the application's internationalization for this locale.

Suggested change
"saving": "Сохранение..."
"saving": "Сохранение...",

Copilot uses AI. Check for mistakes.
"user": "مستخدم",
"id": "ID",
"itemsPerPage": "عدد العناصر في الصفحة:",
"saving": "جاري الحفظ...",
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing comma after the "saving" translation entry. This will cause a JSON parsing error and break the application's internationalization for this locale.

Copilot uses AI. Check for mistakes.
Comment on lines 44 to +51
if (!user) {
await store.dispatch('autoSignIn')
const authUser = await store.dispatch('autoSignIn')
user = store.state.Auth.user
// If user is logged in but email not verified, redirect to verify-email
if (authUser && authUser.emailVerified === false && !publicPages.includes(to.path)) {
return next('/verify-email')
}
}
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing closing brace for the if statement. The if statement starting at line 44 is not properly closed, causing a syntax error.

Copilot uses AI. Check for mistakes.
Comment on lines +48 to +51
if (authUser && authUser.emailVerified === false && !publicPages.includes(to.path)) {
return next('/verify-email')
}
}
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code checks publicPages.includes(to.path) after already executing return next() on line 41 for public pages. This means the condition on line 48 will never be true for public pages, creating redundant and potentially confusing logic. The router guard should be restructured to avoid this redundancy.

Suggested change
if (authUser && authUser.emailVerified === false && !publicPages.includes(to.path)) {
return next('/verify-email')
}
}
if (authUser && authUser.emailVerified === false) {
return next('/verify-email')
}
}

Copilot uses AI. Check for mistakes.
Comment on lines +247 to +254
async logout() {
try {
await this.logout()
this.$router.push('/signin')
} catch (err) {
console.error('Error signing out:', err)
}
},
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recursive call to the same method. The method calls this.logout() which is mapped to the Vuex action logout, but then inside a try-catch it calls await this.logout() again. This will cause an infinite recursion. The method should simply call the Vuex action once: await this.logout().

Copilot uses AI. Check for mistakes.
"user": "用户",
"id": "ID",
"itemsPerPage": "每页项目数:",
"saving": "保存中..."
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing comma after the "saving" translation entry. This will cause a JSON parsing error and break the application's internationalization for this locale.

Suggested change
"saving": "保存中..."
"saving": "保存中...",

Copilot uses AI. Check for mistakes.
"user": "ユーザー",
"id": "ID",
"itemsPerPage": "ページごとの項目数:",
"saving": "保存中...",
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing comma after the "saving" translation entry. This will cause a JSON parsing error and break the application's internationalization for this locale.

Copilot uses AI. Check for mistakes.
Signed-off-by: Rakshit Yadav <yadavrakshit60@gmail.com>
Signed-off-by: Rakshit Yadav <yadavrakshit60@gmail.com>
Copilot AI review requested due to automatic review settings February 13, 2026 14:05
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 21 out of 21 changed files in this pull request and generated 19 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 1 to 5
import { admin, functions } from "../f.firebase.js";
import nodemailer from "nodemailer";
import * as fs from "fs";
import * as path from "path";
import logger from "../utils/logger.js";

Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logger import was removed but logger is still being used on lines 69 and 72. This will cause a ReferenceError at runtime. The logger import should be restored: import logger from "../utils/logger.js";

Copilot uses AI. Check for mistakes.

async updateEmail() {
try {
if (!this.newEmail || !this.newEmail.includes('@')) {
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The email validation is too weak. Simply checking if the email contains '@' using .includes('@') is insufficient. This allows invalid emails like 'test@' or '@test' to pass. Use a proper email validation regex or Firebase's email validation to ensure the email format is correct before attempting to update.

Suggested change
if (!this.newEmail || !this.newEmail.includes('@')) {
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
if (!this.newEmail || !emailPattern.test(this.newEmail)) {

Copilot uses AI. Check for mistakes.
Comment on lines 47 to 72
"emailNotVerified": "Email not verified",
"verificationEmailSent": "Verification email sent successfully!",
"errorSendingVerification": "Error sending verification email",
"verifyEmailTitle": "Verify Your Email",
"verifyEmailSubtitle": "We've sent a verification link to your email",
"emailLabel": "Email",
"verifyingEmail": "Verifying...",
"emailVerified": "Email verified successfully!",
"checkEmailTitle": "Check Your Email",
"checkEmailStep1": "Check your inbox for a verification email",
"checkEmailStep2": "Click the link in the email to verify your account",
"checkEmailStep3": "Return here and your account will be verified",
"checkSpamFolder": "Don't see it? Check your spam folder",
"resending": "Resending...",
"resendEmail": "Resend Email",
"changeEmail": "Change Email",
"continueToDashboard": "Continue to Dashboard",
"signOut": "Sign Out",
"newEmail": "New Email",
"enterNewEmail": "Enter your new email address",
"saving": "Saving...",
"errorCheckingVerification": "Error checking verification status",
"errorResendingEmail": "Error resending verification email",
"invalidEmail": "Invalid email address",
"emailUpdatedVerification": "Email updated! We've sent a verification link to your new email.",
"errorUpdatingEmail": "Error updating email"
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All new email verification translations are in English instead of Portuguese (Brazilian). These should be translated to Portuguese for proper internationalization support.

Suggested change
"emailNotVerified": "Email not verified",
"verificationEmailSent": "Verification email sent successfully!",
"errorSendingVerification": "Error sending verification email",
"verifyEmailTitle": "Verify Your Email",
"verifyEmailSubtitle": "We've sent a verification link to your email",
"emailLabel": "Email",
"verifyingEmail": "Verifying...",
"emailVerified": "Email verified successfully!",
"checkEmailTitle": "Check Your Email",
"checkEmailStep1": "Check your inbox for a verification email",
"checkEmailStep2": "Click the link in the email to verify your account",
"checkEmailStep3": "Return here and your account will be verified",
"checkSpamFolder": "Don't see it? Check your spam folder",
"resending": "Resending...",
"resendEmail": "Resend Email",
"changeEmail": "Change Email",
"continueToDashboard": "Continue to Dashboard",
"signOut": "Sign Out",
"newEmail": "New Email",
"enterNewEmail": "Enter your new email address",
"saving": "Saving...",
"errorCheckingVerification": "Error checking verification status",
"errorResendingEmail": "Error resending verification email",
"invalidEmail": "Invalid email address",
"emailUpdatedVerification": "Email updated! We've sent a verification link to your new email.",
"errorUpdatingEmail": "Error updating email"
"emailNotVerified": "E-mail não verificado",
"verificationEmailSent": "E-mail de verificação enviado com sucesso!",
"errorSendingVerification": "Erro ao enviar e-mail de verificação",
"verifyEmailTitle": "Verifique seu e-mail",
"verifyEmailSubtitle": "Enviamos um link de verificação para o seu e-mail",
"emailLabel": "E-mail",
"verifyingEmail": "Verificando...",
"emailVerified": "E-mail verificado com sucesso!",
"checkEmailTitle": "Verifique seu e-mail",
"checkEmailStep1": "Verifique sua caixa de entrada para encontrar o e-mail de verificação",
"checkEmailStep2": "Clique no link do e-mail para verificar sua conta",
"checkEmailStep3": "Volte aqui e sua conta será verificada",
"checkSpamFolder": "Não encontrou? Verifique a pasta de spam",
"resending": "Reenviando...",
"resendEmail": "Reenviar e-mail",
"changeEmail": "Alterar e-mail",
"continueToDashboard": "Continuar para o painel",
"signOut": "Sair",
"newEmail": "Novo e-mail",
"enterNewEmail": "Digite seu novo endereço de e-mail",
"saving": "Salvando...",
"errorCheckingVerification": "Erro ao verificar o status da verificação",
"errorResendingEmail": "Erro ao reenviar o e-mail de verificação",
"invalidEmail": "Endereço de e-mail inválido",
"emailUpdatedVerification": "E-mail atualizado! Enviamos um link de verificação para o seu novo e-mail.",
"errorUpdatingEmail": "Erro ao atualizar o e-mail"

Copilot uses AI. Check for mistakes.
Comment on lines 47 to 72
"emailNotVerified": "Email not verified",
"verificationEmailSent": "Verification email sent successfully!",
"errorSendingVerification": "Error sending verification email",
"verifyEmailTitle": "Verify Your Email",
"verifyEmailSubtitle": "We've sent a verification link to your email",
"emailLabel": "Email",
"verifyingEmail": "Verifying...",
"emailVerified": "Email verified successfully!",
"checkEmailTitle": "Check Your Email",
"checkEmailStep1": "Check your inbox for a verification email",
"checkEmailStep2": "Click the link in the email to verify your account",
"checkEmailStep3": "Return here and your account will be verified",
"checkSpamFolder": "Don't see it? Check your spam folder",
"resending": "Resending...",
"resendEmail": "Resend Email",
"changeEmail": "Change Email",
"continueToDashboard": "Continue to Dashboard",
"signOut": "Sign Out",
"newEmail": "New Email",
"enterNewEmail": "Enter your new email address",
"saving": "Saving...",
"errorCheckingVerification": "Error checking verification status",
"errorResendingEmail": "Error resending verification email",
"invalidEmail": "Invalid email address",
"emailUpdatedVerification": "Email updated! We've sent a verification link to your new email.",
"errorUpdatingEmail": "Error updating email"
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All new email verification translations are in English instead of Japanese. These should be translated to Japanese for proper internationalization support.

Suggested change
"emailNotVerified": "Email not verified",
"verificationEmailSent": "Verification email sent successfully!",
"errorSendingVerification": "Error sending verification email",
"verifyEmailTitle": "Verify Your Email",
"verifyEmailSubtitle": "We've sent a verification link to your email",
"emailLabel": "Email",
"verifyingEmail": "Verifying...",
"emailVerified": "Email verified successfully!",
"checkEmailTitle": "Check Your Email",
"checkEmailStep1": "Check your inbox for a verification email",
"checkEmailStep2": "Click the link in the email to verify your account",
"checkEmailStep3": "Return here and your account will be verified",
"checkSpamFolder": "Don't see it? Check your spam folder",
"resending": "Resending...",
"resendEmail": "Resend Email",
"changeEmail": "Change Email",
"continueToDashboard": "Continue to Dashboard",
"signOut": "Sign Out",
"newEmail": "New Email",
"enterNewEmail": "Enter your new email address",
"saving": "Saving...",
"errorCheckingVerification": "Error checking verification status",
"errorResendingEmail": "Error resending verification email",
"invalidEmail": "Invalid email address",
"emailUpdatedVerification": "Email updated! We've sent a verification link to your new email.",
"errorUpdatingEmail": "Error updating email"
"emailNotVerified": "メールが確認されていません",
"verificationEmailSent": "確認メールを送信しました。",
"errorSendingVerification": "確認メールの送信中にエラーが発生しました",
"verifyEmailTitle": "メールアドレスを確認してください",
"verifyEmailSubtitle": "確認用リンクを記載したメールを送信しました。",
"emailLabel": "メールアドレス",
"verifyingEmail": "確認しています...",
"emailVerified": "メールアドレスの確認が完了しました。",
"checkEmailTitle": "メールを確認してください",
"checkEmailStep1": "受信トレイに確認メールが届いているか確認してください",
"checkEmailStep2": "メール内のリンクをクリックしてアカウントを認証してください",
"checkEmailStep3": "この画面に戻ると、アカウントの確認が完了します",
"checkSpamFolder": "届かない場合は、迷惑メールフォルダもご確認ください",
"resending": "再送信しています...",
"resendEmail": "メールを再送信",
"changeEmail": "メールアドレスを変更",
"continueToDashboard": "ダッシュボードへ進む",
"signOut": "サインアウト",
"newEmail": "新しいメールアドレス",
"enterNewEmail": "新しいメールアドレスを入力してください",
"saving": "保存しています...",
"errorCheckingVerification": "確認ステータスの取得中にエラーが発生しました",
"errorResendingEmail": "確認メールの再送信中にエラーが発生しました",
"invalidEmail": "メールアドレスの形式が正しくありません",
"emailUpdatedVerification": "メールアドレスを更新しました。新しいメールアドレス宛に確認リンクを送信しました。",
"errorUpdatingEmail": "メールアドレスの更新中にエラーが発生しました"

Copilot uses AI. Check for mistakes.
this.modalError = null

// Update email in Firebase
await updateEmail(auth.currentUser, this.newEmail)
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The updateEmail function in Firebase requires recent authentication. Calling updateEmail without recent re-authentication will throw an error (auth/requires-recent-login). Users should be prompted to re-authenticate (enter password or re-login with Google) before changing their email address. This is a Firebase security requirement to prevent unauthorized email changes.

Copilot uses AI. Check for mistakes.
Comment on lines 279 to 282
// Check verification status every 3 seconds
this.verificationCheckInterval = setInterval(() => {
this.checkEmailVerificationStatus()
}, 3000)
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The verification polling interval checks email verification status every 3 seconds indefinitely. This could cause unnecessary Firebase API calls if the user leaves the page open for extended periods without verifying. Consider adding a maximum number of retries or increasing the interval progressively (e.g., 3s, 5s, 10s, 30s) to reduce API load. Also, consider stopping after a certain time period (e.g., 10-15 minutes) and prompting the user to manually refresh.

Suggested change
// Check verification status every 3 seconds
this.verificationCheckInterval = setInterval(() => {
this.checkEmailVerificationStatus()
}, 3000)
// Check verification status with bounded, progressively increasing intervals
const maxDurationMs = 15 * 60 * 1000 // 15 minutes
const startTime = Date.now()
const intervals = [3000, 5000, 10000, 30000]
let attempt = 0
const pollVerificationStatus = () => {
const elapsed = Date.now() - startTime
if (elapsed >= maxDurationMs) {
// Stop polling after max duration to avoid unnecessary API calls
if (this.verificationCheckInterval) {
clearInterval(this.verificationCheckInterval)
}
this.isVerifying = false
return
}
this.checkEmailVerificationStatus()
const delay = intervals[Math.min(attempt, intervals.length - 1)]
attempt += 1
// Use setTimeout instead of setInterval for progressive backoff
this.verificationCheckInterval = setTimeout(pollVerificationStatus, delay)
}
pollVerificationStatus()

Copilot uses AI. Check for mistakes.

async logout() {
try {
await this.logout()
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method is calling itself recursively. Line 265 calls await this.logout() within the logout method, which will result in infinite recursion. This should call the Vuex action instead: await this.$store.dispatch('logout') or map the action differently to avoid the name collision.

Suggested change
await this.logout()
await this.$store.dispatch('logout')

Copilot uses AI. Check for mistakes.
Comment on lines 176 to 190
async checkEmailVerificationStatus() {
try {
this.isVerifying = true
// Reload user to get updated verification status
await auth.currentUser.reload()

if (auth.currentUser.emailVerified) {
this.isVerified = true
this.isVerifying = false
// Clear interval and redirect after 2 seconds
clearInterval(this.verificationCheckInterval)
setTimeout(() => {
this.$router.push('/admin')
}, 2000)
}
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The checkEmailVerificationStatus method doesn't check if auth.currentUser exists before calling .reload(). If the user logs out or their session expires while this is running, it will throw an error trying to access .reload() on null. Add a null check: if (!auth.currentUser) return; at the start of the try block.

Copilot uses AI. Check for mistakes.
Signed-off-by: Rakshit Yadav <yadavrakshit60@gmail.com>
Copy link
Member

@KarinePistili KarinePistili left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello I've tested it and I have the following feedback:

  1. There is a not desired loop here which keeps making requests to identity toolikit and making the page to rerender. It would be better to just make a button like "Already verified" and check there to prevent this behaviour.
  2. For colors, make sure to use the same ones as ruxailab logo. For example, the blue is #00213F
  3. I received the email, take a look at the colors at the template as well.
  4. After verifying the email, the ideal flow would be to redirect the user to dashboard and not to redo the signin. Can you update to be like this?

Signed-off-by: Rakshit Yadav <yadavrakshit60@gmail.com>
Copilot AI review requested due to automatic review settings February 13, 2026 19:08
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 21 out of 21 changed files in this pull request and generated 18 comments.

Comments suppressed due to low confidence (2)

src/features/auth/store/Auth.js:181

  • Google OAuth users are not subject to email verification checks. The signInWithGoogle action (lines 138-181) doesn't check user.emailVerified before allowing authentication, unlike the regular signin action which checks on line 106. According to the PR description, "Google OAuth users auto-verified and redirect to dashboard", but there's no code actually verifying their email status. This creates an inconsistency where Google users bypass the email verification requirement that regular signup users must complete, which may be a security concern depending on the intended behavior.
    async signInWithGoogle({ commit }, payload) {
      try {
        const { user } = await authController.signInWithGoogle(
          payload.rememberMe,
        )

        // Check if user already exists in database
        let dbUser = null
        try {
          dbUser = await userController.getById(user.uid)
        } catch (error) {
          // User doesn't exist in DB, will be created below
          return error
        }

        // Create user if they don't exist yet
        if (!dbUser || !dbUser.email) {
          await userController.create({
            id: user.uid,
            email: user.email,
            displayName: user.displayName || '',
            profileImage: user.photoURL || '',
            createdAt: new Date().toISOString(),
            authProvider: 'google',
          })
          dbUser = await userController.getById(user.uid)
        }

        commit('SET_USER', dbUser)
        commit('SET_TOAST', {
          message: i18n.global.t('auth.loginSuccess'),
          type: 'success',
        })
      } catch (err) {
        if (err.message === 'EMAIL_NOT_VERIFIED') {
          throw err
        }
        commit('SET_TOAST', {
          message: i18n.global.t('errors.globalError'),
          type: 'error',
        })
        throw err
      }
    },

functions/src/https/email.js:74

  • The error handling in the catch block returns the error object directly rather than throwing it or returning a consistent error response structure. This means the caller receives the raw error object instead of a standardized response format. Earlier in the function, success returns a string message 'Email sent successfully.', but on error it returns the err object itself. This inconsistency makes it difficult for callers to handle responses uniformly. Consider returning a consistent error structure or re-throwing the error.
    } catch (err) {
      logger.error('Error sending email:', { error: err });
      return err;
    }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +247 to +277
async updateEmail() {
try {
if (!this.newEmail || !this.newEmail.includes('@')) {
this.modalError = this.$t('auth.invalidEmail')
return
}

this.isUpdatingEmail = true
this.modalError = null

// Update email in Firebase
await updateEmail(auth.currentUser, this.newEmail)

// Send verification email to new address
const displayName = auth.currentUser.displayName || 'User'
await this.sendVerificationEmail({ email: this.newEmail, userName: displayName })

this.userEmail = this.newEmail
this.showChangeEmailModal = false

this.$store.commit('SET_TOAST', {
message: this.$t('auth.emailUpdatedVerification'),
type: 'success',
})
} catch (err) {
console.error('Error updating email:', err)
this.modalError = err.message || this.$t('auth.errorUpdatingEmail')
} finally {
this.isUpdatingEmail = false
}
},
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updating the email address with updateEmail from Firebase Auth requires recent authentication for security reasons. If the user's session is old, Firebase will throw an error requiring re-authentication (auth/requires-recent-login). This code doesn't handle this case - it should catch this specific error and prompt the user to re-authenticate before allowing email changes. Without this, users with older sessions will encounter cryptic errors when trying to change their email.

Copilot uses AI. Check for mistakes.
Comment on lines +69 to 73
logger.info('Email sent successfully to', { to: content.to });
return 'Email sent successfully.';
} catch (err) {
logger.error('Error sending email:', { error: err });
return err;
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logger import was removed (previously on line 4) but logger is still being used on lines 69 and 72. This will cause a ReferenceError when the email function tries to log messages. The import statement should be restored: import logger from "../utils/logger.js";

Copilot uses AI. Check for mistakes.
Comment on lines 67 to 141
@@ -112,6 +138,7 @@
"user": "उपयोगकर्ता",
"id": "आईडी",
"itemsPerPage": "प्रति पृष्ठ आइटम:",
"saving": "सहेजा जा रहा है...",
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The translation key "saving" is duplicated - it appears both in the "auth" section (line 67) and in the "common" section (line 141). This creates ambiguity and the second definition will override the first. Since the VerifyEmailView.vue component uses $t('common.saving'), only the "common" section needs this key. The duplicate in the "auth" section should be removed.

Copilot uses AI. Check for mistakes.
Comment on lines 67 to 141
@@ -112,6 +138,7 @@
"user": "Usuario",
"id": "ID",
"itemsPerPage": "Elementos por página:",
"saving": "Guardando...",
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The translation key "saving" is duplicated - it appears both in the "auth" section (line 67) and in the "common" section (line 141). This creates ambiguity and the second definition will override the first. Since the VerifyEmailView.vue component uses $t('common.saving'), only the "common" section needs this key. The duplicate in the "auth" section should be removed.

Copilot uses AI. Check for mistakes.
Comment on lines 67 to 141
@@ -112,6 +138,7 @@
"user": "مستخدم",
"id": "ID",
"itemsPerPage": "عدد العناصر في الصفحة:",
"saving": "جاري الحفظ...",
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The translation key "saving" is duplicated - it appears both in the "auth" section (line 67) and in the "common" section (line 141). This creates ambiguity and the second definition will override the first. Since the VerifyEmailView.vue component uses $t('common.saving'), only the "common" section needs this key. The duplicate in the "auth" section should be removed.

Copilot uses AI. Check for mistakes.
Comment on lines +154 to +169
async sendVerificationEmail(email, userName) {
try {
const emailController = new EmailController()
await emailController.send({
to: email,
subject: 'Verify Your Email Address',
template: 'emailVerification',
data: {
userName: userName || email,
},
})
} catch (err) {
console.error('Error sending verification email:', err)
throw err
}
}
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new sendVerificationEmail method added to AuthController lacks test coverage. The AuthController.spec.js file exists and contains tests for other AuthController methods, but doesn't include tests for this new method. Given that this repository has comprehensive test coverage for controllers (as evidenced by the existing AuthController.spec.js with 308 lines), this new method should also have corresponding unit tests to verify its behavior, error handling, and integration with EmailController.

Copilot uses AI. Check for mistakes.
Comment on lines +157 to +176
verificationCheckInterval: null,
}
},
computed: {
...mapState({
currentUser: state => state.user,
}),
},
mounted() {
// Ensure user is logged in
onAuthStateChanged(auth, (user) => {
this.initializeVerification()
})
},

beforeUnmount() {
if (this.verificationCheckInterval) {
clearInterval(this.verificationCheckInterval)
}
},
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The verificationCheckInterval variable is declared and cleared in beforeUnmount, but it's never actually set anywhere in the component. The variable is initialized to null on line 157 and cleared on line 174, but there's no code that creates an interval using setInterval. This appears to be dead code - either the auto-check interval functionality was planned but not implemented, or it was removed but the cleanup code was left behind. This dead code should be removed to avoid confusion.

Copilot uses AI. Check for mistakes.
Comment on lines +196 to +301
setTimeout(() => {
this.$router.push('/admin')
}, 1500)
} else {
this.error = this.$t('auth.emailNotVerified')
this.$store.commit('SET_TOAST', {
message: this.$t('auth.emailNotVerified'),
type: 'warning',
})
}
} catch (err) {
console.error('Error checking email verification:', err)
this.error = this.$t('auth.errorCheckingVerification')
this.$store.commit('SET_TOAST', {
message: this.$t('auth.errorCheckingVerification'),
type: 'error',
})
} finally {
this.isVerifying = false
}
},

async resendVerificationEmail() {
try {
this.isResending = true
this.error = null

const email = auth.currentUser.email
const displayName = auth.currentUser.displayName || 'User'

await this.sendVerificationEmail({ email, userName: displayName })

// Show success message using Vuex toast
this.$store.commit('SET_TOAST', {
message: this.$t('auth.verificationEmailSent'),
type: 'success',
})
} catch (err) {
console.error('Error resending verification email:', err)
this.error = this.$t('auth.errorResendingEmail')
} finally {
this.isResending = false
}
},

changeEmail() {
this.showChangeEmailModal = true
this.newEmail = ''
this.modalError = null
},

async updateEmail() {
try {
if (!this.newEmail || !this.newEmail.includes('@')) {
this.modalError = this.$t('auth.invalidEmail')
return
}

this.isUpdatingEmail = true
this.modalError = null

// Update email in Firebase
await updateEmail(auth.currentUser, this.newEmail)

// Send verification email to new address
const displayName = auth.currentUser.displayName || 'User'
await this.sendVerificationEmail({ email: this.newEmail, userName: displayName })

this.userEmail = this.newEmail
this.showChangeEmailModal = false

this.$store.commit('SET_TOAST', {
message: this.$t('auth.emailUpdatedVerification'),
type: 'success',
})
} catch (err) {
console.error('Error updating email:', err)
this.modalError = err.message || this.$t('auth.errorUpdatingEmail')
} finally {
this.isUpdatingEmail = false
}
},

goToDashboard() {
this.$router.push('/admin')
},

async logout() {
try {
await this.logout()
this.$router.push('/signin')
} catch (err) {
console.error('Error signing out:', err)
}
},

initializeVerification() {
const user = auth.currentUser
if (user) {
this.userEmail = user.email
if (user.emailVerified) {
this.isVerified = true
// Auto-redirect to dashboard after 1.5 seconds
setTimeout(() => {
this.$router.push('/admin')
}, 1500)
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The setTimeout calls on lines 196-198 and 299-301 create timers for auto-redirecting to the dashboard, but these timers are not stored or cleaned up. If the user navigates away from the page or the component unmounts before the timeout completes, the redirect will still execute, potentially causing unexpected navigation. The timeout IDs should be stored (similar to verificationCheckInterval) and cleared in beforeUnmount using clearTimeout to prevent this issue.

Copilot uses AI. Check for mistakes.
Comment on lines 67 to 141
@@ -112,6 +138,7 @@
"user": "Пользователь",
"id": "ID",
"itemsPerPage": "Элементов на странице:",
"saving": "Сохранение...",
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The translation key "saving" is duplicated - it appears both in the "auth" section (line 67) and in the "common" section (line 141). This creates ambiguity and the second definition will override the first. Since the VerifyEmailView.vue component uses $t('common.saving'), only the "common" section needs this key. The duplicate in the "auth" section should be removed.

Copilot uses AI. Check for mistakes.
"user": "ユーザー",
"id": "ID",
"itemsPerPage": "ページごとの項目数:",
"saving": "保存中...",
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The translation key "saving" is duplicated - it appears both in the "auth" section (line 67) and in the "common" section (line 141). This creates ambiguity and the second definition will override the first. Since the VerifyEmailView.vue component uses $t('common.saving'), only the "common" section needs this key. The duplicate in the "auth" section should be removed.

Suggested change
"saving": "保存中...",

Copilot uses AI. Check for mistakes.
@rakshityadav1868
Copy link
Contributor Author

fixed @KarinePistili

  1. Change the colour scheme
  2. fixed the loop of requests for sendEmail
  3. added already verified btn
  4. user redirects to dashboard
    lmk if any changes needed
Screen.Recording.2026-02-14.at.12.43.32.AM.mp4

@sonarqubecloud
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Security Improvement]: Emails are not being verified during sign up.

2 participants