admin: send /admin/ to /admin, also after login
Opening /admin/ logged out stored next=/admin/ in the login form, and a successful login then redirected to /admin/, which has no route (404). Redirect trailing-slash admin URLs to the slashless route with a 301, and drop trailing slashes from the post-login target too. Assisted-by: Claude:opus-5-5
This commit is contained in:
parent
648effc21f
commit
9a802106ce
2 changed files with 19 additions and 1 deletions
|
|
@ -58,8 +58,10 @@ module AdminAuth
|
||||||
Audit.log(action: action, user: user, target: target, detail: detail, ip: client_ip)
|
Audit.log(action: action, user: user, target: target, detail: detail, ip: client_ip)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Trailing slashes dropped: /admin/ has no route (see the before filter).
|
||||||
def safe_next(target)
|
def safe_next(target)
|
||||||
target.to_s.match?(%r{\A/admin(/[\w\-/]*)?\z}) ? target : '/admin'
|
target = target.to_s.sub(%r{(?<=.)/+\z}, '')
|
||||||
|
target.match?(%r{\A/admin(/[\w\-/]*)?\z}) ? target : '/admin'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -68,6 +70,8 @@ module AdminAuth
|
||||||
|
|
||||||
app.before do
|
app.before do
|
||||||
next unless admin_path?
|
next unless admin_path?
|
||||||
|
# One URL per page: /admin/ and /admin/bingo/ go to the slashless route.
|
||||||
|
redirect request.fullpath.sub(%r{/+(?=\?|\z)}, ''), 301 if request.path_info.match?(%r{./\z})
|
||||||
halt 503, 'Admin needs DATABASE_URL.' unless defined?(User)
|
halt 503, 'Admin needs DATABASE_URL.' unless defined?(User)
|
||||||
next if request.path_info.match?(PUBLIC)
|
next if request.path_info.match?(PUBLIC)
|
||||||
unless current_user
|
unless current_user
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,20 @@ class AdminAuthTest < Minitest::Test
|
||||||
assert_equal 302, last_response.status
|
assert_equal 302, last_response.status
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_trailing_slash_admin_urls
|
||||||
|
get '/admin/'
|
||||||
|
assert_equal 301, last_response.status
|
||||||
|
assert_match %r{/admin\z}, last_response.location
|
||||||
|
get '/admin/bingo/?x=1'
|
||||||
|
assert_match %r{/admin/bingo\?x=1\z}, last_response.location
|
||||||
|
|
||||||
|
User.create(username: 'alice', superuser: true, password: 'alice-password-1')
|
||||||
|
get '/admin/login?next=/admin/'
|
||||||
|
token = last_response.body[/name="authenticity_token" value="([^"]+)"/, 1]
|
||||||
|
post '/admin/login', { authenticity_token: token, username: 'alice', password: 'alice-password-1', next: '/admin/' }
|
||||||
|
assert_match %r{/admin\z}, last_response.location
|
||||||
|
end
|
||||||
|
|
||||||
def test_wrong_password_and_unknown_user
|
def test_wrong_password_and_unknown_user
|
||||||
User.create(username: 'alice', superuser: true, password: 'alice-password-1')
|
User.create(username: 'alice', superuser: true, password: 'alice-password-1')
|
||||||
login('alice', 'nope')
|
login('alice', 'nope')
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue