3 Commits

Author SHA1 Message Date
fde1dd14b5 use jsonify() helper
Some checks failed
Gitea Actions Demo / test (push) Blocked by required conditions
Gitea Actions Demo / release-image (push) Blocked by required conditions
Gitea Actions Demo / lint (push) Has been cancelled
2025-03-09 15:35:40 -05:00
a4955d35fa add configuration class 2025-03-09 15:35:40 -05:00
aa907dfa5f add class to wrap sensitive values 2025-03-09 15:35:40 -05:00
5 changed files with 164 additions and 15 deletions

38
app.rb
View File

@ -13,9 +13,14 @@ require "anyflake"
require "jwt"
SESSION_SECRET_HEX_LENGTH = 64
$LOAD_PATH.unshift File.dirname(__FILE__) + "/lib"
set :session_secret, ENV.fetch("SESSION_SECRET") { SecureRandom.hex(SESSION_SECRET_HEX_LENGTH) }
require "config"
SESSION_SECRET_HEX_LENGTH = 64
JWT_SECRET_HEX_LENGTH = 64
ENV_PREFIX = "KIPUNJI"
CLK_TCK = 100
PID_FILE_PATH = "/run/app/pid".freeze
@ -38,7 +43,9 @@ DURATION_PARTS = [
[1, "second", "s"]
].freeze
JWT_SECRET = SecureRandom.bytes(64).freeze
config = Config.new
set :session_secret, config.session_secret.unwrap
module Sinatra
module RequestHeadersHelper
@ -209,12 +216,13 @@ before do
end
helpers do
def json(obj, opts: nil, pretty: false)
if pretty
def jsonify(obj, opts: nil, pretty: false)
buf = if pretty
JSON.pretty_generate obj, opts:
else
JSON.generate(obj, opts:)
end
"#{buf}\n"
end
def protected! hidden = false
@ -244,14 +252,14 @@ end
get "/env", provides: "json" do
pretty = params.key? :pretty
json ENV.sort.to_h, pretty:
jsonify ENV.sort.to_h, pretty:
end
get "/headers", provides: "json" do
pretty = params.key? :pretty
h = req_headers
json h, pretty:
jsonify h, pretty:
end
get "/livez" do
@ -266,7 +274,7 @@ get "/livez/uptime" do
tt = TickTock.new
x = {started_at: tt.started_at, seconds: tt.uptime.to_i, human: human_time(tt.uptime.to_i)}
json x
jsonify x
end
post "/livez/toggle" do
@ -330,7 +338,7 @@ end
get "/pid" do
pretty = params.key? :pretty
json({puma: master_pid, pid: Process.pid}, pretty:)
jsonify({puma: master_pid, pid: Process.pid}, pretty:)
end
get "/token" do
@ -340,31 +348,31 @@ get "/token" do
token = JWT.encode payload, JWT_SECRET, "HS256"
x = {token: token, expires_at: expires_at}
json x
jsonify x
end
get "/token/validate" do
token = req_headers["authorization"].split[1]
payload = JWT.decode token, JWT_SECRET, true, algorithm: "HS256"
json payload
jsonify payload
end
post "/session" do
session.merge! params
json session.to_hash
jsonify session.to_hash
end
get "/session" do
j = session.to_hash
j[:hostname] = ENV["HOSTNAME"]
json j
jsonify j
end
get "/cookies" do
json response.headers
jsonify response.headers
end
get "/_cat/headers" do
@ -415,5 +423,5 @@ route :delete, :get, :patch, :post, :put, "/auth/basic", provides: "json" do
protected!
end
json({authenticated: true, user: @auth.username}, pretty:)
jsonify({authenticated: true, user: @auth.username}, pretty:)
end

50
lib/config.rb Normal file
View File

@ -0,0 +1,50 @@
require "sensitive"
class Config
attr_accessor :cat
attr_reader :jwt_secret, :session_secret
def initialize(prefix = ENV_PREFIX, jwt_secret = nil, session_secret = nil, cat = nil)
@prefix = prefix
@cat = cat
session_secret ||= ENV.fetch "SESSION_SECRET" do
SecureRandom.hex SESSION_SECRET_HEX_LENGTH
end
jwt_secret ||= fetch_env "JWT_SECRET" do
SecureRandom.hex JWT_SECRET_HEX_LENGTH
end
@session_secret = Sensitive.new session_secret
@jwt_secret = Sensitive.new jwt_secret
@cat ||= ENV.fetch "#{@prefix}_CAT", nil
end
def fetch_env(name, &)
ENV.fetch "#{@prefix}_#{name}", &
end
def as_json(options = nil)
{jwt_secret: jwt_secret, session_secret: @session_secret, cat: @cat}
end
def to_json(options = nil)
if options &&
options.key?(:pretty) &&
options[:pretty] == true
JSON.pretty_generate as_json(options)
else
JSON.generate as_json(options)
end
end
def session_secret=(v)
@session_secret = Sensitive.new v
end
def jwt_secret=(v)
@jwt_secret = Sensitive.new v
end
end

39
lib/sensitive.rb Normal file
View File

@ -0,0 +1,39 @@
class Sensitive
alias_method :eql?, :==
alias_method :equal?, :==
def initialize(v, ch: "*", head: 2, tail: 2)
@v = v
@ch = ch
@head = head
@tail = tail
end
def mask(v)
"".concat(v[0, @head], @ch * (v.length - (@head + @tail)), v[-@tail, @tail])
end
def unwrap
@v
end
def length
@v.length
end
def to_s
mask @v
end
def inspect
"#<#{self.class.name} @v=#{wrap}>"
end
def hash
@v.hash
end
def ==(other)
other.is_a?(Sensitive) && other.hash == hash
end
end

26
spec/sensitive_spec.rb Normal file
View File

@ -0,0 +1,26 @@
require "minitest/autorun"
$LOAD_PATH.unshift File.dirname(__FILE__) + "/../lib"
require "sensitive"
ALPHABET = ('a' .. 'z').reduce(:concat)
describe "Sensitive" do
before do
@s = Sensitive.new ALPHABET
end
it "test initialize" do
_(@s.to_s).must_equal "ab" + "*" * 22 + "yz"
end
it "test initialize" do
_(@s.unwrap).must_equal ALPHABET
end
it "test using different mask character" do
s = Sensitive.new ALPHABET, ch: "x"
_(s.to_s).must_equal "x"
end
end

26
test/test_sensitive.rb Normal file
View File

@ -0,0 +1,26 @@
require "minitest/autorun"
$LOAD_PATH.unshift File.dirname(__FILE__) + "/../lib"
require "sensitive"
ALPHABET = ("a".."z").reduce(:concat)
class TestSensitive < Minitest::Test
def setup
@s = Sensitive.new ALPHABET
end
def test_initialize
assert_equal @s.to_s, "ab" + "*" * 22 + "yz"
end
def test_unwrap
assert_equal @s.unwrap, ALPHABET
end
def test_using_a_different_mask_character
s = Sensitive.new ALPHABET, ch: "x"
assert_equal s.to_s, "ab" + "x" * 22 + "yz"
end
end