use findUserByIdentifier for emails, use it in send coins

This commit is contained in:
Moriz Wahl 2023-03-14 21:14:41 +01:00
parent 4c15669c4b
commit 761ff80b97
8 changed files with 99 additions and 54 deletions

View File

@ -4,7 +4,7 @@ import Decimal from 'decimal.js-light'
@ArgsType() @ArgsType()
export default class TransactionSendArgs { export default class TransactionSendArgs {
@Field(() => String) @Field(() => String)
email: string identifier: string
@Field(() => Decimal) @Field(() => Decimal)
amount: Decimal amount: Decimal

View File

@ -24,7 +24,6 @@ import { User } from '@entity/User'
import { cleanDB, testEnvironment } from '@test/helpers' import { cleanDB, testEnvironment } from '@test/helpers'
import { logger } from '@test/testSetup' import { logger } from '@test/testSetup'
import { GraphQLError } from 'graphql' import { GraphQLError } from 'graphql'
import { findUserByEmail } from './UserResolver'
let mutate: any, query: any, con: any let mutate: any, query: any, con: any
let testEnv: any let testEnv: any
@ -81,7 +80,7 @@ describe('send coins', () => {
await mutate({ await mutate({
mutation: sendCoins, mutation: sendCoins,
variables: { variables: {
email: 'wrong@email.com', identifier: 'wrong@email.com',
amount: 100, amount: 100,
memo: 'test', memo: 'test',
}, },
@ -109,22 +108,20 @@ describe('send coins', () => {
await mutate({ await mutate({
mutation: sendCoins, mutation: sendCoins,
variables: { variables: {
email: 'stephen@hawking.uk', identifier: 'stephen@hawking.uk',
amount: 100, amount: 100,
memo: 'test', memo: 'test',
}, },
}), }),
).toEqual( ).toEqual(
expect.objectContaining({ expect.objectContaining({
errors: [new GraphQLError('The recipient account was deleted')], errors: [new GraphQLError('No user to given contact')],
}), }),
) )
}) })
it('logs the error thrown', async () => { it('logs the error thrown', () => {
// find peter to check the log expect(logger.error).toBeCalledWith('No user to given contact', 'stephen@hawking.uk')
const user = await findUserByEmail('stephen@hawking.uk')
expect(logger.error).toBeCalledWith('The recipient account was deleted', user)
}) })
}) })
@ -140,22 +137,23 @@ describe('send coins', () => {
await mutate({ await mutate({
mutation: sendCoins, mutation: sendCoins,
variables: { variables: {
email: 'garrick@ollivander.com', identifier: 'garrick@ollivander.com',
amount: 100, amount: 100,
memo: 'test', memo: 'test',
}, },
}), }),
).toEqual( ).toEqual(
expect.objectContaining({ 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 () => { it('logs the error thrown', () => {
// find peter to check the log expect(logger.error).toBeCalledWith(
const user = await findUserByEmail('garrick@ollivander.com') 'No user with this credentials',
expect(logger.error).toBeCalledWith('The recipient account is not activated', user) 'garrick@ollivander.com',
)
}) })
}) })
}) })
@ -175,7 +173,7 @@ describe('send coins', () => {
await mutate({ await mutate({
mutation: sendCoins, mutation: sendCoins,
variables: { variables: {
email: 'bob@baumeister.de', identifier: 'bob@baumeister.de',
amount: 100, amount: 100,
memo: 'test', memo: 'test',
}, },
@ -199,7 +197,7 @@ describe('send coins', () => {
await mutate({ await mutate({
mutation: sendCoins, mutation: sendCoins,
variables: { variables: {
email: 'peter@lustig.de', identifier: 'peter@lustig.de',
amount: 100, amount: 100,
memo: 'test', memo: 'test',
}, },
@ -223,7 +221,7 @@ describe('send coins', () => {
await mutate({ await mutate({
mutation: sendCoins, mutation: sendCoins,
variables: { variables: {
email: 'peter@lustig.de', identifier: 'peter@lustig.de',
amount: 100, 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', 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({ await mutate({
mutation: sendCoins, mutation: sendCoins,
variables: { variables: {
email: 'peter@lustig.de', identifier: 'peter@lustig.de',
amount: 100, amount: 100,
memo: 'testing', memo: 'testing',
}, },
@ -297,7 +295,7 @@ describe('send coins', () => {
await mutate({ await mutate({
mutation: sendCoins, mutation: sendCoins,
variables: { variables: {
email: 'peter@lustig.de', identifier: 'peter@lustig.de',
amount: -50, amount: -50,
memo: 'testing negative', memo: 'testing negative',
}, },
@ -320,7 +318,7 @@ describe('send coins', () => {
await mutate({ await mutate({
mutation: sendCoins, mutation: sendCoins,
variables: { variables: {
email: 'peter@lustig.de', identifier: 'peter@lustig.de',
amount: 50, amount: 50,
memo: 'unrepeatable memo', memo: 'unrepeatable memo',
}, },
@ -375,7 +373,7 @@ describe('send coins', () => {
mutate({ mutate({
mutation: sendCoins, mutation: sendCoins,
variables: { variables: {
email: 'peter@lustig.de', identifier: 'peter@lustig.de',
amount: 10, amount: 10,
memo: 'first transaction', memo: 'first transaction',
}, },
@ -391,7 +389,7 @@ describe('send coins', () => {
mutate({ mutate({
mutation: sendCoins, mutation: sendCoins,
variables: { variables: {
email: 'peter@lustig.de', identifier: 'peter@lustig.de',
amount: 20, amount: 20,
memo: 'second transaction', memo: 'second transaction',
}, },
@ -407,7 +405,7 @@ describe('send coins', () => {
mutate({ mutate({
mutation: sendCoins, mutation: sendCoins,
variables: { variables: {
email: 'peter@lustig.de', identifier: 'peter@lustig.de',
amount: 30, amount: 30,
memo: 'third transaction', memo: 'third transaction',
}, },
@ -423,7 +421,7 @@ describe('send coins', () => {
mutate({ mutate({
mutation: sendCoins, mutation: sendCoins,
variables: { variables: {
email: 'peter@lustig.de', identifier: 'peter@lustig.de',
amount: 40, amount: 40,
memo: 'fourth transaction', memo: 'fourth transaction',
}, },

View File

@ -34,7 +34,7 @@ import { EVENT_TRANSACTION_RECEIVE, EVENT_TRANSACTION_SEND } from '@/event/Event
import { BalanceResolver } from './BalanceResolver' import { BalanceResolver } from './BalanceResolver'
import { MEMO_MAX_CHARS, MEMO_MIN_CHARS } from './const/const' 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 { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK'
import LogError from '@/server/LogError' import LogError from '@/server/LogError'
@ -307,10 +307,9 @@ export class TransactionResolver {
@Authorized([RIGHTS.SEND_COINS]) @Authorized([RIGHTS.SEND_COINS])
@Mutation(() => Boolean) @Mutation(() => Boolean)
async sendCoins( async sendCoins(
@Args() { email, amount, memo }: TransactionSendArgs, @Args() { identifier, amount, memo }: TransactionSendArgs,
@Ctx() context: Context, @Ctx() context: Context,
): Promise<boolean> { ): Promise<boolean> {
logger.info(`sendCoins(email=${email}, amount=${amount}, memo=${memo})`)
if (amount.lte(0)) { if (amount.lte(0)) {
throw new LogError('Amount to send must be positive', amount) throw new LogError('Amount to send must be positive', amount)
} }
@ -319,13 +318,9 @@ export class TransactionResolver {
const senderUser = getUser(context) const senderUser = getUser(context)
// validate recipient user // validate recipient user
const recipientUser = await findUserByEmail(email) const recipientUser = await findUserByIdentifier(identifier)
if (recipientUser.deletedAt) { if (!recipientUser) {
throw new LogError('The recipient account was deleted', recipientUser) throw new LogError('The recipient user was not found', recipientUser)
}
const emailContact = recipientUser.emailContact
if (!emailContact.emailChecked) {
throw new LogError('The recipient account is not activated', recipientUser)
} }
await executeTransaction(amount, memo, senderUser, recipientUser) await executeTransaction(amount, memo, senderUser, recipientUser)

View File

@ -2228,8 +2228,8 @@ describe('UserResolver', () => {
}) })
}) })
describe('identifier is no gradido ID', () => { describe('identifier is no gradido ID and no email', () => {
it('throws and logs "No valid gradido ID" error', async () => { it('throws and logs "Unknown identifier type" error', async () => {
await expect( await expect(
query({ query({
query: userQuery, query: userQuery,
@ -2239,10 +2239,10 @@ describe('UserResolver', () => {
}), }),
).resolves.toEqual( ).resolves.toEqual(
expect.objectContaining({ 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 () => { it('returns user', async () => {
await expect( await expect(
query({ query({

View File

@ -68,6 +68,7 @@ import { encryptPassword, verifyPassword } from '@/password/PasswordEncryptor'
import { PasswordEncryptionType } from '../enum/PasswordEncryptionType' import { PasswordEncryptionType } from '../enum/PasswordEncryptionType'
import LogError from '@/server/LogError' import LogError from '@/server/LogError'
import { EventProtocolType } from '@/event/EventProtocolType' import { EventProtocolType } from '@/event/EventProtocolType'
import { findUserByIdentifier } from './util/findUserByIdentifier'
// eslint-disable-next-line @typescript-eslint/no-var-requires // eslint-disable-next-line @typescript-eslint/no-var-requires
const sodium = require('sodium-native') const sodium = require('sodium-native')
@ -815,16 +816,8 @@ export class UserResolver {
@Authorized([RIGHTS.USER]) @Authorized([RIGHTS.USER])
@Query(() => User, { nullable: true }) @Query(() => User, { nullable: true })
async user(@Arg('identifier') identifier: string): Promise<User | null> { async user(@Arg('identifier') identifier: string): Promise<User | null> {
const isGradidoID = const user = await findUserByIdentifier(identifier)
/^[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) return user ? new User(user) : null
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)
} }
} }

View File

@ -152,7 +152,7 @@ describe('semaphore', () => {
}) })
const bibisTransaction = mutate({ const bibisTransaction = mutate({
mutation: sendCoins, 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({ await mutate({
mutation: login, mutation: login,
@ -168,7 +168,7 @@ describe('semaphore', () => {
}) })
const bobsTransaction = mutate({ const bobsTransaction = mutate({
mutation: sendCoins, 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({ await mutate({
mutation: login, mutation: login,

View File

@ -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<DbUser | null> => {
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
}

View File

@ -75,8 +75,8 @@ export const sendActivationEmail = gql`
` `
export const sendCoins = gql` export const sendCoins = gql`
mutation ($email: String!, $amount: Decimal!, $memo: String!) { mutation ($identifier: String!, $amount: Decimal!, $memo: String!) {
sendCoins(email: $email, amount: $amount, memo: $memo) sendCoins(identifier: $identifier, amount: $amount, memo: $memo)
} }
` `