Skip to content

Commit 73e8e95

Browse files
Add receipt preview lightbox with carousel navigation
- Add receiptPreview.js component with keyboard/swipe navigation - Add receipt-preview.css with lightbox modal styling - Add receipt indicator icon on expense rows (clickable to preview) - Fix X-Frame-Options to allow PDF iframe embedding - Exempt receipt serving from rate limiting - Remove preview button from modals (click thumbnail instead) - Move Export & Backup card, remove HSA Balance from settings 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent 9496792 commit 73e8e95

File tree

9 files changed

+974
-87
lines changed

9 files changed

+974
-87
lines changed

app/app.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""HSA/FSA Expense & Reimbursement Tracker - Main Application"""
2-
from flask import Flask, session, jsonify, render_template
2+
from flask import Flask, session, jsonify, render_template, request
33
import os
44
import secrets
55
import logging
@@ -163,6 +163,8 @@ def inject_user_preferences():
163163
if limiter:
164164
limiter.limit("5 per minute")(app.view_functions['auth.login'])
165165
limiter.limit("3 per hour")(app.view_functions['auth.register'])
166+
# Exempt receipt serving from rate limits (PDFs trigger many requests for page rendering)
167+
limiter.exempt(app.view_functions['attachments.serve_receipt'])
166168

167169
# ============================================================================
168170
# Error Handlers - Custom error pages for production
@@ -191,9 +193,15 @@ def forbidden_error(error):
191193
def set_security_headers(response):
192194
"""Add security headers to all responses"""
193195
response.headers['X-Content-Type-Options'] = 'nosniff'
194-
response.headers['X-Frame-Options'] = 'DENY'
195196
response.headers['X-XSS-Protection'] = '1; mode=block'
196197

198+
# Allow same-origin framing for receipt files (needed for PDF preview)
199+
# Use SAMEORIGIN for /receipts/ paths, DENY for everything else
200+
if request.path.startswith('/receipts/'):
201+
response.headers['X-Frame-Options'] = 'SAMEORIGIN'
202+
else:
203+
response.headers['X-Frame-Options'] = 'DENY'
204+
197205
# Only set HSTS in production
198206
if os.environ.get('FLASK_ENV') == 'production':
199207
response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'

0 commit comments

Comments
 (0)