develop #3

Merged
kbe merged 227 commits from develop into main 2025-09-16 14:35:23 +00:00
2 changed files with 281 additions and 6 deletions
Showing only changes of commit 8ad2194d48 - Show all commits

View File

@@ -1,6 +1,6 @@
<div class="container mx-auto px-4 sm:px-6 lg:px-8 py-8"> <div class="container mx-auto px-4 sm:px-6 lg:px-8 py-8">
<!-- Breadcrumb --> <!-- Breadcrumb -->
<nav class="flex mb-6" aria-label="Breadcrumb"> <nav class="flex my-6" aria-label="Breadcrumb">
<ol class="inline-flex items-center space-x-1 md:space-x-2 rounded-lg bg-white px-4 py-2 shadow-sm"> <ol class="inline-flex items-center space-x-1 md:space-x-2 rounded-lg bg-white px-4 py-2 shadow-sm">
<li class="inline-flex items-center"> <li class="inline-flex items-center">
<%= link_to "Accueil", root_path, class: "inline-flex items-center text-sm font-medium text-gray-700 hover:text-purple-600" %> <%= link_to "Accueil", root_path, class: "inline-flex items-center text-sm font-medium text-gray-700 hover:text-purple-600" %>

View File

@@ -0,0 +1,275 @@
# Ticket Download Security Implementation
## Overview
This document describes how to implement secure unique identifiers for ticket PDF downloads to enhance security and prevent unauthorized access to user tickets.
## Problem Statement
Currently, the ticket download functionality uses the QR code directly as an identifier in URLs. This approach presents several security risks:
1. **Predictability**: QR codes may follow predictable patterns
2. **Information Disclosure**: QR codes might reveal internal system information
3. **Brute Force Vulnerability**: Attackers can enumerate valid tickets
4. **Lack of Revocability**: Cannot invalidate download links without affecting the QR code
## Solution
Implement a separate, cryptographically secure unique identifier specifically for PDF downloads.
## Implementation Steps
### 1. Database Migration
Create a migration to add the new column:
```ruby
# db/migrate/xxx_add_pdf_download_token_to_tickets.rb
class AddPdfDownloadTokenToTickets < ActiveRecord::Migration[7.0]
def change
add_column :tickets, :pdf_download_token, :string, limit: 50
add_column :tickets, :pdf_download_token_expires_at, :datetime
add_index :tickets, :pdf_download_token, unique: true
end
end
```
### 2. Model Implementation
Update the Ticket model to generate secure tokens:
```ruby
# app/models/ticket.rb
class Ticket < ApplicationRecord
before_create :generate_pdf_download_token
# Generate a secure token for PDF downloads
def generate_pdf_download_token
self.pdf_download_token = SecureRandom.urlsafe_base64(32)
self.pdf_download_token_expires_at = 24.hours.from_now
end
# Check if the download token is still valid
def pdf_download_token_valid?
pdf_download_token.present? &&
pdf_download_token_expires_at.present? &&
pdf_download_token_expires_at > Time.current
end
# Regenerate token (useful for security or when token expires)
def regenerate_pdf_download_token
generate_pdf_download_token
save!
end
# Ensure tokens are generated for existing records
def ensure_pdf_download_token
if pdf_download_token.blank?
generate_pdf_download_token
save!
end
end
end
```
### 3. Controller Updates
Update the TicketsController to use the new token system:
```ruby
# app/controllers/tickets_controller.rb
class TicketsController < ApplicationController
before_action :authenticate_user!
def show
@ticket = Ticket.joins(order: :user)
.includes(:event, :ticket_type, order: :user)
.find_by(tickets: { qr_code: params[:qr_code] })
if @ticket.nil?
redirect_to dashboard_path, alert: "Billet non trouvé"
return
end
@event = @ticket.event
@order = @ticket.order
end
def download
# Find ticket by PDF download token instead of QR code
@ticket = Ticket.find_by(pdf_download_token: params[:pdf_download_token])
# Check if ticket exists
if @ticket.nil?
redirect_to dashboard_path, alert: "Lien de téléchargement invalide ou expiré"
return
end
# Verify token validity
unless @ticket.pdf_download_token_valid?
redirect_to dashboard_path, alert: "Le lien de téléchargement a expiré"
return
end
# Verify ownership
unless @ticket.order.user == current_user
redirect_to dashboard_path, alert: "Vous n'avez pas l'autorisation d'accéder à ce billet"
return
end
# Generate and send PDF
pdf_content = @ticket.to_pdf
# Optionally regenerate token to make it single-use
# @ticket.regenerate_pdf_download_token
send_data pdf_content,
filename: "ticket_#{@ticket.id}_#{@ticket.event.name.parameterize}.pdf",
type: "application/pdf",
disposition: "attachment"
rescue => e
Rails.logger.error "Error generating ticket PDF: #{e.message}"
redirect_to dashboard_path, alert: "Erreur lors de la génération du billet"
end
end
```
### 4. Route Configuration
Update routes to use the new token-based system:
```ruby
# config/routes.rb
Rails.application.routes.draw do
# Existing routes...
# Update ticket download route
get "tickets/:pdf_download_token/download", to: "tickets#download", as: "ticket_download"
# Keep existing show route for QR code functionality
get "tickets/:qr_code", to: "tickets#show", as: "ticket"
end
```
### 5. View Updates
Update views to use the new download URL:
```erb
<!-- In app/views/tickets/show.html.erb -->
<%= link_to ticket_download_path(@ticket.pdf_download_token),
class: "flex-1 bg-gradient-to-r from-purple-600 to-indigo-600 hover:from-purple-700 hover:to-indigo-700 text-white font-medium py-3 px-6 rounded-xl shadow-sm transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2 transform hover:-translate-y-0.5 text-center" do %>
<svg class="w-4 h-4 inline-block mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
</svg>
Télécharger le PDF
<% end %>
```
### 6. Background Job for Token Management
Create a job to clean up expired tokens periodically:
```ruby
# app/jobs/cleanup_expired_ticket_tokens_job.rb
class CleanupExpiredTicketTokensJob < ApplicationJob
queue_as :default
def perform
# Clear expired tokens to free up database space
Ticket.where("pdf_download_token_expires_at < ?", 1.week.ago)
.update_all(pdf_download_token: nil, pdf_download_token_expires_at: nil)
end
end
```
Schedule this job to run regularly:
```ruby
# config/schedule.rb (if using whenever gem)
every 1.day, at: '4:30 am' do
rake "tickets:cleanup_expired_tokens"
end
```
## Security Benefits
1. **Unpredictability**: Tokens are cryptographically secure and random
2. **Separation of Concerns**: QR codes for physical entry, tokens for digital downloads
3. **Revocability**: Tokens can be regenerated without affecting QR codes
4. **Expirability**: Time-limited access prevents long-term exposure
5. **Ownership Verification**: Additional checks ensure only ticket owners can download
6. **Audit Trail**: Token usage can be logged for security monitoring
## Additional Security Considerations
### Rate Limiting
Implement rate limiting to prevent abuse:
```ruby
# In ApplicationController or specific controller
before_action :rate_limit_downloads, only: [:download]
def rate_limit_downloads
if Rails.cache.read("download_attempts_#{current_user.id}")&.to_i > 10
render json: { error: "Too many download attempts" }, status: :too_many_requests
else
Rails.cache.write("download_attempts_#{current_user.id}",
(Rails.cache.read("download_attempts_#{current_user.id}") || 0) + 1,
expires_in: 1.hour)
end
end
```
### Logging
Add logging for security monitoring:
```ruby
# In TicketsController#download
Rails.logger.info "Ticket PDF download attempted - User: #{current_user.id}, Ticket: #{@ticket.id}, Token: #{params[:pdf_download_token]}"
```
## Migration Process
1. Run the database migration
2. Update existing tickets with tokens:
```ruby
# In rails console or a rake task
Ticket.find_each(&:ensure_pdf_download_token)
```
3. Deploy code changes
4. Update any external references to use the new system
5. Monitor for issues and adjust expiration times as needed
## Testing
Ensure comprehensive testing of the new functionality:
```ruby
# spec/controllers/tickets_controller_spec.rb
RSpec.describe TicketsController, type: :controller do
describe "GET #download" do
it "downloads PDF for valid token" do
# Test implementation
end
it "rejects expired tokens" do
# Test implementation
end
it "rejects invalid tokens" do
# Test implementation
end
it "verifies ticket ownership" do
# Test implementation
end
end
end
```
## Conclusion
This implementation provides a robust security framework for ticket PDF downloads while maintaining usability. The separation of QR codes (for physical entry) and download tokens (for digital access) follows security best practices and provides multiple layers of protection against unauthorized access.