Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

741 otp #747

Merged
merged 20 commits into from
Feb 9, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 30 additions & 31 deletions packages/backend/src/config/getDeliveryServiceProperties.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,12 @@ describe('ReadDeliveryServiceProperties', () => {
messageTTL: 12345,
sizeLimit: 456,
notificationChannel: [],
smtpHost: 'smtp.host',
smtpPort: 587,
smtpEmail: '[email protected]',
smtpUsername: '[email protected]',
smtpPassword: 'dm312345',
});

expect(config).toStrictEqual({
messageTTL: 12345,
sizeLimit: 456,
notificationChannel: [],
smtpHost: 'smtp.host',
smtpPort: 587,
smtpEmail: '[email protected]',
smtpUsername: '[email protected]',
smtpPassword: 'dm312345',
});
});

Expand All @@ -61,11 +51,6 @@ describe('ReadDeliveryServiceProperties', () => {
},
},
],
smtpHost: 'smtp.host',
smtpPort: 587,
smtpEmail: '[email protected]',
smtpUsername: '[email protected]',
smtpPassword: 'dm312345',
}),
{ encoding: 'utf-8' },
);
Expand All @@ -89,23 +74,28 @@ describe('ReadDeliveryServiceProperties', () => {
},
},
],
smtpHost: 'smtp.host',
smtpPort: 587,
smtpEmail: '[email protected]',
smtpUsername: '[email protected]',
smtpPassword: 'dm312345',
});
});
it('Adds default properties if config.yml is not fully specified', () => {
writeFileSync(
path,
stringify({
messageTTL: 12345,
smtpHost: 'smtp.host',
smtpPort: 587,
smtpEmail: '[email protected]',
smtpUsername: '[email protected]',
smtpPassword: 'dm312345',
notificationChannel: [
{
type: NotificationChannelType.EMAIL,
config: {
host: 'mail.alice.com',
port: 465,
secure: true,
auth: {
user: 'foo',
pass: 'bar',
},
senderAddress: '[email protected]',
},
},
],
}),
{ encoding: 'utf-8' },
);
Expand All @@ -114,12 +104,21 @@ describe('ReadDeliveryServiceProperties', () => {
expect(config).toStrictEqual({
messageTTL: 12345,
sizeLimit: 100000,
notificationChannel: [],
smtpHost: 'smtp.host',
smtpPort: 587,
smtpEmail: '[email protected]',
smtpUsername: '[email protected]',
smtpPassword: 'dm312345',
notificationChannel: [
{
type: NotificationChannelType.EMAIL,
config: {
host: 'mail.alice.com',
port: 465,
secure: true,
auth: {
user: 'foo',
pass: 'bar',
},
senderAddress: '[email protected]',
},
},
],
});
});
});
24 changes: 17 additions & 7 deletions packages/backend/src/config/getDeliveryServiceProperties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,29 @@ import { parse } from 'yaml';
import { existsSync, readFileSync } from 'fs';
import { resolve } from 'path';
import { logInfo, validateSchema } from '@dm3-org/dm3-lib-shared';
import { schema, DeliveryServiceProperties } from '@dm3-org/dm3-lib-delivery';
import {
schema,
DeliveryServiceProperties,
NotificationChannelType,
} from '@dm3-org/dm3-lib-delivery';

const DEFAULT_CONFIG_FILE_PATH = resolve(__dirname, './../config.yml');
const DEFAULT_DELIVERY_SERVICE_PROPERTIES: DeliveryServiceProperties = {
messageTTL: 0,
//100Kb
sizeLimit: 100000,
notificationChannel: [],
smtpHost: '',
smtpPort: 0,
smtpEmail: '',
smtpUsername: '',
smtpPassword: '',
notificationChannel: [
{
type: NotificationChannelType.EMAIL,
config: {
smtpHost: 'smtp.gmail.com',
smtpPort: 587,
smtpEmail: '',
smtpUsername: '',
smtpPassword: '',
},
},
],
};

export function getDeliveryServiceProperties(
Expand Down
24 changes: 15 additions & 9 deletions packages/backend/src/notifications.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,9 @@ describe('Notifications', () => {

const token = await createAuthToken();

const addNewNotificationChannelMock = jest.fn();
const addUsersNotificationChannelMock = jest.fn();
const setOtpMock = jest.fn();

app.locals.db = {
getSession: async (ensName: string) =>
Expand All @@ -265,7 +267,20 @@ describe('Notifications', () => {
getIdEnsName: async (ensName: string) => ensName,
getGlobalNotification: async (ensName: string) =>
Promise.resolve({ isEnabled: true }),
getUsersNotificationChannels: async (ensName: string) =>
Promise.resolve([
{
type: NotificationChannelType.EMAIL,
config: {
recipientValue: '[email protected]',
isEnabled: true,
isVerified: false,
},
},
]),
addNewNotificationChannel: addNewNotificationChannelMock,
addUsersNotificationChannel: addUsersNotificationChannelMock,
setOtp: setOtpMock,
};
app.locals.web3Provider = {
resolveName: async () =>
Expand All @@ -283,15 +298,6 @@ describe('Notifications', () => {
});

expect(status).toBe(200);
expect(addUsersNotificationChannelMock).toHaveBeenCalledWith(
'bob.eth',
{
type: 'EMAIL',
config: {
recipientValue: '[email protected]',
},
},
);
});
});

Expand Down
19 changes: 12 additions & 7 deletions packages/backend/src/notifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { normalizeEnsName } from '@dm3-org/dm3-lib-profile';
import express from 'express';
import { auth } from './utils';
import { validateNotificationChannel } from './validation/notification/notificationChannelValidation';
import { addNewNotificationChannel } from '@dm3-org/dm3-lib-delivery';
import { getDeliveryServiceProperties } from './config/getDeliveryServiceProperties';

// Exporting a function that returns an Express router
export default () => {
Expand Down Expand Up @@ -91,13 +93,16 @@ export default () => {
error: 'Global notifications is off',
});
} else {
// Adding a user's notification channel to the database
await req.app.locals.db.addUsersNotificationChannel(account, {
type: notificationChannelType,
config: {
recipientValue: recipientValue,
},
});
// add new notification channel & send OTP for verification
await addNewNotificationChannel(
notificationChannelType,
recipientValue,
account,
getDeliveryServiceProperties().notificationChannel,
req.app.locals.db.getUsersNotificationChannels,
req.app.locals.db.addUsersNotificationChannel,
req.app.locals.db.setOtp,
);

// Sending a success response
res.sendStatus(200);
Expand Down
17 changes: 17 additions & 0 deletions packages/backend/src/persistance/getDatabase.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import {
Session as DSSession,
IGlobalNotification,
IOtp,
NotificationChannel,
NotificationChannelType,
spamFilter,
} from '@dm3-org/dm3-lib-delivery';
import { EncryptionEnvelop } from '@dm3-org/dm3-lib-messaging';
Expand All @@ -14,6 +16,7 @@ import Notification from './notification';
import Pending from './pending';
import Session from './session';
import Storage from './storage';
import Otp from './otp';

export enum RedisPrefix {
Conversation = 'conversation:',
Expand All @@ -24,6 +27,7 @@ export enum RedisPrefix {
Pending = 'pending:',
NotificationChannel = 'notificationChannel:',
GlobalNotification = 'globalNotification:',
Otp = 'otp:',
}

export async function getRedisClient() {
Expand Down Expand Up @@ -90,6 +94,9 @@ export async function getDatabase(_redis?: Redis): Promise<IDatabase> {
// Global Notification
getGlobalNotification: Notification.getGlobalNotification(redis),
setGlobalNotification: Notification.setGlobalNotification(redis),
// Otp
setOtp: Otp.setOtp(redis),
getOtp: Otp.getOtp(redis),
};
}

Expand Down Expand Up @@ -154,6 +161,16 @@ export interface IDatabase {
ensName: string,
isEnabled: boolean,
) => Promise<void>;
setOtp: (
ensName: string,
otp: string,
channelType: NotificationChannelType,
generatedAt: Date,
) => Promise<void>;
getOtp: (
ensName: string,
channelType: NotificationChannelType,
) => Promise<IOtp | null>;
}

export type Redis = Awaited<ReturnType<typeof getRedisClient>>;
29 changes: 29 additions & 0 deletions packages/backend/src/persistance/otp/getOtp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Redis, RedisPrefix } from '../getDatabase';
import { getIdEnsName } from '../getIdEnsName';
import { IOtp, NotificationChannelType } from '@dm3-org/dm3-lib-delivery';

export function getOtp(redis: Redis) {
return async (
ensName: string,
channelType: NotificationChannelType,
): Promise<IOtp | null> => {
// Fetch all the otp from Redis
const channelOtps = await redis.get(
RedisPrefix.Otp + (await getIdEnsName(redis)(ensName)),
);

if (channelOtps) {
const parsedOtps: IOtp[] = JSON.parse(channelOtps);

// filter the record with specific channel type
const filteredOtp = parsedOtps.filter(
(data) => data.type === channelType,
);

// return if found else null
return filteredOtp.length ? filteredOtp[0] : null;
} else {
return null;
}
};
}
7 changes: 7 additions & 0 deletions packages/backend/src/persistance/otp/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { getOtp } from './getOtp';
import { setOtp } from './setOtp';

export default {
setOtp,
getOtp,
};
63 changes: 63 additions & 0 deletions packages/backend/src/persistance/otp/setOtp.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { NotificationChannelType } from '@dm3-org/dm3-lib-delivery';
import { IDatabase, Redis, getDatabase, getRedisClient } from '../getDatabase';
import winston from 'winston';

const USER_ADDRESS = '0x25A643B6e52864d0eD816F1E43c0CF49C83B8292';

global.logger = winston.createLogger({
transports: [new winston.transports.Console()],
});

describe('Email Verification OTP', () => {
let redisClient: Redis;
let db: IDatabase;

beforeEach(async () => {
redisClient = await getRedisClient();
db = await getDatabase(redisClient);
await redisClient.flushDb();
});

afterEach(async () => {
await redisClient.flushDb();
await redisClient.disconnect();
});

it('Set Email Verification OTP', async () => {
const otp = '19283';
const generatedAt = new Date();

// fetch OTP from Redis
const priorOtp = await db.getOtp(
USER_ADDRESS,
NotificationChannelType.EMAIL,
);

// User's email otp is null initially
expect(priorOtp).toEqual(null);

// set email OTP
await db.setOtp(
USER_ADDRESS,
otp,
NotificationChannelType.EMAIL,
generatedAt,
);

// fetch OTP from Redis after setting OTP
const afterSettingOtp = await db.getOtp(
USER_ADDRESS,
NotificationChannelType.EMAIL,
);

const expectedData = {
generatedAt: generatedAt,
otp: otp,
type: NotificationChannelType.EMAIL,
};

expect(JSON.stringify(afterSettingOtp)).toEqual(
JSON.stringify(expectedData),
);
});
});
Loading