diff --git a/Gemfile b/Gemfile index 9b0813b..262f341 100755 --- a/Gemfile +++ b/Gemfile @@ -49,7 +49,6 @@ group :assets do gem 'uglifier' end - gem 'jquery-rails' # To use ActiveModel has_secure_password @@ -78,3 +77,5 @@ gem 'aruba' gem 'execjs' gem 'therubyracer' +# Add SMTP server support using MailCatcher +gem 'mailcatcher' diff --git a/Gemfile.lock b/Gemfile.lock index b02055f..5c5b57f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -79,6 +79,7 @@ GEM gherkin (~> 2.12) multi_json (>= 1.7.5, < 2.0) multi_test (>= 0.0.2) + daemons (1.1.9) database_cleaner (1.0.1) debug_inspector (0.0.2) diff-lcs (1.2.5) @@ -150,6 +151,15 @@ GEM i18n (>= 0.4.0) mime-types (~> 1.16) treetop (~> 1.4.8) + mailcatcher (0.5.12) + activesupport (~> 3.0) + eventmachine (~> 1.0.0) + haml (>= 3.1, < 5) + mail (~> 2.3) + sinatra (~> 1.2) + skinny (~> 0.2.3) + sqlite3 (~> 1.3) + thin (~> 1.5.0) method_source (0.8.2) mime-types (1.25.1) multi_json (1.8.2) @@ -172,6 +182,8 @@ GEM rack (>= 0.4) rack-livereload (0.3.15) rack + rack-protection (1.5.1) + rack rack-ssl (1.3.3) rack rack-test (0.6.2) @@ -231,6 +243,13 @@ GEM multi_json simplecov-html (~> 0.7.1) simplecov-html (0.7.1) + sinatra (1.4.4) + rack (~> 1.4) + rack-protection (~> 1.4) + tilt (~> 1.3, >= 1.3.4) + skinny (0.2.3) + eventmachine (~> 1.0.0) + thin (~> 1.5.0) slim (2.0.2) temple (~> 0.6.6) tilt (>= 1.3.3, < 2.1) @@ -246,6 +265,10 @@ GEM therubyracer (0.12.0) libv8 (~> 3.16.14.0) ref + thin (1.5.1) + daemons (>= 1.0.9) + eventmachine (>= 0.12.6) + rack (>= 1.0.0) thor (0.18.1) tilt (1.4.1) timers (1.1.0) @@ -290,6 +313,7 @@ DEPENDENCIES jquery-fileupload-rails jquery-rails launchy + mailcatcher poltergeist powder pry diff --git a/README.md b/README.md index 680cbd0..bb79789 100755 --- a/README.md +++ b/README.md @@ -69,6 +69,27 @@ $ rake training NOTE: As vulnerabilities are fixed in the application, these specs will not change to `passing`, but to `pending`. +## Processing Email + +In order for RailsGoat to effectively process email, you will first need to run MailCatcher, an SMTP server that will intercept email messages and display them in a web interface. + +To start an instance of MailCatcher, simply run: + +``` +$ mailcatcher +``` + +If successful, you should see the following output: + +``` +Starting MailCatcher +==> smtp://127.0.0.1:1025 +==> http://127.0.0.1:1080 +*** MailCatcher runs as a daemon by default. Go to the web interface to quit. +``` + +Alternatively, you can run MailCatcher in the foreground by running `mailcatcher -f` in your terminal. + ## Contributing As changes are made to the application, the Capybara RSpecs can be used to verify that the vulnerabilities in the application are still intact. To use them in this way, and have them `pass` instead of `fail`, set the `RAILSGOAT_MAINTAINER` environment variable. diff --git a/app/assets/javascripts/password_resets.js.coffee b/app/assets/javascripts/password_resets.js.coffee new file mode 100644 index 0000000..7615679 --- /dev/null +++ b/app/assets/javascripts/password_resets.js.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ diff --git a/app/assets/stylesheets/main.css.erb b/app/assets/stylesheets/main.css.erb index 2296075..a46df97 100755 --- a/app/assets/stylesheets/main.css.erb +++ b/app/assets/stylesheets/main.css.erb @@ -4616,7 +4616,7 @@ button.close { .signup .signup-wrapper .actions { padding: 10px; } .signup .signup-wrapper .actions a { - color: #b3b3b3; } + color: #ffffff; } .signup .signup-wrapper .checkbox { visibility: hidden; } .signup .signup-wrapper .checkbox-wrapper { diff --git a/app/assets/stylesheets/password_resets.css.scss b/app/assets/stylesheets/password_resets.css.scss new file mode 100644 index 0000000..8160ad8 --- /dev/null +++ b/app/assets/stylesheets/password_resets.css.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the password_resets controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 9d5628d..7590751 100755 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -2,23 +2,23 @@ class ApplicationController < ActionController::Base before_filter :authenticated, :has_info helper_method :current_user, :is_admin? - + # Our security guy keep talking about sea-surfing, cool story bro. # protect_from_forgery - + private def current_user @current_user ||= User.find_by_user_id(session[:user_id].to_s) end - + def authenticated path = request.fullpath.present? ? root_url(:url => request.fullpath) : root_url redirect_to path and reset_session if not current_user end def is_admin? - current_user.admin if current_user + current_user.admin if current_user end def administrative @@ -27,11 +27,11 @@ class ApplicationController < ActionController::Base redirect_to root_url end end - + def has_info redirect = false if current_user - begin + begin if !(current_user.retirement || current_user.paid_time_off.schedule || current_user.paid_time_off || current_user.work_info || current_user.performance) redirect = true end @@ -41,5 +41,5 @@ class ApplicationController < ActionController::Base end redirect_to home_dashboard_index_path if redirect end - + end diff --git a/app/controllers/password_resets_controller.rb b/app/controllers/password_resets_controller.rb new file mode 100644 index 0000000..533643a --- /dev/null +++ b/app/controllers/password_resets_controller.rb @@ -0,0 +1,63 @@ +class PasswordResetsController < ApplicationController + skip_before_filter :authenticated + + + def reset_password + user = Marshal.load(Base64.decode64(params[:user])) unless params[:user].nil? + + if user && params[:password] && params[:confirm_password] && params[:password] == params[:confirm_password] + user.password = params[:password] + user.save! + flash[:success] = "Your password has been reset please login" + redirect_to :login + else + flash[:error] = "Error resetting your password. Please try again." + redirect_to :login + end + end + + def confirm_token + if !params[:token].nil? && is_valid?(params[:token]) + flash[:success] = "Password reset token confirmed! Please create a new password." + render :reset_password + else + flash[:error] = "Invalid password reset token. Please try again." + redirect_to :login + end + end + + def forgot_password + @user = User.find_by_email(params[:email]) unless params[:email].nil? + + if @user && password_reset_mailer(@user) + flash[:success] = "Password reset email sent to #{params[:email]}" + redirect_to :login + else + flash[:error] = "There was an issue sending password reset email to #{params[:email]}".html_safe unless params[:email].nil? + end + end + + private + + def password_reset_mailer(user) + token = generate_token(user.id, user.email) + UserMailer.forgot_password(user.email, token).deliver + end + + def generate_token(id, email) + hash = Digest::MD5.hexdigest(email) + "#{id}-#{hash}" + end + + def is_valid?(token) + if token =~ /(?\d+)-(?[A-Z0-9]{32})/i + + # Fetch the user by their id, and hash their email address + @user = User.find_by_id($~[:user_id]) + email = Digest::MD5.hexdigest(@user.email) + + # Compare and validate our hashes + return true if email == $~[:email_hash] + end + end +end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index aa6dde1..9a48c27 100755 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,18 +1,8 @@ class UsersController < ApplicationController skip_before_filter :has_info - skip_before_filter :authenticated, :only => [:new, :create, :forgot_password] + skip_before_filter :authenticated, :only => [:new, :create] - def forgot_password - @user = User.find_by_email(params[:email]) unless params[:email].nil? - - if @user && password_reset_mailer_setup(@user) - flash[:success] = "Password reset email sent to #{params[:email]}" - redirect_to :login - else - flash[:error] = "There was an issue sending password reset email to #{params[:email]}".html_safe unless params[:email].nil? - end - end def new @user = User.new @@ -63,16 +53,4 @@ class UsersController < ApplicationController end end - private - - def password_reset_mailer_setup(user) - token = generate_token(user.id, user.email) - #reset_password_mailer(user.email, token) - end - - def generate_token(id, email) - hash = Digest::MD5.hexdigest(email) - "#{id}~#{hash}" - end - end diff --git a/app/helpers/password_resets_helper.rb b/app/helpers/password_resets_helper.rb new file mode 100644 index 0000000..0c9d96e --- /dev/null +++ b/app/helpers/password_resets_helper.rb @@ -0,0 +1,2 @@ +module PasswordResetsHelper +end diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb new file mode 100644 index 0000000..209b839 --- /dev/null +++ b/app/mailers/user_mailer.rb @@ -0,0 +1,10 @@ +class UserMailer < ActionMailer::Base + default from: "noreply@railsgoat.dev" + + def forgot_password(email, token) + @token = token + @url = url_for(controller: "password_resets", action: "reset_password", only_path: false) + "?token=#{token}" + + mail(to: "#{email}", subject: "Reset your MetaCorp password") + end +end diff --git a/app/views/password_resets/forgot_password.html.erb b/app/views/password_resets/forgot_password.html.erb new file mode 100644 index 0000000..be3d8a3 --- /dev/null +++ b/app/views/password_resets/forgot_password.html.erb @@ -0,0 +1,32 @@ +
+

MetaCorp

+

A GoatGroup Company

+
+
+
+ + + +
+
+
+
\ No newline at end of file diff --git a/app/views/password_resets/reset_password.html.erb b/app/views/password_resets/reset_password.html.erb new file mode 100644 index 0000000..f931a33 --- /dev/null +++ b/app/views/password_resets/reset_password.html.erb @@ -0,0 +1,39 @@ +
+

MetaCorp

+

A GoatGroup Company

+
+
+
+ + + + + + + +
+
+
+
diff --git a/app/views/sessions/new.html.erb b/app/views/sessions/new.html.erb index a7c9932..a1fa5a0 100755 --- a/app/views/sessions/new.html.erb +++ b/app/views/sessions/new.html.erb @@ -1,34 +1,36 @@
-

MetaCorp

-

A GoatGroup Company

+

MetaCorp

+

A GoatGroup Company

-
-
- -
\ No newline at end of file +
+
\ No newline at end of file diff --git a/app/views/user_mailer/forgot_password.html.erb b/app/views/user_mailer/forgot_password.html.erb new file mode 100644 index 0000000..ca2fe76 --- /dev/null +++ b/app/views/user_mailer/forgot_password.html.erb @@ -0,0 +1,23 @@ + + + + + + +

Need help logging in?

+

+ A password reset was requested for your user account.
+
+ + To reset your MetaCorp password, simply click on the + following link and follow the instructions:
+
+ + <%= link_to "Click here to reset your password", @url %>
+
+ + If you don't want to change your password, you can ignore this email. +

+

Thanks, and have a great day!

+ + \ No newline at end of file diff --git a/app/views/user_mailer/forgot_password.text.erb b/app/views/user_mailer/forgot_password.text.erb new file mode 100644 index 0000000..92067db --- /dev/null +++ b/app/views/user_mailer/forgot_password.text.erb @@ -0,0 +1,13 @@ +Need help logging in? +========================================================== + +A password reset was requested for your user account. + +To reset your MetaCorp password, simply copy the +following link and follow the instructions: + +<%= @url %> + +If you don't want to change your password, you can ignore this email. + +Thanks, and have a great day! \ No newline at end of file diff --git a/app/views/users/forgot_password.html.erb b/app/views/users/forgot_password.html.erb deleted file mode 100644 index 034add2..0000000 --- a/app/views/users/forgot_password.html.erb +++ /dev/null @@ -1,30 +0,0 @@ -
-

MetaCorp

-

A GoatGroup Company

-
-
-
- -
-
-
\ No newline at end of file diff --git a/config/environments/development.rb b/config/environments/development.rb index 9db258d..39b0f53 100755 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -34,7 +34,12 @@ Railsgoat::Application.configure do # Expands the lines which load the assets config.assets.debug = true - + + # ActionMailer settings for email support + config.action_mailer.delivery_method = :smtp + config.action_mailer.smtp_settings = { :address => "localhost", :port => 1025 } + config.action_mailer.default_url_options = { :host => "localhost:3000" } + config.middleware.insert_before( Rack::Lock, Rack::LiveReload, :min_delay => 500, diff --git a/config/routes.rb b/config/routes.rb index 441d118..9c21e1a 100755 --- a/config/routes.rb +++ b/config/routes.rb @@ -3,10 +3,12 @@ Railsgoat::Application.routes.draw do get "login" => "sessions#new" get "signup" => "users#new" get "logout" => "sessions#destroy" - match "forgot_password" => "users#forgot_password" + match "forgot_password" => "password_resets#forgot_password" + get "password_resets" => "password_resets#confirm_token" + post "password_resets" => "password_resets#reset_password" + resources :sessions do - end resources :users do @@ -83,4 +85,4 @@ Railsgoat::Application.routes.draw do root :to => "sessions#new" -end \ No newline at end of file +end diff --git a/spec/controllers/password_resets_controller_spec.rb b/spec/controllers/password_resets_controller_spec.rb new file mode 100644 index 0000000..335cafc --- /dev/null +++ b/spec/controllers/password_resets_controller_spec.rb @@ -0,0 +1 @@ +require 'spec_helper' \ No newline at end of file diff --git a/spec/helpers/password_resets_helper_spec.rb b/spec/helpers/password_resets_helper_spec.rb new file mode 100644 index 0000000..a0df3dd --- /dev/null +++ b/spec/helpers/password_resets_helper_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +# Specs in this file have access to a helper object that includes +# the PasswordResetsHelper. For example: +# +# describe PasswordResetsHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# expect(helper.concat_strings("this","that")).to eq("this that") +# end +# end +# end +describe PasswordResetsHelper do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/mailers/user_mailer_spec.rb b/spec/mailers/user_mailer_spec.rb new file mode 100644 index 0000000..a79b75f --- /dev/null +++ b/spec/mailers/user_mailer_spec.rb @@ -0,0 +1 @@ +require "spec_helper" \ No newline at end of file diff --git a/spec/views/password_resets/new.html.erb_spec.rb b/spec/views/password_resets/new.html.erb_spec.rb new file mode 100644 index 0000000..38c6853 --- /dev/null +++ b/spec/views/password_resets/new.html.erb_spec.rb @@ -0,0 +1,5 @@ +require 'spec_helper' + +describe "password_resets/new.html.erb" do + pending "add some examples to (or delete) #{__FILE__}" +end