diff --git a/api-js/src/user/mutations/__tests__/send-phone-code.test.js b/api-js/src/user/mutations/__tests__/send-phone-code.test.js index 7bed0f51ed..4998cab510 100644 --- a/api-js/src/user/mutations/__tests__/send-phone-code.test.js +++ b/api-js/src/user/mutations/__tests__/send-phone-code.test.js @@ -96,7 +96,15 @@ describe('user send password reset email', () => { ` mutation { sendPhoneCode(input: { phoneNumber: "+12345678901" }) { - status + result { + ... on SendPhoneCodeResult { + status + } + ... on SendPhoneCodeError { + code + description + } + } } } `, @@ -125,8 +133,10 @@ describe('user send password reset email', () => { const expectedResult = { data: { sendPhoneCode: { - status: - 'Two factor code has been successfully sent, you will receive a text message shortly.', + result: { + status: + 'Two factor code has been successfully sent, you will receive a text message shortly.', + }, }, }, } @@ -163,7 +173,15 @@ describe('user send password reset email', () => { ` mutation { sendPhoneCode(input: { phoneNumber: "+12345678901" }) { - status + result { + ... on SendPhoneCodeResult { + status + } + ... on SendPhoneCodeError { + code + description + } + } } } `, @@ -189,11 +207,18 @@ describe('user send password reset email', () => { }, ) - const error = [ - new GraphQLError('Unable to send TFA code, please try again.'), - ] + const error = { + data: { + sendPhoneCode: { + result: { + code: 400, + description: 'Unable to send TFA code, please try again.', + }, + }, + }, + } - expect(response.errors).toEqual(error) + expect(response).toEqual(error) expect(consoleOutput).toEqual([ `User attempted to send TFA text message, however no account is associated with this key: 1.`, ]) @@ -206,7 +231,15 @@ describe('user send password reset email', () => { ` mutation { sendPhoneCode(input: { phoneNumber: "+12345678901" }) { - status + result { + ... on SendPhoneCodeResult { + status + } + ... on SendPhoneCodeError { + code + description + } + } } } `, @@ -232,11 +265,18 @@ describe('user send password reset email', () => { }, ) - const error = [ - new GraphQLError('Authentication error, please sign in again.'), - ] + const error = { + data: { + sendPhoneCode: { + result: { + code: 400, + description: 'Authentication error, please sign in again.', + }, + }, + }, + } - expect(response.errors).toEqual(error) + expect(response).toEqual(error) expect(consoleOutput).toEqual([ `User attempted to send TFA text message, however the userKey does not exist.`, ]) @@ -255,7 +295,15 @@ describe('user send password reset email', () => { ` mutation { sendPhoneCode(input: { phoneNumber: "+12345678901" }) { - status + result { + ... on SendPhoneCodeResult { + status + } + ... on SendPhoneCodeError { + code + description + } + } } } `, @@ -305,7 +353,15 @@ describe('user send password reset email', () => { ` mutation { sendPhoneCode(input: { phoneNumber: "+12345678901" }) { - status + result { + ... on SendPhoneCodeResult { + status + } + ... on SendPhoneCodeError { + code + description + } + } } } `, @@ -375,7 +431,15 @@ describe('user send password reset email', () => { ` mutation { sendPhoneCode(input: { phoneNumber: "+12345678901" }) { - status + result { + ... on SendPhoneCodeResult { + status + } + ... on SendPhoneCodeError { + code + description + } + } } } `, @@ -404,7 +468,9 @@ describe('user send password reset email', () => { const expectedResult = { data: { sendPhoneCode: { - status: 'todo', + result: { + status: 'todo', + }, }, }, } @@ -441,7 +507,15 @@ describe('user send password reset email', () => { ` mutation { sendPhoneCode(input: { phoneNumber: "+12345678901" }) { - status + result { + ... on SendPhoneCodeResult { + status + } + ... on SendPhoneCodeError { + code + description + } + } } } `, @@ -467,9 +541,18 @@ describe('user send password reset email', () => { }, ) - const error = [new GraphQLError('todo')] + const error = { + data: { + sendPhoneCode: { + result: { + code: 400, + description: 'todo', + }, + }, + }, + } - expect(response.errors).toEqual(error) + expect(response).toEqual(error) expect(consoleOutput).toEqual([ `User attempted to send TFA text message, however no account is associated with this key: 1.`, ]) @@ -482,7 +565,15 @@ describe('user send password reset email', () => { ` mutation { sendPhoneCode(input: { phoneNumber: "+12345678901" }) { - status + result { + ... on SendPhoneCodeResult { + status + } + ... on SendPhoneCodeError { + code + description + } + } } } `, @@ -508,9 +599,18 @@ describe('user send password reset email', () => { }, ) - const error = [new GraphQLError('todo')] + const error = { + data: { + sendPhoneCode: { + result: { + code: 400, + description: 'todo', + }, + }, + }, + } - expect(response.errors).toEqual(error) + expect(response).toEqual(error) expect(consoleOutput).toEqual([ `User attempted to send TFA text message, however the userKey does not exist.`, ]) @@ -529,7 +629,15 @@ describe('user send password reset email', () => { ` mutation { sendPhoneCode(input: { phoneNumber: "+12345678901" }) { - status + result { + ... on SendPhoneCodeResult { + status + } + ... on SendPhoneCodeError { + code + description + } + } } } `, @@ -576,7 +684,15 @@ describe('user send password reset email', () => { ` mutation { sendPhoneCode(input: { phoneNumber: "+12345678901" }) { - status + result { + ... on SendPhoneCodeResult { + status + } + ... on SendPhoneCodeError { + code + description + } + } } } `, diff --git a/api-js/src/user/mutations/send-phone-code.js b/api-js/src/user/mutations/send-phone-code.js index 40379ea799..ca1cba056f 100644 --- a/api-js/src/user/mutations/send-phone-code.js +++ b/api-js/src/user/mutations/send-phone-code.js @@ -1,9 +1,11 @@ import crypto from 'crypto' -import { GraphQLNonNull, GraphQLString } from 'graphql' +import { GraphQLNonNull } from 'graphql' import { mutationWithClientMutationId } from 'graphql-relay' import { GraphQLPhoneNumber } from 'graphql-scalars' import { t } from '@lingui/macro' +import { sendPhoneCodeUnion } from '../unions' + const { CIPHER_KEY } = process.env export const sendPhoneCode = new mutationWithClientMutationId({ @@ -17,13 +19,11 @@ export const sendPhoneCode = new mutationWithClientMutationId({ }, }), outputFields: () => ({ - status: { - type: GraphQLString, + result: { + type: sendPhoneCodeUnion, description: - 'Informs the user if the text message was successfully sent.', - resolve: async (payload) => { - return payload.status - }, + '`SendPhoneCodeUnion` returning either a `SendPhoneCodeResult`, or `SendPhoneCodeError` object.', + resolve: (payload) => payload, }, }), mutateAndGetPayload: async ( @@ -45,7 +45,11 @@ export const sendPhoneCode = new mutationWithClientMutationId({ console.warn( `User attempted to send TFA text message, however the userKey does not exist.`, ) - throw new Error(i18n._(t`Authentication error, please sign in again.`)) + return { + _type: 'error', + code: 400, + description: i18n._(t`Authentication error, please sign in again.`), + } } // Get User From Db @@ -55,7 +59,11 @@ export const sendPhoneCode = new mutationWithClientMutationId({ console.warn( `User attempted to send TFA text message, however no account is associated with this key: ${userKey}.`, ) - throw new Error(i18n._(t`Unable to send TFA code, please try again.`)) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to send TFA code, please try again.`), + } } // Generate TFA code @@ -114,6 +122,7 @@ export const sendPhoneCode = new mutationWithClientMutationId({ console.info(`User: ${user._key} successfully sent tfa code.`) return { + _type: 'regular', status: i18n._( t`Two factor code has been successfully sent, you will receive a text message shortly.`, ), diff --git a/api-js/src/user/objects/__tests__/send-phone-code-error.test.js b/api-js/src/user/objects/__tests__/send-phone-code-error.test.js new file mode 100644 index 0000000000..d508de491d --- /dev/null +++ b/api-js/src/user/objects/__tests__/send-phone-code-error.test.js @@ -0,0 +1,39 @@ +import { GraphQLInt, GraphQLString } from 'graphql' + +import { sendPhoneCodeErrorType } from '../index' + +describe('given the sendPhoneCodeErrorType object', () => { + describe('testing the field definitions', () => { + it('has an code field', () => { + const demoType = sendPhoneCodeErrorType.getFields() + + expect(demoType).toHaveProperty('code') + expect(demoType.code.type).toMatchObject(GraphQLInt) + }) + it('has a description field', () => { + const demoType = sendPhoneCodeErrorType.getFields() + + expect(demoType).toHaveProperty('description') + expect(demoType.description.type).toMatchObject(GraphQLString) + }) + }) + + describe('testing the field resolvers', () => { + describe('testing the code resolver', () => { + it('returns the resolved field', () => { + const demoType = sendPhoneCodeErrorType.getFields() + + expect(demoType.code.resolve({ code: 400 })).toEqual(400) + }) + }) + describe('testing the description field', () => { + it('returns the resolved value', () => { + const demoType = sendPhoneCodeErrorType.getFields() + + expect( + demoType.description.resolve({ description: 'description' }), + ).toEqual('description') + }) + }) + }) +}) diff --git a/api-js/src/user/objects/__tests__/send-phone-code-result.test.js b/api-js/src/user/objects/__tests__/send-phone-code-result.test.js new file mode 100644 index 0000000000..46c19d7367 --- /dev/null +++ b/api-js/src/user/objects/__tests__/send-phone-code-result.test.js @@ -0,0 +1,24 @@ +import { GraphQLString } from 'graphql' + +import { sendPhoneCodeResultType } from '../index' + +describe('given the sendPhoneCodeResultType object', () => { + describe('testing the field definitions', () => { + it('has an status field', () => { + const demoType = sendPhoneCodeResultType.getFields() + + expect(demoType).toHaveProperty('status') + expect(demoType.status.type).toMatchObject(GraphQLString) + }) + }) + + describe('testing the field resolvers', () => { + describe('testing the status resolver', () => { + it('returns the resolved field', () => { + const demoType = sendPhoneCodeResultType.getFields() + + expect(demoType.status.resolve({ status: 'status' })).toEqual('status') + }) + }) + }) +}) diff --git a/api-js/src/user/objects/index.js b/api-js/src/user/objects/index.js index 6bc7deef5e..b3b39d2f49 100644 --- a/api-js/src/user/objects/index.js +++ b/api-js/src/user/objects/index.js @@ -2,6 +2,8 @@ export * from './auth-result' export * from './authenticate-error' export * from './reset-password-error' export * from './reset-password-result' +export * from './send-phone-code-error' +export * from './send-phone-code-result' export * from './sign-in-error' export * from './sign-up-error' export * from './tfa-sign-in-result' diff --git a/api-js/src/user/objects/send-phone-code-error.js b/api-js/src/user/objects/send-phone-code-error.js new file mode 100644 index 0000000000..8c00edd812 --- /dev/null +++ b/api-js/src/user/objects/send-phone-code-error.js @@ -0,0 +1,19 @@ +import { GraphQLInt, GraphQLObjectType, GraphQLString } from 'graphql' + +export const sendPhoneCodeErrorType = new GraphQLObjectType({ + name: 'SendPhoneCodeError', + description: + 'This object is used to inform the user if any errors occurred while sending a code to their phone.', + fields: () => ({ + code: { + type: GraphQLInt, + description: 'Error code to inform user what the issue is related to.', + resolve: ({ code }) => code, + }, + description: { + type: GraphQLString, + description: 'Description of the issue that was encountered.', + resolve: ({ description }) => description, + }, + }), +}) diff --git a/api-js/src/user/objects/send-phone-code-result.js b/api-js/src/user/objects/send-phone-code-result.js new file mode 100644 index 0000000000..f327351b8d --- /dev/null +++ b/api-js/src/user/objects/send-phone-code-result.js @@ -0,0 +1,15 @@ +import { GraphQLObjectType, GraphQLString } from 'graphql' + +export const sendPhoneCodeResultType = new GraphQLObjectType({ + name: 'SendPhoneCodeResult', + description: + 'This object is used to inform the user that no errors were encountered while sending their phone code.', + fields: () => ({ + status: { + type: GraphQLString, + description: + 'Informs the user if their phone code was successfully sent.', + resolve: ({ status }) => status, + }, + }), +}) diff --git a/api-js/src/user/unions/__tests__/send-phone-code-union.test.js b/api-js/src/user/unions/__tests__/send-phone-code-union.test.js new file mode 100644 index 0000000000..8e32ce404a --- /dev/null +++ b/api-js/src/user/unions/__tests__/send-phone-code-union.test.js @@ -0,0 +1,45 @@ +import { sendPhoneCodeErrorType, sendPhoneCodeResultType } from '../../objects/index' +import { sendPhoneCodeUnion } from '../send-phone-code-union' + +describe('given the sendPhoneCodeUnion', () => { + describe('testing the field types', () => { + it('contains sendPhoneCodeResultType', () => { + const demoType = sendPhoneCodeUnion.getTypes() + + expect(demoType).toContain(sendPhoneCodeResultType) + }) + it('contains sendPhoneCodeErrorType', () => { + const demoType = sendPhoneCodeUnion.getTypes() + + expect(demoType).toContain(sendPhoneCodeErrorType) + }) + }) + describe('testing the field selection', () => { + describe('testing the sendPhoneCodeResultType', () => { + it('returns the correct type', () => { + const obj = { + _type: 'regular', + authResult: {}, + } + + expect(sendPhoneCodeUnion.resolveType(obj)).toMatchObject( + sendPhoneCodeResultType, + ) + }) + }) + describe('testing the sendPhoneCodeErrorType', () => { + it('returns the correct type', () => { + const obj = { + _type: 'error', + error: 'sign-in-error', + code: 401, + description: 'text', + } + + expect(sendPhoneCodeUnion.resolveType(obj)).toMatchObject( + sendPhoneCodeErrorType, + ) + }) + }) + }) +}) diff --git a/api-js/src/user/unions/index.js b/api-js/src/user/unions/index.js index 565e885d56..38b33db64c 100644 --- a/api-js/src/user/unions/index.js +++ b/api-js/src/user/unions/index.js @@ -1,4 +1,5 @@ export * from './authenticate-union' export * from './reset-password-union' +export * from './send-phone-code-union' export * from './sign-in-union' export * from './sign-up-union' diff --git a/api-js/src/user/unions/send-phone-code-union.js b/api-js/src/user/unions/send-phone-code-union.js new file mode 100644 index 0000000000..662730aa41 --- /dev/null +++ b/api-js/src/user/unions/send-phone-code-union.js @@ -0,0 +1,16 @@ +import { GraphQLUnionType } from 'graphql' +import { sendPhoneCodeErrorType, sendPhoneCodeResultType } from '../objects' + +export const sendPhoneCodeUnion = new GraphQLUnionType({ + name: 'SendPhoneCodeUnion', + description: + 'This union is used with the `sendPhoneCode` mutation, allowing for users to send a verification code to their phone, and support any errors that may occur', + types: [sendPhoneCodeErrorType, sendPhoneCodeResultType], + resolveType({ _type }) { + if (_type === 'regular') { + return sendPhoneCodeResultType + } else { + return sendPhoneCodeErrorType + } + }, +}) diff --git a/frontend/schema.faker.graphql b/frontend/schema.faker.graphql index 5bb859c085..80fa5f8aad 100644 --- a/frontend/schema.faker.graphql +++ b/frontend/schema.faker.graphql @@ -1484,6 +1484,14 @@ type SendPasswordResetLinkPayload { clientMutationId: String } +# This object is used to inform the user if any errors occurred while sending a code to their phone. +type SendPhoneCodeError { + # Error code to inform user what the issue is related to. + code: Int + # Description of the issue that was encountered. + description: String +} + input sendPhoneCodeInput { # The phone number that the text message will be sent to. phoneNumber: PhoneNumber! @@ -1491,11 +1499,21 @@ input sendPhoneCodeInput { } type sendPhoneCodePayload { - # Informs the user if the text message was successfully sent. - status: String + # `SendPhoneCodeUnion` returning either a `SendPhoneCodeResult`, or `SendPhoneCodeError` object. + result: SendPhoneCodeUnion clientMutationId: String } +# This object is used to inform the user that no errors were encountered while sending their phone code. +type SendPhoneCodeResult { + # Informs the user if their phone code was successfully sent. + status: String +} + +# This union is used with the `sendPhoneCode` mutation, allowing for users to send +# a verification code to their phone, and support any errors that may occur +union SendPhoneCodeUnion = SendPhoneCodeError | SendPhoneCodeResult + # This object is used for showing none personal user details, # and is used for limiting admins to the personal details of users. type SharedUser implements Node {