diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index f21082d1d..7a55de8e8 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -51,7 +51,6 @@ const email = { EMAIL_PASSWORD: process.env.EMAIL_PASSWORD || 'xxx', EMAIL_SMTP_URL: process.env.EMAIL_SMTP_URL || 'gmail.com', EMAIL_SMTP_PORT: process.env.EMAIL_SMTP_PORT || '587', - EMAIL_LINK_VERIFICATION: process.env.EMAIL_LINK_VERIFICATION || 'http://localhost/vue/checkEmail/$1', } diff --git a/backend/src/graphql/directive/isAuthorized.ts b/backend/src/graphql/directive/isAuthorized.ts index d72f19456..6245ef8ba 100644 --- a/backend/src/graphql/directive/isAuthorized.ts +++ b/backend/src/graphql/directive/isAuthorized.ts @@ -2,9 +2,6 @@ import { AuthChecker } from 'type-graphql' -import CONFIG from '../../config' -import { apiGet } from '../../apis/HttpRequest' - import decode from '../../jwt/decode' import encode from '../../jwt/encode' @@ -13,7 +10,7 @@ const isAuthorized: AuthChecker = async ( ) => { if (context.token) { const decoded = decode(context.token) - context.pubKey = decoded.pubKey + context.pubKey = Buffer.from(decoded.pubKey).toString('hex') context.setHeaders.push({ key: 'token', value: encode(decoded.pubKey) }) return true } diff --git a/backend/src/graphql/model/User.ts b/backend/src/graphql/model/User.ts index 08651ae17..5b7682e01 100644 --- a/backend/src/graphql/model/User.ts +++ b/backend/src/graphql/model/User.ts @@ -10,15 +10,17 @@ export class User { @PrimaryGeneratedColumn() id: number */ - constructor(json: any) { - this.email = json.email - this.firstName = json.first_name - this.lastName = json.last_name - this.username = json.username - this.description = json.description - this.pubkey = json.public_hex - this.language = json.language - this.publisherId = json.publisher_id + constructor(json?: any) { + if (json) { + this.email = json.email + this.firstName = json.first_name + this.lastName = json.last_name + this.username = json.username + this.description = json.description + this.pubkey = json.public_hex + this.language = json.language + this.publisherId = json.publisher_id + } } @Field(() => String) diff --git a/backend/src/graphql/resolver/TransactionResolver.ts b/backend/src/graphql/resolver/TransactionResolver.ts index 968ce9d4c..ae9e318ae 100644 --- a/backend/src/graphql/resolver/TransactionResolver.ts +++ b/backend/src/graphql/resolver/TransactionResolver.ts @@ -613,9 +613,6 @@ export class TransactionResolver { await queryRunner.commitTransaction() } catch (e) { await queryRunner.rollbackTransaction() - throw e - } finally { - await queryRunner.release() // TODO: This is broken code - we should never correct an autoincrement index in production // according to dario it is required tho to properly work. The index of the table is used as // index for the transaction which requires a chain without gaps @@ -627,6 +624,9 @@ export class TransactionResolver { // eslint-disable-next-line no-console console.log('problems with reset auto increment: %o', error) }) + throw e + } finally { + await queryRunner.release() } // send notification email // TODO: translate diff --git a/backend/src/graphql/resolver/UserResolver.ts b/backend/src/graphql/resolver/UserResolver.ts index 5c4625938..25f83bb09 100644 --- a/backend/src/graphql/resolver/UserResolver.ts +++ b/backend/src/graphql/resolver/UserResolver.ts @@ -22,14 +22,14 @@ import { } from '../../middleware/klicktippMiddleware' import { CheckEmailResponse } from '../model/CheckEmailResponse' import { UserSettingRepository } from '../../typeorm/repository/UserSettingRepository' +import { LoginUserRepository } from '../../typeorm/repository/LoginUser' import { Setting } from '../enum/Setting' import { UserRepository } from '../../typeorm/repository/User' import { LoginUser } from '@entity/LoginUser' -import { LoginElopageBuys } from '@entity/LoginElopageBuys' import { LoginUserBackup } from '@entity/LoginUserBackup' import { LoginEmailOptIn } from '@entity/LoginEmailOptIn' import { sendEMail } from '../../util/sendEMail' -import { LoginUserRepository } from '../../typeorm/repository/LoginUser' +import { LoginElopageBuysRepository } from '../../typeorm/repository/LoginElopageBuys' // eslint-disable-next-line @typescript-eslint/no-var-requires const sodium = require('sodium-native') @@ -201,33 +201,32 @@ export class UserResolver { @Ctx() context: any, ): Promise { email = email.trim().toLowerCase() - const result = await apiPost(CONFIG.LOGIN_API_URL + 'unsecureLogin', { email, password }) - - // if there is no user, throw an authentication error - if (!result.success) { - throw new Error(result.data) - } - - context.setHeaders.push({ - key: 'token', - value: encode(result.data.user.public_hex), + // const result = await apiPost(CONFIG.LOGIN_API_URL + 'unsecureLogin', { email, password }) + // UnsecureLogin + const loginUserRepository = getCustomRepository(LoginUserRepository) + const loginUser = await loginUserRepository.findByEmail(email).catch(() => { + throw new Error('No user with this credentials') }) - const user = new User(result.data.user) - // Hack: Database Field is not validated properly and not nullable - if (user.publisherId === 0) { - user.publisherId = undefined + const passwordHash = SecretKeyCryptographyCreateKey(email, password) // return short and long hash + const loginUserPassword = BigInt(loginUser.password.toString()) + if (loginUserPassword !== passwordHash[0].readBigUInt64LE()) { + throw new Error('No user with this credentials') } - user.hasElopage = result.data.hasElopage - // read additional settings from settings table + // TODO: If user has no pubKey Create it again and update user. + const userRepository = getCustomRepository(UserRepository) let userEntity: void | DbUser - userEntity = await userRepository.findByPubkeyHex(user.pubkey).catch(() => { + const loginUserPubKey = loginUser.pubKey + const loginUserPubKeyString = loginUserPubKey.toString('hex') + userEntity = await userRepository.findByPubkeyHex(loginUserPubKeyString).catch(() => { + // User not stored in state_users + // TODO: Check with production data - email is unique which can cause problems userEntity = new DbUser() - userEntity.firstName = user.firstName - userEntity.lastName = user.lastName - userEntity.username = user.username - userEntity.email = user.email - userEntity.pubkey = Buffer.from(user.pubkey, 'hex') + userEntity.firstName = loginUser.firstName + userEntity.lastName = loginUser.lastName + userEntity.username = loginUser.username + userEntity.email = loginUser.email + userEntity.pubkey = loginUser.pubKey userRepository.save(userEntity).catch(() => { throw new Error('error by save userEntity') @@ -237,16 +236,28 @@ export class UserResolver { throw new Error('error with cannot happen') } - // Save publisherId if Elopage is not yet registered + const user = new User() + user.email = email + user.firstName = loginUser.firstName + user.lastName = loginUser.lastName + user.username = loginUser.username + user.description = loginUser.description + user.pubkey = loginUserPubKeyString + user.language = loginUser.language + + // Elopage Status & Stored PublisherId + user.hasElopage = await this.hasElopage({ pubKey: loginUserPubKeyString }) if (!user.hasElopage && publisherId) { user.publisherId = publisherId - + // TODO: Check if we can use updateUserInfos + // await this.updateUserInfos({ publisherId }, { pubKey: loginUser.pubKey }) const loginUserRepository = getCustomRepository(LoginUserRepository) const loginUser = await loginUserRepository.findOneOrFail({ email: userEntity.email }) loginUser.publisherId = publisherId loginUserRepository.save(loginUser) } + // coinAnimation const userSettingRepository = getCustomRepository(UserSettingRepository) const coinanimation = await userSettingRepository .readBoolean(userEntity.id, Setting.COIN_ANIMATION) @@ -254,6 +265,12 @@ export class UserResolver { throw new Error(error) }) user.coinanimation = coinanimation + + context.setHeaders.push({ + key: 'token', + value: encode(loginUser.pubKey), + }) + return user } @@ -537,7 +554,7 @@ export class UserResolver { await queryRunner.startTransaction('READ UNCOMMITTED') try { - if (coinanimation) { + if (coinanimation !== null && coinanimation !== undefined) { queryRunner.manager .getCustomRepository(UserSettingRepository) .setOrUpdate(userEntity.id, Setting.COIN_ANIMATION, coinanimation.toString()) @@ -609,7 +626,8 @@ export class UserResolver { return false } - const elopageBuyCount = await LoginElopageBuys.count({ payerEmail: userEntity.email }) + const loginElopageBuysRepository = getCustomRepository(LoginElopageBuysRepository) + const elopageBuyCount = await loginElopageBuysRepository.count({ payerEmail: userEntity.email }) return elopageBuyCount > 0 } } diff --git a/backend/src/typeorm/repository/LoginElopageBuys.ts b/backend/src/typeorm/repository/LoginElopageBuys.ts new file mode 100644 index 000000000..15f2a8492 --- /dev/null +++ b/backend/src/typeorm/repository/LoginElopageBuys.ts @@ -0,0 +1,5 @@ +import { EntityRepository, Repository } from 'typeorm' +import { LoginElopageBuys } from '@entity/LoginElopageBuys' + +@EntityRepository(LoginElopageBuys) +export class LoginElopageBuysRepository extends Repository {} diff --git a/backend/src/typeorm/repository/LoginUser.ts b/backend/src/typeorm/repository/LoginUser.ts index d0db007d0..65ac6f67b 100644 --- a/backend/src/typeorm/repository/LoginUser.ts +++ b/backend/src/typeorm/repository/LoginUser.ts @@ -2,4 +2,10 @@ import { EntityRepository, Repository } from 'typeorm' import { LoginUser } from '@entity/LoginUser' @EntityRepository(LoginUser) -export class LoginUserRepository extends Repository {} +export class LoginUserRepository extends Repository { + async findByEmail(email: string): Promise { + return this.createQueryBuilder('loginUser') + .where('loginUser.email = :email', { email }) + .getOneOrFail() + } +} diff --git a/backend/src/typeorm/repository/User.ts b/backend/src/typeorm/repository/User.ts index 441c1b2c8..e127c179c 100644 --- a/backend/src/typeorm/repository/User.ts +++ b/backend/src/typeorm/repository/User.ts @@ -9,6 +9,15 @@ export class UserRepository extends Repository { .getOneOrFail() } + async findByPubkeyHexBuffer(pubkeyHexBuffer: Buffer): Promise { + const pubKeyString = pubkeyHexBuffer.toString('hex') + return await this.findByPubkeyHex(pubKeyString) + } + + async findByEmail(email: string): Promise { + return this.createQueryBuilder('user').where('user.email = :email', { email }).getOneOrFail() + } + async getUsersIndiced(userIds: number[]): Promise { if (!userIds.length) return [] const users = await this.createQueryBuilder('user') diff --git a/frontend/src/locales/de.json b/frontend/src/locales/de.json index 0dd3ba926..faa61886d 100644 --- a/frontend/src/locales/de.json +++ b/frontend/src/locales/de.json @@ -46,6 +46,7 @@ "change-password": "Fehler beim Ändern des Passworts", "error": "Fehler", "no-account": "Leider konnten wir keinen Account finden mit diesen Daten!", + "no-email-verify": "Die Email wurde noch nicht bestätigt, bitte überprüfe deine Emails und klicke auf den Aktivierungslink!", "session-expired": "Sitzung abgelaufen!" }, "form": { @@ -180,9 +181,12 @@ "uppercase": "Ein Großbuchstabe erforderlich." }, "thx": { + "activateEmail": "Deine Email wurde noch nicht aktiviert, bitte überprüfe deine Email und Klicke den Aktivierungslink!", "checkEmail": "Deine Email würde erfolgreich verifiziert.", "email": "Wir haben dir eine eMail gesendet.", - "register": "Du bist jetzt registriert.", + "emailActivated": "Danke dass Du deine Email bestätigt hast.", + "errorTitle": "Achtung!", + "register": "Du bist jetzt registriert, bitte überprüfe deine Emails und klicke auf den Aktivierungslink.", "reset": "Dein Passwort wurde geändert.", "title": "Danke!" } diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index 99fcd46a7..91e25f61d 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -46,6 +46,7 @@ "change-password": "Error while changing password", "error": "Error", "no-account": "Unfortunately we could not find an account to the given data!", + "no-email-verify": "Your email is not activated yet, please check your emails and click the activation link!", "session-expired": "The session expired" }, "form": { @@ -180,9 +181,12 @@ "uppercase": "One uppercase letter required." }, "thx": { + "activateEmail": "Your email has not been activated yet, please check your emails and click the activation link!", "checkEmail": "Your email has been successfully verified.", "email": "We have sent you an email.", - "register": "You are registred now.", + "emailActivated": "Thank you your email has been activated.", + "errorTitle": "Attention!", + "register": "You are registered now, please check your emails and click the activation link.", "reset": "Your password has been changed.", "title": "Thank you!" } diff --git a/frontend/src/routes/routes.js b/frontend/src/routes/routes.js index f4f0dfe04..a3c1389ce 100755 --- a/frontend/src/routes/routes.js +++ b/frontend/src/routes/routes.js @@ -47,7 +47,7 @@ const routes = [ path: '/thx/:comingFrom', component: () => import('../views/Pages/thx.vue'), beforeEnter: (to, from, next) => { - const validFrom = ['password', 'reset', 'register'] + const validFrom = ['password', 'reset', 'register', 'login'] if (!validFrom.includes(from.path.split('/')[1])) { next({ path: '/login' }) } else { diff --git a/frontend/src/views/Pages/Login.vue b/frontend/src/views/Pages/Login.vue index de1ae993a..45e700099 100755 --- a/frontend/src/views/Pages/Login.vue +++ b/frontend/src/views/Pages/Login.vue @@ -104,9 +104,14 @@ export default { this.$router.push('/overview') loader.hide() }) - .catch(() => { + .catch((error) => { + if (!error.message.includes('user email not validated')) { + this.$toasted.error(this.$t('error.no-account')) + } else { + // : this.$t('error.no-email-verify') + this.$router.push('/thx/login') + } loader.hide() - this.$toasted.error(this.$t('error.no-account')) }) }, }, diff --git a/frontend/src/views/Pages/Register.vue b/frontend/src/views/Pages/Register.vue index 00114eb04..ea4000cff 100755 --- a/frontend/src/views/Pages/Register.vue +++ b/frontend/src/views/Pages/Register.vue @@ -161,6 +161,7 @@ import InputEmail from '../../components/Inputs/InputEmail.vue' import InputPasswordConfirmation from '../../components/Inputs/InputPasswordConfirmation.vue' import LanguageSwitchSelect from '../../components/LanguageSwitchSelect.vue' import { registerUser } from '../../graphql/mutations' +import { localeChanged } from 'vee-validate' import { getCommunityInfoMixin } from '../../mixins/getCommunityInfo' export default { @@ -189,6 +190,9 @@ export default { methods: { updateLanguage(e) { this.language = e + this.$store.commit('language', this.language) + this.$i18n.locale = this.language + localeChanged(this.language) }, getValidationState({ dirty, validated, valid = null }) { return dirty || validated ? valid : null diff --git a/frontend/src/views/Pages/thx.vue b/frontend/src/views/Pages/thx.vue index 9d9143456..5884cc61c 100644 --- a/frontend/src/views/Pages/thx.vue +++ b/frontend/src/views/Pages/thx.vue @@ -4,10 +4,12 @@
-

{{ $t('site.thx.title') }}

+

{{ $t(displaySetup.headline) }}

{{ $t(displaySetup.subtitle) }}


- {{ $t(displaySetup.button) }} + + {{ $t(displaySetup.button) }} +
@@ -17,25 +19,33 @@