Статьи

Обработка изменений пароля и электронной почты в вашем Rails API

Это вторая часть нашей серии статей об аутентификации с нуля с использованием JWT. Первая часть серии может быть найдена здесь . В предыдущем уроке мы увидели краткий обзор JWT, различных механизмов аутентификации и набора базовых API аутентификации, таких как регистрация, подтверждение и вход в систему. В этой части мы увидим следующий набор API, таких как пароль (сброс и изменение) и обновление электронной почты.

Эта серия — больше, чем учебник JWT. Его главная цель — увидеть, как создать собственное решение для аутентификации с нуля, а JWT — это именно тот метод, который мы выбрали.

Мы продолжим работу над нашим примером приложения, которое мы разработали в первой части, которую можно найти здесь . Если вы хотите следовать, вы можете проверить ветку part-i

пароль

API, который мы рассмотрим здесь — это забытая последовательность паролей. Поток генерирует токен сброса пароля вместе с конечной точкой, чтобы пользователь мог проверить токен. Эта конечная точка вызывается, когда пользователь щелкает ссылку для сброса пароля, отправленную ему по электронной почте. Эта последняя конечная точка предназначена для окончательной смены пароля.

Забыл пароль

Конечная точка забытого пароля генерирует токен сброса пароля, сохраняет его в базе данных и отправляет электронное письмо пользователю. Это похоже на модуль инструкций подтверждения, который мы видели в первой части. Начнем с добавления столбцов, необходимых для сброса пароля. Бегать,

 rails g migration AddPasswordResetColumnsToUser

И в сгенерированный файл миграции добавьте следующее:

 add_column :users, :reset_password_token, :string
add_column :users, :reset_password_sent_at, :datetime

Этих двух столбцов достаточно для этой цели. reset_password_tokenreset_password_sent_at Давайте добавим конечные точки сейчас. Начнем с генерации пароля контроллера:

 rails g controller passwords

Добавьте следующие маршруты в ваш файл config / rout.rb :

 post 'password/forgot', to: 'password#forgot'
post 'password/reset', to: 'password#reset'

Теперь давайте добавим соответствующее действие для вышеупомянутых маршрутов в controllers / password_controller.rb :

 ...
  def forgot
    if params[:email].blank?
      return render json: {error: 'Email not present'}
    end

    user = User.find_by(email: email.downcase)

    if user.present? && user.confirmed_at?
      user.generate_password_token!
      # SEND EMAIL HERE
      render json: {status: 'ok'}, status: :ok
    else
      render json: {error: ['Email address not found. Please check and try again.']}, status: :not_found
    end
  end

  def reset
    token = params[:token].to_s

    if params[:email].blank?
      return render json: {error: 'Token not present'}
    end

    user = User.find_by(reset_password_token: token)

    if user.present? && user.password_token_valid?
      if user.reset_password!(params[:password])
        render json: {status: 'ok'}, status: :ok
      else
        render json: {error: user.errors.full_messages}, status: :unprocessable_entity
      end
    else
      render json: {error:  ['Link not valid or expired. Try generating a new link.']}, status: :not_found
    end
  end
 ...

Давай пройдемся по этому вопросу быстро. В forgot Если пользователь найден и подтвержден, вызовите generate_password_token Часть отправки электронной почты пропущена, но обязательно включите в нее password_reset_token В действии resetpassword_token_valid? и сбросьте пароль с помощью reset_password Эти методы еще не добавлены в пользовательскую модель, давайте сделаем это сейчас:

Добавьте следующие методы в models / user.rb :

 ...
def generate_password_token!
  self.reset_password_token = generate_token
  self.reset_password_sent_at = Time.now.utc
  save!
end

def password_token_valid?
  (self.reset_password_sent_at + 4.hours) > Time.now.utc
end

def reset_password!(password)
  self.reset_password_token = nil
  self.password = password
  save!
end

private

def generate_token
  SecureRandom.hex(10)
end
...

В generate_password_token! В методе мы генерируем токен с помощью метода generate_tokenreset_password_tokenreset_password_sent_at В password_token_valid? Убедитесь, что токен отправлен в течение последних 4 часов, что является истечением срока действия пароля. Вы можете изменить его, как считаете нужным. reset_password! Метод обновляет новый пароль пользователя и аннулирует токен сброса.

Сброс пароля установлен. Вы можете проверить это, отправив почтовый запрос /passwords/forgot/passwords/reset Давайте добавим ссылку для обновления пароля сейчас.

Обновить пароль

Чтобы добавить пароль для обновления, добавьте маршрут в ваш файл маршрутов:

 put 'password/update', to: 'password#update'

Вот соответствующее действие в PasswordsController

 def update
  if !params[:password].present?
    render json: {error: 'Password not present'}, status: :unprocessable_entity
    return
  end

  if current_user.reset_password(params[:password])
    render json: {status: 'ok'}, status: :ok
  else
    render json: {errors: current_user.errors.full_messages}, status: :unprocessable_entity
  end
end

Действие по update Получите пароль от параметра и сохраните его в БД, используя метод reset_password Теперь вы можете проверить URL обновления пароля, отправив запрос PUT/password/update Давайте перейдем к следующей большой функциональности, Email Update.

Обновление электронной почты

Обновление по электронной почте позволяет пользователю обновить свою основную электронную почту в своей учетной записи. По запросу мы должны проверить, используется ли электронная почта любым другим пользователем. Если электронная почта в порядке, сохраните ее и отправьте письмо с подтверждением на новую электронную почту. После подтверждения мы заменим основной адрес электронной почты новым и удалим токен.

Итак, всего существует два API: один для отправки запроса на обновление электронной почты, второй для фактического обновления электронной почты. Давайте начнем.

Начните с миграции, чтобы добавить необходимый столбец для поддержки этого модуля. Генерация миграции:

 rails g migration AddUnconfirmedEmailTouser

Добавьте следующий контент и запустите rake db:migrate

 add_column :users, :unconfirmed_email, :string

Обновить

Теперь давайте обновим маршруты для этих двух конечных точек. Добавьте эти строки в config / rout.rb :

 ...
resources :users, only: [:create, :update] do
    collection do
        post 'email_update'
...

Добавьте соответствующие действия в UsersController

 def update
    if current_user.update_new_email!(@new_email)
      # SEND EMAIL HERE
      render json: { status: 'Email Confirmation has been sent to your new Email.' }, status: :ok
    else
      render json: { errors: current_user.errors.values.flatten.compact }, status: :bad_request
    end
end

Также добавьте before_actionprivate

 class UsersController < ApplicationController
    before_action :validate_email_update, only: :update
    ...
    ...

    private
    def validate_email_update
      @new_email = params[:email].to_s.downcase

      if @new_email.blank?
        return render json: { status: 'Email cannot be blank' }, status: :bad_request
      end

      if  @new_email == current_user.email
        return render json: { status: 'Current Email and New email cannot be the same' }, status: :bad_request
      end

      if User.email_used?(@new_email)
        return render json: { error: 'Email is already in use.'] }, status: :unprocessable_entity
      end
    end
    ...

Здесь мы проверяем, используется ли запрошенная электронная почта, и совпадает ли электронная почта с той, что уже есть в учетной записи. Если все хорошо, звоните update_new_email! и отправьте письмо. Обратите внимание, что электронное письмо должно быть отправлено пользователю unconfirmed_email Мы использовали несколько новых методов модели, поэтому давайте определимся с ними. В models / user.rb добавьте следующие функции:

 def update_new_email!(email)
  self.unconfirmed_email = email
  self.generate_confirmation_instructions
  save
end

def self.email_used?(email)
  existing_user = find_by("email = ?", email)

  if existing_user.present?
    return true
  else
    waiting_for_confirmation = find_by("unconfirmed_email = ?", email)
    return waiting_for_confirmation.present? && waiting_for_confirmation.confirmation_token_valid?
  end
end

Здесь, в email_used? Помимо проверки того, используется ли электронная почта главным образом в каких-либо учетных записях, мы также проверяем, обновляется ли она и ожидает подтверждения. Это может быть удалено в зависимости от ваших потребностей. confirmation_token_valid? метод был добавлен в первой части этого урока.

Теперь вы можете проверить этот маршрут, отправив запрос POST/users/updateemail

Обновление электронной почты

Теперь давайте добавим действие для конечной точки обновления электронной почты. Добавьте этот код в UsersController

 def email_update
  token = params[:token].to_s
  user = User.find_by(confirmation_token: token)

  if !user || !user.confirmation_token_valid?
    render json: {error: 'The email link seems to be invalid / expired. Try requesting for a new one.'}, status: :not_found
  else
    user.update_new_email!
    render json: {status: 'Email updated successfully'}, status: :ok
  end
end

Это действие довольно простое. Получить пользователя по токену и посмотреть, является ли токен действительным. Если так, обновите электронную почту и ответьте. Давайте добавим update_new_email! Метод для пользовательской модели:

 def update_new_email!
  self.email = self.unconfirmed_email
  self.unconfirmed_email = nil
  self.mark_as_confirmed!
end

Здесь мы заменяем основной адрес электронной почты обновленным адресом электронной почты и устанавливаем поле обновленного адреса электронной почты nil Кроме того, вызовите mark_as_confirmed! который мы добавили в предыдущей части серии. Этот метод аннулирует токен подтверждения и устанавливает подтвержденное значение. Конечная точка обновления электронной почты также работает. Попробуйте отправить запрос POST/users/email_update

Вывод

На этом мы подошли к завершению нашего учебника из двух частей по аутентификации с нуля для Rails API. Напомним, что мы реализовали модули аутентификации, подтверждения, пароля и повторного подтверждения Devise. Не слишком потрепанный.

Код, используемый в этом руководстве, доступен здесь . Код, с которого начинается данное руководство, доступен в ветке part-i .

Я надеюсь, что это руководство помогло вам понять аутентификацию и развернуть собственную систему аутентификации. Спасибо за прочтение.