b4um_generators 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +30 -0
- data/README.de.md +338 -4
- data/README.md +330 -5
- data/lib/b4um_generators/version.rb +1 -1
- data/lib/generators/b4um/admin/admin_generator.rb +614 -0
- data/lib/generators/b4um/admin/templates/add_admin_fields_migration.rb.tt +10 -0
- data/lib/generators/b4um/admin/templates/admin_base_controller.rb.tt +5 -0
- data/lib/generators/b4um/admin/templates/admin_dashboard.html.erb.tt +19 -0
- data/lib/generators/b4um/admin/templates/admin_dashboard_controller.rb.tt +6 -0
- data/lib/generators/b4um/admin/templates/admin_users_controller.rb.tt +70 -0
- data/lib/generators/b4um/admin/templates/admin_users_index.html.erb.tt +110 -0
- data/lib/generators/b4um/admin/templates/b4um_admin.rake.tt +106 -0
- data/lib/generators/b4um/authentication/authentication_generator.rb +90 -1
- data/lib/generators/b4um/guest/guest_generator.rb +673 -0
- data/lib/generators/b4um/guest/templates/add_guest_fields_migration.rb.tt +12 -0
- data/lib/generators/b4um/guest/templates/b4um_guests.rake.tt +12 -0
- data/lib/generators/b4um/guest/templates/edit.html.erb.tt +156 -0
- data/lib/generators/b4um/guest/templates/guest_expiration_warning.html.erb.tt +12 -0
- data/lib/generators/b4um/guest/templates/guests_controller.rb.tt +88 -0
- data/lib/generators/b4um/guest/templates/new.html.erb.tt +136 -0
- data/lib/generators/b4um/help/help_generator.rb +162 -0
- data/lib/generators/b4um/scaffold/scaffold_generator.rb +50 -0
- data/lib/generators/b4um/scaffold/templates/_form.html.erb.tt +18 -14
- metadata +16 -1
|
@@ -0,0 +1,673 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
require "rails/generators/migration"
|
|
5
|
+
|
|
6
|
+
module B4um
|
|
7
|
+
module Generators
|
|
8
|
+
class GuestGenerator < Rails::Generators::NamedBase
|
|
9
|
+
include Rails::Generators::Migration
|
|
10
|
+
|
|
11
|
+
source_root File.expand_path("templates", __dir__)
|
|
12
|
+
|
|
13
|
+
class_option :required,
|
|
14
|
+
type: :string,
|
|
15
|
+
desc: "Fields required for guest access. Example: name,email"
|
|
16
|
+
|
|
17
|
+
class_option :expires_in,
|
|
18
|
+
type: :numeric,
|
|
19
|
+
default: 7,
|
|
20
|
+
desc: "Number of days before guest access expires"
|
|
21
|
+
|
|
22
|
+
def initialize(args, options, config)
|
|
23
|
+
super
|
|
24
|
+
|
|
25
|
+
validate_authentication_model
|
|
26
|
+
validate_authentication_setup
|
|
27
|
+
validate_required_fields
|
|
28
|
+
validate_expiration
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def create_guest_fields_migration
|
|
32
|
+
return if guest_fields_exist?
|
|
33
|
+
|
|
34
|
+
migration_template(
|
|
35
|
+
"add_guest_fields_migration.rb.tt",
|
|
36
|
+
"db/migrate/add_guest_fields_to_#{authentication_model_name.pluralize}.rb"
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.next_migration_number(dirname)
|
|
41
|
+
migration_number = Time.now.utc.strftime("%Y%m%d%H%M%S").to_i
|
|
42
|
+
|
|
43
|
+
existing_numbers =
|
|
44
|
+
Dir[File.join(dirname, "*.rb")]
|
|
45
|
+
.filter_map do |path|
|
|
46
|
+
File.basename(path)[/\A(\d+)_/, 1]&.to_i
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
migration_number += 1 while existing_numbers.include?(migration_number)
|
|
50
|
+
|
|
51
|
+
migration_number.to_s
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def add_guest_status_helpers
|
|
55
|
+
model_path =
|
|
56
|
+
"app/models/#{authentication_model_name}.rb"
|
|
57
|
+
|
|
58
|
+
full_model_path = File.join(
|
|
59
|
+
destination_root,
|
|
60
|
+
model_path
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
model = File.read(full_model_path)
|
|
64
|
+
|
|
65
|
+
missing_helpers = []
|
|
66
|
+
|
|
67
|
+
unless model.include?("def guest_expired?")
|
|
68
|
+
missing_helpers << <<~RUBY
|
|
69
|
+
def guest_expired?
|
|
70
|
+
guest? &&
|
|
71
|
+
guest_expires_at.present? &&
|
|
72
|
+
guest_expires_at <= Time.current
|
|
73
|
+
end
|
|
74
|
+
RUBY
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
unless model.include?("def guest_expiring_soon?")
|
|
78
|
+
missing_helpers << <<~RUBY
|
|
79
|
+
def guest_expiring_soon?
|
|
80
|
+
guest? &&
|
|
81
|
+
guest_expires_at.present? &&
|
|
82
|
+
!guest_expired? &&
|
|
83
|
+
guest_expires_at <= 24.hours.from_now
|
|
84
|
+
end
|
|
85
|
+
RUBY
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
return if missing_helpers.empty?
|
|
89
|
+
|
|
90
|
+
class_end_position = model.rindex(/^end\s*$/)
|
|
91
|
+
|
|
92
|
+
unless class_end_position
|
|
93
|
+
raise Thor::Error,
|
|
94
|
+
"Could not find the end of #{authentication_class_name}."
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
guest_helpers =
|
|
98
|
+
missing_helpers
|
|
99
|
+
.map { |helper| helper.lines.map { |line| " #{line}" }.join }
|
|
100
|
+
.join("\n")
|
|
101
|
+
|
|
102
|
+
model.insert(
|
|
103
|
+
class_end_position,
|
|
104
|
+
"\n#{guest_helpers}\n"
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
File.write(
|
|
108
|
+
full_model_path,
|
|
109
|
+
model
|
|
110
|
+
)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def add_guest_validations
|
|
114
|
+
model_path =
|
|
115
|
+
"app/models/#{authentication_model_name}.rb"
|
|
116
|
+
|
|
117
|
+
full_model_path = File.join(
|
|
118
|
+
destination_root,
|
|
119
|
+
model_path
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
model = File.read(full_model_path)
|
|
123
|
+
|
|
124
|
+
model.gsub!(
|
|
125
|
+
/^(\s*validates\s+:[^\n]+,\s+presence:\s+true)\s*$/,
|
|
126
|
+
'\1, unless: :guest?'
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
validations = guest_required_validations
|
|
130
|
+
|
|
131
|
+
missing_validations =
|
|
132
|
+
validations.reject do |validation|
|
|
133
|
+
model.include?(validation.strip)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
if missing_validations.any?
|
|
137
|
+
class_end_position = model.rindex(/^end\s*$/)
|
|
138
|
+
|
|
139
|
+
unless class_end_position
|
|
140
|
+
raise Thor::Error,
|
|
141
|
+
"Could not find the end of #{authentication_class_name}."
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
model.insert(
|
|
145
|
+
class_end_position,
|
|
146
|
+
"\n#{missing_validations.join}"
|
|
147
|
+
)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
File.write(
|
|
151
|
+
full_model_path,
|
|
152
|
+
model
|
|
153
|
+
)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def guest_required_validations
|
|
157
|
+
boolean_fields, presence_fields =
|
|
158
|
+
guest_required_fields.partition do |field|
|
|
159
|
+
guest_field_type(field) == :boolean
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
validations = []
|
|
163
|
+
|
|
164
|
+
if presence_fields.any?
|
|
165
|
+
fields =
|
|
166
|
+
presence_fields
|
|
167
|
+
.map { |field| ":#{field}" }
|
|
168
|
+
.join(", ")
|
|
169
|
+
|
|
170
|
+
validations << " validates #{fields}, " \
|
|
171
|
+
"presence: true, if: :guest?\n"
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
boolean_fields.each do |field|
|
|
175
|
+
validations << " validates :#{field}, " \
|
|
176
|
+
"acceptance: true, if: :guest?\n"
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
validations
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def add_guest_expiration_to_authentication
|
|
183
|
+
controller_path =
|
|
184
|
+
"app/controllers/application_controller.rb"
|
|
185
|
+
|
|
186
|
+
full_controller_path = File.join(
|
|
187
|
+
destination_root,
|
|
188
|
+
controller_path
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
controller = File.read(full_controller_path)
|
|
192
|
+
|
|
193
|
+
return if controller.include?(
|
|
194
|
+
"if #{authentication_model_name}&.guest_expired?"
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
simple_method_pattern = /
|
|
198
|
+
^\s*def\ #{Regexp.escape(current_authentication_method)}\n
|
|
199
|
+
\s+@#{Regexp.escape(current_authentication_method)}\s*\|\|=\s*
|
|
200
|
+
#{Regexp.escape(authentication_class_name)}\.find_by\(
|
|
201
|
+
id:\s*session\[:#{Regexp.escape(authentication_model_name)}_id\]
|
|
202
|
+
\)\n
|
|
203
|
+
\s*end
|
|
204
|
+
/x
|
|
205
|
+
|
|
206
|
+
blocked_method_pattern = /
|
|
207
|
+
^\s*def\ #{Regexp.escape(current_authentication_method)}\n
|
|
208
|
+
\s+@#{Regexp.escape(current_authentication_method)}\s*\|\|=\s*
|
|
209
|
+
#{Regexp.escape(authentication_class_name)}\.find_by\(
|
|
210
|
+
id:\s*session\[:#{Regexp.escape(authentication_model_name)}_id\]
|
|
211
|
+
\)\n
|
|
212
|
+
\s*
|
|
213
|
+
if\s+@#{Regexp.escape(current_authentication_method)}&\.blocked\?\n
|
|
214
|
+
\s+session\.delete\(
|
|
215
|
+
:#{Regexp.escape(authentication_model_name)}_id
|
|
216
|
+
\)\n
|
|
217
|
+
\s+@#{Regexp.escape(current_authentication_method)}\s*=\s*nil\n
|
|
218
|
+
\s*end\n
|
|
219
|
+
\s*
|
|
220
|
+
@#{Regexp.escape(current_authentication_method)}\n
|
|
221
|
+
\s*end
|
|
222
|
+
/x
|
|
223
|
+
|
|
224
|
+
if controller.match?(blocked_method_pattern)
|
|
225
|
+
replacement =
|
|
226
|
+
guest_aware_authentication_method(
|
|
227
|
+
include_blocked_check: true
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
pattern = blocked_method_pattern
|
|
231
|
+
elsif controller.match?(simple_method_pattern)
|
|
232
|
+
replacement =
|
|
233
|
+
guest_aware_authentication_method(
|
|
234
|
+
include_blocked_check: false
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
pattern = simple_method_pattern
|
|
238
|
+
else
|
|
239
|
+
raise Thor::Error,
|
|
240
|
+
"Could not update #{current_authentication_method} " \
|
|
241
|
+
"in app/controllers/application_controller.rb"
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
controller.sub!(
|
|
245
|
+
pattern,
|
|
246
|
+
replacement
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
File.write(
|
|
250
|
+
full_controller_path,
|
|
251
|
+
controller
|
|
252
|
+
)
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def add_guest_expiration_view_helper
|
|
256
|
+
controller_path =
|
|
257
|
+
"app/controllers/application_controller.rb"
|
|
258
|
+
|
|
259
|
+
full_controller_path = File.join(
|
|
260
|
+
destination_root,
|
|
261
|
+
controller_path
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
controller = File.read(full_controller_path)
|
|
265
|
+
|
|
266
|
+
return if controller.include?(
|
|
267
|
+
"helper_method :guest_expiring_soon?"
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
helper_code = <<~RUBY
|
|
271
|
+
|
|
272
|
+
helper_method :guest_expiring_soon?
|
|
273
|
+
|
|
274
|
+
def guest_expiring_soon?
|
|
275
|
+
#{current_authentication_method}&.guest_expiring_soon? || false
|
|
276
|
+
end
|
|
277
|
+
RUBY
|
|
278
|
+
|
|
279
|
+
class_end_position = controller.rindex(/^end\s*$/)
|
|
280
|
+
|
|
281
|
+
unless class_end_position
|
|
282
|
+
raise Thor::Error,
|
|
283
|
+
"Could not find the end of ApplicationController."
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
controller.insert(
|
|
287
|
+
class_end_position,
|
|
288
|
+
helper_code
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
File.write(
|
|
292
|
+
full_controller_path,
|
|
293
|
+
controller
|
|
294
|
+
)
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
def create_guest_cleanup_task
|
|
298
|
+
template(
|
|
299
|
+
"b4um_guests.rake.tt",
|
|
300
|
+
"lib/tasks/b4um_guests.rake"
|
|
301
|
+
)
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def create_guest_expiration_warning
|
|
305
|
+
template(
|
|
306
|
+
"guest_expiration_warning.html.erb.tt",
|
|
307
|
+
"app/views/shared/_guest_expiration_warning.html.erb"
|
|
308
|
+
)
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
def add_guest_expiration_warning_to_layout
|
|
312
|
+
layout_path =
|
|
313
|
+
"app/views/layouts/application.html.erb"
|
|
314
|
+
|
|
315
|
+
full_layout_path = File.join(
|
|
316
|
+
destination_root,
|
|
317
|
+
layout_path
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
return unless File.exist?(full_layout_path)
|
|
321
|
+
|
|
322
|
+
layout = File.read(full_layout_path)
|
|
323
|
+
|
|
324
|
+
warning_render =
|
|
325
|
+
'<%= render "shared/guest_expiration_warning" %>'
|
|
326
|
+
|
|
327
|
+
return if layout.include?(warning_render)
|
|
328
|
+
|
|
329
|
+
flash_render =
|
|
330
|
+
'<%= render "shared/flash" %>'
|
|
331
|
+
|
|
332
|
+
return unless layout.include?(flash_render)
|
|
333
|
+
|
|
334
|
+
layout.sub!(
|
|
335
|
+
flash_render,
|
|
336
|
+
"#{flash_render}\n #{warning_render}"
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
File.write(
|
|
340
|
+
full_layout_path,
|
|
341
|
+
layout
|
|
342
|
+
)
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
def create_guests_controller
|
|
346
|
+
template(
|
|
347
|
+
"guests_controller.rb.tt",
|
|
348
|
+
"app/controllers/guests_controller.rb"
|
|
349
|
+
)
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
def add_guest_routes
|
|
353
|
+
routes_path = File.join(
|
|
354
|
+
destination_root,
|
|
355
|
+
"config/routes.rb"
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
routes = File.read(routes_path)
|
|
359
|
+
|
|
360
|
+
guest_routes = [
|
|
361
|
+
'get "/guest", to: "guests#new", as: :guest',
|
|
362
|
+
'post "/guest", to: "guests#create"',
|
|
363
|
+
'delete "/guest", to: "guests#destroy"',
|
|
364
|
+
'get "/guest/convert", to: "guests#edit", as: :convert_guest',
|
|
365
|
+
'patch "/guest/convert", to: "guests#update"'
|
|
366
|
+
]
|
|
367
|
+
|
|
368
|
+
missing_routes = guest_routes.reject do |route|
|
|
369
|
+
routes.include?(route)
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
return if missing_routes.empty?
|
|
373
|
+
|
|
374
|
+
route_block_end = routes.rindex(/^end\s*$/)
|
|
375
|
+
|
|
376
|
+
unless route_block_end
|
|
377
|
+
raise Thor::Error,
|
|
378
|
+
"Could not find the routes block in config/routes.rb."
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
routes.insert(
|
|
382
|
+
route_block_end,
|
|
383
|
+
"#{missing_routes.join("\n")}\n"
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
File.write(
|
|
387
|
+
routes_path,
|
|
388
|
+
routes
|
|
389
|
+
)
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
def create_guest_form
|
|
393
|
+
template(
|
|
394
|
+
"new.html.erb.tt",
|
|
395
|
+
"app/views/guests/new.html.erb"
|
|
396
|
+
)
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
def create_guest_conversion_form
|
|
400
|
+
template(
|
|
401
|
+
"edit.html.erb.tt",
|
|
402
|
+
"app/views/guests/edit.html.erb"
|
|
403
|
+
)
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
private
|
|
407
|
+
|
|
408
|
+
def validate_authentication_model
|
|
409
|
+
model_path = File.join(
|
|
410
|
+
destination_root,
|
|
411
|
+
"app/models",
|
|
412
|
+
"#{authentication_model_name}.rb"
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
return if File.exist?(model_path)
|
|
416
|
+
|
|
417
|
+
raise Thor::Error,
|
|
418
|
+
"Authentication model #{authentication_class_name} was not found. " \
|
|
419
|
+
"Expected: app/models/#{authentication_model_name}.rb"
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
def validate_authentication_setup
|
|
423
|
+
application_controller_path = File.join(
|
|
424
|
+
destination_root,
|
|
425
|
+
"app/controllers/application_controller.rb"
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
authentication_helper =
|
|
429
|
+
"helper_method :#{current_authentication_method}, :logged_in?"
|
|
430
|
+
|
|
431
|
+
authentication_installed =
|
|
432
|
+
File.exist?(application_controller_path) &&
|
|
433
|
+
File.read(application_controller_path).include?(authentication_helper)
|
|
434
|
+
|
|
435
|
+
unless authentication_installed
|
|
436
|
+
raise Thor::Error,
|
|
437
|
+
"b4um authentication is not installed for " \
|
|
438
|
+
"#{authentication_class_name}. " \
|
|
439
|
+
"Run: bin/rails generate b4um:authentication " \
|
|
440
|
+
"#{authentication_class_name}"
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
application_controller =
|
|
444
|
+
File.read(application_controller_path)
|
|
445
|
+
|
|
446
|
+
current_user_assignment =
|
|
447
|
+
"@#{current_authentication_method} ||= " \
|
|
448
|
+
"#{authentication_class_name}.find_by(" \
|
|
449
|
+
"id: session[:#{authentication_model_name}_id])"
|
|
450
|
+
|
|
451
|
+
guest_aware_current_user =
|
|
452
|
+
"if #{authentication_model_name}&.guest_expired?"
|
|
453
|
+
|
|
454
|
+
unless application_controller.include?(current_user_assignment) ||
|
|
455
|
+
application_controller.include?(guest_aware_current_user)
|
|
456
|
+
raise Thor::Error,
|
|
457
|
+
"Could not find the current authentication method " \
|
|
458
|
+
"in app/controllers/application_controller.rb"
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
sessions_controller_path = File.join(
|
|
462
|
+
destination_root,
|
|
463
|
+
"app/controllers/sessions_controller.rb"
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
unless File.exist?(sessions_controller_path)
|
|
467
|
+
raise Thor::Error,
|
|
468
|
+
"SessionsController not found: " \
|
|
469
|
+
"app/controllers/sessions_controller.rb"
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
sessions_controller = File.read(sessions_controller_path)
|
|
473
|
+
|
|
474
|
+
session_assignment =
|
|
475
|
+
"session[:#{authentication_model_name}_id] = " \
|
|
476
|
+
"#{authentication_model_name}.id"
|
|
477
|
+
|
|
478
|
+
return if sessions_controller.include?(session_assignment)
|
|
479
|
+
|
|
480
|
+
raise Thor::Error,
|
|
481
|
+
"Could not find the authentication session assignment " \
|
|
482
|
+
"in app/controllers/sessions_controller.rb"
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
def validate_required_fields
|
|
486
|
+
required_fields = options[:required]
|
|
487
|
+
.to_s
|
|
488
|
+
.split(",")
|
|
489
|
+
.map(&:strip)
|
|
490
|
+
.reject(&:empty?)
|
|
491
|
+
|
|
492
|
+
if required_fields.empty?
|
|
493
|
+
raise Thor::Error,
|
|
494
|
+
"At least one required guest field must be specified."
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
unknown_fields = required_fields - guest_model_fields
|
|
498
|
+
|
|
499
|
+
unless unknown_fields.empty?
|
|
500
|
+
raise Thor::Error,
|
|
501
|
+
"Unknown required guest field: #{unknown_fields.first}"
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
invalid_fields = required_fields - guest_available_fields
|
|
505
|
+
|
|
506
|
+
return if invalid_fields.empty?
|
|
507
|
+
|
|
508
|
+
raise Thor::Error,
|
|
509
|
+
"Invalid required guest field: #{invalid_fields.first}"
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
def validate_expiration
|
|
513
|
+
return if options[:expires_in].to_i >= 1
|
|
514
|
+
|
|
515
|
+
raise Thor::Error,
|
|
516
|
+
"Guest expiration must be at least one day."
|
|
517
|
+
end
|
|
518
|
+
|
|
519
|
+
def guest_fields_exist?
|
|
520
|
+
fields = guest_model_fields
|
|
521
|
+
|
|
522
|
+
fields.include?("guest") &&
|
|
523
|
+
fields.include?("guest_expires_at")
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
def guest_model_fields
|
|
527
|
+
schema_path = File.join(
|
|
528
|
+
destination_root,
|
|
529
|
+
"db/schema.rb"
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
return [] unless File.exist?(schema_path)
|
|
533
|
+
|
|
534
|
+
schema = File.read(schema_path)
|
|
535
|
+
table_name = authentication_model_name.pluralize
|
|
536
|
+
|
|
537
|
+
table_match = schema.match(
|
|
538
|
+
/create_table "#{Regexp.escape(table_name)}".*?do \|t\|(.*?)^\s*end/m
|
|
539
|
+
)
|
|
540
|
+
|
|
541
|
+
return [] unless table_match
|
|
542
|
+
|
|
543
|
+
table_match[1].scan(
|
|
544
|
+
/^\s*t\.\w+\s+"([^"]+)"/
|
|
545
|
+
).flatten
|
|
546
|
+
end
|
|
547
|
+
|
|
548
|
+
def guest_available_fields
|
|
549
|
+
excluded_fields = %w[
|
|
550
|
+
id
|
|
551
|
+
admin
|
|
552
|
+
blocked
|
|
553
|
+
guest
|
|
554
|
+
guest_expires_at
|
|
555
|
+
created_at
|
|
556
|
+
updated_at
|
|
557
|
+
]
|
|
558
|
+
|
|
559
|
+
sensitive_field_pattern = /(?:password|token|secret|digest)/i
|
|
560
|
+
|
|
561
|
+
guest_model_fields.reject do |field|
|
|
562
|
+
excluded_fields.include?(field) ||
|
|
563
|
+
field.match?(sensitive_field_pattern)
|
|
564
|
+
end
|
|
565
|
+
end
|
|
566
|
+
|
|
567
|
+
def guest_required_fields
|
|
568
|
+
options[:required]
|
|
569
|
+
.to_s
|
|
570
|
+
.split(",")
|
|
571
|
+
.map(&:strip)
|
|
572
|
+
.reject(&:empty?)
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
def account_required_fields
|
|
576
|
+
model_path = File.join(
|
|
577
|
+
destination_root,
|
|
578
|
+
"app/models",
|
|
579
|
+
"#{authentication_model_name}.rb"
|
|
580
|
+
)
|
|
581
|
+
|
|
582
|
+
model = File.read(model_path)
|
|
583
|
+
|
|
584
|
+
model
|
|
585
|
+
.scan(
|
|
586
|
+
/validates\s+((?::[a-zA-Z_]\w*\s*,?\s*)+),\s*presence:\s+true/
|
|
587
|
+
)
|
|
588
|
+
.flat_map do |match|
|
|
589
|
+
match.first.scan(/:([a-zA-Z_]\w*)/).flatten
|
|
590
|
+
end
|
|
591
|
+
.uniq
|
|
592
|
+
end
|
|
593
|
+
|
|
594
|
+
def guest_field_type(field)
|
|
595
|
+
schema_path = File.join(
|
|
596
|
+
destination_root,
|
|
597
|
+
"db/schema.rb"
|
|
598
|
+
)
|
|
599
|
+
|
|
600
|
+
schema = File.read(schema_path)
|
|
601
|
+
table_name = authentication_model_name.pluralize
|
|
602
|
+
|
|
603
|
+
table_match = schema.match(
|
|
604
|
+
/create_table "#{Regexp.escape(table_name)}".*?do \|t\|(.*?)^\s*end/m
|
|
605
|
+
)
|
|
606
|
+
|
|
607
|
+
return :string unless table_match
|
|
608
|
+
|
|
609
|
+
field_match = table_match[1].match(
|
|
610
|
+
/^\s*t\.(\w+)\s+"#{Regexp.escape(field)}"/
|
|
611
|
+
)
|
|
612
|
+
|
|
613
|
+
return :string unless field_match
|
|
614
|
+
|
|
615
|
+
field_match[1].to_sym
|
|
616
|
+
end
|
|
617
|
+
|
|
618
|
+
def authentication_model_name
|
|
619
|
+
name.underscore
|
|
620
|
+
end
|
|
621
|
+
|
|
622
|
+
def authentication_class_name
|
|
623
|
+
name.camelize
|
|
624
|
+
end
|
|
625
|
+
|
|
626
|
+
def current_authentication_method
|
|
627
|
+
"current_#{authentication_model_name}"
|
|
628
|
+
end
|
|
629
|
+
|
|
630
|
+
def guest_aware_authentication_method(include_blocked_check:)
|
|
631
|
+
lines = [
|
|
632
|
+
"def #{current_authentication_method}",
|
|
633
|
+
" return @#{current_authentication_method} " \
|
|
634
|
+
"if defined?(@#{current_authentication_method})",
|
|
635
|
+
"",
|
|
636
|
+
" #{authentication_model_name} = " \
|
|
637
|
+
"#{authentication_class_name}.find_by(",
|
|
638
|
+
" id: session[:#{authentication_model_name}_id]",
|
|
639
|
+
" )",
|
|
640
|
+
"",
|
|
641
|
+
" if #{authentication_model_name}&.guest_expired?",
|
|
642
|
+
" session.delete(:#{authentication_model_name}_id)",
|
|
643
|
+
" #{authentication_model_name}.destroy!",
|
|
644
|
+
" @#{current_authentication_method} = nil",
|
|
645
|
+
" return nil",
|
|
646
|
+
" end"
|
|
647
|
+
]
|
|
648
|
+
|
|
649
|
+
if include_blocked_check
|
|
650
|
+
lines.push(
|
|
651
|
+
"",
|
|
652
|
+
" if #{authentication_model_name}&.blocked?",
|
|
653
|
+
" session.delete(:#{authentication_model_name}_id)",
|
|
654
|
+
" @#{current_authentication_method} = nil",
|
|
655
|
+
" return nil",
|
|
656
|
+
" end"
|
|
657
|
+
)
|
|
658
|
+
end
|
|
659
|
+
|
|
660
|
+
lines.push(
|
|
661
|
+
"",
|
|
662
|
+
" @#{current_authentication_method} = " \
|
|
663
|
+
"#{authentication_model_name}",
|
|
664
|
+
"end"
|
|
665
|
+
)
|
|
666
|
+
|
|
667
|
+
lines
|
|
668
|
+
.map { |line| line.empty? ? line : " #{line}" }
|
|
669
|
+
.join("\n")
|
|
670
|
+
end
|
|
671
|
+
end
|
|
672
|
+
end
|
|
673
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
class AddGuestFieldsTo<%= authentication_class_name.pluralize %> < ActiveRecord::Migration[8.0]
|
|
2
|
+
def change
|
|
3
|
+
<% unless guest_model_fields.include?("guest") -%>
|
|
4
|
+
add_column :<%= authentication_model_name.pluralize %>, :guest, :boolean,
|
|
5
|
+
default: false, null: false
|
|
6
|
+
<% end -%>
|
|
7
|
+
<% unless guest_model_fields.include?("guest_expires_at") -%>
|
|
8
|
+
add_column :<%= authentication_model_name.pluralize %>,
|
|
9
|
+
:guest_expires_at, :datetime
|
|
10
|
+
<% end -%>
|
|
11
|
+
end
|
|
12
|
+
end
|