diff --git a/backend/src/graphql/arg/TransactionSendArgs.ts b/backend/src/graphql/arg/TransactionSendArgs.ts index e75921383..c83e30252 100644 --- a/backend/src/graphql/arg/TransactionSendArgs.ts +++ b/backend/src/graphql/arg/TransactionSendArgs.ts @@ -4,7 +4,7 @@ import Decimal from 'decimal.js-light' @ArgsType() export default class TransactionSendArgs { @Field(() => String) - email: string + identifier: string @Field(() => Decimal) amount: Decimal diff --git a/backend/src/graphql/resolver/TransactionResolver.test.ts b/backend/src/graphql/resolver/TransactionResolver.test.ts index c660a72da..c3cd484ea 100644 --- a/backend/src/graphql/resolver/TransactionResolver.test.ts +++ b/backend/src/graphql/resolver/TransactionResolver.test.ts @@ -24,7 +24,6 @@ import { User } from '@entity/User' import { cleanDB, testEnvironment } from '@test/helpers' import { logger } from '@test/testSetup' import { GraphQLError } from 'graphql' -import { findUserByEmail } from './UserResolver' let mutate: any, query: any, con: any let testEnv: any @@ -81,7 +80,7 @@ describe('send coins', () => { await mutate({ mutation: sendCoins, variables: { - email: 'wrong@email.com', + identifier: 'wrong@email.com', amount: 100, memo: 'test', }, @@ -109,22 +108,20 @@ describe('send coins', () => { await mutate({ mutation: sendCoins, variables: { - email: 'stephen@hawking.uk', + identifier: 'stephen@hawking.uk', amount: 100, memo: 'test', }, }), ).toEqual( expect.objectContaining({ - errors: [new GraphQLError('The recipient account was deleted')], + errors: [new GraphQLError('No user to given contact')], }), ) }) - it('logs the error thrown', async () => { - // find peter to check the log - const user = await findUserByEmail('stephen@hawking.uk') - expect(logger.error).toBeCalledWith('The recipient account was deleted', user) + it('logs the error thrown', () => { + expect(logger.error).toBeCalledWith('No user to given contact', 'stephen@hawking.uk') }) }) @@ -140,22 +137,23 @@ describe('send coins', () => { await mutate({ mutation: sendCoins, variables: { - email: 'garrick@ollivander.com', + identifier: 'garrick@ollivander.com', amount: 100, memo: 'test', }, }), ).toEqual( expect.objectContaining({ - errors: [new GraphQLError('The recipient account is not activated')], + errors: [new GraphQLError('No user with this credentials')], }), ) }) - it('logs the error thrown', async () => { - // find peter to check the log - const user = await findUserByEmail('garrick@ollivander.com') - expect(logger.error).toBeCalledWith('The recipient account is not activated', user) + it('logs the error thrown', () => { + expect(logger.error).toBeCalledWith( + 'No user with this credentials', + 'garrick@ollivander.com', + ) }) }) }) @@ -175,7 +173,7 @@ describe('send coins', () => { await mutate({ mutation: sendCoins, variables: { - email: 'bob@baumeister.de', + identifier: 'bob@baumeister.de', amount: 100, memo: 'test', }, @@ -199,7 +197,7 @@ describe('send coins', () => { await mutate({ mutation: sendCoins, variables: { - email: 'peter@lustig.de', + identifier: 'peter@lustig.de', amount: 100, memo: 'test', }, @@ -223,7 +221,7 @@ describe('send coins', () => { await mutate({ mutation: sendCoins, variables: { - email: 'peter@lustig.de', + identifier: 'peter@lustig.de', amount: 100, memo: 'test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test t', }, @@ -247,7 +245,7 @@ describe('send coins', () => { await mutate({ mutation: sendCoins, variables: { - email: 'peter@lustig.de', + identifier: 'peter@lustig.de', amount: 100, memo: 'testing', }, @@ -297,7 +295,7 @@ describe('send coins', () => { await mutate({ mutation: sendCoins, variables: { - email: 'peter@lustig.de', + identifier: 'peter@lustig.de', amount: -50, memo: 'testing negative', }, @@ -320,7 +318,7 @@ describe('send coins', () => { await mutate({ mutation: sendCoins, variables: { - email: 'peter@lustig.de', + identifier: 'peter@lustig.de', amount: 50, memo: 'unrepeatable memo', }, @@ -375,7 +373,7 @@ describe('send coins', () => { mutate({ mutation: sendCoins, variables: { - email: 'peter@lustig.de', + identifier: 'peter@lustig.de', amount: 10, memo: 'first transaction', }, @@ -391,7 +389,7 @@ describe('send coins', () => { mutate({ mutation: sendCoins, variables: { - email: 'peter@lustig.de', + identifier: 'peter@lustig.de', amount: 20, memo: 'second transaction', }, @@ -407,7 +405,7 @@ describe('send coins', () => { mutate({ mutation: sendCoins, variables: { - email: 'peter@lustig.de', + identifier: 'peter@lustig.de', amount: 30, memo: 'third transaction', }, @@ -423,7 +421,7 @@ describe('send coins', () => { mutate({ mutation: sendCoins, variables: { - email: 'peter@lustig.de', + identifier: 'peter@lustig.de', amount: 40, memo: 'fourth transaction', }, diff --git a/backend/src/graphql/resolver/TransactionResolver.ts b/backend/src/graphql/resolver/TransactionResolver.ts index a11c5b377..203b10b0a 100644 --- a/backend/src/graphql/resolver/TransactionResolver.ts +++ b/backend/src/graphql/resolver/TransactionResolver.ts @@ -34,7 +34,7 @@ import { EVENT_TRANSACTION_RECEIVE, EVENT_TRANSACTION_SEND } from '@/event/Event import { BalanceResolver } from './BalanceResolver' import { MEMO_MAX_CHARS, MEMO_MIN_CHARS } from './const/const' -import { findUserByEmail } from './UserResolver' +import { findUserByIdentifier } from './util/findUserByIdentifier' import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK' import LogError from '@/server/LogError' @@ -307,10 +307,9 @@ export class TransactionResolver { @Authorized([RIGHTS.SEND_COINS]) @Mutation(() => Boolean) async sendCoins( - @Args() { email, amount, memo }: TransactionSendArgs, + @Args() { identifier, amount, memo }: TransactionSendArgs, @Ctx() context: Context, ): Promise { - logger.info(`sendCoins(email=${email}, amount=${amount}, memo=${memo})`) if (amount.lte(0)) { throw new LogError('Amount to send must be positive', amount) } @@ -319,13 +318,9 @@ export class TransactionResolver { const senderUser = getUser(context) // validate recipient user - const recipientUser = await findUserByEmail(email) - if (recipientUser.deletedAt) { - throw new LogError('The recipient account was deleted', recipientUser) - } - const emailContact = recipientUser.emailContact - if (!emailContact.emailChecked) { - throw new LogError('The recipient account is not activated', recipientUser) + const recipientUser = await findUserByIdentifier(identifier) + if (!recipientUser) { + throw new LogError('The recipient user was not found', recipientUser) } await executeTransaction(amount, memo, senderUser, recipientUser) diff --git a/backend/src/graphql/resolver/UserResolver.test.ts b/backend/src/graphql/resolver/UserResolver.test.ts index c75678439..036004db8 100644 --- a/backend/src/graphql/resolver/UserResolver.test.ts +++ b/backend/src/graphql/resolver/UserResolver.test.ts @@ -2228,8 +2228,8 @@ describe('UserResolver', () => { }) }) - describe('identifier is no gradido ID', () => { - it('throws and logs "No valid gradido ID" error', async () => { + describe('identifier is no gradido ID and no email', () => { + it('throws and logs "Unknown identifier type" error', async () => { await expect( query({ query: userQuery, @@ -2239,10 +2239,10 @@ describe('UserResolver', () => { }), ).resolves.toEqual( expect.objectContaining({ - errors: [new GraphQLError('No valid gradido ID')], + errors: [new GraphQLError('Unknown identifier type')], }), ) - expect(logger.error).toBeCalledWith('No valid gradido ID', 'identifier') + expect(logger.error).toBeCalledWith('Unknown identifier type', 'identifier') }) }) @@ -2267,7 +2267,30 @@ describe('UserResolver', () => { }) }) - describe('identifier is found', () => { + describe('identifier is found via email', () => { + it('returns user', async () => { + await expect( + query({ + query: userQuery, + variables: { + identifier: 'bibi@bloxberg.de', + }, + }), + ).resolves.toEqual( + expect.objectContaining({ + data: { + user: { + firstName: 'Bibi', + lastName: 'Bloxberg', + }, + }, + errors: undefined, + }), + ) + }) + }) + + describe('identifier is found via gradidoID', () => { it('returns user', async () => { await expect( query({ diff --git a/backend/src/graphql/resolver/UserResolver.ts b/backend/src/graphql/resolver/UserResolver.ts index c07946f0b..6125b418a 100644 --- a/backend/src/graphql/resolver/UserResolver.ts +++ b/backend/src/graphql/resolver/UserResolver.ts @@ -68,6 +68,7 @@ import { encryptPassword, verifyPassword } from '@/password/PasswordEncryptor' import { PasswordEncryptionType } from '../enum/PasswordEncryptionType' import LogError from '@/server/LogError' import { EventProtocolType } from '@/event/EventProtocolType' +import { findUserByIdentifier } from './util/findUserByIdentifier' // eslint-disable-next-line @typescript-eslint/no-var-requires const sodium = require('sodium-native') @@ -815,16 +816,8 @@ export class UserResolver { @Authorized([RIGHTS.USER]) @Query(() => User, { nullable: true }) async user(@Arg('identifier') identifier: string): Promise { - const isGradidoID = - /^[0-9a-f]{8,8}-[0-9a-f]{4,4}-[0-9a-f]{4,4}-[0-9a-f]{4,4}-[0-9a-f]{12,12}$/.exec(identifier) - if (!isGradidoID) { - throw new LogError('No valid gradido ID', identifier) - } - const user = await DbUser.findOne({ where: { gradidoID: identifier } }) - if (!user) { - throw new LogError('No user found to given identifier', identifier) - } - return new User(user) + const user = await findUserByIdentifier(identifier) + return user ? new User(user) : null } } diff --git a/backend/src/graphql/resolver/semaphore.test.ts b/backend/src/graphql/resolver/semaphore.test.ts index b3c99ba7d..28d311a7b 100644 --- a/backend/src/graphql/resolver/semaphore.test.ts +++ b/backend/src/graphql/resolver/semaphore.test.ts @@ -152,7 +152,7 @@ describe('semaphore', () => { }) const bibisTransaction = mutate({ mutation: sendCoins, - variables: { email: 'bob@baumeister.de', amount: '50', memo: 'Das ist für dich, Bob' }, + variables: { identifier: 'bob@baumeister.de', amount: '50', memo: 'Das ist für dich, Bob' }, }) await mutate({ mutation: login, @@ -168,7 +168,7 @@ describe('semaphore', () => { }) const bobsTransaction = mutate({ mutation: sendCoins, - variables: { email: 'bibi@bloxberg.de', amount: '50', memo: 'Das ist für dich, Bibi' }, + variables: { identifier: 'bibi@bloxberg.de', amount: '50', memo: 'Das ist für dich, Bibi' }, }) await mutate({ mutation: login, diff --git a/backend/src/graphql/resolver/util/findUserByIdentifier.ts b/backend/src/graphql/resolver/util/findUserByIdentifier.ts new file mode 100644 index 000000000..7f8ae4a7a --- /dev/null +++ b/backend/src/graphql/resolver/util/findUserByIdentifier.ts @@ -0,0 +1,36 @@ +import { User as DbUser } from '@entity/User' +import { UserContact as DbUserContact } from '@entity/UserContact' +import LogError from '@/server/LogError' + +export const findUserByIdentifier = async (identifier: string): Promise => { + let user: DbUser | undefined + if ( + /^[0-9a-f]{8,8}-[0-9a-f]{4,4}-[0-9a-f]{4,4}-[0-9a-f]{4,4}-[0-9a-f]{12,12}$/.exec(identifier) + ) { + user = await DbUser.findOne({ where: { gradidoID: identifier } }) + if (!user) { + throw new LogError('No user found to given identifier', identifier) + } + } else if (/^.{2,}@.{2,}\..{2,}$/.exec(identifier)) { + const userContact = await DbUserContact.findOne( + { + email: identifier, + emailChecked: true, + }, + { relations: ['user'] }, + ) + if (!userContact) { + throw new LogError('No user with this credentials', identifier) + } + if (!userContact.user) { + throw new LogError('No user to given contact', identifier) + } + user = userContact.user + user.emailContact = userContact + } else { + // last is alias when implemented + throw new LogError('Unknown identifier type', identifier) + } + + return user +} diff --git a/backend/src/seeds/graphql/mutations.ts b/backend/src/seeds/graphql/mutations.ts index 1aa12a32f..6625be62e 100644 --- a/backend/src/seeds/graphql/mutations.ts +++ b/backend/src/seeds/graphql/mutations.ts @@ -75,8 +75,8 @@ export const sendActivationEmail = gql` ` export const sendCoins = gql` - mutation ($email: String!, $amount: Decimal!, $memo: String!) { - sendCoins(email: $email, amount: $amount, memo: $memo) + mutation ($identifier: String!, $amount: Decimal!, $memo: String!) { + sendCoins(identifier: $identifier, amount: $amount, memo: $memo) } `