There was no way to put a file up for public use short of running an S3 client with the uploader key. Add a "files" area to the admin: upload one or more files, copy the link, edit the note, replace the contents under the same link, delete. It has its own read/write/delete permission flags like the other areas, and every action is audited. The objects go to the storage stack's public bucket and are served by Garage at s3.asxp.io/public/<key>, not by this app. The app's key can only put and delete there, so a public_files table is the record of what was published. Keys get a random prefix: links are not guessable and names never collide. That host is same-site with the admin, so a file that can carry script must not render there. Only a fixed list of types (images, PDF, plain text, audio, video) is served inline; the rest, HTML and SVG included, is stored as an attachment. Image metadata is stripped by default, as for the bingo photos, because of GPS tags. A per-file limit and a total quota keep the small disk on asxpio-ops from filling up. Assisted-by: Claude:fable-5-2
34 lines
1.2 KiB
Ruby
34 lines
1.2 KiB
Ruby
# Which permission an /admin request needs. Every admin path must map to
|
|
# an area (or be public/open); unmapped paths are superuser-only, and a
|
|
# test fails if any registered route is unmapped.
|
|
#
|
|
# GET/HEAD → read (except …/new and …/duplicate: write)
|
|
# POST …/delete, …/disable → delete
|
|
# any other non-GET → write
|
|
module AdminACL
|
|
RULES = [
|
|
[%r{\A/admin/(invoices|gel-rate|crypto-rate)(/|\z)}, 'invoices'],
|
|
[%r{\A/admin/bingo(/|\z)}, 'bingo'],
|
|
[%r{\A/admin/files(/|\z)}, 'files'],
|
|
[%r{\A/admin/(users|audit)(/|\z)}, 'users'],
|
|
].freeze
|
|
|
|
module_function
|
|
|
|
def area_for(path)
|
|
RULES.find { |re, _| path.match?(re) }&.last
|
|
end
|
|
|
|
def perm_for(method, path)
|
|
get = %w[GET HEAD].include?(method)
|
|
return 'delete' if !get && path.match?(%r{/(delete|disable)\z})
|
|
return 'write' if !get || path.match?(%r{/(new|duplicate)\z})
|
|
'read'
|
|
end
|
|
|
|
def allowed?(user, method, path)
|
|
return true if user.superuser || path.match?(AdminAuth::OPEN)
|
|
area = area_for(path) or return false
|
|
user.can?(area, perm_for(method, path))
|
|
end
|
|
end
|