From 303d90646f0acba451d095b5faa9598891643922 Mon Sep 17 00:00:00 2001 From: Mattia Roccoberton Date: Sun, 22 Mar 2026 14:09:43 +0000 Subject: [PATCH 1/2] refactor(core): consolidate Utils usage, extract authorize! helper, harden settings Consolidate to_class into BasicApp.authentication_plugin via Utils include. Use Utils.humanize in Field.create_field instead of inline logic. Extract repeated authorization checks in Router into a single authorize! helper method. Guard Settings#load_settings against nil authentication plugin and make it idempotent with @loaded flag so repeated calls (e.g. per-request) are a no-op. --- lib/tiny_admin/basic_app.rb | 4 +++- lib/tiny_admin/field.rb | 5 +++-- lib/tiny_admin/router.rb | 28 +++++++++++++--------------- lib/tiny_admin/settings.rb | 7 ++++++- 4 files changed, 25 insertions(+), 19 deletions(-) diff --git a/lib/tiny_admin/basic_app.rb b/lib/tiny_admin/basic_app.rb index 1d73ce4..0653101 100644 --- a/lib/tiny_admin/basic_app.rb +++ b/lib/tiny_admin/basic_app.rb @@ -5,9 +5,11 @@ class BasicApp < Roda include Utils class << self + include Utils + def authentication_plugin plugin = TinyAdmin.settings.authentication&.dig(:plugin) - plugin_class = plugin.is_a?(String) ? Object.const_get(plugin) : plugin + plugin_class = to_class(plugin) if plugin plugin_class || TinyAdmin::Plugins::NoAuth end end diff --git a/lib/tiny_admin/field.rb b/lib/tiny_admin/field.rb index 85748eb..7d2a1fa 100644 --- a/lib/tiny_admin/field.rb +++ b/lib/tiny_admin/field.rb @@ -30,10 +30,11 @@ def translate_value(value) end class << self + include Utils + def create_field(name:, title: nil, type: nil, options: {}) field_name = name.to_s - field_title = field_name.respond_to?(:humanize) ? field_name.humanize : field_name.tr("_", " ").capitalize - new(name: field_name, title: title || field_title, type: type || :string, options: options || {}) + new(name: field_name, title: title || humanize(field_name), type: type || :string, options: options || {}) end end end diff --git a/lib/tiny_admin/router.rb b/lib/tiny_admin/router.rb index 2a372b0..a49be68 100644 --- a/lib/tiny_admin/router.rb +++ b/lib/tiny_admin/router.rb @@ -54,7 +54,7 @@ def render_page(page) end def root_route(req) - if authorization.allowed?(current_user, :root) + authorize!(:root) do if TinyAdmin.settings.root[:redirect] req.redirect route_for(TinyAdmin.settings.root[:redirect]) else @@ -62,18 +62,14 @@ def root_route(req) attributes = TinyAdmin.settings.root.slice(:content, :title, :widgets) render_page prepare_page(page_class, attributes: attributes, params: request.params) end - else - render_page prepare_page(TinyAdmin.settings.page_not_allowed) end end def setup_page_route(req, slug, page_data) req.get slug do - if authorization.allowed?(current_user, :page, slug) + authorize!(:page, slug) do attributes = page_data.slice(:content, :title, :widgets) render_page prepare_page(page_data[:class], slug: slug, attributes: attributes, params: request.params) - else - render_page prepare_page(TinyAdmin.settings.page_not_allowed) end end end @@ -101,7 +97,7 @@ def setup_collection_routes(req, slug, options:) # Index if options[:only].include?(:index) || options[:only].include?("index") req.is do - if authorization.allowed?(current_user, :resource_index, slug) + authorize!(:resource_index, slug) do context = Context.new( actions: custom_actions, repository: repository, @@ -111,8 +107,6 @@ def setup_collection_routes(req, slug, options:) ) index_action = TinyAdmin::Actions::Index.new render_page index_action.call(app: self, context: context, options: action_options) - else - render_page prepare_page(TinyAdmin.settings.page_not_allowed) end end end @@ -136,7 +130,7 @@ def setup_member_routes(req, slug, options:) # Show if options[:only].include?(:show) || options[:only].include?("show") req.is do - if authorization.allowed?(current_user, :resource_show, slug) + authorize!(:resource_show, slug) do context = Context.new( actions: custom_actions, reference: reference, @@ -147,8 +141,6 @@ def setup_member_routes(req, slug, options:) ) show_action = TinyAdmin::Actions::Show.new render_page show_action.call(app: self, context: context, options: action_options) - else - render_page prepare_page(TinyAdmin.settings.page_not_allowed) end end end @@ -161,7 +153,7 @@ def setup_custom_actions(req, custom_actions = nil, options:, repository:, slug: action_class = to_class(action) req.get action_slug.to_s do - if authorization.allowed?(current_user, :custom_action, action_slug.to_s) + authorize!(:custom_action, action_slug.to_s) do context = Context.new( actions: {}, reference: reference, @@ -172,8 +164,6 @@ def setup_custom_actions(req, custom_actions = nil, options:, repository:, slug: ) custom_action = action_class.new render_page custom_action.call(app: self, context: context, options: options) - else - render_page prepare_page(TinyAdmin.settings.page_not_allowed) end end @@ -184,5 +174,13 @@ def setup_custom_actions(req, custom_actions = nil, options:, repository:, slug: def authorization TinyAdmin.settings.authorization_class end + + def authorize!(action, param = nil) + if authorization.allowed?(current_user, action, param) + yield + else + render_page prepare_page(TinyAdmin.settings.page_not_allowed) + end + end end end diff --git a/lib/tiny_admin/settings.rb b/lib/tiny_admin/settings.rb index 1f546a0..9bd8cca 100644 --- a/lib/tiny_admin/settings.rb +++ b/lib/tiny_admin/settings.rb @@ -67,6 +67,8 @@ def []=(*path, value) end def load_settings + return if @loaded + # default values DEFAULTS.each do |(option, param), default| if param @@ -80,15 +82,18 @@ def load_settings @store ||= TinyAdmin::Store.new(self) self.root_path = "/" if root_path == "" - if authentication[:plugin] <= Plugins::SimpleAuth + if authentication[:plugin].is_a?(Module) && authentication[:plugin] <= Plugins::SimpleAuth logout_path = "#{root_path}/auth/logout" authentication[:logout] ||= TinyAdmin::Section.new(name: "logout", slug: "logout", path: logout_path) end store.prepare_sections(sections, logout: authentication[:logout]) + @loaded = true end def reset! @options = {} + @store = nil + @loaded = false end private From b85ada987ccf206fb12c546722a0fb679f1f62f0 Mon Sep 17 00:00:00 2001 From: Mattia Roccoberton Date: Sun, 22 Mar 2026 14:10:07 +0000 Subject: [PATCH 2/2] feat(auth): accept session secret from TINY_ADMIN_SECRET env var The session secret was regenerated with SecureRandom.hex(64) on every boot, which invalidates all existing sessions on restart. Now reads from ENV["TINY_ADMIN_SECRET"] with fallback to the random value, so production deployments can maintain stable sessions across restarts. --- lib/tiny_admin/basic_app.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tiny_admin/basic_app.rb b/lib/tiny_admin/basic_app.rb index 0653101..a14e97f 100644 --- a/lib/tiny_admin/basic_app.rb +++ b/lib/tiny_admin/basic_app.rb @@ -17,7 +17,7 @@ def authentication_plugin plugin :flash plugin :not_found plugin :render, engine: "html" - plugin :sessions, secret: SecureRandom.hex(64) + plugin :sessions, secret: ENV.fetch("TINY_ADMIN_SECRET") { SecureRandom.hex(64) } plugin authentication_plugin, TinyAdmin.settings.authentication