masks 0.4.0 → 0.5.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 (179) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +16 -0
  3. data/LICENSE +21 -0
  4. data/README.md +215 -29
  5. data/app/controllers/masks/rails/base_controller.rb +13 -0
  6. data/app/controllers/masks/rails/handshakes_controller.rb +87 -0
  7. data/app/controllers/masks/rails/sessions_controller.rb +113 -0
  8. data/app/views/layouts/masks/rails/plain.html.erb +47 -0
  9. data/app/views/masks/rails/handshakes/refused.html.erb +12 -0
  10. data/app/views/masks/rails/handshakes/show.html.erb +40 -0
  11. data/config/routes.rb +10 -82
  12. data/lib/generators/masks/install/install_generator.rb +35 -22
  13. data/lib/generators/masks/install/templates/masks.rb.tt +38 -0
  14. data/lib/masks/client/claims.rb +103 -0
  15. data/lib/masks/client/errors.rb +52 -0
  16. data/lib/masks/client/handshake.rb +104 -0
  17. data/lib/masks/client/http.rb +81 -0
  18. data/lib/masks/client/introspection.rb +29 -0
  19. data/lib/masks/client/issuer.rb +76 -0
  20. data/lib/masks/client/pkce.rb +27 -0
  21. data/lib/masks/client/rack.rb +62 -0
  22. data/lib/masks/client/registration.rb +91 -0
  23. data/lib/masks/client/registry.rb +25 -0
  24. data/lib/masks/client/resource.rb +92 -0
  25. data/lib/masks/client/session.rb +146 -0
  26. data/lib/masks/client/stores.rb +40 -0
  27. data/lib/masks/client/tokens.rb +70 -0
  28. data/lib/masks/client/tracker.rb +122 -0
  29. data/lib/masks/client/verifier.rb +42 -0
  30. data/lib/masks/client.rb +51 -0
  31. data/lib/masks/rails/authentication.rb +217 -0
  32. data/lib/masks/rails/configurable.rb +11 -0
  33. data/lib/masks/rails/configuration.rb +172 -0
  34. data/lib/masks/rails/credentials.rb +60 -0
  35. data/lib/masks/rails/engine.rb +20 -0
  36. data/lib/masks/rails/protected_resource.rb +51 -0
  37. data/lib/masks/rails.rb +10 -0
  38. data/lib/masks/version.rb +1 -3
  39. data/lib/masks.rb +3 -183
  40. metadata +47 -416
  41. data/MIT-LICENSE +0 -20
  42. data/Rakefile +0 -11
  43. data/app/assets/builds/masks/application.css +0 -1
  44. data/app/assets/builds/masks/application.js +0 -8549
  45. data/app/assets/builds/masks/application.js.map +0 -7
  46. data/app/assets/images/masks.png +0 -0
  47. data/app/assets/javascripts/application.js +0 -2
  48. data/app/assets/javascripts/controllers/application.js +0 -9
  49. data/app/assets/javascripts/controllers/emails_controller.js +0 -28
  50. data/app/assets/javascripts/controllers/index.js +0 -21
  51. data/app/assets/javascripts/controllers/keys_controller.js +0 -20
  52. data/app/assets/javascripts/controllers/recover_controller.js +0 -21
  53. data/app/assets/javascripts/controllers/recover_password_controller.js +0 -21
  54. data/app/assets/javascripts/controllers/session_controller.js +0 -94
  55. data/app/assets/javascripts/controllers/table_controller.js +0 -15
  56. data/app/assets/manifest.js +0 -2
  57. data/app/assets/masks_manifest.js +0 -2
  58. data/app/assets/stylesheets/application.css +0 -34
  59. data/app/controllers/concerns/masks/controller.rb +0 -114
  60. data/app/controllers/masks/actors_controller.rb +0 -15
  61. data/app/controllers/masks/application_controller.rb +0 -35
  62. data/app/controllers/masks/backup_codes_controller.rb +0 -34
  63. data/app/controllers/masks/debug_controller.rb +0 -9
  64. data/app/controllers/masks/devices_controller.rb +0 -20
  65. data/app/controllers/masks/emails_controller.rb +0 -60
  66. data/app/controllers/masks/error_controller.rb +0 -14
  67. data/app/controllers/masks/keys_controller.rb +0 -45
  68. data/app/controllers/masks/manage/actors_controller.rb +0 -83
  69. data/app/controllers/masks/manage/base_controller.rb +0 -20
  70. data/app/controllers/masks/manage/clients_controller.rb +0 -84
  71. data/app/controllers/masks/manage/dashboard_controller.rb +0 -15
  72. data/app/controllers/masks/manage/devices_controller.rb +0 -19
  73. data/app/controllers/masks/one_time_code_controller.rb +0 -49
  74. data/app/controllers/masks/openid/authorizations_controller.rb +0 -45
  75. data/app/controllers/masks/openid/discoveries_controller.rb +0 -55
  76. data/app/controllers/masks/openid/tokens_controller.rb +0 -45
  77. data/app/controllers/masks/openid/userinfo_controller.rb +0 -28
  78. data/app/controllers/masks/passwords_controller.rb +0 -33
  79. data/app/controllers/masks/recoveries_controller.rb +0 -43
  80. data/app/controllers/masks/sessions_controller.rb +0 -53
  81. data/app/helpers/masks/application_helper.rb +0 -49
  82. data/app/jobs/masks/application_job.rb +0 -7
  83. data/app/jobs/masks/expire_actors_job.rb +0 -15
  84. data/app/jobs/masks/expire_recoveries_job.rb +0 -15
  85. data/app/mailers/masks/actor_mailer.rb +0 -22
  86. data/app/mailers/masks/application_mailer.rb +0 -15
  87. data/app/models/concerns/masks/access.rb +0 -162
  88. data/app/models/concerns/masks/actor.rb +0 -132
  89. data/app/models/concerns/masks/adapter.rb +0 -68
  90. data/app/models/concerns/masks/role.rb +0 -9
  91. data/app/models/concerns/masks/scoped.rb +0 -54
  92. data/app/models/masks/access/actor_password.rb +0 -21
  93. data/app/models/masks/access/actor_scopes.rb +0 -18
  94. data/app/models/masks/access/actor_signup.rb +0 -21
  95. data/app/models/masks/actors/anonymous.rb +0 -40
  96. data/app/models/masks/actors/system.rb +0 -24
  97. data/app/models/masks/adapters/active_record.rb +0 -85
  98. data/app/models/masks/application_model.rb +0 -15
  99. data/app/models/masks/application_record.rb +0 -8
  100. data/app/models/masks/check.rb +0 -192
  101. data/app/models/masks/credential.rb +0 -166
  102. data/app/models/masks/credentials/access_token.rb +0 -60
  103. data/app/models/masks/credentials/backup_code.rb +0 -30
  104. data/app/models/masks/credentials/device.rb +0 -59
  105. data/app/models/masks/credentials/email.rb +0 -48
  106. data/app/models/masks/credentials/factor2.rb +0 -71
  107. data/app/models/masks/credentials/key.rb +0 -38
  108. data/app/models/masks/credentials/last_login.rb +0 -12
  109. data/app/models/masks/credentials/masquerade.rb +0 -32
  110. data/app/models/masks/credentials/nickname.rb +0 -63
  111. data/app/models/masks/credentials/one_time_code.rb +0 -34
  112. data/app/models/masks/credentials/password.rb +0 -28
  113. data/app/models/masks/credentials/recovery.rb +0 -71
  114. data/app/models/masks/credentials/return_to.rb +0 -27
  115. data/app/models/masks/credentials/session.rb +0 -67
  116. data/app/models/masks/device.rb +0 -30
  117. data/app/models/masks/error.rb +0 -51
  118. data/app/models/masks/event.rb +0 -14
  119. data/app/models/masks/mask.rb +0 -266
  120. data/app/models/masks/openid/authorization.rb +0 -116
  121. data/app/models/masks/openid/token.rb +0 -56
  122. data/app/models/masks/rails/actor.rb +0 -212
  123. data/app/models/masks/rails/actor_role.rb +0 -12
  124. data/app/models/masks/rails/device.rb +0 -47
  125. data/app/models/masks/rails/email.rb +0 -96
  126. data/app/models/masks/rails/key.rb +0 -61
  127. data/app/models/masks/rails/openid/access_token.rb +0 -55
  128. data/app/models/masks/rails/openid/authorization.rb +0 -45
  129. data/app/models/masks/rails/openid/client.rb +0 -186
  130. data/app/models/masks/rails/openid/id_token.rb +0 -43
  131. data/app/models/masks/rails/recovery.rb +0 -116
  132. data/app/models/masks/rails/role.rb +0 -20
  133. data/app/models/masks/rails/scope.rb +0 -15
  134. data/app/models/masks/session.rb +0 -447
  135. data/app/models/masks/sessions/access.rb +0 -27
  136. data/app/models/masks/sessions/inline.rb +0 -16
  137. data/app/models/masks/sessions/request.rb +0 -42
  138. data/app/resources/masks/actor_resource.rb +0 -9
  139. data/app/resources/masks/session_resource.rb +0 -15
  140. data/app/views/layouts/masks/application.html.erb +0 -17
  141. data/app/views/layouts/masks/mailer.html.erb +0 -17
  142. data/app/views/layouts/masks/mailer.text.erb +0 -1
  143. data/app/views/layouts/masks/manage.html.erb +0 -42
  144. data/app/views/masks/actor_mailer/recover_credentials.html.erb +0 -32
  145. data/app/views/masks/actor_mailer/recover_credentials.text.erb +0 -1
  146. data/app/views/masks/actor_mailer/verify_email.html.erb +0 -33
  147. data/app/views/masks/actor_mailer/verify_email.text.erb +0 -8
  148. data/app/views/masks/actors/current.html.erb +0 -145
  149. data/app/views/masks/application/_header.html.erb +0 -30
  150. data/app/views/masks/backup_codes/new.html.erb +0 -117
  151. data/app/views/masks/emails/new.html.erb +0 -109
  152. data/app/views/masks/emails/verify.html.erb +0 -51
  153. data/app/views/masks/keys/new.html.erb +0 -127
  154. data/app/views/masks/manage/actors/index.html.erb +0 -104
  155. data/app/views/masks/manage/actors/show.html.erb +0 -172
  156. data/app/views/masks/manage/clients/index.html.erb +0 -102
  157. data/app/views/masks/manage/clients/show.html.erb +0 -156
  158. data/app/views/masks/manage/dashboard/index.html.erb +0 -10
  159. data/app/views/masks/manage/devices/index.html.erb +0 -47
  160. data/app/views/masks/one_time_code/new.html.erb +0 -167
  161. data/app/views/masks/openid/authorizations/error.html.erb +0 -23
  162. data/app/views/masks/openid/authorizations/new.html.erb +0 -46
  163. data/app/views/masks/passwords/edit.html.erb +0 -71
  164. data/app/views/masks/recoveries/new.html.erb +0 -69
  165. data/app/views/masks/recoveries/password.html.erb +0 -63
  166. data/app/views/masks/sessions/new.html.erb +0 -152
  167. data/config/brakeman.ignore +0 -28
  168. data/config/initializers/inflections.rb +0 -5
  169. data/config/locales/en.yml +0 -307
  170. data/db/migrate/20231205173845_create_actors.rb +0 -94
  171. data/db/migrate/20240329182422_support_openid.rb +0 -64
  172. data/lib/generators/masks/install/USAGE +0 -8
  173. data/lib/generators/masks/install/templates/initializer.rb +0 -5
  174. data/lib/generators/masks/install/templates/masks.json +0 -9
  175. data/lib/masks/configuration.rb +0 -249
  176. data/lib/masks/engine.rb +0 -25
  177. data/lib/masks/middleware.rb +0 -70
  178. data/lib/tasks/masks_tasks.rake +0 -72
  179. data/masks.json +0 -315
@@ -1,249 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "set"
4
- require "recursive-open-struct"
5
-
6
- module Masks
7
- # Container for masks configuration data.
8
- class Configuration
9
- include ActiveModel::Model
10
- include ActiveModel::Validations
11
- include ActiveModel::Attributes
12
- include ActiveModel::Serializers::JSON
13
-
14
- attribute :data
15
- attribute :name
16
- attribute :adapter, default: "Masks::Adapters::ActiveRecord"
17
- attribute :theme
18
- attribute :site_name
19
- attribute :site_url
20
- attribute :site_links
21
- attribute :site_logo
22
- attribute :lifetimes
23
- attribute :openid
24
- attribute :masks
25
- attribute :models
26
- attribute :version
27
-
28
- # Returns all configuration data as +RecursiveOpenStruct+.
29
- #
30
- # @return RecursiveOpenStruct
31
- def dat
32
- RecursiveOpenStruct.new(data)
33
- end
34
-
35
- # Returns a string to use as the "issuer" for various secrets—TOTP, JWT, etc.
36
- # @return [String]
37
- def issuer
38
- data.fetch(:name, "masks").parameterize
39
- end
40
-
41
- # Returns a site theme to use on the frontend.
42
- # @return [String]
43
- def theme
44
- super || data.fetch(:theme, "dark")
45
- end
46
-
47
- # Returns the site logo, used for visual identification of the site.
48
- # @return [String]
49
- def site_logo
50
- super || data.fetch(:logo, nil)
51
- end
52
-
53
- # Returns the site name, used for referring to the site (e.g. in the logo).
54
- # @return [String]
55
- def site_name
56
- super || data.fetch(:name, "masks")
57
- end
58
-
59
- # Returns the site title, used primarily in meta tags.
60
- # @return [String]
61
- def site_title
62
- super || data.fetch(:title, "")
63
- end
64
-
65
- # Returns the canonical url for the site.
66
- # @return [String]
67
- def site_url
68
- super || data.fetch(:url, nil)
69
- end
70
-
71
- # Returns a string to use as the "issuer" for various secrets—TOTP, JWT, etc.
72
- # @return [String]
73
- def openid
74
- {
75
- scopes: %w[openid profile email address phone],
76
- subject_types: %w[nickname email pairwise],
77
- response_types: %w[code token id_token],
78
- grant_types: %w[
79
- client_credentials
80
- authorization_code
81
- implicit
82
- refresh_token
83
- ],
84
- pairwise_salt: "masks"
85
- }.merge(super || data.fetch(:openid, {}))
86
- end
87
-
88
- # A hash of links—urls to various places on the frontend.
89
- #
90
- # These default to generated rails routes, but can be overridden
91
- # where necessary.
92
- #
93
- # @return [Hash]
94
- def site_links
95
- {
96
- root: rails_url.try(:root_path) || "/",
97
- recover: rails_url.recover_path,
98
- login: rails_url.session_path,
99
- after_login: rails_url.session_path,
100
- after_logout: rails_url.session_path
101
- }.merge(super || data.fetch(:links, {}))
102
- end
103
-
104
- # Returns the current global session key.
105
- # @return [String]
106
- def version
107
- super || "v1"
108
- end
109
-
110
- # A hash of expiration times for various resources.
111
- #
112
- # These values are used to override expiration times for things like
113
- # password reset emails and other time-based authentication methods.
114
- #
115
- # @return [Hash]
116
- def lifetimes
117
- (super || dat.lifetimes&.to_h || {}).transform_values do |seconds|
118
- seconds.to_i.seconds
119
- end
120
- end
121
-
122
- # A hash of default models the app relies on.
123
- #
124
- # This makes it easy to provide a substitute for key models
125
- # while still relying on the base active record implementation.
126
- #
127
- # @return [Hash{Symbol => String}]
128
- def models
129
- {
130
- actor: "Masks::Rails::Actor",
131
- scope: "Masks::Rails::Scope",
132
- role: "Masks::Rails::Role",
133
- email: "Masks::Rails::Email",
134
- recovery: "Masks::Rails::Recovery",
135
- device: "Masks::Rails::Device",
136
- key: "Masks::Rails::Key",
137
- openid_client: "Masks::Rails::OpenID::Client",
138
- openid_access_token: "Masks::Rails::OpenID::AccessToken",
139
- openid_id_token: "Masks::Rails::OpenID::IdToken",
140
- openid_authorization: "Masks::Rails::OpenID::Authorization",
141
- session_json: "Masks::SessionResource",
142
- request: "Masks::Sessions::Request",
143
- inline: "Masks::Sessions::Inline",
144
- access: "Masks::Sessions::Access"
145
- }.merge(super || data.fetch(:models, {}))
146
- end
147
-
148
- # Returns a model +Class+.
149
- #
150
- # @return [Class]
151
- def model(name)
152
- models[name]&.constantize
153
- end
154
-
155
- # Returns configuration data for a given mask type.
156
- #
157
- # @param [String] type
158
- # @return [Hash]
159
- def mask(type)
160
- config = data.dig(:types, type.to_sym)
161
- raise Masks::Error::InvalidConfiguration, type unless config
162
- config
163
- end
164
-
165
- # Returns all configured masks.
166
- #
167
- # @return [Array<Masks::Mask>]
168
- def masks
169
- @masks ||=
170
- begin
171
- masks = super || data.fetch(:masks, [])
172
- masks.map do |opts|
173
- case opts
174
- when Masks::Mask
175
- opts
176
- when Hash
177
- Masks::Mask.new(opts.merge(config: self))
178
- end
179
- end
180
- end
181
- end
182
-
183
- # Returns configuration data for the given access name.
184
- # @param [String] name
185
- # @return [Hash]
186
- def access(name)
187
- defaults = Masks::Access.defaults(name)
188
- raise Masks::Error::UnknownAccess, name unless defaults
189
-
190
- defaults.deep_merge(access_types&.fetch(name, {}))
191
- end
192
-
193
- # Returns configuration data for all access types.
194
- #
195
- # This does not include defaults created with +Masks::Access.access+.
196
- #
197
- # @return [Hash]
198
- def access_types
199
- data
200
- .fetch(:access, {})
201
- .to_h do |name, opts|
202
- [name, opts.merge(name:, cls: opts[:cls]&.constantize)]
203
- end
204
- end
205
-
206
- # Setter for whether or not sigups are allowed.
207
- #
208
- # @param [Boolean] value
209
- def signups=(value)
210
- data[:signups] = value
211
- end
212
-
213
- # Whether or not sigups are allowed.
214
- #
215
- # @return [Boolean]
216
- def signups?
217
- data[:signups]
218
- end
219
-
220
- delegate :find_key,
221
- :find_actor,
222
- :find_actors,
223
- :build_actor,
224
- :find_device,
225
- :find_recovery,
226
- :build_recovery,
227
- :expire_actors,
228
- :expire_recoveries,
229
- to: :adapt
230
-
231
- private
232
-
233
- def rails_url
234
- Masks::Engine.routes.url_helpers
235
- end
236
-
237
- def adapt
238
- @adapt =
239
- case adapter
240
- when String
241
- adapter.constantize.new(self)
242
- when Class
243
- adapter.new(self)
244
- else
245
- raise Masks::Error::InvalidConfiguration, adapter
246
- end
247
- end
248
- end
249
- end
data/lib/masks/engine.rb DELETED
@@ -1,25 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "middleware"
4
- module Masks
5
- # The Rails engine/railtie for Masks.
6
- #
7
- # Adds two initializers:
8
- #
9
- # - masks.assets.precompile - ensures masks assets are precompiled and added to the Rails asset pipeline
10
- # - masks.middleware - appends +Masks::Middleware+ to the middleware chain
11
- #
12
- class Engine < ::Rails::Engine
13
- isolate_namespace Masks
14
-
15
- initializer "masks.assets.precompile" do |app|
16
- app.config.assets.precompile += %w[masks_manifest.js] if app.config.try(
17
- :assets
18
- )
19
- end
20
-
21
- initializer "masks.middleware" do |app|
22
- app.config.app_middleware.use Masks::Middleware
23
- end
24
- end
25
- end
@@ -1,70 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Masks
4
- # Integrate masks with Rails, via middleware.
5
- #
6
- # This middleware creates a new session for every request using
7
- # +Masks.request+. If the session is +passed?+ the middleware
8
- # chain is allowed to continue.
9
- #
10
- # The middleware uses the +fail+ value on the session's mask to
11
- # determine what to do otherwise. If +false+ the chain is allowed
12
- # to continue regardless of pass/fail status. It will also accept
13
- # a URI or path for redirects, an HTTP status code, or a controller
14
- # action in the form of +ControllerClass#action_name+.
15
- #
16
- # The session is stored in the rack environment under the name
17
- # +Mask::Middleware::SESSION_KEY+, which is how it is made available to
18
- # controllers in +Masks::Controller#masked_session+.
19
- #
20
- # @see Masks.request Masks.request
21
- # @see Masks::Session Masks::Session
22
- # @see Masks::Controller Masks::Controller
23
- class Middleware
24
- SESSION_KEY = "masks.session"
25
-
26
- def initialize(app)
27
- @app = app
28
- end
29
-
30
- def call(env)
31
- request = ActionDispatch::Request.new(env)
32
- session = Masks.request(request)
33
-
34
- ::Rails.logger.info(
35
- [
36
- "Mask #{session.passed? ? "allowed" : "blocked"}",
37
- "by type=#{session.mask.type}",
38
- session.mask.name ? "name=#{session.mask.name}" : ""
39
- ].join(" ")
40
- )
41
-
42
- env[SESSION_KEY] = session
43
- app = @app
44
- app = (session.passed? ? app : error_app(session, app))
45
-
46
- app.call(env)
47
- end
48
-
49
- private
50
-
51
- def error_app(session, app)
52
- value = session.mask.fail
53
- case value
54
- when false
55
- app
56
- when /.+\#.+/
57
- controller, action = value.split("#")
58
- controller.constantize.action(action)
59
- when %r{/.*}, %r{https?://.+}
60
- Masks::ErrorController.action(:redirect)
61
- when /\d+/
62
- Masks::ErrorController.action("render_#{value}")
63
- when /\w+/
64
- Masks::ErrorController.action(value)
65
- else
66
- Masks::ErrorController.action(:render_unauthorized)
67
- end
68
- end
69
- end
70
- end
@@ -1,72 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- def cli_access(access_name, **opts)
4
- access = nil
5
- actor = opts.delete(:as) || ENV["ACTOR"] || Masks::ANON
6
-
7
- Masks.mask("cli", as: actor, **opts) do |session|
8
- access = session.access(access_name)
9
- end
10
-
11
- access
12
- end
13
-
14
- namespace :masks do
15
- task boot: :environment do
16
- # required to ensure access classes (and others) are registered properly
17
- Rails.application.eager_load!
18
- end
19
-
20
- task :signup, %i[nickname password] => :boot do |_task, args|
21
- access = cli_access("actor.signup", args:)
22
- signup = access.signup(**args) if access
23
-
24
- if !access
25
- puts "could not find actor..."
26
- elsif signup.invalid?
27
- puts "failed to signup '#{signup.nickname}'"
28
- puts "error: #{signup.errors.full_messages.join(", ")}"
29
- else
30
- puts "created '#{signup.nickname}'!"
31
- end
32
- end
33
-
34
- task :assign_scopes, %i[actor scopes] => :boot do |_task, args|
35
- if !args[:actor] || !args[:scopes]
36
- puts "specify an actor and scopes, e.g. masks:assign_scopes[@foo,scope1;scope2;scope3]"
37
- exit 1
38
- end
39
-
40
- scopes = args[:scopes].split(";")
41
- access = cli_access("actor.scopes", as: args[:actor])
42
- access&.assign_scopes(scopes)
43
-
44
- if !access
45
- puts "could not find actor..."
46
- elsif access.actor.invalid?
47
- puts "failed to assign scopes to '#{access.actor.nickname}'"
48
- puts "error: #{access.actor.errors.full_messages.join(", ")}"
49
- else
50
- puts "assigned scopes '#{scopes.join(", ")}' to '#{access.actor.nickname}'"
51
- end
52
- end
53
-
54
- task :change_password, %i[actor password] => :boot do |_task, args|
55
- if !args[:actor] || !args[:password]
56
- puts "specify an actor and password, e.g. masks:change_password[@foo,password]"
57
- exit 1
58
- end
59
-
60
- access = cli_access("actor.password", as: args[:actor])
61
- access&.change_password(args[:password])
62
-
63
- if !access
64
- puts "could not find actor..."
65
- elsif access.actor.invalid?
66
- puts "failed to change password for '#{access.actor.nickname}'"
67
- puts "error: #{access.actor.errors.full_messages.join(", ")}"
68
- else
69
- puts "changed password for '#{access.actor.nickname}'"
70
- end
71
- end
72
- end