Skip to content

Commit

Permalink
Merge pull request #537 from VitNode/email/send_again_verification_email
Browse files Browse the repository at this point in the history
feat: Send verification email when user trying to sign in without confirm_email
  • Loading branch information
aXenDeveloper authored Sep 28, 2024
2 parents 4044475 + ed3c0b8 commit a19e031
Show file tree
Hide file tree
Showing 6 changed files with 46 additions and 7 deletions.
4 changes: 4 additions & 0 deletions apps/frontend/src/plugins/core/langs/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,10 @@
"error": {
"title": "Invalid credentials",
"desc": "The email address or password was incorrect. Please try again (make sure your caps lock is off)."
},
"not_verified": {
"title": "Email is not verified",
"desc": "Please check your inbox and click the link to activate your account."
}
},
"sign_up": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ export class SendConfirmEmailCoreSessionsService {
// If user has confirm email, delete it
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (user.confirm_email?.id) {
// Skip if email was sent less than 10 minutes ago
if (user.confirm_email.created > new Date(Date.now() - 1000 * 60 * 10)) {
return;
}

await this.databaseService.db
.delete(core_users_confirm_emails)
.where(eq(core_users_confirm_emails.id, user.confirm_email.id));
Expand Down
22 changes: 22 additions & 0 deletions packages/backend/src/core/sessions/sign_in/sign_in.service.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { EmailProvider, getConfigFile } from '@/providers';
import { InternalDatabaseService } from '@/utils/database/internal_database.service';
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
Expand All @@ -8,6 +9,7 @@ import { core_admin_sessions } from '../../../database/schema/admins';
import { core_sessions } from '../../../database/schema/sessions';
import { AccessDeniedError, CustomError } from '../../../errors';
import { GqlContext } from '../../../utils';
import { SendConfirmEmailCoreSessionsService } from '../confirm_email/send.confirm_email.service';
import { verifyPassword } from '../password';
import { DeviceSignInCoreSessionsService } from './device.service';
import { SignInCoreSessionsArgs } from './sign_in.dto';
Expand All @@ -27,6 +29,7 @@ export class SignInCoreSessionsService {
private readonly jwtService: JwtService,
private readonly configService: ConfigService,
private readonly deviceService: DeviceSignInCoreSessionsService,
private readonly sendConfirmEmailCoreSessionsService: SendConfirmEmailCoreSessionsService,
) {}

protected async createSession({
Expand Down Expand Up @@ -205,15 +208,34 @@ export class SignInCoreSessionsService {
{ admin, email: emailRaw, password, remember }: SignInCoreSessionsArgs,
context: GqlContext,
) {
const { settings } = getConfigFile();
const email = emailRaw.toLowerCase();
const user = await this.databaseService.db.query.core_users.findFirst({
where: (table, { eq }) => eq(table.email, email),
with: {
confirm_email: true,
},
});
if (!user) throw new AccessDeniedError();

const validPassword = await verifyPassword(password, user.password);
if (!validPassword) throw new AccessDeniedError();

if (
!user.email_verified &&
settings.authorization.require_confirm_email &&
settings.email.provider !== EmailProvider.none
) {
await this.sendConfirmEmailCoreSessionsService.sendConfirmEmail({
userId: user.id,
});

throw new CustomError({
code: 'EMAIL_NOT_VERIFIED',
message: 'Email not verified',
});
}

// If admin mode is enabled, check if user has access to admin cp
if (admin) {
const accessToAdminCP =
Expand Down
2 changes: 2 additions & 0 deletions packages/frontend/src/hooks/core/sign/up/use-sign-up-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ export const useSignUpView = () => {
shouldFocus: true,
},
);

return;
} else if (mutation.error === 'NAME_ALREADY_EXISTS') {
form.setError(
'name',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { Button } from '@/components/ui/button';
import { useSignInView } from '@/hooks/core/sign/in/use-sign-in-view';
import { AlertCircle, LogIn } from 'lucide-react';
import { AlertCircle, LogIn, MailQuestion } from 'lucide-react';
import { useTranslations } from 'next-intl';
import React from 'react';

Expand All @@ -21,15 +21,21 @@ export const FormSignIn = () => {
<>
{error && (
<div className="mb-6 space-y-4">
{error === 'ACCESS_DENIED' && (
{error === 'EMAIL_NOT_VERIFIED' ? (
<Alert variant="warn">
<MailQuestion className="size-4" />
<AlertTitle>{t('sign_in.not_verified.title')}</AlertTitle>
<AlertDescription>
{t('sign_in.not_verified.desc')}
</AlertDescription>
</Alert>
) : error === 'ACCESS_DENIED' ? (
<Alert variant="error">
<AlertCircle className="size-4" />
<AlertTitle>{t('sign_in.error.title')}</AlertTitle>
<AlertDescription>{t('sign_in.error.desc')}</AlertDescription>
</Alert>
)}

{error !== 'ACCESS_DENIED' && (
) : (
<Alert variant="error">
<AlertCircle className="size-4" />
<AlertTitle>{t('errors.title')}</AlertTitle>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export const ConfirmEmailSignUpView = async ({
}) => {
const t = await getTranslations('core.sign_up.confirm_email');
if (!userId || !token) {
redirect('/');
redirect('/login');

return;
}
Expand All @@ -47,7 +47,7 @@ export const ConfirmEmailSignUpView = async ({
userId: +userId,
});
} catch (_e) {
redirect('/');
redirect('/login');
}

return (
Expand Down

0 comments on commit a19e031

Please sign in to comment.