mirror of https://github.com/docusealco/docuseal
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
83 lines
2.6 KiB
83 lines
2.6 KiB
# frozen_string_literal: true
|
|
|
|
require 'aws-sdk-cloudfront'
|
|
|
|
# Service for handling secure document access with CloudFront signed URLs
|
|
# Reuses same infrastructure and key pairs as ATS
|
|
class DocumentSecurityService
|
|
class << self
|
|
# Generate signed URL for a secured attachment
|
|
# @param attachment [ActiveStorage::Attachment] The attachment to generate URL for
|
|
# @param expires_in [ActiveSupport::Duration] How long the URL should be valid
|
|
# @return [String] Signed CloudFront URL
|
|
def signed_url_for(attachment, expires_in: 1.hour)
|
|
return attachment.url unless cloudfront_configured?
|
|
|
|
# Get the CloudFront URL for this attachment
|
|
cloudfront_url = build_cloudfront_url(attachment)
|
|
|
|
# Generate signed URL using same system as ATS
|
|
signer = cloudfront_signer
|
|
signer.signed_url(cloudfront_url, expires: expires_in.from_now.to_i)
|
|
rescue StandardError => e
|
|
Rails.logger.error("Failed to generate signed URL: #{e.message}")
|
|
# Fallback to direct URL if signing fails
|
|
attachment.url
|
|
end
|
|
|
|
private
|
|
|
|
def cloudfront_configured?
|
|
cloudfront_base_url.present? &&
|
|
cloudfront_key_pair_id.present? &&
|
|
cloudfront_private_key.present?
|
|
end
|
|
|
|
def cloudfront_signer
|
|
@cloudfront_signer ||= Aws::CloudFront::UrlSigner.new(
|
|
key_pair_id: cloudfront_key_pair_id,
|
|
private_key: cloudfront_private_key
|
|
)
|
|
end
|
|
|
|
def build_cloudfront_url(attachment)
|
|
key = ensure_docuseal_prefix(attachment.blob.key)
|
|
base_url = "#{cloudfront_base_url}/#{key}"
|
|
query_string = build_query_params(attachment)
|
|
|
|
"#{base_url}?#{query_string}"
|
|
end
|
|
|
|
def ensure_docuseal_prefix(s3_key)
|
|
s3_key.start_with?('docuseal/') ? s3_key : "docuseal/#{s3_key}"
|
|
end
|
|
|
|
def build_query_params(attachment)
|
|
filename = attachment.blob.filename.to_s.presence || 'download.pdf'
|
|
|
|
{
|
|
'response-content-disposition' => content_disposition_for(filename),
|
|
'response-content-type' => attachment.blob.content_type
|
|
}.to_query
|
|
end
|
|
|
|
def content_disposition_for(filename)
|
|
# RFC 6266 with RFC 5987 encoding for international characters
|
|
rfc5987_encoded = CGI.escape(filename)
|
|
"inline; filename=\"#{filename}\"; filename*=UTF-8''#{rfc5987_encoded}"
|
|
end
|
|
|
|
def cloudfront_base_url
|
|
@cloudfront_base_url ||= ENV.fetch('CF_URL', nil)
|
|
end
|
|
|
|
def cloudfront_key_pair_id
|
|
@cloudfront_key_pair_id ||= ENV.fetch('CF_KEY_PAIR_ID', nil)
|
|
end
|
|
|
|
def cloudfront_private_key
|
|
@cloudfront_private_key ||= ENV.fetch('SECURE_ATTACHMENT_PRIVATE_KEY', nil)
|
|
end
|
|
end
|
|
end
|