masks 0.4.0 → 0.6.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 (180) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +29 -0
  3. data/LICENSE +21 -0
  4. data/README.md +235 -29
  5. data/app/controllers/masks/rails/avatars_controller.rb +51 -0
  6. data/app/controllers/masks/rails/base_controller.rb +13 -0
  7. data/app/controllers/masks/rails/handshakes_controller.rb +86 -0
  8. data/app/controllers/masks/rails/sessions_controller.rb +118 -0
  9. data/app/views/layouts/masks/rails/plain.html.erb +47 -0
  10. data/app/views/masks/rails/handshakes/refused.html.erb +12 -0
  11. data/app/views/masks/rails/handshakes/show.html.erb +40 -0
  12. data/config/routes.rb +11 -82
  13. data/lib/generators/masks/install/install_generator.rb +35 -22
  14. data/lib/generators/masks/install/templates/masks.rb.tt +38 -0
  15. data/lib/masks/client/claims.rb +144 -0
  16. data/lib/masks/client/errors.rb +60 -0
  17. data/lib/masks/client/handshake.rb +104 -0
  18. data/lib/masks/client/http.rb +96 -0
  19. data/lib/masks/client/introspection.rb +29 -0
  20. data/lib/masks/client/issuer.rb +92 -0
  21. data/lib/masks/client/pkce.rb +27 -0
  22. data/lib/masks/client/rack.rb +62 -0
  23. data/lib/masks/client/registration.rb +115 -0
  24. data/lib/masks/client/registry.rb +25 -0
  25. data/lib/masks/client/resource.rb +92 -0
  26. data/lib/masks/client/session.rb +146 -0
  27. data/lib/masks/client/stores.rb +40 -0
  28. data/lib/masks/client/tokens.rb +70 -0
  29. data/lib/masks/client/tracker.rb +122 -0
  30. data/lib/masks/client/verifier.rb +42 -0
  31. data/lib/masks/client.rb +51 -0
  32. data/lib/masks/rails/authentication.rb +260 -0
  33. data/lib/masks/rails/configurable.rb +11 -0
  34. data/lib/masks/rails/configuration.rb +184 -0
  35. data/lib/masks/rails/credentials.rb +60 -0
  36. data/lib/masks/rails/engine.rb +20 -0
  37. data/lib/masks/rails/protected_resource.rb +51 -0
  38. data/lib/masks/rails.rb +10 -0
  39. data/lib/masks/version.rb +1 -3
  40. data/lib/masks.rb +3 -183
  41. metadata +48 -416
  42. data/MIT-LICENSE +0 -20
  43. data/Rakefile +0 -11
  44. data/app/assets/builds/masks/application.css +0 -1
  45. data/app/assets/builds/masks/application.js +0 -8549
  46. data/app/assets/builds/masks/application.js.map +0 -7
  47. data/app/assets/images/masks.png +0 -0
  48. data/app/assets/javascripts/application.js +0 -2
  49. data/app/assets/javascripts/controllers/application.js +0 -9
  50. data/app/assets/javascripts/controllers/emails_controller.js +0 -28
  51. data/app/assets/javascripts/controllers/index.js +0 -21
  52. data/app/assets/javascripts/controllers/keys_controller.js +0 -20
  53. data/app/assets/javascripts/controllers/recover_controller.js +0 -21
  54. data/app/assets/javascripts/controllers/recover_password_controller.js +0 -21
  55. data/app/assets/javascripts/controllers/session_controller.js +0 -94
  56. data/app/assets/javascripts/controllers/table_controller.js +0 -15
  57. data/app/assets/manifest.js +0 -2
  58. data/app/assets/masks_manifest.js +0 -2
  59. data/app/assets/stylesheets/application.css +0 -34
  60. data/app/controllers/concerns/masks/controller.rb +0 -114
  61. data/app/controllers/masks/actors_controller.rb +0 -15
  62. data/app/controllers/masks/application_controller.rb +0 -35
  63. data/app/controllers/masks/backup_codes_controller.rb +0 -34
  64. data/app/controllers/masks/debug_controller.rb +0 -9
  65. data/app/controllers/masks/devices_controller.rb +0 -20
  66. data/app/controllers/masks/emails_controller.rb +0 -60
  67. data/app/controllers/masks/error_controller.rb +0 -14
  68. data/app/controllers/masks/keys_controller.rb +0 -45
  69. data/app/controllers/masks/manage/actors_controller.rb +0 -83
  70. data/app/controllers/masks/manage/base_controller.rb +0 -20
  71. data/app/controllers/masks/manage/clients_controller.rb +0 -84
  72. data/app/controllers/masks/manage/dashboard_controller.rb +0 -15
  73. data/app/controllers/masks/manage/devices_controller.rb +0 -19
  74. data/app/controllers/masks/one_time_code_controller.rb +0 -49
  75. data/app/controllers/masks/openid/authorizations_controller.rb +0 -45
  76. data/app/controllers/masks/openid/discoveries_controller.rb +0 -55
  77. data/app/controllers/masks/openid/tokens_controller.rb +0 -45
  78. data/app/controllers/masks/openid/userinfo_controller.rb +0 -28
  79. data/app/controllers/masks/passwords_controller.rb +0 -33
  80. data/app/controllers/masks/recoveries_controller.rb +0 -43
  81. data/app/controllers/masks/sessions_controller.rb +0 -53
  82. data/app/helpers/masks/application_helper.rb +0 -49
  83. data/app/jobs/masks/application_job.rb +0 -7
  84. data/app/jobs/masks/expire_actors_job.rb +0 -15
  85. data/app/jobs/masks/expire_recoveries_job.rb +0 -15
  86. data/app/mailers/masks/actor_mailer.rb +0 -22
  87. data/app/mailers/masks/application_mailer.rb +0 -15
  88. data/app/models/concerns/masks/access.rb +0 -162
  89. data/app/models/concerns/masks/actor.rb +0 -132
  90. data/app/models/concerns/masks/adapter.rb +0 -68
  91. data/app/models/concerns/masks/role.rb +0 -9
  92. data/app/models/concerns/masks/scoped.rb +0 -54
  93. data/app/models/masks/access/actor_password.rb +0 -21
  94. data/app/models/masks/access/actor_scopes.rb +0 -18
  95. data/app/models/masks/access/actor_signup.rb +0 -21
  96. data/app/models/masks/actors/anonymous.rb +0 -40
  97. data/app/models/masks/actors/system.rb +0 -24
  98. data/app/models/masks/adapters/active_record.rb +0 -85
  99. data/app/models/masks/application_model.rb +0 -15
  100. data/app/models/masks/application_record.rb +0 -8
  101. data/app/models/masks/check.rb +0 -192
  102. data/app/models/masks/credential.rb +0 -166
  103. data/app/models/masks/credentials/access_token.rb +0 -60
  104. data/app/models/masks/credentials/backup_code.rb +0 -30
  105. data/app/models/masks/credentials/device.rb +0 -59
  106. data/app/models/masks/credentials/email.rb +0 -48
  107. data/app/models/masks/credentials/factor2.rb +0 -71
  108. data/app/models/masks/credentials/key.rb +0 -38
  109. data/app/models/masks/credentials/last_login.rb +0 -12
  110. data/app/models/masks/credentials/masquerade.rb +0 -32
  111. data/app/models/masks/credentials/nickname.rb +0 -63
  112. data/app/models/masks/credentials/one_time_code.rb +0 -34
  113. data/app/models/masks/credentials/password.rb +0 -28
  114. data/app/models/masks/credentials/recovery.rb +0 -71
  115. data/app/models/masks/credentials/return_to.rb +0 -27
  116. data/app/models/masks/credentials/session.rb +0 -67
  117. data/app/models/masks/device.rb +0 -30
  118. data/app/models/masks/error.rb +0 -51
  119. data/app/models/masks/event.rb +0 -14
  120. data/app/models/masks/mask.rb +0 -266
  121. data/app/models/masks/openid/authorization.rb +0 -116
  122. data/app/models/masks/openid/token.rb +0 -56
  123. data/app/models/masks/rails/actor.rb +0 -212
  124. data/app/models/masks/rails/actor_role.rb +0 -12
  125. data/app/models/masks/rails/device.rb +0 -47
  126. data/app/models/masks/rails/email.rb +0 -96
  127. data/app/models/masks/rails/key.rb +0 -61
  128. data/app/models/masks/rails/openid/access_token.rb +0 -55
  129. data/app/models/masks/rails/openid/authorization.rb +0 -45
  130. data/app/models/masks/rails/openid/client.rb +0 -186
  131. data/app/models/masks/rails/openid/id_token.rb +0 -43
  132. data/app/models/masks/rails/recovery.rb +0 -116
  133. data/app/models/masks/rails/role.rb +0 -20
  134. data/app/models/masks/rails/scope.rb +0 -15
  135. data/app/models/masks/session.rb +0 -447
  136. data/app/models/masks/sessions/access.rb +0 -27
  137. data/app/models/masks/sessions/inline.rb +0 -16
  138. data/app/models/masks/sessions/request.rb +0 -42
  139. data/app/resources/masks/actor_resource.rb +0 -9
  140. data/app/resources/masks/session_resource.rb +0 -15
  141. data/app/views/layouts/masks/application.html.erb +0 -17
  142. data/app/views/layouts/masks/mailer.html.erb +0 -17
  143. data/app/views/layouts/masks/mailer.text.erb +0 -1
  144. data/app/views/layouts/masks/manage.html.erb +0 -42
  145. data/app/views/masks/actor_mailer/recover_credentials.html.erb +0 -32
  146. data/app/views/masks/actor_mailer/recover_credentials.text.erb +0 -1
  147. data/app/views/masks/actor_mailer/verify_email.html.erb +0 -33
  148. data/app/views/masks/actor_mailer/verify_email.text.erb +0 -8
  149. data/app/views/masks/actors/current.html.erb +0 -145
  150. data/app/views/masks/application/_header.html.erb +0 -30
  151. data/app/views/masks/backup_codes/new.html.erb +0 -117
  152. data/app/views/masks/emails/new.html.erb +0 -109
  153. data/app/views/masks/emails/verify.html.erb +0 -51
  154. data/app/views/masks/keys/new.html.erb +0 -127
  155. data/app/views/masks/manage/actors/index.html.erb +0 -104
  156. data/app/views/masks/manage/actors/show.html.erb +0 -172
  157. data/app/views/masks/manage/clients/index.html.erb +0 -102
  158. data/app/views/masks/manage/clients/show.html.erb +0 -156
  159. data/app/views/masks/manage/dashboard/index.html.erb +0 -10
  160. data/app/views/masks/manage/devices/index.html.erb +0 -47
  161. data/app/views/masks/one_time_code/new.html.erb +0 -167
  162. data/app/views/masks/openid/authorizations/error.html.erb +0 -23
  163. data/app/views/masks/openid/authorizations/new.html.erb +0 -46
  164. data/app/views/masks/passwords/edit.html.erb +0 -71
  165. data/app/views/masks/recoveries/new.html.erb +0 -69
  166. data/app/views/masks/recoveries/password.html.erb +0 -63
  167. data/app/views/masks/sessions/new.html.erb +0 -152
  168. data/config/brakeman.ignore +0 -28
  169. data/config/initializers/inflections.rb +0 -5
  170. data/config/locales/en.yml +0 -307
  171. data/db/migrate/20231205173845_create_actors.rb +0 -94
  172. data/db/migrate/20240329182422_support_openid.rb +0 -64
  173. data/lib/generators/masks/install/USAGE +0 -8
  174. data/lib/generators/masks/install/templates/initializer.rb +0 -5
  175. data/lib/generators/masks/install/templates/masks.json +0 -9
  176. data/lib/masks/configuration.rb +0 -249
  177. data/lib/masks/engine.rb +0 -25
  178. data/lib/masks/middleware.rb +0 -70
  179. data/lib/tasks/masks_tasks.rake +0 -72
  180. data/masks.json +0 -315
@@ -0,0 +1,60 @@
1
+ module Masks
2
+ module Rails
3
+ # The default place a single-tenant app keeps what the handshake gives it,
4
+ # so `config.store` has something to be before a consumer writes one. An
5
+ # app with more than one issuer wants its own — `things` keeps these on
6
+ # `tenants`, because a handshake is per tenant there.
7
+ class Credentials
8
+ KEYS = %w[client_id client_secret registration_access_token registration_client_uri].freeze
9
+
10
+ attr_reader :path
11
+
12
+ def initialize(path)
13
+ @path = Pathname.new(path)
14
+ @lock = Mutex.new
15
+ end
16
+
17
+ def read
18
+ @lock.synchronize { held }
19
+ end
20
+
21
+ def write(registration)
22
+ @lock.synchronize do
23
+ @held = {
24
+ "client_id" => registration.client_id,
25
+ "client_secret" => registration.client_secret,
26
+ "registration_access_token" => registration.access_token,
27
+ "registration_client_uri" => registration.uri,
28
+ "connected_at" => Time.now.utc.iso8601
29
+ }.compact
30
+
31
+ path.dirname.mkpath
32
+ path.write(JSON.pretty_generate(@held))
33
+ path.chmod(0o600)
34
+ @held
35
+ end
36
+ end
37
+
38
+ def clear!
39
+ @lock.synchronize do
40
+ @held = nil
41
+ path.delete if path.exist?
42
+ end
43
+ end
44
+
45
+ def connected?
46
+ read["client_id"].present?
47
+ end
48
+
49
+ private
50
+
51
+ def held
52
+ return @held if defined?(@held) && @held
53
+
54
+ @held = path.exist? ? JSON.parse(path.read) : {}
55
+ rescue JSON::ParserError
56
+ @held = {}
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,20 @@
1
+ module Masks
2
+ module Rails
3
+ class Engine < ::Rails::Engine
4
+ isolate_namespace Masks::Rails
5
+
6
+ config.masks = Masks::Rails.config
7
+
8
+ # Opt-in, not blanket. This used to `include Masks::Rails::Authentication`
9
+ # into every controller in the host app, which lands twenty-five `masks_*`
10
+ # methods and four helper_methods on code the consumer owns, with no way
11
+ # to say no — and a name collision found that way is the kind of thing
12
+ # `grants:` versus `scope:` already cost this project once.
13
+ initializer "masks.authentication" do
14
+ ActiveSupport.on_load(:action_controller) do
15
+ include Masks::Rails::Authentication if Masks::Rails.config.authenticate_everything
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,51 @@
1
+ module Masks
2
+ module Rails
3
+ module ProtectedResource
4
+ extend ActiveSupport::Concern
5
+ include Masks::Rails::Configurable
6
+
7
+ class_methods do
8
+ def masks_protect!(**options)
9
+ before_action(-> { masks_authenticate!(**options) }, **options.slice(:only, :except))
10
+ end
11
+ end
12
+
13
+ def masks_resource
14
+ @masks_resource ||= masks_config.resource_server_for(request)
15
+ end
16
+
17
+ def masks_claims
18
+ @masks_claims if defined?(@masks_claims)
19
+ end
20
+
21
+ def masks_authenticate!(scope: nil, **)
22
+ @masks_claims = masks_resource.authenticate(request.authorization, scope: scope)
23
+ rescue Masks::Client::Challenge => e
24
+ masks_challenge(e)
25
+ false
26
+ end
27
+
28
+ def masks_authenticate(scope: nil)
29
+ masks_resource.authenticate(request.authorization, scope: scope)
30
+ rescue Masks::Client::Unauthenticated
31
+ nil
32
+ end
33
+
34
+ def masks_challenge(error)
35
+ error = Masks::Client::Unauthorized.new(error.to_s) unless error.is_a?(Masks::Client::Challenge)
36
+
37
+ response.headers["WWW-Authenticate"] = masks_resource.challenge(error)
38
+ response.headers["Cache-Control"] = "no-store"
39
+
40
+ render json: {
41
+ "error" => error.code || "invalid_token",
42
+ "error_description" => error.description
43
+ }, status: error.status
44
+ end
45
+
46
+ def masks_resource_metadata
47
+ masks_resource.metadata
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,10 @@
1
+ require "rails"
2
+
3
+ require_relative "version"
4
+ require_relative "client"
5
+ require_relative "rails/credentials"
6
+ require_relative "rails/configuration"
7
+ require_relative "rails/configurable"
8
+ require_relative "rails/authentication"
9
+ require_relative "rails/protected_resource"
10
+ require_relative "rails/engine"
data/lib/masks/version.rb CHANGED
@@ -1,5 +1,3 @@
1
- # frozen_string_literal: true
2
-
3
1
  module Masks
4
- VERSION = Gem::Version.new("0.4.0")
2
+ VERSION = "0.6.0".freeze
5
3
  end
data/lib/masks.rb CHANGED
@@ -1,184 +1,4 @@
1
- # frozen_string_literal: true
1
+ require_relative "masks/version"
2
+ require_relative "masks/client"
2
3
 
3
- require "masks/version"
4
- require "masks/engine"
5
- require "masks/configuration"
6
- require "fuzzyurl"
7
- require "valid_email"
8
- require "phonelib"
9
- require "rqrcode"
10
- require "rotp"
11
- require "alba"
12
- require "pagy"
13
- require "device_detector"
14
- require "active_model"
15
- require "chronic_duration"
16
-
17
- # Top-level module for masks.
18
- #
19
- # Includes helper methods for managing configuration and building
20
- # sessions, access, etc.
21
- module Masks
22
- # @visibility private
23
- ANON = :anon
24
-
25
- class << self
26
- # Returns an access instance based on the type & session passed.
27
- #
28
- # @param [Symbol|String] name
29
- # @param [Masks::Session] session
30
- # @return [Masks::Access]
31
- # @raise [Masks::Error::Unauthorized] if access cannot be granted to the session passed
32
- # @raise [Masks::UnknownAccess] if the access type cannot be found
33
- def access(name, session, **_opts)
34
- configuration.access(name).fetch(:cls).build(session)
35
- end
36
-
37
- # Returns a masked session based on the request passed.
38
- #
39
- # @param [ActionDispatch::Request] request
40
- # @return [Masks::Sessions::Request] session
41
- def request(request)
42
- model = configuration.model(:request)
43
- session = model.new(config: configuration, request:)
44
- session.mask!
45
- session
46
- end
47
-
48
- # Returns a masked session based on the params and data passed.
49
- #
50
- # This method is useful for creating masks directly, outside of a
51
- # specific context like a web request.
52
- #
53
- # @param params [Hash]
54
- # @param data [Hash]
55
- # @yield [Masks::Sessions::Inline]
56
- # @return [Masks::Sessions::Inline] session
57
- def mask(name, params: {}, **data)
58
- model = configuration.model(:inline)
59
- session = model.new(name:, config: configuration, params:, data:)
60
- session.mask!
61
- yield session if session.passed? && block_given?
62
- session
63
- end
64
-
65
- # Yields the global masks configuration object.
66
- # @return [Masks::Configuration]
67
- # @yield [Masks::Configuration]
68
- def configure
69
- yield configuration
70
- end
71
-
72
- # Returns the global configuration object for masks.
73
- # @return [Masks::Configuration]
74
- def configuration
75
- @configuration ||= Masks::Configuration.new(data: config.dup)
76
- end
77
-
78
- # @visibility private
79
- def event(name, **opts, &block)
80
- ActiveSupport::Notifications.instrument("masks.#{name}", **opts) do
81
- block.call
82
- end
83
- end
84
-
85
- # @visibility private
86
- def config
87
- @config ||=
88
- begin
89
- config = load_config(config_path)
90
-
91
- defaults =
92
- if config[:extend]
93
- # extend a "masks.json" from a gem
94
- load_config(gem_path(config[:extend]))
95
- else
96
- {}
97
- end
98
-
99
- config = defaults.except(:masks).deep_merge(config)
100
- config[:masks] = [*config[:masks], *defaults[:masks]] if defaults[
101
- :masks
102
- ]
103
- config
104
- end
105
- end
106
-
107
- # @visibility private
108
- def with_config(path_or_config)
109
- previous_config = @config
110
- previous_path = @config_path
111
-
112
- case path_or_config
113
- when String
114
- @config_path = Pathname.new(path_or_config)
115
- when Pathname
116
- @config_path = path_or_config
117
- when Hash
118
- @config_path = :custom
119
- @config = path_or_config
120
- else
121
- raise Error::InvalidConfiguration, path_or_config
122
- end
123
-
124
- result = yield
125
- ensure
126
- @config = previous_config
127
- @config_path = previous_path
128
-
129
- result
130
- end
131
-
132
- # @visibility private
133
- def config_path
134
- path =
135
- [
136
- @config_path,
137
- ENV["MASKS_DIR"] ? Pathname.new(ENV["MASKS_DIR"]) : nil,
138
- Pathname.new(Dir.pwd),
139
- defined?(ENGINE_ROOT) ? Pathname.new(ENGINE_ROOT) : nil
140
- ].find do |p|
141
- p.is_a?(Symbol) || (p && File.exist?(p.join("masks.json")))
142
- end
143
-
144
- raise Error::ConfigurationNotFound unless path
145
-
146
- path
147
- end
148
-
149
- # @visibility private
150
- def config_path=(value)
151
- @config_path =
152
- case value
153
- when String
154
- Pathname.new(value)
155
- when Pathname
156
- value
157
- else
158
- raise Error::InvalidConfiguration, value
159
- end
160
- end
161
-
162
- # @visibility private
163
- def reset!
164
- @configuration = @config = @config_path = nil
165
- end
166
-
167
- def load_config(dir)
168
- JSON.parse(File.read(dir.join("masks.json")), symbolize_names: true)
169
- end
170
-
171
- def gem_path(gem_name)
172
- begin
173
- # Try to require the gem
174
- require gem_name
175
- rescue LoadError
176
- return nil
177
- end
178
-
179
- # Get the gem specification
180
- spec = Gem::Specification.find_by_name(gem_name)
181
- Pathname.new(spec.gem_dir) if spec
182
- end
183
- end
184
- end
4
+ require_relative "masks/rails" if defined?(::Rails::Engine)