diff --git a/Gemfile b/Gemfile index e91c3ae..4edf025 100644 --- a/Gemfile +++ b/Gemfile @@ -1,11 +1,10 @@ source 'https://rubygems.org' git_source(:github) do |repo_name| - repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/") - "https://github.com/#{repo_name}.git" + repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?('/') + "https://github.com/#{repo_name}.git" end - # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' gem 'rails', '~> 5.0.2' @@ -32,6 +31,8 @@ gem 'jquery-rails' gem 'turbolinks', '~> 5' # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder gem 'jbuilder', '~> 2.5' +gem 'rack-attack' + # Use Redis adapter to run Action Cable in production # gem 'redis', '~> 3.0' # Use ActiveModel has_secure_password @@ -41,20 +42,22 @@ gem 'jbuilder', '~> 2.5' # gem 'capistrano-rails', group: :development group :development, :test do - gem 'rspec-rails' - # Call 'byebug' anywhere in the code to stop execution and get a debugger console - gem 'byebug', platform: :mri + gem 'rspec-rails' + gem 'brakeman' + gem 'bundler-audit' + # Call 'byebug' anywhere in the code to stop execution and get a debugger console + gem 'byebug', platform: :mri end group :development do - gem 'faker' - - # Access an IRB console on exception pages or by using <%= console %> anywhere in the code. - gem 'web-console', '>= 3.3.0' - gem 'listen', '~> 3.0.5' - # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring - gem 'spring' - gem 'spring-watcher-listen', '~> 2.0.0' + gem 'faker' + + # Access an IRB console on exception pages or by using <%= console %> anywhere in the code. + gem 'web-console', '>= 3.3.0' + gem 'listen', '~> 3.0.5' + # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring + gem 'spring' + gem 'spring-watcher-listen', '~> 2.0.0' end # Windows does not include zoneinfo files, so bundle the tzinfo-data gem diff --git a/Gemfile.lock b/Gemfile.lock index 30c443b..161f862 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -46,7 +46,11 @@ GEM bootstrap-sass (3.3.7) autoprefixer-rails (>= 5.2.1) sass (>= 3.3.4) + brakeman (3.6.2) builder (3.2.3) + bundler-audit (0.5.0) + bundler (~> 1.2) + thor (~> 0.18) byebug (9.0.6) coffee-rails (4.2.1) coffee-script (>= 2.2.0) @@ -98,6 +102,8 @@ GEM orm_adapter (0.5.0) puma (3.8.2) rack (2.0.1) + rack-attack (5.0.1) + rack rack-test (0.6.3) rack (>= 1.0) rails (5.0.2) @@ -192,6 +198,8 @@ PLATFORMS DEPENDENCIES bootstrap-sass + brakeman + bundler-audit byebug coffee-rails (~> 4.2) devise @@ -200,6 +208,7 @@ DEPENDENCIES jquery-rails listen (~> 3.0.5) puma (~> 3.0) + rack-attack rails (~> 5.0.2) rspec-rails sass-rails (~> 5.0) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 682a07d..5682b4c 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,29 +1,25 @@ class ApplicationController < ActionController::Base + protect_from_forgery with: :exception - # protect_from_forgery with: :exception + helper_method :current_cart - helper_method :current_cart - - def current_cart - @current_cart ||= find_cart - end + def current_cart + @current_cart ||= find_cart + end - private + private - def find_cart - cart = Cart.find_by(id: session[:cart_id]) - if cart.blank? - cart = Cart.create + def find_cart + cart = Cart.find_by(id: session[:cart_id]) + cart = Cart.create if cart.blank? + session[:cart_id] = cart.id + cart end - session[:cart_id] = cart.id - return cart - end - def require_admin! - unless current_user.is_admin? - flash[:alert] = "您的權限不足" - redirect_to root_path + def require_admin! + unless current_user.is_admin? + flash[:alert] = '您的權限不足' + redirect_to root_path + end end - end - end diff --git a/app/controllers/events_controller.rb b/app/controllers/events_controller.rb index 245dece..a88b519 100644 --- a/app/controllers/events_controller.rb +++ b/app/controllers/events_controller.rb @@ -1,21 +1,19 @@ class EventsController < ApplicationController + def index + @events = Event.all + end - def index - @events = Event.all - end + def show + @event = Event.find(params[:id]) + @comments = @event.comments - def show - @event = Event.find(params[:id]) - @comments = @event.comments + if params[:keyword] - if params[:keyword] - @comments = @comments.where( "comments.content LIKE '%#{params[:keyword]}%'") - end + @comments = @comments.where('comments.content LIKE ?', "%#{params[:keyword]}%") + end - if params[:sort] - @comments = @comments.order(params[:sort]) + if params[:sort] && ['id DESC', 'id ASC'].include?(params[:sort]) # 只有白名单内的参数可以用 + @comments = @comments.order(params[:sort]) + end end - - end - end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index f1d232d..556334c 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,27 +1,25 @@ class UsersController < ApplicationController + def show + @user = User.find(params[:id]) + end - def show - @user = User.find( params[:id] ) - end - - def edit - @user = current_user - end + def edit + @user = current_user + end - def update - @user = current_user + def update + @user = current_user - if @user.update(user_params) - redirect_to user_path(@user) - else - render "edit" + if @user.update(user_params) + redirect_to user_path(@user) + else + render 'edit' + end end - end - protected - - def user_params - params.require(:user).permit(:nickname, :role) - end + protected + def user_params + params.require(:user).permit(:nickname) + end end diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb index ed49051..8017777 100644 --- a/app/helpers/users_helper.rb +++ b/app/helpers/users_helper.rb @@ -1,15 +1,13 @@ require 'digest/md5' module UsersHelper - - def user_avatar_link(user) - # https://cn.gravatar.com/ - email_md5 = Digest::MD5.hexdigest(user.email) - gravatar_url = "https://www.gravatar.com/avatar/#{email_md5}" - - str = "" - - str.html_safe - end - + def user_avatar_link(user) + # https://cn.gravatar.com/ + email_md5 = Digest::MD5.hexdigest(user.email) + gravatar_url = "https://www.gravatar.com/avatar/#{email_md5}" + + content_tag(:div, + link_to(image_tag(gravatar_url), user_path(user)) + ' ' + user.display_name, + class: 'user-link') + end end diff --git a/app/views/events/show.html.erb b/app/views/events/show.html.erb index 0e89067..10fa83a 100644 --- a/app/views/events/show.html.erb +++ b/app/views/events/show.html.erb @@ -1,53 +1,41 @@

<%= @event.name %>

- <%= simple_format @event.description %> - - <%= form_tag event_path(@event), :method => :get do %> -

- <%= text_field_tag "keyword", "", :size => 50 %> - <%= submit_tag "Search" %> -

+

+ <%= text_field_tag "keyword", "", :size => 50 %> + <%= submit_tag "Search" %> +

<% end %> -

- <%= link_to "新留言在上", event_path(@event, :sort => "id DESC") %> - <%= link_to "舊留言在上", event_path(@event, :sort => "id ASC") %> + <%= link_to "新留言在上", event_path(@event, :sort => "id DESC") %> + <%= link_to "舊留言在上", event_path(@event, :sort => "id ASC") %>

- <% @comments.each do |comment| %> -
"> -
- <%= user_avatar_link(comment.author) %> +
"> +
+ <%= user_avatar_link(comment.author) %> +
+
+ <%= sanitize comment.content %> +
+
- -
- <%= raw comment.content %> -
- - -
<% end %> -
- <%= form_for [@event, Comment.new] do |f| %> -
- <%= f.label :content %> - <%= f.text_area :content, :class => "form-control" %> -
- -
- <%= f.submit "Comment", :class => "btn btn-primary" %> -
-<% end %> \ No newline at end of file +
+ <%= f.label :content %> + <%= f.text_area :content, :class => "form-control" %> +
+
+ <%= f.submit "Comment", :class => "btn btn-primary" %> +
+<% end %> diff --git a/config/application.rb b/config/application.rb index 2d9ee51..e6461b7 100644 --- a/config/application.rb +++ b/config/application.rb @@ -7,13 +7,14 @@ Bundler.require(*Rails.groups) module RailsRecipes - class Application < Rails::Application - # Settings in config/environments/* take precedence over those specified here. - # Application configuration should go into files in config/initializers - # -- all .rb files in that directory are automatically loaded. - config.time_zone = "Beijing" - - end + class Application < Rails::Application + # Settings in config/environments/* take precedence over those specified here. + # Application configuration should go into files in config/initializers + # -- all .rb files in that directory are automatically loaded. + config.time_zone = 'Beijing' + config.middleware.use Rack::Attack + end end -Time::DATE_FORMATS.merge!(:default => '%Y/%m/%d %I:%M %p', :ymd => '%Y/%m/%d') \ No newline at end of file +Time::DATE_FORMATS[:default] = '%Y/%m/%d %I:%M %p' +Time::DATE_FORMATS[:ymd] = '%Y/%m/%d' diff --git a/config/initializers/rack-attack.rb b/config/initializers/rack-attack.rb new file mode 100644 index 0000000..b92cd91 --- /dev/null +++ b/config/initializers/rack-attack.rb @@ -0,0 +1,51 @@ +class Rack::Attack + throttle('req/ip', limit: 180, period: 1.minutes, &:ip) + + ### Prevent Brute-Force Login Attacks ### + + # The most common brute-force login attack is a brute-force password + + # attack where an attacker simply tries a large number of emails and + + # passwords to see if any credentials match. + + # + + # Another common method of attack is to use a swarm of computers with + + # different IPs to try brute-forcing a password for a specific account. + + # Throttle POST requests to /login by IP address + + # + + # Key: "rack::attack:#{Time.now.to_i/:period}:logins/ip:#{req.ip}" + + throttle('logins/ip', limit: 5, period: 20.seconds) do |req| + req.ip if req.path == '/users/sign_in' && req.post? + end + + # Throttle POST requests to /login by email param + + # + + # Key: "rack::attack:#{Time.now.to_i/:period}:logins/email:#{req.email}" + + # + + # Note: This creates a problem where a malicious user could intentionally + + # throttle logins for another user and force their login requests to be + + # denied, but that's not very common and shouldn't happen to you. (Knock + + # on wood!) + + throttle('logins/email', limit: 5, period: 20.seconds) do |req| + if req.path == '/users/sign_in' && req.post? + # return the email if present, nil otherwise + + req.params['email'].presence + end + end +end diff --git a/config/routes.rb b/config/routes.rb index 2cfd938..53e80f0 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,37 +1,35 @@ Rails.application.routes.draw do + devise_for :users - devise_for :users + resources :users - resources :users - - resources :events do - resources :comments do - member do - get :highlight - end + resources :events do + resources :comments do + member do + post :highlight + end + end end - end - resource :cart do - collection do - post :clean - post :checkout + resource :cart do + collection do + post :clean + post :checkout + end end - end - resources :cart_items + resources :cart_items - resources :products do - member do - post :add_to_cart + resources :products do + member do + post :add_to_cart + end end - end - - namespace :admin do - root "events#index" - resources :events - end - root "events#index" + namespace :admin do + root 'events#index' + resources :events + end + root 'events#index' end