diff --git a/admin/src/graphql/confirmPendingCreation.js b/admin/src/graphql/confirmPendingCreation.js index 29ac5b4fc..df72cc04d 100644 --- a/admin/src/graphql/confirmPendingCreation.js +++ b/admin/src/graphql/confirmPendingCreation.js @@ -1,7 +1,7 @@ import gql from 'graphql-tag' export const confirmPendingCreation = gql` - mutation ($id: Float!) { + mutation ($id: Int!) { confirmPendingCreation(id: $id) } ` diff --git a/admin/src/graphql/deletePendingCreation.js b/admin/src/graphql/deletePendingCreation.js index e47fa6a8d..edf45fe74 100644 --- a/admin/src/graphql/deletePendingCreation.js +++ b/admin/src/graphql/deletePendingCreation.js @@ -1,7 +1,7 @@ import gql from 'graphql-tag' export const deletePendingCreation = gql` - mutation ($id: Float!) { + mutation ($id: Int!) { deletePendingCreation(id: $id) } ` diff --git a/admin/src/graphql/deleteUser.js b/admin/src/graphql/deleteUser.js index 3b9bc73b2..9e3606bd7 100644 --- a/admin/src/graphql/deleteUser.js +++ b/admin/src/graphql/deleteUser.js @@ -1,7 +1,7 @@ import gql from 'graphql-tag' export const deleteUser = gql` - mutation ($userId: Float!) { + mutation ($userId: Int!) { deleteUser(userId: $userId) } ` diff --git a/admin/src/graphql/unDeleteUser.js b/admin/src/graphql/unDeleteUser.js index 2c6d603b2..b48c8981f 100644 --- a/admin/src/graphql/unDeleteUser.js +++ b/admin/src/graphql/unDeleteUser.js @@ -1,7 +1,7 @@ import gql from 'graphql-tag' export const unDeleteUser = gql` - mutation ($userId: Float!) { + mutation ($userId: Int!) { unDeleteUser(userId: $userId) } ` diff --git a/admin/src/pages/UserSearch.vue b/admin/src/pages/UserSearch.vue index 1fe44eda7..24334e0c9 100644 --- a/admin/src/pages/UserSearch.vue +++ b/admin/src/pages/UserSearch.vue @@ -99,7 +99,7 @@ export default { }, updateDeletedAt(userId, deletedAt) { this.searchResult.find((obj) => obj.userId === userId).deletedAt = deletedAt - this.toastSuccess(this.$t('user_deleted')) + this.toastSuccess(deletedAt ? this.$t('user_deleted') : this.$t('user_recovered')) }, }, watch: { diff --git a/backend/.env.dist b/backend/.env.dist index b1b16972f..3c93f1576 100644 --- a/backend/.env.dist +++ b/backend/.env.dist @@ -40,7 +40,7 @@ EMAIL_SMTP_URL=gmail.com EMAIL_SMTP_PORT=587 EMAIL_LINK_VERIFICATION=http://localhost/checkEmail/{code} EMAIL_LINK_SETPASSWORD=http://localhost/reset/{code} -RESEND_TIME=10 +EMAIL_CODE_VALID_TIME=10 # Webhook WEBHOOK_ELOPAGE_SECRET=secret \ No newline at end of file diff --git a/backend/src/auth/INALIENABLE_RIGHTS.ts b/backend/src/auth/INALIENABLE_RIGHTS.ts index fa9ea2224..348cd5b20 100644 --- a/backend/src/auth/INALIENABLE_RIGHTS.ts +++ b/backend/src/auth/INALIENABLE_RIGHTS.ts @@ -8,4 +8,5 @@ export const INALIENABLE_RIGHTS = [ RIGHTS.SEND_RESET_PASSWORD_EMAIL, RIGHTS.SET_PASSWORD, RIGHTS.QUERY_TRANSACTION_LINK, + RIGHTS.QUERY_OPT_IN, ] diff --git a/backend/src/auth/RIGHTS.ts b/backend/src/auth/RIGHTS.ts index cc108c7d3..f40088779 100644 --- a/backend/src/auth/RIGHTS.ts +++ b/backend/src/auth/RIGHTS.ts @@ -16,6 +16,7 @@ export enum RIGHTS { CREATE_USER = 'CREATE_USER', SEND_RESET_PASSWORD_EMAIL = 'SEND_RESET_PASSWORD_EMAIL', SET_PASSWORD = 'SET_PASSWORD', + QUERY_OPT_IN = 'QUERY_OPT_IN', UPDATE_USER_INFOS = 'UPDATE_USER_INFOS', HAS_ELOPAGE = 'HAS_ELOPAGE', CREATE_TRANSACTION_LINK = 'CREATE_TRANSACTION_LINK', diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index 79101856c..8143e7b92 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -54,8 +54,6 @@ const loginServer = { LOGIN_SERVER_KEY: process.env.LOGIN_SERVER_KEY || 'a51ef8ac7ef1abf162fb7a65261acd7a', } -// TODO: Hannes if I find you... this looks like blasphemy -const resendTime = parseInt(process.env.RESEND_TIME ? process.env.RESEND_TIME : 'null') const email = { EMAIL: process.env.EMAIL === 'true' || false, EMAIL_USERNAME: process.env.EMAIL_USERNAME || 'gradido_email', @@ -67,7 +65,9 @@ const email = { process.env.EMAIL_LINK_VERIFICATION || 'http://localhost/checkEmail/{code}', EMAIL_LINK_SETPASSWORD: process.env.EMAIL_LINK_SETPASSWORD || 'http://localhost/reset-password/{code}', - RESEND_TIME: isNaN(resendTime) ? 10 : resendTime, + EMAIL_CODE_VALID_TIME: process.env.EMAIL_CODE_VALID_TIME + ? parseInt(process.env.EMAIL_CODE_VALID_TIME) || 10 + : 10, } const webhook = { diff --git a/backend/src/graphql/resolver/AdminResolver.ts b/backend/src/graphql/resolver/AdminResolver.ts index 4c902496d..d98b38b7f 100644 --- a/backend/src/graphql/resolver/AdminResolver.ts +++ b/backend/src/graphql/resolver/AdminResolver.ts @@ -127,7 +127,10 @@ export class AdminResolver { @Authorized([RIGHTS.DELETE_USER]) @Mutation(() => Date, { nullable: true }) - async deleteUser(@Arg('userId') userId: number, @Ctx() context: any): Promise { + async deleteUser( + @Arg('userId', () => Int) userId: number, + @Ctx() context: any, + ): Promise { const user = await dbUser.findOne({ id: userId }) // user exists ? if (!user) { @@ -146,7 +149,7 @@ export class AdminResolver { @Authorized([RIGHTS.UNDELETE_USER]) @Mutation(() => Date, { nullable: true }) - async unDeleteUser(@Arg('userId') userId: number): Promise { + async unDeleteUser(@Arg('userId', () => Int) userId: number): Promise { const user = await dbUser.findOne({ id: userId }, { withDeleted: true }) // user exists ? if (!user) { @@ -288,7 +291,7 @@ export class AdminResolver { @Authorized([RIGHTS.DELETE_PENDING_CREATION]) @Mutation(() => Boolean) - async deletePendingCreation(@Arg('id') id: number): Promise { + async deletePendingCreation(@Arg('id', () => Int) id: number): Promise { const entity = await AdminPendingCreation.findOneOrFail(id) const res = await AdminPendingCreation.delete(entity) return !!res @@ -296,7 +299,10 @@ export class AdminResolver { @Authorized([RIGHTS.CONFIRM_PENDING_CREATION]) @Mutation(() => Boolean) - async confirmPendingCreation(@Arg('id') id: number, @Ctx() context: any): Promise { + async confirmPendingCreation( + @Arg('id', () => Int) id: number, + @Ctx() context: any, + ): Promise { const pendingCreation = await AdminPendingCreation.findOneOrFail(id) const moderatorUser = context.user if (moderatorUser.id === pendingCreation.userId) diff --git a/backend/src/graphql/resolver/UserResolver.ts b/backend/src/graphql/resolver/UserResolver.ts index 311916aad..33fba47a2 100644 --- a/backend/src/graphql/resolver/UserResolver.ts +++ b/backend/src/graphql/resolver/UserResolver.ts @@ -156,15 +156,11 @@ const createEmailOptIn = async ( emailOptInTypeId: EMAIL_OPT_IN_REGISTER, }) if (emailOptIn) { - const timeElapsed = Date.now() - new Date(emailOptIn.updatedAt).getTime() - if (timeElapsed <= parseInt(CONFIG.RESEND_TIME.toString()) * 60 * 1000) { - throw new Error( - 'email already sent less than ' + parseInt(CONFIG.RESEND_TIME.toString()) + ' minutes ago', - ) - } else { - emailOptIn.updatedAt = new Date() - emailOptIn.resendCount++ + if (isOptInCodeValid(emailOptIn)) { + throw new Error(`email already sent less than $(CONFIG.EMAIL_CODE_VALID_TIME} minutes ago`) } + emailOptIn.updatedAt = new Date() + emailOptIn.resendCount++ } else { emailOptIn = new LoginEmailOptIn() emailOptIn.verificationCode = random(64) @@ -185,17 +181,13 @@ const getOptInCode = async (loginUserId: number): Promise => { emailOptInTypeId: EMAIL_OPT_IN_RESET_PASSWORD, }) - // Check for 10 minute delay + // Check for `CONFIG.EMAIL_CODE_VALID_TIME` minute delay if (optInCode) { - const timeElapsed = Date.now() - new Date(optInCode.updatedAt).getTime() - if (timeElapsed <= parseInt(CONFIG.RESEND_TIME.toString()) * 60 * 1000) { - throw new Error( - 'email already sent less than ' + parseInt(CONFIG.RESEND_TIME.toString()) + ' minutes ago', - ) - } else { - optInCode.updatedAt = new Date() - optInCode.resendCount++ + if (isOptInCodeValid(optInCode)) { + throw new Error(`email already sent less than $(CONFIG.EMAIL_CODE_VALID_TIME} minutes ago`) } + optInCode.updatedAt = new Date() + optInCode.resendCount++ } else { optInCode = new LoginEmailOptIn() optInCode.verificationCode = random(64) @@ -497,10 +489,9 @@ export class UserResolver { throw new Error('Could not login with emailVerificationCode') }) - // Code is only valid for 10minutes - const timeElapsed = Date.now() - new Date(optInCode.updatedAt).getTime() - if (timeElapsed > 10 * 60 * 1000) { - throw new Error('Code is older than 10 minutes') + // Code is only valid for `CONFIG.EMAIL_CODE_VALID_TIME` minutes + if (!isOptInCodeValid(optInCode)) { + throw new Error(`email already more than $(CONFIG.EMAIL_CODE_VALID_TIME} minutes ago`) } // load user @@ -573,6 +564,17 @@ export class UserResolver { return true } + @Authorized([RIGHTS.QUERY_OPT_IN]) + @Query(() => Boolean) + async queryOptIn(@Arg('optIn') optIn: string): Promise { + const optInCode = await LoginEmailOptIn.findOneOrFail({ verificationCode: optIn }) + // Code is only valid for `CONFIG.EMAIL_CODE_VALID_TIME` minutes + if (!isOptInCodeValid(optInCode)) { + throw new Error(`email was sent more than $(CONFIG.EMAIL_CODE_VALID_TIME} minutes ago`) + } + return true + } + @Authorized([RIGHTS.UPDATE_USER_INFOS]) @Mutation(() => Boolean) async updateUserInfos( @@ -674,3 +676,7 @@ export class UserResolver { return hasElopageBuys(userEntity.email) } } +function isOptInCodeValid(optInCode: LoginEmailOptIn) { + const timeElapsed = Date.now() - new Date(optInCode.updatedAt).getTime() + return timeElapsed <= CONFIG.EMAIL_CODE_VALID_TIME * 60 * 1000 +} diff --git a/backend/src/seeds/graphql/mutations.ts b/backend/src/seeds/graphql/mutations.ts index cab864c4e..298d56bdb 100644 --- a/backend/src/seeds/graphql/mutations.ts +++ b/backend/src/seeds/graphql/mutations.ts @@ -96,7 +96,7 @@ export const createPendingCreation = gql` ` export const confirmPendingCreation = gql` - mutation ($id: Float!) { + mutation ($id: Int!) { confirmPendingCreation(id: $id) } ` diff --git a/frontend/src/components/GddSend/TransactionForm.vue b/frontend/src/components/GddSend/TransactionForm.vue index 845e749cc..ec4aff4d3 100644 --- a/frontend/src/components/GddSend/TransactionForm.vue +++ b/frontend/src/components/GddSend/TransactionForm.vue @@ -19,7 +19,7 @@

{{ $t('gdd_per_link.header') }}

- {{ $t('gdd_per_link.sentence_1') }} + {{ $t('gdd_per_link.choose-amount') }}
diff --git a/frontend/src/components/LinkInformations/RedeemInformation.vue b/frontend/src/components/LinkInformations/RedeemInformation.vue index 71c0b27ad..bdc17db9a 100644 --- a/frontend/src/components/LinkInformations/RedeemInformation.vue +++ b/frontend/src/components/LinkInformations/RedeemInformation.vue @@ -2,7 +2,7 @@

- {{ user.firstName }} + {{ firstName }} {{ $t('transaction-link.send_you') }} {{ amount | GDD }}

{{ memo }} @@ -13,7 +13,7 @@ export default { name: 'RedeemInformation', props: { - user: { type: Object, required: true }, + firstName: { type: String, required: true }, amount: { type: String, required: true }, memo: { type: String, required: true, default: '' }, }, diff --git a/frontend/src/components/LinkInformations/RedeemLoggedOut.vue b/frontend/src/components/LinkInformations/RedeemLoggedOut.vue index 15d9a985b..20b75869a 100644 --- a/frontend/src/components/LinkInformations/RedeemLoggedOut.vue +++ b/frontend/src/components/LinkInformations/RedeemLoggedOut.vue @@ -1,6 +1,6 @@