action-audit 1.0.1

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.
data/docs/examples.md ADDED
@@ -0,0 +1,609 @@
1
+ # Examples
2
+
3
+ Real-world examples of using ActionAudit in different scenarios and applications.
4
+
5
+ ## Basic Examples
6
+
7
+ ### Simple User Management
8
+
9
+ ```ruby
10
+ # app/controllers/users_controller.rb
11
+ class UsersController < ApplicationController
12
+ include ActionAudit
13
+
14
+ def create
15
+ @user = User.create!(user_params)
16
+ redirect_to @user, notice: 'User created successfully'
17
+ end
18
+
19
+ def update
20
+ @user = User.find(params[:id])
21
+ @user.update!(user_params)
22
+ redirect_to @user, notice: 'User updated successfully'
23
+ end
24
+
25
+ def destroy
26
+ @user = User.find(params[:id])
27
+ @user.destroy!
28
+ redirect_to users_path, notice: 'User deleted successfully'
29
+ end
30
+
31
+ private
32
+
33
+ def user_params
34
+ params.require(:user).permit(:name, :email, :role)
35
+ end
36
+ end
37
+ ```
38
+
39
+ ```yaml
40
+ # config/audit.yml
41
+ users:
42
+ create: "Created user %{email} with role %{role}"
43
+ update: "Updated user %{id} - %{name}"
44
+ destroy: "Deleted user %{id}"
45
+ ```
46
+
47
+ **Log Output:**
48
+ ```
49
+ users/create - Created user john@example.com with role admin
50
+ users/update - Updated user 123 - John Smith
51
+ users/destroy - Deleted user 123
52
+ ```
53
+
54
+ ## Advanced Examples
55
+
56
+ ### Multi-Level Admin Interface
57
+
58
+ ```ruby
59
+ # app/controllers/admin/users_controller.rb
60
+ class Admin::UsersController < ApplicationController
61
+ include ActionAudit
62
+ before_action :require_admin
63
+
64
+ def create
65
+ @user = User.create!(user_params)
66
+ @user.send_welcome_email if params[:send_welcome]
67
+ redirect_to admin_user_path(@user)
68
+ end
69
+
70
+ def activate
71
+ @user = User.find(params[:id])
72
+ @user.update!(active: true, activated_at: Time.current)
73
+ redirect_to admin_user_path(@user)
74
+ end
75
+
76
+ def impersonate
77
+ @user = User.find(params[:id])
78
+ session[:impersonated_user_id] = @user.id
79
+ redirect_to root_path
80
+ end
81
+
82
+ private
83
+
84
+ def user_params
85
+ params.require(:user).permit(:name, :email, :role, :department)
86
+ end
87
+ end
88
+ ```
89
+
90
+ ```yaml
91
+ # config/audit.yml
92
+ admin:
93
+ users:
94
+ create: "Admin created user %{email} in %{department} department"
95
+ update: "Admin updated user %{id}"
96
+ destroy: "Admin deleted user %{id}"
97
+ activate: "Admin activated user %{id}"
98
+ deactivate: "Admin deactivated user %{id}"
99
+ impersonate: "Admin impersonated user %{id}"
100
+ ```
101
+
102
+ ### API Endpoints with Detailed Logging
103
+
104
+ ```ruby
105
+ # app/controllers/api/v1/webhooks_controller.rb
106
+ class API::V1::WebhooksController < ApplicationController
107
+ include ActionAudit
108
+ skip_before_action :verify_authenticity_token
109
+ before_action :set_audit_context
110
+
111
+ def create
112
+ @webhook = WebhookEvent.create!(webhook_params)
113
+ WebhookProcessor.perform_async(@webhook.id)
114
+ render json: { status: 'received', id: @webhook.id }
115
+ end
116
+
117
+ private
118
+
119
+ def webhook_params
120
+ params.permit(:source, :event_type, :payload).tap do |wp|
121
+ wp[:payload_size] = params[:payload].to_s.bytesize
122
+ wp[:client_ip] = request.remote_ip
123
+ end
124
+ end
125
+
126
+ def set_audit_context
127
+ # Add context for auditing
128
+ params[:source] = request.headers['X-Webhook-Source'] || 'unknown'
129
+ params[:user_agent] = request.user_agent
130
+ end
131
+ end
132
+ ```
133
+
134
+ ```yaml
135
+ # config/audit.yml
136
+ api:
137
+ v1:
138
+ webhooks:
139
+ create: "Webhook received from %{source} - %{event_type} (%{payload_size} bytes) from %{client_ip}"
140
+ ```
141
+
142
+ ## E-commerce Examples
143
+
144
+ ### Order Management
145
+
146
+ ```ruby
147
+ # app/controllers/orders_controller.rb
148
+ class OrdersController < ApplicationController
149
+ include ActionAudit
150
+ before_action :authenticate_user!
151
+ before_action :set_audit_user_context
152
+
153
+ def create
154
+ @order = current_user.orders.build(order_params)
155
+ @order.calculate_totals!
156
+ @order.save!
157
+
158
+ redirect_to @order, notice: 'Order placed successfully'
159
+ end
160
+
161
+ def cancel
162
+ @order = current_user.orders.find(params[:id])
163
+ @order.cancel!
164
+
165
+ redirect_to @order, notice: 'Order cancelled'
166
+ end
167
+
168
+ def refund
169
+ @order = Order.find(params[:id])
170
+ @refund = @order.create_refund!(refund_amount: params[:amount])
171
+
172
+ redirect_to @order, notice: 'Refund processed'
173
+ end
174
+
175
+ private
176
+
177
+ def order_params
178
+ params.require(:order).permit(:shipping_address, line_items: [:product_id, :quantity])
179
+ end
180
+
181
+ def set_audit_user_context
182
+ params[:user_id] = current_user.id
183
+ params[:user_email] = current_user.email
184
+ end
185
+ end
186
+ ```
187
+
188
+ ```yaml
189
+ # config/audit.yml
190
+ orders:
191
+ create: "User %{user_email} placed order %{id} for $%{total}"
192
+ cancel: "User %{user_email} cancelled order %{id}"
193
+ refund: "Refunded $%{amount} for order %{id} to user %{user_email}"
194
+ ```
195
+
196
+ ### Inventory Management
197
+
198
+ ```ruby
199
+ # app/controllers/admin/inventory_controller.rb
200
+ class Admin::InventoryController < ApplicationController
201
+ include ActionAudit
202
+ before_action :require_inventory_manager
203
+
204
+ def adjust
205
+ @product = Product.find(params[:id])
206
+ old_quantity = @product.inventory_quantity
207
+ @product.update!(inventory_quantity: params[:new_quantity])
208
+
209
+ params[:old_quantity] = old_quantity
210
+ params[:adjustment] = params[:new_quantity].to_i - old_quantity
211
+ params[:product_name] = @product.name
212
+
213
+ redirect_to admin_product_path(@product)
214
+ end
215
+
216
+ def restock
217
+ @product = Product.find(params[:id])
218
+ @product.increment!(:inventory_quantity, params[:quantity].to_i)
219
+
220
+ params[:product_name] = @product.name
221
+
222
+ redirect_to admin_product_path(@product)
223
+ end
224
+ end
225
+ ```
226
+
227
+ ```yaml
228
+ # config/audit.yml
229
+ admin:
230
+ inventory:
231
+ adjust: "Inventory adjusted for %{product_name}: %{old_quantity} → %{new_quantity} (Δ%{adjustment})"
232
+ restock: "Restocked %{product_name} with %{quantity} units"
233
+ ```
234
+
235
+ ## Authentication & Security Examples
236
+
237
+ ### Session Management
238
+
239
+ ```ruby
240
+ # app/controllers/sessions_controller.rb
241
+ class SessionsController < ApplicationController
242
+ include ActionAudit
243
+ before_action :set_audit_context, except: [:new]
244
+
245
+ def create
246
+ @user = User.find_by(email: params[:email])
247
+
248
+ if @user&.authenticate(params[:password])
249
+ if @user.active?
250
+ session[:user_id] = @user.id
251
+ params[:login_method] = 'password'
252
+ redirect_to dashboard_path
253
+ else
254
+ params[:failure_reason] = 'account_inactive'
255
+ redirect_to login_path, alert: 'Account is inactive'
256
+ end
257
+ else
258
+ params[:failure_reason] = 'invalid_credentials'
259
+ redirect_to login_path, alert: 'Invalid credentials'
260
+ end
261
+ end
262
+
263
+ def destroy
264
+ params[:session_duration] = time_since_login
265
+ session.clear
266
+ redirect_to root_path
267
+ end
268
+
269
+ private
270
+
271
+ def set_audit_context
272
+ params[:ip_address] = request.remote_ip
273
+ params[:user_agent] = request.user_agent&.truncate(100)
274
+ end
275
+
276
+ def time_since_login
277
+ return 'unknown' unless session[:login_time]
278
+ Time.current - Time.parse(session[:login_time])
279
+ end
280
+ end
281
+ ```
282
+
283
+ ```yaml
284
+ # config/audit.yml
285
+ sessions:
286
+ create: "Login %{email} via %{login_method} from %{ip_address} - %{user_agent}"
287
+ destroy: "Logout %{email} after %{session_duration} seconds"
288
+
289
+ # For failed attempts, you might have:
290
+ login_failures:
291
+ create: "Failed login attempt for %{email}: %{failure_reason} from %{ip_address}"
292
+ ```
293
+
294
+ ### Password Management
295
+
296
+ ```ruby
297
+ # app/controllers/passwords_controller.rb
298
+ class PasswordsController < ApplicationController
299
+ include ActionAudit
300
+ before_action :authenticate_user!, except: [:forgot, :reset]
301
+
302
+ def forgot
303
+ @user = User.find_by(email: params[:email])
304
+ if @user
305
+ @user.generate_reset_token!
306
+ PasswordMailer.reset_instructions(@user).deliver_now
307
+ params[:user_id] = @user.id
308
+ end
309
+
310
+ redirect_to login_path, notice: 'Reset instructions sent if email exists'
311
+ end
312
+
313
+ def reset
314
+ @user = User.find_by(reset_token: params[:token])
315
+ if @user&.reset_token_valid?
316
+ @user.update!(password: params[:password], reset_token: nil)
317
+ params[:user_id] = @user.id
318
+ redirect_to login_path, notice: 'Password reset successfully'
319
+ else
320
+ redirect_to forgot_password_path, alert: 'Invalid or expired token'
321
+ end
322
+ end
323
+
324
+ def change
325
+ if current_user.authenticate(params[:current_password])
326
+ current_user.update!(password: params[:new_password])
327
+ params[:user_id] = current_user.id
328
+ redirect_to profile_path, notice: 'Password changed successfully'
329
+ else
330
+ redirect_to change_password_path, alert: 'Current password is incorrect'
331
+ end
332
+ end
333
+ end
334
+ ```
335
+
336
+ ```yaml
337
+ # config/audit.yml
338
+ passwords:
339
+ forgot: "Password reset requested for user %{user_id}"
340
+ reset: "Password reset completed for user %{user_id}"
341
+ change: "Password changed for user %{user_id}"
342
+ ```
343
+
344
+ ## Content Management Examples
345
+
346
+ ### Blog Post Management
347
+
348
+ ```ruby
349
+ # app/controllers/admin/posts_controller.rb
350
+ class Admin::PostsController < ApplicationController
351
+ include ActionAudit
352
+ before_action :authenticate_admin!
353
+
354
+ def create
355
+ @post = current_user.posts.build(post_params)
356
+ @post.save!
357
+
358
+ params[:author_name] = current_user.name
359
+
360
+ redirect_to admin_post_path(@post)
361
+ end
362
+
363
+ def publish
364
+ @post = Post.find(params[:id])
365
+ @post.update!(published: true, published_at: Time.current)
366
+
367
+ params[:title] = @post.title
368
+ params[:author_name] = @post.author.name
369
+
370
+ redirect_to admin_post_path(@post)
371
+ end
372
+
373
+ def feature
374
+ @post = Post.find(params[:id])
375
+ @post.update!(featured: true, featured_at: Time.current)
376
+
377
+ params[:title] = @post.title
378
+
379
+ redirect_to admin_post_path(@post)
380
+ end
381
+
382
+ private
383
+
384
+ def post_params
385
+ params.require(:post).permit(:title, :content, :category_id, :tags)
386
+ end
387
+ end
388
+ ```
389
+
390
+ ```yaml
391
+ # config/audit.yml
392
+ admin:
393
+ posts:
394
+ create: "Created post '%{title}' by %{author_name}"
395
+ update: "Updated post '%{title}'"
396
+ destroy: "Deleted post '%{title}'"
397
+ publish: "Published post '%{title}' by %{author_name}"
398
+ unpublish: "Unpublished post '%{title}'"
399
+ feature: "Featured post '%{title}'"
400
+ unfeature: "Unfeatured post '%{title}'"
401
+ ```
402
+
403
+ ## Integration Examples
404
+
405
+ ### Third-Party Service Integration
406
+
407
+ ```ruby
408
+ # app/controllers/integrations/stripe_controller.rb
409
+ class Integrations::StripeController < ApplicationController
410
+ include ActionAudit
411
+ skip_before_action :verify_authenticity_token
412
+ before_action :verify_stripe_signature
413
+ before_action :set_audit_context
414
+
415
+ def webhook
416
+ case params[:type]
417
+ when 'payment_intent.succeeded'
418
+ handle_successful_payment
419
+ when 'subscription.cancelled'
420
+ handle_subscription_cancellation
421
+ end
422
+
423
+ render json: { received: true }
424
+ end
425
+
426
+ private
427
+
428
+ def handle_successful_payment
429
+ payment_intent = params[:data][:object]
430
+ order = Order.find_by(stripe_payment_intent: payment_intent[:id])
431
+ order&.mark_as_paid!
432
+
433
+ params[:order_id] = order&.id
434
+ params[:amount] = payment_intent[:amount]
435
+ end
436
+
437
+ def handle_subscription_cancellation
438
+ subscription = params[:data][:object]
439
+ user = User.find_by(stripe_subscription_id: subscription[:id])
440
+ user&.cancel_subscription!
441
+
442
+ params[:user_id] = user&.id
443
+ params[:subscription_id] = subscription[:id]
444
+ end
445
+
446
+ def set_audit_context
447
+ params[:webhook_id] = params[:id]
448
+ params[:event_type] = params[:type]
449
+ params[:stripe_account] = params[:account] if params[:account]
450
+ end
451
+ end
452
+ ```
453
+
454
+ ```yaml
455
+ # config/audit.yml
456
+ integrations:
457
+ stripe:
458
+ webhook: "Stripe webhook %{webhook_id}: %{event_type} processed"
459
+ ```
460
+
461
+ ## Custom Formatting Examples
462
+
463
+ ### JSON Structured Logging
464
+
465
+ ```ruby
466
+ # config/initializers/action_audit.rb
467
+ ActionAudit.log_formatter = lambda do |controller, action, message|
468
+ {
469
+ event_type: 'audit',
470
+ timestamp: Time.current.iso8601,
471
+ controller: controller,
472
+ action: action,
473
+ message: message,
474
+ user_id: defined?(current_user) && current_user&.id,
475
+ request_id: defined?(request) && request&.request_id,
476
+ ip_address: defined?(request) && request&.remote_ip
477
+ }.to_json
478
+ end
479
+ ```
480
+
481
+ ### Syslog Integration
482
+
483
+ ```ruby
484
+ # config/initializers/action_audit.rb
485
+ require 'syslog/logger'
486
+
487
+ ActionAudit.log_formatter = lambda do |controller, action, message|
488
+ # Format for syslog
489
+ "AUDIT user_id=#{current_user&.id} controller=#{controller} action=#{action} message=\"#{message}\""
490
+ end
491
+
492
+ # Custom logger for audit messages
493
+ class AuditLogger
494
+ def self.info(message)
495
+ syslog = Syslog::Logger.new('rails_audit')
496
+ syslog.info(message)
497
+ end
498
+ end
499
+
500
+ # Override default Rails logger for audit messages
501
+ module ActionAudit
502
+ private
503
+
504
+ def audit_request
505
+ # ... existing logic ...
506
+
507
+ # Use custom logger instead of Rails.logger
508
+ AuditLogger.info(formatted_message)
509
+ end
510
+ end
511
+ ```
512
+
513
+ ### Slack Integration
514
+
515
+ ```ruby
516
+ # config/initializers/action_audit.rb
517
+ ActionAudit.log_formatter = lambda do |controller, action, message|
518
+ # Log to Rails logger
519
+ Rails.logger.info("[AUDIT] #{controller}/#{action} - #{message}")
520
+
521
+ # Also send critical actions to Slack
522
+ if critical_action?(controller, action)
523
+ SlackNotifier.ping(
524
+ text: "🔒 Critical Action: #{message}",
525
+ channel: '#security-alerts',
526
+ username: 'AuditBot'
527
+ )
528
+ end
529
+
530
+ "[AUDIT] #{controller}/#{action} - #{message}"
531
+ end
532
+
533
+ def critical_action?(controller, action)
534
+ critical_patterns = [
535
+ 'admin/users/destroy',
536
+ 'admin/settings/update',
537
+ 'integrations/*/webhook'
538
+ ]
539
+
540
+ path = "#{controller}/#{action}"
541
+ critical_patterns.any? { |pattern| File.fnmatch(pattern, path) }
542
+ end
543
+ ```
544
+
545
+ ## Testing Examples
546
+
547
+ ### RSpec Controller Tests
548
+
549
+ ```ruby
550
+ # spec/controllers/users_controller_spec.rb
551
+ RSpec.describe UsersController, type: :controller do
552
+ describe '#create' do
553
+ let(:user_params) { { name: 'John Doe', email: 'john@example.com' } }
554
+
555
+ it 'creates a user and logs the action' do
556
+ expect(Rails.logger).to receive(:info).with(/Created user john@example\.com/)
557
+
558
+ post :create, params: { user: user_params }
559
+
560
+ expect(response).to redirect_to(User.last)
561
+ expect(User.last.email).to eq('john@example.com')
562
+ end
563
+
564
+ context 'with custom log formatter' do
565
+ before do
566
+ ActionAudit.log_formatter = ->(c, a, m) { "CUSTOM: #{c}/#{a} - #{m}" }
567
+ end
568
+
569
+ it 'uses custom formatting' do
570
+ expect(Rails.logger).to receive(:info).with(/CUSTOM: users\/create - Created user/)
571
+
572
+ post :create, params: { user: user_params }
573
+ end
574
+ end
575
+ end
576
+ end
577
+ ```
578
+
579
+ ### Feature Tests
580
+
581
+ ```ruby
582
+ # spec/features/admin_user_management_spec.rb
583
+ RSpec.feature 'Admin User Management', type: :feature do
584
+ let(:admin) { create(:admin_user) }
585
+
586
+ before { sign_in admin }
587
+
588
+ scenario 'Admin creates a new user' do
589
+ visit new_admin_user_path
590
+
591
+ fill_in 'Email', with: 'newuser@example.com'
592
+ fill_in 'Name', with: 'New User'
593
+ select 'Editor', from: 'Role'
594
+
595
+ expect {
596
+ click_button 'Create User'
597
+ }.to change(User, :count).by(1)
598
+
599
+ # Check that audit log was created
600
+ expect(Rails.logger).to have_received(:info).with(/Admin created user newuser@example\.com/)
601
+ end
602
+ end
603
+ ```
604
+
605
+ ## Next Steps
606
+
607
+ - [Learn about troubleshooting](troubleshooting.md)
608
+ - [Check the API reference](api-reference.md)
609
+ - [See migration guide](migration.md)
@@ -0,0 +1,114 @@
1
+ # Installation Guide
2
+
3
+ This guide will walk you through installing and setting up ActionAudit in your Rails application.
4
+
5
+ ## Prerequisites
6
+
7
+ - Rails 6.0 or higher
8
+ - Ruby 3.1.0 or higher
9
+
10
+ ## Installation
11
+
12
+ ### 1. Add to Gemfile
13
+
14
+ Add ActionAudit to your application's Gemfile:
15
+
16
+ ```ruby
17
+ gem 'action_audit'
18
+ ```
19
+
20
+ Then execute:
21
+
22
+ ```bash
23
+ bundle install
24
+ ```
25
+
26
+ ### 2. Run the Generator
27
+
28
+ ActionAudit provides a Rails generator to set up the initial configuration:
29
+
30
+ ```bash
31
+ rails generate action_audit:install
32
+ ```
33
+
34
+ This generator will create:
35
+ - `config/audit.yml` - Your audit message configuration file
36
+ - `config/initializers/action_audit.rb` - Configuration for custom formatting and tagging
37
+
38
+ ### 3. Include in Controllers
39
+
40
+ Add ActionAudit to your controllers. You can include it in your `ApplicationController` to enable auditing across all controllers:
41
+
42
+ ```ruby
43
+ # app/controllers/application_controller.rb
44
+ class ApplicationController < ActionController::Base
45
+ include ActionAudit
46
+
47
+ # Your existing code...
48
+ end
49
+ ```
50
+
51
+ Or include it in specific controllers:
52
+
53
+ ```ruby
54
+ # app/controllers/admin/users_controller.rb
55
+ class Admin::UsersController < ApplicationController
56
+ include ActionAudit
57
+
58
+ # Your controller actions...
59
+ end
60
+ ```
61
+
62
+ ### 4. Configure Audit Messages
63
+
64
+ Edit the generated `config/audit.yml` file to define your audit messages:
65
+
66
+ ```yaml
67
+ # config/audit.yml
68
+ admin:
69
+ users:
70
+ create: "Created user %{email}"
71
+ update: "Updated user %{id}"
72
+ destroy: "Deleted user %{id}"
73
+
74
+ sessions:
75
+ create: "User logged in with %{email}"
76
+ destroy: "User logged out"
77
+ ```
78
+
79
+ ### 5. Test Your Setup
80
+
81
+ Start your Rails server and trigger a controller action that should be audited. Check your Rails logs for audit entries.
82
+
83
+ ## Verification
84
+
85
+ To verify that ActionAudit is working correctly:
86
+
87
+ 1. Start your Rails console:
88
+ ```bash
89
+ rails console
90
+ ```
91
+
92
+ 2. Check that audit messages are loaded:
93
+ ```ruby
94
+ ActionAudit::AuditMessages.lookup("admin/users", "create")
95
+ # Should return: "Created user %{email}"
96
+ ```
97
+
98
+ 3. Test in your application by triggering an audited action and checking the logs.
99
+
100
+ ## Next Steps
101
+
102
+ - [Configure custom logging](configuration.md)
103
+ - [Learn about usage patterns](usage.md)
104
+ - [Set up multi-engine auditing](multi-engine.md)
105
+
106
+ ## Troubleshooting
107
+
108
+ If you encounter issues during installation:
109
+
110
+ 1. **Missing audit messages**: Ensure your `config/audit.yml` file exists and has the correct YAML structure
111
+ 2. **No log output**: Check that you've included `ActionAudit` in your controllers
112
+ 3. **Rails engine issues**: See the [Multi-Engine Setup Guide](multi-engine.md)
113
+
114
+ For more detailed troubleshooting, see the [Troubleshooting Guide](troubleshooting.md).