Module: KairosMcp::Admin::Helpers
- Included in:
- Router
- Defined in:
- lib/kairos_mcp/admin/helpers.rb
Overview
Helpers: ERB template helpers for the admin UI
Provides HTML escaping, template rendering, flash messages, CSRF protection, and session management utilities.
Constant Summary collapse
- VIEWS_DIR =
File.('views', __dir__)
- STATIC_DIR =
File.('static', __dir__)
- SESSION_COOKIE =
Session Management
'kairos_admin_session'- SESSION_SECRET =
ENV['KAIROS_SESSION_SECRET'] || SecureRandom.hex(32)
Instance Method Summary collapse
-
#clear_session_cookie ⇒ String
Build Set-Cookie header to clear session.
-
#decode_session(cookie_value) ⇒ Hash?
Decode and verify a signed session cookie.
-
#encode_session(data) ⇒ String
Encode a session value into a signed cookie.
-
#generate_csrf_token ⇒ String
Generate a CSRF token.
-
#get_session(env) ⇒ Hash?
Extract session from Rack env.
-
#h(text) ⇒ String
HTML-escape a string to prevent XSS.
-
#html_response(status, body) ⇒ Array
Generate an HTML response.
-
#parse_cookies(env) ⇒ Hash
Parse cookies from Rack env.
-
#parse_form_body(body) ⇒ Hash
Parse URL-encoded form body.
-
#parse_query(env) ⇒ Hash
Parse query string.
-
#redirect(path) ⇒ Array
Redirect response.
-
#render(template_name, layout: true, **locals) ⇒ String
Render an ERB template.
-
#render_layout(content) ⇒ String
Wrap content in the shared layout.
-
#render_partial(partial_name, **locals) ⇒ String
Render a partial (no layout).
-
#serve_static(filename) ⇒ Array
Serve a static file.
-
#session_cookie(data) ⇒ String
Build Set-Cookie header for session.
-
#valid_csrf?(env, session) ⇒ Boolean
Verify a CSRF token from form submission.
Instance Method Details
#clear_session_cookie ⇒ String
Build Set-Cookie header to clear session
163 164 165 |
# File 'lib/kairos_mcp/admin/helpers.rb', line 163 def "#{SESSION_COOKIE}=; Path=/admin; HttpOnly; SameSite=Strict; Max-Age=0" end |
#decode_session(cookie_value) ⇒ Hash?
Decode and verify a signed session cookie
128 129 130 131 132 133 134 135 136 137 138 139 |
# File 'lib/kairos_mcp/admin/helpers.rb', line 128 def decode_session() return nil unless encoded, signature = .split('--', 2) return nil unless encoded && signature return nil unless secure_compare(sign(encoded), signature) payload = encoded.unpack1('m0') JSON.parse(payload, symbolize_names: true) rescue StandardError nil end |
#encode_session(data) ⇒ String
Encode a session value into a signed cookie
117 118 119 120 121 122 |
# File 'lib/kairos_mcp/admin/helpers.rb', line 117 def encode_session(data) payload = JSON.generate(data) encoded = [payload].pack('m0') # Base64 (no newlines) signature = sign(encoded) "#{encoded}--#{signature}" end |
#generate_csrf_token ⇒ String
Generate a CSRF token
174 175 176 |
# File 'lib/kairos_mcp/admin/helpers.rb', line 174 def generate_csrf_token SecureRandom.hex(32) end |
#get_session(env) ⇒ Hash?
Extract session from Rack env
145 146 147 148 149 |
# File 'lib/kairos_mcp/admin/helpers.rb', line 145 def get_session(env) = (env) = [SESSION_COOKIE] decode_session() end |
#h(text) ⇒ String
HTML-escape a string to prevent XSS
22 23 24 |
# File 'lib/kairos_mcp/admin/helpers.rb', line 22 def h(text) ERB::Util.html_escape(text.to_s) end |
#html_response(status, body) ⇒ Array
Generate an HTML response
74 75 76 |
# File 'lib/kairos_mcp/admin/helpers.rb', line 74 def html_response(status, body) [status, { 'Content-Type' => 'text/html; charset=utf-8' }, [body]] end |
#parse_cookies(env) ⇒ Hash
Parse cookies from Rack env
205 206 207 208 209 210 211 |
# File 'lib/kairos_mcp/admin/helpers.rb', line 205 def (env) = env['HTTP_COOKIE'] || '' .split(';').each_with_object({}) do |pair, hash| key, value = pair.strip.split('=', 2) hash[key] = value if key end end |
#parse_form_body(body) ⇒ Hash
Parse URL-encoded form body
217 218 219 220 221 222 |
# File 'lib/kairos_mcp/admin/helpers.rb', line 217 def parse_form_body(body) body.split('&').each_with_object({}) do |pair, hash| key, value = pair.split('=', 2) hash[URI.decode_www_form_component(key)] = URI.decode_www_form_component(value || '') end end |
#parse_query(env) ⇒ Hash
Parse query string
228 229 230 231 |
# File 'lib/kairos_mcp/admin/helpers.rb', line 228 def parse_query(env) qs = env['QUERY_STRING'] || '' parse_form_body(qs) end |
#redirect(path) ⇒ Array
Redirect response
82 83 84 |
# File 'lib/kairos_mcp/admin/helpers.rb', line 82 def redirect(path) [302, { 'Location' => path }, []] end |
#render(template_name, layout: true, **locals) ⇒ String
Render an ERB template
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
# File 'lib/kairos_mcp/admin/helpers.rb', line 32 def render(template_name, layout: true, **locals) template_path = File.join(VIEWS_DIR, "#{template_name}.erb") template = ERB.new(File.read(template_path), trim_mode: '-') # Make locals available as instance variables for ERB binding b = binding locals.each { |k, v| b.local_variable_set(k, v) } content = template.result(b) if layout render_layout(content) else content end end |
#render_layout(content) ⇒ String
Wrap content in the shared layout
62 63 64 65 66 67 |
# File 'lib/kairos_mcp/admin/helpers.rb', line 62 def render_layout(content) @content = content layout_path = File.join(VIEWS_DIR, 'layout.erb') layout_template = ERB.new(File.read(layout_path), trim_mode: '-') layout_template.result(binding) end |
#render_partial(partial_name, **locals) ⇒ String
Render a partial (no layout)
54 55 56 |
# File 'lib/kairos_mcp/admin/helpers.rb', line 54 def render_partial(partial_name, **locals) render("partials/#{partial_name}", layout: false, **locals) end |
#serve_static(filename) ⇒ Array
Serve a static file
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
# File 'lib/kairos_mcp/admin/helpers.rb', line 90 def serve_static(filename) filepath = File.join(STATIC_DIR, filename) return [404, {}, ['Not found']] unless File.exist?(filepath) content_type = case File.extname(filename) when '.css' then 'text/css' when '.js' then 'application/javascript' when '.png' then 'image/png' when '.svg' then 'image/svg+xml' else 'application/octet-stream' end [200, { 'Content-Type' => content_type, 'Cache-Control' => 'public, max-age=3600' }, [File.read(filepath)]] end |
#session_cookie(data) ⇒ String
Build Set-Cookie header for session
155 156 157 158 |
# File 'lib/kairos_mcp/admin/helpers.rb', line 155 def (data) value = encode_session(data) "#{SESSION_COOKIE}=#{value}; Path=/admin; HttpOnly; SameSite=Strict" end |
#valid_csrf?(env, session) ⇒ Boolean
Verify a CSRF token from form submission
183 184 185 186 187 188 189 190 191 192 193 194 195 |
# File 'lib/kairos_mcp/admin/helpers.rb', line 183 def valid_csrf?(env, session) body = env['rack.input']&.read env['rack.input']&.rewind return false unless body params = parse_form_body(body) submitted_token = params['_csrf'] session_token = session&.dig(:csrf_token) return false unless submitted_token && session_token secure_compare(submitted_token, session_token) end |