From 90dcbadc52a688dd77ff609b82c91e98566b8456 Mon Sep 17 00:00:00 2001 From: Faruk AYDIN Date: Sat, 18 Feb 2023 16:54:01 +0100 Subject: [PATCH] feat: Implement forgotPassword mutation --- ...50517_add_reset_password_token_to_users.ts | 13 ++++++++++ ...d_reset_password_token_sent_at_to_users.ts | 13 ++++++++++ .../backend/src/graphql/mutation-resolvers.ts | 2 ++ .../graphql/mutations/forgot-password.ee.ts | 24 +++++++++++++++++++ packages/backend/src/graphql/schema.graphql | 9 +++++-- .../backend/src/helpers/authentication.ts | 1 + packages/backend/src/models/user.ts | 10 ++++++++ 7 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 packages/backend/src/db/migrations/20230218150517_add_reset_password_token_to_users.ts create mode 100644 packages/backend/src/db/migrations/20230218150758_add_reset_password_token_sent_at_to_users.ts create mode 100644 packages/backend/src/graphql/mutations/forgot-password.ee.ts diff --git a/packages/backend/src/db/migrations/20230218150517_add_reset_password_token_to_users.ts b/packages/backend/src/db/migrations/20230218150517_add_reset_password_token_to_users.ts new file mode 100644 index 00000000..7d6cc518 --- /dev/null +++ b/packages/backend/src/db/migrations/20230218150517_add_reset_password_token_to_users.ts @@ -0,0 +1,13 @@ +import { Knex } from 'knex'; + +export async function up(knex: Knex): Promise { + return knex.schema.table('users', (table) => { + table.string('reset_password_token'); + }); +} + +export async function down(knex: Knex): Promise { + return knex.schema.table('users', (table) => { + table.dropColumn('reset_password_token'); + }); +} diff --git a/packages/backend/src/db/migrations/20230218150758_add_reset_password_token_sent_at_to_users.ts b/packages/backend/src/db/migrations/20230218150758_add_reset_password_token_sent_at_to_users.ts new file mode 100644 index 00000000..0371d30f --- /dev/null +++ b/packages/backend/src/db/migrations/20230218150758_add_reset_password_token_sent_at_to_users.ts @@ -0,0 +1,13 @@ +import { Knex } from 'knex'; + +export async function up(knex: Knex): Promise { + return knex.schema.table('users', (table) => { + table.timestamp('reset_password_token_sent_at'); + }); +} + +export async function down(knex: Knex): Promise { + return knex.schema.table('users', (table) => { + table.dropColumn('reset_password_token_sent_at'); + }); +} diff --git a/packages/backend/src/graphql/mutation-resolvers.ts b/packages/backend/src/graphql/mutation-resolvers.ts index ecd49dce..ffd2cb55 100644 --- a/packages/backend/src/graphql/mutation-resolvers.ts +++ b/packages/backend/src/graphql/mutation-resolvers.ts @@ -14,6 +14,7 @@ import updateStep from './mutations/update-step'; import deleteStep from './mutations/delete-step'; import createUser from './mutations/create-user.ee'; import updateUser from './mutations/update-user'; +import forgotPassword from './mutations/forgot-password.ee'; import login from './mutations/login'; const mutationResolvers = { @@ -33,6 +34,7 @@ const mutationResolvers = { deleteStep, createUser, updateUser, + forgotPassword, login, }; diff --git a/packages/backend/src/graphql/mutations/forgot-password.ee.ts b/packages/backend/src/graphql/mutations/forgot-password.ee.ts new file mode 100644 index 00000000..a2e2ff86 --- /dev/null +++ b/packages/backend/src/graphql/mutations/forgot-password.ee.ts @@ -0,0 +1,24 @@ +import User from '../../models/user'; + +type Params = { + input: { + email: string; + }; +}; + +const forgotPassword = async (_parent: unknown, params: Params) => { + const { email } = params.input; + + const user = await User.query().findOne({ email }); + + if (!user) { + throw new Error('Email address not found!'); + } + + await user.generateResetPasswordToken(); + // TODO: Send email with reset password link + + return; +}; + +export default forgotPassword; diff --git a/packages/backend/src/graphql/schema.graphql b/packages/backend/src/graphql/schema.graphql index 5d95a4de..07b8b9a0 100644 --- a/packages/backend/src/graphql/schema.graphql +++ b/packages/backend/src/graphql/schema.graphql @@ -50,6 +50,7 @@ type Mutation { deleteStep(input: DeleteStepInput): Step createUser(input: CreateUserInput): User updateUser(input: UpdateUserInput): User + forgotPassword(input: ForgotPasswordInput): Boolean login(input: LoginInput): Auth } @@ -302,8 +303,8 @@ input DeleteStepInput { } input CreateUserInput { - email: String - password: String + email: String! + password: String! } input UpdateUserInput { @@ -311,6 +312,10 @@ input UpdateUserInput { password: String } +input ForgotPasswordInput { + email: String +} + input LoginInput { email: String! password: String! diff --git a/packages/backend/src/helpers/authentication.ts b/packages/backend/src/helpers/authentication.ts index 837a3736..c1368e0a 100644 --- a/packages/backend/src/helpers/authentication.ts +++ b/packages/backend/src/helpers/authentication.ts @@ -30,6 +30,7 @@ const authentication = shield( '*': isAuthenticated, login: allow, createUser: allow, + forgotPassword: allow, }, }, { diff --git a/packages/backend/src/models/user.ts b/packages/backend/src/models/user.ts index a1c92444..5fe668c9 100644 --- a/packages/backend/src/models/user.ts +++ b/packages/backend/src/models/user.ts @@ -5,12 +5,15 @@ import Flow from './flow'; import Step from './step'; import Execution from './execution'; import bcrypt from 'bcrypt'; +import crypto from 'crypto'; class User extends Base { id!: string; email!: string; password!: string; role: string; + resetPasswordToken: string; + resetPasswordTokenSentAt: string; connections?: Connection[]; flows?: Flow[]; steps?: Step[]; @@ -77,6 +80,13 @@ class User extends Base { return bcrypt.compare(password, this.password); } + async generateResetPasswordToken() { + const resetPasswordToken = crypto.randomBytes(64).toString('hex'); + const resetPasswordTokenSentAt = new Date().toISOString(); + + await this.$query().patch({ resetPasswordToken, resetPasswordTokenSentAt }); + } + async generateHash() { this.password = await bcrypt.hash(this.password, 10); }