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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +29 -0
- data/LICENSE +21 -0
- data/README.md +235 -29
- data/app/controllers/masks/rails/avatars_controller.rb +51 -0
- data/app/controllers/masks/rails/base_controller.rb +13 -0
- data/app/controllers/masks/rails/handshakes_controller.rb +86 -0
- data/app/controllers/masks/rails/sessions_controller.rb +118 -0
- data/app/views/layouts/masks/rails/plain.html.erb +47 -0
- data/app/views/masks/rails/handshakes/refused.html.erb +12 -0
- data/app/views/masks/rails/handshakes/show.html.erb +40 -0
- data/config/routes.rb +11 -82
- data/lib/generators/masks/install/install_generator.rb +35 -22
- data/lib/generators/masks/install/templates/masks.rb.tt +38 -0
- data/lib/masks/client/claims.rb +144 -0
- data/lib/masks/client/errors.rb +60 -0
- data/lib/masks/client/handshake.rb +104 -0
- data/lib/masks/client/http.rb +96 -0
- data/lib/masks/client/introspection.rb +29 -0
- data/lib/masks/client/issuer.rb +92 -0
- data/lib/masks/client/pkce.rb +27 -0
- data/lib/masks/client/rack.rb +62 -0
- data/lib/masks/client/registration.rb +115 -0
- data/lib/masks/client/registry.rb +25 -0
- data/lib/masks/client/resource.rb +92 -0
- data/lib/masks/client/session.rb +146 -0
- data/lib/masks/client/stores.rb +40 -0
- data/lib/masks/client/tokens.rb +70 -0
- data/lib/masks/client/tracker.rb +122 -0
- data/lib/masks/client/verifier.rb +42 -0
- data/lib/masks/client.rb +51 -0
- data/lib/masks/rails/authentication.rb +260 -0
- data/lib/masks/rails/configurable.rb +11 -0
- data/lib/masks/rails/configuration.rb +184 -0
- data/lib/masks/rails/credentials.rb +60 -0
- data/lib/masks/rails/engine.rb +20 -0
- data/lib/masks/rails/protected_resource.rb +51 -0
- data/lib/masks/rails.rb +10 -0
- data/lib/masks/version.rb +1 -3
- data/lib/masks.rb +3 -183
- metadata +48 -416
- data/MIT-LICENSE +0 -20
- data/Rakefile +0 -11
- data/app/assets/builds/masks/application.css +0 -1
- data/app/assets/builds/masks/application.js +0 -8549
- data/app/assets/builds/masks/application.js.map +0 -7
- data/app/assets/images/masks.png +0 -0
- data/app/assets/javascripts/application.js +0 -2
- data/app/assets/javascripts/controllers/application.js +0 -9
- data/app/assets/javascripts/controllers/emails_controller.js +0 -28
- data/app/assets/javascripts/controllers/index.js +0 -21
- data/app/assets/javascripts/controllers/keys_controller.js +0 -20
- data/app/assets/javascripts/controllers/recover_controller.js +0 -21
- data/app/assets/javascripts/controllers/recover_password_controller.js +0 -21
- data/app/assets/javascripts/controllers/session_controller.js +0 -94
- data/app/assets/javascripts/controllers/table_controller.js +0 -15
- data/app/assets/manifest.js +0 -2
- data/app/assets/masks_manifest.js +0 -2
- data/app/assets/stylesheets/application.css +0 -34
- data/app/controllers/concerns/masks/controller.rb +0 -114
- data/app/controllers/masks/actors_controller.rb +0 -15
- data/app/controllers/masks/application_controller.rb +0 -35
- data/app/controllers/masks/backup_codes_controller.rb +0 -34
- data/app/controllers/masks/debug_controller.rb +0 -9
- data/app/controllers/masks/devices_controller.rb +0 -20
- data/app/controllers/masks/emails_controller.rb +0 -60
- data/app/controllers/masks/error_controller.rb +0 -14
- data/app/controllers/masks/keys_controller.rb +0 -45
- data/app/controllers/masks/manage/actors_controller.rb +0 -83
- data/app/controllers/masks/manage/base_controller.rb +0 -20
- data/app/controllers/masks/manage/clients_controller.rb +0 -84
- data/app/controllers/masks/manage/dashboard_controller.rb +0 -15
- data/app/controllers/masks/manage/devices_controller.rb +0 -19
- data/app/controllers/masks/one_time_code_controller.rb +0 -49
- data/app/controllers/masks/openid/authorizations_controller.rb +0 -45
- data/app/controllers/masks/openid/discoveries_controller.rb +0 -55
- data/app/controllers/masks/openid/tokens_controller.rb +0 -45
- data/app/controllers/masks/openid/userinfo_controller.rb +0 -28
- data/app/controllers/masks/passwords_controller.rb +0 -33
- data/app/controllers/masks/recoveries_controller.rb +0 -43
- data/app/controllers/masks/sessions_controller.rb +0 -53
- data/app/helpers/masks/application_helper.rb +0 -49
- data/app/jobs/masks/application_job.rb +0 -7
- data/app/jobs/masks/expire_actors_job.rb +0 -15
- data/app/jobs/masks/expire_recoveries_job.rb +0 -15
- data/app/mailers/masks/actor_mailer.rb +0 -22
- data/app/mailers/masks/application_mailer.rb +0 -15
- data/app/models/concerns/masks/access.rb +0 -162
- data/app/models/concerns/masks/actor.rb +0 -132
- data/app/models/concerns/masks/adapter.rb +0 -68
- data/app/models/concerns/masks/role.rb +0 -9
- data/app/models/concerns/masks/scoped.rb +0 -54
- data/app/models/masks/access/actor_password.rb +0 -21
- data/app/models/masks/access/actor_scopes.rb +0 -18
- data/app/models/masks/access/actor_signup.rb +0 -21
- data/app/models/masks/actors/anonymous.rb +0 -40
- data/app/models/masks/actors/system.rb +0 -24
- data/app/models/masks/adapters/active_record.rb +0 -85
- data/app/models/masks/application_model.rb +0 -15
- data/app/models/masks/application_record.rb +0 -8
- data/app/models/masks/check.rb +0 -192
- data/app/models/masks/credential.rb +0 -166
- data/app/models/masks/credentials/access_token.rb +0 -60
- data/app/models/masks/credentials/backup_code.rb +0 -30
- data/app/models/masks/credentials/device.rb +0 -59
- data/app/models/masks/credentials/email.rb +0 -48
- data/app/models/masks/credentials/factor2.rb +0 -71
- data/app/models/masks/credentials/key.rb +0 -38
- data/app/models/masks/credentials/last_login.rb +0 -12
- data/app/models/masks/credentials/masquerade.rb +0 -32
- data/app/models/masks/credentials/nickname.rb +0 -63
- data/app/models/masks/credentials/one_time_code.rb +0 -34
- data/app/models/masks/credentials/password.rb +0 -28
- data/app/models/masks/credentials/recovery.rb +0 -71
- data/app/models/masks/credentials/return_to.rb +0 -27
- data/app/models/masks/credentials/session.rb +0 -67
- data/app/models/masks/device.rb +0 -30
- data/app/models/masks/error.rb +0 -51
- data/app/models/masks/event.rb +0 -14
- data/app/models/masks/mask.rb +0 -266
- data/app/models/masks/openid/authorization.rb +0 -116
- data/app/models/masks/openid/token.rb +0 -56
- data/app/models/masks/rails/actor.rb +0 -212
- data/app/models/masks/rails/actor_role.rb +0 -12
- data/app/models/masks/rails/device.rb +0 -47
- data/app/models/masks/rails/email.rb +0 -96
- data/app/models/masks/rails/key.rb +0 -61
- data/app/models/masks/rails/openid/access_token.rb +0 -55
- data/app/models/masks/rails/openid/authorization.rb +0 -45
- data/app/models/masks/rails/openid/client.rb +0 -186
- data/app/models/masks/rails/openid/id_token.rb +0 -43
- data/app/models/masks/rails/recovery.rb +0 -116
- data/app/models/masks/rails/role.rb +0 -20
- data/app/models/masks/rails/scope.rb +0 -15
- data/app/models/masks/session.rb +0 -447
- data/app/models/masks/sessions/access.rb +0 -27
- data/app/models/masks/sessions/inline.rb +0 -16
- data/app/models/masks/sessions/request.rb +0 -42
- data/app/resources/masks/actor_resource.rb +0 -9
- data/app/resources/masks/session_resource.rb +0 -15
- data/app/views/layouts/masks/application.html.erb +0 -17
- data/app/views/layouts/masks/mailer.html.erb +0 -17
- data/app/views/layouts/masks/mailer.text.erb +0 -1
- data/app/views/layouts/masks/manage.html.erb +0 -42
- data/app/views/masks/actor_mailer/recover_credentials.html.erb +0 -32
- data/app/views/masks/actor_mailer/recover_credentials.text.erb +0 -1
- data/app/views/masks/actor_mailer/verify_email.html.erb +0 -33
- data/app/views/masks/actor_mailer/verify_email.text.erb +0 -8
- data/app/views/masks/actors/current.html.erb +0 -145
- data/app/views/masks/application/_header.html.erb +0 -30
- data/app/views/masks/backup_codes/new.html.erb +0 -117
- data/app/views/masks/emails/new.html.erb +0 -109
- data/app/views/masks/emails/verify.html.erb +0 -51
- data/app/views/masks/keys/new.html.erb +0 -127
- data/app/views/masks/manage/actors/index.html.erb +0 -104
- data/app/views/masks/manage/actors/show.html.erb +0 -172
- data/app/views/masks/manage/clients/index.html.erb +0 -102
- data/app/views/masks/manage/clients/show.html.erb +0 -156
- data/app/views/masks/manage/dashboard/index.html.erb +0 -10
- data/app/views/masks/manage/devices/index.html.erb +0 -47
- data/app/views/masks/one_time_code/new.html.erb +0 -167
- data/app/views/masks/openid/authorizations/error.html.erb +0 -23
- data/app/views/masks/openid/authorizations/new.html.erb +0 -46
- data/app/views/masks/passwords/edit.html.erb +0 -71
- data/app/views/masks/recoveries/new.html.erb +0 -69
- data/app/views/masks/recoveries/password.html.erb +0 -63
- data/app/views/masks/sessions/new.html.erb +0 -152
- data/config/brakeman.ignore +0 -28
- data/config/initializers/inflections.rb +0 -5
- data/config/locales/en.yml +0 -307
- data/db/migrate/20231205173845_create_actors.rb +0 -94
- data/db/migrate/20240329182422_support_openid.rb +0 -64
- data/lib/generators/masks/install/USAGE +0 -8
- data/lib/generators/masks/install/templates/initializer.rb +0 -5
- data/lib/generators/masks/install/templates/masks.json +0 -9
- data/lib/masks/configuration.rb +0 -249
- data/lib/masks/engine.rb +0 -25
- data/lib/masks/middleware.rb +0 -70
- data/lib/tasks/masks_tasks.rake +0 -72
- data/masks.json +0 -315
data/app/models/masks/check.rb
DELETED
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Masks
|
|
4
|
-
# Checks track attempts to verify one attribute of a session, like the actor
|
|
5
|
-
# or their password.
|
|
6
|
-
#
|
|
7
|
-
# Every session contains a list of checks that can be manipulated while it is
|
|
8
|
-
# masked. Credentials associated with the session are typical consumers of
|
|
9
|
-
# checks, but direct manipulation is possible as well.
|
|
10
|
-
#
|
|
11
|
-
# Once a check's consumers have reported their status (approved, denied, or
|
|
12
|
-
# skipped) it will report an overall status based on the results, either:
|
|
13
|
-
#
|
|
14
|
-
# - +passed?+ - true if attempts were made and all were approved, not skipped, or the check is optional
|
|
15
|
-
# - +failed?+ - true if attempts were made and any were denied
|
|
16
|
-
#
|
|
17
|
-
# **Note**: Checks can exist in a middle state, neither passed or failed, in
|
|
18
|
-
# the case that no attempts were made.
|
|
19
|
-
#
|
|
20
|
-
# @see Masks::Credential Masks::Credential
|
|
21
|
-
class Check < ApplicationModel
|
|
22
|
-
attribute :key
|
|
23
|
-
attribute :lifetime
|
|
24
|
-
attribute :optional, default: false
|
|
25
|
-
attribute :attempted, default: -> { {} }
|
|
26
|
-
attribute :approved
|
|
27
|
-
attribute :skipped
|
|
28
|
-
attribute :denied
|
|
29
|
-
|
|
30
|
-
# Returns a hash of attempts for the check.
|
|
31
|
-
#
|
|
32
|
-
# Each key in the hash is the name of a specific attemptee, like a class or
|
|
33
|
-
# credential. The value is a hash of data about the attempt (like when it was
|
|
34
|
-
# attempted, approved, denied, and/or skipped).
|
|
35
|
-
#
|
|
36
|
-
# @return [Hash]
|
|
37
|
-
def attempts
|
|
38
|
-
attempted.deep_merge(@attempts || {}).deep_stringify_keys
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
# Whether or not the check is optional.
|
|
42
|
-
#
|
|
43
|
-
# Optional checks always return +true+ for +passed?+.
|
|
44
|
-
#
|
|
45
|
-
# @return Boolean
|
|
46
|
-
def optional?
|
|
47
|
-
optional
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
# Whether or not the check passed.
|
|
51
|
-
#
|
|
52
|
-
# +true+ if attempts were made and all were approved, not skipped, or the check is optional
|
|
53
|
-
#
|
|
54
|
-
# @return Boolean
|
|
55
|
-
def passed?
|
|
56
|
-
return true if optional? && !failed?
|
|
57
|
-
return false if attempts.keys.empty?
|
|
58
|
-
|
|
59
|
-
attempts.all? do |id, _opts|
|
|
60
|
-
attempt_approved?(id) || attempt_skipped?(id)
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
# Returns true if a specific attempt was approved.
|
|
65
|
-
# @param [String] id
|
|
66
|
-
# @return Boolean
|
|
67
|
-
def attempt_approved?(id)
|
|
68
|
-
opts = attempts.fetch(id.to_s, {})
|
|
69
|
-
|
|
70
|
-
return approved unless lifetime
|
|
71
|
-
return false if opts["skipped_at"]
|
|
72
|
-
|
|
73
|
-
time =
|
|
74
|
-
case opts["approved_at"]
|
|
75
|
-
when nil
|
|
76
|
-
return false
|
|
77
|
-
when String
|
|
78
|
-
Time.try(:parse, opts["approved_at"])
|
|
79
|
-
else
|
|
80
|
-
time
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
if time
|
|
84
|
-
time + ActiveSupport::Duration.parse(lifetime) > Time.current
|
|
85
|
-
else
|
|
86
|
-
false
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
# Returns true if a specific attempt was skipped.
|
|
91
|
-
# @param [String] id
|
|
92
|
-
# @return Boolean
|
|
93
|
-
def attempt_skipped?(id)
|
|
94
|
-
opts = attempts.fetch(id.to_s, {})
|
|
95
|
-
opts["skipped_at"] && optional
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
# Approves an attempt.
|
|
99
|
-
#
|
|
100
|
-
# Additional metadata can be passed as keyword arguments, and it will be
|
|
101
|
-
# saved alongside the attempt data.
|
|
102
|
-
#
|
|
103
|
-
# @param [String] id
|
|
104
|
-
# @param [Hash] opts
|
|
105
|
-
# @return Boolean
|
|
106
|
-
def approve!(id, **opts)
|
|
107
|
-
self.approved = true
|
|
108
|
-
|
|
109
|
-
merge_attempt(
|
|
110
|
-
id,
|
|
111
|
-
opts.merge(approved_at: Time.current.iso8601, skipped_at: nil)
|
|
112
|
-
)
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
# Skips an attempt. Skips count as approvals.
|
|
116
|
-
#
|
|
117
|
-
# Additional metadata can be passed as keyword arguments, and it will be
|
|
118
|
-
# saved alongside the attempt data.
|
|
119
|
-
#
|
|
120
|
-
# @param [String] id
|
|
121
|
-
# @param [Hash] opts
|
|
122
|
-
# @return Boolean
|
|
123
|
-
def skip!(id, **opts)
|
|
124
|
-
self.skipped = true
|
|
125
|
-
|
|
126
|
-
merge_attempt(
|
|
127
|
-
id,
|
|
128
|
-
opts.merge(approved_at: nil, skipped_at: Time.current.iso8601)
|
|
129
|
-
)
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
# Denies an attempt.
|
|
133
|
-
#
|
|
134
|
-
# Additional metadata can be passed as keyword arguments, and it will be
|
|
135
|
-
# saved alongside the attempt data.
|
|
136
|
-
#
|
|
137
|
-
# @param [String] id
|
|
138
|
-
# @param [Hash] opts
|
|
139
|
-
# @return Boolean
|
|
140
|
-
def deny!(id, **opts)
|
|
141
|
-
self.denied = true
|
|
142
|
-
|
|
143
|
-
merge_attempt(id, opts.merge(approved_at: nil, skipped_at: nil))
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
# Returns the time the check passed, if it did.
|
|
147
|
-
# @return [Datetime]
|
|
148
|
-
def passed_at
|
|
149
|
-
return unless passed?
|
|
150
|
-
|
|
151
|
-
attempts
|
|
152
|
-
.map do |_id, opts|
|
|
153
|
-
time = opts["approved_at"] || opts["skipped_at"]
|
|
154
|
-
Time.try(:parse, time) if time
|
|
155
|
-
end
|
|
156
|
-
.compact
|
|
157
|
-
.max
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
# Clears all data for attempts by the given +id+.
|
|
161
|
-
# @param [String] id
|
|
162
|
-
# @return [Datetime]
|
|
163
|
-
def clear!(id)
|
|
164
|
-
@attempts&.except!(id)
|
|
165
|
-
attempted.except!(id)
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
# Returns a version of the check intended for the rails session.
|
|
169
|
-
# @return [Hash]
|
|
170
|
-
def to_session
|
|
171
|
-
return { optional:, attempted: } unless lifetime
|
|
172
|
-
|
|
173
|
-
{ optional:, attempted: attempts }
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
private
|
|
177
|
-
|
|
178
|
-
def failed?
|
|
179
|
-
return false if attempts.keys.empty?
|
|
180
|
-
|
|
181
|
-
attempts.any? do |id, _opts|
|
|
182
|
-
!attempt_approved?(id) && !attempt_skipped?(id)
|
|
183
|
-
end
|
|
184
|
-
end
|
|
185
|
-
|
|
186
|
-
def merge_attempt(id, data)
|
|
187
|
-
@attempts ||= {}
|
|
188
|
-
@attempts[id] ||= {}
|
|
189
|
-
@attempts[id].deep_merge!(data.deep_stringify_keys)
|
|
190
|
-
end
|
|
191
|
-
end
|
|
192
|
-
end
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Masks
|
|
4
|
-
# A base class for credentials, which identify actors and check their access.
|
|
5
|
-
#
|
|
6
|
-
# When a session is masked, a set of credentials are given the chance to
|
|
7
|
-
# inspect the session parameters, propose an actor, and approve or deny
|
|
8
|
-
# their access.
|
|
9
|
-
#
|
|
10
|
-
# There are a few lifecycle methods available to credentials:
|
|
11
|
-
#
|
|
12
|
-
# - +lookup+ - should return an identified actor if found
|
|
13
|
-
# - +maskup+ - validates the session, actor, and any other data
|
|
14
|
-
# - +backup+ - records the status of the credential's check(s), if necessary
|
|
15
|
-
# - +cleanup+ - deletes any recorded data for the credential
|
|
16
|
-
#
|
|
17
|
-
# Sessions expect credentials to use checks to record their results, so there
|
|
18
|
-
# are helper methods to approve, deny, or skip associated checks—+approve!+,
|
|
19
|
-
# +deny!+, and +skip!+ respectively.
|
|
20
|
-
#
|
|
21
|
-
# @see Masks::Check Masks::Check
|
|
22
|
-
# @see Masks::Credentials Masks::Credentials
|
|
23
|
-
class Credential < ApplicationModel
|
|
24
|
-
class << self
|
|
25
|
-
def checks(value = nil)
|
|
26
|
-
@checks ||= {}
|
|
27
|
-
@checks[self.class.name] = value.to_s if value
|
|
28
|
-
@checks[self.class.name]
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
attribute :session
|
|
33
|
-
attribute :masked_at
|
|
34
|
-
attribute :passed_at
|
|
35
|
-
|
|
36
|
-
delegate :config,
|
|
37
|
-
:actor,
|
|
38
|
-
:session_params,
|
|
39
|
-
:account_params,
|
|
40
|
-
:params,
|
|
41
|
-
:writable?,
|
|
42
|
-
to: :session
|
|
43
|
-
|
|
44
|
-
# return an actor if it's found and valid
|
|
45
|
-
def lookup
|
|
46
|
-
nil
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def mask!
|
|
50
|
-
before_mask
|
|
51
|
-
|
|
52
|
-
# existing checks (found from the session) can be
|
|
53
|
-
# skipped when already present and not expired
|
|
54
|
-
return if check&.passed? && check.attempts[slug] && valid?
|
|
55
|
-
|
|
56
|
-
self.masked_at = Time.current
|
|
57
|
-
|
|
58
|
-
maskup
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
# verify the session and actor
|
|
62
|
-
def maskup
|
|
63
|
-
nil
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def backup!
|
|
67
|
-
self.passed_at = Time.current if check&.passed? &&
|
|
68
|
-
check&.attempt_approved?(slug)
|
|
69
|
-
|
|
70
|
-
backup
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
# write any data after all credentials/checks have run
|
|
74
|
-
def backup
|
|
75
|
-
nil # but overridable
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
# cleanup data re: the mask
|
|
79
|
-
def cleanup
|
|
80
|
-
nil # but overridable
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
def cleanup!
|
|
84
|
-
cleanup
|
|
85
|
-
reset!
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
delegate :optional?,
|
|
89
|
-
:passed?,
|
|
90
|
-
:skipped?,
|
|
91
|
-
:invalidated?,
|
|
92
|
-
to: :check,
|
|
93
|
-
allow_nil: true
|
|
94
|
-
|
|
95
|
-
def slug
|
|
96
|
-
self.class.name.split("::").join("_").underscore
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
def name
|
|
100
|
-
I18n.t("auth.credentials.#{slug}.name")
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
def check
|
|
104
|
-
session&.find_check(self.class.checks)
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
def patch_params
|
|
108
|
-
session&.account_params&.fetch(slug, {})
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
private
|
|
112
|
-
|
|
113
|
-
def before_mask
|
|
114
|
-
nil
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
def approve!(**opts)
|
|
118
|
-
check&.approve!(slug, **opts)
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
def deny!(**opts)
|
|
122
|
-
check&.deny!(slug, **opts)
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
def skip!(**opts)
|
|
126
|
-
check&.skip!(slug, **opts)
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
def reset!
|
|
130
|
-
check&.clear!(slug)
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
def nickname_config
|
|
134
|
-
session.config.dat.nickname
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
def nickname_format
|
|
138
|
-
return unless nickname_config.format
|
|
139
|
-
|
|
140
|
-
Regexp.new(nickname_config.format)
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
def prefix_nickname(value, default: nil)
|
|
144
|
-
prefix = nickname_config&.prefix
|
|
145
|
-
|
|
146
|
-
return default unless value.present?
|
|
147
|
-
|
|
148
|
-
prefixed = value
|
|
149
|
-
prefixed = "#{prefix}#{value}" if prefix && !value.start_with?(prefix)
|
|
150
|
-
|
|
151
|
-
return default if nickname_format && !nickname_format.match?(prefixed)
|
|
152
|
-
|
|
153
|
-
prefixed
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
def validates_length(key, opts)
|
|
157
|
-
return unless opts
|
|
158
|
-
|
|
159
|
-
if opts[:min] && send(key).length < opts[:min]
|
|
160
|
-
errors.add(key, :too_short, count: opts[:min])
|
|
161
|
-
elsif opts[:max] && send(key).length > opts[:max]
|
|
162
|
-
errors.add(key, :too_long, count: opts[:max])
|
|
163
|
-
end
|
|
164
|
-
end
|
|
165
|
-
end
|
|
166
|
-
end
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Masks
|
|
4
|
-
module Credentials
|
|
5
|
-
# Checks :key given a valid Authorization header.
|
|
6
|
-
class AccessToken < Masks::Credential
|
|
7
|
-
checks :access_token
|
|
8
|
-
|
|
9
|
-
def lookup
|
|
10
|
-
access_token =
|
|
11
|
-
session.config.model(:openid_access_token).valid.find_by(token:)
|
|
12
|
-
|
|
13
|
-
return unless access_token&.actor
|
|
14
|
-
|
|
15
|
-
session.extras(access_token:)
|
|
16
|
-
session.scoped = access_token
|
|
17
|
-
|
|
18
|
-
access_token.actor
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def maskup
|
|
22
|
-
access_token = session.extra(:access_token)
|
|
23
|
-
|
|
24
|
-
if access_token&.actor && access_token&.actor == session&.actor &&
|
|
25
|
-
session.scoped == access_token
|
|
26
|
-
approve!
|
|
27
|
-
else
|
|
28
|
-
deny!
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
private
|
|
33
|
-
|
|
34
|
-
def token
|
|
35
|
-
return if [header_token, param_token].uniq.compact.length != 1
|
|
36
|
-
|
|
37
|
-
header_token || param_token
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def header_token
|
|
41
|
-
unless auth_header.provided? && !auth_header.parts.first.nil? &&
|
|
42
|
-
auth_header.scheme.to_s == "bearer"
|
|
43
|
-
return
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
auth_header.params
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def param_token
|
|
50
|
-
params[:access_token]
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def auth_header
|
|
54
|
-
return unless session.try(:request)
|
|
55
|
-
|
|
56
|
-
@auth_header = Rack::Auth::AbstractRequest.new(session.request.env)
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
end
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Masks
|
|
4
|
-
module Credentials
|
|
5
|
-
# Checks :factor2 for a valid backup code.
|
|
6
|
-
class BackupCode < Masks::Credential
|
|
7
|
-
include Factor2
|
|
8
|
-
|
|
9
|
-
private
|
|
10
|
-
|
|
11
|
-
def param
|
|
12
|
-
:backup_code
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def secret
|
|
16
|
-
actor&.backup_codes if actor&.saved_backup_codes?
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def verify(code)
|
|
20
|
-
code if secret&.fetch(code, false)
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def backup
|
|
24
|
-
return unless verified?
|
|
25
|
-
|
|
26
|
-
actor.update_attribute("backup_codes", secret.merge(code => false))
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
end
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Masks
|
|
4
|
-
module Credentials
|
|
5
|
-
# Checks for a known, valid :device.
|
|
6
|
-
#
|
|
7
|
-
# If the device is not associated with the session's actor, it will be.
|
|
8
|
-
# Identification is based on the +user_agent+ and a few other facets.
|
|
9
|
-
class Device < Masks::Credential
|
|
10
|
-
checks :device
|
|
11
|
-
|
|
12
|
-
def lookup
|
|
13
|
-
return unless actor
|
|
14
|
-
|
|
15
|
-
device = config.find_device(session, actor:)
|
|
16
|
-
|
|
17
|
-
session.extras(device:)
|
|
18
|
-
|
|
19
|
-
nil
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def maskup
|
|
23
|
-
device = session.extra(:device)
|
|
24
|
-
|
|
25
|
-
return deny! unless device
|
|
26
|
-
|
|
27
|
-
session_key = session.data[:device_key]
|
|
28
|
-
|
|
29
|
-
# ensure devices match across sessions, which would only happen if a
|
|
30
|
-
# session cookie happened is shared across machines. this destroys
|
|
31
|
-
# the entire session and cleans up everything involved.
|
|
32
|
-
if session_key && device.session_key != session_key
|
|
33
|
-
raise "invalid device"
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
# store devices that are found in a database of known devices
|
|
37
|
-
if device.known?
|
|
38
|
-
session.data[:device_key] = device.session_key
|
|
39
|
-
actor.devices << device
|
|
40
|
-
approve!
|
|
41
|
-
else
|
|
42
|
-
cleanup
|
|
43
|
-
deny!
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def backup
|
|
48
|
-
session.extra(:device)&.touch(:accessed_at) if session&.passed?
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def cleanup
|
|
52
|
-
device = session.extra(:device)
|
|
53
|
-
device&.reset_version
|
|
54
|
-
|
|
55
|
-
session.data[:device_key] = nil
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
end
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Masks
|
|
4
|
-
module Credentials
|
|
5
|
-
# Checks for an :actor given a matching email.
|
|
6
|
-
class Email < Masks::Credential
|
|
7
|
-
checks :actor
|
|
8
|
-
|
|
9
|
-
def lookup
|
|
10
|
-
return if actor || !email
|
|
11
|
-
|
|
12
|
-
actor = config.find_actor(session, email:)
|
|
13
|
-
actor ||=
|
|
14
|
-
config.build_actor(
|
|
15
|
-
session,
|
|
16
|
-
email:,
|
|
17
|
-
nickname: generate_nickname(email)
|
|
18
|
-
)
|
|
19
|
-
actor.signup = true
|
|
20
|
-
actor
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def maskup
|
|
24
|
-
approve! if actor && email && valid?
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
private
|
|
28
|
-
|
|
29
|
-
def email
|
|
30
|
-
@email ||=
|
|
31
|
-
[session_params[:email], session_params[:nickname]].find do |param|
|
|
32
|
-
ValidateEmail.valid?(param)
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def generate_nickname(email)
|
|
37
|
-
return email if !nickname_format || nickname_format.match?(email)
|
|
38
|
-
|
|
39
|
-
parts = email.split("@")
|
|
40
|
-
name = parts[0].gsub(/[^\w\d]/, "")
|
|
41
|
-
|
|
42
|
-
prefix_nickname(
|
|
43
|
-
"#{name.downcase.slice(0, 16)}#{SecureRandom.hex.slice(0, 8)}"
|
|
44
|
-
)
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
end
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Masks
|
|
4
|
-
module Credentials
|
|
5
|
-
# Base class for factor2 credentials.
|
|
6
|
-
module Factor2
|
|
7
|
-
extend ActiveSupport::Concern
|
|
8
|
-
|
|
9
|
-
included do
|
|
10
|
-
validates :secret, presence: true, if: :enabled?
|
|
11
|
-
validates :code, presence: { allow_nil: true }
|
|
12
|
-
|
|
13
|
-
attribute :code
|
|
14
|
-
|
|
15
|
-
checks :factor2
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def lookup
|
|
19
|
-
# nothing to do here
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def maskup
|
|
23
|
-
check.optional = false if actor&.factor2?
|
|
24
|
-
|
|
25
|
-
return unless enabled?
|
|
26
|
-
|
|
27
|
-
code_param = session_params&.fetch(param, nil)&.presence
|
|
28
|
-
|
|
29
|
-
self.code = verify(code_param) if code_param
|
|
30
|
-
|
|
31
|
-
if code
|
|
32
|
-
approve!
|
|
33
|
-
elsif code_param
|
|
34
|
-
deny!
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def verified?
|
|
39
|
-
code
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def enabled?
|
|
43
|
-
secret.present?
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def param
|
|
47
|
-
raise NotImplementedError
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def secret
|
|
51
|
-
raise NotImplementedError
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def enable(code, secret:)
|
|
55
|
-
raise NotImplementedError
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def verify(code)
|
|
59
|
-
raise NotImplementedError
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def verify_on_enable?
|
|
63
|
-
false
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def generate_secret
|
|
67
|
-
nil
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
end
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Masks
|
|
4
|
-
module Credentials
|
|
5
|
-
# Checks :key given a valid Authorization header.
|
|
6
|
-
class Key < Masks::Credential
|
|
7
|
-
checks :key
|
|
8
|
-
|
|
9
|
-
attribute :accessed
|
|
10
|
-
|
|
11
|
-
def lookup
|
|
12
|
-
secret = session.request.authorization&.split&.last
|
|
13
|
-
key = session.config.find_key(session, secret:)
|
|
14
|
-
|
|
15
|
-
return unless key
|
|
16
|
-
|
|
17
|
-
session.extras(key:)
|
|
18
|
-
session.scoped = key
|
|
19
|
-
self.accessed = true
|
|
20
|
-
key.actor
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def maskup
|
|
24
|
-
key = session.extra(:key)
|
|
25
|
-
|
|
26
|
-
if key&.actor == session&.actor && session.scoped == key
|
|
27
|
-
approve!
|
|
28
|
-
else
|
|
29
|
-
deny!
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def backup
|
|
34
|
-
session.extra(:key).touch(:accessed_at) if session&.passed? && accessed
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
end
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Masks
|
|
4
|
-
module Credentials
|
|
5
|
-
# As of now, simply keeps track of +last_login+ times on the actor.
|
|
6
|
-
class LastLogin < Masks::Credential
|
|
7
|
-
def backup
|
|
8
|
-
actor&.touch(:last_login_at) if session&.passed?
|
|
9
|
-
end
|
|
10
|
-
end
|
|
11
|
-
end
|
|
12
|
-
end
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Masks
|
|
4
|
-
module Credentials
|
|
5
|
-
# Checks for an :actor to masquerade as.
|
|
6
|
-
class Masquerade < Masks::Credential
|
|
7
|
-
checks :actor
|
|
8
|
-
|
|
9
|
-
def lookup
|
|
10
|
-
return if session.actor
|
|
11
|
-
|
|
12
|
-
value = session.data[:as]
|
|
13
|
-
|
|
14
|
-
@loaded =
|
|
15
|
-
case value
|
|
16
|
-
when Masks::ANON
|
|
17
|
-
Actors::Anonymous.new(session:) if session.mask&.allow_anonymous?
|
|
18
|
-
when session.mask.actor_scope
|
|
19
|
-
value
|
|
20
|
-
when ValidateEmail.valid?(value)
|
|
21
|
-
config.find_actor(session, email: value)
|
|
22
|
-
when String
|
|
23
|
-
config.find_actor(session, nickname: prefix_nickname(value))
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def maskup
|
|
28
|
-
approve! if @loaded
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
end
|