Merge branch 'master' of github.com:kyledrake/neocities-web

This commit is contained in:
Kyle Drake 2014-04-05 01:21:10 -04:00
commit f367ed389e
24 changed files with 437 additions and 245 deletions

1
.gitignore vendored
View file

@ -20,3 +20,4 @@ tests/coverage
config.yml
.DS_Store
domains
public/sites_test

3
.travis.yml Normal file
View file

@ -0,0 +1,3 @@
language: ruby
rvm:
- "2.1.0"

View file

@ -15,6 +15,8 @@ gem 'selenium-webdriver', require: nil
gem 'sidekiq'
gem 'ago'
gem 'mail'
gem 'google-api-client', require: 'google/api_client'
gem 'tilt'
platform :mri do
gem 'magic' # sudo apt-get install file, For OSX: brew install libmagic
@ -52,6 +54,10 @@ group :test do
gem 'webmock'
gem 'mocha', require: nil
gem 'rake', require: nil
gem 'poltergeist'
gem 'phantomjs', require: 'phantomjs/poltergeist'
gem 'capybara'
gem 'capybara_minitest_spec'
platform :mri do
gem 'simplecov', require: nil

View file

@ -4,12 +4,26 @@ GEM
addressable (2.3.6)
ago (0.1.5)
ansi (1.4.3)
autoparse (0.3.3)
addressable (>= 2.3.1)
extlib (>= 0.9.15)
multi_json (>= 1.0.0)
bcrypt (3.1.7)
builder (3.2.2)
capybara (2.2.1)
mime-types (>= 1.16)
nokogiri (>= 1.3.3)
rack (>= 1.0.0)
rack-test (>= 0.5.4)
xpath (~> 2.0)
capybara_minitest_spec (1.0.1)
capybara (>= 2)
minitest (>= 2)
celluloid (0.15.2)
timers (~> 1.1.0)
childprocess (0.5.2)
ffi (~> 1.0, >= 1.0.11)
cliver (0.3.2)
coderay (1.1.0)
columnize (0.3.6)
connection_pool (2.0.0)
@ -22,15 +36,33 @@ GEM
debugger-linecache (1.2.0)
debugger-ruby_core_source (1.3.2)
docile (1.1.3)
extlib (0.9.16)
fabrication (2.11.0)
faker (1.3.0)
i18n (~> 0.5)
faraday (0.9.0)
multipart-post (>= 1.2, < 3)
ffi (1.9.3)
google-api-client (0.7.1)
addressable (>= 2.3.2)
autoparse (>= 0.3.3)
extlib (>= 0.9.15)
faraday (>= 0.9.0)
jwt (>= 0.1.5)
launchy (>= 2.1.1)
multi_json (>= 1.0.0)
retriable (>= 1.4)
signet (>= 0.5.0)
uuidtools (>= 2.1.0)
hashie (2.0.5)
hiredis (0.5.0)
i18n (0.6.9)
json (1.8.1)
jwt (0.1.11)
multi_json (>= 1.5)
kgio (2.9.2)
launchy (2.4.2)
addressable (~> 2.3)
magic (0.2.6)
ffi (>= 0.6.3)
mail (2.5.4)
@ -39,6 +71,7 @@ GEM
metaclass (0.0.4)
method_source (0.8.2)
mime-types (1.25.1)
mini_portile (0.5.3)
minitest (5.3.1)
minitest-reporters (1.0.2)
ansi
@ -48,7 +81,16 @@ GEM
mocha (1.0.0)
metaclass (~> 0.0.1)
multi_json (1.9.2)
multipart-post (2.0.0)
nokogiri (1.6.1)
mini_portile (~> 0.5.0)
pg (0.17.1)
phantomjs (1.9.7.0)
poltergeist (1.5.0)
capybara (~> 2.1)
cliver (~> 0.3.1)
multi_json (~> 1.0)
websocket-driver (>= 0.2.0)
polyglot (0.3.4)
powerbar (1.0.11)
ansi (~> 1.4.0)
@ -78,6 +120,7 @@ GEM
redis (3.0.7)
redis-namespace (1.4.1)
redis (~> 3.0.4)
retriable (1.4.1)
rmagick (2.13.2)
rubyzip (1.1.2)
safe_yaml (1.0.1)
@ -98,6 +141,11 @@ GEM
json
redis (>= 3.0.6)
redis-namespace (>= 1.3.1)
signet (0.5.0)
addressable (>= 2.2.3)
faraday (>= 0.9.0.rc5)
jwt (>= 0.1.5)
multi_json (>= 1.0.0)
simplecov (0.8.2)
docile (~> 1.1.0)
multi_json
@ -125,10 +173,14 @@ GEM
kgio (~> 2.6)
rack
raindrops (~> 0.7)
uuidtools (2.1.4)
webmock (1.17.4)
addressable (>= 2.2.7)
crack (>= 0.3.2)
websocket (1.0.7)
websocket-driver (0.3.2)
xpath (2.0.0)
nokogiri (~> 1.3)
PLATFORMS
ruby
@ -136,8 +188,11 @@ PLATFORMS
DEPENDENCIES
ago
bcrypt
capybara
capybara_minitest_spec
fabrication
faker
google-api-client
hiredis
jdbc-postgres
jruby-openssl
@ -148,6 +203,8 @@ DEPENDENCIES
minitest-reporters
mocha
pg
phantomjs
poltergeist
pry
pry-debugger
puma
@ -169,4 +226,5 @@ DEPENDENCIES
sinatra-flash
sinatra-xsendfile
slim
tilt
webmock

View file

@ -7,7 +7,7 @@ end
desc "Run all tests"
Rake::TestTask.new do |t|
t.libs << "spec"
t.test_files = FileList['tests/*_test.rb']
t.test_files = FileList['tests/*_tests.rb']
t.verbose = true
end

151
app.rb
View file

@ -1,4 +1,6 @@
require 'base64'
require 'uri'
require 'net/http'
require './environment.rb'
use Rack::Session::Cookie, key: 'neocities',
@ -102,15 +104,13 @@ get '/donate' do
end
get '/blog' do
# expires 500, :public, :must_revalidate
return File.read File.join(DIR_ROOT, 'public', 'sites', 'blog', 'index.html')
expires 500, :public, :must_revalidate
return Net::HTTP.get_response(URI('http://blog.neocities.org')).body
end
get '/blog/:article' do |article|
# expires 500, :public, :must_revalidate
path = File.join DIR_ROOT, 'public', 'sites', 'blog', "#{article}.html"
pass if !File.exist?(path)
File.read path
expires 500, :public, :must_revalidate
return Net::HTTP.get_response(URI("http://blog.neocities.org/#{article}.html")).body
end
get '/new' do
@ -137,22 +137,19 @@ end
post '/create' do
dashboard_if_signed_in
@site = Site.new username: params[:username], password: params[:password], email: params[:email], new_tags: params[:tags], is_nsfw: params[:is_nsfw], ip: request.ip
@site = Site.new(
username: params[:username],
password: params[:password],
email: params[:email],
new_tags: params[:tags],
is_nsfw: params[:is_nsfw],
ip: request.ip
)
recaptcha_is_valid = recaptcha_valid?
recaptcha_is_valid = ENV['RACK_ENV'] == 'test' || recaptcha_valid?
if @site.valid? && recaptcha_is_valid
base_path = site_base_path @site.username
DB.transaction {
@site.save
FileUtils.mkdir base_path
File.write File.join(base_path, 'index.html'), slim(:'templates/index', pretty: true, layout: false)
File.write File.join(base_path, 'not_found.html'), slim(:'templates/not_found', pretty: true, layout: false)
}
@site.save
session[:id] = @site.id
redirect '/dashboard'
@ -225,9 +222,9 @@ end
post '/change_name' do
require_login
current_username = current_site.username
old_username = current_site.username
if current_site.username == params[:name]
if old_username == params[:name]
flash[:error] = 'You already have this name.'
redirect '/settings'
end
@ -237,7 +234,7 @@ post '/change_name' do
if current_site.valid?
DB.transaction {
current_site.save
FileUtils.mv site_base_path(current_username), site_base_path(current_site.username)
current_site.move_files_from old_username
}
flash[:success] = "Site/user name has been changed. You will need to use this name to login, <b>don't forget it</b>."
@ -266,14 +263,13 @@ post '/site_files/create_page' do
end
name = "#{params[:pagefilename]}.html"
path = site_file_path name
if File.exist? path
if current_site.file_exists?(name)
@errors << %{Web page "#{name}" already exists! Choose another name.}
halt slim(:'site_files/new_page')
end
File.write path, slim(:'templates/index', pretty: true, layout: false)
current_site.install_new_html_file name
flash[:success] = %{#{name} was created! <a style="color: #FFFFFF; text-decoration: underline" href="/site_files/text_editor/#{name}">Click here to edit it</a>.}
@ -297,12 +293,12 @@ post '/site_files/upload' do
if params[:newfile] == '' || params[:newfile].nil?
@errors << 'You must select a file to upload.'
halt http_error_code, 'Did not receive file upload.' # slim(:'site_files/new')
halt http_error_code, 'Did not receive file upload.'
end
if params[:newfile][:tempfile].size > Site::MAX_SPACE || (params[:newfile][:tempfile].size + current_site.total_space) > Site::MAX_SPACE
@errors << 'File size must be smaller than available space.'
halt http_error_code, 'File size must be smaller than available space.' # slim(:'site_files/new')
halt http_error_code, 'File size must be smaller than available space.'
end
mime_type = Magic.guess_file_mime_type params[:newfile][:tempfile].path
@ -313,10 +309,7 @@ post '/site_files/upload' do
end
sanitized_filename = params[:newfile][:filename].gsub(/[^a-zA-Z0-9_\-.]/, '')
dest_path = File.join(site_base_path(current_site.username), sanitized_filename)
FileUtils.mv params[:newfile][:tempfile].path, dest_path
File.chmod(0640, dest_path) if self.class.production?
current_site.store_file sanitized_filename, params[:newfile][:tempfile]
if sanitized_filename =~ /index\.html/
ScreenshotWorker.perform_async current_site.username
@ -332,71 +325,56 @@ end
post '/site_files/delete' do
require_login
sanitized_filename = params[:filename].gsub(/[^a-zA-Z0-9_\-.]/, '')
begin
FileUtils.rm File.join(site_base_path(current_site.username), sanitized_filename)
rescue Errno::ENOENT
flash[:error] = 'File was already deleted.'
redirect '/dashboard'
end
current_site.delete_file(sanitized_filename)
flash[:success] = "Deleted file #{params[:filename]}."
redirect '/dashboard'
end
get '/site_files/:username.zip' do |username|
require_login
file_path = "/tmp/neocities-site-#{username}.zip"
Zip::File.open(file_path, Zip::File::CREATE) do |zipfile|
current_site.file_list.collect {|f| f.filename}.each do |filename|
zipfile.add filename, site_file_path(filename)
end
end
# I don't want to have to deal with cleaning up old tmpfiles
zipfile = File.read file_path
File.delete file_path
zipfile = current_site.files_zip
content_type 'application/octet-stream'
attachment "#{current_site.username}.zip"
return zipfile
zipfile
end
get '/site_files/download/:filename' do |filename|
require_login
send_file File.join(site_base_path(current_site.username), filename), filename: filename, type: 'Application/octet-stream'
content_type 'application/octet-stream'
attachment filename
current_site.get_file filename
end
get '/site_files/text_editor/:filename' do |filename|
require_login
begin
@file_data = File.read File.join(site_base_path(current_site.username), filename)
@file_data = current_site.get_file filename
rescue Errno::ENOENT
flash[:error] = 'We could not find the requested file.'
redirect '/dashboard'
end
slim :'site_files/text_editor'
slim :'site_files/text_editor', indent: false
end
post '/site_files/save/:filename' do |filename|
require_login_ajax
tmpfile = Tempfile.new 'neocities_saving_file'
tempfile = Tempfile.new 'neocities_saving_file'
if (tmpfile.size + current_site.total_space) > Site::MAX_SPACE
if (tempfile.size + current_site.total_space) > Site::MAX_SPACE
halt 'File is too large to fit in your space, it has NOT been saved. Please make a local copy and then try to reduce the size.'
end
input = request.body.read
tmpfile.set_encoding input.encoding
tmpfile.write input
tmpfile.close
tempfile.set_encoding input.encoding
tempfile.write input
tempfile.close
sanitized_filename = filename.gsub(/[^a-zA-Z0-9_\-.]/, '')
dest_path = File.join site_base_path(current_site.username), sanitized_filename
FileUtils.mv tmpfile.path, dest_path
File.chmod(0640, dest_path) if self.class.production?
current_site.store_file sanitized_filename, tempfile
if sanitized_filename =~ /index\.html/
ScreenshotWorker.perform_async current_site.username
@ -423,24 +401,6 @@ get '/admin' do
slim :'admin'
end
def ban_site(username)
site = Site[username: username]
return false if site.nil?
return false if site.is_banned == true
DB.transaction {
FileUtils.mv site_base_path(site.username), File.join(settings.public_folder, 'banned_sites', site.username)
site.is_banned = true
site.save(validate: false)
}
if !['127.0.0.1', nil, ''].include? site.ip
`sudo ufw insert 1 deny from #{site.ip}`
end
true
end
post '/admin/banip' do
require_admin
site = Site[username: params[:username]]
@ -455,8 +415,8 @@ post '/admin/banip' do
redirect '/admin'
end
sites = Site.filter(ip: site.ip).all
sites.each {|s| ban_site(s.username)}
sites = Site.filter(ip: site.ip, is_banned: false).all
sites.each {|s| s.ban!}
flash[:error] = "#{sites.length} sites have been banned."
redirect '/admin'
end
@ -476,7 +436,7 @@ post '/admin/banhammer' do
redirect '/admin'
end
ban_site params[:username]
site.ban!
flash[:success] = 'MISSION ACCOMPLISHED'
redirect '/admin'
@ -577,18 +537,9 @@ post '/custom_domain' do
require_login
original_domain = current_site.domain
current_site.domain = params[:domain]
if current_site.valid?
DB.transaction do
current_site.save
if !params[:domain].empty? && !params[:domain].nil?
File.open(File.join(DIR_ROOT, 'domains', "#{current_site.username}.conf"), 'w') do |file|
file.write erb(:'templates/domain', layout: false)
end
end
end
current_site.save
flash[:success] = 'The domain has been successfully updated.'
redirect '/custom_domain'
else
@ -660,18 +611,6 @@ def current_site
@site ||= Site[id: session[:id]]
end
def site_base_path(subname)
File.join settings.public_folder, 'sites', subname
end
def site_file_path(filename)
File.join(site_base_path(current_site.username), filename)
end
def template_site_title(username)
"#{username.capitalize}#{username[username.length-1] == 's' ? "'" : "'s"} Site"
end
def encoding_fix(file)
begin
Rack::Utils.escape_html file

View file

@ -4,6 +4,6 @@ Sequel.migration do
}
down {
DB.add_column :sites, :hits
DB.drop_column :sites, :hits
}
end

View file

@ -4,6 +4,6 @@ Sequel.migration do
}
down {
DB.add_column :sites, :is_admin
DB.drop_column :sites, :is_admin
}
end

View file

@ -4,6 +4,6 @@ Sequel.migration do
}
down {
DB.add_column :sites, :is_banned
DB.drop_column :sites, :is_banned
}
end

View file

@ -4,6 +4,6 @@ Sequel.migration do
}
down {
DB.add_column :sites, :ip
DB.drop_column :sites, :ip
}
end

View file

@ -6,6 +6,5 @@ Sequel.migration do
down {
DB.drop_column :sites, :password_reset_token
DB.drop_index :sites, :password_reset_token
}
end

View file

@ -6,6 +6,5 @@ Sequel.migration do
down {
DB.drop_column :sites, :site_changed
DB.drop_index :sites, :site_changed
}
end

View file

@ -6,6 +6,5 @@ Sequel.migration do
down {
DB.drop_column :sites, :changed_count
DB.drop_index :sites, :changed_count
}
end

View file

@ -1,11 +1,44 @@
require 'tilt'
class Site < Sequel::Model
# We might need to include fonts in here..
VALID_MIME_TYPES = ['text/plain', 'text/html', 'text/css', 'application/javascript', 'image/png', 'image/jpeg', 'image/gif', 'image/svg+xml', 'application/vnd.ms-fontobject', 'application/x-font-ttf', 'application/octet-stream', 'text/csv', 'text/tsv', 'text/cache-manifest', 'image/x-icon', 'application/pdf', 'application/pgp-keys', 'text/xml', 'application/xml', 'audio/midi']
VALID_EXTENSIONS = %w{ html htm txt text css js jpg jpeg png gif svg md markdown eot ttf woff json geojson csv tsv mf ico pdf asc key pgp xml mid midi }
#USERNAME_SHITLIST = %w{ payment secure login signin www ww web } # I thought they were funny personally, but everybody is freaking out so..
VALID_MIME_TYPES = %w{
text/plain
text/html
text/css
application/javascript
image/png
image/jpeg
image/gif
image/svg+xml
application/vnd.ms-fontobject
application/x-font-ttf
application/octet-stream
text/csv
text/tsv
text/cache-manifest
image/x-icon
application/pdf
application/pgp-keys
text/xml
application/xml
audio/midi
}
VALID_EXTENSIONS = %w{
html htm txt text css js jpg jpeg png gif svg md markdown eot ttf woff json
geojson csv tsv mf ico pdf asc key pgp xml mid midi
}
MAX_SPACE = (5242880*2) # 10MB
MINIMUM_PASSWORD_LENGTH = 5
BAD_USERNAME_REGEX = /[^\w-]/i
VALID_HOSTNAME = /^[a-z0-9][a-z0-9-]+?[a-z0-9]$/i # http://tools.ietf.org/html/rfc1123
# FIXME smarter DIR_ROOT discovery
DIR_ROOT = './'
TEMPLATE_ROOT = File.join DIR_ROOT, 'views', 'templates'
PUBLIC_ROOT = File.join DIR_ROOT, 'public'
SITE_FILES_ROOT = File.join PUBLIC_ROOT, (ENV['RACK_ENV'] == 'test' ? 'sites_test' : 'sites')
many_to_one :server
many_to_many :tags
@ -51,6 +84,87 @@ class Site < Sequel::Model
super
end
def save(validate={})
DB.transaction do
is_new = new?
install_custom_domain if !domain.nil? && !domain.empty?
result = super(validate)
install_new_files if is_new
result
end
end
def install_custom_domain
File.open(File.join(DIR_ROOT, 'domains', "#{username}.conf"), 'w') do |file|
file.write render_template('domain.erb')
end
end
def install_new_files
FileUtils.mkdir_p files_path
%w{index not_found}.each do |name|
File.write file_path("#{name}.html"), render_template("#{name}.slim")
end
end
def get_file(filename)
File.read file_path(filename)
end
def ban!
DB.transaction {
FileUtils.mv files_path, File.join(PUBLIC_ROOT, 'banned_sites', username)
self.is_banned = true
if !['127.0.0.1', nil, ''].include? ip
`sudo ufw insert 1 deny from #{ip}`
end
save(validate: false)
}
end
def store_file(filename, uploaded)
FileUtils.mv uploaded.path, file_path(filename)
File.chmod(0640, file_path(filename))
end
def files_zip
file_path = "/tmp/neocities-site-#{username}.zip"
Zip::File.open(file_path, Zip::File::CREATE) do |zipfile|
file_list.collect {|f| f.filename}.each do |filename|
zipfile.add filename, file_path(filename)
end
end
# TODO Don't dump the zipfile into memory
zipfile = File.read file_path
File.delete file_path
zipfile
end
def delete_file(filename)
begin
FileUtils.rm file_path(filename)
rescue Errno::ENOENT
# File was probably already deleted
end
end
def move_files_from(oldusername)
FileUtils.mv files_path(oldusername), files_path
end
def install_new_html_file(name)
File.write file_path(name), render_template('index.slim')
end
def file_exists?(filename)
File.exist? file_path(filename)
end
def after_save
if @new_tag_strings
@new_tag_strings.each do |new_tag_string|
@ -77,13 +191,15 @@ class Site < Sequel::Model
errors.add :over_capacity, 'We are currently at capacity, and cannot create your home page. We will fix this shortly. Please come back later and try again, our apologies.'
end
if values[:username].nil? || values[:username].empty? || values[:username].match(BAD_USERNAME_REGEX)
errors.add :username, 'A valid username is required.'
if !values[:username].match(VALID_HOSTNAME)
errors.add :username, 'A valid user/site name is required.'
end
if values[:username].length > 32
errors.add :username, 'User/site name cannot exceed 32 characters.'
end
# Check for existing user
user = self.class.select(:id, :username).filter(username: values[:username]).first
if user
@ -108,16 +224,24 @@ class Site < Sequel::Model
end
end
def file_path
File.join DIR_ROOT, 'public', 'sites', username
def render_template(name)
Tilt.new(File.join(TEMPLATE_ROOT, name), pretty: true).render self
end
def files_path(name=nil)
File.join SITE_FILES_ROOT, (name || username)
end
def file_path(filename)
File.join files_path, filename
end
def file_list
Dir.glob(File.join(file_path, '*')).collect {|p| File.basename(p)}.sort.collect {|sitename| SiteFile.new sitename}
Dir.glob(File.join(files_path, '*')).collect {|p| File.basename(p)}.sort.collect {|sitename| SiteFile.new sitename}
end
def total_space
space = Dir.glob(File.join(file_path, '*')).collect {|p| File.size(p)}.inject {|sum,x| sum += x}
space = Dir.glob(File.join(files_path, '*')).collect {|p| File.size(p)}.inject {|sum,x| sum += x}
space.nil? ? 0 : space
end

143
tests/acceptance_tests.rb Normal file
View file

@ -0,0 +1,143 @@
require_relative './environment'
Capybara.app = Sinatra::Application
def teardown
Capybara.reset_sessions!
Capybara.use_default_driver
end
describe 'index' do
include Capybara::DSL
it 'goes to signup' do
visit '/'
click_button 'Create My Website'
page.must_have_content('Create a New Home Page')
end
end
describe 'signup' do
include Capybara::DSL
def fill_in_valid
@site = Fabricate.attributes_for(:site)
fill_in 'username', with: @site[:username]
fill_in 'password', with: @site[:password]
end
def visit_signup
visit '/'
click_button 'Create My Website'
end
before do
Capybara.reset_sessions!
visit_signup
end
it 'succeeds with valid data' do
fill_in_valid
click_button 'Create Home Page'
page.must_have_content 'Your Website'
assert_equal(
true,
File.exist?(File.join(Site::SITE_FILES_ROOT, @site[:username], 'index.html'))
)
end
it 'fails to create for existing site' do
fill_in_valid
click_button 'Create Home Page'
page.must_have_content 'Your Website'
Capybara.reset_sessions!
visit_signup
fill_in 'username', with: @site[:username]
fill_in 'password', with: @site[:password]
click_button 'Create Home Page'
page.must_have_content 'already taken'
end
it 'fails with missing password' do
fill_in_valid
fill_in 'password', with: ''
click_button 'Create Home Page'
page.must_have_content 'Password must be at least 5 characters'
end
it 'fails with short password' do
fill_in_valid
fill_in 'password', with: 'derp'
click_button 'Create Home Page'
page.must_have_content 'Password must be at least 5 characters'
end
it 'fails with invalid hostname for username' do
fill_in_valid
fill_in 'username', with: '|\|0p|E'
click_button 'Create Home Page'
page.current_path.must_equal '/create'
page.must_have_content 'A valid user/site name is required'
fill_in 'username', with: 'nope-'
click_button 'Create Home Page'
page.must_have_content 'A valid user/site name is required'
fill_in 'username', with: '-nope'
click_button 'Create Home Page'
page.must_have_content 'A valid user/site name is required'
end
it 'fails with username greater than 32 characters' do
fill_in_valid
fill_in 'username', with: SecureRandom.hex+'1'
click_button 'Create Home Page'
page.must_have_content 'cannot exceed 32 characters'
end
end
describe 'signin' do
include Capybara::DSL
def fill_in_valid
site = Fabricate.attributes_for :site
fill_in 'username', with: site[:username]
fill_in 'password', with: site[:password]
end
before do
Capybara.reset_sessions!
end
it 'fails for invalid login' do
visit '/'
click_link 'Sign In'
page.must_have_content 'Welcome back'
fill_in_valid
click_button 'Sign in'
page.must_have_content 'Invalid login'
end
it 'fails for missing login' do
visit '/'
click_link 'Sign In'
auth = {username: SecureRandom.hex, password: Faker::Internet.password}
fill_in 'username', with: auth[:username]
fill_in 'password', with: auth[:password]
click_button 'Sign in'
page.must_have_content 'Invalid login'
end
it 'logs in with proper credentials' do
visit '/'
click_button 'Create My Website'
site = Fabricate.attributes_for(:site)
fill_in 'username', with: site[:username]
fill_in 'password', with: site[:password]
click_button 'Create Home Page'
Capybara.reset_sessions!
visit '/'
click_link 'Sign In'
fill_in 'username', with: site[:username]
fill_in 'password', with: site[:password]
click_button 'Sign in'
page.must_have_content 'Your Website'
end
end

View file

@ -1,93 +0,0 @@
require_relative './environment'
include Rack::Test::Methods
def app; App end
def status; last_response.status end
def headers; last_response.headers end
def body; last_response.body end
describe 'index' do
it 'loads' do
get '/'
status.must_equal 200
end
end
describe 'signin' do
it 'fails for missing login' do
post '/signin', username: 'derpie', password: 'lol'
fail_signin
end
it 'fails for bad password' do
@site = Fabricate :site
post '/signin', username: @site.username, password: 'derp'
fail_signin
end
it 'fails for no input' do
post '/signin'
fail_signin
end
it 'succeeds for valid input' do
password = '1tw0rkz'
@account = Fabricate :account, password: password
post '/accounts/signin', username: @account.email, password: password
headers['Location'].must_equal 'http://example.org/dashboard'
mock_dashboard_calls @account.email
get '/dashboard'
body.must_match /Dashboard/
end
end
describe 'account creation' do
it 'fails for no input' do
post '/accounts/create'
status.must_equal 200
body.must_match /There were some errors.+Valid email address is required.+Password must be/
end
it 'fails with invalid email' do
post '/accounts/create', email: 'derplol'
status.must_equal 200
body.must_match /errors.+valid email/i
end
it 'fails with invalid password' do
post '/accounts/create', 'email@example.com', password: 'sdd'
status.must_equal 200
body.must_match /errors.+Password must be at least #{Account::MINIMUM_PASSWORD_LENGTH} characters/i
end
it 'succeeds with valid info' do
account_attributes = Fabricate.attributes_for :account
mock_dashboard_calls account_attributes[:email]
post '/accounts/create', account_attributes
status.must_equal 302
headers['Location'].must_equal 'http://example.org/dashboard'
get '/dashboard'
body.must_match /Dashboard/
end
end
describe 'temporary account login' do
end
def fail_signin
headers['Location'].must_equal 'http://example.org/'
get '/'
body.must_match /invalid signin/i
end
def api_url
uri = Addressable::URI.parse $config['bitcoind_rpchost'] ? $config['bitcoind_rpchost'] : 'http://localhost'
uri.port = 8332 if uri.port.nil?
uri.user = $config['bitcoind_rpcuser'] if uri.user.nil?
uri.password = $config['bitcoind_rpcpassword'] if uri.password.nil?
"#{uri.to_s}/"
end

View file

@ -20,7 +20,7 @@ Bundler.require :test
require 'minitest/autorun'
require 'sidekiq/testing/inline'
Account.bcrypt_cost = BCrypt::Engine::MIN_COST
Site.bcrypt_cost = BCrypt::Engine::MIN_COST
MiniTest::Reporters.use! MiniTest::Reporters::SpecReporter.new
@ -29,8 +29,11 @@ Sequel.extension :migration
Sequel::Migrator.apply DB, './migrations', 0
Sequel::Migrator.apply DB, './migrations'
Server.create ip: '127.0.0.1', slots_available: 999999
Fabrication.configure do |config|
config.fabricator_path = 'tests/fabricators'
config.path_prefix = DIR_ROOT
end
I18n.enforce_available_locales = true

View file

@ -1,4 +1,4 @@
Fabricator(:site) do
username { Faker::Internet.email }
username { SecureRandom.hex }
password { 'abcde' }
end

View file

@ -20,8 +20,7 @@ javascript:
span
<i class="icon-file-alt icon-3x"></i>&nbsp;&nbsp; <span style="font-size: 20pt">#{file.filename}</span>
- if file.filename == 'index.html'
p.tiny
This is your index file! It is the "default file" that loads when you go to <a href="http://#{current_site.username}.neocities.org">#{current_site.username}.neocities.org</a>. In effect, it's your front page. If you want to change your front page, you need to edit (or overwrite) this file. The default file is always named <b>index.html</b>.
p.tiny This is your index file! It is the "default file" that loads when you go to <a href="http://#{current_site.username}.neocities.org">#{current_site.username}.neocities.org</a>. In effect, it's your front page. If you want to change your front page, you need to edit (or overwrite) this file, which you should do right now if you just created your site. The default file is always named <b>index.html</b>, and you cannot delete it.
div style="margin-bottom:30px"
span
@ -36,14 +35,23 @@ javascript:
span
i class="icon-edit" &nbsp;&nbsp;
span: a href="/site_files/download/#{file.filename}" Download <br />
span
i class="icon-trash" &nbsp;&nbsp;
a href="#" onclick="confirmFileDelete('#{file.filename}')" Delete
- if file.filename != 'index.html'
span
i class="icon-trash" &nbsp;&nbsp;
a href="#" onclick="confirmFileDelete('#{file.filename}')" Delete
- else
<i class="icon-picture icon-3x"></i>&nbsp;&nbsp; <span style="font-size: 20pt">#{file.filename}</span>
div style="margin-top: 3px; margin-bottom:10px"
| To use in an HTML file, paste this text: <code class="tiny" style="margin:0">&lt;img src="/#{file.filename}"&gt;</code>
div style="margin-top: 3px; margin-bottom: 30px"
| To use in an HTML file, paste this text: <code class="tiny" style="margin:0">&lt;img src="/#{file.filename}"&gt;</code>
span
i class="icon-globe" &nbsp;&nbsp;
a href="http://#{current_site.username}.neocities.org/#{file.filename}" target="_blank" View <br />
span
i class="icon-edit" &nbsp;&nbsp;
span: a href="/site_files/download/#{file.filename}" Download <br />
span
i class="icon-trash" &nbsp;&nbsp;
a href="#" onclick="confirmFileDelete('#{file.filename}')" Delete
.col.col-40

View file

@ -19,35 +19,39 @@ javascript:
h2.txt-Center Create a New Home Page
.col.col-50 style="margin:0 auto; float:none"
p.tiny First, enter a username. This will also be used as your site path.<br><b>Do not forget this, it will be used to sign in to and manage your home page.</b> It cannot contain spaces, and can only use the following characters: a-z A-Z 0-9 _ -
hr
p.tiny First, enter a username. This will also be used as your site name.<br><b>Do not forget this, it will be used to sign in to and manage your home page.</b> It can only contain letters, numbers, underscores and hyphens, and can only be 32 characters long.
br
h5 Username
p.tiny <input class="input-Area" name="username" type="text" placeholder="yourusername" value="#{@site.username}" autocapitalize="off" autocorrect="off">.neocities.org
hr
p.tiny Next, enter a password. This will be used to allow you to login. Minimum 5 characters. If you don't make it a good password, Dade Murphy from the movie Hackers will come in and steal your "garbage files".
hr
h5 Password
input class="input-Area" name="password" type="password"
br
hr
p.tiny Now you can enter an e-mail address. Your e-mail address is private and we will not show it to anyone for any reason. You don't have to provide one, but <b>we will not be able to reset your password without it, so don't lose your username and password if you leave this blank!</b>
hr
h5 Email
input class="input-Area" name="email" type="email" placeholder="youremail@example.com" value="#{@site.email}"
br
hr
p.tiny You can optionally enter some tags! Tags will allow others to find your site based on your interests, or your site's theme. <b>Separate multiple tags with commas</b>. Don't think too hard about this, you can change them later. You can have a maximum of ten tags, and there is a two word per tag maximum (extra words in a tag will be removed).
hr
h5 Tags
p: input class="input-Area" name="tags" type="text" style="width: 400px; max-width:100%" placeholder="pokemon, video games, bulbasaur" value="#{params[:tags]}" autocapitalize="off" autocorrect="off"
hr
input name="is_nsfw" type="hidden" value="false"
p: strong If your page will contain objectionable (adult) content, check this box:&nbsp;&nbsp;&nbsp;<input name="is_nsfw" type="checkbox" value="true" style="margin-top:0">
p If your page will contain objectionable (adult) content, check this box:&nbsp;&nbsp;&nbsp;<input name="is_nsfw" type="checkbox" value="true" style="margin-top:0">
hr
p.tiny <b>Last thing!</b> Enter these two words correctly (with spaces) so we know you're not a robot (don't worry robots, we still love you).
div style="background:#fff; width:100%; overflow:auto"
== recaptcha_tag :challenge, ssl: true

View file

@ -53,9 +53,7 @@ css:
option value="ace/theme/twilight" Twilight
option value="ace/theme/vibrant_ink" Vibrant Ink
div id="editor" style="width: 100%; height: 600px; position: relative; margin-bottom:25px"
== encoding_fix @file_data
<div id="editor" style="width: 100%; height: 600px; position: relative; margin-bottom:25px">#{{encoding_fix(@file_data)}}</div>
.row
.col.col-33.txt-Center style="margin-bottom:10px"
@ -125,4 +123,5 @@ css:
editor.getSession().setUseWrapMode(true);
editor.setFontSize(14);
editor.setShowPrintMargin(false);
});

View file

@ -1,8 +1,8 @@
server {
listen 80;
server_name <%= current_site.domain %> *.<%= current_site.domain %>;
server_name <%= domain %> *.<%= domain %>;
access_log /var/log/nginx/neocities-domains.log neocitiesdomain;
root /home/web/neocities-web/public/sites/<%= current_site.username %>;
root /home/web/neocities-web/public/sites/<%= username %>;
index /index.html;
error_page 404 = @notfound;

View file

@ -3,7 +3,7 @@ html
head
meta http-equiv="Content-Type" content="text/html; charset=UTF-8"
title #{template_site_title @site.username} - Front Page
title #{username}
meta name="description" content=""
meta name="keywords" content=""

View file

@ -3,7 +3,7 @@ html
head
meta http-equiv="Content-Type" content="text/html; charset=UTF-8"
title #{template_site_title @site.username} - Not Found
title #{username} - Page Not Found
link href="//groundfloor.neocities.org/default.css" rel="stylesheet" type="text/css" media="all"