Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2要素認証機能を追加 #1622

Draft
wants to merge 23 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env.template
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ ENJU_LEAF_DEFAULT_LOCALE=ja
ENJU_LEAF_TIME_ZONE=Asia/Tokyo
# ENJU_LEAF_STORAGE_BUCKET=enju-leaf
# ENJU_LEAF_STORAGE_ENDPOINT=http://minio:9000
ENJU_LEAF_2FA_ENCRYPTION_KEY=
ENJU_LEAF_ACTION_MAILER_DELIVERY_METHOD=test
ENJU_LEAF_EXTRACT_TEXT=
ENJU_LEAF_EXTRACT_FILESIZE_LIMIT=2097152
# ENJU_LEAF_RESOURCESYNC_BASE_URL=http://localhost:8080
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/rubyonrails.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ jobs:
POSTGRES_USER: rails
POSTGRES_PASSWORD: password
CC_TEST_REPORTER_ID: c193cb8ea058a7d62fd62d6d05adaaf95f6bdf882c1039500b30b54494a36e52
ENJU_LEAF_2FA_ENCRYPTION_KEY: 64aba18129eb855d71a785b0aca726dd5eb8f4104cc779e97f2868d9a8cf796105f4599d9320793a5dfb58cc3ccbe93b293d5db1a12cba05ab79d15d5710cb51
SECRET_KEY_BASE: 4a54f07a6c5e604edac920225ab6f4d9a919edf8597f61ff85dbce0b3ab64433de86a54c192df1e242022c64108923d5382705a1e221c1ca39b79d902824d948
NODE_OPTIONS: --openssl-legacy-provider
steps:
- name: Checkout code
Expand Down
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ gem 'kramdown'
gem 'solid_queue'
gem 'mission_control-jobs'
gem 'acts-as-taggable-on'
gem 'devise-two-factor'
gem 'rqrcode'
gem 'resync' # , github: 'nabeta/resync', branch: 'add-datetime'
gem 'pretender'
gem 'caxlsx'
Expand Down
13 changes: 13 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ GEM
caxlsx_rails (0.6.4)
actionpack (>= 3.1)
caxlsx (>= 3.0)
chunky_png (1.4.0)
climate_control (1.2.0)
cocoon (1.2.15)
concurrent-ruby (1.3.4)
Expand All @@ -142,6 +143,11 @@ GEM
railties (>= 4.1.0)
responders
warden (~> 1.2.3)
devise-two-factor (5.1.0)
activesupport (~> 7.0)
devise (~> 4.0)
railties (~> 7.0)
rotp (~> 6.0)
diff-lcs (1.5.1)
docile (1.4.1)
dotenv (3.1.2)
Expand Down Expand Up @@ -390,6 +396,11 @@ GEM
xml-mapping_extensions (~> 0.4, >= 0.4.8)
rexml (3.3.5)
strscan
rotp (6.3.0)
rqrcode (2.2.0)
chunky_png (~> 1.0)
rqrcode_core (~> 1.0)
rqrcode_core (1.2.0)
rsolr (2.6.0)
builder (>= 2.1.2)
faraday (>= 0.9, < 3, != 2.0.0)
Expand Down Expand Up @@ -538,6 +549,7 @@ DEPENDENCIES
date_validator
debug
devise
devise-two-factor
dotenv-rails
factory_bot_rails (~> 6.4.0)
faraday-multipart
Expand Down Expand Up @@ -570,6 +582,7 @@ DEPENDENCIES
rdf-vocab
redis (>= 4.0.1)
resync
rqrcode
rspec-rails
rspec_junit_formatter
rss
Expand Down
7 changes: 7 additions & 0 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ class ApplicationController < ActionController::Base
include EnjuEvent::Controller
include EnjuSubject::Controller
include Pundit::Authorization
before_action :configure_permitted_parameters, if: :devise_controller?
after_action :verify_authorized, unless: :devise_controller?
impersonates :user

protected

def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_in, keys: [:otp_attempt])
end
end
13 changes: 13 additions & 0 deletions app/controllers/otp_secrets_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
class OtpSecretsController < ApplicationController
before_action :skip_authorization

def create
if current_user.validate_and_consume_otp!(params[:otp_attempt])
current_user.update!(
otp_required_for_login: true
)
end

redirect_to two_factor_authentication_url
end
end
22 changes: 22 additions & 0 deletions app/controllers/two_factor_authentications_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
class TwoFactorAuthenticationsController < ApplicationController
before_action :skip_authorization

def show
end

def create
current_user.update!(
otp_secret: User.generate_otp_secret
)

redirect_to two_factor_authentication_url
end

def destroy
current_user.update!(
otp_secret: nil,
otp_required_for_login: false
)
redirect_to two_factor_authentication_url
end
end
10 changes: 10 additions & 0 deletions app/helpers/my_account_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module MyAccountHelper
def otp_barcode(user = current_user, issuer = @library_group.display_name.localize)
qrcode = RQRCode::QRCode.new(current_user.otp_provisioning_uri(user.username, issuer: issuer))
image_tag("data:image/png;base64,#{Base64.encode64(qrcode.as_png(
bit_depth: 1,
size: 240,
module_px_size: 12
).to_s)}")
end
end
4 changes: 2 additions & 2 deletions app/models/concerns/enju_seed/enju_user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ module EnjuUser
with_options if: :password_required? do |v|
v.validates_presence_of :password
v.validates_confirmation_of :password
v.validates_length_of :password, allow_blank: true,
within: Devise::password_length
end
validates_length_of :password, allow_blank: true,
within: Devise::password_length

before_validation :set_lock_information
before_destroy :check_role_before_destroy
Expand Down
39 changes: 24 additions & 15 deletions app/models/user.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
class User < ApplicationRecord
devise :two_factor_authenticatable, :two_factor_backupable,
:otp_secret_encryption_key => ENV['ENJU_LEAF_2FA_ENCRYPTION_KEY']

# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
devise :registerable,
:recoverable, :rememberable, :lockable
include EnjuSeed::EnjuUser
include EnjuCirculation::EnjuUser
Expand All @@ -14,18 +17,24 @@ class User < ApplicationRecord
#
# Table name: users
#
# id :bigint not null, primary key
# email :string default(""), not null
# encrypted_password :string default(""), not null
# reset_password_token :string
# reset_password_sent_at :datetime
# remember_created_at :datetime
# created_at :datetime not null
# updated_at :datetime not null
# username :string
# expired_at :datetime
# failed_attempts :integer default(0)
# unlock_token :string
# locked_at :datetime
# confirmed_at :datetime
# id :bigint not null, primary key
# email :string default(""), not null
# encrypted_password :string default(""), not null
# reset_password_token :string
# reset_password_sent_at :datetime
# remember_created_at :datetime
# created_at :datetime not null
# updated_at :datetime not null
# username :string
# expired_at :datetime
# failed_attempts :integer default(0)
# unlock_token :string
# locked_at :datetime
# confirmed_at :datetime
# encrypted_otp_secret :string
# encrypted_otp_secret_iv :string
# encrypted_otp_secret_salt :string
# consumed_timestep :integer
# otp_required_for_login :boolean default(FALSE), not null
# otp_backup_codes :string is an Array
#
5 changes: 5 additions & 0 deletions app/views/devise/sessions/new.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@
<%= f.password_field :password, autocomplete: "off" %>
</div>

<div class="field">
<%= f.label :otp_attempt %><br />
<%= f.password_field :otp_attempt, autocomplete: "off" %>
</div>

<% if devise_mapping.rememberable? -%>
<div class="field">
<%= f.check_box :remember_me %>
Expand Down
19 changes: 19 additions & 0 deletions app/views/layouts/two_factor_authentications.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<%= @locale.to_s -%>" lang="<%= @locale.to_s -%>">
<head>
<meta charset="UTF-8" />
<%= render 'page/include' %>
<title><%= t('page.two_factor_authentication') %></title>
</head>

<%= render 'page/header' %>
<%= render 'page/menu' %>

<div id="content">
<%= yield %>
</div>

<%= render 'page/footer' %>

</body>
</html>
1 change: 1 addition & 0 deletions app/views/my_accounts/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
<ul>
<li><%= link_to t('page.edit'), edit_my_account_path -%></li>
<li><%= link_to t('activerecord.models.registration'), edit_user_registration_path -%></li>
<li><%= link_to t('page.two_factor_authentication'), two_factor_authentication_path -%></li>
<%- if policy(@profile).destroy? -%>
<li><%= link_to t('page.destroy'), @profile, data: {confirm: t('page.are_you_sure')}, method: :delete -%></li>
<%- end -%>
Expand Down
18 changes: 18 additions & 0 deletions app/views/two_factor_authentications/new.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<div id="content_detail" class="ui-corner-all ui-widget-content">
<h1 class="title"><%= t('page.two_factor_authentication') -%></h1>
<div id="content_list">
<% if current_user.otp_secret %>
<div><%= otp_barcode %></div>
<%= button_to 'Disable', two_factor_authentication_path, method: :delete %>
<% else %>
<div><%= otp_barcode %></div>
<%= form_with url: two_factor_authentication_path, method: :post do |f| %>
<%= f.text_field :otp_attempt %>
<%= f.submit 'Enable' %>
<% end %>
<% end %>
</div>
</div>

<div id="submenu" class="ui-corner-all ui-widget-content">
</div>
29 changes: 29 additions & 0 deletions app/views/two_factor_authentications/show.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<div id="content_detail" class="ui-corner-all ui-widget-content">
<h1 class="title"><%= t('page.two_factor_authentication') -%></h1>
<div id="content_list">
<% if current_user.otp_secret %>
<% if current_user.otp_required_for_login %>
<p><%= t('two_factor_authentication.already_enabled') %></p>
<% else %>
<div><%= otp_barcode %></div>

<%= form_with url: otp_secret_path, method: :post do |f| %>
<%= f.text_field :otp_attempt %>
<%= f.submit t('two_factor_authentication.verify') %>
<% end %>
<% end %>
<% end %>
</div>
</div>

<div id="submenu" class="ui-corner-all ui-widget-content">
<ul>
<% if current_user.otp_required_for_login %>
<li><%= link_to t('two_factor_authentication.disable'), two_factor_authentication_path, method: :delete, data: {confirm: t('page.are_you_sure')} %></li>
<% elsif current_user.otp_secret %>
<li><%= link_to t('two_factor_authentication.disable'), two_factor_authentication_path, method: :delete, data: {confirm: t('page.are_you_sure')} %></li>
<% else %>
<li><%= link_to t('two_factor_authentication.enable'), two_factor_authentication_path, method: :post, data: {confirm: t('page.are_you_sure')} %></li>
<% end %>
</ul>
</div>
5 changes: 5 additions & 0 deletions config/initializers/devise.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@
# Use this hook to configure devise mailer, warden hooks and so forth.
# Many of these configuration options can be set straight in your model.
Devise.setup do |config|
config.warden do |manager|
manager.default_strategies(:scope => :user).unshift :two_factor_authenticatable
manager.default_strategies(:scope => :user).unshift :two_factor_backupable
end

# The secret key used by Devise. Devise uses this key to generate
# random tokens. Changing this key will render invalid all existing
# confirmation, reset password and unlock tokens in the database.
Expand Down
7 changes: 7 additions & 0 deletions config/locales/enju_leaf_en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ en:
auto_generated_password: Set auto-generated password
friendly_id: Username
remember_me: "Remember me"
otp_attempt: Authentication code
role:
name: Name
display_name: Display name
Expand Down Expand Up @@ -296,6 +297,7 @@ en:
delegated: Delegated
impersonate: "Impersonate"
stop_impersonating: "Back to %{username}"
two_factor_authentication: Two factor authentication
title:
index: "index"
show: "show"
Expand Down Expand Up @@ -406,3 +408,8 @@ en:
no_number: No number
iiif:
open_in_new_window: Open in new window
two_factor_authentication:
enable: Enable
disable: Disable
verify: Verify
already_enabled: Two factor authentication is enabled.
7 changes: 7 additions & 0 deletions config/locales/enju_leaf_ja.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ ja:
auto_generated_password: パスワードの自動生成
friendly_id: ユーザ名
remember_me: "ウインドウを閉じてもログインしたままにする"
otp_attempt: 2要素認証コード
role:
name: 名前
display_name: 表示名
Expand Down Expand Up @@ -281,6 +282,7 @@ ja:
delegated: 代理
impersonate: "ログインユーザを切り替える"
stop_impersonating: "%{username}に戻る"
two_factor_authentication: 2要素認証
title:
index: "一覧"
show: "表示"
Expand Down Expand Up @@ -388,3 +390,8 @@ ja:
no_number: 番号なし
iiif:
open_in_new_window: 新しいウインドウで開く
two_factor_authentication:
enable: 有効化
disable: 無効化
verify: 確認
already_enabled: 2要素認証は有効になっています。
3 changes: 3 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,9 @@
resources :order_lists
end

resource :two_factor_authentication, only: [:show, :create, :destroy]
resource :otp_secret, only: :create

devise_for :users

get '/page/about' => 'page#about'
Expand Down
9 changes: 9 additions & 0 deletions db/migrate/20220319174806_add_devise_two_factor_to_users.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class AddDeviseTwoFactorToUsers < ActiveRecord::Migration[6.1]
def change
add_column :users, :encrypted_otp_secret, :string
add_column :users, :encrypted_otp_secret_iv, :string
add_column :users, :encrypted_otp_secret_salt, :string
add_column :users, :consumed_timestep, :integer
add_column :users, :otp_required_for_login, :boolean, default: false, null: false
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class AddDeviseTwoFactorBackupableToUsers < ActiveRecord::Migration[6.1]
def change
add_column :users, :otp_backup_codes, :string, array: true
end
end
6 changes: 6 additions & 0 deletions db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1971,6 +1971,12 @@
t.string "unlock_token"
t.datetime "locked_at", precision: nil
t.datetime "confirmed_at", precision: nil
t.string "encrypted_otp_secret"
t.string "encrypted_otp_secret_iv"
t.string "encrypted_otp_secret_salt"
t.integer "consumed_timestep"
t.boolean "otp_required_for_login", default: false, null: false
t.string "otp_backup_codes", array: true
t.index ["email"], name: "index_users_on_email"
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
t.index ["unlock_token"], name: "index_users_on_unlock_token", unique: true
Expand Down
Loading
Loading