masks 0.2.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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +50 -0
- data/Rakefile +11 -0
- data/app/assets/builds/application.css +4764 -0
- data/app/assets/builds/application.js +8236 -0
- data/app/assets/builds/application.js.map +7 -0
- data/app/assets/builds/masks/application.css +1 -0
- data/app/assets/builds/masks/application.js +7122 -0
- data/app/assets/builds/masks/application.js.map +7 -0
- data/app/assets/images/masks.png +0 -0
- data/app/assets/javascripts/application.js +2 -0
- data/app/assets/javascripts/controllers/application.js +9 -0
- data/app/assets/javascripts/controllers/emails_controller.js +28 -0
- data/app/assets/javascripts/controllers/index.js +12 -0
- data/app/assets/javascripts/controllers/keys_controller.js +20 -0
- data/app/assets/javascripts/controllers/recover_controller.js +21 -0
- data/app/assets/javascripts/controllers/recover_password_controller.js +21 -0
- data/app/assets/javascripts/controllers/session_controller.js +94 -0
- data/app/assets/manifest.js +2 -0
- data/app/assets/masks_manifest.js +2 -0
- data/app/assets/stylesheets/application.css +26 -0
- data/app/controllers/concerns/masks/controller.rb +114 -0
- data/app/controllers/masks/actors_controller.rb +15 -0
- data/app/controllers/masks/application_controller.rb +35 -0
- data/app/controllers/masks/backup_codes_controller.rb +34 -0
- data/app/controllers/masks/debug_controller.rb +9 -0
- data/app/controllers/masks/devices_controller.rb +20 -0
- data/app/controllers/masks/emails_controller.rb +60 -0
- data/app/controllers/masks/error_controller.rb +14 -0
- data/app/controllers/masks/keys_controller.rb +45 -0
- data/app/controllers/masks/manage/actor_controller.rb +35 -0
- data/app/controllers/masks/manage/actors_controller.rb +12 -0
- data/app/controllers/masks/manage/base_controller.rb +12 -0
- data/app/controllers/masks/one_time_code_controller.rb +49 -0
- data/app/controllers/masks/passwords_controller.rb +33 -0
- data/app/controllers/masks/recoveries_controller.rb +43 -0
- data/app/controllers/masks/sessions_controller.rb +53 -0
- data/app/helpers/masks/application_helper.rb +49 -0
- data/app/jobs/masks/application_job.rb +7 -0
- data/app/jobs/masks/expire_actors_job.rb +15 -0
- data/app/jobs/masks/expire_recoveries_job.rb +15 -0
- data/app/mailers/masks/actor_mailer.rb +22 -0
- data/app/mailers/masks/application_mailer.rb +15 -0
- data/app/models/concerns/masks/access.rb +162 -0
- data/app/models/concerns/masks/actor.rb +132 -0
- data/app/models/concerns/masks/adapter.rb +68 -0
- data/app/models/concerns/masks/role.rb +9 -0
- data/app/models/concerns/masks/scoped.rb +54 -0
- data/app/models/masks/access/actor_password.rb +20 -0
- data/app/models/masks/access/actor_scopes.rb +18 -0
- data/app/models/masks/access/actor_signup.rb +22 -0
- data/app/models/masks/actors/anonymous.rb +40 -0
- data/app/models/masks/actors/system.rb +24 -0
- data/app/models/masks/adapters/active_record.rb +85 -0
- data/app/models/masks/application_model.rb +15 -0
- data/app/models/masks/application_record.rb +8 -0
- data/app/models/masks/check.rb +192 -0
- data/app/models/masks/credential.rb +166 -0
- data/app/models/masks/credentials/backup_code.rb +30 -0
- data/app/models/masks/credentials/device.rb +59 -0
- data/app/models/masks/credentials/email.rb +48 -0
- data/app/models/masks/credentials/factor2.rb +71 -0
- data/app/models/masks/credentials/key.rb +38 -0
- data/app/models/masks/credentials/last_login.rb +12 -0
- data/app/models/masks/credentials/masquerade.rb +32 -0
- data/app/models/masks/credentials/nickname.rb +63 -0
- data/app/models/masks/credentials/one_time_code.rb +34 -0
- data/app/models/masks/credentials/password.rb +28 -0
- data/app/models/masks/credentials/recovery.rb +71 -0
- data/app/models/masks/credentials/session.rb +67 -0
- data/app/models/masks/device.rb +30 -0
- data/app/models/masks/error.rb +51 -0
- data/app/models/masks/event.rb +14 -0
- data/app/models/masks/mask.rb +255 -0
- data/app/models/masks/rails/actor.rb +190 -0
- data/app/models/masks/rails/actor_role.rb +12 -0
- data/app/models/masks/rails/device.rb +47 -0
- data/app/models/masks/rails/email.rb +96 -0
- data/app/models/masks/rails/key.rb +61 -0
- data/app/models/masks/rails/recovery.rb +116 -0
- data/app/models/masks/rails/role.rb +20 -0
- data/app/models/masks/rails/scope.rb +15 -0
- data/app/models/masks/session.rb +447 -0
- data/app/models/masks/sessions/access.rb +26 -0
- data/app/models/masks/sessions/inline.rb +16 -0
- data/app/models/masks/sessions/request.rb +42 -0
- data/app/resources/masks/actor_resource.rb +9 -0
- data/app/resources/masks/session_resource.rb +15 -0
- data/app/views/layouts/masks/application.html.erb +17 -0
- data/app/views/layouts/masks/mailer.html.erb +17 -0
- data/app/views/layouts/masks/mailer.text.erb +1 -0
- data/app/views/layouts/masks/manage.html.erb +25 -0
- data/app/views/masks/actor_mailer/recover_credentials.html.erb +33 -0
- data/app/views/masks/actor_mailer/recover_credentials.text.erb +1 -0
- data/app/views/masks/actor_mailer/verify_email.html.erb +34 -0
- data/app/views/masks/actor_mailer/verify_email.text.erb +8 -0
- data/app/views/masks/actors/current.html.erb +152 -0
- data/app/views/masks/application/_header.html.erb +31 -0
- data/app/views/masks/backup_codes/new.html.erb +103 -0
- data/app/views/masks/emails/new.html.erb +103 -0
- data/app/views/masks/emails/verify.html.erb +51 -0
- data/app/views/masks/keys/new.html.erb +127 -0
- data/app/views/masks/manage/actor/show.html.erb +126 -0
- data/app/views/masks/manage/actors/index.html.erb +40 -0
- data/app/views/masks/one_time_code/new.html.erb +150 -0
- data/app/views/masks/passwords/edit.html.erb +58 -0
- data/app/views/masks/recoveries/new.html.erb +71 -0
- data/app/views/masks/recoveries/password.html.erb +64 -0
- data/app/views/masks/sessions/new.html.erb +153 -0
- data/config/brakeman.ignore +28 -0
- data/config/locales/en.yml +286 -0
- data/config/routes.rb +46 -0
- data/db/migrate/20231205173845_create_actors.rb +94 -0
- data/lib/generators/masks/install/USAGE +8 -0
- data/lib/generators/masks/install/install_generator.rb +33 -0
- data/lib/generators/masks/install/templates/initializer.rb +5 -0
- data/lib/generators/masks/install/templates/masks.json +6 -0
- data/lib/masks/configuration.rb +236 -0
- data/lib/masks/engine.rb +25 -0
- data/lib/masks/middleware.rb +70 -0
- data/lib/masks/version.rb +5 -0
- data/lib/masks.rb +183 -0
- data/lib/tasks/masks_tasks.rake +71 -0
- data/masks.json +274 -0
- metadata +416 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Masks
|
|
4
|
+
# @visibility private
|
|
5
|
+
class OneTimeCodeController < ApplicationController
|
|
6
|
+
require_mask type: :session, only: :new
|
|
7
|
+
|
|
8
|
+
before_action only: %i[create destroy] do
|
|
9
|
+
require_sudo(one_time_code_path)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def new
|
|
13
|
+
respond_to { |format| format.html { render(:new) } }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def create
|
|
17
|
+
if secret_param && code_param
|
|
18
|
+
@actor.totp_secret = secret_param
|
|
19
|
+
@actor.totp_code = code_param
|
|
20
|
+
@actor.save if @actor.valid?
|
|
21
|
+
|
|
22
|
+
flash[:errors] = @actor.errors.full_messages unless @actor.valid?
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
respond_to { |format| format.html { redirect_to one_time_code_path } }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def destroy
|
|
29
|
+
@actor.totp_secret = nil
|
|
30
|
+
@actor.save if @actor.valid?
|
|
31
|
+
|
|
32
|
+
respond_to { |format| format.html { redirect_to one_time_code_path } }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def create_params
|
|
38
|
+
params.require(:one_time_code).permit(:code, :secret)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def code_param
|
|
42
|
+
create_params[:code]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def secret_param
|
|
46
|
+
create_params[:secret]
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Masks
|
|
4
|
+
# @visibility private
|
|
5
|
+
class PasswordsController < ApplicationController
|
|
6
|
+
require_mask type: :session, only: :edit
|
|
7
|
+
require_access "actor.password", only: :update
|
|
8
|
+
|
|
9
|
+
before_action only: %i[update] do
|
|
10
|
+
require_sudo(password_path)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def edit
|
|
14
|
+
respond_to { |format| format.html { render(:edit) } }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def update
|
|
18
|
+
if password_param
|
|
19
|
+
current_access.change_password(password_param)
|
|
20
|
+
|
|
21
|
+
flash[:errors] = @actor.errors.full_messages unless @actor.valid?
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
respond_to { |format| format.html { redirect_to password_path } }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def password_param
|
|
30
|
+
params.dig(:password, :change)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Masks
|
|
4
|
+
# @visibility private
|
|
5
|
+
class RecoveriesController < ApplicationController
|
|
6
|
+
before_action :redirect_if_logged_in, only: %i[new]
|
|
7
|
+
|
|
8
|
+
def new
|
|
9
|
+
respond_to { |format| format.html { render(:new) } }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def create
|
|
13
|
+
flash[:recovery] = true
|
|
14
|
+
|
|
15
|
+
respond_to { |format| format.html { redirect_to recover_path } }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def password
|
|
19
|
+
@recovery = masked_session.extra(:recovery)
|
|
20
|
+
|
|
21
|
+
respond_to { |format| format.html { render(:password) } }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def reset
|
|
25
|
+
@recovery = masked_session.extra(:recovery)
|
|
26
|
+
|
|
27
|
+
flash[:reset] = true if @recovery&.destroyed?
|
|
28
|
+
flash[:errors] = @recovery.errors.full_messages if @recovery &&
|
|
29
|
+
!@recovery&.valid?
|
|
30
|
+
|
|
31
|
+
redirect =
|
|
32
|
+
@recovery ? recover_password_path(token: @recovery.token) : recover_path
|
|
33
|
+
|
|
34
|
+
respond_to { |format| format.html { redirect_to redirect } }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def redirect_if_logged_in
|
|
40
|
+
redirect_to Masks.configuration.site_links[:root] if current_actor
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Masks
|
|
4
|
+
# @visibility private
|
|
5
|
+
class SessionsController < ApplicationController
|
|
6
|
+
def new
|
|
7
|
+
respond_to do |format|
|
|
8
|
+
format.json { render json: resource_cls.new(masked_session) }
|
|
9
|
+
format.html { render(:new) }
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def create
|
|
14
|
+
flash[
|
|
15
|
+
:errors
|
|
16
|
+
] = masked_session.errors.full_messages unless request.format == :json
|
|
17
|
+
|
|
18
|
+
respond_to do |format|
|
|
19
|
+
format.json { render json: resource_cls.new(masked_session) }
|
|
20
|
+
format.html do
|
|
21
|
+
path =
|
|
22
|
+
(
|
|
23
|
+
if masked_session.passed?
|
|
24
|
+
masked_session.mask.pass ||
|
|
25
|
+
Masks.configuration.site_links[:after_login]
|
|
26
|
+
else
|
|
27
|
+
masked_session.mask.fail ||
|
|
28
|
+
Masks.configuration.site_links[:login]
|
|
29
|
+
end
|
|
30
|
+
)
|
|
31
|
+
redirect_to path
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def destroy
|
|
37
|
+
masked_session.cleanup!
|
|
38
|
+
|
|
39
|
+
respond_to do |format|
|
|
40
|
+
format.json { render json: resource_cls.new(masked_session) }
|
|
41
|
+
format.html do
|
|
42
|
+
redirect_to Masks.configuration.site_links[:after_logout]
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def resource_cls
|
|
50
|
+
Masks.configuration.model(:session_json)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Masks
|
|
4
|
+
# @visibility private
|
|
5
|
+
module ApplicationHelper
|
|
6
|
+
include Pagy::Frontend
|
|
7
|
+
|
|
8
|
+
def totp_svg(uri, **opts)
|
|
9
|
+
qrcode = RQRCode::QRCode.new(uri)
|
|
10
|
+
raw qrcode.as_svg(**opts)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def device_icon(device)
|
|
14
|
+
case device.device_type
|
|
15
|
+
when "desktop"
|
|
16
|
+
"computer"
|
|
17
|
+
when "smartphone", "feature phone"
|
|
18
|
+
"smartphone"
|
|
19
|
+
when "tablet", "phablet", "portable media player"
|
|
20
|
+
"tablet"
|
|
21
|
+
when "console"
|
|
22
|
+
"gamepad"
|
|
23
|
+
when "tv", "smart display"
|
|
24
|
+
"tv-2"
|
|
25
|
+
when "car browser"
|
|
26
|
+
"car"
|
|
27
|
+
when "camera"
|
|
28
|
+
"camera"
|
|
29
|
+
when "smart speaker", "wearable", "peripheral"
|
|
30
|
+
"bluetooth"
|
|
31
|
+
else
|
|
32
|
+
"question-circle"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def logged_in?
|
|
37
|
+
@session&.passed? && @session&.actor && !@session.actor.anonymous?
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def factor2_required?
|
|
41
|
+
checks = @session.checks_for(:session)
|
|
42
|
+
|
|
43
|
+
return false unless checks
|
|
44
|
+
|
|
45
|
+
checks[:factor2] && !checks[:factor2]&.passed? &&
|
|
46
|
+
checks[:actor]&.passed? && checks[:password]&.passed?
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Masks
|
|
4
|
+
# Job for deleting actors that have passed their expiration time.
|
|
5
|
+
#
|
|
6
|
+
# This job effectively delegates its work to the configured
|
|
7
|
+
# +Masks::Adapter+, specifically calling +#expire_actors+.
|
|
8
|
+
#
|
|
9
|
+
# @see Masks::Adapter Masks::Adapter
|
|
10
|
+
class ExpireActorsJob < ApplicationJob
|
|
11
|
+
def perform
|
|
12
|
+
Masks.configuration.expire_actors
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Masks
|
|
4
|
+
# Job for deleting recovies requests that passed their expiration time.
|
|
5
|
+
#
|
|
6
|
+
# This job delegates its work to the configured
|
|
7
|
+
# +Masks::Adapter+, specifically calling +#expire_recoveries+.
|
|
8
|
+
#
|
|
9
|
+
# @see Masks::Adapter Masks::Adapter
|
|
10
|
+
class ExpireRecoveriesJob < ApplicationJob
|
|
11
|
+
def perform
|
|
12
|
+
Masks.configuration.expire_recoveries
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Masks
|
|
4
|
+
# @visibility private
|
|
5
|
+
class ActorMailer < ApplicationMailer
|
|
6
|
+
layout "masks/mailer"
|
|
7
|
+
|
|
8
|
+
def verify_email
|
|
9
|
+
@config = Masks.configuration
|
|
10
|
+
@email = params[:email]
|
|
11
|
+
|
|
12
|
+
mail(to: @email.email, subject: t(".subject"))
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def recover_credentials
|
|
16
|
+
@config = Masks.configuration
|
|
17
|
+
@recovery = @config.find_recovery(nil, id: params[:recovery])
|
|
18
|
+
|
|
19
|
+
mail(to: @recovery.to, subject: t(".subject"))
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Masks
|
|
4
|
+
# Concern to help with building access classes.
|
|
5
|
+
#
|
|
6
|
+
# Access classes must include this module, which will result in the class
|
|
7
|
+
# behaving like an +ActiveModel+ instance, with attributes, validations, and
|
|
8
|
+
# json serialization available by default.
|
|
9
|
+
#
|
|
10
|
+
# After including the module, classes must call +access+ to register the
|
|
11
|
+
# class and its canonical name.
|
|
12
|
+
#
|
|
13
|
+
# @example
|
|
14
|
+
# class ExampleAccess
|
|
15
|
+
# include Masks::Access
|
|
16
|
+
#
|
|
17
|
+
# access 'example'
|
|
18
|
+
#
|
|
19
|
+
# def say_hi
|
|
20
|
+
# puts 'hello world!'
|
|
21
|
+
# end
|
|
22
|
+
# end
|
|
23
|
+
#
|
|
24
|
+
# # Later, this class can be accessed with a valid session,
|
|
25
|
+
# # provided there is a corresponding mask that allows it.
|
|
26
|
+
# access = Masks.access('example', session)
|
|
27
|
+
# access.say_hi if access
|
|
28
|
+
#
|
|
29
|
+
# @see Masks::Access::ClassMethods
|
|
30
|
+
module Access
|
|
31
|
+
extend ActiveSupport::Concern
|
|
32
|
+
|
|
33
|
+
class << self
|
|
34
|
+
# @visibility private
|
|
35
|
+
def classes
|
|
36
|
+
@classes ||= {}
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# @visibility private
|
|
40
|
+
def register(name, **defaults)
|
|
41
|
+
classes[name.to_s] = defaults
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# @visibility private
|
|
45
|
+
def defaults(name)
|
|
46
|
+
classes.fetch(name.to_s, nil)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
included do
|
|
51
|
+
attr_accessor :session
|
|
52
|
+
|
|
53
|
+
def actor
|
|
54
|
+
raise Masks::Error::InvalidSession unless session
|
|
55
|
+
|
|
56
|
+
session.scoped || session.actor
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
delegate :configuration,
|
|
60
|
+
:roles,
|
|
61
|
+
:role?,
|
|
62
|
+
:role_records,
|
|
63
|
+
:scopes,
|
|
64
|
+
:scope?,
|
|
65
|
+
:params,
|
|
66
|
+
to: :session
|
|
67
|
+
|
|
68
|
+
delegate :access_config, to: :class
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Class methods for classes that include +Masks::Access+.
|
|
72
|
+
module ClassMethods
|
|
73
|
+
# Overrides `new` to ensure classes are constructed with proper access.
|
|
74
|
+
# @visibility private
|
|
75
|
+
def new(*args, **opts)
|
|
76
|
+
original =
|
|
77
|
+
if args[0].is_a?(Masks::Session)
|
|
78
|
+
args.delete_at(0)
|
|
79
|
+
elsif opts[:session]
|
|
80
|
+
opts.delete(:session)
|
|
81
|
+
else
|
|
82
|
+
raise Masks::Error::InvalidSession
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
model = original.config.model(:access)
|
|
86
|
+
session = model.new(name: access_name, original:)
|
|
87
|
+
session.mask!
|
|
88
|
+
|
|
89
|
+
raise Masks::Error::Unauthorized unless session.passed?
|
|
90
|
+
|
|
91
|
+
instance = super(*args, **opts)
|
|
92
|
+
instance.session = session
|
|
93
|
+
instance
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Returns the canonical access name for the class.
|
|
97
|
+
#
|
|
98
|
+
# @return [String]
|
|
99
|
+
def access_name
|
|
100
|
+
@access_name
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Registers the class by the supplied name.
|
|
104
|
+
#
|
|
105
|
+
# Any +opts+ provided will be used as defaults for instances,
|
|
106
|
+
# in addition to the data supplied in the app's configuration.
|
|
107
|
+
#
|
|
108
|
+
# This can be called multiple times. If names are the same, +opts+
|
|
109
|
+
# will be deep merged together. Different names can be supplied as
|
|
110
|
+
# well.
|
|
111
|
+
#
|
|
112
|
+
# @param [String] name
|
|
113
|
+
# @param [Hash] opts
|
|
114
|
+
# @return [nil]
|
|
115
|
+
def access(name, **opts)
|
|
116
|
+
@access_name = name
|
|
117
|
+
|
|
118
|
+
Masks::Access.register(access_name, **opts.merge(cls: self))
|
|
119
|
+
|
|
120
|
+
nil
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Returns the configuration for the class.
|
|
124
|
+
#
|
|
125
|
+
# This is a combination of data provided to +access+ along with whatever
|
|
126
|
+
# is specifed in the configuration object for the access class.
|
|
127
|
+
#
|
|
128
|
+
# @return [String]
|
|
129
|
+
def access_config
|
|
130
|
+
Masks.configuration.access(access_name)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Builds a new access class, given a variety of arguments.
|
|
134
|
+
#
|
|
135
|
+
# @overload build(request)
|
|
136
|
+
# @param [ActionDispatch::Request] request
|
|
137
|
+
# @overload build(controller)
|
|
138
|
+
# @param [ActionController::Metal] controller
|
|
139
|
+
# @overload build(session)
|
|
140
|
+
# @param [Masks::Session] session
|
|
141
|
+
# @overload build(actor)
|
|
142
|
+
# @param [Masks::Actor] actor
|
|
143
|
+
# @overload build(**attrs)
|
|
144
|
+
# @param [ActionController::Metal] controller
|
|
145
|
+
# @return [Masks::Access]
|
|
146
|
+
def build(arg1 = nil, **args)
|
|
147
|
+
case arg1
|
|
148
|
+
when ActionDispatch::Request
|
|
149
|
+
new(session: Masks::Sessions::Request.new(session: arg1))
|
|
150
|
+
when ActionController::Metal
|
|
151
|
+
new(session: arg1.send(:masked_session))
|
|
152
|
+
when Masks::Session
|
|
153
|
+
new(session: arg1, **args)
|
|
154
|
+
when Masks::Actor
|
|
155
|
+
new(session: Masks::Sessions::Actor.new(arg1, **opts))
|
|
156
|
+
when nil
|
|
157
|
+
new(**opts)
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Masks
|
|
4
|
+
# An interface that all masked actors should adhere to.
|
|
5
|
+
#
|
|
6
|
+
# @see Masks::Scoped Masks::Scoped
|
|
7
|
+
# @see Masks::Rails::Actor Masks::Rails::Actor
|
|
8
|
+
# @see Actors::Anonymous
|
|
9
|
+
# @see Actors::System
|
|
10
|
+
module Actor
|
|
11
|
+
include Scoped
|
|
12
|
+
|
|
13
|
+
# A unique identifier for the actor.
|
|
14
|
+
#
|
|
15
|
+
# This value is used for internal references to actors, e.g. when they are
|
|
16
|
+
# stored in the rails session.
|
|
17
|
+
#
|
|
18
|
+
# @return [String]
|
|
19
|
+
def actor_id
|
|
20
|
+
nickname
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# A session identifier for the actor.
|
|
24
|
+
#
|
|
25
|
+
# This value is used for internal references to actors, e.g. when they are
|
|
26
|
+
# stored in the rails session.
|
|
27
|
+
#
|
|
28
|
+
# @return [String]
|
|
29
|
+
def session_key
|
|
30
|
+
Digest::MD5.hexdigest(
|
|
31
|
+
[Masks.configuration.version, version, actor_id].join("-")
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# An internal version counter for the actor.
|
|
36
|
+
#
|
|
37
|
+
# Session keys use this value so they automatically expire when it changes.
|
|
38
|
+
#
|
|
39
|
+
# @return [String]
|
|
40
|
+
def version
|
|
41
|
+
raise NotImplementedError unless defined?(super)
|
|
42
|
+
|
|
43
|
+
super
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# A nickname for the actor.
|
|
47
|
+
#
|
|
48
|
+
# @return [String]
|
|
49
|
+
def nickname
|
|
50
|
+
raise NotImplementedError unless defined?(super)
|
|
51
|
+
|
|
52
|
+
super
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Sets a password for the actor.
|
|
56
|
+
#
|
|
57
|
+
# @param [String] password
|
|
58
|
+
# @return [String]
|
|
59
|
+
def password=(password)
|
|
60
|
+
raise NotImplementedError unless defined?(super)
|
|
61
|
+
|
|
62
|
+
super
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Sets the current session on the actor.
|
|
66
|
+
#
|
|
67
|
+
# @param [Masks::Session] session
|
|
68
|
+
# @return [String]
|
|
69
|
+
def session=(session)
|
|
70
|
+
raise NotImplementedError unless defined?(super)
|
|
71
|
+
|
|
72
|
+
super
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Validates the given password.
|
|
76
|
+
#
|
|
77
|
+
# @param [String] password
|
|
78
|
+
# @return [Boolean]
|
|
79
|
+
def authenticate(password)
|
|
80
|
+
raise NotImplementedError unless defined?(super)
|
|
81
|
+
|
|
82
|
+
super
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Returns a list of scopes granted to the actor.
|
|
86
|
+
#
|
|
87
|
+
# @return [Array<String>] An array of scopes (as strings)
|
|
88
|
+
def scopes
|
|
89
|
+
raise NotImplementedError
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# A callback that is invoked for masked sessions involving this actor.
|
|
93
|
+
#
|
|
94
|
+
# This method is only called when everything else—credentials, checks,
|
|
95
|
+
# and other mask rules—have passed. If this method returns a truthy value
|
|
96
|
+
# the session will pass, otherwise it will fail.
|
|
97
|
+
#
|
|
98
|
+
# This is a great place to add final checks, validations, and onboarding
|
|
99
|
+
# data to the actor before saving it.
|
|
100
|
+
#
|
|
101
|
+
# @return [Boolean]
|
|
102
|
+
def mask!
|
|
103
|
+
raise NotImplementedError
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Whether or not the actor requires a second layer of authentication.
|
|
107
|
+
#
|
|
108
|
+
# @return [Boolean]
|
|
109
|
+
def factor2?
|
|
110
|
+
false
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Whether or not the actor is anonymous, e.g. instantiated but un-identified.
|
|
114
|
+
#
|
|
115
|
+
# @return [Boolean]
|
|
116
|
+
def anonymous?
|
|
117
|
+
false
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Whether or not to save the results of any masked sessions involving this actor.
|
|
121
|
+
#
|
|
122
|
+
# Typically this is best controlled at the mask level, but there are some cases
|
|
123
|
+
# where it is useful to control at the actor-level. For example, the implementation
|
|
124
|
+
# for anonymous actors always returns `true` for this method, so that anonymous
|
|
125
|
+
# actors never end up in the rails session or other databases.
|
|
126
|
+
#
|
|
127
|
+
# @return [Boolean]
|
|
128
|
+
def backup?
|
|
129
|
+
!anonymous?
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Masks
|
|
4
|
+
# An interface that all configuration adapters should adhere to.
|
|
5
|
+
#
|
|
6
|
+
# @see Masks::Adapters::ActiveRecord
|
|
7
|
+
module Adapter
|
|
8
|
+
# Creates a new adapter.
|
|
9
|
+
#
|
|
10
|
+
# @param [Masks::Configuration] config
|
|
11
|
+
# @return [Masks::Adapter]
|
|
12
|
+
def initialize(config)
|
|
13
|
+
@config = config
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Returns an actor given the passed options, which may
|
|
17
|
+
# contain a nickname and/or an email.
|
|
18
|
+
#
|
|
19
|
+
# @param [Masks::Session] session
|
|
20
|
+
# @param [Hash] opts
|
|
21
|
+
# @return [Masks::Actor] or nil if not found
|
|
22
|
+
def find_actor(session, **opts)
|
|
23
|
+
raise NotImplementedError
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Returns a list of actors matching the passed actor_ids.
|
|
27
|
+
#
|
|
28
|
+
# @param [Masks::Session] session
|
|
29
|
+
# @param [Array] actor_ids
|
|
30
|
+
# @return [Array<Masks::Actor>]
|
|
31
|
+
def find_actors(session, actor_ids)
|
|
32
|
+
raise NotImplementedError
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Expires any outdated or invalid actors.
|
|
36
|
+
#
|
|
37
|
+
# @return
|
|
38
|
+
def expire_actors
|
|
39
|
+
raise NotImplementedError
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Builds an actor from the passed options, which may contain
|
|
43
|
+
# a nickname or email. Additional attributes, like a password,
|
|
44
|
+
# will not be supplied.
|
|
45
|
+
#
|
|
46
|
+
# @param [Masks::Session] session
|
|
47
|
+
# @param [Hash] opts
|
|
48
|
+
# @return [Masks::Actor]
|
|
49
|
+
def build_actor(session, **opts)
|
|
50
|
+
raise NotImplementedError
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# @visibility private
|
|
54
|
+
def find_recovery(session, **opts)
|
|
55
|
+
raise NotImplementedError
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# @visibility private
|
|
59
|
+
def build_recovery(session, **opts)
|
|
60
|
+
raise NotImplementedError
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# @visibility private
|
|
64
|
+
def expire_recoveries
|
|
65
|
+
raise NotImplementedError
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|