Skip to content

Commit

Permalink
feat(clientportal): send otp code via mail (#4931)
Browse files Browse the repository at this point in the history
  • Loading branch information
Daarii committed Jan 26, 2024
1 parent 39b3988 commit 975560f
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 6 deletions.
Expand Up @@ -134,7 +134,7 @@ const clientPortalUserMutations = {

const optConfig = clientPortal.otpConfig;

if (args.phoneOtp && optConfig && optConfig.loginWithOTP) {
if (optConfig && optConfig.loginWithOTP) {
return tokenHandler(user, clientPortal, res);
}

Expand Down Expand Up @@ -687,6 +687,124 @@ const clientPortalUserMutations = {
return { userId: user._id, message: 'Sms sent' };
},

clientPortalLoginWithMailOTP: async (
_root,
args: { email: string; clientPortalId: string; deviceToken },
{ models, subdomain, res }: IContext,
) => {
const { email, clientPortalId, deviceToken } = args;
const clientPortal = await models.ClientPortals.getConfig(clientPortalId);

const config = clientPortal.otpConfig || {
content: '',
codeLength: 4,
loginWithOTP: false,
expireAfter: 5,
emailSubject: 'Email verification',
};

if (!config.loginWithOTP) {
throw new Error('Login with OTP is not enabled');
}

const doc = { email };

const user = await models.ClientPortalUsers.loginWithoutPassword(
subdomain,
clientPortal,
doc,
deviceToken,
);

try {
if (clientPortal?.testUserEmail && clientPortal._id) {
const cpUser = await models.ClientPortalUsers.findOne({
firstName: 'test clientportal user',
clientPortalId: clientPortal._id,
});
if (cpUser) {
if (!clientPortal?.testUserOTP) {
throw new Error('Test user email otp not provided!');
}

if (
config.codeLength !== clientPortal?.testUserOTP?.toString().length
) {
throw new Error(
'Client portal otp config and test user otp does not same length!',
);
}

if (
clientPortal?.testUserOTP &&
config.codeLength === clientPortal?.testUserOTP?.toString().length
) {
const testEmailCode =
await models.ClientPortalUsers.imposeVerificationCode({
clientPortalId: clientPortal._id,
codeLength: config.codeLength,
email: clientPortal?.testUserEmail,
expireAfter: config.expireAfter,
testUserOTP: clientPortal?.testUserOTP,
});

const body =
config.content.replace(/{.*}/, testEmailCode) ||
`Your OTP is ${testEmailCode}`;

await sendCoreMessage({
subdomain,
action: 'sendEmail',
data: {
toEmails: [email],
title: config.emailSubject || 'OTP verification',
template: {
name: 'base',
data: {
content: body,
},
},
},
});
}

return { userId: user._id, message: 'sent' };
}
}
} catch (e) {
console.log(e.message);
}

const emailCode = await models.ClientPortalUsers.imposeVerificationCode({
clientPortalId: clientPortal._id,
codeLength: config.codeLength,
email: user.email,
expireAfter: config.expireAfter,
});

if (emailCode) {
const body =
config.content.replace(/{.*}/, emailCode) || `Your OTP is ${emailCode}`;

await sendCoreMessage({
subdomain,
action: 'sendEmail',
data: {
toEmails: [email],
title: config.emailSubject || 'OTP verification',
template: {
name: 'base',
data: {
content: body,
},
},
},
});
}

return { userId: user._id, message: 'Sent' };
},

clientPortalLoginWithSocialPay: async (
_root,
args: { token: string; clientPortalId: string },
Expand Down
Expand Up @@ -171,6 +171,7 @@ export const mutations = () => `
clientPortalUsersVerify(userIds: [String]!, type: String): JSON
clientPortalLogin(login: String!, password: String!, clientPortalId: String!, deviceToken: String): JSON
clientPortalLoginWithPhone(phone: String!, clientPortalId: String!, deviceToken: String): JSON
clientPortalLoginWithMailOTP(email: String!, clientPortalId: String!, deviceToken: String): JSON
clientPortalLoginWithSocialPay(clientPortalId: String!, token: String!) : JSON
clientPortalRefreshToken: String
clientPortalGoogleAuthentication(clientPortalId: String, code: String): JSON
Expand Down
Expand Up @@ -8,7 +8,7 @@ import { generateModels } from '../connectionResolver';
export default async function cpUserMiddleware(
req: Request & { cpUser?: any },
res: Response,
next: NextFunction
next: NextFunction,
) {
const subdomain = getSubdomain(req);
const models = await generateModels(subdomain);
Expand All @@ -28,12 +28,13 @@ export default async function cpUserMiddleware(
'clientPortalLogin',
'clientPortalLogout',
'clientPortalLoginWithPhone',
'clientPortalLoginWithMailOTP',
'clientPortalLoginWithSocialPay',
'clientPortalRegister',
'clientPortalVerifyOTP',
'clientPortalRefreshToken',
'clientPortalGetConfigByDomain',
'clientPortalKnowledgeBaseTopicDetail'
'clientPortalKnowledgeBaseTopicDetail',
].includes(operationName)
) {
return next();
Expand All @@ -53,7 +54,7 @@ export default async function cpUserMiddleware(
// verify user token and retrieve stored user information
const verifyResult: any = jwt.verify(
token,
process.env.JWT_TOKEN_SECRET || ''
process.env.JWT_TOKEN_SECRET || '',
);

const { userId } = verifyResult;
Expand Down
Expand Up @@ -3,6 +3,7 @@ import { Document, Schema } from 'mongoose';
import { field } from './utils';

export interface IOTPConfig {
emailSubject?: any;
content: string;
codeLength: number;
smsTransporterType: string;
Expand Down
18 changes: 16 additions & 2 deletions packages/plugin-clientportal-ui/src/components/forms/Config.tsx
Expand Up @@ -101,6 +101,7 @@ function General({
handleFormChange('otpConfig', null);
} else {
handleFormChange('otpConfig', {
emailSubject: 'OTP verification',
smsTransporterType: '',
codeLength: 4,
content: 'Your verification code is {{ code }}',
Expand Down Expand Up @@ -195,6 +196,7 @@ function General({

const renderOtp = () => {
const obj = otpConfig || {
emailSubject: 'OTP Verification',
content: '',
codeLength: 4,
smsTransporterType: 'messagePro',
Expand Down Expand Up @@ -237,7 +239,7 @@ function General({
};

return (
<CollapseContent title={__('Mobile OTP')} compact={true} open={false}>
<CollapseContent title={__('OTP')} compact={true} open={false}>
<ToggleWrap>
<FormGroup>
<ControlLabel>Enable OTP config</ControlLabel>
Expand Down Expand Up @@ -268,9 +270,21 @@ function General({
onChange={onChangeConfiguration}
/>
</FormGroup>
<FormGroup>
<ControlLabel>emailSubject</ControlLabel>
<p>OTP email subject</p>
<FlexContent>
<FormControl
id="emailSubject"
name="emailSubject"
value={obj.emailSubject}
onChange={handleChange}
/>
</FlexContent>
</FormGroup>
<FormGroup>
<ControlLabel required={true}>Content</ControlLabel>
<p>OTP message body</p>
<p>OTP body</p>
<FlexContent>
<FormControl
id="content"
Expand Down
1 change: 1 addition & 0 deletions packages/plugin-clientportal-ui/src/types.ts
Expand Up @@ -4,6 +4,7 @@ import { ICustomer } from '@erxes/ui-contacts/src/customers/types';
import { QueryResponse } from '@erxes/ui/src/types';

export type OTPConfig = {
emailSubject?: string;
content: string;
smsTransporterType?: '' | 'messagePro';
codeLength: number;
Expand Down

0 comments on commit 975560f

Please sign in to comment.