From a82f0cb2845d0d2649d8177b2e41265d52294a88 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Wed, 10 May 2023 17:37:01 +0200 Subject: [PATCH] separate validation function for alias, tested --- .../src/graphql/resolver/UserResolver.test.ts | 101 -------------- backend/src/graphql/resolver/UserResolver.ts | 15 +-- .../resolver/util/validateAlias.test.ts | 125 ++++++++++++++++++ .../graphql/resolver/util/validateAlias.ts | 36 +++++ 4 files changed, 163 insertions(+), 114 deletions(-) create mode 100644 backend/src/graphql/resolver/util/validateAlias.test.ts create mode 100644 backend/src/graphql/resolver/util/validateAlias.ts diff --git a/backend/src/graphql/resolver/UserResolver.test.ts b/backend/src/graphql/resolver/UserResolver.test.ts index 8ebe3e7af..5e39ae2ff 100644 --- a/backend/src/graphql/resolver/UserResolver.test.ts +++ b/backend/src/graphql/resolver/UserResolver.test.ts @@ -1208,107 +1208,6 @@ describe('UserResolver', () => { jest.clearAllMocks() }) - describe('too short', () => { - it('throws and logs an error', async () => { - await expect( - mutate({ - mutation: updateUserInfos, - variables: { - alias: 'bibi', - }, - }), - ).resolves.toMatchObject({ - errors: [new GraphQLError('Given alias is too short')], - data: null, - }) - expect(logger.error).toBeCalledWith('Given alias is too short', 'bibi') - }) - }) - - describe('too long', () => { - it('throws and logs an error', async () => { - await expect( - mutate({ - mutation: updateUserInfos, - variables: { - alias: 'bibis_alias_far_too_long', - }, - }), - ).resolves.toMatchObject({ - errors: [new GraphQLError('Given alias is too long')], - data: null, - }) - expect(logger.error).toBeCalledWith( - 'Given alias is too long', - 'bibis_alias_far_too_long', - ) - }) - }) - - describe('invalid characters', () => { - it('throws and logs an error', async () => { - await expect( - mutate({ - mutation: updateUserInfos, - variables: { - alias: 'no+äöllll', - }, - }), - ).resolves.toMatchObject({ - errors: [new GraphQLError('Invalid characters in alias')], - data: null, - }) - expect(logger.error).toBeCalledWith('Invalid characters in alias', 'no+äöllll') - }) - }) - - describe('alias exists', () => { - let peter: User - beforeAll(async () => { - peter = await userFactory(testEnv, peterLustig) - await mutate({ - mutation: login, - variables: { - email: 'peter@lustig.de', - password: 'Aa12345_', - }, - }) - await mutate({ - mutation: updateUserInfos, - variables: { - alias: 'bibiBloxberg', - }, - }) - await mutate({ - mutation: login, - variables: { - email: 'bibi@bloxberg.de', - password: 'Aa12345_', - }, - }) - }) - - afterAll(async () => { - const user = await User.findOne({ id: peter.id }) - await user.remove() - }) - - it('throws and logs an error', async () => { - await expect( - mutate({ - mutation: updateUserInfos, - variables: { - alias: 'bibiBloxberg', - }, - }), - ).resolves.toMatchObject({ - errors: [new GraphQLError('Alias already in use')], - data: null, - }) - expect(logger.error).toBeCalledWith('Alias already in use', 'bibiBloxberg') - }) - }) - describe('valid alias', () => { it('updates the user in DB', async () => { await mutate({ diff --git a/backend/src/graphql/resolver/UserResolver.ts b/backend/src/graphql/resolver/UserResolver.ts index 7198a3bdc..6b4844154 100644 --- a/backend/src/graphql/resolver/UserResolver.ts +++ b/backend/src/graphql/resolver/UserResolver.ts @@ -73,6 +73,7 @@ import { getTimeDurationObject, printTimeDuration } from '@/util/time' import { FULL_CREATION_AVAILABLE } from './const/const' import { getUserCreations } from './util/creations' import { findUserByIdentifier } from './util/findUserByIdentifier' +import { validateAlias } from './util/validateAlias' // eslint-disable-next-line @typescript-eslint/no-var-requires, import/no-commonjs const random = require('random-bigint') @@ -525,19 +526,7 @@ export class UserResolver { } if (alias) { - if (alias.length < 5) { - throw new LogError('Given alias is too short', alias) - } - if (alias.length > 20) { - throw new LogError('Given alias is too long', alias) - } - if (!alias.match(/^[0-9A-Za-z]([_-]?[A-Za-z0-9])+$/)) { - throw new LogError('Invalid characters in alias', alias) - } - const aliasInUse = await DbUser.find({ alias }) - if (aliasInUse.length !== 0) { - throw new LogError('Alias already in use', alias) - } + await validateAlias(alias) user.alias = alias } diff --git a/backend/src/graphql/resolver/util/validateAlias.test.ts b/backend/src/graphql/resolver/util/validateAlias.test.ts new file mode 100644 index 000000000..733a09ffe --- /dev/null +++ b/backend/src/graphql/resolver/util/validateAlias.test.ts @@ -0,0 +1,125 @@ +import { Connection } from '@dbTools/typeorm' +import { User } from '@entity/User' +import { ApolloServerTestClient } from 'apollo-server-testing' + +import { testEnvironment, cleanDB } from '@test/helpers' +import { logger, i18n as localization } from '@test/testSetup' + +import { userFactory } from '@/seeds/factory/user' +import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg' + +import { validateAlias } from './validateAlias' + +let con: Connection +let testEnv: { + mutate: ApolloServerTestClient['mutate'] + query: ApolloServerTestClient['query'] + con: Connection +} + +beforeAll(async () => { + testEnv = await testEnvironment(logger, localization) + con = testEnv.con + await cleanDB() +}) + +afterAll(async () => { + await cleanDB() + await con.close() +}) + +describe('validate alias', () => { + beforeAll(() => { + jest.clearAllMocks() + }) + + describe('alias too short', () => { + it('throws and logs an error', async () => { + await expect(validateAlias('Bi')).rejects.toEqual(new Error('Given alias is too short')) + expect(logger.error).toBeCalledWith('Given alias is too short', 'Bi') + }) + }) + + describe('alias too long', () => { + it('throws and logs an error', async () => { + await expect(validateAlias('BibiBloxbergHexHexHex')).rejects.toEqual( + new Error('Given alias is too long'), + ) + expect(logger.error).toBeCalledWith('Given alias is too long', 'BibiBloxbergHexHexHex') + }) + }) + + describe('alias contains invalid characters', () => { + it('throws and logs an error', async () => { + await expect(validateAlias('Bibi.Bloxberg')).rejects.toEqual( + new Error('Invalid characters in alias'), + ) + expect(logger.error).toBeCalledWith('Invalid characters in alias', 'Bibi.Bloxberg') + }) + }) + + describe('alias is a reserved word', () => { + it('throws and logs an error', async () => { + await expect(validateAlias('admin')).rejects.toEqual(new Error('Alias is not allowed')) + expect(logger.error).toBeCalledWith('Alias is not allowed', 'admin') + }) + }) + + describe('alias is a reserved word with uppercase characters', () => { + it('throws and logs an error', async () => { + await expect(validateAlias('Admin')).rejects.toEqual(new Error('Alias is not allowed')) + expect(logger.error).toBeCalledWith('Alias is not allowed', 'Admin') + }) + }) + + describe('hyphens and underscore', () => { + describe('alias starts with underscore', () => { + it('throws and logs an error', async () => { + await expect(validateAlias('_bibi')).rejects.toEqual( + new Error('Invalid characters in alias'), + ) + expect(logger.error).toBeCalledWith('Invalid characters in alias', '_bibi') + }) + }) + + describe('alias contains two following hyphens', () => { + it('throws and logs an error', async () => { + await expect(validateAlias('bi--bi')).rejects.toEqual( + new Error('Invalid characters in alias'), + ) + expect(logger.error).toBeCalledWith('Invalid characters in alias', 'bi--bi') + }) + }) + }) + + describe('test against existing alias in database', () => { + beforeAll(async () => { + const bibi = await userFactory(testEnv, bibiBloxberg) + const user = await User.findOne({ id: bibi.id }) + if (user) { + user.alias = 'b-b' + await user.save() + } + }) + + describe('alias exists in database', () => { + it('throws and logs an error', async () => { + await expect(validateAlias('b-b')).rejects.toEqual(new Error('Alias already in use')) + expect(logger.error).toBeCalledWith('Alias already in use', 'b-b') + }) + }) + + describe('alias exists in database with in lower-case', () => { + it('throws and logs an error', async () => { + await expect(validateAlias('b-B')).rejects.toEqual(new Error('Alias already in use')) + expect(logger.error).toBeCalledWith('Alias already in use', 'b-B') + }) + }) + + describe('valid alias', () => { + it('resolves to void', async () => { + await expect(validateAlias('bibi')).resolves.toEqual(undefined) + }) + }) + }) +}) diff --git a/backend/src/graphql/resolver/util/validateAlias.ts b/backend/src/graphql/resolver/util/validateAlias.ts new file mode 100644 index 000000000..d35b9e363 --- /dev/null +++ b/backend/src/graphql/resolver/util/validateAlias.ts @@ -0,0 +1,36 @@ +import { Raw } from '@dbTools/typeorm' +import { User as DbUser } from '@entity/User' + +import { LogError } from '@/server/LogError' + +const reservedAlias = [ + 'admin', + 'email', + 'gast', + 'gdd', + 'gradido', + 'guest', + 'home', + 'root', + 'support', + 'temp', + 'tmp', + 'tmp', + 'user', + 'usr', + 'var', +] + +export const validateAlias = async (alias: string): Promise => { + if (alias.length < 3) throw new LogError('Given alias is too short', alias) + if (alias.length > 20) throw new LogError('Given alias is too long', alias) + if (!alias.match(/^[0-9A-Za-z]([_-]?[A-Za-z0-9])+$/)) + throw new LogError('Invalid characters in alias', alias) + if (reservedAlias.includes(alias.toLowerCase())) throw new LogError('Alias is not allowed', alias) + const aliasInUse = await DbUser.find({ + where: { alias: Raw((a) => `LOWER(${a}) = "${alias.toLowerCase()}"`) }, + }) + if (aliasInUse.length !== 0) { + throw new LogError('Alias already in use', alias) + } +}