Skip to content

Commit 65a4139

Browse files
committed
clean
1 parent d2147b2 commit 65a4139

File tree

14 files changed

+375
-596
lines changed

14 files changed

+375
-596
lines changed

app.rb

Lines changed: 217 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
require 'roda'
44
require 'rack/cache'
55
require 'json'
6+
require 'base64'
67

78
require 'html2rss'
89
require_relative 'app/environment_validator'
@@ -11,6 +12,7 @@
1112
require_relative 'app/auto_source'
1213
require_relative 'app/feeds'
1314
require_relative 'app/health_check'
15+
require_relative 'app/local_config'
1416
require_relative 'app/response_context'
1517
require_relative 'app/xml_builder'
1618
require_relative 'app/security_logger'
@@ -33,17 +35,73 @@ def development? = self.class.development?
3335
RodaConfig.configure(self)
3436

3537
plugin :hash_branches
38+
plugin :json_parser
39+
plugin :public
40+
plugin :exception_page
41+
plugin :error_handler do |error|
42+
next exception_page(error) if development?
3643

37-
Dir['routes/*.rb'].each { |f| require_relative f }
44+
response.status = 500
45+
response['Content-Type'] = 'application/xml'
46+
require_relative 'app/xml_builder'
47+
XmlBuilder.build_error_feed(message: error.message)
48+
end
3849

39-
@show_backtrace = development? && !ENV['CI']
50+
# Routes are now defined directly in this file for better clarity
4051

41-
AppRoutes.load_routes(self)
52+
@show_backtrace = development? && !ENV['CI']
4253

4354
route do |r|
4455
r.public
45-
r.hash_branches('')
4656

57+
# API routes
58+
r.on 'api' do
59+
r.response['Content-Type'] = 'application/json'
60+
61+
r.get 'feeds.json' do
62+
r.response['Cache-Control'] = 'public, max-age=300'
63+
JSON.generate(Feeds.list_feeds)
64+
end
65+
66+
r.get 'strategies.json' do
67+
r.response['Cache-Control'] = 'public, max-age=3600'
68+
JSON.generate(list_available_strategies)
69+
end
70+
71+
r.get String do |feed_name|
72+
handle_feed_generation(r, feed_name)
73+
end
74+
end
75+
76+
# Auto source routes
77+
r.on 'auto_source' do
78+
if AutoSource.enabled?
79+
r.post 'create' do
80+
handle_create_feed(r)
81+
end
82+
83+
r.get String do |encoded_url|
84+
handle_legacy_feed(r, encoded_url)
85+
end
86+
else
87+
r.response.status = 400
88+
'Auto source feature is disabled'
89+
end
90+
end
91+
92+
# Feed routes
93+
r.on 'feeds' do
94+
r.get String do |feed_id|
95+
handle_stable_feed(r, feed_id)
96+
end
97+
end
98+
99+
# Health check
100+
r.get 'health_check.txt' do
101+
handle_health_check(r)
102+
end
103+
104+
# Root route
47105
r.root do
48106
index_path = 'public/frontend/index.html'
49107
response['Content-Type'] = 'text/html'
@@ -77,6 +135,161 @@ def fallback_html
77135
</html>
78136
HTML
79137
end
138+
139+
private
140+
141+
def list_available_strategies
142+
strategies = Html2rss::RequestService.strategy_names.map do |name|
143+
{
144+
name: name.to_s,
145+
display_name: name.to_s.split('_').map(&:capitalize).join(' ')
146+
}
147+
end
148+
149+
{ strategies: strategies }
150+
end
151+
152+
def handle_feed_generation(router, feed_name)
153+
rss_content = Feeds.generate_feed(feed_name, router.params)
154+
config = LocalConfig.find(feed_name)
155+
ttl = config.dig(:channel, :ttl) || 3600
156+
157+
router.response['Content-Type'] = 'application/xml'
158+
router.response['Cache-Control'] = "public, max-age=#{ttl}"
159+
rss_content
160+
end
161+
162+
def handle_create_feed(router)
163+
account = Auth.authenticate(router)
164+
unless account
165+
router.response.status = 401
166+
return 'Unauthorized'
167+
end
168+
169+
url = router.params['url']
170+
unless url && Auth.valid_url?(url)
171+
router.response.status = 400
172+
return 'Bad Request'
173+
end
174+
175+
unless Auth.url_allowed?(account, url)
176+
router.response.status = 403
177+
return 'Forbidden'
178+
end
179+
180+
strategy = router.params['strategy'] || 'ssrf_filter'
181+
feed_data = AutoSource.create_stable_feed('Generated Feed', url, account, strategy)
182+
unless feed_data
183+
router.response.status = 500
184+
return 'Internal Server Error'
185+
end
186+
187+
router.response['Content-Type'] = 'application/json'
188+
JSON.generate(feed_data)
189+
end
190+
191+
def handle_legacy_feed(router, encoded_url)
192+
account = Auth.authenticate(router)
193+
unless account
194+
router.response.status = 401
195+
return 'Unauthorized'
196+
end
197+
198+
unless AutoSource.allowed_origin?(router)
199+
router.response.status = 403
200+
return 'Forbidden'
201+
end
202+
203+
decoded_url = Base64.urlsafe_decode64(encoded_url)
204+
unless decoded_url && Auth.valid_url?(decoded_url)
205+
router.response.status = 400
206+
return 'Bad Request'
207+
end
208+
209+
unless AutoSource.url_allowed_for_token?(account, decoded_url)
210+
router.response.status = 403
211+
return 'Forbidden'
212+
end
213+
214+
strategy = router.params['strategy'] || 'ssrf_filter'
215+
rss_content = AutoSource.generate_feed_content(decoded_url, strategy)
216+
217+
router.response['Content-Type'] = 'application/xml'
218+
rss_content.to_s
219+
rescue ArgumentError
220+
router.response.status = 400
221+
'Bad Request'
222+
end
223+
224+
def handle_stable_feed(router, feed_id)
225+
feed_token = router.params['token']
226+
227+
if feed_token
228+
handle_public_feed(router, feed_id, feed_token)
229+
else
230+
handle_authenticated_feed(router)
231+
end
232+
end
233+
234+
def handle_public_feed(router, _feed_id, feed_token)
235+
url = router.params['url']
236+
unless url && Auth.valid_url?(url)
237+
router.response.status = 400
238+
return 'Bad Request'
239+
end
240+
241+
unless Auth.feed_url_allowed?(feed_token, url)
242+
router.response.status = 403
243+
return 'Forbidden'
244+
end
245+
246+
strategy = router.params['strategy'] || 'ssrf_filter'
247+
rss_content = AutoSource.generate_feed_content(url, strategy)
248+
249+
router.response['Content-Type'] = 'application/xml'
250+
rss_content.to_s
251+
end
252+
253+
def handle_authenticated_feed(router)
254+
account = Auth.authenticate(router)
255+
unless account
256+
router.response.status = 401
257+
return 'Unauthorized'
258+
end
259+
260+
url = router.params['url']
261+
unless url && Auth.valid_url?(url)
262+
router.response.status = 400
263+
return 'Bad Request'
264+
end
265+
266+
unless Auth.url_allowed?(account, url)
267+
router.response.status = 403
268+
return 'Forbidden'
269+
end
270+
271+
strategy = router.params['strategy'] || 'ssrf_filter'
272+
rss_content = AutoSource.generate_feed_content(url, strategy)
273+
274+
router.response['Content-Type'] = 'application/xml'
275+
rss_content.to_s
276+
end
277+
278+
def handle_health_check(router)
279+
health_check_account = HealthCheck.find_health_check_account
280+
account = Auth.authenticate(router)
281+
282+
if account && health_check_account && account[:token] == health_check_account[:token]
283+
router.response['Content-Type'] = 'text/plain'
284+
HealthCheck.run
285+
else
286+
router.response.status = 401
287+
router.response['WWW-Authenticate'] = 'Bearer realm="Health Check"'
288+
router.response['Content-Type'] = 'application/xml'
289+
require_relative 'app/xml_builder'
290+
XmlBuilder.build_error_feed(message: 'Unauthorized', title: 'Health Check Unauthorized')
291+
end
292+
end
80293
end
81294
end
82295
end

app/roda_config.rb

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -104,21 +104,11 @@ def advanced_security_headers
104104
end
105105

106106
def setup_error_handling(app)
107-
app.plugin :exception_page
108-
app.plugin :error_handler do |error|
109-
next exception_page(error) if app.development?
110-
111-
response.status = 500
112-
response['Content-Type'] = 'application/xml'
113-
require_relative 'xml_builder'
114-
XmlBuilder.build_error_feed(message: error.message)
115-
end
107+
# Error handling is now configured directly in app.rb for better clarity
116108
end
117109

118110
def setup_plugins(app)
119-
app.plugin :public
120-
app.plugin :hash_branches
121-
app.plugin :json_parser # Handle JSON request bodies automatically
111+
# Plugins are now configured directly in app.rb for better clarity
122112
end
123113
end
124114
end

frontend/astro.config.mjs

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,26 +11,11 @@ export default defineConfig({
1111
vite: {
1212
server: {
1313
proxy: {
14-
"/api": {
15-
target: "http://localhost:3000",
16-
changeOrigin: true,
17-
},
18-
"/auto_source": {
19-
target: "http://localhost:3000",
20-
changeOrigin: true,
21-
},
22-
"/feeds": {
23-
target: "http://localhost:3000",
24-
changeOrigin: true,
25-
},
26-
"/health_check.txt": {
27-
target: "http://localhost:3000",
28-
changeOrigin: true,
29-
},
30-
"/rss.xsl": {
31-
target: "http://localhost:3000",
32-
changeOrigin: true,
33-
},
14+
"/api": "http://localhost:3000",
15+
"/auto_source": "http://localhost:3000",
16+
"/feeds": "http://localhost:3000",
17+
"/health_check.txt": "http://localhost:3000",
18+
"/rss.xsl": "http://localhost:3000",
3419
},
3520
},
3621
},

0 commit comments

Comments
 (0)