devise-otp 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +7 -10
- data/.gitignore +3 -1
- data/CHANGELOG.md +54 -8
- data/Gemfile +10 -0
- data/README.md +8 -2
- data/app/controllers/devise_otp/devise/otp_credentials_controller.rb +46 -27
- data/app/controllers/devise_otp/devise/otp_tokens_controller.rb +28 -10
- data/app/views/devise/otp_credentials/show.html.erb +6 -6
- data/app/views/devise/otp_tokens/_token_secret.html.erb +3 -4
- data/app/views/devise/otp_tokens/edit.html.erb +26 -0
- data/app/views/devise/otp_tokens/show.html.erb +7 -14
- data/config/locales/en.yml +23 -14
- data/devise-otp.gemspec +3 -13
- data/docs/QR_CODES.md +1 -40
- data/lib/devise/strategies/database_authenticatable.rb +64 -0
- data/lib/devise-otp/version.rb +1 -1
- data/lib/devise-otp.rb +31 -11
- data/lib/devise_otp_authenticatable/controllers/helpers.rb +9 -10
- data/lib/devise_otp_authenticatable/controllers/public_helpers.rb +39 -0
- data/lib/devise_otp_authenticatable/controllers/url_helpers.rb +10 -0
- data/lib/devise_otp_authenticatable/engine.rb +2 -5
- data/lib/devise_otp_authenticatable/hooks/refreshable.rb +5 -0
- data/lib/devise_otp_authenticatable/models/otp_authenticatable.rb +22 -20
- data/lib/devise_otp_authenticatable/routes.rb +3 -1
- data/lib/generators/active_record/templates/migration.rb +1 -1
- data/test/dummy/app/controllers/admin_posts_controller.rb +85 -0
- data/test/dummy/app/controllers/application_controller.rb +0 -1
- data/test/dummy/app/controllers/base_controller.rb +6 -0
- data/test/dummy/app/models/admin.rb +25 -0
- data/test/dummy/app/views/admin_posts/_form.html.erb +25 -0
- data/test/dummy/app/views/admin_posts/edit.html.erb +6 -0
- data/test/dummy/app/views/admin_posts/index.html.erb +25 -0
- data/test/dummy/app/views/admin_posts/new.html.erb +5 -0
- data/test/dummy/app/views/admin_posts/show.html.erb +15 -0
- data/test/dummy/app/views/base/home.html.erb +1 -0
- data/test/dummy/config/application.rb +0 -2
- data/test/dummy/config/routes.rb +4 -1
- data/test/dummy/db/migrate/20240604000001_create_admins.rb +9 -0
- data/test/dummy/db/migrate/20240604000002_add_devise_to_admins.rb +52 -0
- data/test/dummy/db/migrate/20240604000003_devise_otp_add_to_admins.rb +28 -0
- data/test/integration/disable_token_test.rb +53 -0
- data/test/integration/enable_otp_form_test.rb +57 -0
- data/test/integration/persistence_test.rb +3 -6
- data/test/integration/refresh_test.rb +32 -0
- data/test/integration/reset_token_test.rb +45 -0
- data/test/integration/sign_in_test.rb +10 -14
- data/test/integration/trackable_test.rb +50 -0
- data/test/integration_tests_helper.rb +24 -6
- data/test/models/otp_authenticatable_test.rb +62 -27
- data/test/orm/active_record.rb +6 -1
- data/test/test_helper.rb +1 -71
- metadata +26 -135
- data/lib/devise_otp_authenticatable/hooks/sessions.rb +0 -58
- data/lib/devise_otp_authenticatable/hooks.rb +0 -11
- data/test/integration/token_test.rb +0 -30
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'devise/strategies/authenticatable'
|
4
|
+
|
5
|
+
module Devise
|
6
|
+
module Strategies
|
7
|
+
# Default strategy for signing in a user, based on their email and password in the database.
|
8
|
+
class DatabaseAuthenticatable < Authenticatable
|
9
|
+
def authenticate!
|
10
|
+
resource = password.present? && mapping.to.find_for_database_authentication(authentication_hash)
|
11
|
+
hashed = false
|
12
|
+
|
13
|
+
if validate(resource){ hashed = true; resource.valid_password?(password) }
|
14
|
+
if otp_challenge_required_on?(resource)
|
15
|
+
# Redirect to challenge
|
16
|
+
challenge = resource.generate_otp_challenge!
|
17
|
+
redirect!(otp_challenge_url, {:challenge => challenge})
|
18
|
+
else
|
19
|
+
# Sign in user as usual
|
20
|
+
remember_me(resource)
|
21
|
+
resource.after_database_authentication
|
22
|
+
success!(resource)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# In paranoid mode, hash the password even when a resource doesn't exist for the given authentication key.
|
27
|
+
# This is necessary to prevent enumeration attacks - e.g. the request is faster when a resource doesn't
|
28
|
+
# exist in the database if the password hashing algorithm is not called.
|
29
|
+
mapping.to.new.password = password if !hashed && Devise.paranoid
|
30
|
+
unless resource
|
31
|
+
Devise.paranoid ? fail(:invalid) : fail(:not_found_in_database)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
#
|
38
|
+
# resource should be challenged for otp
|
39
|
+
#
|
40
|
+
def otp_challenge_required_on?(resource)
|
41
|
+
resource.respond_to?(:otp_enabled?) && resource.otp_enabled?
|
42
|
+
end
|
43
|
+
|
44
|
+
def otp_challenge_url
|
45
|
+
if Rails.env.development? || Rails.env.test?
|
46
|
+
host = "#{request.host}:#{request.port}"
|
47
|
+
else
|
48
|
+
host = "#{request.host}"
|
49
|
+
end
|
50
|
+
|
51
|
+
path_fragments = ["otp", mapping.path_names[:credentials]]
|
52
|
+
if mapping.fullpath == "/"
|
53
|
+
path = mapping.fullpath + path_fragments.join("/")
|
54
|
+
else
|
55
|
+
path = path_fragments.prepend(mapping.fullpath).join("/")
|
56
|
+
end
|
57
|
+
|
58
|
+
request.protocol + host + path
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
Warden::Strategies.add(:database_authenticatable, Devise::Strategies::DatabaseAuthenticatable)
|
data/lib/devise-otp/version.rb
CHANGED
data/lib/devise-otp.rb
CHANGED
@@ -9,6 +9,24 @@ require "active_support/concern"
|
|
9
9
|
|
10
10
|
require "devise"
|
11
11
|
|
12
|
+
#
|
13
|
+
# define DeviseOtpAuthenticatable module, and autoload hooks and helpers
|
14
|
+
#
|
15
|
+
module DeviseOtpAuthenticatable
|
16
|
+
module Controllers
|
17
|
+
autoload :Helpers, "devise_otp_authenticatable/controllers/helpers"
|
18
|
+
autoload :UrlHelpers, "devise_otp_authenticatable/controllers/url_helpers"
|
19
|
+
autoload :PublicHelpers, "devise_otp_authenticatable/controllers/public_helpers"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
require "devise_otp_authenticatable/routes"
|
24
|
+
require "devise_otp_authenticatable/engine"
|
25
|
+
require "devise_otp_authenticatable/hooks/refreshable"
|
26
|
+
|
27
|
+
#
|
28
|
+
# update Devise module with additions needed for DeviseOtpAuthenticatable
|
29
|
+
#
|
12
30
|
module Devise
|
13
31
|
mattr_accessor :otp_mandatory
|
14
32
|
@@otp_mandatory = false
|
@@ -49,22 +67,24 @@ module Devise
|
|
49
67
|
mattr_accessor :otp_controller_path
|
50
68
|
@@otp_controller_path = "devise"
|
51
69
|
|
70
|
+
#
|
71
|
+
# add PublicHelpers to helpers class variable to ensure that per-mapping helpers are present.
|
72
|
+
# this integrates with the "define_helpers," which is run when adding each mapping in the Devise gem (lib/devise.rb#541)
|
73
|
+
#
|
74
|
+
@@helpers << DeviseOtpAuthenticatable::Controllers::PublicHelpers
|
75
|
+
|
52
76
|
module Otp
|
53
77
|
end
|
54
|
-
end
|
55
|
-
|
56
|
-
module DeviseOtpAuthenticatable
|
57
|
-
autoload :Hooks, "devise_otp_authenticatable/hooks"
|
58
78
|
|
59
|
-
module Controllers
|
60
|
-
autoload :Helpers, "devise_otp_authenticatable/controllers/helpers"
|
61
|
-
autoload :UrlHelpers, "devise_otp_authenticatable/controllers/url_helpers"
|
62
|
-
end
|
63
79
|
end
|
64
80
|
|
65
|
-
require "devise_otp_authenticatable/routes"
|
66
|
-
require "devise_otp_authenticatable/engine"
|
67
|
-
|
68
81
|
Devise.add_module :otp_authenticatable,
|
69
82
|
controller: :tokens,
|
70
83
|
model: "devise_otp_authenticatable/models/otp_authenticatable", route: :otp
|
84
|
+
|
85
|
+
#
|
86
|
+
# add PublicHelpers after adding Devise module to ensure that per-mapping routes from above are included
|
87
|
+
#
|
88
|
+
ActiveSupport.on_load(:action_controller) do
|
89
|
+
include DeviseOtpAuthenticatable::Controllers::PublicHelpers
|
90
|
+
end
|
@@ -39,7 +39,6 @@ module DeviseOtpAuthenticatable
|
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
42
|
-
# fixme do cookies and persistence need to be scoped? probably
|
43
42
|
#
|
44
43
|
# check if the resource needs a credentials refresh. IE, they need to be asked a password again to access
|
45
44
|
# this resource.
|
@@ -47,8 +46,8 @@ module DeviseOtpAuthenticatable
|
|
47
46
|
def needs_credentials_refresh?(resource)
|
48
47
|
return false unless resource.class.otp_credentials_refresh
|
49
48
|
|
50
|
-
(!session[
|
51
|
-
|
49
|
+
(!warden.session(resource_name)[otp_refresh_property].present? ||
|
50
|
+
(warden.session(resource_name)[otp_refresh_property] < DateTime.now)).tap { |need| otp_set_refresh_return_url if need }
|
52
51
|
end
|
53
52
|
|
54
53
|
#
|
@@ -56,7 +55,7 @@ module DeviseOtpAuthenticatable
|
|
56
55
|
#
|
57
56
|
def otp_refresh_credentials_for(resource)
|
58
57
|
return false unless resource.class.otp_credentials_refresh
|
59
|
-
session[
|
58
|
+
warden.session(resource_name)[otp_refresh_property] = (Time.now + resource.class.otp_credentials_refresh)
|
60
59
|
end
|
61
60
|
|
62
61
|
#
|
@@ -85,19 +84,19 @@ module DeviseOtpAuthenticatable
|
|
85
84
|
end
|
86
85
|
|
87
86
|
def otp_set_refresh_return_url
|
88
|
-
session[
|
87
|
+
warden.session(resource_name)[otp_refresh_return_url_property] = request.fullpath
|
89
88
|
end
|
90
89
|
|
91
90
|
def otp_fetch_refresh_return_url
|
92
|
-
session.delete(
|
91
|
+
warden.session(resource_name).delete(otp_refresh_return_url_property) { :root }
|
93
92
|
end
|
94
93
|
|
95
|
-
def
|
96
|
-
"
|
94
|
+
def otp_refresh_return_url_property
|
95
|
+
"refresh_return_url"
|
97
96
|
end
|
98
97
|
|
99
|
-
def
|
100
|
-
"
|
98
|
+
def otp_refresh_property
|
99
|
+
"credentials_refreshed_at"
|
101
100
|
end
|
102
101
|
|
103
102
|
def otp_scoped_persistence_cookie
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module DeviseOtpAuthenticatable
|
2
|
+
module Controllers
|
3
|
+
module PublicHelpers
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
def self.generate_helpers!
|
7
|
+
Devise.mappings.each do |key, mapping|
|
8
|
+
self.define_helpers(mapping)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.define_helpers(mapping) #:nodoc:
|
13
|
+
mapping = mapping.name
|
14
|
+
|
15
|
+
class_eval <<-METHODS, __FILE__, __LINE__ + 1
|
16
|
+
def ensure_mandatory_#{mapping}_otp!
|
17
|
+
resource = current_#{mapping}
|
18
|
+
if !devise_controller?
|
19
|
+
if mandatory_otp_missing_on?(resource)
|
20
|
+
redirect_to edit_#{mapping}_otp_token_path
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
METHODS
|
25
|
+
end
|
26
|
+
|
27
|
+
def otp_mandatory_on?(resource)
|
28
|
+
return false unless resource.respond_to?(:otp_mandatory)
|
29
|
+
|
30
|
+
resource.class.otp_mandatory or resource.otp_mandatory
|
31
|
+
end
|
32
|
+
|
33
|
+
def mandatory_otp_missing_on?(resource)
|
34
|
+
otp_mandatory_on?(resource) && !resource.otp_enabled
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -21,6 +21,16 @@ module DeviseOtpAuthenticatable
|
|
21
21
|
send("#{scope}_otp_token_path", opts)
|
22
22
|
end
|
23
23
|
|
24
|
+
def edit_otp_token_path_for(resource_or_scope, opts = {})
|
25
|
+
scope = ::Devise::Mapping.find_scope!(resource_or_scope)
|
26
|
+
send("edit_#{scope}_otp_token_path", opts)
|
27
|
+
end
|
28
|
+
|
29
|
+
def reset_otp_token_path_for(resource_or_scope, opts = {})
|
30
|
+
scope = ::Devise::Mapping.find_scope!(resource_or_scope)
|
31
|
+
send("reset_#{scope}_otp_token_path", opts)
|
32
|
+
end
|
33
|
+
|
24
34
|
def otp_credential_path_for(resource_or_scope, opts = {})
|
25
35
|
scope = ::Devise::Mapping.find_scope!(resource_or_scope)
|
26
36
|
send("#{scope}_otp_credential_path", opts)
|
@@ -3,20 +3,17 @@ module DeviseOtpAuthenticatable
|
|
3
3
|
config.devise_otp = ActiveSupport::OrderedOptions.new
|
4
4
|
config.devise_otp.precompile_assets = true
|
5
5
|
|
6
|
-
# We use to_prepare instead of after_initialize here because Devise is a Rails engine;
|
7
|
-
config.to_prepare do
|
8
|
-
DeviseOtpAuthenticatable::Hooks.apply
|
9
|
-
end
|
10
|
-
|
11
6
|
initializer "devise-otp", group: :all do |app|
|
12
7
|
ActiveSupport.on_load(:devise_controller) do
|
13
8
|
include DeviseOtpAuthenticatable::Controllers::UrlHelpers
|
14
9
|
include DeviseOtpAuthenticatable::Controllers::Helpers
|
10
|
+
include DeviseOtpAuthenticatable::Controllers::PublicHelpers
|
15
11
|
end
|
16
12
|
|
17
13
|
ActiveSupport.on_load(:action_view) do
|
18
14
|
include DeviseOtpAuthenticatable::Controllers::UrlHelpers
|
19
15
|
include DeviseOtpAuthenticatable::Controllers::Helpers
|
16
|
+
include DeviseOtpAuthenticatable::Controllers::PublicHelpers
|
20
17
|
end
|
21
18
|
|
22
19
|
# See: https://guides.rubyonrails.org/engines.html#separate-assets-and-precompiling
|
@@ -5,8 +5,6 @@ module Devise::Models
|
|
5
5
|
extend ActiveSupport::Concern
|
6
6
|
|
7
7
|
included do
|
8
|
-
before_validation :generate_otp_auth_secret, on: :create
|
9
|
-
before_validation :generate_otp_persistence_seed, on: :create
|
10
8
|
scope :with_valid_otp_challenge, lambda { |time| where("otp_challenge_expires > ?", time) }
|
11
9
|
end
|
12
10
|
|
@@ -36,21 +34,6 @@ module Devise::Models
|
|
36
34
|
email
|
37
35
|
end
|
38
36
|
|
39
|
-
def reset_otp_credentials
|
40
|
-
@time_based_otp = nil
|
41
|
-
@recovery_otp = nil
|
42
|
-
generate_otp_auth_secret
|
43
|
-
reset_otp_persistence
|
44
|
-
update!(otp_enabled: false,
|
45
|
-
otp_session_challenge: nil, otp_challenge_expires: nil,
|
46
|
-
otp_recovery_counter: 0)
|
47
|
-
end
|
48
|
-
|
49
|
-
def reset_otp_credentials!
|
50
|
-
reset_otp_credentials
|
51
|
-
save!
|
52
|
-
end
|
53
|
-
|
54
37
|
def reset_otp_persistence
|
55
38
|
generate_otp_persistence_seed
|
56
39
|
end
|
@@ -60,11 +43,30 @@ module Devise::Models
|
|
60
43
|
save!
|
61
44
|
end
|
62
45
|
|
63
|
-
def
|
64
|
-
if otp_persistence_seed.
|
65
|
-
|
46
|
+
def populate_otp_secrets!
|
47
|
+
if [otp_auth_secret, otp_recovery_secret, otp_persistence_seed].any? { |a| a.blank? }
|
48
|
+
generate_otp_auth_secret
|
49
|
+
generate_otp_persistence_seed
|
50
|
+
self.save!
|
66
51
|
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def clear_otp_fields!
|
55
|
+
@time_based_otp = nil
|
56
|
+
@recovery_otp = nil
|
57
|
+
|
58
|
+
self.update!(
|
59
|
+
:otp_auth_secret => nil,
|
60
|
+
:otp_recovery_secret => nil,
|
61
|
+
:otp_persistence_seed => nil,
|
62
|
+
:otp_session_challenge => nil,
|
63
|
+
:otp_challenge_expires => nil,
|
64
|
+
:otp_failed_attempts => 0,
|
65
|
+
:otp_recovery_counter => 0
|
66
|
+
)
|
67
|
+
end
|
67
68
|
|
69
|
+
def enable_otp!
|
68
70
|
update!(otp_enabled: true, otp_enabled_on: Time.now)
|
69
71
|
end
|
70
72
|
|
@@ -4,7 +4,7 @@ module ActionDispatch::Routing
|
|
4
4
|
|
5
5
|
def devise_otp(mapping, controllers)
|
6
6
|
namespace :otp, module: :devise_otp do
|
7
|
-
resource :token, only: [:show, :update, :destroy],
|
7
|
+
resource :token, only: [:show, :edit, :update, :destroy],
|
8
8
|
path: mapping.path_names[:token], controller: controllers[:otp_tokens] do
|
9
9
|
if Devise.otp_trust_persistence
|
10
10
|
get :persistence, action: "get_persistence"
|
@@ -13,6 +13,7 @@ module ActionDispatch::Routing
|
|
13
13
|
end
|
14
14
|
|
15
15
|
get :recovery
|
16
|
+
post :reset
|
16
17
|
end
|
17
18
|
|
18
19
|
resource :credential, only: [:show, :update],
|
@@ -20,6 +21,7 @@ module ActionDispatch::Routing
|
|
20
21
|
get :refresh, action: "get_refresh"
|
21
22
|
put :refresh, action: "set_refresh"
|
22
23
|
end
|
24
|
+
|
23
25
|
end
|
24
26
|
end
|
25
27
|
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
class AdminPostsController < ApplicationController
|
2
|
+
before_action :authenticate_admin!
|
3
|
+
|
4
|
+
# GET /posts
|
5
|
+
# GET /posts.json
|
6
|
+
def index
|
7
|
+
@posts = Post.all
|
8
|
+
|
9
|
+
respond_to do |format|
|
10
|
+
format.html # index.html.erb
|
11
|
+
format.json { render json: @posts }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# GET /posts/1
|
16
|
+
# GET /posts/1.json
|
17
|
+
def show
|
18
|
+
@post = Post.find(params[:id])
|
19
|
+
|
20
|
+
respond_to do |format|
|
21
|
+
format.html # show.html.erb
|
22
|
+
format.json { render json: @post }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# GET /posts/new
|
27
|
+
# GET /posts/new.json
|
28
|
+
def new
|
29
|
+
@post = Post.new
|
30
|
+
|
31
|
+
respond_to do |format|
|
32
|
+
format.html # new.html.erb
|
33
|
+
format.json { render json: @post }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# GET /posts/1/edit
|
38
|
+
def edit
|
39
|
+
@post = Post.find(params[:id])
|
40
|
+
end
|
41
|
+
|
42
|
+
# POST /posts
|
43
|
+
# POST /posts.json
|
44
|
+
def create
|
45
|
+
@post = Post.new(params[:post])
|
46
|
+
|
47
|
+
respond_to do |format|
|
48
|
+
if @post.save
|
49
|
+
format.html { redirect_to @post, notice: "Post was successfully created." }
|
50
|
+
format.json { render json: @post, status: :created, location: @post }
|
51
|
+
else
|
52
|
+
format.html { render action: "new" }
|
53
|
+
format.json { render json: @post.errors, status: :unprocessable_entity }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# PUT /posts/1
|
59
|
+
# PUT /posts/1.json
|
60
|
+
def update
|
61
|
+
@post = Post.find(params[:id])
|
62
|
+
|
63
|
+
respond_to do |format|
|
64
|
+
if @post.update_attributes(params[:post])
|
65
|
+
format.html { redirect_to @post, notice: "Post was successfully updated." }
|
66
|
+
format.json { head :ok }
|
67
|
+
else
|
68
|
+
format.html { render action: "edit" }
|
69
|
+
format.json { render json: @post.errors, status: :unprocessable_entity }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# DELETE /posts/1
|
75
|
+
# DELETE /posts/1.json
|
76
|
+
def destroy
|
77
|
+
@post = Post.find(params[:id])
|
78
|
+
@post.destroy
|
79
|
+
|
80
|
+
respond_to do |format|
|
81
|
+
format.html { redirect_to posts_url }
|
82
|
+
format.json { head :ok }
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class Admin < PARENT_MODEL_CLASS
|
2
|
+
if DEVISE_ORM == :mongoid
|
3
|
+
include Mongoid::Document
|
4
|
+
|
5
|
+
## Database authenticatable
|
6
|
+
field :email, type: String, null: false, default: ""
|
7
|
+
field :encrypted_password, type: String, null: false, default: ""
|
8
|
+
|
9
|
+
## Recoverable
|
10
|
+
field :reset_password_token, type: String
|
11
|
+
field :reset_password_sent_at, type: Time
|
12
|
+
end
|
13
|
+
|
14
|
+
devise :otp_authenticatable, :database_authenticatable, :registerable,
|
15
|
+
:trackable, :validatable
|
16
|
+
|
17
|
+
# Setup accessible (or protected) attributes for your model
|
18
|
+
# attr_accessible :otp_enabled, :otp_mandatory, :as => :otp_privileged
|
19
|
+
# attr_accessible :email, :password, :password_confirmation, :remember_me
|
20
|
+
|
21
|
+
def self.otp_mandatory
|
22
|
+
true
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
<%= form_for([:admin, @post]) do |f| %>
|
2
|
+
<% if @post.errors.any? %>
|
3
|
+
<div id="error_explanation">
|
4
|
+
<h2><%= pluralize(@post.errors.count, "error") %> prohibited this post from being saved:</h2>
|
5
|
+
|
6
|
+
<ul>
|
7
|
+
<% @post.errors.full_messages.each do |msg| %>
|
8
|
+
<li><%= msg %></li>
|
9
|
+
<% end %>
|
10
|
+
</ul>
|
11
|
+
</div>
|
12
|
+
<% end %>
|
13
|
+
|
14
|
+
<div class="field">
|
15
|
+
<%= f.label :title %><br />
|
16
|
+
<%= f.text_field :title %>
|
17
|
+
</div>
|
18
|
+
<div class="field">
|
19
|
+
<%= f.label :body %><br />
|
20
|
+
<%= f.text_area :body %>
|
21
|
+
</div>
|
22
|
+
<div class="actions">
|
23
|
+
<%= f.submit %>
|
24
|
+
</div>
|
25
|
+
<% end %>
|
@@ -0,0 +1,25 @@
|
|
1
|
+
<h1>Listing posts</h1>
|
2
|
+
|
3
|
+
<table>
|
4
|
+
<tr>
|
5
|
+
<th>Title</th>
|
6
|
+
<th>Body</th>
|
7
|
+
<th></th>
|
8
|
+
<th></th>
|
9
|
+
<th></th>
|
10
|
+
</tr>
|
11
|
+
|
12
|
+
<% @posts.each do |post| %>
|
13
|
+
<tr>
|
14
|
+
<td><%= post.title %></td>
|
15
|
+
<td><%= post.body %></td>
|
16
|
+
<td><%= link_to 'Show', post %></td>
|
17
|
+
<td><%= link_to 'Edit', edit_admin_post_path(post) %></td>
|
18
|
+
<td><%= link_to 'Destroy', [:admin, post], confirm: 'Are you sure?', method: :delete %></td>
|
19
|
+
</tr>
|
20
|
+
<% end %>
|
21
|
+
</table>
|
22
|
+
|
23
|
+
<br />
|
24
|
+
|
25
|
+
<%= link_to 'New Post', new_admin_post_path %>
|
@@ -0,0 +1 @@
|
|
1
|
+
<h1>Hello world!</h1>
|
@@ -53,8 +53,6 @@ module Dummy
|
|
53
53
|
# Enable escaping HTML in JSON.
|
54
54
|
config.active_support.escape_html_entities_in_json = true
|
55
55
|
|
56
|
-
config.active_record.legacy_connection_handling = false
|
57
|
-
|
58
56
|
# Use SQL instead of Active Record's schema dumper when creating the database.
|
59
57
|
# This is necessary if your schema can't be completely dumped by the schema dumper,
|
60
58
|
# like if you have constraints or database-specific column types
|
data/test/dummy/config/routes.rb
CHANGED
@@ -0,0 +1,52 @@
|
|
1
|
+
class AddDeviseToAdmins < ActiveRecord::Migration[5.0]
|
2
|
+
def self.up
|
3
|
+
change_table(:admins) do |t|
|
4
|
+
## Database authenticatable
|
5
|
+
t.string :email, null: false, default: ""
|
6
|
+
t.string :encrypted_password, null: false, default: ""
|
7
|
+
|
8
|
+
## Recoverable
|
9
|
+
t.string :reset_password_token
|
10
|
+
t.datetime :reset_password_sent_at
|
11
|
+
|
12
|
+
## Rememberable
|
13
|
+
t.datetime :remember_created_at
|
14
|
+
|
15
|
+
## Trackable
|
16
|
+
t.integer :sign_in_count, default: 0
|
17
|
+
t.datetime :current_sign_in_at
|
18
|
+
t.datetime :last_sign_in_at
|
19
|
+
t.string :current_sign_in_ip
|
20
|
+
t.string :last_sign_in_ip
|
21
|
+
|
22
|
+
## Confirmable
|
23
|
+
# t.string :confirmation_token
|
24
|
+
# t.datetime :confirmed_at
|
25
|
+
# t.datetime :confirmation_sent_at
|
26
|
+
# t.string :unconfirmed_email # Only if using reconfirmable
|
27
|
+
|
28
|
+
## Lockable
|
29
|
+
t.integer :failed_attempts, default: 0 # Only if lock strategy is :failed_attempts
|
30
|
+
t.string :unlock_token # Only if unlock strategy is :email or :both
|
31
|
+
t.datetime :locked_at
|
32
|
+
|
33
|
+
## Token authenticatable
|
34
|
+
t.string :authentication_token
|
35
|
+
|
36
|
+
# Uncomment below if timestamps were not included in your original model.
|
37
|
+
# t.timestamps
|
38
|
+
end
|
39
|
+
|
40
|
+
add_index :admins, :email, unique: true
|
41
|
+
add_index :admins, :reset_password_token, unique: true
|
42
|
+
# add_index :admins, :confirmation_token, :unique => true
|
43
|
+
add_index :admins, :unlock_token, unique: true
|
44
|
+
add_index :admins, :authentication_token, unique: true
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.down
|
48
|
+
# By default, we don't want to make any assumption about how to roll back a migration when your
|
49
|
+
# model already existed. Please edit below which fields you would like to remove in this migration.
|
50
|
+
raise ActiveRecord::IrreversibleMigration
|
51
|
+
end
|
52
|
+
end
|