From 0a512fc0cd4b8dee94648167e154eb3c09d31c6b Mon Sep 17 00:00:00 2001 From: Aidan Cornelius-Bell Date: Thu, 10 Oct 2024 08:23:29 +1030 Subject: [PATCH] 2FA Support --- Gemfile | 3 ++ Gemfile.lock | 13 +++++ app/controllers/application_controller.rb | 1 + app/controllers/two_factor_controller.rb | 36 ++++++++++++++ app/controllers/users/sessions_controller.rb | 19 +++++++ .../two_factor_authentication_controller.rb | 22 ++++++++ app/helpers/two_factor_helper.rb | 2 + app/models/user.rb | 47 +++++++++++++++++- app/views/devise/registrations/edit.html.erb | 5 ++ app/views/devise/sessions/new.html.erb | 5 ++ app/views/pubview/join.html.erb | 9 ++-- app/views/two_factor/backup_codes.html.erb | 19 +++++++ app/views/two_factor/create.html.erb | 2 + app/views/two_factor/destroy.html.erb | 2 + app/views/two_factor/new.html.erb | 30 +++++++++++ .../two_factor_authentication/show.html.erb | 16 ++++++ config/application.rb | 4 ++ config/credentials.yml.enc | 2 +- .../images => config/initializers}/.DS_Store | Bin config/initializers/devise.rb | 2 +- config/routes.rb | 13 ++++- .../20241009205529_add_two_factor_to_users.rb | 6 +++ ...0241009210726_change_otp_secret_to_text.rb | 9 ++++ ...009212849_add_otp_backup_codes_to_users.rb | 5 ++ db/schema.rb | 5 +- ..._test.rb => two_factor_controller_test.rb} | 10 ++-- 26 files changed, 272 insertions(+), 15 deletions(-) create mode 100644 app/controllers/two_factor_controller.rb create mode 100644 app/controllers/users/sessions_controller.rb create mode 100644 app/controllers/users/two_factor_authentication_controller.rb create mode 100644 app/helpers/two_factor_helper.rb create mode 100644 app/views/two_factor/backup_codes.html.erb create mode 100644 app/views/two_factor/create.html.erb create mode 100644 app/views/two_factor/destroy.html.erb create mode 100644 app/views/two_factor/new.html.erb create mode 100644 app/views/users/two_factor_authentication/show.html.erb copy {app/assets/images => config/initializers}/.DS_Store (100%) create mode 100644 db/migrate/20241009205529_add_two_factor_to_users.rb create mode 100644 db/migrate/20241009210726_change_otp_secret_to_text.rb create mode 100644 db/migrate/20241009212849_add_otp_backup_codes_to_users.rb copy test/controllers/{subscriptions_controller_test.rb => two_factor_controller_test.rb} (50%) diff --git a/Gemfile b/Gemfile index 6213de9..6bcc6bb 100644 --- a/Gemfile +++ b/Gemfile @@ -26,6 +26,9 @@ gem "hcaptcha" gem "stripe" # Scheduling gem "whenever", require: false +# 2FA +gem "devise-two-factor" +gem "rqrcode" # Use Kredis to get higher-level data types in Redis [https://github.com/rails/kredis] # gem "kredis" #api stuff diff --git a/Gemfile.lock b/Gemfile.lock index d37a19d..1b6e1d2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -95,6 +95,7 @@ GEM childprocess (5.1.0) logger (~> 1.5) chronic (0.10.2) + chunky_png (1.4.0) concurrent-ruby (1.3.4) connection_pool (2.4.1) crass (1.0.6) @@ -109,6 +110,11 @@ GEM railties (>= 4.1.0) responders warden (~> 1.2.3) + devise-two-factor (6.0.0) + activesupport (~> 7.0) + devise (~> 4.0) + railties (~> 7.0) + rotp (~> 6.0) dotenv (3.1.2) drb (2.2.1) erubi (1.13.0) @@ -246,7 +252,12 @@ GEM actionpack (>= 5.2) railties (>= 5.2) rexml (3.3.7) + rotp (6.3.0) rouge (4.3.0) + rqrcode (2.2.0) + chunky_png (~> 1.0) + rqrcode_core (~> 1.0) + rqrcode_core (1.2.0) rubocop (1.66.1) json (~> 2.3) language_server-protocol (>= 3.17.0) @@ -331,6 +342,7 @@ DEPENDENCIES capybara debug devise + devise-two-factor dotenv hcaptcha httparty @@ -342,6 +354,7 @@ DEPENDENCIES rails (~> 7.2.1) redcarpet rouge + rqrcode rubocop-rails-omakase selenium-webdriver sprockets-rails diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index bfd3d22..f635692 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -8,5 +8,6 @@ class ApplicationController < ActionController::Base def configure_permitted_parameters devise_parameter_sanitizer.permit(:sign_up, keys: [:first_name, :last_name]) devise_parameter_sanitizer.permit(:account_update, keys: [:first_name, :last_name]) + devise_parameter_sanitizer.permit(:sign_in, keys: [:otp_attempt]) end end diff --git a/app/controllers/two_factor_controller.rb b/app/controllers/two_factor_controller.rb new file mode 100644 index 0000000..ceed820 --- /dev/null +++ b/app/controllers/two_factor_controller.rb @@ -0,0 +1,36 @@ +# This controller is for setup of 2FA +class TwoFactorController < ApplicationController + before_action :authenticate_user! + + def new + current_user.generate_two_factor_secret_if_missing! + @qr_code_uri = current_user.two_factor_qr_code_uri + @qr_code = RQRCode::QRCode.new(@qr_code_uri).as_svg( + offset: 0, + color: '000', + shape_rendering: 'crispEdges', + module_size: 4 + ) + @manual_entry_code = current_user.otp_secret.upcase.scan(/.{4}/).join(' ') + end + + def create + if current_user.validate_and_consume_otp!(params[:otp_attempt]) + current_user.enable_two_factor! + current_user.generate_otp_backup_codes! + redirect_to backup_codes_two_factor_path, notice: '2FA has been enabled. Please save your backup codes.' + else + flash.now[:alert] = 'Invalid OTP code.' + render :new + end + end + + def backup_codes + @backup_codes = current_user.otp_backup_codes + end + + def destroy + current_user.disable_two_factor! + redirect_to root_path, notice: '2FA has been disabled.' + end +end \ No newline at end of file diff --git a/app/controllers/users/sessions_controller.rb b/app/controllers/users/sessions_controller.rb new file mode 100644 index 0000000..2e42726 --- /dev/null +++ b/app/controllers/users/sessions_controller.rb @@ -0,0 +1,19 @@ +class Users::SessionsController < Devise::SessionsController + def create + user = User.find_by_email(params[:user][:email]) + + if user && user.valid_password?(params[:user][:password]) + if user.otp_required_for_login? + session[:user_id] = user.id + redirect_to users_two_factor_authentication_path + else + sign_in(user) + set_flash_message!(:notice, :signed_in) + respond_with user, location: after_sign_in_path_for(user) + end + else + flash[:alert] = "Invalid email or password." + redirect_to new_user_session_path + end + end +end \ No newline at end of file diff --git a/app/controllers/users/two_factor_authentication_controller.rb b/app/controllers/users/two_factor_authentication_controller.rb new file mode 100644 index 0000000..474b544 --- /dev/null +++ b/app/controllers/users/two_factor_authentication_controller.rb @@ -0,0 +1,22 @@ +# This controller is for login with 2FA +class Users::TwoFactorAuthenticationController < ApplicationController + def show + if session[:user_id].nil? + redirect_to new_user_session_path + end + @user = User.find_by(id: session[:user_id]) + redirect_to new_user_session_path if @user.nil? || !@user.otp_required_for_login? + end + + def create + user = User.find_by(id: session[:user_id]) + if user && user.validate_and_consume_otp!(params[:otp_attempt]) + sign_in user + session.delete(:user_id) + redirect_to root_path, notice: 'Signed in successfully.' + else + flash.now[:alert] = 'Invalid two-factor code.' + render :show + end + end +end \ No newline at end of file diff --git a/app/helpers/two_factor_helper.rb b/app/helpers/two_factor_helper.rb new file mode 100644 index 0000000..773c882 --- /dev/null +++ b/app/helpers/two_factor_helper.rb @@ -0,0 +1,2 @@ +module TwoFactorHelper +end diff --git a/app/models/user.rb b/app/models/user.rb index d21d2ed..f5a2f92 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -3,7 +3,11 @@ class User < ApplicationRecord # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable, - :confirmable, :lockable + :confirmable, :lockable, :two_factor_authenticatable, :two_factor_backupable, + otp_secret_encryption_key: Rails.application.credentials.active_record_encryption[:primary_key] + + encrypts :otp_secret + attr_accessor :otp_plain_secret validates :first_name, presence: true validates :last_name, presence: true @@ -51,6 +55,47 @@ class User < ApplicationRecord send_welcome_email end + def generate_two_factor_secret_if_missing! + return unless otp_secret.nil? + update!(otp_secret: User.generate_otp_secret) + end + + def enable_two_factor! + update!(otp_required_for_login: true) + end + + def disable_two_factor! + update!( + otp_required_for_login: false, + otp_secret: nil, + otp_backup_codes: nil + ) + end + + def two_factor_qr_code_uri + issuer = 'Your App Name' + label = "#{issuer}:#{email}" + otp_provisioning_uri(label, issuer: issuer) + end + + def generate_otp_backup_codes! + codes = [] + 10.times do + codes << SecureRandom.hex(8) + end + update!(otp_backup_codes: codes) + end + + # Getter and setter for otp_backup_codes + def otp_backup_codes + return [] if super.nil? + JSON.parse(super) + end + + def otp_backup_codes=(codes) + super(codes.to_json) + end + private def sync_with_buttondown ButtondownService.new.subscribe(self) diff --git a/app/views/devise/registrations/edit.html.erb b/app/views/devise/registrations/edit.html.erb index 3d44108..91debf6 100644 --- a/app/views/devise/registrations/edit.html.erb +++ b/app/views/devise/registrations/edit.html.erb @@ -66,6 +66,11 @@ <%= f.submit "Update" %> <% end %> + <% if current_user.otp_required_for_login? %> + <%= link_to "Disable 2FA", two_factor_path, method: :delete, data: { confirm: "Are you sure you want to disable 2FA?" }, class: "button" %> + <% else %> + <%= link_to "Enable 2FA", new_two_factor_path, class: "button" %> + <% end %> <%= button_to "Log out", destroy_user_session_path, method: :delete %> <% if current_user&.admin? or current_user&.support_type? %>

Account tools

diff --git a/app/views/devise/sessions/new.html.erb b/app/views/devise/sessions/new.html.erb index a471d0d..156c433 100644 --- a/app/views/devise/sessions/new.html.erb +++ b/app/views/devise/sessions/new.html.erb @@ -23,6 +23,11 @@ <%= f.submit "Log in" %> <% end %> + +

+ If you have two-factor authentication enabled and were redirected here, + please <%= link_to "enter your two-factor code", users_two_factor_authentication_path %>. +

<% end %> <%= render template: 'layouts/user_page_template' %> \ No newline at end of file diff --git a/app/views/pubview/join.html.erb b/app/views/pubview/join.html.erb index ddfa5ac..18c128d 100644 --- a/app/views/pubview/join.html.erb +++ b/app/views/pubview/join.html.erb @@ -11,11 +11,12 @@
<%= image_tag "growchart.svg", class: "porter" %>

Below you can join mind reader and sign up to receive new dispatches via email.

-

Joining by email means you never have to visit this (admittedly beautiful) website ever again. You’ll receive new posts straight in your email inbox. Bookmarks, however, are sent in digest to subscribers only – so if you’d like to see those, come back or keep reading.

-

Optionally, once you’ve signed up and confirmed your email address you are able to subscribe to mind reader financially by entering any non $0 AUD amount you wish. You are under no obligation to do this! Paid subscribers may opt to receive a digest of bookmarks and a monthly update from me on the state of the world that regular members miss out on.

+

Joining by email means you never have to visit this (admittedly beautiful) website ever again. You’ll receive new posts straight in your email inbox. Bookmarks are, optionally, sent in digest to paid subscribers.

+

Optionally, once you’ve signed up and confirmed your email address you are able to subscribe to mind reader financially by entering any non $0 AUD amount you wish – you can do this just once, or annually. You are under no obligation to do this! Paid subscribers may opt to receive a digest of bookmarks and a monthly update from me on the state of the world that regular members miss out on.

If you’re looking to cancel an existing membership or subscription, <%= link_to "please click here", subscriptions_path %>.

<%= image_tag "aidan_arrow.svg", class: "aidans_arrow" %> -

To get started with mind reader membership, first register for an account on this website. Don’t worry, I’ll guide you through each step of the process! Just click on that button below:

-

<%= link_to "Get started, free forever", new_user_registration_path, class: "button" %>

+

To get started with mind reader membership – always free – first register for an account on this website. Don’t worry, I’ll guide you through each step of the process! Just click on that button below:

+

<%= link_to "Get started, free forever", new_user_registration_path, class: "button" %> <%= link_to "Log back in", new_user_session_path, class: "button" %>

+

You can subscribe, unsubscribe, pay, cancel, and never hear from me again without ever contacting a human – note, though, if you pay for the service account deletion is manual to ensure all your details are securely erased. You can always one-click opt-out of communication and services.

diff --git a/app/views/two_factor/backup_codes.html.erb b/app/views/two_factor/backup_codes.html.erb new file mode 100644 index 0000000..8edfa9f --- /dev/null +++ b/app/views/two_factor/backup_codes.html.erb @@ -0,0 +1,19 @@ +
+

Your Backup Codes

+
+ +
+
+

Please save these backup codes in a secure location. You will need them if you lose access to your authenticator app.

+ +
    + <% @backup_codes.each do |code| %> +
  • <%= code %>
  • + <% end %> +
+ +

Each code can only be used once. If you use a backup code to sign in, a new set of codes will be generated.

+ + <%= link_to "I have saved these codes", edit_user_registration_path, class: "button" %> +
+
\ No newline at end of file diff --git a/app/views/two_factor/create.html.erb b/app/views/two_factor/create.html.erb new file mode 100644 index 0000000..1b208b7 --- /dev/null +++ b/app/views/two_factor/create.html.erb @@ -0,0 +1,2 @@ +

TwoFactor#create

+

Find me in app/views/two_factor/create.html.erb

diff --git a/app/views/two_factor/destroy.html.erb b/app/views/two_factor/destroy.html.erb new file mode 100644 index 0000000..6ff97fe --- /dev/null +++ b/app/views/two_factor/destroy.html.erb @@ -0,0 +1,2 @@ +

TwoFactor#destroy

+

Find me in app/views/two_factor/destroy.html.erb

diff --git a/app/views/two_factor/new.html.erb b/app/views/two_factor/new.html.erb new file mode 100644 index 0000000..033f411 --- /dev/null +++ b/app/views/two_factor/new.html.erb @@ -0,0 +1,30 @@ +
+

Enable two-factor authentication

+ <%= link_to "Cancel two-factor setup...", edit_user_registration_path %> +
+ +
+
+

Scan this QR code with your authenticator app:

+ + <%= @qr_code.html_safe %> + +

Or manually enter this code:

+ + <%= @manual_entry_code %> + +

Enter these details in your authenticator app:

+
    +
  • Account: <%= current_user.email %>
  • +
  • Key: <%= @manual_entry_code %>
  • +
  • Time based: Yes
  • +
  • Issuer: arelpe
  • +
+ + <%= form_tag two_factor_path, method: :post do %> + <%= label_tag :otp_attempt, "Enter the code from your authenticator app:" %> + <%= text_field_tag :otp_attempt, nil, autocomplete: 'off' %> + <%= submit_tag "Enable 2FA" %> + <% end %> +
+
\ No newline at end of file diff --git a/app/views/users/two_factor_authentication/show.html.erb b/app/views/users/two_factor_authentication/show.html.erb new file mode 100644 index 0000000..1cd9604 --- /dev/null +++ b/app/views/users/two_factor_authentication/show.html.erb @@ -0,0 +1,16 @@ +
+

Two-Factor Authentication

+
+ +
+
+ <%= form_tag(users_two_factor_authentication_path, method: :post) do %> +
+ <%= label_tag :otp_attempt, "Enter your two-factor code" %> + <%= text_field_tag :otp_attempt, nil, autocomplete: 'off', inputmode: 'numeric', pattern: '[0-9]*' %> +
+ + <%= submit_tag "Verify" %> + <% end %> +
+
\ No newline at end of file diff --git a/config/application.rb b/config/application.rb index 056949a..da4c4d5 100644 --- a/config/application.rb +++ b/config/application.rb @@ -35,5 +35,9 @@ module Arelpe # config.time_zone = "Australia/Adelaide" # config.eager_load_paths << Rails.root.join("extras") + #2FA Encryption + config.active_record.encryption.primary_key = Rails.application.credentials.active_record_encryption[:primary_key] + config.active_record.encryption.deterministic_key = Rails.application.credentials.active_record_encryption[:deterministic_key] + config.active_record.encryption.key_derivation_salt = Rails.application.credentials.active_record_encryption[:key_derivation_salt] end end diff --git a/config/credentials.yml.enc b/config/credentials.yml.enc index 2370933..19f60ef 100644 --- a/config/credentials.yml.enc +++ b/config/credentials.yml.enc @@ -1 +1 @@ -9kYVka9CFCj1yyYXajvTfcLa2BGRH44UtuAChP+O+StY/AtHrBFePFG9llTUtAi/OUIT/eIWgdlKRRGFBsq0lTlVucsqN+C+34Xs7YRz9nfRvrPb+liyWZIR0k81TkrU8CuN31aVlp/I2ZZeBEONbe/2ISbBKpXA1GScvveXCzhH93PZKJh2R1sDg+IGXAYeY39H71/pmJ0vwcmcHrrs/V5Fdvb6Ze17hBiS5NKuetEa9EvbLXzxTk8Vo8Bl9yqZkaoFaTvhiomraQUFP92TvCbjtzp7JESWU2uosyd0fAI+qwZXT/AIWLVfKSJ62oz9SygpJMvNJHi4SyuS9LcnN34ce9VHwQBB1/nf4lqqrJiXI7EP2LkISpprqTkqXOrf1mrklQ6uKNl/yI2Z/KoR8C/tE/Or--QXMk23jZC7frOfwO--M9QxrCetYHjpBOk9X/fZAQ== \ No newline at end of file +R6pLqAgOKbr+y07Is0RC9w3wQKbkr2h2KZO4RNpGCPAaE+X9ZK6i3AdB0XYfDiBRfvtj05EhYDJ+Yr72985yeeRhnppVqkwNK1mWp+SGfY+KVvaKI7HFYG8igI0LthDAdsyeoA2+ElpZgjHCCLqz36gRKSY/KEbT/y0NukHdQRzsWFHwqgY85vQK9gRfDBpM72h4iPwb9TOPvMtJuVwfaIAdcDY0OqaQpTlFIGjh/bT9njHuO2h4mJ6dCBJYoFR0+8oCpd8hHWHtxeB4bsqKspfxrNvFTYEDQUnNwJEbQGNl14577rhEshQ2WfgUVFGzTzfEpzQmFZZJtnWhJuJbgVaxUwH5STUKWEWNY5/gD2vgp1oj0aUO6eiyOjz5YCkUieR2RV3hVF+yvOMb9NTt2EBYjJGeoqngzKi/Hq0xnLHIRmkoFHPvuC1RHn0Ucg1Nez4cmWppoOK3xm/b0H2lKsWi97vkkii38lqtfmmRFtsdXf4AvFOQ2NWpFlLKLwHT87/bU32XIzPObwErw/wv9irxrBMAlDDqOTqtqd6k3liddoNGCqUqHLxSC0IoH/G2tdg1PzoKBvtIVyjtctYtUnta1vgjpz727WCSSYO6gDTOxTkRmwc++DcBAzOJFkYSz8gFm02TjHaBhzXQeqw=--fmjiwM3a6xuvqqTn--wwZXJd1Zcs5AJV/lQbpCtw== \ No newline at end of file diff --git a/app/assets/images/.DS_Store b/config/initializers/.DS_Store similarity index 100% copy from app/assets/images/.DS_Store copy to config/initializers/.DS_Store diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 545e8c6..9d0139e 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -209,7 +209,7 @@ Devise.setup do |config| # Number of authentication tries before locking an account if lock_strategy # is failed attempts. config.maximum_attempts = 6 - + # Time interval to unlock the account if :time is enabled as unlock_strategy. # config.unlock_in = 1.hour diff --git a/config/routes.rb b/config/routes.rb index efa8e9f..9678326 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -10,7 +10,7 @@ Rails.application.routes.draw do end end -resources :mailing_lists, only: [:index] do + resources :mailing_lists, only: [:index] do collection do post 'subscribe' delete 'unsubscribe' @@ -18,13 +18,22 @@ resources :mailing_lists, only: [:index] do post 'resync_from_buttondown' end end + resource :two_factor, only: [:new, :create, :destroy], controller: 'two_factor' do + get 'backup_codes', on: :member + end resources :subscriptions, only: [:new, :create, :index] resources :pages get 'importer', to: 'posts#importer' get 'export', to: 'posts#export' post 'import', to: 'posts#import' resources :api_keys - devise_for :users, controllers: { registrations: 'users/registrations', confirmations: 'users/confirmations' } + devise_for :users, controllers: { + registrations: 'users/registrations', + confirmations: 'users/confirmations', + sessions: 'users/sessions' + } + get 'users/two_factor_authentication', to: 'users/two_factor_authentication#show' + post 'users/two_factor_authentication', to: 'users/two_factor_authentication#create' resources :posts get '/feed', to: 'pubview#rss', as: 'rss', defaults: { format: 'rss' } get '/feed/dispatches', to: 'pubview#dispatches_rss', as: 'dispatches_rss', defaults: { format: 'rss' } diff --git a/db/migrate/20241009205529_add_two_factor_to_users.rb b/db/migrate/20241009205529_add_two_factor_to_users.rb new file mode 100644 index 0000000..2984da9 --- /dev/null +++ b/db/migrate/20241009205529_add_two_factor_to_users.rb @@ -0,0 +1,6 @@ +class AddTwoFactorToUsers < ActiveRecord::Migration[7.2] + def change + add_column :users, :otp_secret, :string + add_column :users, :otp_required_for_login, :boolean + end +end diff --git a/db/migrate/20241009210726_change_otp_secret_to_text.rb b/db/migrate/20241009210726_change_otp_secret_to_text.rb new file mode 100644 index 0000000..8d6bbeb --- /dev/null +++ b/db/migrate/20241009210726_change_otp_secret_to_text.rb @@ -0,0 +1,9 @@ +class ChangeOtpSecretToText < ActiveRecord::Migration[7.2] + def up + change_column :users, :otp_secret, :text + end + + def down + change_column :users, :otp_secret, :string + end +end diff --git a/db/migrate/20241009212849_add_otp_backup_codes_to_users.rb b/db/migrate/20241009212849_add_otp_backup_codes_to_users.rb new file mode 100644 index 0000000..ce8ca7e --- /dev/null +++ b/db/migrate/20241009212849_add_otp_backup_codes_to_users.rb @@ -0,0 +1,5 @@ +class AddOtpBackupCodesToUsers < ActiveRecord::Migration[7.2] + def change + add_column :users, :otp_backup_codes, :text + end +end diff --git a/db/schema.rb b/db/schema.rb index 8275001..ddc1cd4 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.2].define(version: 2024_10_08_010447) do +ActiveRecord::Schema[7.2].define(version: 2024_10_09_212849) do create_table "api_keys", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| t.string "key" t.datetime "created_at", null: false @@ -69,6 +69,9 @@ ActiveRecord::Schema[7.2].define(version: 2024_10_08_010447) do t.string "buttondown_status", default: "unactivated" t.string "subscription_type" t.string "support_type" + t.text "otp_secret" + t.boolean "otp_required_for_login" + t.text "otp_backup_codes" t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true t.index ["email"], name: "index_users_on_email", unique: true t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true diff --git a/test/controllers/subscriptions_controller_test.rb b/test/controllers/two_factor_controller_test.rb similarity index 50% copy from test/controllers/subscriptions_controller_test.rb copy to test/controllers/two_factor_controller_test.rb index 3958a5a..4ef2cf7 100644 --- a/test/controllers/subscriptions_controller_test.rb +++ b/test/controllers/two_factor_controller_test.rb @@ -1,18 +1,18 @@ require "test_helper" -class SubscriptionsControllerTest < ActionDispatch::IntegrationTest +class TwoFactorControllerTest < ActionDispatch::IntegrationTest test "should get new" do - get subscriptions_new_url + get two_factor_new_url assert_response :success end test "should get create" do - get subscriptions_create_url + get two_factor_create_url assert_response :success end - test "should get view" do - get subscriptions_view_url + test "should get destroy" do + get two_factor_destroy_url assert_response :success end end -- 2.39.5