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.
Files changed (126) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +50 -0
  4. data/Rakefile +11 -0
  5. data/app/assets/builds/application.css +4764 -0
  6. data/app/assets/builds/application.js +8236 -0
  7. data/app/assets/builds/application.js.map +7 -0
  8. data/app/assets/builds/masks/application.css +1 -0
  9. data/app/assets/builds/masks/application.js +7122 -0
  10. data/app/assets/builds/masks/application.js.map +7 -0
  11. data/app/assets/images/masks.png +0 -0
  12. data/app/assets/javascripts/application.js +2 -0
  13. data/app/assets/javascripts/controllers/application.js +9 -0
  14. data/app/assets/javascripts/controllers/emails_controller.js +28 -0
  15. data/app/assets/javascripts/controllers/index.js +12 -0
  16. data/app/assets/javascripts/controllers/keys_controller.js +20 -0
  17. data/app/assets/javascripts/controllers/recover_controller.js +21 -0
  18. data/app/assets/javascripts/controllers/recover_password_controller.js +21 -0
  19. data/app/assets/javascripts/controllers/session_controller.js +94 -0
  20. data/app/assets/manifest.js +2 -0
  21. data/app/assets/masks_manifest.js +2 -0
  22. data/app/assets/stylesheets/application.css +26 -0
  23. data/app/controllers/concerns/masks/controller.rb +114 -0
  24. data/app/controllers/masks/actors_controller.rb +15 -0
  25. data/app/controllers/masks/application_controller.rb +35 -0
  26. data/app/controllers/masks/backup_codes_controller.rb +34 -0
  27. data/app/controllers/masks/debug_controller.rb +9 -0
  28. data/app/controllers/masks/devices_controller.rb +20 -0
  29. data/app/controllers/masks/emails_controller.rb +60 -0
  30. data/app/controllers/masks/error_controller.rb +14 -0
  31. data/app/controllers/masks/keys_controller.rb +45 -0
  32. data/app/controllers/masks/manage/actor_controller.rb +35 -0
  33. data/app/controllers/masks/manage/actors_controller.rb +12 -0
  34. data/app/controllers/masks/manage/base_controller.rb +12 -0
  35. data/app/controllers/masks/one_time_code_controller.rb +49 -0
  36. data/app/controllers/masks/passwords_controller.rb +33 -0
  37. data/app/controllers/masks/recoveries_controller.rb +43 -0
  38. data/app/controllers/masks/sessions_controller.rb +53 -0
  39. data/app/helpers/masks/application_helper.rb +49 -0
  40. data/app/jobs/masks/application_job.rb +7 -0
  41. data/app/jobs/masks/expire_actors_job.rb +15 -0
  42. data/app/jobs/masks/expire_recoveries_job.rb +15 -0
  43. data/app/mailers/masks/actor_mailer.rb +22 -0
  44. data/app/mailers/masks/application_mailer.rb +15 -0
  45. data/app/models/concerns/masks/access.rb +162 -0
  46. data/app/models/concerns/masks/actor.rb +132 -0
  47. data/app/models/concerns/masks/adapter.rb +68 -0
  48. data/app/models/concerns/masks/role.rb +9 -0
  49. data/app/models/concerns/masks/scoped.rb +54 -0
  50. data/app/models/masks/access/actor_password.rb +20 -0
  51. data/app/models/masks/access/actor_scopes.rb +18 -0
  52. data/app/models/masks/access/actor_signup.rb +22 -0
  53. data/app/models/masks/actors/anonymous.rb +40 -0
  54. data/app/models/masks/actors/system.rb +24 -0
  55. data/app/models/masks/adapters/active_record.rb +85 -0
  56. data/app/models/masks/application_model.rb +15 -0
  57. data/app/models/masks/application_record.rb +8 -0
  58. data/app/models/masks/check.rb +192 -0
  59. data/app/models/masks/credential.rb +166 -0
  60. data/app/models/masks/credentials/backup_code.rb +30 -0
  61. data/app/models/masks/credentials/device.rb +59 -0
  62. data/app/models/masks/credentials/email.rb +48 -0
  63. data/app/models/masks/credentials/factor2.rb +71 -0
  64. data/app/models/masks/credentials/key.rb +38 -0
  65. data/app/models/masks/credentials/last_login.rb +12 -0
  66. data/app/models/masks/credentials/masquerade.rb +32 -0
  67. data/app/models/masks/credentials/nickname.rb +63 -0
  68. data/app/models/masks/credentials/one_time_code.rb +34 -0
  69. data/app/models/masks/credentials/password.rb +28 -0
  70. data/app/models/masks/credentials/recovery.rb +71 -0
  71. data/app/models/masks/credentials/session.rb +67 -0
  72. data/app/models/masks/device.rb +30 -0
  73. data/app/models/masks/error.rb +51 -0
  74. data/app/models/masks/event.rb +14 -0
  75. data/app/models/masks/mask.rb +255 -0
  76. data/app/models/masks/rails/actor.rb +190 -0
  77. data/app/models/masks/rails/actor_role.rb +12 -0
  78. data/app/models/masks/rails/device.rb +47 -0
  79. data/app/models/masks/rails/email.rb +96 -0
  80. data/app/models/masks/rails/key.rb +61 -0
  81. data/app/models/masks/rails/recovery.rb +116 -0
  82. data/app/models/masks/rails/role.rb +20 -0
  83. data/app/models/masks/rails/scope.rb +15 -0
  84. data/app/models/masks/session.rb +447 -0
  85. data/app/models/masks/sessions/access.rb +26 -0
  86. data/app/models/masks/sessions/inline.rb +16 -0
  87. data/app/models/masks/sessions/request.rb +42 -0
  88. data/app/resources/masks/actor_resource.rb +9 -0
  89. data/app/resources/masks/session_resource.rb +15 -0
  90. data/app/views/layouts/masks/application.html.erb +17 -0
  91. data/app/views/layouts/masks/mailer.html.erb +17 -0
  92. data/app/views/layouts/masks/mailer.text.erb +1 -0
  93. data/app/views/layouts/masks/manage.html.erb +25 -0
  94. data/app/views/masks/actor_mailer/recover_credentials.html.erb +33 -0
  95. data/app/views/masks/actor_mailer/recover_credentials.text.erb +1 -0
  96. data/app/views/masks/actor_mailer/verify_email.html.erb +34 -0
  97. data/app/views/masks/actor_mailer/verify_email.text.erb +8 -0
  98. data/app/views/masks/actors/current.html.erb +152 -0
  99. data/app/views/masks/application/_header.html.erb +31 -0
  100. data/app/views/masks/backup_codes/new.html.erb +103 -0
  101. data/app/views/masks/emails/new.html.erb +103 -0
  102. data/app/views/masks/emails/verify.html.erb +51 -0
  103. data/app/views/masks/keys/new.html.erb +127 -0
  104. data/app/views/masks/manage/actor/show.html.erb +126 -0
  105. data/app/views/masks/manage/actors/index.html.erb +40 -0
  106. data/app/views/masks/one_time_code/new.html.erb +150 -0
  107. data/app/views/masks/passwords/edit.html.erb +58 -0
  108. data/app/views/masks/recoveries/new.html.erb +71 -0
  109. data/app/views/masks/recoveries/password.html.erb +64 -0
  110. data/app/views/masks/sessions/new.html.erb +153 -0
  111. data/config/brakeman.ignore +28 -0
  112. data/config/locales/en.yml +286 -0
  113. data/config/routes.rb +46 -0
  114. data/db/migrate/20231205173845_create_actors.rb +94 -0
  115. data/lib/generators/masks/install/USAGE +8 -0
  116. data/lib/generators/masks/install/install_generator.rb +33 -0
  117. data/lib/generators/masks/install/templates/initializer.rb +5 -0
  118. data/lib/generators/masks/install/templates/masks.json +6 -0
  119. data/lib/masks/configuration.rb +236 -0
  120. data/lib/masks/engine.rb +25 -0
  121. data/lib/masks/middleware.rb +70 -0
  122. data/lib/masks/version.rb +5 -0
  123. data/lib/masks.rb +183 -0
  124. data/lib/tasks/masks_tasks.rake +71 -0
  125. data/masks.json +274 -0
  126. 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,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Masks
4
+ # @visibility private
5
+ class ApplicationJob < ActiveJob::Base
6
+ end
7
+ 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,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Masks
4
+ # @visibility private
5
+ class ApplicationMailer < ActionMailer::Base
6
+ default from: "from@example.com"
7
+ layout "mailer"
8
+
9
+ helper do
10
+ def logged_in?
11
+ false
12
+ end
13
+ end
14
+ end
15
+ 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
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Masks
4
+ # An interface that all roles should adhere to.
5
+ #
6
+ # @see Masks::Rails::Role Masks::Rails::Role
7
+ module Role
8
+ end
9
+ end