door_check 0.3.2 → 0.4.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/app/controllers/concerns/door_check/entry_check.rb +1 -1
- data/app/controllers/door_check/second_steps_controller.rb +1 -1
- data/app/models/door_check/person_rule.rb +10 -2
- data/app/models/door_check/require_own_second_step.rb +10 -0
- data/app/models/door_check/require_second_step.rb +12 -1
- data/app/models/door_check/second_step.rb +13 -3
- data/app/views/door_check/second_steps/new.html.erb +2 -2
- data/app/views/door_check/settings/_sign_in_methods.html.erb +56 -31
- data/app/views/door_check/settings/_sign_in_rules.html.erb +21 -7
- data/db/migrate/20261113191736_add_members_choose_second_step_to_door_check_account_rules.rb +5 -0
- data/lib/door_check/configuration.rb +3 -1
- data/lib/door_check/version.rb +1 -1
- data/the_local/agents/door_check-develop.md +90 -19
- data/the_local/agents/door_check-info.md +49 -5
- data/the_local/agents/door_check-install.md +63 -3
- data/the_local/interface.yml +2 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 809331dc702d546a27776ecd87d0ad8a7f169defb4814ee895a49f1af926c934
|
|
4
|
+
data.tar.gz: e738576ea8756212978c60542adf296319761e95bfd38442f3f4274ccd80858a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3a414ec12f431ce25dab37699c546700c9f5c9bbc34dc333bd2cdf092f3ba03e4dfb118709aab29c5ade9f233ba2b535e7ce0798c4564a16bfb38204937bbaf6
|
|
7
|
+
data.tar.gz: b1a90170eeb48463e613b2a680e289cc50b1d4f4781d15e426b2d5adc9a950f3481a584be36ee72c2a7fa197a2398a51137d69c1eb290bdeb7a0665b121780a7
|
|
@@ -27,7 +27,7 @@ module DoorCheck
|
|
|
27
27
|
proved = door_check_proved_by.map(&:first)
|
|
28
28
|
proved += [ :trusted_device ] if door_check_trusted_browser?(person)
|
|
29
29
|
account = door_check_account
|
|
30
|
-
PersonRule.for(person).met_by?(proved) && (account.nil? || AccountRule.for(account).met_by?(proved))
|
|
30
|
+
PersonRule.for(person).met_by?(proved, account: account) && (account.nil? || AccountRule.for(account).met_by?(proved))
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
def door_check_trusted_browser?(person)
|
|
@@ -31,7 +31,7 @@ module DoorCheck
|
|
|
31
31
|
@person = door_check_person
|
|
32
32
|
@authenticator = DoorCheck.configuration.authenticator
|
|
33
33
|
@second_step_set_up = SecondStep.set_up?(@person)
|
|
34
|
-
@setups = SecondStep.
|
|
34
|
+
@setups = SecondStep.setups_before_second_step(door_check_account)
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
def prepare_passkey
|
|
@@ -6,8 +6,16 @@ module DoorCheck
|
|
|
6
6
|
find_or_initialize_by(person: person)
|
|
7
7
|
end
|
|
8
8
|
|
|
9
|
-
def met_by?(method_names)
|
|
10
|
-
!
|
|
9
|
+
def met_by?(method_names, account: nil)
|
|
10
|
+
!in_force_in?(account) || method_names.any? { |name| Methods.roles(name).include?(:second_step) }
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def in_force_in?(account)
|
|
14
|
+
second_step_required && may_choose_in?(account)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def may_choose_in?(account)
|
|
18
|
+
DoorCheck.configuration.people_choose_second_step && (account.nil? || AccountRule.for(account).members_choose_second_step)
|
|
11
19
|
end
|
|
12
20
|
end
|
|
13
21
|
end
|
|
@@ -7,8 +7,18 @@ module DoorCheck
|
|
|
7
7
|
end
|
|
8
8
|
|
|
9
9
|
def call
|
|
10
|
+
return Result.new(ok: false, message: refusal) unless PersonRule.for(@person).may_choose_in?(@account)
|
|
11
|
+
|
|
10
12
|
PersonRule.for(@person).update!(second_step_required: @values[:second_step_required] == "1")
|
|
11
13
|
Result.new(ok: true)
|
|
12
14
|
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def refusal
|
|
19
|
+
return "This app decides when you are asked for a second step." unless DoorCheck.configuration.people_choose_second_step
|
|
20
|
+
|
|
21
|
+
"Your account decides whether you are asked for a second step."
|
|
22
|
+
end
|
|
13
23
|
end
|
|
14
24
|
end
|
|
@@ -8,13 +8,24 @@ module DoorCheck
|
|
|
8
8
|
|
|
9
9
|
def call
|
|
10
10
|
return Result.new(ok: false, message: "Pick only methods this app offers.") if picks_unoffered_method?
|
|
11
|
+
return Result.new(ok: false, message: "This app decides when members are asked for a second step.") if sets_forbidden_members_choice?
|
|
11
12
|
|
|
12
|
-
AccountRule.for(@account).update!(
|
|
13
|
+
AccountRule.for(@account).update!(saved_values)
|
|
13
14
|
Result.new(ok: true)
|
|
14
15
|
end
|
|
15
16
|
|
|
16
17
|
private
|
|
17
18
|
|
|
19
|
+
def saved_values
|
|
20
|
+
saved = { second_step_required: @values[:second_step_required] == "1", accepted_methods: @values[:accepted_methods] }
|
|
21
|
+
saved[:members_choose_second_step] = @values[:members_choose_second_step] == "1" if @values.key?(:members_choose_second_step)
|
|
22
|
+
saved
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def sets_forbidden_members_choice?
|
|
26
|
+
@values.key?(:members_choose_second_step) && !DoorCheck.configuration.people_choose_second_step
|
|
27
|
+
end
|
|
28
|
+
|
|
18
29
|
def picks_unoffered_method?
|
|
19
30
|
picked = Array(@values[:accepted_methods]).map(&:to_sym)
|
|
20
31
|
(picked - DoorCheck.configuration.offered_methods).any?
|
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
module DoorCheck
|
|
2
2
|
module SecondStep
|
|
3
|
-
|
|
3
|
+
Setup = Data.define(:method_name, :label, :page, :request_method, :before_second_step)
|
|
4
|
+
|
|
5
|
+
SETUPS = [
|
|
6
|
+
Setup.new(:authenticator_app, "Set up an authenticator app", :new_authenticator_path, :get, true),
|
|
7
|
+
Setup.new(:passkey, "Add a passkey or hardware key", :new_passkey_path, :get, false),
|
|
8
|
+
Setup.new(:backup_codes, "Make new backup codes", :backup_codes_path, :post, false)
|
|
9
|
+
].freeze
|
|
4
10
|
|
|
5
11
|
def self.set_up?(person)
|
|
6
12
|
Credential.exists?(person: person, method_name: %w[authenticator_app backup_codes passkey]) || DoorCheck.configuration.authenticator&.set_up?(person)
|
|
7
13
|
end
|
|
8
14
|
|
|
9
|
-
def self.offered_setups
|
|
15
|
+
def self.offered_setups
|
|
16
|
+
SETUPS.select { |setup| DoorCheck.configuration.offered_methods.include?(setup.method_name) }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.setups_before_second_step(account)
|
|
10
20
|
rule = account && AccountRule.for(account)
|
|
11
|
-
|
|
21
|
+
offered_setups.select { |setup| setup.before_second_step && (rule.nil? || rule.accepts?(setup.method_name)) }
|
|
12
22
|
end
|
|
13
23
|
end
|
|
14
24
|
end
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
<% else %>
|
|
22
22
|
<%= ui_section(title: "Set up a second step", subtitle: "A second step is required here, and you have none set up yet.") do %>
|
|
23
23
|
<%= ui_grid(gap: :sm) do %>
|
|
24
|
-
<% @setups.each do |
|
|
25
|
-
<%= ui_button(label: "Set up #{method_name.to_s.humanize.downcase}", href: door_check.public_send(
|
|
24
|
+
<% @setups.each do |setup| %>
|
|
25
|
+
<%= ui_button(label: "Set up #{setup.method_name.to_s.humanize.downcase}", href: door_check.public_send(setup.page), full_width: true) %>
|
|
26
26
|
<% end %>
|
|
27
27
|
<% end %>
|
|
28
28
|
<% end %>
|
|
@@ -1,40 +1,65 @@
|
|
|
1
|
-
<%=
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
<%= ui_section(title: "Sign-in methods") do %>
|
|
2
|
+
<%= ui_panel do %>
|
|
3
|
+
<%= ui_data_table(items: DoorCheck::Credential.where(person: person),
|
|
4
|
+
columns: [ { label: "Method" }, { last_used: "Last used" }, { accepted_by: "Accepted by" } ],
|
|
5
|
+
empty_message: "No sign-in methods set up yet.", phone_list: true) do |table| %>
|
|
6
|
+
<% table.actions do |credential| %>
|
|
7
|
+
<%= ui_action_menu_item(label: "Remove", href: "#{submit_urls[:remove]}?credential_id=#{credential.id}", method: :patch,
|
|
8
|
+
confirm: "Remove this sign-in method?") %>
|
|
9
|
+
<% end %>
|
|
10
|
+
<% end %>
|
|
11
|
+
<%= ui_section(spacing: :sm) do %>
|
|
12
|
+
<%= ui_grid(cols: { default: 1, sm: 3 }, gap: :sm) do %>
|
|
13
|
+
<% DoorCheck::SecondStep.offered_setups.each do |setup| %>
|
|
14
|
+
<%= ui_button(label: setup.label, href: door_check.public_send(setup.page), method: setup.request_method, variant: :secondary, full_width: true) %>
|
|
15
|
+
<% end %>
|
|
16
|
+
<% end %>
|
|
17
|
+
<% end %>
|
|
7
18
|
<% end %>
|
|
8
19
|
<% end %>
|
|
9
|
-
<%=
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
20
|
+
<%= ui_section(title: "Signed-in sessions") do %>
|
|
21
|
+
<%= ui_panel do %>
|
|
22
|
+
<%= ui_data_table(items: DoorCheck::SignedInSession.where(person: person, signed_out_at: nil).order(last_used_at: :desc),
|
|
23
|
+
columns: [ { device: "Signed in on" }, { last_used: "Last used" } ],
|
|
24
|
+
empty_message: "No signed-in sessions.", phone_list: true) do |table| %>
|
|
25
|
+
<% table.actions do |signed_in_session| %>
|
|
26
|
+
<%= ui_action_menu_item(label: "Sign out", href: "#{submit_urls[:sign_out_session]}?signed_in_session_id=#{signed_in_session.id}", method: :patch,
|
|
27
|
+
confirm: "Sign out this session?") %>
|
|
28
|
+
<% end %>
|
|
29
|
+
<% end %>
|
|
15
30
|
<% end %>
|
|
16
31
|
<% end %>
|
|
17
32
|
<% if DoorCheck.configuration.offered_methods.include?(:trusted_device) %>
|
|
18
|
-
<%=
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
33
|
+
<%= ui_section(title: "Trusted browsers") do %>
|
|
34
|
+
<%= ui_panel do %>
|
|
35
|
+
<%= ui_data_table(items: DoorCheck::TrustedDevice.where(person: person).where("expires_at > ?", Time.current),
|
|
36
|
+
columns: [ { device: "Trusted browser" }, { trusted_until: "Trusted until" } ],
|
|
37
|
+
empty_message: "No trusted browsers.", phone_list: true) do |table| %>
|
|
38
|
+
<% table.actions do |trusted_device| %>
|
|
39
|
+
<%= ui_action_menu_item(label: "Remove", href: "#{submit_urls[:remove_trusted_browser]}?trusted_device_id=#{trusted_device.id}", method: :patch,
|
|
40
|
+
confirm: "Remove this trusted browser?") %>
|
|
41
|
+
<% end %>
|
|
42
|
+
<% end %>
|
|
24
43
|
<% end %>
|
|
25
44
|
<% end %>
|
|
26
45
|
<% end %>
|
|
27
|
-
|
|
28
|
-
<%=
|
|
29
|
-
|
|
30
|
-
<% if DoorCheck.
|
|
31
|
-
|
|
32
|
-
<% end %>
|
|
33
|
-
<% if DoorCheck.configuration.
|
|
34
|
-
|
|
35
|
-
<%
|
|
36
|
-
<%= ui_form(action: submit_urls[:own_second_step], method: :patch) do %>
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
46
|
+
<%= ui_section(title: "Second step") do %>
|
|
47
|
+
<%= ui_panel do %>
|
|
48
|
+
<%= ui_grid(gap: :md) do %>
|
|
49
|
+
<% if local_assigns[:account] && DoorCheck::AccountRule.for(local_assigns[:account]).second_step_required %>
|
|
50
|
+
<%= ui_alert(message: "Your account asks everyone for a second step.") %>
|
|
51
|
+
<% end %>
|
|
52
|
+
<% if !DoorCheck.configuration.people_choose_second_step %>
|
|
53
|
+
<%= ui_alert(message: "This app decides when you are asked for a second step.") %>
|
|
54
|
+
<% elsif DoorCheck::PersonRule.for(person).may_choose_in?(local_assigns[:account]) %>
|
|
55
|
+
<%= ui_form(action: submit_urls[:own_second_step], method: :patch) do %>
|
|
56
|
+
<%= ui_form_field(attribute: "second_step_required", label: "Ask me for a second step every time I sign in",
|
|
57
|
+
type: :checkbox, value: (DoorCheck::PersonRule.for(person).second_step_required ? "1" : "0")) %>
|
|
58
|
+
<%= ui_button(label: "Save") %>
|
|
59
|
+
<% end %>
|
|
60
|
+
<% else %>
|
|
61
|
+
<%= ui_alert(message: "Your account decides whether you are asked for a second step.") %>
|
|
62
|
+
<% end %>
|
|
63
|
+
<% end %>
|
|
64
|
+
<% end %>
|
|
40
65
|
<% end %>
|
|
@@ -1,10 +1,24 @@
|
|
|
1
1
|
<% rule = DoorCheck::AccountRule.for(account) %>
|
|
2
|
-
<%=
|
|
3
|
-
|
|
4
|
-
<%=
|
|
2
|
+
<%= ui_panel do %>
|
|
3
|
+
<%= ui_form(action: submit_url, method: :patch) do %>
|
|
4
|
+
<%= ui_section(title: "Accepted methods", subtitle: "Members enter this account only after using one of the ticked methods.", spacing: :sm) do %>
|
|
5
|
+
<% DoorCheck.configuration.offered_methods.each do |method_name| %>
|
|
6
|
+
<%= ui_checkbox_row(name: "accepted_methods[]", value: method_name, label: method_name.to_s.humanize, checked: rule.accepts?(method_name)) %>
|
|
7
|
+
<% end %>
|
|
8
|
+
<% end %>
|
|
9
|
+
<%= ui_section(title: "Second step", subtitle: "When ticked, members who signed in with only a password are asked for a second step before they enter.", spacing: :sm) do %>
|
|
10
|
+
<%= ui_form_field(attribute: "second_step_required", label: "Require a second step to enter this account",
|
|
11
|
+
type: :checkbox, value: (rule.second_step_required ? "1" : "0")) %>
|
|
12
|
+
<% end %>
|
|
13
|
+
<% people_choose = DoorCheck.configuration.people_choose_second_step %>
|
|
14
|
+
<%= ui_section(title: "Members", subtitle: (people_choose ? "When ticked, a member who asks for a second step every time they sign in is asked for it in this account." : nil), spacing: :sm) do %>
|
|
15
|
+
<% if people_choose %>
|
|
16
|
+
<%= ui_form_field(attribute: "members_choose_second_step", label: "Members choose their own second step",
|
|
17
|
+
type: :checkbox, value: (rule.members_choose_second_step ? "1" : "0")) %>
|
|
18
|
+
<% else %>
|
|
19
|
+
<%= ui_alert(message: "This app decides when members are asked for a second step.") %>
|
|
20
|
+
<% end %>
|
|
21
|
+
<% end %>
|
|
22
|
+
<%= ui_button(label: "Save") %>
|
|
5
23
|
<% end %>
|
|
6
|
-
<%= ui_form_field(attribute: "second_step_required", label: "Require a second step to enter this account",
|
|
7
|
-
type: :checkbox, value: (rule.second_step_required ? "1" : "0"),
|
|
8
|
-
hint: "Members who signed in with only a password are asked for their authenticator code when they enter.") %>
|
|
9
|
-
<%= ui_button(label: "Save") %>
|
|
10
24
|
<% end %>
|
|
@@ -7,12 +7,14 @@ module DoorCheck
|
|
|
7
7
|
attr_reader :offered_methods
|
|
8
8
|
attr_writer :authenticator_issuer
|
|
9
9
|
attr_accessor :person_method, :account_method, :parent_controller, :parent_mailer, :existing_authenticator, :person_accounts,
|
|
10
|
-
:find_person_by_email, :sign_in_person, :sign_out_person, :passkey_domain, :locate_ip, :stylesheet, :head_partial
|
|
10
|
+
:find_person_by_email, :sign_in_person, :sign_out_person, :passkey_domain, :locate_ip, :stylesheet, :head_partial,
|
|
11
|
+
:people_choose_second_step
|
|
11
12
|
|
|
12
13
|
def initialize
|
|
13
14
|
@parent_controller = "::ApplicationController"
|
|
14
15
|
@parent_mailer = "::ApplicationMailer"
|
|
15
16
|
@stylesheet = "application"
|
|
17
|
+
@people_choose_second_step = true
|
|
16
18
|
end
|
|
17
19
|
|
|
18
20
|
def offered_methods=(names)
|
data/lib/door_check/version.rb
CHANGED
|
@@ -34,7 +34,7 @@ Method names:
|
|
|
34
34
|
Rule and session actions. Each is built with `person:`, `account:` and
|
|
35
35
|
`values:`, all three required, and `call` returns a `DoorCheck::Result`:
|
|
36
36
|
|
|
37
|
-
- `DoorCheck::RequireSecondStep` — saves the account's rule: the methods it accepts from `values[:accepted_methods]`,
|
|
37
|
+
- `DoorCheck::RequireSecondStep` — saves the account's rule: the methods it accepts from `values[:accepted_methods]`, whether it requires a second step from `values[:second_step_required]`, and whether its members choose their own second step from `values[:members_choose_second_step]`.
|
|
38
38
|
- `DoorCheck::RequireOwnSecondStep` — saves the person's own rule: whether they require a second step, from `values[:second_step_required]`.
|
|
39
39
|
- `DoorCheck::RemoveMethod` — removes the person's saved method whose id is `values[:credential_id]`.
|
|
40
40
|
- `DoorCheck::SignOutSession` — signs out the person's signed-in session whose id is `values[:signed_in_session_id]`.
|
|
@@ -133,7 +133,8 @@ class SettingsController < ApplicationController
|
|
|
133
133
|
|
|
134
134
|
def sign_in_rules
|
|
135
135
|
finish DoorCheck::RequireSecondStep.new(person: current_user, account: door_check_account,
|
|
136
|
-
values: params.permit(:second_step_required,
|
|
136
|
+
values: params.permit(:second_step_required, :members_choose_second_step,
|
|
137
|
+
accepted_methods: [])).call
|
|
137
138
|
end
|
|
138
139
|
|
|
139
140
|
private
|
|
@@ -151,11 +152,41 @@ end
|
|
|
151
152
|
`current_user` stands for the host's person method. Use the same method the
|
|
152
153
|
install local gave door_check as the person method.
|
|
153
154
|
|
|
155
|
+
The person section shows titled groups, in this order: Sign-in methods,
|
|
156
|
+
Signed-in sessions, Trusted browsers and Second step. It leaves out the Trusted
|
|
157
|
+
browsers group when the app does not offer `trusted_device`, so the trusted
|
|
158
|
+
browser route is only reached when `trusted_device` is offered. Ask the
|
|
159
|
+
developer whether to write that route when it is not offered.
|
|
160
|
+
|
|
161
|
+
The account section is one panel holding one form, with titled groups in this
|
|
162
|
+
order: Accepted methods, Second step and Members, then a single Save button.
|
|
163
|
+
That one Save sends every group's values together to the `sign_in_rules`
|
|
164
|
+
route.
|
|
165
|
+
|
|
154
166
|
What each section sends, and what the action does with it:
|
|
155
167
|
|
|
168
|
+
- The Second step group shows the alert "Your account asks everyone for a
|
|
169
|
+
second step." when it was rendered with an account whose rule requires a
|
|
170
|
+
second step. It shows this as well as the checkbox or the alerts below.
|
|
171
|
+
- When the app is set so people do not choose their own second step, the
|
|
172
|
+
person section shows the alert "This app decides when you are asked for a
|
|
173
|
+
second step." in place of the second-step checkbox, whatever the account
|
|
174
|
+
allows, and sends nothing to the own second-step route.
|
|
175
|
+
- Otherwise the person section shows its second-step checkbox only while the
|
|
176
|
+
account it was rendered with lets members choose, or when it was rendered
|
|
177
|
+
with no account. When the account does not let members choose, it shows the
|
|
178
|
+
alert "Your account decides whether you are asked for a second step." in
|
|
179
|
+
place of the checkbox, and sends nothing to the own second-step route.
|
|
156
180
|
- The person section's second-step form sends `second_step_required`.
|
|
157
181
|
`RequireOwnSecondStep` saves the rule as on when the value is `"1"` and off
|
|
158
182
|
for any other value.
|
|
183
|
+
- `RequireOwnSecondStep` refuses the save, keeps the person's earlier choice,
|
|
184
|
+
and returns a result whose `ok?` is false when the person may not choose
|
|
185
|
+
with the `account` it was given:
|
|
186
|
+
- When the app is set so people do not choose their own second step, the
|
|
187
|
+
`message` is `"This app decides when you are asked for a second step."`.
|
|
188
|
+
- Otherwise, when `account` is not nil and does not let members choose, the
|
|
189
|
+
`message` is `"Your account decides whether you are asked for a second step."`.
|
|
159
190
|
- The person section's Remove on a saved method sends `credential_id`.
|
|
160
191
|
`RemoveMethod` removes that method only when it belongs to `person`.
|
|
161
192
|
- The person section's Sign out on a session sends `signed_in_session_id`.
|
|
@@ -166,11 +197,28 @@ What each section sends, and what the action does with it:
|
|
|
166
197
|
`RemoveTrustedBrowser` forgets that browser only when it belongs to `person`,
|
|
167
198
|
and the browser is asked for a second step again.
|
|
168
199
|
- The account section sends `accepted_methods[]`, one value per ticked method,
|
|
169
|
-
and `
|
|
170
|
-
ticked method is not one the app offers, and returns
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
the second step on only when
|
|
200
|
+
`second_step_required` and `members_choose_second_step`. `RequireSecondStep`
|
|
201
|
+
refuses the save when a ticked method is not one the app offers, and returns
|
|
202
|
+
a result whose `ok?` is false and whose `message` is
|
|
203
|
+
`"Pick only methods this app offers."`. Otherwise it saves the values to the
|
|
204
|
+
rule of the `account` it was given, with the second step on only when
|
|
205
|
+
`second_step_required` is `"1"` and members choosing only when
|
|
206
|
+
`members_choose_second_step` is `"1"`.
|
|
207
|
+
- When the app is set so people do not choose their own second step, the
|
|
208
|
+
account section shows the alert "This app decides when members are asked for
|
|
209
|
+
a second step." in place of the members checkbox, and sends no
|
|
210
|
+
`members_choose_second_step`.
|
|
211
|
+
- When the app is set so people do not choose their own second step and
|
|
212
|
+
`values` has a `members_choose_second_step` key, whatever its value,
|
|
213
|
+
`RequireSecondStep` refuses the save, keeps the account's earlier rule, and
|
|
214
|
+
returns a result whose `ok?` is false and whose `message` is
|
|
215
|
+
`"This app decides when members are asked for a second step."`. A save that
|
|
216
|
+
ticks a method the app does not offer is refused with the offered-methods
|
|
217
|
+
message first.
|
|
218
|
+
- Permit `members_choose_second_step` in the `sign_in_rules` action. When
|
|
219
|
+
`values` has no `members_choose_second_step` key, `RequireSecondStep` leaves
|
|
220
|
+
the account's setting as it was, so an action that does not permit it never
|
|
221
|
+
changes it.
|
|
174
222
|
|
|
175
223
|
Ask the developer who may save the account's rules. door_check does not check
|
|
176
224
|
it, so the `sign_in_rules` action must refuse anyone the host does not allow,
|
|
@@ -202,22 +250,26 @@ before building `RequireSecondStep`.
|
|
|
202
250
|
3. The sign-in email always carries both a code and a link. The link returns
|
|
203
251
|
not found unless `email_link` is offered, so ask the developer whether to
|
|
204
252
|
offer `email_link` whenever `email_code` is offered.
|
|
205
|
-
4.
|
|
253
|
+
4. The person section's Sign-in methods group already shows buttons to set up
|
|
254
|
+
an authenticator app, add a passkey and make new backup codes, each only
|
|
255
|
+
when its method is offered. Add those links elsewhere only when the
|
|
256
|
+
developer asks for them outside the settings page.
|
|
257
|
+
5. Put the sign-in links on the host's sign-in page, for a person who is not
|
|
206
258
|
signed in. The approvals, authenticator, passkey and backup codes links are
|
|
207
259
|
for a signed-in person only.
|
|
208
|
-
|
|
260
|
+
6. Link to `backup_codes_path` with a form that sends a `POST`, such as
|
|
209
261
|
`button_to`, never a plain link.
|
|
210
|
-
|
|
262
|
+
7. When the person already has a second step set up, the authenticator, passkey
|
|
211
263
|
and backup codes pages first ask for it if this session has not passed one,
|
|
212
264
|
then send the person back.
|
|
213
|
-
|
|
265
|
+
8. The authenticator page keeps showing the same secret key in this browser
|
|
214
266
|
session until setup succeeds, so reloading it or entering a wrong code does
|
|
215
267
|
not change the key the person already scanned. A wrong code shows an error on
|
|
216
268
|
the same page and saves nothing.
|
|
217
|
-
|
|
269
|
+
9. When the browser session no longer holds the key, such as after the person
|
|
218
270
|
signed out and back in, saving a code sends the person back to the
|
|
219
271
|
authenticator page with a new key and a warning to scan it again.
|
|
220
|
-
|
|
272
|
+
10. A right code saves the authenticator app, counts as this session's second
|
|
221
273
|
step, and sends the person to the page they were on before door_check asked
|
|
222
274
|
for a second step, or to `/` when there was none. The app lists the account
|
|
223
275
|
under the person's email when the person responds to `email`.
|
|
@@ -227,15 +279,34 @@ before building `RequireSecondStep`.
|
|
|
227
279
|
- Never edit door_check's records directly. Change rules, sessions, saved methods
|
|
228
280
|
and trusted browsers through the five actions above.
|
|
229
281
|
- Pass the five actions the person from the host's person method and the
|
|
230
|
-
account from `door_check_account`. `RequireOwnSecondStep
|
|
282
|
+
account from `door_check_account`. `RequireOwnSecondStep` decides from that
|
|
283
|
+
account whether the person may choose, so passing another account gives a
|
|
284
|
+
different answer than the settings section showed. `RemoveMethod`,
|
|
231
285
|
`SignOutSession` and `RemoveTrustedBrowser` take `account:` but do not use it.
|
|
232
|
-
- `
|
|
233
|
-
`
|
|
234
|
-
|
|
286
|
+
- Show `result.message` whenever `ok?` is false, since `RequireSecondStep` and
|
|
287
|
+
`RequireOwnSecondStep` can both refuse a save.
|
|
288
|
+
- `RemoveMethod`, `SignOutSession` and `RemoveTrustedBrowser` always return a
|
|
289
|
+
result whose `ok?` is true. An id that does not belong to the person changes
|
|
290
|
+
nothing and still returns true.
|
|
235
291
|
- When no `accepted_methods[]` box is ticked, the form sends none and the
|
|
236
292
|
account's rule is saved as accepting every offered method.
|
|
237
|
-
-
|
|
238
|
-
|
|
293
|
+
- An account lets its members choose their own second step until its rule is
|
|
294
|
+
saved with `members_choose_second_step` off.
|
|
295
|
+
- While the app lets people choose and the current account lets members
|
|
296
|
+
choose, a person's rule and the account's rule both apply, so a person who requires a second step is asked
|
|
297
|
+
for one whatever the account requires.
|
|
298
|
+
- While the current account does not let members choose, the person's own rule
|
|
299
|
+
is not in force there, and only the account's rule decides whether a second
|
|
300
|
+
step is asked for. The person's saved choice is kept, and it applies again
|
|
301
|
+
once the account lets members choose.
|
|
302
|
+
- When `door_check_account` is nil, the person's own rule is in force unless
|
|
303
|
+
the app is set so people do not choose their own second step.
|
|
304
|
+
- While the app is set so people do not choose their own second step, no
|
|
305
|
+
person's own rule is in force in any account or with no account, and only
|
|
306
|
+
the account's rule decides whether a second step is asked for. Each person's
|
|
307
|
+
saved choice is kept, and it applies again once the app lets people choose.
|
|
308
|
+
Read the host's door_check initializer, or ask the developer, to learn which
|
|
309
|
+
way the app is set.
|
|
239
310
|
- Out of scope: adding the gem, its migrations, its routes, its settings, the
|
|
240
311
|
host controller's tracking and second-step check, and placing the settings
|
|
241
312
|
sections. Those belong to the install local.
|
|
@@ -62,12 +62,56 @@ door_check declares nothing for this local to document.
|
|
|
62
62
|
- **Offered methods** — the subset of known methods this app turns on. Naming a
|
|
63
63
|
method door_check does not know is an error at boot. A page for a method the
|
|
64
64
|
app does not offer returns not found.
|
|
65
|
-
- **
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
- **People choosing their own second step** — an app-wide switch, on by
|
|
66
|
+
default. When the app turns it off, no person rule is in force anywhere, and
|
|
67
|
+
only account rules decide when a second step is asked for.
|
|
68
|
+
- **Account rule** — whether an account requires a second step, which of the
|
|
69
|
+
offered methods it accepts, and whether its members choose their own second
|
|
70
|
+
step. An account can only narrow what the app offers. Members choose by
|
|
71
|
+
default, and only while the app lets people choose.
|
|
72
|
+
- **Refused account rule save** — saving an account rule that accepts a method
|
|
73
|
+
the app does not offer is refused with "Pick only methods this app offers."
|
|
74
|
+
Saving one that sets whether members choose, while the app does not let people
|
|
75
|
+
choose, is refused with "This app decides when members are asked for a second
|
|
76
|
+
step." A refused save changes nothing, and the account keeps its earlier rule.
|
|
77
|
+
- **Person rule** — whether a person requires a second step on their own login.
|
|
78
|
+
It is in force only while the app lets people choose, and then with no
|
|
79
|
+
account or in an account that lets members choose. Elsewhere only the
|
|
80
|
+
account's rule decides, and the person's saved choice is kept and applies
|
|
81
|
+
again once both the app and the account let them choose.
|
|
82
|
+
- **Refused person rule save** — saving a person rule where the person may not
|
|
83
|
+
choose is refused. The message is "This app decides when you are asked for a
|
|
84
|
+
second step." when the app does not let people choose, and "Your account
|
|
85
|
+
decides whether you are asked for a second step." when the person's account
|
|
86
|
+
does not let members choose. A refused save changes nothing, and the person
|
|
87
|
+
keeps their earlier choice.
|
|
88
|
+
- **Personal settings section** — the section where a person sees their saved
|
|
89
|
+
methods, signed-in sessions and trusted browsers, and sets their person rule.
|
|
90
|
+
It shows four titled groups, each in its own panel: Sign-in methods, with the
|
|
91
|
+
buttons to add one; Signed-in sessions; Trusted browsers, left out when the
|
|
92
|
+
app does not offer trusted browsers; and Second step. On a phone each table
|
|
93
|
+
shows as a list and the add buttons stack at full width.
|
|
94
|
+
- **Second step group** — the part of the personal settings section that holds
|
|
95
|
+
the person rule's checkbox. When the person's current account requires a
|
|
96
|
+
second step, it shows the note "Your account asks everyone for a second
|
|
97
|
+
step." above the rest. When the app does not let people choose, it shows the
|
|
98
|
+
note "This app decides when you are asked for a second step." in place of the
|
|
99
|
+
checkbox. When the person's current account does not let members choose, it
|
|
100
|
+
shows the note "Your account decides whether you are asked for a second
|
|
101
|
+
step." instead.
|
|
102
|
+
- **Account settings section** — the section where an account's rule is set,
|
|
103
|
+
in one panel with one Save button. It shows three titled groups, each with a
|
|
104
|
+
line saying what ticking it does. Accepted methods lists every offered method,
|
|
105
|
+
and members enter the account only after using one of the ticked methods.
|
|
106
|
+
Second step holds the checkbox that makes members who signed in with only a
|
|
107
|
+
password take a second step before they enter. Members holds the checkbox that
|
|
108
|
+
lets a member's own second-step choice apply in this account. When the app
|
|
109
|
+
does not let people choose, the Members group shows the note "This app
|
|
110
|
+
decides when members are asked for a second step." in place of its checkbox
|
|
111
|
+
and line.
|
|
69
112
|
- **Proof** — a method a session has passed. A person may enter a page once the
|
|
70
|
-
session's proofs meet
|
|
113
|
+
session's proofs meet their current account's rule and, where it is in force,
|
|
114
|
+
their person rule.
|
|
71
115
|
Otherwise they are sent to the second step page and returned afterwards.
|
|
72
116
|
- **Signed-in session** — one sign-in on one device, with its device, IP address
|
|
73
117
|
and last use. Ending one signs that device out on its next request.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: door_check-install
|
|
3
|
-
description: Use to hook door_check into a project — adding the gem, copying its migrations, mounting its routes, configuring the offered methods and the host's person, account, sign-in and sign-out hooks, the parent controller and mailer, the passkey domain, the IP lookup, the stylesheet and head partial, the name shown in authenticator apps, adding the sign-in tracking and second-step check to the host's controller, and placing the two settings sections.
|
|
3
|
+
description: Use to hook door_check into a project — adding the gem, copying its migrations, mounting its routes, configuring the offered methods and the host's person, account, sign-in and sign-out hooks, the parent controller and mailer, the passkey domain, the IP lookup, the stylesheet and head partial, the name shown in authenticator apps, whether people may choose their own second step, adding the sign-in tracking and second-step check to the host's controller, and placing the two settings sections.
|
|
4
4
|
tools: Bash, Read, Edit
|
|
5
5
|
scope: sign-in — the sign-in methods and second steps an app offers, each account narrows, and each person chooses from
|
|
6
6
|
---
|
|
@@ -36,11 +36,12 @@ trusted browsers.
|
|
|
36
36
|
- `config.stylesheet` — the stylesheet door_check's pages link.
|
|
37
37
|
- `config.head_partial` — a host partial rendered at the end of the head of door_check's pages.
|
|
38
38
|
- `config.authenticator_issuer` — the name an authenticator app shows above the account it adds during setup.
|
|
39
|
+
- `config.people_choose_second_step` — whether people may require a second step on their own login, true or false.
|
|
39
40
|
- `include DoorCheck::Tracking` — adds sign-in recording and the reads of proofs and the current account to the host's controller.
|
|
40
41
|
- `include DoorCheck::EntryCheck` — adds the second-step check to the host's controller.
|
|
41
42
|
- `before_action :door_check_check_entry` — runs that check on every host request.
|
|
42
43
|
- `door_check/settings/sign_in_methods` — the partial a person uses to see and remove their methods, sessions and trusted browsers, set up new ones, and require a second step on their own login.
|
|
43
|
-
- `door_check/settings/sign_in_rules` — the partial an account admin uses to choose which offered methods the account accepts
|
|
44
|
+
- `door_check/settings/sign_in_rules` — the partial an account admin uses to choose which offered methods the account accepts, whether it requires a second step, and whether its members' own second-step choice applies inside it.
|
|
44
45
|
|
|
45
46
|
## How to use it
|
|
46
47
|
|
|
@@ -139,6 +140,17 @@ trusted browsers.
|
|
|
139
140
|
application's module, such as `"Shop"` for `Shop::Application`. Set it when
|
|
140
141
|
people should see another name in their authenticator app. Changing it does
|
|
141
142
|
not rename accounts people already added.
|
|
143
|
+
- `config.people_choose_second_step = false` — defaults to true. Set it to
|
|
144
|
+
false when only the app and its accounts' rules decide who is asked for a
|
|
145
|
+
second step. While it is false, no person's saved choice is applied in any
|
|
146
|
+
account, the personal section shows "This app decides when you are asked
|
|
147
|
+
for a second step." in place of its checkbox, and the account section
|
|
148
|
+
shows "This app decides when members are asked for a second step." in
|
|
149
|
+
place of its members checkbox. While it is false, door_check also refuses
|
|
150
|
+
a save of a person's own second-step choice with the first message, and a
|
|
151
|
+
save of the account section that carries the members choice with the
|
|
152
|
+
second. A refused save leaves the earlier value in place. Saved choices
|
|
153
|
+
are kept, and apply again if it is set back to true.
|
|
142
154
|
|
|
143
155
|
8. Add the tracking and the second-step check to the host's
|
|
144
156
|
`app/controllers/application_controller.rb`, or to the controller named in
|
|
@@ -164,6 +176,7 @@ trusted browsers.
|
|
|
164
176
|
```erb
|
|
165
177
|
<%= render "door_check/settings/sign_in_methods",
|
|
166
178
|
person: current_user,
|
|
179
|
+
account: current_account,
|
|
167
180
|
submit_urls: {
|
|
168
181
|
own_second_step: own_second_step_settings_path,
|
|
169
182
|
remove: remove_sign_in_method_settings_path,
|
|
@@ -181,7 +194,54 @@ trusted browsers.
|
|
|
181
194
|
- `remove` receives `credential_id`.
|
|
182
195
|
- `sign_out_session` receives `signed_in_session_id`.
|
|
183
196
|
- `remove_trusted_browser` receives `trusted_device_id`.
|
|
184
|
-
- The account section's URL receives `accepted_methods[]
|
|
197
|
+
- The account section's URL receives `accepted_methods[]`,
|
|
198
|
+
`second_step_required` and `members_choose_second_step`. While
|
|
199
|
+
`people_choose_second_step` is false it does not send
|
|
200
|
+
`members_choose_second_step`.
|
|
201
|
+
- The host action behind the account section's URL must pass
|
|
202
|
+
`members_choose_second_step` on only when the request carries it.
|
|
203
|
+
door_check keeps the account's earlier value when the parameter is
|
|
204
|
+
missing, and refuses the save when it is present while
|
|
205
|
+
`people_choose_second_step` is false.
|
|
206
|
+
|
|
207
|
+
The personal section renders its own titled groups, so place it on the page
|
|
208
|
+
without a heading or panel around it:
|
|
209
|
+
- **Sign-in methods** — the person's saved methods, with a button to set up
|
|
210
|
+
an authenticator app, a passkey or new backup codes for each of those
|
|
211
|
+
that `offered_methods` includes.
|
|
212
|
+
- **Signed-in sessions** — the person's sessions that are still signed in.
|
|
213
|
+
- **Trusted browsers** — shown only when `trusted_device` is offered.
|
|
214
|
+
- **Second step** — the person's own second-step checkbox, or a note in its
|
|
215
|
+
place.
|
|
216
|
+
|
|
217
|
+
On a phone-width screen each table shows as a list and the setup buttons
|
|
218
|
+
stack at full width.
|
|
219
|
+
|
|
220
|
+
The account section renders its own panel, holding three titled groups and
|
|
221
|
+
one Save button, so place it on the page without a heading or panel around
|
|
222
|
+
it either:
|
|
223
|
+
- **Accepted methods** — a checkbox for each method in `offered_methods`.
|
|
224
|
+
Members enter the account only after using one of the ticked methods.
|
|
225
|
+
- **Second step** — the checkbox that asks members who signed in with only
|
|
226
|
+
a password for a second step before they enter.
|
|
227
|
+
- **Members** — the checkbox that lets a member's own second-step choice
|
|
228
|
+
apply in this account, or, while `people_choose_second_step` is false,
|
|
229
|
+
the note "This app decides when members are asked for a second step." in
|
|
230
|
+
its place.
|
|
231
|
+
|
|
232
|
+
Every account starts out letting its members choose their own second step.
|
|
233
|
+
While an account does not, a person's own choice is not applied inside that
|
|
234
|
+
account, and only the account's own rule decides.
|
|
235
|
+
|
|
236
|
+
Pass the person's current account as `account:` to the personal section.
|
|
237
|
+
When that account requires a second step, the Second step group shows
|
|
238
|
+
"Your account asks everyone for a second step." above the checkbox or note.
|
|
239
|
+
When that account does not let members choose, the group shows the note
|
|
240
|
+
"Your account decides whether you are asked for a second step." in place of
|
|
241
|
+
the checkbox. A save of the person's own choice in that account is refused
|
|
242
|
+
with the same note, and the person's earlier choice is kept. Leave
|
|
243
|
+
`account:` out only when the host has no accounts, since without it the
|
|
244
|
+
checkbox always shows and no account note appears.
|
|
185
245
|
|
|
186
246
|
The controller actions behind those routes are the develop local's to
|
|
187
247
|
write. Ask the developer which settings page each section goes on, and who
|
data/the_local/interface.yml
CHANGED
|
@@ -20,6 +20,7 @@ install:
|
|
|
20
20
|
- config.stylesheet
|
|
21
21
|
- config.head_partial
|
|
22
22
|
- config.authenticator_issuer
|
|
23
|
+
- config.people_choose_second_step
|
|
23
24
|
- include DoorCheck::Tracking
|
|
24
25
|
- include DoorCheck::EntryCheck
|
|
25
26
|
- before_action :door_check_check_entry
|
|
@@ -68,6 +69,7 @@ sources:
|
|
|
68
69
|
- app/models/door_check/challenge.rb
|
|
69
70
|
- app/models/door_check/require_second_step.rb
|
|
70
71
|
- app/models/door_check/require_own_second_step.rb
|
|
72
|
+
- app/models/door_check/person_rule.rb
|
|
71
73
|
- app/models/door_check/remove_method.rb
|
|
72
74
|
- app/models/door_check/sign_out_session.rb
|
|
73
75
|
- app/models/door_check/remove_trusted_browser.rb
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: door_check
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tyler Schneider
|
|
@@ -174,6 +174,7 @@ files:
|
|
|
174
174
|
- db/migrate/20261111162243_create_door_check_trusted_devices.rb
|
|
175
175
|
- db/migrate/20261111170231_add_device_to_door_check_signed_in_sessions.rb
|
|
176
176
|
- db/migrate/20261111170432_add_approval_to_door_check_challenges.rb
|
|
177
|
+
- db/migrate/20261113191736_add_members_choose_second_step_to_door_check_account_rules.rb
|
|
177
178
|
- lib/door_check.rb
|
|
178
179
|
- lib/door_check/configuration.rb
|
|
179
180
|
- lib/door_check/engine.rb
|