mirror of
https://github.com/neocities/neocities.git
synced 2025-08-27 03:13:26 +02:00
more admin panel functionality, improve test coverage
This commit is contained in:
parent
36868837fb
commit
0baee03ad7
4 changed files with 209 additions and 46 deletions
6
app.rb
6
app.rb
|
@ -22,6 +22,10 @@ helpers do
|
||||||
%{<input name="csrf_token" type="hidden" value="#{csrf_token}">}
|
%{<input name="csrf_token" type="hidden" value="#{csrf_token}">}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def require_admin
|
||||||
|
redirect '/' unless signed_in? && current_site.is_admin
|
||||||
|
end
|
||||||
|
|
||||||
def hcaptcha_input
|
def hcaptcha_input
|
||||||
%{
|
%{
|
||||||
<script src="https://hcaptcha.com/1/api.js" async defer></script>
|
<script src="https://hcaptcha.com/1/api.js" async defer></script>
|
||||||
|
@ -77,6 +81,8 @@ before do
|
||||||
content_type :json
|
content_type :json
|
||||||
elsif request.path.match /^\/webhooks\//
|
elsif request.path.match /^\/webhooks\//
|
||||||
# Skips the CSRF/validation check for stripe web hooks
|
# Skips the CSRF/validation check for stripe web hooks
|
||||||
|
elsif request.path.match /^\/admin/
|
||||||
|
require_admin
|
||||||
elsif current_site && current_site.email_not_validated? && !(request.path =~ /^\/site\/.+\/confirm_email|^\/settings\/change_email|^\/welcome|^\/supporter|^\/signout/)
|
elsif current_site && current_site.email_not_validated? && !(request.path =~ /^\/site\/.+\/confirm_email|^\/settings\/change_email|^\/welcome|^\/supporter|^\/signout/)
|
||||||
redirect "/site/#{current_site.username}/confirm_email"
|
redirect "/site/#{current_site.username}/confirm_email"
|
||||||
elsif current_site && current_site.phone_verification_needed? && !(request.path =~ /^\/site\/.+\/confirm_email|^\/settings\/change_email|^\/site\/.+\/confirm_phone|^\/welcome|^\/supporter|^\/signout/)
|
elsif current_site && current_site.phone_verification_needed? && !(request.path =~ /^\/site\/.+\/confirm_email|^\/settings\/change_email|^\/site\/.+\/confirm_phone|^\/welcome|^\/supporter|^\/signout/)
|
||||||
|
|
43
app/admin.rb
43
app/admin.rb
|
@ -1,18 +1,15 @@
|
||||||
get '/admin' do
|
get '/admin' do
|
||||||
require_admin
|
|
||||||
@banned_sites = Site.select(:username).filter(is_banned: true).order(:username).all
|
@banned_sites = Site.select(:username).filter(is_banned: true).order(:username).all
|
||||||
@nsfw_sites = Site.select(:username).filter(is_nsfw: true).order(:username).all
|
@nsfw_sites = Site.select(:username).filter(is_nsfw: true).order(:username).all
|
||||||
erb :'admin'
|
erb :'admin'
|
||||||
end
|
end
|
||||||
|
|
||||||
get '/admin/reports' do
|
get '/admin/reports' do
|
||||||
require_admin
|
|
||||||
@reports = Report.order(:created_at.desc).all
|
@reports = Report.order(:created_at.desc).all
|
||||||
erb :'admin/reports'
|
erb :'admin/reports'
|
||||||
end
|
end
|
||||||
|
|
||||||
get '/admin/site/:username' do |username|
|
get '/admin/site/:username' do |username|
|
||||||
require_admin
|
|
||||||
@site = Site[username: username]
|
@site = Site[username: username]
|
||||||
not_found if @site.nil?
|
not_found if @site.nil?
|
||||||
@title = "Site Inspector - #{@site.username}"
|
@title = "Site Inspector - #{@site.username}"
|
||||||
|
@ -20,11 +17,9 @@ get '/admin/site/:username' do |username|
|
||||||
end
|
end
|
||||||
|
|
||||||
post '/admin/reports' do
|
post '/admin/reports' do
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
post '/admin/site_files/train' do
|
post '/admin/site_files/train' do
|
||||||
require_admin
|
|
||||||
site = Site[params[:site_id]]
|
site = Site[params[:site_id]]
|
||||||
site_file = site.site_files_dataset.where(path: params[:path]).first
|
site_file = site.site_files_dataset.where(path: params[:path]).first
|
||||||
not_found if site_file.nil?
|
not_found if site_file.nil?
|
||||||
|
@ -34,7 +29,6 @@ post '/admin/site_files/train' do
|
||||||
end
|
end
|
||||||
|
|
||||||
get '/admin/usage' do
|
get '/admin/usage' do
|
||||||
require_admin
|
|
||||||
today = Date.today
|
today = Date.today
|
||||||
current_month = Date.new today.year, today.month, 1
|
current_month = Date.new today.year, today.month, 1
|
||||||
|
|
||||||
|
@ -69,12 +63,10 @@ get '/admin/usage' do
|
||||||
end
|
end
|
||||||
|
|
||||||
get '/admin/email' do
|
get '/admin/email' do
|
||||||
require_admin
|
|
||||||
erb :'admin/email'
|
erb :'admin/email'
|
||||||
end
|
end
|
||||||
|
|
||||||
get '/admin/stats' do
|
get '/admin/stats' do
|
||||||
require_admin
|
|
||||||
|
|
||||||
@stats = {
|
@stats = {
|
||||||
total_hosted_site_hits: DB['SELECT SUM(hits) FROM sites'].first[:sum],
|
total_hosted_site_hits: DB['SELECT SUM(hits) FROM sites'].first[:sum],
|
||||||
|
@ -171,8 +163,6 @@ get '/admin/stats' do
|
||||||
end
|
end
|
||||||
|
|
||||||
post '/admin/email' do
|
post '/admin/email' do
|
||||||
require_admin
|
|
||||||
|
|
||||||
%i{subject body}.each do |k|
|
%i{subject body}.each do |k|
|
||||||
if params[k].nil? || params[k].empty?
|
if params[k].nil? || params[k].empty?
|
||||||
flash[:error] = "#{k.capitalize} is missing."
|
flash[:error] = "#{k.capitalize} is missing."
|
||||||
|
@ -209,9 +199,7 @@ post '/admin/email' do
|
||||||
redirect '/'
|
redirect '/'
|
||||||
end
|
end
|
||||||
|
|
||||||
post '/admin/banhammer' do
|
post '/admin/ban' do
|
||||||
require_admin
|
|
||||||
|
|
||||||
if params[:usernames].empty?
|
if params[:usernames].empty?
|
||||||
flash[:error] = 'no usernames provided'
|
flash[:error] = 'no usernames provided'
|
||||||
redirect '/admin'
|
redirect '/admin'
|
||||||
|
@ -259,8 +247,26 @@ post '/admin/banhammer' do
|
||||||
redirect '/admin'
|
redirect '/admin'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
post '/admin/unban' do
|
||||||
|
site = Site[username: params[:username]]
|
||||||
|
|
||||||
|
if site.nil?
|
||||||
|
flash[:error] = 'User not found'
|
||||||
|
redirect '/admin'
|
||||||
|
end
|
||||||
|
|
||||||
|
if !site.is_banned
|
||||||
|
flash[:error] = 'Site is not banned'
|
||||||
|
redirect '/admin'
|
||||||
|
end
|
||||||
|
|
||||||
|
site.unban!
|
||||||
|
|
||||||
|
flash[:success] = "Site #{site.username} was unbanned."
|
||||||
|
redirect '/admin'
|
||||||
|
end
|
||||||
|
|
||||||
post '/admin/mark_nsfw' do
|
post '/admin/mark_nsfw' do
|
||||||
require_admin
|
|
||||||
site = Site[username: params[:username]]
|
site = Site[username: params[:username]]
|
||||||
|
|
||||||
if site.nil?
|
if site.nil?
|
||||||
|
@ -277,7 +283,6 @@ post '/admin/mark_nsfw' do
|
||||||
end
|
end
|
||||||
|
|
||||||
post '/admin/feature' do
|
post '/admin/feature' do
|
||||||
require_admin
|
|
||||||
site = Site[username: params[:username]]
|
site = Site[username: params[:username]]
|
||||||
|
|
||||||
if site.nil?
|
if site.nil?
|
||||||
|
@ -292,17 +297,9 @@ post '/admin/feature' do
|
||||||
end
|
end
|
||||||
|
|
||||||
get '/admin/masquerade/:username' do
|
get '/admin/masquerade/:username' do
|
||||||
require_admin
|
|
||||||
site = Site[username: params[:username]]
|
site = Site[username: params[:username]]
|
||||||
not_found if site.nil?
|
not_found if site.nil?
|
||||||
session[:id] = site.id
|
session[:id] = site.id
|
||||||
redirect '/'
|
redirect '/'
|
||||||
end
|
end
|
||||||
|
|
||||||
def require_admin
|
|
||||||
redirect '/' unless is_admin?
|
|
||||||
end
|
|
||||||
|
|
||||||
def is_admin?
|
|
||||||
signed_in? && current_site.is_admin
|
|
||||||
end
|
|
|
@ -4,11 +4,9 @@ describe '/admin' do
|
||||||
include Capybara::DSL
|
include Capybara::DSL
|
||||||
include Capybara::Minitest::Assertions
|
include Capybara::Minitest::Assertions
|
||||||
|
|
||||||
|
|
||||||
before do
|
before do
|
||||||
Capybara.reset_sessions!
|
Capybara.reset_sessions!
|
||||||
@admin = Fabricate :site, is_admin: true
|
@admin = Fabricate :site, is_admin: true
|
||||||
@site = Fabricate :site
|
|
||||||
page.set_rack_session id: @admin.id
|
page.set_rack_session id: @admin.id
|
||||||
visit '/admin'
|
visit '/admin'
|
||||||
end
|
end
|
||||||
|
@ -17,13 +15,42 @@ describe '/admin' do
|
||||||
include Capybara::DSL
|
include Capybara::DSL
|
||||||
|
|
||||||
it 'works for admin site' do
|
it 'works for admin site' do
|
||||||
_(page.body).must_match /Administration/
|
_(page.body).must_match /Admin/
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'fails for site without admin' do
|
it 'blocks all /admin paths for non-admin users' do
|
||||||
page.set_rack_session id: @site.id
|
non_admin_site = Fabricate :site
|
||||||
|
page.set_rack_session id: non_admin_site.id
|
||||||
|
|
||||||
|
# Test GET routes
|
||||||
|
['/admin', '/admin/reports', '/admin/usage', '/admin/email', '/admin/stats', '/admin/masquerade/test'].each do |path|
|
||||||
|
visit path
|
||||||
|
_(page.current_path).must_equal '/', "Failed to block GET #{path}"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Test POST routes
|
||||||
|
['/admin/reports', '/admin/ban', '/admin/unban', '/admin/mark_nsfw', '/admin/feature', '/admin/email'].each do |path|
|
||||||
|
page.driver.post path, {}
|
||||||
|
_(page.driver.status_code).must_equal 302, "Expected redirect for POST #{path}"
|
||||||
|
|
||||||
|
# Follow the redirect and verify we end up at home page (blocked)
|
||||||
|
visit page.driver.response_headers['Location'] if page.driver.response_headers['Location']
|
||||||
|
_(page.current_path).must_equal '/', "POST #{path} should redirect to home page, not #{page.current_path}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'blocks /admin paths for signed out users' do
|
||||||
|
page.set_rack_session id: nil
|
||||||
|
|
||||||
visit '/admin'
|
visit '/admin'
|
||||||
_(page.current_path).must_equal '/'
|
_(page.current_path).must_equal '/'
|
||||||
|
|
||||||
|
page.driver.post '/admin/ban', {usernames: 'test'}
|
||||||
|
_(page.driver.status_code).must_equal 302
|
||||||
|
|
||||||
|
# Follow the redirect and verify we end up at home page (blocked)
|
||||||
|
visit page.driver.response_headers['Location'] if page.driver.response_headers['Location']
|
||||||
|
_(page.current_path).must_equal '/', "Signed out user POST should redirect to home page"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -31,20 +58,147 @@ describe '/admin' do
|
||||||
include Capybara::DSL
|
include Capybara::DSL
|
||||||
|
|
||||||
it 'works for valid site' do
|
it 'works for valid site' do
|
||||||
|
site = Fabricate :site
|
||||||
|
|
||||||
within(:css, '#upgradeToSupporter') do
|
within(:css, '#upgradeToSupporter') do
|
||||||
fill_in 'username', with: @site.username
|
fill_in 'username', with: site.username
|
||||||
click_button 'Upgrade to Supporter'
|
click_button 'Upgrade to Supporter'
|
||||||
@site.reload
|
site.reload
|
||||||
_(@site.stripe_customer_id).wont_be_nil
|
_(site.stripe_customer_id).wont_be_nil
|
||||||
_(@site.stripe_subscription_id).wont_be_nil
|
_(site.stripe_subscription_id).wont_be_nil
|
||||||
_(@site.values[:plan_type]).must_equal 'special'
|
_(site.values[:plan_type]).must_equal 'special'
|
||||||
_(@site.supporter?).must_equal true
|
_(site.supporter?).must_equal true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'ban site form' do
|
||||||
|
include Capybara::DSL
|
||||||
|
|
||||||
|
it 'bans single site successfully' do
|
||||||
|
site_to_ban = Fabricate :site
|
||||||
|
|
||||||
|
fill_in 'usernames', with: site_to_ban.username
|
||||||
|
# select 'Spam', from: 'classifier'
|
||||||
|
click_button 'Ban'
|
||||||
|
|
||||||
|
site_to_ban.reload
|
||||||
|
_(site_to_ban.is_banned).must_equal true
|
||||||
|
_(page.body).must_match(/sites have been banned/)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'bans multiple sites successfully' do
|
||||||
|
site1 = Fabricate :site
|
||||||
|
site2 = Fabricate :site
|
||||||
|
|
||||||
|
fill_in 'usernames', with: "#{site1.username}\n#{site2.username}"
|
||||||
|
#select 'Phishing', from: 'classifier'
|
||||||
|
click_button 'Ban'
|
||||||
|
|
||||||
|
site1.reload
|
||||||
|
site2.reload
|
||||||
|
_(site1.is_banned).must_equal true
|
||||||
|
_(site2.is_banned).must_equal true
|
||||||
|
_(page.body).must_match(/sites have been banned/)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'bans sites using IP when checkbox is checked' do
|
||||||
|
ip_address = '192.168.1.1'
|
||||||
|
site1 = Fabricate :site, ip: ip_address
|
||||||
|
site2 = Fabricate :site, ip: ip_address
|
||||||
|
|
||||||
|
fill_in 'usernames', with: site1.username
|
||||||
|
check 'ban_using_ips'
|
||||||
|
select 'Spam', from: 'classifier'
|
||||||
|
click_button 'Ban'
|
||||||
|
|
||||||
|
site1.reload
|
||||||
|
site2.reload
|
||||||
|
_(site1.is_banned).must_equal true
|
||||||
|
_(site2.is_banned).must_equal true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'unban site form' do
|
||||||
|
include Capybara::DSL
|
||||||
|
|
||||||
|
before do
|
||||||
|
@banned_site = Fabricate :site, is_banned: true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'unbans site successfully' do
|
||||||
|
within(:css, 'form[action="/admin/unban"]') do
|
||||||
|
fill_in 'username', with: @banned_site.username
|
||||||
|
click_button 'Unban'
|
||||||
|
end
|
||||||
|
|
||||||
|
@banned_site.reload
|
||||||
|
_(@banned_site.is_banned).must_equal false
|
||||||
|
_(page.body).must_match(/was unbanned/)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'handles non-existent username gracefully' do
|
||||||
|
within(:css, 'form[action="/admin/unban"]') do
|
||||||
|
fill_in 'username', with: 'nonexistent_user'
|
||||||
|
click_button 'Unban'
|
||||||
|
end
|
||||||
|
|
||||||
|
_(page.body).must_match(/User not found/)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'mark as NSFW form' do
|
||||||
|
include Capybara::DSL
|
||||||
|
|
||||||
|
it 'marks site as NSFW successfully' do
|
||||||
|
site_to_mark = Fabricate :site
|
||||||
|
|
||||||
|
within(:css, 'form[action="/admin/mark_nsfw"]') do
|
||||||
|
fill_in 'username', with: site_to_mark.username
|
||||||
|
click_button 'Mark NSFW'
|
||||||
|
end
|
||||||
|
|
||||||
|
site_to_mark.reload
|
||||||
|
_(site_to_mark.is_nsfw).must_equal true
|
||||||
|
_(page.body).must_match(/MISSION ACCOMPLISHED/)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'handles non-existent username gracefully' do
|
||||||
|
within(:css, 'form[action="/admin/mark_nsfw"]') do
|
||||||
|
fill_in 'username', with: 'nonexistent_user'
|
||||||
|
click_button 'Mark NSFW'
|
||||||
|
end
|
||||||
|
|
||||||
|
_(page.body).must_match(/User not found/)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'feature site form' do
|
||||||
|
include Capybara::DSL
|
||||||
|
|
||||||
|
it 'features site successfully' do
|
||||||
|
site_to_feature = Fabricate :site
|
||||||
|
|
||||||
|
within(:css, '#featureSite') do
|
||||||
|
fill_in 'username', with: site_to_feature.username
|
||||||
|
click_button 'Feature Site'
|
||||||
|
end
|
||||||
|
|
||||||
|
site_to_feature.reload
|
||||||
|
_(site_to_feature.featured_at).wont_be_nil
|
||||||
|
_(page.body).must_match(/Site has been featured/)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'handles non-existent username gracefully' do
|
||||||
|
within(:css, '#featureSite') do
|
||||||
|
fill_in 'username', with: 'nonexistent_user'
|
||||||
|
click_button 'Feature Site'
|
||||||
|
end
|
||||||
|
|
||||||
|
_(page.body).must_match(/User not found/)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe 'email blasting' do
|
describe 'email blasting' do
|
||||||
before do
|
before do
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<div class="header-Outro">
|
<div class="header-Outro">
|
||||||
<div class="row content single-Col">
|
<div class="row content single-Col">
|
||||||
<h1>Administration</h1>
|
<h1>Admin</h1>
|
||||||
<h2 class="subtitle">Freedom Ain't Free</h2>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -19,9 +18,8 @@
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col col-50">
|
<div class="col col-50">
|
||||||
<h2>Ban Site</h2>
|
<h2>Ban Site</h2>
|
||||||
<form action="/admin/banhammer" method="POST">
|
<form action="/admin/ban" method="POST">
|
||||||
<%== csrf_token_input_html %>
|
<%== csrf_token_input_html %>
|
||||||
<p>Site Username(s):</p>
|
|
||||||
<textarea name="usernames" cols="10" rows="5" autocapitalize="off" autocorrect="off"></textarea>
|
<textarea name="usernames" cols="10" rows="5" autocapitalize="off" autocorrect="off"></textarea>
|
||||||
<div class="select-Container" style="display: block; width: 100px; float: none; margin: 0 auto;">
|
<div class="select-Container" style="display: block; width: 100px; float: none; margin: 0 auto;">
|
||||||
<select name="classifier" class="input-Select">
|
<select name="classifier" class="input-Select">
|
||||||
|
@ -34,13 +32,25 @@
|
||||||
<p><input class="btn-Action" type="submit" value="Ban"></p>
|
<p><input class="btn-Action" type="submit" value="Ban"></p>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col col-50">
|
||||||
|
<h2>Unban Site</h2>
|
||||||
|
<form action="/admin/unban" method="POST">
|
||||||
|
<%== csrf_token_input_html %>
|
||||||
|
<p>
|
||||||
|
<input type="text" name="username" placeholder="" autocapitalize="off" autocorrect="off">
|
||||||
|
</p>
|
||||||
|
<p><input class="btn-Action" type="submit" value="Unban"></p>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
<div class="col col-50">
|
<div class="col col-50">
|
||||||
<h2>Mark as NSFW</h2>
|
<h2>Mark as NSFW</h2>
|
||||||
<form action="/admin/mark_nsfw" method="POST">
|
<form action="/admin/mark_nsfw" method="POST">
|
||||||
<%== csrf_token_input_html %>
|
<%== csrf_token_input_html %>
|
||||||
<p>Site Name:</p>
|
|
||||||
<p>
|
<p>
|
||||||
<input type="text" name="username" placeholder="edwardsnowden" autocapitalize="off" autocorrect="off">
|
<input type="text" name="username" placeholder="" autocapitalize="off" autocorrect="off">
|
||||||
</p>
|
</p>
|
||||||
<p><input class="btn-Action" type="submit" value="Mark NSFW"></p>
|
<p><input class="btn-Action" type="submit" value="Mark NSFW"></p>
|
||||||
</form>
|
</form>
|
||||||
|
@ -53,9 +63,7 @@
|
||||||
<form id="upgradeToSupporter" action="/supporter/update" method="POST">
|
<form id="upgradeToSupporter" action="/supporter/update" method="POST">
|
||||||
<input type="hidden" name="plan_type" value="special">
|
<input type="hidden" name="plan_type" value="special">
|
||||||
<%== csrf_token_input_html %>
|
<%== csrf_token_input_html %>
|
||||||
<p>This site will be upgraded to the supporter plan.</p>
|
<p><input type="text" name="username" placeholder="" autocapitalize="off" autocorrect="off"></p>
|
||||||
<p>Site Name:</p>
|
|
||||||
<p><input type="text" name="username" placeholder="edwardsnowden" autocapitalize="off" autocorrect="off"></p>
|
|
||||||
<p><input class="btn-Action" type="submit" value="Upgrade to Supporter"></p>
|
<p><input class="btn-Action" type="submit" value="Upgrade to Supporter"></p>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -64,9 +72,7 @@
|
||||||
<h2>Feature Site</h2>
|
<h2>Feature Site</h2>
|
||||||
<form id="featureSite" action="/admin/feature" method="POST">
|
<form id="featureSite" action="/admin/feature" method="POST">
|
||||||
<%== csrf_token_input_html %>
|
<%== csrf_token_input_html %>
|
||||||
<p>This site will be featured on the front page and in a special browse section.</p>
|
<p><input type="text" name="username" placeholder="" autocapitalize="off" autocorrect="off"></p>
|
||||||
<p>Site Name:</p>
|
|
||||||
<p><input type="text" name="username" placeholder="edwardsnowden" autocapitalize="off" autocorrect="off"></p>
|
|
||||||
<p><input class="btn-Action" type="submit" value="Feature Site"></p>
|
<p><input class="btn-Action" type="submit" value="Feature Site"></p>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue