mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
Merge branch 'master' into 2594-contributions-list-frontend
This commit is contained in:
commit
0f449de4a0
@ -4,6 +4,7 @@ import path from 'path'
|
||||
import { createTransport } from 'nodemailer'
|
||||
import Email from 'email-templates'
|
||||
import i18n from 'i18n'
|
||||
import LogError from '@/server/LogError'
|
||||
|
||||
export const sendEmailTranslated = async (params: {
|
||||
receiver: {
|
||||
@ -73,8 +74,7 @@ export const sendEmailTranslated = async (params: {
|
||||
logger.info('Result: ', result)
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
logger.error('Error sending notification email: ', error)
|
||||
throw new Error('Error sending notification email!')
|
||||
throw new LogError('Error sending notification email', error)
|
||||
})
|
||||
|
||||
i18n.setLocale(rememberLocaleToRestore)
|
||||
|
||||
@ -180,14 +180,14 @@ export class ContributionResolver {
|
||||
@Args()
|
||||
{ currentPage = 1, pageSize = 5, order = Order.DESC }: Paginated,
|
||||
@Arg('statusFilter', () => [ContributionStatus], { nullable: true })
|
||||
statusFilters?: ContributionStatus[],
|
||||
statusFilter?: ContributionStatus[],
|
||||
): Promise<ContributionListResult> {
|
||||
const where: {
|
||||
contributionStatus?: FindOperator<string> | null
|
||||
} = {}
|
||||
|
||||
if (statusFilters && statusFilters.length) {
|
||||
where.contributionStatus = In(statusFilters)
|
||||
if (statusFilter && statusFilter.length) {
|
||||
where.contributionStatus = In(statusFilter)
|
||||
}
|
||||
|
||||
const [dbContributions, count] = await getConnection()
|
||||
|
||||
@ -75,7 +75,7 @@ describe('EmailOptinCodes', () => {
|
||||
query({ query: queryOptIn, variables: { optIn: optinCode } }),
|
||||
).resolves.toMatchObject({
|
||||
data: null,
|
||||
errors: [new GraphQLError('email was sent more than 24 hours ago')],
|
||||
errors: [new GraphQLError('Email was sent more than 24 hours ago')],
|
||||
})
|
||||
})
|
||||
|
||||
@ -84,7 +84,7 @@ describe('EmailOptinCodes', () => {
|
||||
mutate({ mutation: setPassword, variables: { code: optinCode, password: 'Aa12345_' } }),
|
||||
).resolves.toMatchObject({
|
||||
data: null,
|
||||
errors: [new GraphQLError('email was sent more than 24 hours ago')],
|
||||
errors: [new GraphQLError('Email was sent more than 24 hours ago')],
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -96,7 +96,7 @@ describe('EmailOptinCodes', () => {
|
||||
mutate({ mutation: forgotPassword, variables: { email: 'peter@lustig.de' } }),
|
||||
).resolves.toMatchObject({
|
||||
data: null,
|
||||
errors: [new GraphQLError('email already sent less than 10 minutes ago')],
|
||||
errors: [new GraphQLError('Email already sent less than 10 minutes ago')],
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -89,7 +89,7 @@ describe('send coins', () => {
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith(`UserContact with email=wrong@email.com does not exists`)
|
||||
expect(logger.error).toBeCalledWith('No user with this credentials', 'wrong@email.com')
|
||||
})
|
||||
|
||||
describe('deleted recipient', () => {
|
||||
|
||||
@ -549,7 +549,9 @@ describe('UserResolver', () => {
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith('Password entered is lexically invalid')
|
||||
expect(logger.error).toBeCalledWith(
|
||||
'Please enter a valid password with at least 8 characters, upper and lower case letters, at least one number and one special character!',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@ -606,9 +608,7 @@ describe('UserResolver', () => {
|
||||
})
|
||||
|
||||
it('logs the error found', () => {
|
||||
expect(logger.error).toBeCalledWith(
|
||||
'UserContact with email=bibi@bloxberg.de does not exists',
|
||||
)
|
||||
expect(logger.error).toBeCalledWith('No user with this credentials', variables.email)
|
||||
})
|
||||
})
|
||||
|
||||
@ -668,7 +668,112 @@ describe('UserResolver', () => {
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith('The User has no valid credentials.')
|
||||
expect(logger.error).toBeCalledWith('No user with this credentials', variables.email)
|
||||
})
|
||||
})
|
||||
|
||||
describe('user is in database but deleted', () => {
|
||||
beforeAll(async () => {
|
||||
jest.clearAllMocks()
|
||||
await userFactory(testEnv, stephenHawking)
|
||||
const variables = {
|
||||
email: stephenHawking.email,
|
||||
password: 'Aa12345_',
|
||||
publisherId: 1234,
|
||||
}
|
||||
result = await mutate({ mutation: login, variables })
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDB()
|
||||
})
|
||||
|
||||
it('returns an error', () => {
|
||||
expect(result).toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [
|
||||
new GraphQLError('This user was permanently deleted. Contact support for questions'),
|
||||
],
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith(
|
||||
'This user was permanently deleted. Contact support for questions',
|
||||
expect.objectContaining({
|
||||
firstName: stephenHawking.firstName,
|
||||
lastName: stephenHawking.lastName,
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('user is in database but email not confirmed', () => {
|
||||
beforeAll(async () => {
|
||||
jest.clearAllMocks()
|
||||
await userFactory(testEnv, garrickOllivander)
|
||||
const variables = {
|
||||
email: garrickOllivander.email,
|
||||
password: 'Aa12345_',
|
||||
publisherId: 1234,
|
||||
}
|
||||
result = await mutate({ mutation: login, variables })
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDB()
|
||||
})
|
||||
|
||||
it('returns an error', () => {
|
||||
expect(result).toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('The Users email is not validate yet')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith(
|
||||
'The Users email is not validate yet',
|
||||
expect.objectContaining({
|
||||
firstName: garrickOllivander.firstName,
|
||||
lastName: garrickOllivander.lastName,
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe.skip('user is in database but password is not set', () => {
|
||||
beforeAll(async () => {
|
||||
jest.clearAllMocks()
|
||||
// TODO: we need an user without password set
|
||||
const user = await userFactory(testEnv, bibiBloxberg)
|
||||
user.password = BigInt(0)
|
||||
await user.save()
|
||||
result = await mutate({ mutation: login, variables })
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDB()
|
||||
})
|
||||
|
||||
it('returns an error', () => {
|
||||
expect(result).toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('The User has not set a password yet')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith(
|
||||
'The User has not set a password yet',
|
||||
expect.objectContaining({
|
||||
firstName: bibiBloxberg.firstName,
|
||||
lastName: bibiBloxberg.lastName,
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -828,7 +933,7 @@ describe('UserResolver', () => {
|
||||
expect.objectContaining({
|
||||
errors: [
|
||||
new GraphQLError(
|
||||
`email already sent less than ${printTimeDuration(
|
||||
`Email already sent less than ${printTimeDuration(
|
||||
CONFIG.EMAIL_CODE_REQUEST_TIME,
|
||||
)} ago`,
|
||||
),
|
||||
@ -870,13 +975,13 @@ describe('UserResolver', () => {
|
||||
CONFIG.EMAIL_CODE_REQUEST_TIME = emailCodeRequestTime
|
||||
await expect(mutate({ mutation: forgotPassword, variables })).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('email already sent less than 10 minutes ago')],
|
||||
errors: [new GraphQLError('Email already sent less than 10 minutes ago')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error found', () => {
|
||||
expect(logger.error).toBeCalledWith(`email already sent less than 10 minutes ago`)
|
||||
expect(logger.error).toBeCalledWith(`Email already sent less than 10 minutes ago`)
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -1001,13 +1106,13 @@ describe('UserResolver', () => {
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError(`"not-valid" isn't a valid language`)],
|
||||
errors: [new GraphQLError('Given language is not a valid language')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error found', () => {
|
||||
expect(logger.error).toBeCalledWith(`"not-valid" isn't a valid language`)
|
||||
expect(logger.error).toBeCalledWith('Given language is not a valid language', 'not-valid')
|
||||
})
|
||||
})
|
||||
|
||||
@ -1058,7 +1163,9 @@ describe('UserResolver', () => {
|
||||
})
|
||||
|
||||
it('logs the error found', () => {
|
||||
expect(logger.error).toBeCalledWith('newPassword does not fullfil the rules')
|
||||
expect(logger.error).toBeCalledWith(
|
||||
'Please enter a valid password with at least 8 characters, upper and lower case letters, at least one number and one special character!',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@ -1116,7 +1223,9 @@ describe('UserResolver', () => {
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith('The User has no valid credentials.')
|
||||
expect(logger.error).toBeCalledWith(
|
||||
'Please enter a valid password with at least 8 characters, upper and lower case letters, at least one number and one special character!',
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -1322,13 +1431,13 @@ describe('UserResolver', () => {
|
||||
mutate({ mutation: setUserRole, variables: { userId: admin.id + 1, isAdmin: true } }),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError(`Could not find user with userId: ${admin.id + 1}`)],
|
||||
errors: [new GraphQLError('Could not find user with given ID')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith(`Could not find user with userId: ${admin.id + 1}`)
|
||||
expect(logger.error).toBeCalledWith('Could not find user with given ID', admin.id + 1)
|
||||
})
|
||||
})
|
||||
|
||||
@ -1379,12 +1488,12 @@ describe('UserResolver', () => {
|
||||
mutate({ mutation: setUserRole, variables: { userId: admin.id, isAdmin: false } }),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('Administrator can not change his own role!')],
|
||||
errors: [new GraphQLError('Administrator can not change his own role')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith('Administrator can not change his own role!')
|
||||
expect(logger.error).toBeCalledWith('Administrator can not change his own role')
|
||||
})
|
||||
})
|
||||
|
||||
@ -1400,13 +1509,13 @@ describe('UserResolver', () => {
|
||||
mutate({ mutation: setUserRole, variables: { userId: user.id, isAdmin: true } }),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('User is already admin!')],
|
||||
errors: [new GraphQLError('User is already admin')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith('User is already admin!')
|
||||
expect(logger.error).toBeCalledWith('User is already admin')
|
||||
})
|
||||
})
|
||||
|
||||
@ -1421,13 +1530,13 @@ describe('UserResolver', () => {
|
||||
mutate({ mutation: setUserRole, variables: { userId: user.id, isAdmin: false } }),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('User is already a usual user!')],
|
||||
errors: [new GraphQLError('User is already an usual user')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith('User is already a usual user!')
|
||||
expect(logger.error).toBeCalledWith('User is already an usual user')
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -1494,13 +1603,13 @@ describe('UserResolver', () => {
|
||||
mutate({ mutation: deleteUser, variables: { userId: admin.id + 1 } }),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError(`Could not find user with userId: ${admin.id + 1}`)],
|
||||
errors: [new GraphQLError('Could not find user with given ID')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith(`Could not find user with userId: ${admin.id + 1}`)
|
||||
expect(logger.error).toBeCalledWith('Could not find user with given ID', admin.id + 1)
|
||||
})
|
||||
})
|
||||
|
||||
@ -1511,13 +1620,13 @@ describe('UserResolver', () => {
|
||||
mutate({ mutation: deleteUser, variables: { userId: admin.id } }),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('Moderator can not delete his own account!')],
|
||||
errors: [new GraphQLError('Moderator can not delete his own account')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith('Moderator can not delete his own account!')
|
||||
expect(logger.error).toBeCalledWith('Moderator can not delete his own account')
|
||||
})
|
||||
})
|
||||
|
||||
@ -1545,13 +1654,13 @@ describe('UserResolver', () => {
|
||||
mutate({ mutation: deleteUser, variables: { userId: user.id } }),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError(`Could not find user with userId: ${user.id}`)],
|
||||
errors: [new GraphQLError('Could not find user with given ID')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith(`Could not find user with userId: ${user.id}`)
|
||||
expect(logger.error).toBeCalledWith('Could not find user with given ID', user.id)
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -1617,13 +1726,13 @@ describe('UserResolver', () => {
|
||||
mutate({ mutation: unDeleteUser, variables: { userId: admin.id + 1 } }),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError(`Could not find user with userId: ${admin.id + 1}`)],
|
||||
errors: [new GraphQLError('Could not find user with given ID')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith(`Could not find user with userId: ${admin.id + 1}`)
|
||||
expect(logger.error).toBeCalledWith('Could not find user with given ID', admin.id + 1)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -63,6 +63,7 @@ import { isValidPassword } from '@/password/EncryptorUtils'
|
||||
import { FULL_CREATION_AVAILABLE } from './const/const'
|
||||
import { encryptPassword, verifyPassword } from '@/password/PasswordEncryptor'
|
||||
import { PasswordEncryptionType } from '../enum/PasswordEncryptionType'
|
||||
import LogError from '@/server/LogError'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const sodium = require('sodium-native')
|
||||
@ -134,22 +135,19 @@ export class UserResolver {
|
||||
email = email.trim().toLowerCase()
|
||||
const dbUser = await findUserByEmail(email)
|
||||
if (dbUser.deletedAt) {
|
||||
logger.error('The User was permanently deleted in database.')
|
||||
throw new Error('This user was permanently deleted. Contact support for questions.')
|
||||
throw new LogError('This user was permanently deleted. Contact support for questions', dbUser)
|
||||
}
|
||||
if (!dbUser.emailContact.emailChecked) {
|
||||
logger.error('The Users email is not validate yet.')
|
||||
throw new Error('User email not validated')
|
||||
throw new LogError('The Users email is not validate yet', dbUser)
|
||||
}
|
||||
// TODO: at least in test this does not work since `dbUser.password = 0` and `BigInto(0) = 0n`
|
||||
if (dbUser.password === BigInt(0)) {
|
||||
logger.error('The User has not set a password yet.')
|
||||
// TODO we want to catch this on the frontend and ask the user to check his emails or resend code
|
||||
throw new Error('User has no password set yet')
|
||||
throw new LogError('The User has not set a password yet', dbUser)
|
||||
}
|
||||
|
||||
if (!verifyPassword(dbUser, password)) {
|
||||
logger.error('The User has no valid credentials.')
|
||||
throw new Error('No user with this credentials')
|
||||
throw new LogError('No user with this credentials', dbUser)
|
||||
}
|
||||
|
||||
if (dbUser.passwordEncryptionType !== PasswordEncryptionType.GRADIDO_ID) {
|
||||
@ -309,30 +307,19 @@ export class UserResolver {
|
||||
await queryRunner.startTransaction('REPEATABLE READ')
|
||||
try {
|
||||
dbUser = await queryRunner.manager.save(dbUser).catch((error) => {
|
||||
logger.error('Error while saving dbUser', error)
|
||||
throw new Error('error saving user')
|
||||
throw new LogError('Error while saving dbUser', error)
|
||||
})
|
||||
let emailContact = newEmailContact(email, dbUser.id)
|
||||
emailContact = await queryRunner.manager.save(emailContact).catch((error) => {
|
||||
logger.error('Error while saving emailContact', error)
|
||||
throw new Error('error saving email user contact')
|
||||
throw new LogError('Error while saving user email contact', error)
|
||||
})
|
||||
|
||||
dbUser.emailContact = emailContact
|
||||
dbUser.emailId = emailContact.id
|
||||
await queryRunner.manager.save(dbUser).catch((error) => {
|
||||
logger.error('Error while updating dbUser', error)
|
||||
throw new Error('error updating user')
|
||||
throw new LogError('Error while updating dbUser', error)
|
||||
})
|
||||
|
||||
/*
|
||||
const emailOptIn = newEmailOptIn(dbUser.id)
|
||||
await queryRunner.manager.save(emailOptIn).catch((error) => {
|
||||
logger.error('Error while saving emailOptIn', error)
|
||||
throw new Error('error saving email opt in')
|
||||
})
|
||||
*/
|
||||
|
||||
const activationLink = CONFIG.EMAIL_LINK_VERIFICATION.replace(
|
||||
/{optin}/g,
|
||||
emailContact.emailVerificationCode.toString(),
|
||||
@ -358,9 +345,8 @@ export class UserResolver {
|
||||
await queryRunner.commitTransaction()
|
||||
logger.addContext('user', dbUser.id)
|
||||
} catch (e) {
|
||||
logger.error(`error during create user with ${e}`)
|
||||
await queryRunner.rollbackTransaction()
|
||||
throw e
|
||||
throw new LogError('Error creating user', e)
|
||||
} finally {
|
||||
await queryRunner.release()
|
||||
}
|
||||
@ -392,11 +378,9 @@ export class UserResolver {
|
||||
}
|
||||
|
||||
if (!canEmailResend(user.emailContact.updatedAt || user.emailContact.createdAt)) {
|
||||
const errorMessage = `email already sent less than ${printTimeDuration(
|
||||
CONFIG.EMAIL_CODE_REQUEST_TIME,
|
||||
)} ago`
|
||||
logger.error(errorMessage)
|
||||
throw new Error(errorMessage)
|
||||
throw new LogError(
|
||||
`Email already sent less than ${printTimeDuration(CONFIG.EMAIL_CODE_REQUEST_TIME)} ago`,
|
||||
)
|
||||
}
|
||||
|
||||
user.emailContact.updatedAt = new Date()
|
||||
@ -404,8 +388,7 @@ export class UserResolver {
|
||||
user.emailContact.emailVerificationCode = random(64)
|
||||
user.emailContact.emailOptInTypeId = OptInType.EMAIL_OPT_IN_RESET_PASSWORD
|
||||
await user.emailContact.save().catch(() => {
|
||||
logger.error('Unable to save email verification code= ' + user.emailContact)
|
||||
throw new Error('Unable to save email verification code.')
|
||||
throw new LogError('Unable to save email verification code', user.emailContact)
|
||||
})
|
||||
|
||||
logger.info(`optInCode for ${email}=${user.emailContact}`)
|
||||
@ -440,34 +423,23 @@ export class UserResolver {
|
||||
logger.info(`setPassword(${code}, ***)...`)
|
||||
// Validate Password
|
||||
if (!isValidPassword(password)) {
|
||||
logger.error('Password entered is lexically invalid')
|
||||
throw new Error(
|
||||
throw new LogError(
|
||||
'Please enter a valid password with at least 8 characters, upper and lower case letters, at least one number and one special character!',
|
||||
)
|
||||
}
|
||||
|
||||
// Load code
|
||||
/*
|
||||
const optInCode = await LoginEmailOptIn.findOneOrFail({ verificationCode: code }).catch(() => {
|
||||
logger.error('Could not login with emailVerificationCode')
|
||||
throw new Error('Could not login with emailVerificationCode')
|
||||
})
|
||||
*/
|
||||
// load code
|
||||
const userContact = await DbUserContact.findOneOrFail(
|
||||
{ emailVerificationCode: code },
|
||||
{ relations: ['user'] },
|
||||
).catch(() => {
|
||||
logger.error('Could not login with emailVerificationCode')
|
||||
throw new Error('Could not login with emailVerificationCode')
|
||||
throw new LogError('Could not login with emailVerificationCode')
|
||||
})
|
||||
logger.debug('userContact loaded...')
|
||||
// Code is only valid for `CONFIG.EMAIL_CODE_VALID_TIME` minutes
|
||||
if (!isEmailVerificationCodeValid(userContact.updatedAt || userContact.createdAt)) {
|
||||
logger.error(
|
||||
`email was sent more than ${printTimeDuration(CONFIG.EMAIL_CODE_VALID_TIME)} ago`,
|
||||
)
|
||||
throw new Error(
|
||||
`email was sent more than ${printTimeDuration(CONFIG.EMAIL_CODE_VALID_TIME)} ago`,
|
||||
throw new LogError(
|
||||
`Email was sent more than ${printTimeDuration(CONFIG.EMAIL_CODE_VALID_TIME)} ago`,
|
||||
)
|
||||
}
|
||||
logger.debug('EmailVerificationCode is valid...')
|
||||
@ -493,13 +465,11 @@ export class UserResolver {
|
||||
try {
|
||||
// Save user
|
||||
await queryRunner.manager.save(user).catch((error) => {
|
||||
logger.error('error saving user: ' + error)
|
||||
throw new Error('error saving user: ' + error)
|
||||
throw new LogError('Error saving user', error)
|
||||
})
|
||||
// Save userContact
|
||||
await queryRunner.manager.save(userContact).catch((error) => {
|
||||
logger.error('error saving userContact: ' + error)
|
||||
throw new Error('error saving userContact: ' + error)
|
||||
throw new LogError('Error saving userContact', error)
|
||||
})
|
||||
|
||||
await queryRunner.commitTransaction()
|
||||
@ -510,8 +480,7 @@ export class UserResolver {
|
||||
eventProtocol.writeEvent(event.setEventActivateAccount(eventActivateAccount))
|
||||
} catch (e) {
|
||||
await queryRunner.rollbackTransaction()
|
||||
logger.error('Error on writing User and UserContact data:' + e)
|
||||
throw e
|
||||
throw new LogError('Error on writing User and User Contact data', e)
|
||||
} finally {
|
||||
await queryRunner.release()
|
||||
}
|
||||
@ -525,7 +494,7 @@ export class UserResolver {
|
||||
`klicktippSignIn(${userContact.email}, ${user.language}, ${user.firstName}, ${user.lastName})`,
|
||||
)
|
||||
} catch (e) {
|
||||
logger.error('Error subscribe to klicktipp:' + e)
|
||||
logger.error('Error subscribing to klicktipp', e)
|
||||
// TODO is this a problem?
|
||||
// eslint-disable-next-line no-console
|
||||
/* uncomment this, when you need the activation link on the console
|
||||
@ -545,11 +514,8 @@ export class UserResolver {
|
||||
logger.debug(`found optInCode=${userContact}`)
|
||||
// Code is only valid for `CONFIG.EMAIL_CODE_VALID_TIME` minutes
|
||||
if (!isEmailVerificationCodeValid(userContact.updatedAt || userContact.createdAt)) {
|
||||
logger.error(
|
||||
`email was sent more than ${printTimeDuration(CONFIG.EMAIL_CODE_VALID_TIME)} ago`,
|
||||
)
|
||||
throw new Error(
|
||||
`email was sent more than ${printTimeDuration(CONFIG.EMAIL_CODE_VALID_TIME)} ago`,
|
||||
throw new LogError(
|
||||
`Email was sent more than ${printTimeDuration(CONFIG.EMAIL_CODE_VALID_TIME)} ago`,
|
||||
)
|
||||
}
|
||||
logger.info(`queryOptIn(${optIn}) successful...`)
|
||||
@ -584,8 +550,7 @@ export class UserResolver {
|
||||
|
||||
if (language) {
|
||||
if (!isLanguage(language)) {
|
||||
logger.error(`"${language}" isn't a valid language`)
|
||||
throw new Error(`"${language}" isn't a valid language`)
|
||||
throw new LogError('Given language is not a valid language', language)
|
||||
}
|
||||
userEntity.language = language
|
||||
i18n.setLocale(language)
|
||||
@ -594,15 +559,13 @@ export class UserResolver {
|
||||
if (password && passwordNew) {
|
||||
// Validate Password
|
||||
if (!isValidPassword(passwordNew)) {
|
||||
logger.error('newPassword does not fullfil the rules')
|
||||
throw new Error(
|
||||
throw new LogError(
|
||||
'Please enter a valid password with at least 8 characters, upper and lower case letters, at least one number and one special character!',
|
||||
)
|
||||
}
|
||||
|
||||
if (!verifyPassword(userEntity, password)) {
|
||||
logger.error(`Old password is invalid`)
|
||||
throw new Error(`Old password is invalid`)
|
||||
throw new LogError(`Old password is invalid`)
|
||||
}
|
||||
|
||||
// Save new password hash and newly encrypted private key
|
||||
@ -625,16 +588,14 @@ export class UserResolver {
|
||||
|
||||
try {
|
||||
await queryRunner.manager.save(userEntity).catch((error) => {
|
||||
logger.error('error saving user: ' + error)
|
||||
throw new Error('error saving user: ' + error)
|
||||
throw new LogError('Error saving user', error)
|
||||
})
|
||||
|
||||
await queryRunner.commitTransaction()
|
||||
logger.debug('writing User data successful...')
|
||||
} catch (e) {
|
||||
await queryRunner.rollbackTransaction()
|
||||
logger.error(`error on writing updated user data: ${e}`)
|
||||
throw e
|
||||
throw new LogError('Error on writing updated user data', e)
|
||||
} finally {
|
||||
await queryRunner.release()
|
||||
}
|
||||
@ -761,14 +722,12 @@ export class UserResolver {
|
||||
const user = await DbUser.findOne({ id: userId })
|
||||
// user exists ?
|
||||
if (!user) {
|
||||
logger.error(`Could not find user with userId: ${userId}`)
|
||||
throw new Error(`Could not find user with userId: ${userId}`)
|
||||
throw new LogError('Could not find user with given ID', userId)
|
||||
}
|
||||
// administrator user changes own role?
|
||||
const moderatorUser = getUser(context)
|
||||
if (moderatorUser.id === userId) {
|
||||
logger.error('Administrator can not change his own role!')
|
||||
throw new Error('Administrator can not change his own role!')
|
||||
throw new LogError('Administrator can not change his own role')
|
||||
}
|
||||
// change isAdmin
|
||||
switch (user.isAdmin) {
|
||||
@ -776,16 +735,14 @@ export class UserResolver {
|
||||
if (isAdmin === true) {
|
||||
user.isAdmin = new Date()
|
||||
} else {
|
||||
logger.error('User is already a usual user!')
|
||||
throw new Error('User is already a usual user!')
|
||||
throw new LogError('User is already an usual user')
|
||||
}
|
||||
break
|
||||
default:
|
||||
if (isAdmin === false) {
|
||||
user.isAdmin = null
|
||||
} else {
|
||||
logger.error('User is already admin!')
|
||||
throw new Error('User is already admin!')
|
||||
throw new LogError('User is already admin')
|
||||
}
|
||||
break
|
||||
}
|
||||
@ -803,14 +760,12 @@ export class UserResolver {
|
||||
const user = await DbUser.findOne({ id: userId })
|
||||
// user exists ?
|
||||
if (!user) {
|
||||
logger.error(`Could not find user with userId: ${userId}`)
|
||||
throw new Error(`Could not find user with userId: ${userId}`)
|
||||
throw new LogError('Could not find user with given ID', userId)
|
||||
}
|
||||
// moderator user disabled own account?
|
||||
const moderatorUser = getUser(context)
|
||||
if (moderatorUser.id === userId) {
|
||||
logger.error('Moderator can not delete his own account!')
|
||||
throw new Error('Moderator can not delete his own account!')
|
||||
throw new LogError('Moderator can not delete his own account')
|
||||
}
|
||||
// soft-delete user
|
||||
await user.softRemove()
|
||||
@ -823,12 +778,10 @@ export class UserResolver {
|
||||
async unDeleteUser(@Arg('userId', () => Int) userId: number): Promise<Date | null> {
|
||||
const user = await DbUser.findOne({ id: userId }, { withDeleted: true })
|
||||
if (!user) {
|
||||
logger.error(`Could not find user with userId: ${userId}`)
|
||||
throw new Error(`Could not find user with userId: ${userId}`)
|
||||
throw new LogError('Could not find user with given ID', userId)
|
||||
}
|
||||
if (!user.deletedAt) {
|
||||
logger.error('User is not deleted')
|
||||
throw new Error('User is not deleted')
|
||||
throw new LogError('User is not deleted')
|
||||
}
|
||||
await user.recover()
|
||||
return null
|
||||
@ -841,17 +794,14 @@ export class UserResolver {
|
||||
// const user = await dbUser.findOne({ id: emailContact.userId })
|
||||
const user = await findUserByEmail(email)
|
||||
if (!user) {
|
||||
logger.error(`Could not find User to emailContact: ${email}`)
|
||||
throw new Error(`Could not find User to emailContact: ${email}`)
|
||||
throw new LogError('Could not find user to given email contact', email)
|
||||
}
|
||||
if (user.deletedAt) {
|
||||
logger.error(`User with emailContact: ${email} is deleted.`)
|
||||
throw new Error(`User with emailContact: ${email} is deleted.`)
|
||||
throw new LogError('User with given email contact is deleted', email)
|
||||
}
|
||||
const emailContact = user.emailContact
|
||||
if (emailContact.deletedAt) {
|
||||
logger.error(`The emailContact: ${email} of this User is deleted.`)
|
||||
throw new Error(`The emailContact: ${email} of this User is deleted.`)
|
||||
throw new LogError('The given email contact for this user is deleted', email)
|
||||
}
|
||||
|
||||
emailContact.emailResendCount++
|
||||
@ -888,8 +838,7 @@ export async function findUserByEmail(email: string): Promise<DbUser> {
|
||||
{ email: email },
|
||||
{ withDeleted: true, relations: ['user'] },
|
||||
).catch(() => {
|
||||
logger.error(`UserContact with email=${email} does not exists`)
|
||||
throw new Error('No user with this credentials')
|
||||
throw new LogError('No user with this credentials', email)
|
||||
})
|
||||
const dbUser = dbUserContact.user
|
||||
dbUser.emailContact = dbUserContact
|
||||
@ -904,31 +853,16 @@ async function checkEmailExists(email: string): Promise<boolean> {
|
||||
return false
|
||||
}
|
||||
|
||||
/*
|
||||
const isTimeExpired = (optIn: LoginEmailOptIn, duration: number): boolean => {
|
||||
const timeElapsed = Date.now() - new Date(optIn.updatedAt).getTime()
|
||||
// time is given in minutes
|
||||
return timeElapsed <= duration * 60 * 1000
|
||||
}
|
||||
*/
|
||||
const isTimeExpired = (updatedAt: Date, duration: number): boolean => {
|
||||
const timeElapsed = Date.now() - new Date(updatedAt).getTime()
|
||||
// time is given in minutes
|
||||
return timeElapsed <= duration * 60 * 1000
|
||||
}
|
||||
/*
|
||||
const isOptInValid = (optIn: LoginEmailOptIn): boolean => {
|
||||
return isTimeExpired(optIn, CONFIG.EMAIL_CODE_VALID_TIME)
|
||||
}
|
||||
*/
|
||||
|
||||
const isEmailVerificationCodeValid = (updatedAt: Date): boolean => {
|
||||
return isTimeExpired(updatedAt, CONFIG.EMAIL_CODE_VALID_TIME)
|
||||
}
|
||||
/*
|
||||
const canResendOptIn = (optIn: LoginEmailOptIn): boolean => {
|
||||
return !isTimeExpired(optIn, CONFIG.EMAIL_CODE_REQUEST_TIME)
|
||||
}
|
||||
*/
|
||||
|
||||
const canEmailResend = (updatedAt: Date): boolean => {
|
||||
return !isTimeExpired(updatedAt, CONFIG.EMAIL_CODE_REQUEST_TIME)
|
||||
}
|
||||
|
||||
26
backend/src/server/LogError.test.ts
Normal file
26
backend/src/server/LogError.test.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { logger } from '@test/testSetup'
|
||||
|
||||
import LogError from './LogError'
|
||||
|
||||
describe('LogError', () => {
|
||||
it('logs an Error when created', () => {
|
||||
/* eslint-disable-next-line no-new */
|
||||
new LogError('new LogError')
|
||||
expect(logger.error).toBeCalledWith('new LogError')
|
||||
})
|
||||
|
||||
it('logs an Error including additional data when created', () => {
|
||||
/* eslint-disable-next-line no-new */
|
||||
new LogError('new LogError', { some: 'data' })
|
||||
expect(logger.error).toBeCalledWith('new LogError', { some: 'data' })
|
||||
})
|
||||
|
||||
it('does not contain additional data in Error object when thrown', () => {
|
||||
try {
|
||||
throw new LogError('new LogError', { someWeirdValue123: 'arbitraryData456' })
|
||||
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
||||
} catch (e: any) {
|
||||
expect(e.stack).not.toMatch(/(someWeirdValue123|arbitraryData456)/i)
|
||||
}
|
||||
})
|
||||
})
|
||||
9
backend/src/server/LogError.ts
Normal file
9
backend/src/server/LogError.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { backendLogger as logger } from './logger'
|
||||
|
||||
export default class LogError extends Error {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
constructor(msg: string, ...details: any[]) {
|
||||
super(msg)
|
||||
logger.error(msg, ...details)
|
||||
}
|
||||
}
|
||||
@ -3,7 +3,7 @@
|
||||
<b-form
|
||||
ref="form"
|
||||
@submit.prevent="submit"
|
||||
class="p-3 bg-white appBoxShadow gradido-border-radius"
|
||||
class="form-style p-3 bg-white appBoxShadow gradido-border-radius"
|
||||
>
|
||||
<label>{{ $t('contribution.selectDate') }}</label>
|
||||
<b-form-datepicker
|
||||
@ -23,6 +23,13 @@
|
||||
<template #nav-next-year><span></span></template>
|
||||
</b-form-datepicker>
|
||||
|
||||
<div
|
||||
v-if="(isThisMonth && maxGddThisMonth <= 0) || (!isThisMonth && maxGddLastMonth <= 0)"
|
||||
class="p-3"
|
||||
>
|
||||
{{ noOpenCreation }}
|
||||
</div>
|
||||
<div v-else>
|
||||
<input-textarea
|
||||
id="contribution-memo"
|
||||
v-model="form.memo"
|
||||
@ -56,17 +63,30 @@
|
||||
></input-amount>
|
||||
|
||||
<b-row class="mt-5">
|
||||
<b-col>
|
||||
<b-button type="reset" variant="secondary" @click="reset" data-test="button-cancel">
|
||||
<b-col cols="12" lg="6">
|
||||
<b-button
|
||||
block
|
||||
type="reset"
|
||||
variant="secondary"
|
||||
@click="reset"
|
||||
data-test="button-cancel"
|
||||
>
|
||||
{{ $t('form.cancel') }}
|
||||
</b-button>
|
||||
</b-col>
|
||||
<b-col class="text-right">
|
||||
<b-button type="submit" variant="gradido" :disabled="disabled" data-test="button-submit">
|
||||
<b-col cols="12" lg="6" class="text-right mt-4 mt-lg-0">
|
||||
<b-button
|
||||
block
|
||||
type="submit"
|
||||
variant="gradido"
|
||||
:disabled="disabled"
|
||||
data-test="button-submit"
|
||||
>
|
||||
{{ form.id ? $t('form.change') : $t('contribution.submit') }}
|
||||
</b-button>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</div>
|
||||
</b-form>
|
||||
</div>
|
||||
</template>
|
||||
@ -133,6 +153,18 @@ export default {
|
||||
validMaxTime() {
|
||||
return Number(this.validMaxGDD / 20)
|
||||
},
|
||||
noOpenCreation() {
|
||||
if (this.maxGddThisMonth <= 0 && this.maxGddLastMonth <= 0) {
|
||||
return this.$t('contribution.noOpenCreation.allMonth')
|
||||
}
|
||||
if (this.isThisMonth && this.maxGddThisMonth <= 0) {
|
||||
return this.$t('contribution.noOpenCreation.thisMonth')
|
||||
}
|
||||
if (!this.isThisMonth && this.maxGddLastMonth <= 0) {
|
||||
return this.$t('contribution.noOpenCreation.lastMonth')
|
||||
}
|
||||
return ''
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
value() {
|
||||
@ -142,6 +174,9 @@ export default {
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.form-style {
|
||||
min-height: 410px;
|
||||
}
|
||||
span.errors {
|
||||
color: red;
|
||||
}
|
||||
|
||||
@ -57,7 +57,16 @@
|
||||
"yourContribution": "Dein Beitrag zum Gemeinwohl"
|
||||
},
|
||||
"lastContribution": "Letzte Beiträge",
|
||||
"noContributions": {
|
||||
"allContributions": "Es wurden noch keine Beiträge eingereicht.",
|
||||
"myContributions": "Du hast noch keine Beiträge eingereicht."
|
||||
},
|
||||
"noDateSelected": "Wähle irgendein Datum im Monat",
|
||||
"noOpenCreation": {
|
||||
"allMonth": "Für alle beiden Monate ist dein Schöpfungslimit erreicht. Den Nächsten Monat kannst du wieder 1000 GDD Schöpfen.",
|
||||
"lastMonth": "Für den ausgewählten Monat ist das Schöpfungslimit erreicht.",
|
||||
"thisMonth": "Für den aktuellen Monat ist das Schöpfungslimit erreicht."
|
||||
},
|
||||
"selectDate": "Wann war dein Beitrag?",
|
||||
"submit": "Einreichen",
|
||||
"submitted": "Der Beitrag wurde eingereicht.",
|
||||
|
||||
@ -57,7 +57,16 @@
|
||||
"yourContribution": "Your Contributions to the Common Good"
|
||||
},
|
||||
"lastContribution": "Last Contributions",
|
||||
"noContributions": {
|
||||
"allContributions": "No contributions have been submitted yet.",
|
||||
"myContributions": "You have not submitted any entries yet."
|
||||
},
|
||||
"noDateSelected": "Choose any date in the month",
|
||||
"noOpenCreation": {
|
||||
"allMonth": "For all two months your creation limit is reached. The next month you can create 1000 GDD again.",
|
||||
"lastMonth": "The creation limit is reached for the selected month.",
|
||||
"thisMonth": "The creation limit has been reached for the current month."
|
||||
},
|
||||
"selectDate": "When was your contribution?",
|
||||
"submit": "Submit",
|
||||
"submitted": "The contribution was submitted.",
|
||||
|
||||
@ -20,6 +20,10 @@
|
||||
/>
|
||||
</b-tab>
|
||||
<b-tab no-body>
|
||||
<div v-if="items.length === 0">
|
||||
{{ $t('contribution.noContributions.myContributions') }}
|
||||
</div>
|
||||
<div v-else>
|
||||
<contribution-list
|
||||
@closeAllOpenCollapse="closeAllOpenCollapse"
|
||||
:items="items"
|
||||
@ -31,8 +35,13 @@
|
||||
:showPagination="true"
|
||||
:pageSize="pageSize"
|
||||
/>
|
||||
</div>
|
||||
</b-tab>
|
||||
<b-tab no-body>
|
||||
<div v-if="itemsAll.length === 0">
|
||||
{{ $t('contribution.noContributions.allContributions') }}
|
||||
</div>
|
||||
<div v-else>
|
||||
<contribution-list
|
||||
:items="itemsAll"
|
||||
@update-list-contributions="updateListAllContributions"
|
||||
@ -42,6 +51,7 @@
|
||||
:pageSize="pageSizeAll"
|
||||
:allContribution="true"
|
||||
/>
|
||||
</div>
|
||||
</b-tab>
|
||||
</b-tabs>
|
||||
</div>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user