rockstart 0.1.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 (97) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +100 -0
  4. data/Rakefile +19 -0
  5. data/lib/generators/rockstart/USAGE +13 -0
  6. data/lib/generators/rockstart/devise/USAGE +9 -0
  7. data/lib/generators/rockstart/devise/devise_generator.rb +258 -0
  8. data/lib/generators/rockstart/devise/templates/controllers/passwords_controller.rb +56 -0
  9. data/lib/generators/rockstart/devise/templates/controllers/registrations_controller.rb +88 -0
  10. data/lib/generators/rockstart/devise/templates/controllers/sessions_controller.rb +32 -0
  11. data/lib/generators/rockstart/devise/templates/create_user_migration.rb.tt +11 -0
  12. data/lib/generators/rockstart/devise/templates/models/user.rb +42 -0
  13. data/lib/generators/rockstart/devise/templates/spec/factories/users.rb +17 -0
  14. data/lib/generators/rockstart/devise/templates/spec/models/user_spec.rb +64 -0
  15. data/lib/generators/rockstart/devise/templates/spec/requests/users/passwords_spec.rb +202 -0
  16. data/lib/generators/rockstart/devise/templates/spec/requests/users/registrations_spec.rb +445 -0
  17. data/lib/generators/rockstart/devise/templates/spec/requests/users/sessions_spec.rb +171 -0
  18. data/lib/generators/rockstart/devise/templates/spec/support/devise_request_spec_helper.rb +29 -0
  19. data/lib/generators/rockstart/devise/templates/translations.en.yml +4 -0
  20. data/lib/generators/rockstart/docker/USAGE +10 -0
  21. data/lib/generators/rockstart/docker/docker_generator.rb +86 -0
  22. data/lib/generators/rockstart/docker/templates/app/Dockerfile-app +47 -0
  23. data/lib/generators/rockstart/docker/templates/docker-compose.test.yml +29 -0
  24. data/lib/generators/rockstart/docker/templates/docker-compose.yml +47 -0
  25. data/lib/generators/rockstart/docker/templates/dockerignore +16 -0
  26. data/lib/generators/rockstart/docker/templates/dotenv.docker.tt +4 -0
  27. data/lib/generators/rockstart/docker/templates/localhost_domains.ext.tt +7 -0
  28. data/lib/generators/rockstart/docker/templates/setup-localhost.tt +27 -0
  29. data/lib/generators/rockstart/docker/templates/web/Dockerfile-web +15 -0
  30. data/lib/generators/rockstart/docker/templates/web/nginx.conf +62 -0
  31. data/lib/generators/rockstart/frontend_helpers/USAGE +8 -0
  32. data/lib/generators/rockstart/frontend_helpers/frontend_helpers_generator.rb +65 -0
  33. data/lib/generators/rockstart/frontend_helpers/templates/application_urls.rb +26 -0
  34. data/lib/generators/rockstart/frontend_helpers/templates/application_urls_helper.rb +20 -0
  35. data/lib/generators/rockstart/frontend_helpers/templates/titles.en.yml.tt +5 -0
  36. data/lib/generators/rockstart/logging/USAGE +8 -0
  37. data/lib/generators/rockstart/logging/logging_generator.rb +12 -0
  38. data/lib/generators/rockstart/logging/templates/rockstart/lograge_initializer.rb +50 -0
  39. data/lib/generators/rockstart/postgres/USAGE +8 -0
  40. data/lib/generators/rockstart/postgres/postgres_generator.rb +32 -0
  41. data/lib/generators/rockstart/postgres/templates/config/database.yml.tt +18 -0
  42. data/lib/generators/rockstart/postgres/templates/migration.rb.tt +7 -0
  43. data/lib/generators/rockstart/pundit/USAGE +8 -0
  44. data/lib/generators/rockstart/pundit/pundit_generator.rb +32 -0
  45. data/lib/generators/rockstart/pundit/templates/app/controllers/concerns/pundit_error_handling.rb +29 -0
  46. data/lib/generators/rockstart/pundit/templates/app/policies/application_policy.rb +71 -0
  47. data/lib/generators/rockstart/pundit/templates/app/policies/user_policy.rb +47 -0
  48. data/lib/generators/rockstart/pundit/templates/config/locales/pundit.en.yml +6 -0
  49. data/lib/generators/rockstart/pundit/templates/lib/templates/pundit/policy/policy.rb +36 -0
  50. data/lib/generators/rockstart/pundit/templates/lib/templates/rspec/policy/policy_spec.rb +58 -0
  51. data/lib/generators/rockstart/pundit/templates/spec/policies/user_policy_spec.rb +95 -0
  52. data/lib/generators/rockstart/pundit/templates/spec/support/pundit_matchers.rb +7 -0
  53. data/lib/generators/rockstart/quality/USAGE +10 -0
  54. data/lib/generators/rockstart/quality/quality_generator.rb +28 -0
  55. data/lib/generators/rockstart/quality/templates/quality.rake +4 -0
  56. data/lib/generators/rockstart/quality/templates/rubocop.rake +4 -0
  57. data/lib/generators/rockstart/quality/templates/rubocop.yml +45 -0
  58. data/lib/generators/rockstart/rockstart_generator.rb +77 -0
  59. data/lib/generators/rockstart/rspec/USAGE +8 -0
  60. data/lib/generators/rockstart/rspec/rspec_generator.rb +70 -0
  61. data/lib/generators/rockstart/rspec/templates/dotenv.development +1 -0
  62. data/lib/generators/rockstart/rspec/templates/dotenv.test +1 -0
  63. data/lib/generators/rockstart/rspec/templates/rspec_templates/model/model_spec.rb +13 -0
  64. data/lib/generators/rockstart/rspec/templates/support/factory_bot.rb +6 -0
  65. data/lib/generators/rockstart/rspec/templates/support/shoulda_matchers.rb +9 -0
  66. data/lib/generators/rockstart/rspec/templates/support/test_helpers.rb +9 -0
  67. data/lib/generators/rockstart/scaffold_templates/USAGE +8 -0
  68. data/lib/generators/rockstart/scaffold_templates/scaffold_templates_generator.rb +39 -0
  69. data/lib/generators/rockstart/scaffold_templates/templates/api_controller.rb.tt +96 -0
  70. data/lib/generators/rockstart/scaffold_templates/templates/controller.rb.tt +126 -0
  71. data/lib/generators/rockstart/scaffold_templates/templates/rspec/scaffold/api_request_spec.rb +139 -0
  72. data/lib/generators/rockstart/scaffold_templates/templates/rspec/scaffold/request_spec.rb +408 -0
  73. data/lib/generators/rockstart/security/USAGE +13 -0
  74. data/lib/generators/rockstart/security/security_generator.rb +108 -0
  75. data/lib/generators/rockstart/security/templates/brakeman.rake +6 -0
  76. data/lib/generators/rockstart/security/templates/bundler_audit.rake +4 -0
  77. data/lib/generators/rockstart/security/templates/cache_support.rb +18 -0
  78. data/lib/generators/rockstart/security/templates/content_security_policy_initializer.rb.tt +56 -0
  79. data/lib/generators/rockstart/security/templates/content_security_spec.rb.tt +83 -0
  80. data/lib/generators/rockstart/security/templates/csp_violations_controller.rb +39 -0
  81. data/lib/generators/rockstart/security/templates/rack_attack.rb +98 -0
  82. data/lib/generators/rockstart/security/templates/security.rake +9 -0
  83. data/lib/generators/rockstart/security/templates/session_store_initializer.rb.tt +7 -0
  84. data/lib/generators/rockstart/smtp_mailer/USAGE +8 -0
  85. data/lib/generators/rockstart/smtp_mailer/smtp_mailer_generator.rb +30 -0
  86. data/lib/generators/rockstart/smtp_mailer/templates/config/initializers/action_mailer.rb +10 -0
  87. data/lib/generators/rockstart/tailwindcss/USAGE +8 -0
  88. data/lib/generators/rockstart/tailwindcss/tailwindcss_generator.rb +30 -0
  89. data/lib/generators/rockstart/tailwindcss/templates/application.css +3 -0
  90. data/lib/generators/rockstart/tailwindcss/templates/postcss.config.js +32 -0
  91. data/lib/rockstart/base_generator.rb +32 -0
  92. data/lib/rockstart/env.rb +16 -0
  93. data/lib/rockstart/railtie.rb +6 -0
  94. data/lib/rockstart/version.rb +5 -0
  95. data/lib/rockstart.rb +9 -0
  96. data/lib/tasks/rockstart_tasks.rake +5 -0
  97. metadata +187 -0
@@ -0,0 +1,408 @@
1
+ <%- resource_path = name.underscore.pluralize -%>
2
+ <%- permitted_params = attributes.map { |a| ":#{a.name}" }.join(", ") -%>
3
+ # frozen_string_literal: true
4
+
5
+ require "rails_helper"
6
+
7
+ <% module_namespacing do -%>
8
+ RSpec.describe "<%= controller_class_name %>", <%= type_metatag(:request) %> do
9
+ <% unless options[:singleton] -%>
10
+ describe "GET /<%= resource_path %>" do
11
+ context "with an authenticated admin" do
12
+ let(:authenticated_admin) { create(:user, :admin) }
13
+
14
+ before do
15
+ sign_in(authenticated_admin)
16
+ end
17
+
18
+ it "renders a successful response" do
19
+ create(:<%= file_name %>)
20
+ get <%= index_helper %>_url
21
+ expect(response).to be_successful
22
+ end
23
+ end
24
+
25
+ context "with an authenticated user" do
26
+ let(:authenticated_user) { create(:user) }
27
+
28
+ before do
29
+ sign_in(authenticated_user)
30
+ end
31
+
32
+ it "renders a successful response" do
33
+ create(:<%= file_name %>)
34
+ get <%= index_helper %>_url
35
+ expect(response).to be_successful
36
+ end
37
+ end
38
+
39
+ context "with an unauthorized user" do
40
+ let(:unauthorized_user) do
41
+ skip("Provide a user where <%= class_name %>Policy.index? is not granted")
42
+ end
43
+
44
+ before do
45
+ sign_in(unauthorized_user)
46
+ end
47
+
48
+ it "forbids access" do
49
+ create(:<%= file_name %>)
50
+ get <%= index_helper %>_url
51
+ expect(response).to redirect_to(url_for_user_dashboard)
52
+
53
+ follow_redirect!
54
+ expect(response.body).to have_selector(".alert-error", text: t("pundit.example_policy.index?", default: t("pundit.default")))
55
+ end
56
+ end
57
+
58
+ it "does not allow access to guests" do
59
+ create(:<%= file_name %>)
60
+ get <%= index_helper %>_url
61
+ expect(response).to redirect_to(url_for_authentication)
62
+ end
63
+ end
64
+ <% end -%>
65
+
66
+ describe "GET /<%= resource_path %>/:id" do
67
+ context "with an authenticated admin" do
68
+ let(:authenticated_admin) { create(:user, :admin) }
69
+
70
+ before do
71
+ sign_in(authenticated_admin)
72
+ end
73
+
74
+ it "renders a successful response" do
75
+ <%= file_name %> = create(:<%= file_name %>)
76
+ get <%= show_helper.tr('@', '') %>
77
+ expect(response).to be_successful
78
+ end
79
+ end
80
+
81
+ context "with an authenticated user" do
82
+ let(:authenticated_user) { create(:user) }
83
+
84
+ before do
85
+ sign_in(authenticated_user)
86
+ end
87
+
88
+ it "renders a successful response" do
89
+ <%= file_name %> = create(:<%= file_name %>)
90
+ get <%= show_helper.tr('@', '') %>
91
+ expect(response).to be_successful
92
+ end
93
+ end
94
+
95
+ context "with an unauthorized user" do
96
+ let(:unauthorized_user) do
97
+ skip("Provide a user where <%= class_name %>Policy.show? is not granted")
98
+ end
99
+
100
+ before do
101
+ sign_in(unauthorized_user)
102
+ end
103
+
104
+ it "forbids access" do
105
+ <%= file_name %> = create(:<%= file_name %>)
106
+ get <%= show_helper.tr('@', '') %>
107
+ expect(response).to redirect_to(<%= index_helper %>_url)
108
+
109
+ follow_redirect!
110
+ expect(response.body).to have_selector(".alert-error", text: t("pundit.example_policy.show?", default: t("pundit.default")))
111
+ end
112
+ end
113
+
114
+ it "does not allow access to guests" do
115
+ <%= file_name %> = create(:<%= file_name %>)
116
+ get <%= show_helper.tr('@', '') %>
117
+ expect(response).to redirect_to(url_for_authentication)
118
+ end
119
+ end
120
+
121
+ describe "GET /<%= resource_path %>/new" do
122
+ context "with an authenticated admin" do
123
+ let(:authenticated_admin) { create(:user, :admin) }
124
+
125
+ before do
126
+ sign_in(authenticated_admin)
127
+ end
128
+
129
+ it "renders a successful response" do
130
+ get <%= new_helper %>
131
+ expect(response).to be_successful
132
+ end
133
+ end
134
+
135
+ context "with an authenticated user" do
136
+ let(:authenticated_user) { create(:user) }
137
+
138
+ before do
139
+ sign_in(authenticated_user)
140
+ end
141
+
142
+ it "forbids access" do
143
+ get <%= new_helper %>
144
+ expect(response).to redirect_to(<%= index_helper %>_url)
145
+
146
+ follow_redirect!
147
+ expect(response.body).to have_selector(".alert-error", text: t("pundit.example_policy.new?", default: t("pundit.default")))
148
+ end
149
+ end
150
+
151
+ it "does not allow access to guests" do
152
+ get <%= new_helper %>
153
+ expect(response).to redirect_to(url_for_authentication)
154
+ end
155
+ end
156
+
157
+ describe "GET /<%= resource_path %>/:id/edit" do
158
+ context "with an authenticated admin" do
159
+ let(:authenticated_admin) { create(:user, :admin) }
160
+
161
+ before do
162
+ sign_in(authenticated_admin)
163
+ end
164
+
165
+ it "render a successful response" do
166
+ <%= file_name %> = create(:<%= file_name %>)
167
+ get <%= edit_helper.tr('@','') %>
168
+ expect(response).to be_successful
169
+ end
170
+ end
171
+
172
+ context "with an authenticated user" do
173
+ let(:authenticated_user) { create(:user) }
174
+
175
+ before do
176
+ sign_in(authenticated_user)
177
+ end
178
+
179
+ it "forbids access" do
180
+ <%= file_name %> = create(:<%= file_name %>)
181
+ get <%= edit_helper.tr('@','') %>
182
+ expect(response).to redirect_to(<%= show_helper.tr('@', '') %>)
183
+
184
+ follow_redirect!
185
+ expect(response.body).to have_selector(".alert-error", text: t("pundit.example_policy.edit?", default: t("pundit.default")))
186
+ end
187
+ end
188
+
189
+ it "does not allow access to guests" do
190
+ <%= file_name %> = create(:<%= file_name %>)
191
+ get <%= edit_helper.tr('@','') %>
192
+ expect(response).to redirect_to(url_for_authentication)
193
+ end
194
+ end
195
+
196
+ describe "POST /<%= resource_path %>" do
197
+ context "with valid parameters" do
198
+ let(:valid_attributes) do
199
+ <%- if attributes.any? -%>
200
+ attributes_for(:<%= ns_file_name %>).slice(<%= permitted_params %>)
201
+ <%- else -%>
202
+ skip("Add a hash of attributes valid for your model")
203
+ <%- end -%>
204
+ end
205
+
206
+ context "with an authenticated admin" do
207
+ let(:authenticated_admin) { create(:user, :admin) }
208
+
209
+ before do
210
+ sign_in(authenticated_admin)
211
+ end
212
+
213
+ it "creates a new <%= class_name %>" do
214
+ expect do
215
+ post <%= index_helper %>_url, params: { <%= ns_file_name %>: valid_attributes }
216
+ end.to change(<%= class_name %>, :count).by(1)
217
+
218
+ <%= file_name %> = <%= class_name %>.last
219
+ <%- if attributes.any? -%>
220
+ <%- attributes.each do |attribute| -%>
221
+ expect(<%= file_name %>.<%= attribute.name %>).to eq(valid_attributes[:<%= attribute.name %>])
222
+ <%- end -%>
223
+ <%- else -%>
224
+ skip("Add assertions for created state")
225
+ <%- end -%>
226
+ end
227
+
228
+ it "redirects to the created <%= ns_file_name %>" do
229
+ post <%= index_helper %>_url, params: { <%= ns_file_name %>: valid_attributes }
230
+ expect(response).to redirect_to(<%= show_helper.gsub("\@#{file_name}", class_name+".last") %>)
231
+ end
232
+ end
233
+
234
+ context "with an authenticated user" do
235
+ let(:authenticated_user) { create(:user) }
236
+
237
+ before do
238
+ sign_in(authenticated_user)
239
+ end
240
+
241
+ it "forbids access" do
242
+ post <%= index_helper %>_url, params: { <%= ns_file_name %>: valid_attributes }
243
+ expect(response).to redirect_to(<%= index_helper %>_url)
244
+
245
+ follow_redirect!
246
+ expect(response.body).to have_selector(".alert-error", text: t("pundit.example_policy.create?", default: t("pundit.default")))
247
+ end
248
+ end
249
+
250
+ it "does not allow access to guests" do
251
+ post <%= index_helper %>_url, params: { <%= ns_file_name %>: valid_attributes }
252
+ expect(response).to redirect_to(url_for_authentication)
253
+ end
254
+ end
255
+
256
+ context "with invalid parameters" do
257
+ let(:authenticated_admin) { create(:user, :admin) }
258
+
259
+ let(:invalid_attributes) do
260
+ skip("Add a hash of attributes invalid for your model")
261
+ end
262
+
263
+ before do
264
+ sign_in(authenticated_admin)
265
+ end
266
+
267
+ it "does not create a new <%= class_name %>" do
268
+ expect do
269
+ post <%= index_helper %>_url, params: { <%= ns_file_name %>: invalid_attributes }
270
+ end.to change(<%= class_name %>, :count).by(0)
271
+ end
272
+
273
+ it "renders a successful response (i.e. to display the 'new' template)" do
274
+ post <%= index_helper %>_url, params: { <%= ns_file_name %>: invalid_attributes }
275
+ expect(response).to be_successful
276
+ end
277
+ end
278
+ end
279
+
280
+ describe "PATCH /<%= resource_path %>/:id" do
281
+ context "with valid parameters" do
282
+ let(:new_attributes) do
283
+ <%- if attributes.any? -%>
284
+ attributes_for(:<%= ns_file_name %>).slice(<%= permitted_params %>)
285
+ <%- else -%>
286
+ skip("Add a hash of attributes valid for your model")
287
+ <%- end -%>
288
+ end
289
+
290
+ context "with an authenticated admin" do
291
+ let(:authenticated_admin) { create(:user, :admin) }
292
+
293
+ before do
294
+ sign_in(authenticated_admin)
295
+ end
296
+
297
+ it "updates the requested <%= ns_file_name %>" do
298
+ <%= file_name %> = create(:<%= file_name %>)
299
+ patch <%= show_helper.tr('@', '') %>, params: { <%= singular_table_name %>: new_attributes }
300
+
301
+ <%= file_name %>.reload
302
+ <%- if attributes.any? -%>
303
+ <%- attributes.each do |attribute| -%>
304
+ expect(<%= file_name %>.<%= attribute.name %>).to eq(new_attributes[:<%= attribute.name %>])
305
+ <%- end -%>
306
+ <%- else -%>
307
+ skip("Add assertions for updated state")
308
+ <%- end -%>
309
+ end
310
+
311
+ it "redirects to the <%= ns_file_name %>" do
312
+ <%= file_name %> = create(:<%= file_name %>)
313
+ patch <%= show_helper.tr('@', '') %>, params: { <%= singular_table_name %>: new_attributes }
314
+ <%= file_name %>.reload
315
+ expect(response).to redirect_to(<%= singular_table_name %>_url(<%= file_name %>))
316
+ end
317
+ end
318
+
319
+ context "with an authenticated user" do
320
+ let(:authenticated_user) { create(:user) }
321
+
322
+ before do
323
+ sign_in(authenticated_user)
324
+ end
325
+
326
+ it "forbids access" do
327
+ <%= file_name %> = create(:<%= file_name %>)
328
+ patch <%= show_helper.tr('@', '') %>, params: { <%= singular_table_name %>: new_attributes }
329
+ expect(response).to redirect_to(<%= show_helper.tr('@', '') %>)
330
+
331
+ follow_redirect!
332
+ expect(response.body).to have_selector(".alert-error", text: t("pundit.example_policy.update?", default: t("pundit.default")))
333
+ end
334
+ end
335
+
336
+ it "does not allow access to guests" do
337
+ <%= file_name %> = create(:<%= file_name %>)
338
+ patch <%= show_helper.tr('@', '') %>, params: { <%= singular_table_name %>: new_attributes }
339
+ expect(response).to redirect_to(url_for_authentication)
340
+ end
341
+ end
342
+
343
+ context "with invalid parameters" do
344
+ let(:authenticated_admin) { create(:user, :admin) }
345
+
346
+ let(:invalid_attributes) do
347
+ skip("Add a hash of attributes invalid for your model")
348
+ end
349
+
350
+ before do
351
+ sign_in(authenticated_admin)
352
+ end
353
+
354
+ it "renders a successful response (i.e. to display the 'edit' template)" do
355
+ <%= file_name %> = create(:<%= file_name %>)
356
+ patch <%= show_helper.tr('@', '') %>, params: { <%= singular_table_name %>: invalid_attributes }
357
+ expect(response).to be_successful
358
+ end
359
+ end
360
+ end
361
+
362
+ describe "DELETE /<%= resource_path %>/:id" do
363
+ context "with an authenticated admin" do
364
+ let(:authenticated_admin) { create(:user, :admin) }
365
+
366
+ before do
367
+ sign_in(authenticated_admin)
368
+ end
369
+
370
+ it "destroys the requested <%= ns_file_name %>" do
371
+ <%= file_name %> = create(:<%= file_name %>)
372
+ expect do
373
+ delete <%= show_helper.tr('@', '') %>
374
+ end.to change(<%= class_name %>, :count).by(-1)
375
+ end
376
+
377
+ it "redirects to the <%= table_name %> list" do
378
+ <%= file_name %> = create(:<%= file_name %>)
379
+ delete <%= show_helper.tr('@', '') %>
380
+ expect(response).to redirect_to(<%= index_helper %>_url)
381
+ end
382
+ end
383
+
384
+ context "with an authenticated user" do
385
+ let(:authenticated_user) { create(:user) }
386
+
387
+ before do
388
+ sign_in(authenticated_user)
389
+ end
390
+
391
+ it "forbids access" do
392
+ <%= file_name %> = create(:<%= file_name %>)
393
+ delete <%= show_helper.tr('@', '') %>
394
+ expect(response).to redirect_to(<%= show_helper.tr('@', '') %>)
395
+
396
+ follow_redirect!
397
+ expect(response.body).to have_selector(".alert-error", text: t("pundit.example_policy.destroy?", default: t("pundit.default")))
398
+ end
399
+ end
400
+
401
+ it "does not allow access to guests" do
402
+ <%= file_name %> = create(:<%= file_name %>)
403
+ delete <%= show_helper.tr('@', '') %>
404
+ expect(response).to redirect_to(url_for_authentication)
405
+ end
406
+ end
407
+ end
408
+ <% end -%>
@@ -0,0 +1,13 @@
1
+ Description:
2
+ Adds security checks and middleware to a Rails app
3
+
4
+ Example:
5
+ rails generate rockstart:security
6
+
7
+ This will create:
8
+ Installs bundler-audit to safely check gems
9
+ Installs brakeman to check for any potential exploits
10
+ Adds rack_attack with a simple policy to block most attacks
11
+ Configures a minimalist Content Security Policy with supporting test coverage
12
+ Ensures test (and unconfigured) environments are using an in-memory cache
13
+ Enforces secure HTTPS connections by defaults
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Rockstart::SecurityGenerator < Rails::Generators::Base
4
+ include Rails::Generators::AppName
5
+ source_root File.expand_path("templates", __dir__)
6
+
7
+ class_option :font_hosts, type: :array,
8
+ desc: "Known third-party hosts for Fonts",
9
+ default: []
10
+
11
+ class_option :image_hosts, type: :array,
12
+ desc: "Known third-party hosts for Images",
13
+ default: []
14
+
15
+ class_option :script_hosts, type: :array,
16
+ desc: "Known third-party hosts for (Java)Scripts",
17
+ default: []
18
+
19
+ class_option :style_hosts, type: :array,
20
+ desc: "Known third-party hosts for Stylesheets",
21
+ default: []
22
+
23
+ class_option :session_name, type: :string,
24
+ desc: "Name used for Rails Sessions",
25
+ default: Rockstart::Env.default_session_name
26
+
27
+ def install_bundler_audit
28
+ gem "bundler-audit", github: "rubysec/bundler-audit"
29
+
30
+ Bundler.clean_system("bundle install --quiet")
31
+
32
+ copy_file "bundler_audit.rake", "lib/tasks/bundler_audit.rake"
33
+ end
34
+
35
+ def install_brakeman
36
+ gem "brakeman", group: %i[development test]
37
+
38
+ Bundler.clean_system("bundle install --quiet")
39
+
40
+ copy_file "brakeman.rake", "lib/tasks/brakeman.rake"
41
+
42
+ append_to_file ".gitignore", "brakeman\n"
43
+ end
44
+
45
+ def add_security_rake_tasks
46
+ copy_file "security.rake", "lib/tasks/security.rake"
47
+ end
48
+
49
+ def install_rack_attack
50
+ gem "rack-attack"
51
+
52
+ Bundler.clean_system("bundle install --quiet")
53
+
54
+ copy_file "rack_attack.rb", "config/initializers/rack_attack.rb"
55
+ copy_file "cache_support.rb", "spec/support/cache.rb"
56
+
57
+ application do
58
+ <<~CACHE
59
+ # Use memory_store cache for testing and default configurations
60
+ config.cache_store = :memory_store
61
+ CACHE
62
+ end
63
+ comment_lines "config/environments/test.rb", "config.cache_store = "
64
+ end
65
+
66
+ def add_session_initializer
67
+ template "session_store_initializer.rb.tt", "config/initializers/session_store.rb"
68
+ end
69
+
70
+ def add_content_security_policy
71
+ template "content_security_policy_initializer.rb.tt",
72
+ "config/initializers/content_security_policy.rb"
73
+
74
+ copy_file "csp_violations_controller.rb", "app/controllers/csp_violations_controller.rb"
75
+ route "resources :csp_violations, only: [:create]"
76
+
77
+ template "content_security_spec.rb.tt", "spec/requests/content_security_spec.rb"
78
+ end
79
+
80
+ def enforce_ssl
81
+ gsub_file "config/environments/production.rb",
82
+ /config.force_ssl = .+$/,
83
+ 'config.force_ssl = ENV["ALLOW_INSECURE_HTTP"].to_i != 1'
84
+ uncomment_lines "config/environments/production.rb", /config.force_ssl =/
85
+ end
86
+
87
+ private
88
+
89
+ def font_hosts
90
+ options[:font_hosts] || []
91
+ end
92
+
93
+ def image_hosts
94
+ options[:image_hosts] || []
95
+ end
96
+
97
+ def script_hosts
98
+ options[:script_hosts] || []
99
+ end
100
+
101
+ def style_hosts
102
+ options[:style_hosts] || []
103
+ end
104
+
105
+ def session_name
106
+ options[:session_name]
107
+ end
108
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ desc "Run brakeman check on your codebase"
4
+ task :brakeman do
5
+ system "bundle exec brakeman -w 2 -o brakeman"
6
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/audit/task"
4
+ Bundler::Audit::Task.new
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Support for tests that depend on Rails.cache
4
+ module CacheSupport
5
+ def clear_rails_cache
6
+ Rails.cache.clear
7
+ end
8
+ end
9
+
10
+ RSpec.configure do |config|
11
+ config.include CacheSupport
12
+
13
+ config.around(cache_testing: true) do |example|
14
+ clear_rails_cache
15
+ example.run
16
+ clear_rails_cache
17
+ end
18
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Be sure to restart your server when you modify this file.
4
+
5
+ # Define an application-wide content security policy
6
+ # For further information see the following documentation
7
+ # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
8
+
9
+ # Configure any external domains used to host assets here
10
+ csp_connect_sources = []
11
+ csp_font_sources = <%= font_hosts.inspect %>
12
+ csp_image_sources = <%= image_hosts.inspect %>
13
+ csp_script_sources = <%= script_hosts.inspect %>
14
+ csp_style_sources = <%= style_hosts.inspect %>
15
+
16
+ # Allow the asset host to serve assets
17
+ if (asset_host = Rails.application.config.action_controller.asset_host.presence)
18
+ csp_font_sources.append(asset_host)
19
+ csp_image_sources.append(asset_host)
20
+ csp_script_sources.append(asset_host)
21
+ csp_style_sources.append(asset_host)
22
+ end
23
+
24
+ # Allow webpacker to render styles inline
25
+ if Rails.env.development?
26
+ csp_connect_sources += %w[http://localhost:3035 ws://localhost:3035]
27
+ csp_style_sources.append(:unsafe_inline)
28
+ end
29
+
30
+ Rails.application.config.content_security_policy do |policy|
31
+ policy.default_src :none
32
+ policy.connect_src :self, *csp_connect_sources
33
+ policy.font_src :self, *csp_font_sources
34
+ policy.img_src :self, :data, *csp_image_sources
35
+ policy.object_src :none
36
+ policy.script_src :self, *csp_script_sources
37
+ policy.style_src :self, *csp_style_sources
38
+
39
+ # Prevent non secure resources from being loaded
40
+ policy.block_all_mixed_content
41
+ policy.upgrade_insecure_requests
42
+
43
+ # Specify URI for violation reports
44
+ policy.report_uri "/csp_violations"
45
+ end
46
+
47
+ # If you are using UJS then enable automatic nonce generation
48
+ # Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) }
49
+
50
+ # Set the nonce only to specific directives
51
+ # Rails.application.config.content_security_policy_nonce_directives = %w(script-src)
52
+
53
+ # Report CSP violations to a specified URI
54
+ # For further information see the following documentation:
55
+ # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only
56
+ # Rails.application.config.content_security_policy_report_only = true