diff --git a/api-js/src/domain/mutations/__tests__/create-domain.test.js b/api-js/src/domain/mutations/__tests__/create-domain.test.js index 1f355e6dc5..177f5c418d 100644 --- a/api-js/src/domain/mutations/__tests__/create-domain.test.js +++ b/api-js/src/domain/mutations/__tests__/create-domain.test.js @@ -84,6 +84,13 @@ describe('create a domain', () => { loaders: { loadUserByUserName: loadUserByUserName({ query }), }, + notify: { + sendVerificationEmail: jest.fn(), + }, + request: { + protocol: 'https', + get: (text) => text, + }, }, ) org = await collections.organizations.save({ diff --git a/api-js/src/organization/mutations/__tests__/verify-organization.test.js b/api-js/src/organization/mutations/__tests__/verify-organization.test.js index 54151fdc44..d70d725076 100644 --- a/api-js/src/organization/mutations/__tests__/verify-organization.test.js +++ b/api-js/src/organization/mutations/__tests__/verify-organization.test.js @@ -83,6 +83,13 @@ describe('removing an organization', () => { loaders: { loadUserByUserName: loadUserByUserName({ query }), }, + notify: { + sendVerificationEmail: jest.fn(), + }, + request: { + protocol: 'https', + get: (text) => text, + }, }, ) consoleOutput = [] diff --git a/api-js/src/user/mutations/__tests__/reset-password.test.js b/api-js/src/user/mutations/__tests__/reset-password.test.js index f9102956ff..416ae29fd3 100644 --- a/api-js/src/user/mutations/__tests__/reset-password.test.js +++ b/api-js/src/user/mutations/__tests__/reset-password.test.js @@ -81,6 +81,13 @@ describe('reset users password', () => { loaders: { loadUserByUserName: loadUserByUserName({ query }), }, + notify: { + sendVerificationEmail: jest.fn(), + }, + request: { + protocol: 'https', + get: (text) => text, + }, }, ) consoleOutput.length = 0 diff --git a/api-js/src/user/mutations/__tests__/sign-in.test.js b/api-js/src/user/mutations/__tests__/sign-in.test.js index 258a11be94..c9ffd24164 100644 --- a/api-js/src/user/mutations/__tests__/sign-in.test.js +++ b/api-js/src/user/mutations/__tests__/sign-in.test.js @@ -83,6 +83,13 @@ describe('authenticate user account', () => { loaders: { loadUserByUserName: loadUserByUserName({ query }), }, + notify: { + sendVerificationEmail: jest.fn(), + }, + request: { + protocol: 'https', + get: (text) => text, + }, }, ) consoleOutput.length = 0 diff --git a/api-js/src/user/mutations/__tests__/sign-up.test.js b/api-js/src/user/mutations/__tests__/sign-up.test.js index 0f7b93f3b7..c4ae4f092d 100644 --- a/api-js/src/user/mutations/__tests__/sign-up.test.js +++ b/api-js/src/user/mutations/__tests__/sign-up.test.js @@ -3,7 +3,6 @@ import bcrypt from 'bcryptjs' import { graphql, GraphQLError, GraphQLSchema } from 'graphql' import { toGlobalId } from 'graphql-relay' import { setupI18n } from '@lingui/core' -import request from 'supertest' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' @@ -17,7 +16,7 @@ import { loadOrgByKey } from '../../../organization/loaders' const { DB_PASS: rootPass, DB_URL: url } = process.env -describe('user sign up', () => { +describe('testing user sign up', () => { let query, drop, truncate, @@ -25,7 +24,9 @@ describe('user sign up', () => { transaction, schema, i18n, - mockTokenize + mockTokenize, + mockNotify, + request beforeAll(async () => { schema = new GraphQLSchema({ @@ -40,6 +41,10 @@ describe('user sign up', () => { options: databaseOptions({ rootPass }), })) mockTokenize = jest.fn().mockReturnValue('token') + request = { + protocol: 'https', + get: (text) => text, + } }) let consoleOutput = [] @@ -50,6 +55,8 @@ describe('user sign up', () => { console.info = mockedInfo console.warn = mockedWarn console.error = mockedError + + mockNotify = jest.fn() }) afterEach(async () => { @@ -61,9 +68,23 @@ describe('user sign up', () => { await drop() }) - describe('given successful sign up', () => { - describe('when user is not signing up without an invite token', () => { - describe('when the users preferred language is english', () => { + describe('users language is set to english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('given a successful sign up', () => { + describe('when user is not signing up without an invite token', () => { it('returns auth result with user info', async () => { const response = await graphql( schema, @@ -115,14 +136,17 @@ describe('user sign up', () => { loadUserByUserName: loadUserByUserName({ query }), loadUserByKey: loadUserByKey({ query }), }, + notify: { + sendVerificationEmail: mockNotify, + }, }, ) const cursor = await query` - FOR user IN users - FILTER user.userName == "test.account@istio.actually.exists" - RETURN user - ` + FOR user IN users + FILTER user.userName == "test.account@istio.actually.exists" + RETURN user + ` const users = await cursor.all() const expectedResult = { @@ -148,10 +172,8 @@ describe('user sign up', () => { 'User: test.account@istio.actually.exists successfully created a new account.', ]) }) - }) - describe('when the users preferred language is french', () => { - it('returns auth result with user info', async () => { - const response = await graphql( + it('sends verification email', async () => { + await graphql( schema, ` mutation { @@ -161,7 +183,7 @@ describe('user sign up', () => { userName: "test.account@istio.actually.exists" password: "testpassword123" confirmPassword: "testpassword123" - preferredLang: FRENCH + preferredLang: ENGLISH } ) { result { @@ -186,6 +208,7 @@ describe('user sign up', () => { `, null, { + request, query, collections, transaction, @@ -200,77 +223,63 @@ describe('user sign up', () => { loadUserByUserName: loadUserByUserName({ query }), loadUserByKey: loadUserByKey({ query }), }, + notify: { + sendVerificationEmail: mockNotify, + }, }, ) - const cursor = await query` - FOR user IN users - FILTER user.userName == "test.account@istio.actually.exists" - RETURN user - ` - const user = await cursor.next() + const user = await loadUserByUserName({ + query, + userKey: '1', + i18n: {}, + }).load('test.account@istio.actually.exists') - const expectedResult = { - data: { - signUp: { - result: { - authToken: 'token', - user: { - id: `${toGlobalId('users', user._key)}`, - userName: 'test.account@istio.actually.exists', - displayName: 'Test Account', - preferredLang: 'FRENCH', - phoneValidated: false, - emailValidated: false, - }, - }, - }, - }, - } + const verifyUrl = `${request.protocol}://${request.get( + 'host', + )}/validate/token` - expect(response).toEqual(expectedResult) - expect(consoleOutput).toEqual([ - 'User: test.account@istio.actually.exists successfully created a new account.', - ]) + expect(mockNotify).toHaveBeenCalledWith({ + returnUser: user, + verifyUrl, + }) }) }) - }) - describe('when the user is signing up with an invite token', () => { - let org, token - beforeEach(async () => { - org = await collections.organizations.save({ - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', + describe('when the user is signing up with an invite token', () => { + let org, token + beforeEach(async () => { + org = await collections.organizations.save({ + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', + }) + token = tokenize({ + parameters: { + userName: 'test.account@istio.actually.exists', + orgKey: org._key, + requestedRole: 'admin', }, - }, - }) - token = tokenize({ - parameters: { - userName: 'test.account@istio.actually.exists', - orgKey: org._key, - requestedRole: 'admin', - }, + }) }) - }) - describe('when the users preferred language is english', () => { it('returns auth result with user info', async () => { const response = await graphql( schema, @@ -325,6 +334,9 @@ describe('user sign up', () => { loadUserByKey: loadUserByKey({ query }), loadOrgByKey: loadOrgByKey({ query, language: 'en' }), }, + notify: { + sendVerificationEmail: mockNotify, + }, }, ) @@ -412,6 +424,9 @@ describe('user sign up', () => { loadUserByKey: loadUserByKey({ query }), loadOrgByKey: loadOrgByKey({ query, language: 'en' }), }, + notify: { + sendVerificationEmail: mockNotify, + }, }, ) @@ -437,95 +452,7 @@ describe('user sign up', () => { expect(checkAffiliation).toMatchObject(expectedAffiliation) }) - }) - describe('when the users preferred language is french', () => { - it('returns auth result with user info', async () => { - const response = await graphql( - schema, - ` - mutation { - signUp( - input: { - displayName: "Test Account" - userName: "test.account@istio.actually.exists" - password: "testpassword123" - confirmPassword: "testpassword123" - preferredLang: FRENCH - signUpToken: "${token}" - } - ) { - result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } - } - ... on SignUpError { - code - description - } - } - } - } - `, - null, - { - query, - collections, - transaction, - auth: { - bcrypt, - tokenize: mockTokenize, - verifyToken: verifyToken({ i18n: {} }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: loadUserByUserName({ query }), - loadUserByKey: loadUserByKey({ query }), - loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), - }, - }, - ) - - const cursor = await query` - FOR user IN users - FILTER user.userName == "test.account@istio.actually.exists" - RETURN user - ` - const user = await cursor.next() - - const expectedResult = { - data: { - signUp: { - result: { - authToken: 'token', - user: { - id: `${toGlobalId('users', user._key)}`, - userName: 'test.account@istio.actually.exists', - displayName: 'Test Account', - preferredLang: 'FRENCH', - phoneValidated: false, - emailValidated: false, - }, - }, - }, - }, - } - - expect(response).toEqual(expectedResult) - expect(consoleOutput).toEqual([ - 'User: test.account@istio.actually.exists successfully created a new account.', - ]) - }) - it('creates affiliation', async () => { + it('sends verification email', async () => { await graphql( schema, ` @@ -579,49 +506,29 @@ describe('user sign up', () => { loadUserByKey: loadUserByKey({ query }), loadOrgByKey: loadOrgByKey({ query, language: 'en' }), }, + notify: { + sendVerificationEmail: mockNotify, + }, }, ) - const cursor = await query` - FOR user IN users - FILTER user.userName == "test.account@istio.actually.exists" - RETURN user - ` - const user = await cursor.next() - - const affiliationCursor = await query` - FOR affiliation IN affiliations - FILTER affiliation._to == ${user._id} - RETURN affiliation - ` - const checkAffiliation = await affiliationCursor.next() + const user = await loadUserByUserName({ + query, + userKey: '1', + i18n: {}, + }).load('test.account@istio.actually.exists') - const expectedAffiliation = { - _from: org._id, - _to: user._id, - permission: 'admin', - } + const verifyUrl = `${request.protocol}://${request.get( + 'host', + )}/validate/token` - expect(checkAffiliation).toMatchObject(expectedAffiliation) + expect(mockNotify).toHaveBeenCalledWith({ + returnUser: user, + verifyUrl, + }) }) }) }) - }) - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) describe('given unsuccessful sign up', () => { describe('when the password is not strong enough', () => { it('returns a password too short error', async () => { @@ -675,6 +582,9 @@ describe('user sign up', () => { loadUserByUserName: loadUserByUserName({ query }), loadUserByKey: loadUserByKey({ query }), }, + notify: { + sendVerificationEmail: mockNotify, + }, }, ) @@ -747,6 +657,9 @@ describe('user sign up', () => { loadUserByUserName: loadUserByUserName({ query }), loadUserByKey: loadUserByKey({ query }), }, + notify: { + sendVerificationEmail: mockNotify, + }, }, ) @@ -828,6 +741,9 @@ describe('user sign up', () => { loadUserByUserName: loadUserByUserName({ query }), loadUserByKey: loadUserByKey({ query }), }, + notify: { + sendVerificationEmail: mockNotify, + }, }, ) @@ -940,6 +856,9 @@ describe('user sign up', () => { loadUserByKey: loadUserByKey({ query }), loadOrgByKey: loadOrgByKey({ query, language: 'en' }), }, + notify: { + sendVerificationEmail: mockNotify, + }, }, ) @@ -1025,6 +944,9 @@ describe('user sign up', () => { loadUserByKey: loadUserByKey({ query }), loadOrgByKey: loadOrgByKey({ query, language: 'en' }), }, + notify: { + sendVerificationEmail: mockNotify, + }, }, ) @@ -1108,6 +1030,9 @@ describe('user sign up', () => { loadUserByUserName: loadUserByUserName({ query }), loadUserByKey: loadUserByKey({ query }), }, + notify: { + sendVerificationEmail: mockNotify, + }, }, ) @@ -1219,6 +1144,9 @@ describe('user sign up', () => { loadUserByKey: loadUserByKey({ query }), loadOrgByKey: loadOrgByKey({ query, language: 'en' }), }, + notify: { + sendVerificationEmail: mockNotify, + }, }, ) @@ -1294,6 +1222,9 @@ describe('user sign up', () => { loadUserByUserName: loadUserByUserName({ query }), loadUserByKey: loadUserByKey({ query }), }, + notify: { + sendVerificationEmail: mockNotify, + }, }, ) @@ -1325,9 +1256,9 @@ describe('user sign up', () => { }, }) }) - describe('given unsuccessful sign up', () => { - describe('when the password is not strong enough', () => { - it('returns a password too short error', async () => { + describe('given successful sign up', () => { + describe('when user is not signing up without an invite token', () => { + it('returns auth result with user info', async () => { const response = await graphql( schema, ` @@ -1336,8 +1267,8 @@ describe('user sign up', () => { input: { displayName: "Test Account" userName: "test.account@istio.actually.exists" - password: "123" - confirmPassword: "123" + password: "testpassword123" + confirmPassword: "testpassword123" preferredLang: FRENCH } ) { @@ -1363,7 +1294,7 @@ describe('user sign up', () => { `, null, { - i18n, + request, query, collections, transaction, @@ -1378,29 +1309,478 @@ describe('user sign up', () => { loadUserByUserName: loadUserByUserName({ query }), loadUserByKey: loadUserByKey({ query }), }, + notify: { + sendVerificationEmail: mockNotify, + }, }, ) - const error = { + const cursor = await query` + FOR user IN users + FILTER user.userName == "test.account@istio.actually.exists" + RETURN user + ` + const user = await cursor.next() + + const expectedResult = { data: { signUp: { result: { - code: 400, - description: 'todo', + authToken: 'token', + user: { + id: `${toGlobalId('users', user._key)}`, + userName: 'test.account@istio.actually.exists', + displayName: 'Test Account', + preferredLang: 'FRENCH', + phoneValidated: false, + emailValidated: false, + }, }, }, }, } - expect(response).toEqual(error) + expect(response).toEqual(expectedResult) expect(consoleOutput).toEqual([ - 'User: test.account@istio.actually.exists tried to sign up but did not meet requirements.', + 'User: test.account@istio.actually.exists successfully created a new account.', ]) }) - }) - describe('when the passwords do not match', () => { - it('returns a password not matching error', async () => { - const response = await graphql( + it('sends verification email', async () => { + await graphql( + schema, + ` + mutation { + signUp( + input: { + displayName: "Test Account" + userName: "test.account@istio.actually.exists" + password: "testpassword123" + confirmPassword: "testpassword123" + preferredLang: ENGLISH + } + ) { + result { + ... on AuthResult { + authToken + user { + id + userName + displayName + preferredLang + phoneValidated + emailValidated + } + } + ... on SignUpError { + code + description + } + } + } + } + `, + null, + { + request, + query, + collections, + transaction, + auth: { + bcrypt, + tokenize: mockTokenize, + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: loadUserByUserName({ query }), + loadUserByKey: loadUserByKey({ query }), + }, + notify: { + sendVerificationEmail: mockNotify, + }, + }, + ) + + const user = await loadUserByUserName({ + query, + userKey: '1', + i18n: {}, + }).load('test.account@istio.actually.exists') + + const verifyUrl = `${request.protocol}://${request.get( + 'host', + )}/validate/token` + + expect(mockNotify).toHaveBeenCalledWith({ + returnUser: user, + verifyUrl, + }) + }) + }) + describe('when the user is signing up with an invite token', () => { + let org, token + beforeEach(async () => { + org = await collections.organizations.save({ + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }) + token = tokenize({ + parameters: { + userName: 'test.account@istio.actually.exists', + orgKey: org._key, + requestedRole: 'admin', + }, + }) + }) + it('returns auth result with user info', async () => { + const response = await graphql( + schema, + ` + mutation { + signUp( + input: { + displayName: "Test Account" + userName: "test.account@istio.actually.exists" + password: "testpassword123" + confirmPassword: "testpassword123" + preferredLang: FRENCH + signUpToken: "${token}" + } + ) { + result { + ... on AuthResult { + authToken + user { + id + userName + displayName + preferredLang + phoneValidated + emailValidated + } + } + ... on SignUpError { + code + description + } + } + } + } + `, + null, + { + request, + query, + collections, + transaction, + auth: { + bcrypt, + tokenize: mockTokenize, + verifyToken: verifyToken({ i18n: {} }), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: loadUserByUserName({ query }), + loadUserByKey: loadUserByKey({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), + }, + notify: { + sendVerificationEmail: mockNotify, + }, + }, + ) + + const cursor = await query` + FOR user IN users + FILTER user.userName == "test.account@istio.actually.exists" + RETURN user + ` + const user = await cursor.next() + + const expectedResult = { + data: { + signUp: { + result: { + authToken: 'token', + user: { + id: `${toGlobalId('users', user._key)}`, + userName: 'test.account@istio.actually.exists', + displayName: 'Test Account', + preferredLang: 'FRENCH', + phoneValidated: false, + emailValidated: false, + }, + }, + }, + }, + } + + expect(response).toEqual(expectedResult) + expect(consoleOutput).toEqual([ + 'User: test.account@istio.actually.exists successfully created a new account.', + ]) + }) + it('creates affiliation', async () => { + await graphql( + schema, + ` + mutation { + signUp( + input: { + displayName: "Test Account" + userName: "test.account@istio.actually.exists" + password: "testpassword123" + confirmPassword: "testpassword123" + preferredLang: ENGLISH + signUpToken: "${token}" + } + ) { + result { + ... on AuthResult { + authToken + user { + id + userName + displayName + preferredLang + phoneValidated + emailValidated + } + } + ... on SignUpError { + code + description + } + } + } + } + `, + null, + { + request, + query, + collections, + transaction, + auth: { + bcrypt, + tokenize: mockTokenize, + verifyToken: verifyToken({ i18n: {} }), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: loadUserByUserName({ query }), + loadUserByKey: loadUserByKey({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + }, + notify: { + sendVerificationEmail: mockNotify, + }, + }, + ) + + const cursor = await query` + FOR user IN users + FILTER user.userName == "test.account@istio.actually.exists" + RETURN user + ` + const user = await cursor.next() + + const affiliationCursor = await query` + FOR affiliation IN affiliations + FILTER affiliation._to == ${user._id} + RETURN affiliation + ` + const checkAffiliation = await affiliationCursor.next() + + const expectedAffiliation = { + _from: org._id, + _to: user._id, + permission: 'admin', + } + + expect(checkAffiliation).toMatchObject(expectedAffiliation) + }) + it('sends verification email', async () => { + await graphql( + schema, + ` + mutation { + signUp( + input: { + displayName: "Test Account" + userName: "test.account@istio.actually.exists" + password: "testpassword123" + confirmPassword: "testpassword123" + preferredLang: ENGLISH + signUpToken: "${token}" + } + ) { + result { + ... on AuthResult { + authToken + user { + id + userName + displayName + preferredLang + phoneValidated + emailValidated + } + } + ... on SignUpError { + code + description + } + } + } + } + `, + null, + { + request, + query, + collections, + transaction, + auth: { + bcrypt, + tokenize: mockTokenize, + verifyToken: verifyToken({ i18n: {} }), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: loadUserByUserName({ query }), + loadUserByKey: loadUserByKey({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), + }, + notify: { + sendVerificationEmail: mockNotify, + }, + }, + ) + + const user = await loadUserByUserName({ + query, + userKey: '1', + i18n: {}, + }).load('test.account@istio.actually.exists') + + const verifyUrl = `${request.protocol}://${request.get( + 'host', + )}/validate/token` + + expect(mockNotify).toHaveBeenCalledWith({ + returnUser: user, + verifyUrl, + }) + }) + }) + }) + describe('given unsuccessful sign up', () => { + describe('when the password is not strong enough', () => { + it('returns a password too short error', async () => { + const response = await graphql( + schema, + ` + mutation { + signUp( + input: { + displayName: "Test Account" + userName: "test.account@istio.actually.exists" + password: "123" + confirmPassword: "123" + preferredLang: FRENCH + } + ) { + result { + ... on AuthResult { + authToken + user { + id + userName + displayName + preferredLang + phoneValidated + emailValidated + } + } + ... on SignUpError { + code + description + } + } + } + } + `, + null, + { + i18n, + query, + collections, + transaction, + auth: { + bcrypt, + tokenize: mockTokenize, + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: loadUserByUserName({ query }), + loadUserByKey: loadUserByKey({ query }), + }, + notify: { + sendVerificationEmail: mockNotify, + }, + }, + ) + + const error = { + data: { + signUp: { + result: { + code: 400, + description: 'todo', + }, + }, + }, + } + + expect(response).toEqual(error) + expect(consoleOutput).toEqual([ + 'User: test.account@istio.actually.exists tried to sign up but did not meet requirements.', + ]) + }) + }) + describe('when the passwords do not match', () => { + it('returns a password not matching error', async () => { + const response = await graphql( schema, ` mutation { @@ -1450,6 +1830,9 @@ describe('user sign up', () => { loadUserByUserName: loadUserByUserName({ query }), loadUserByKey: loadUserByKey({ query }), }, + notify: { + sendVerificationEmail: mockNotify, + }, }, ) @@ -1531,6 +1914,9 @@ describe('user sign up', () => { loadUserByUserName: loadUserByUserName({ query }), loadUserByKey: loadUserByKey({ query }), }, + notify: { + sendVerificationEmail: mockNotify, + }, }, ) @@ -1643,6 +2029,9 @@ describe('user sign up', () => { loadUserByKey: loadUserByKey({ query }), loadOrgByKey: loadOrgByKey({ query, language: 'en' }), }, + notify: { + sendVerificationEmail: mockNotify, + }, }, ) @@ -1727,6 +2116,9 @@ describe('user sign up', () => { loadUserByKey: loadUserByKey({ query }), loadOrgByKey: loadOrgByKey({ query, language: 'en' }), }, + notify: { + sendVerificationEmail: mockNotify, + }, }, ) @@ -1809,6 +2201,9 @@ describe('user sign up', () => { loadUserByUserName: loadUserByUserName({ query }), loadUserByKey: loadUserByKey({ query }), }, + notify: { + sendVerificationEmail: mockNotify, + }, }, ) @@ -1918,6 +2313,9 @@ describe('user sign up', () => { loadUserByKey: loadUserByKey({ query }), loadOrgByKey: loadOrgByKey({ query, language: 'en' }), }, + notify: { + sendVerificationEmail: mockNotify, + }, }, ) @@ -1991,6 +2389,9 @@ describe('user sign up', () => { loadUserByUserName: loadUserByUserName({ query }), loadUserByKey: loadUserByKey({ query }), }, + notify: { + sendVerificationEmail: mockNotify, + }, }, ) diff --git a/api-js/src/user/mutations/__tests__/update-user-password.test.js b/api-js/src/user/mutations/__tests__/update-user-password.test.js index e7ee5fb374..c08b98fe86 100644 --- a/api-js/src/user/mutations/__tests__/update-user-password.test.js +++ b/api-js/src/user/mutations/__tests__/update-user-password.test.js @@ -82,6 +82,13 @@ describe('authenticate user account', () => { loaders: { loadUserByUserName: loadUserByUserName({ query }), }, + notify: { + sendVerificationEmail: jest.fn(), + }, + request: { + protocol: 'https', + get: (text) => text, + }, }, ) const userCursor = await query` diff --git a/api-js/src/user/mutations/sign-up.js b/api-js/src/user/mutations/sign-up.js index 44a7008091..9a9741b773 100644 --- a/api-js/src/user/mutations/sign-up.js +++ b/api-js/src/user/mutations/sign-up.js @@ -51,9 +51,11 @@ export const signUp = new mutationWithClientMutationId({ { i18n, collections, + request, transaction, auth: { bcrypt, tokenize, verifyToken }, loaders: { loadOrgByKey, loadUserByUserName, loadUserByKey }, + notify: { sendVerificationEmail }, validators: { cleanseInput }, }, ) => { @@ -200,10 +202,16 @@ export const signUp = new mutationWithClientMutationId({ throw new Error(i18n._(t`Unable to sign up. Please try again.`)) } + const returnUser = await loadUserByKey.load(insertedUser._key) + // Generate JWT const token = tokenize({ parameters: { userKey: insertedUser._key } }) - const returnUser = await loadUserByKey.load(insertedUser._key) + const verifyUrl = `${request.protocol}://${request.get( + 'host', + )}/validate/${token}` + + await sendVerificationEmail({ returnUser, verifyUrl }) console.info(`User: ${userName} successfully created a new account.`)