masks 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|