door_check 0.1.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.
Files changed (32) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +2 -0
  4. data/Rakefile +6 -0
  5. data/app/assets/stylesheets/door_check/application.css +15 -0
  6. data/app/controllers/concerns/door_check/entry_check.rb +13 -0
  7. data/app/controllers/concerns/door_check/tracking.rb +31 -0
  8. data/app/controllers/door_check/application_controller.rb +7 -0
  9. data/app/controllers/door_check/second_steps_controller.rb +28 -0
  10. data/app/helpers/door_check/application_helper.rb +4 -0
  11. data/app/jobs/door_check/application_job.rb +4 -0
  12. data/app/mailers/door_check/application_mailer.rb +6 -0
  13. data/app/models/door_check/account_rule.rb +13 -0
  14. data/app/models/door_check/application_record.rb +5 -0
  15. data/app/models/door_check/proof.rb +5 -0
  16. data/app/models/door_check/require_second_step.rb +14 -0
  17. data/app/models/door_check/result.rb +12 -0
  18. data/app/models/door_check/signed_in_session.rb +14 -0
  19. data/app/views/door_check/second_steps/new.html.erb +15 -0
  20. data/app/views/door_check/settings/_sign_in_rules.html.erb +7 -0
  21. data/app/views/layouts/door_check/application.html.erb +17 -0
  22. data/config/routes.rb +3 -0
  23. data/db/migrate/20261111005356_create_door_check_signed_in_sessions.rb +10 -0
  24. data/db/migrate/20261111005407_create_door_check_proofs.rb +11 -0
  25. data/db/migrate/20261111010724_create_door_check_account_rules.rb +10 -0
  26. data/lib/door_check/configuration.rb +25 -0
  27. data/lib/door_check/engine.rb +5 -0
  28. data/lib/door_check/methods.rb +22 -0
  29. data/lib/door_check/version.rb +3 -0
  30. data/lib/door_check.rb +14 -0
  31. data/lib/tasks/door_check_tasks.rake +4 -0
  32. metadata +107 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8dc6f095ae6f16376021826916b13efa16fabacd433c929e760a93a7ee425324
4
+ data.tar.gz: 861f4c4ad212731e360b197ca1d32176849e8558d6eeb1a372de1907d8e9a853
5
+ SHA512:
6
+ metadata.gz: 6ed367d8701e8078d3117552336273b883d06cde8e6dfb1a409482cf509e95b1ab436ddfb75eb6143e469a62cc96e6fbff01ecf027d7e11f91b069134d40e4fb
7
+ data.tar.gz: 4d3e64f5f006a9faa8c3001209859c67606fb3ab979ca7a091674b30434e75666a1e41d05667249ebf50981e0d11d93e4af276046c17a13c8fc97fc359083560
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright TODO: Write your name
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,2 @@
1
+ # door_check
2
+ Sign-in methods and second steps an app offers, each account narrows, and each person chooses from
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/setup"
2
+
3
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
4
+ load "rails/tasks/engine.rake"
5
+
6
+ require "bundler/gem_tasks"
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10
+ * files in this directory. Styles in this file should be added after the last require_* statement.
11
+ * It is generally better to create a new file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,13 @@
1
+ module DoorCheck
2
+ module EntryCheck
3
+ RETURN_KEY = :door_check_return_to
4
+
5
+ def door_check_check_entry
6
+ account = door_check_account
7
+ return if account.nil? || AccountRule.for(account).met_by?(door_check_proved_by.map(&:first))
8
+
9
+ session[RETURN_KEY] = request.fullpath
10
+ redirect_to door_check.new_second_step_path
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,31 @@
1
+ module DoorCheck
2
+ module Tracking
3
+ SESSION_KEY = :door_check_session_id
4
+
5
+ def door_check_signed_in(person, method_name:)
6
+ signed_in_session = SignedInSession.create!(person: person)
7
+ signed_in_session.prove!(method_name)
8
+ session[SESSION_KEY] = signed_in_session.id
9
+ end
10
+
11
+ def door_check_proved_by
12
+ door_check_session&.proved_by || []
13
+ end
14
+
15
+ def door_check_account
16
+ account_method = DoorCheck.configuration.account_method
17
+ send(account_method) if account_method
18
+ end
19
+
20
+ private
21
+
22
+ def door_check_session
23
+ person = door_check_person
24
+ SignedInSession.find_by(id: session[SESSION_KEY], person: person) if person
25
+ end
26
+
27
+ def door_check_person
28
+ send(DoorCheck.configuration.person_method)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,7 @@
1
+ module DoorCheck
2
+ class ApplicationController < DoorCheck.configuration.parent_controller.constantize
3
+ helper KeystoneUiHelper
4
+
5
+ skip_before_action :door_check_check_entry, raise: false
6
+ end
7
+ end
@@ -0,0 +1,28 @@
1
+ module DoorCheck
2
+ class SecondStepsController < ApplicationController
3
+ before_action :find_authenticator
4
+
5
+ def new
6
+ end
7
+
8
+ def create
9
+ method_name = @authenticator.verify(@person, params[:code].to_s.strip)
10
+ return refuse_code unless method_name
11
+
12
+ door_check_session.prove!(method_name)
13
+ redirect_to session.delete(EntryCheck::RETURN_KEY) || "/"
14
+ end
15
+
16
+ private
17
+
18
+ def find_authenticator
19
+ @person = door_check_person
20
+ @authenticator = DoorCheck.configuration.authenticator
21
+ end
22
+
23
+ def refuse_code
24
+ @error = "That code is not right. Check your authenticator app and try again."
25
+ render :new, status: :unprocessable_content
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,4 @@
1
+ module DoorCheck
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module DoorCheck
2
+ class ApplicationJob < ActiveJob::Base
3
+ end
4
+ end
@@ -0,0 +1,6 @@
1
+ module DoorCheck
2
+ class ApplicationMailer < ActionMailer::Base
3
+ default from: "from@example.com"
4
+ layout "mailer"
5
+ end
6
+ end
@@ -0,0 +1,13 @@
1
+ module DoorCheck
2
+ class AccountRule < ApplicationRecord
3
+ belongs_to :account, polymorphic: true
4
+
5
+ def self.for(account)
6
+ find_or_initialize_by(account: account)
7
+ end
8
+
9
+ def met_by?(method_names)
10
+ !second_step_required || method_names.any? { |name| Methods.roles(name).include?(:second_step) }
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,5 @@
1
+ module DoorCheck
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module DoorCheck
2
+ class Proof < ApplicationRecord
3
+ belongs_to :signed_in_session
4
+ end
5
+ end
@@ -0,0 +1,14 @@
1
+ module DoorCheck
2
+ class RequireSecondStep
3
+ def initialize(person:, account:, values:)
4
+ @person = person
5
+ @account = account
6
+ @values = values
7
+ end
8
+
9
+ def call
10
+ AccountRule.for(@account).update!(second_step_required: @values[:second_step_required] == "1")
11
+ Result.new(ok: true)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,12 @@
1
+ module DoorCheck
2
+ class Result
3
+ attr_reader :message
4
+
5
+ def initialize(ok:, message: nil)
6
+ @ok = ok
7
+ @message = message
8
+ end
9
+
10
+ def ok? = @ok
11
+ end
12
+ end
@@ -0,0 +1,14 @@
1
+ module DoorCheck
2
+ class SignedInSession < ApplicationRecord
3
+ belongs_to :person, polymorphic: true
4
+ has_many :proofs, dependent: :delete_all
5
+
6
+ def prove!(method_name)
7
+ proofs.create!(method_name: method_name, proved_at: Time.current)
8
+ end
9
+
10
+ def proved_by
11
+ proofs.order(:proved_at).map { |proof| [ proof.method_name.to_sym, proof.proved_at ] }
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ <%= ui_page(max_width: :md) do %>
2
+ <% if @authenticator.set_up?(@person) %>
3
+ <%= ui_page_header(title: "Confirm it's you", subtitle: "Enter the code from your authenticator app or one of your backup codes.") %>
4
+ <%= ui_alert(message: @error, type: :error) if @error %>
5
+ <%= ui_form(action: door_check.second_step_path) do %>
6
+ <%= ui_form_field(attribute: "code", label: "Code", required: true, autofocus: true,
7
+ autocomplete: "one-time-code", inputmode: "numeric", autocapitalize: "none") %>
8
+ <%= ui_button(label: "Verify") %>
9
+ <% end %>
10
+ <% else %>
11
+ <%= ui_page_header(title: "Set up a second step", subtitle: "This account requires a second step after your password.") %>
12
+ <%= ui_empty_state(title: "No authenticator app yet", message: "Set up an authenticator app, then come back to this account.",
13
+ action: { label: "Set up", href: @authenticator.setup_path }) %>
14
+ <% end %>
15
+ <% end %>
@@ -0,0 +1,7 @@
1
+ <% rule = DoorCheck::AccountRule.for(account) %>
2
+ <%= ui_form(action: submit_url, method: :patch) do %>
3
+ <%= ui_form_field(attribute: "second_step_required", label: "Require a second step to enter this account",
4
+ type: :checkbox, value: (rule.second_step_required ? "1" : "0"),
5
+ hint: "Members who signed in with only a password are asked for their authenticator code when they enter.") %>
6
+ <%= ui_button(label: "Save") %>
7
+ <% end %>
@@ -0,0 +1,17 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Door check</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
+
8
+ <%= yield :head %>
9
+
10
+ <%= stylesheet_link_tag "door_check/application", media: "all" %>
11
+ </head>
12
+ <body>
13
+
14
+ <%= yield %>
15
+
16
+ </body>
17
+ </html>
data/config/routes.rb ADDED
@@ -0,0 +1,3 @@
1
+ DoorCheck::Engine.routes.draw do
2
+ resource :second_step, only: %i[new create]
3
+ end
@@ -0,0 +1,10 @@
1
+ class CreateDoorCheckSignedInSessions < ActiveRecord::Migration[8.1]
2
+ def change
3
+ create_table :door_check_signed_in_sessions do |t|
4
+ t.references :person, polymorphic: true, null: false
5
+ t.datetime :signed_out_at
6
+
7
+ t.timestamps
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,11 @@
1
+ class CreateDoorCheckProofs < ActiveRecord::Migration[8.1]
2
+ def change
3
+ create_table :door_check_proofs do |t|
4
+ t.references :signed_in_session, null: false, foreign_key: { to_table: :door_check_signed_in_sessions }
5
+ t.string :method_name, null: false
6
+ t.datetime :proved_at, null: false
7
+
8
+ t.timestamps
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,10 @@
1
+ class CreateDoorCheckAccountRules < ActiveRecord::Migration[8.1]
2
+ def change
3
+ create_table :door_check_account_rules do |t|
4
+ t.references :account, polymorphic: true, null: false, index: { unique: true }
5
+ t.boolean :second_step_required, null: false, default: false
6
+
7
+ t.timestamps
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,25 @@
1
+ require "door_check/methods"
2
+
3
+ module DoorCheck
4
+ class UnknownMethod < StandardError; end
5
+
6
+ class Configuration
7
+ attr_reader :offered_methods
8
+ attr_accessor :person_method, :account_method, :parent_controller, :existing_authenticator
9
+
10
+ def initialize
11
+ @parent_controller = "::ApplicationController"
12
+ end
13
+
14
+ def offered_methods=(names)
15
+ unknown = names - Methods.names
16
+ raise UnknownMethod, "door_check does not know the sign-in method #{unknown.join(', ')}" if unknown.any?
17
+
18
+ @offered_methods = names
19
+ end
20
+
21
+ def authenticator
22
+ existing_authenticator&.constantize&.new
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,5 @@
1
+ module DoorCheck
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace DoorCheck
4
+ end
5
+ end
@@ -0,0 +1,22 @@
1
+ module DoorCheck
2
+ module Methods
3
+ ROLES = {
4
+ password: %i[sign_in],
5
+ email_code: %i[sign_in],
6
+ email_link: %i[sign_in],
7
+ device_approval: %i[sign_in],
8
+ authenticator_app: %i[second_step],
9
+ sms_code: %i[second_step],
10
+ passkey: %i[sign_in second_step],
11
+ backup_codes: %i[second_step],
12
+ trusted_device: %i[second_step],
13
+ google: %i[sign_in],
14
+ apple: %i[sign_in],
15
+ sso: %i[sign_in]
16
+ }.freeze
17
+
18
+ def self.names = ROLES.keys
19
+
20
+ def self.roles(name) = ROLES.fetch(name)
21
+ end
22
+ end
@@ -0,0 +1,3 @@
1
+ module DoorCheck
2
+ VERSION = "0.1.0"
3
+ end
data/lib/door_check.rb ADDED
@@ -0,0 +1,14 @@
1
+ require "keystone_ui"
2
+ require "door_check/version"
3
+ require "door_check/configuration"
4
+ require "door_check/engine"
5
+
6
+ module DoorCheck
7
+ class << self
8
+ attr_writer :configuration
9
+
10
+ def configuration = @configuration ||= Configuration.new
11
+
12
+ def configure = yield(configuration)
13
+ end
14
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :door_check do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,107 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: door_check
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Tyler Schneider
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-11-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '7.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '7.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: keystone_ui
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0.50'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0.50'
41
+ description: Records which methods proved a person in each sign-in, and checks that
42
+ record against the rule of each account the person enters.
43
+ email:
44
+ - tylercschneider@gmail.com
45
+ executables: []
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - MIT-LICENSE
50
+ - README.md
51
+ - Rakefile
52
+ - app/assets/stylesheets/door_check/application.css
53
+ - app/controllers/concerns/door_check/entry_check.rb
54
+ - app/controllers/concerns/door_check/tracking.rb
55
+ - app/controllers/door_check/application_controller.rb
56
+ - app/controllers/door_check/second_steps_controller.rb
57
+ - app/helpers/door_check/application_helper.rb
58
+ - app/jobs/door_check/application_job.rb
59
+ - app/mailers/door_check/application_mailer.rb
60
+ - app/models/door_check/account_rule.rb
61
+ - app/models/door_check/application_record.rb
62
+ - app/models/door_check/proof.rb
63
+ - app/models/door_check/require_second_step.rb
64
+ - app/models/door_check/result.rb
65
+ - app/models/door_check/signed_in_session.rb
66
+ - app/views/door_check/second_steps/new.html.erb
67
+ - app/views/door_check/settings/_sign_in_rules.html.erb
68
+ - app/views/layouts/door_check/application.html.erb
69
+ - config/routes.rb
70
+ - db/migrate/20261111005356_create_door_check_signed_in_sessions.rb
71
+ - db/migrate/20261111005407_create_door_check_proofs.rb
72
+ - db/migrate/20261111010724_create_door_check_account_rules.rb
73
+ - lib/door_check.rb
74
+ - lib/door_check/configuration.rb
75
+ - lib/door_check/engine.rb
76
+ - lib/door_check/methods.rb
77
+ - lib/door_check/version.rb
78
+ - lib/tasks/door_check_tasks.rake
79
+ homepage: https://github.com/DYB-Development/door_check
80
+ licenses:
81
+ - MIT
82
+ metadata:
83
+ homepage_uri: https://github.com/DYB-Development/door_check
84
+ source_code_uri: https://github.com/DYB-Development/door_check
85
+ bug_tracker_uri: https://github.com/DYB-Development/door_check/issues
86
+ rubygems_mfa_required: 'true'
87
+ post_install_message:
88
+ rdoc_options: []
89
+ require_paths:
90
+ - lib
91
+ required_ruby_version: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: 3.2.0
96
+ required_rubygems_version: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: '0'
101
+ requirements: []
102
+ rubygems_version: 3.5.16
103
+ signing_key:
104
+ specification_version: 4
105
+ summary: Sign-in methods and second steps an app offers, each account narrows, and
106
+ each person chooses from.
107
+ test_files: []