diff --git a/lib/core/users/usecase.ts b/lib/core/users/usecase.ts index e05ca15e..fb719b87 100644 --- a/lib/core/users/usecase.ts +++ b/lib/core/users/usecase.ts @@ -7,8 +7,6 @@ import { EventBus, EventBusEvents } from '../../server/eventBus'; import { FramesRepository } from '../frames/Repository'; import { BasicUser, User } from './User'; -const disposable = require('disposable-email'); - function isEmailValid(email: string) { // RFC 5322 Official Standard // eslint-disable-next-line no-control-regex @@ -144,13 +142,6 @@ export class UsersUsecase { throw new InvalidDataFormatError('Invalid email address provided'); } - const emailDomain = emailMatch[2]; - const allowedEmail = disposable.validate(emailDomain); - - if (!allowedEmail) { - throw new InvalidDataFormatError('Invalid email address provided'); - } - const maybeAlreadyExistentUser = await this.usersRepository.findByEmail(email); const userAlreadyExists = !!maybeAlreadyExistentUser; diff --git a/lib/server/http/users/controller.ts b/lib/server/http/users/controller.ts index 1575906e..8bd5c9fc 100644 --- a/lib/server/http/users/controller.ts +++ b/lib/server/http/users/controller.ts @@ -3,7 +3,6 @@ import { Logger } from 'winston'; import { InvalidDataFormatError, - UserAlreadyExistsError, UserNotFoundError, UsersUsecase } from '../../../core'; @@ -16,29 +15,6 @@ export class HTTPUsersController { private logger: Logger ) {} - async createUser(req: Request<{}, {}, { email?: string, password?: string }, {}>, res: Response) { - if (!req.body || !req.body.email || !req.body.password) { - return res.status(400).send(); - } - - const { email, password } = req.body; - - try { - const user = await this.usersUsecase.createUser(email.toLowerCase().trim(), password); - - res.status(200).send(user); - } catch (err) { - if (err instanceof UserAlreadyExistsError || err instanceof InvalidDataFormatError) { - res.status(400).send({ message: err.message }); - } else { - // TODO: Global middleware for 500 - this.logger.error('Error creating user %s: %s. %s', email, (err as Error).message, (err as Error).stack); - - res.status(500).send({ message: 'Internal Server Error' }); - } - } - } - async resetPassword( req: Request<{ token?: string }, {}, { password?: string }>, res: Response diff --git a/lib/server/http/users/index.ts b/lib/server/http/users/index.ts index 823edd3a..8a787669 100644 --- a/lib/server/http/users/index.ts +++ b/lib/server/http/users/index.ts @@ -7,8 +7,6 @@ export const createUsersHTTPRouter = ( ): Router => { const router = Router(); - router.post('/', controller.createUser.bind(controller)); - router.post('/:id/request-password-reset', controller.requestPasswordReset.bind(controller)); router.post('/confirm-password-reset/:token', controller.resetPassword.bind(controller)); diff --git a/lib/server/routes/users.js b/lib/server/routes/users.js index 31b823a1..d1d53065 100644 --- a/lib/server/routes/users.js +++ b/lib/server/routes/users.js @@ -4,14 +4,11 @@ const Router = require('./index'); const rawbody = require('../middleware/raw-body'); const log = require('../../logger'); const errors = require('../error-types'); -const merge = require('merge'); const inherits = require('util').inherits; const authenticate = require('../middleware/authenticate'); const crypto = require('crypto'); const limiter = require('../limiter').DEFAULTS; const rateLimiter = require('../middleware/rate-limiter'); -const disposable = require('disposable-email'); -const { trackUserActivated } = require('../../analytics'); const url = require('url'); const sendGridMail = require('@sendgrid/mail'); @@ -86,183 +83,6 @@ UsersRouter.prototype._dispatchActivationEmail = function (user, redir, cb) { callback(null); }; -/** - * Sends a delayed activation email with optional `url` for beta signup - * @private - */ -UsersRouter.prototype._dispatchDelayedActivationEmail = function (user, cb) { - let self = this; - self.mailer.dispatchSendGrid( - user.email, - 'delayed-activation', - {}, - function (err) { - if (err) { - log.error( - 'failed to send signup throttling email, reason: %s', - err.message - ); - cb(err); - } else { - cb(null); - } - } - ); -}; - -/** - * Sends an activation email based on configuration options - * @private - */ -UsersRouter.prototype._dispatchActivationEmailSwitch = function (user, cb) { - if (this.config.application.delayedActivation) { - this._dispatchDelayedActivationEmail(user, cb); - } else { - this._dispatchActivationEmail(user, cb); - } -}; - -/** - * Registers a new user - * @param {http.IncomingMessage} req - * @param {http.ServerResponse} res - * @param {Function} next - */ -UsersRouter.prototype.createUser = async function (req, res, next) { - const self = this; - const User = this.storage.models.User; - - if (!req.body.email) { - return next(new errors.BadRequestError('Not valid email address')); - } - - if (!req.body.password) { - return next(new errors.BadRequestError('Not valid password')); - } - - // Ensure email address is lowercase & trimmed to avoid future search errors - req.body.email = req.body.email.toLowerCase().trim(); - - // Check if email address if valid. - // RFC 5322 Official Standard - // eslint-disable-next-line no-control-regex - const emailPattern = - /^((?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|'(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*'))@((?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\]))$/; - const match = req.body.email.match(emailPattern); - - if (!match) { - return next(new errors.BadRequestError('Not valid email address')); - } - - // Check if disposable email - const emailDomain = match[2]; - const allowedEmail = disposable.validate(emailDomain); - - if (!allowedEmail) { - log.error('User %s is a fake email', req.body.email); - - return next(new errors.BadRequestError('Not valid email address provider')); - } - - // Register - log.debug('registering user account for %s', req.body.email); - - - try { - const user = await User.create({ - email: req.body.email, - password: req.body.password, - maxSpaceBytes: 1024 * 1024 * 1024 * 1, - activated: true, - }); - - trackUserActivated(user.uuid, req.body.email); - self._dispatchAndCreatePubKey(user, req, res, next); - } catch (err) { - return next(err); - } -}; - -/** - * Dispatch activation email and create pubkey if none - * @param {Object} user - * @param {http.IncomingMessage} req - * @param {http.ServerResponse} res - * @param {Function} next - */ -UsersRouter.prototype._dispatchAndCreatePubKey = async function ( - user, - req, - res, - next -) { - const self = this; - const PublicKey = this.storage.models.PublicKey; - - if (!req.body.pubkey) { - self._dispatchActivationEmailSwitch(user, function (err) { - if (err) { - log.error('failed to send deactivation email, reason: %s', err.message); - } - }); - - return res.status(201).send(user.toObject()); - } - - PublicKey.create(user, req.body.pubkey, async function (err, pubkey) { - if (err) { - await user.deleteOne(); - - return next(new errors.BadRequestError(err.message)); - } - - // NB: Don't send activation email, send beta list email - // NB: Send email explaining why they cant activate yet - self._dispatchActivationEmailSwitch(user, function (err) { - if (err) { - log.error('failed to send deactivation email, reason: %s', err.message); - } - }); - - res.status(201).send( - merge(user.toObject(), { - pubkey: pubkey.key, - }) - ); - }); -}; - -/** - * Creates user with opts object, does extra logic involved - * @param {http.IncomingMessage} req - * @param {http.ServerResponse} res - * @param {Function} next - */ -UsersRouter.prototype._createUserWithOpts = async function (req, res, next) { - const self = this; - const User = this.storage.models.User; - const Partner = this.storage.models.Partner; - const opts = { - email: req.body.email, - password: req.body.password, - }; - - try { - const partner = await Partner.findOne({ name: req.body.referralPartner }); - opts.referralPartner = partner ? partner.id : null; - - User.create(opts, function (err, user) { - if (err) { - return next(err); - } - - self._dispatchAndCreatePubKey(user, req, res, next); - }); - } catch (err) { - return next(err); - } -}; - /** * Confirms a user account * @param {http.IncomingMessage} req @@ -519,7 +339,6 @@ UsersRouter.prototype.confirmDestroyUserStripe = async function (req, res, next) */ UsersRouter.prototype._definitions = function () { return [ - ['POST', '/users', this.getLimiter(limiter(5)), rawbody, this.createUser], [ 'POST', '/activations', diff --git a/package.json b/package.json index 5239d7ad..8edd0d23 100644 --- a/package.json +++ b/package.json @@ -109,7 +109,6 @@ "commander": "8.3.0", "concat-stream": "^1.6.2", "cors": "^2.8.5", - "disposable-email": "internxt/disposable#v0.2.6", "express": "^4.17.1", "hat": "0.0.3", "helmet": "^3.20.0", diff --git a/tests/lib/core/users/usecase.test.ts b/tests/lib/core/users/usecase.test.ts index b0b3c0ad..717de809 100644 --- a/tests/lib/core/users/usecase.test.ts +++ b/tests/lib/core/users/usecase.test.ts @@ -129,17 +129,6 @@ describe('Users usecases', () => { })); }); - // it(`Should reject a disposable email`, async () => { - // const disposableEmail = 'e@bel.kr'; - - // try { - // await usecase.createUser(disposableEmail, fakeUser.password); - // expect(true).toBeFalsy(); - // } catch (err) { - // expect(err).toBeInstanceOf(InvalidDataFormatError); - // } - // }); - it(`Should reject if the user already exists`, async () => { stub(usersRepository, 'findByEmail').resolves(fakeUser); diff --git a/tests/lib/e2e/users/users.e2e-spec.ts b/tests/lib/e2e/users/users.e2e-spec.ts index e7887097..762ef186 100644 --- a/tests/lib/e2e/users/users.e2e-spec.ts +++ b/tests/lib/e2e/users/users.e2e-spec.ts @@ -1,8 +1,8 @@ -import { getAuth, shutdownEngine, } from '../utils' +import { createTestUser, getAuth, shutdownEngine, } from '../utils' import sendGridMail from '@sendgrid/mail' import { engine, testServer } from '../setup' -import { dataGenerator } from '../users.fixtures' +import { dataGenerator, generateTestUserData } from '../users.fixtures' // Mock SendGrid @@ -29,47 +29,6 @@ describe('Bridge E2E Tests', () => { describe('User Management v1', () => { - describe('Creating a new user', () => { - - it('When creating a user, it should work if email is not in use', async () => { - - // Arrange - const payload = { email: dataGenerator.email(), password: dataGenerator.hash({ length: 64 }), } - - // Act - const response = await testServer - .post('/users') - .send(payload) - - // Assert - expect(response.status).toBe(201); - const user = await engine.storage.models.User.findOne({ _id: response.body.id }) - expect(user).not.toBeNull() - - expect(user.toObject().activated).toBe(true) - - }) - it('When creating a user, it should fail if email is in use', async () => { - - // Arrange - const payload = { email: dataGenerator.email(), password: dataGenerator.hash({ length: 64 }), } - await testServer - .post('/users') - .send(payload) - .expect(201); - - // Act - const response = await testServer - .post('/users') - .send(payload) - - // Assert - expect(response.status).toBe(400); - - - }) - }) - describe('Deleting an existing user', () => { it('When requesting user deactivation, it should work for authorized user email', async () => { @@ -77,11 +36,8 @@ describe('Bridge E2E Tests', () => { engine.mailer.dispatchSendGrid = jest.fn((_, __, ___, cb) => { sendGridMail.send(null as any, null as any, cb) }) // Arrange: Create User - const payload = { email: dataGenerator.email(), password: dataGenerator.hash({ length: 64 }), } - const { body: user } = await testServer - .post('/users') - .send(payload) - .expect(201); + const payload = generateTestUserData() + const user = await createTestUser({ user: payload }) // Act: Request Deactivation const deactivatorHash = dataGenerator.hash() @@ -105,17 +61,11 @@ describe('Bridge E2E Tests', () => { it('When requesting user deactivation, it should fail for an email different than authorized user email', async () => { // Arrange: Create User - const payload1 = { email: dataGenerator.email(), password: dataGenerator.hash({ length: 64 }), } - const { body: user } = await testServer - .post('/users') - .send(payload1) - .expect(201); + const payload1 = generateTestUserData() + const user = await createTestUser({ user: payload1 }) - const payload2 = { email: dataGenerator.email(), password: dataGenerator.hash({ length: 64 }), } - await testServer - .post('/users') - .send(payload2) - .expect(201); + const payload2 = generateTestUserData() + await createTestUser({ user: payload2 }) // Act: Request Deactivation const deactivatorHash = dataGenerator.hash() @@ -136,11 +86,8 @@ describe('Bridge E2E Tests', () => { it('When confirming user deactivation, it should work with the correct deactivator', async () => { // Arrange: Create User - const payload = { email: dataGenerator.email(), password: dataGenerator.hash({ length: 64 }), } - const { body: user } = await testServer - .post('/users') - .send(payload) - .expect(201); + const payload = generateTestUserData() + const user = await createTestUser({ user: payload }) // Arrange: Request Deactivation @@ -162,11 +109,8 @@ describe('Bridge E2E Tests', () => { it('When confirming user deactivation, it should fail with an incorrect deactivator', async () => { // Arrange: Create User - const payload = { email: dataGenerator.email(), password: dataGenerator.hash({ length: 64 }), } - const { body: user } = await testServer - .post('/users') - .send(payload) - .expect(201); + const payload = generateTestUserData() + const user = await createTestUser({ user: payload }) // Arrange: Request Deactivation @@ -190,54 +134,13 @@ describe('Bridge E2E Tests', () => { describe('User Management v2', () => { - describe('Creating a new user', () => { - it('When creating a user, it should work if email is not in use', async () => { - - // Arrange - const payload = { email: dataGenerator.email(), password: dataGenerator.hash({ length: 64 }), } - // Act: Create a user - const response = await testServer - .post('/v2/users') - .send(payload) - - // Assert - expect(response.status).toBe(200); - - const user = await engine.storage.models.User.findOne({ _id: response.body.id }) - expect(user).not.toBeNull() - - expect(user.toObject().activated).toBe(true) - }) - it('When creating a user, it should fail if email is in use', async () => { - - // Arrange - const payload = { email: dataGenerator.email(), password: dataGenerator.hash({ length: 64 }), } - await testServer - .post('/v2/users') - .send(payload) - .expect(200); - - // Act: Create a user - const response = await testServer - .post('/v2/users') - .send(payload) - - // Assert - expect(response.status).toBe(400); - - }) - }) - describe('Deleting an existing user', () => { it('When requesting user deactivation, it should work for authorized user', async () => { // Arrange: Create User - const payload = { email: dataGenerator.email(), password: dataGenerator.hash({ length: 64 }), } - const { body: user } = await testServer - .post('/v2/users') - .send(payload) - .expect(200); + const payload = generateTestUserData() + const user = await createTestUser({ user: payload }) // Act: Request Deactivation @@ -262,11 +165,8 @@ describe('Bridge E2E Tests', () => { it('When confirming user deactivation, it should work with the correct deactivator', async () => { // Arrange: Create User - const payload = { email: dataGenerator.email(), password: dataGenerator.hash({ length: 64 }), } - const { body: user } = await testServer - .post('/v2/users') - .send(payload) - .expect(200); + const payload = generateTestUserData() + const user = await createTestUser({ user: payload }) // Arrange: Request Deactivation @@ -289,11 +189,8 @@ describe('Bridge E2E Tests', () => { it('When confirming user deactivation, it should fail with an incorrect deactivator', async () => { // Arrange: Create User - const payload = { email: dataGenerator.email(), password: dataGenerator.hash({ length: 64 }), } - const { body: user } = await testServer - .post('/v2/users') - .send(payload) - .expect(200); + const payload = generateTestUserData() + const user = await createTestUser({ user: payload }) // Arrange: Request Deactivation @@ -321,11 +218,8 @@ describe('Bridge E2E Tests', () => { it('should be able to update a user email via gateway', async () => { // Arrange: Create User - const payload = { email: dataGenerator.email(), password: dataGenerator.hash({ length: 64 }), } - const { body: user } = await testServer - .post('/v2/users') - .send(payload) - .expect(200); + const payload = generateTestUserData() + const user = await createTestUser({ user: payload }) // Act: Update User Email diff --git a/yarn.lock b/yarn.lock index 240a0f44..b194f33a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3199,12 +3199,6 @@ diff@^5.0.0: resolved "https://registry.yarnpkg.com/diff/-/diff-5.2.2.tgz#0a4742797281d09cfa699b79ea32d27723623bad" integrity sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A== -disposable-email@internxt/disposable#v0.2.6: - version "0.2.5" - resolved "https://codeload.github.com/internxt/disposable/tar.gz/48c0826142783d29522e14cd7da98b8ec04358fe" - dependencies: - axios "1.19.0" - doctrine@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961"