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/UserResolver.ts b/backend/src/graphql/resolver/UserResolver.ts index 04c5fdf63..a56b67945 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) @@ -492,10 +484,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 @@ -568,6 +559,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( @@ -669,3 +671,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/frontend/src/graphql/queries.js b/frontend/src/graphql/queries.js index 6d2b54b62..d330d84f3 100644 --- a/frontend/src/graphql/queries.js +++ b/frontend/src/graphql/queries.js @@ -119,6 +119,12 @@ export const communities = gql` } ` +export const queryOptIn = gql` + query($optIn: String!) { + queryOptIn(optIn: $optIn) + } +` + export const queryTransactionLink = gql` query($code: String!) { queryTransactionLink(code: $code) { diff --git a/frontend/src/locales/de.json b/frontend/src/locales/de.json index 03d8a0fe6..5bdb5f9ab 100644 --- a/frontend/src/locales/de.json +++ b/frontend/src/locales/de.json @@ -220,13 +220,14 @@ "uppercase": "Großbuchstabe erforderlich." }, "thx": { - "activateEmail": "Dein Konto wurde noch nicht aktiviert. Bitte überprüfe deine E-Mail und klicke den Aktivierungslink!", + "activateEmail": "Dein Konto wurde noch nicht aktiviert. Bitte überprüfe deine E-Mail und klicke den Aktivierungslink oder fordere einen neuen Aktivierungslink über die Password Reset Seite.", "checkEmail": "Deine E-Mail wurde erfolgreich verifiziert. Du kannst dich jetzt anmelden.", "email": "Wir haben dir eine E-Mail gesendet.", "emailActivated": "Danke dass Du deine E-Mail 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.", + "resetPassword": "Den Code den Du genutzt hast ist zu alt bitte fordere ein neuen über die Passwort Reset Seite an.", "title": "Danke!" } }, diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index 26c8db9a2..c8c10a66c 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -220,13 +220,14 @@ "uppercase": "One uppercase letter required." }, "thx": { - "activateEmail": "Your account has not been activated yet, please check your emails and click the activation link!", + "activateEmail": "Your account has not been activated yet, please check your emails and click the activation link or order a new activation link over the password reset page.", "checkEmail": "Your email has been successfully verified. You can sign in now.", "email": "We have sent you an email.", "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.", + "resetPassword": "The code you used was to old please order a new on over the password reset page.", "title": "Thank you!" } }, diff --git a/frontend/src/pages/ResetPassword.spec.js b/frontend/src/pages/ResetPassword.spec.js index 814d97bcb..f5d672c99 100644 --- a/frontend/src/pages/ResetPassword.spec.js +++ b/frontend/src/pages/ResetPassword.spec.js @@ -9,6 +9,7 @@ import { toastErrorSpy } from '@test/testSetup' const localVue = global.localVue const apolloMutationMock = jest.fn() +const apolloQueryMock = jest.fn().mockResolvedValue() const routerPushMock = jest.fn() @@ -40,6 +41,7 @@ const mocks = { }, $apollo: { mutate: apolloMutationMock, + query: apolloQueryMock, }, } diff --git a/frontend/src/pages/ResetPassword.vue b/frontend/src/pages/ResetPassword.vue index b0194c0ba..7532953ed 100644 --- a/frontend/src/pages/ResetPassword.vue +++ b/frontend/src/pages/ResetPassword.vue @@ -49,6 +49,7 @@