# frozen_string_literal: true class DsnAuthenticationService class AuthenticationError < StandardError; end def self.authenticate(request, project_id) # Try multiple authentication methods in order of preference # Method 1: Query parameter authentication public_key = extract_key_from_query_params(request) return find_project(public_key, project_id) if public_key # Method 2: X-Baffle-Auth header (similar to X-Sentry-Auth) public_key = extract_key_from_baffle_auth_header(request) return find_project(public_key, project_id) if public_key # Method 3: Authorization Bearer token public_key = extract_key_from_authorization_header(request) return find_project(public_key, project_id) if public_key # Method 4: Basic auth (username is the public_key) public_key = extract_key_from_basic_auth(request) return find_project(public_key, project_id) if public_key raise AuthenticationError, "No valid authentication method found" end private def self.extract_key_from_query_params(request) # Support both baffle_key and sentry_key for compatibility request.GET['baffle_key'] || request.GET['sentry_key'] || request.GET['glitchtip_key'] end def self.extract_key_from_baffle_auth_header(request) auth_header = request.headers['X-Baffle-Auth'] || request.headers['X-Sentry-Auth'] return nil unless auth_header # Parse: Baffle baffle_key=public_key, baffle_version=1 # Or: Sentry sentry_key=public_key, sentry_version=7 match = auth_header.match(/(?:baffle_key|sentry_key)=([^,\s]+)/) match&.[](1) end def self.extract_key_from_authorization_header(request) authorization_header = request.headers['Authorization'] return nil unless authorization_header # Parse: Bearer public_key if authorization_header.start_with?('Bearer ') authorization_header[7..-1].strip end end def self.extract_key_from_basic_auth(request) authorization_header = request.headers['Authorization'] return nil unless authorization_header&.start_with?('Basic ') # Decode basic auth: username:password (password is ignored) credentials = Base64.decode64(authorization_header[6..-1]) username = credentials.split(':').first username end def self.find_project(public_key, project_id) return nil unless public_key.present? && project_id.present? # Find project by public_key first project = Project.find_by(public_key: public_key) raise AuthenticationError, "Invalid public_key" unless project # Verify project_id matches (supports both slug and ID) project_matches = Project.find_by(slug: project_id) || Project.find_by(id: project_id) raise AuthenticationError, "Invalid project_id" unless project_matches == project # Ensure project is enabled raise AuthenticationError, "Project is disabled" unless project.enabled? project end end