masks 0.2.0

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