From 63cd8feadbc85bed6946532214b5b45469e60ca7 Mon Sep 17 00:00:00 2001 From: Jared Pace Date: Tue, 10 Aug 2010 22:43:36 -0400 Subject: [PATCH] WIP - Add Users backed by Devise --- Gemfile | 1 + Gemfile.lock | 9 ++++++++- README.md | 7 +++++-- app/controllers/application_controller.rb | 3 +++ app/mailers/mailer.rb | 1 - app/models/user.rb | 13 +++++++++++++ app/views/devise/confirmations/new.html.haml | 9 +++++++++ app/views/devise/mailer/confirmation_instructions.html.haml | 4 ++++ app/views/devise/mailer/reset_password_instructions.html.haml | 6 ++++++ app/views/devise/mailer/unlock_instructions.html.haml | 5 +++++ app/views/devise/passwords/edit.html.haml | 14 ++++++++++++++ app/views/devise/passwords/new.html.haml | 9 +++++++++ app/views/devise/registrations/edit.html.haml | 27 +++++++++++++++++++++++++++ app/views/devise/registrations/new.html.haml | 20 ++++++++++++++++++++ app/views/devise/sessions/new.html.haml | 16 ++++++++++++++++ app/views/devise/shared/_links.haml | 15 +++++++++++++++ app/views/devise/unlocks/new.html.haml | 9 +++++++++ config/initializers/_load_config.rb | 11 +++++++++++ config/initializers/devise.rb | 142 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ config/initializers/load_config.rb | 8 -------- config/locales/devise.en.yml | 39 +++++++++++++++++++++++++++++++++++++++ config/routes.rb | 4 ++++ db/seeds.rb | 23 ++++++++++++++++------- spec/models/user_spec.rb | 5 +++++ 24 files changed, 381 insertions(+), 19 deletions(-) create mode 100644 app/models/user.rb create mode 100644 app/views/devise/confirmations/new.html.haml create mode 100644 app/views/devise/mailer/confirmation_instructions.html.haml create mode 100644 app/views/devise/mailer/reset_password_instructions.html.haml create mode 100644 app/views/devise/mailer/unlock_instructions.html.haml create mode 100644 app/views/devise/passwords/edit.html.haml create mode 100644 app/views/devise/passwords/new.html.haml create mode 100644 app/views/devise/registrations/edit.html.haml create mode 100644 app/views/devise/registrations/new.html.haml create mode 100644 app/views/devise/sessions/new.html.haml create mode 100644 app/views/devise/shared/_links.haml create mode 100644 app/views/devise/unlocks/new.html.haml create mode 100644 config/initializers/_load_config.rb create mode 100644 config/initializers/devise.rb delete mode 100644 config/initializers/load_config.rb create mode 100644 config/locales/devise.en.yml create mode 100644 spec/models/user_spec.rb diff --git a/Gemfile b/Gemfile index 8f3fcf6..61e1d07 100644 --- a/Gemfile +++ b/Gemfile @@ -6,6 +6,7 @@ gem 'bson_ext', :require => nil gem 'mongoid', '2.0.0.beta.15' gem 'haml' gem 'will_paginate' +gem 'devise', '1.1.1' group :development, :test do gem 'rspec-rails', '>= 2.0.0.beta.19' diff --git a/Gemfile.lock b/Gemfile.lock index 0324d6c..68a23b8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -30,10 +30,14 @@ GEM activesupport (3.0.0.rc) arel (0.4.0) activesupport (>= 3.0.0.beta) + bcrypt-ruby (2.1.2) bson (1.0.4) bson_ext (1.0.4) builder (2.1.2) database_cleaner (0.5.2) + devise (1.1.1) + bcrypt-ruby (~> 2.1.2) + warden (~> 0.10.7) diff-lcs (1.1.2) erubis (2.6.6) abstract (>= 1.0.0) @@ -41,7 +45,7 @@ GEM factory_girl_rails (1.0) factory_girl (~> 1.3) rails (>= 3.0.0.beta4) - haml (3.0.15) + haml (3.0.16) i18n (0.4.1) libxml-ruby (1.1.4) mail (2.2.5) @@ -93,6 +97,8 @@ GEM treetop (1.4.8) polyglot (>= 0.3.1) tzinfo (0.3.22) + warden (0.10.7) + rack (>= 1.0.0) webrat (0.7.2.beta.1) nokogiri (>= 1.2.0) rack (>= 1.0) @@ -105,6 +111,7 @@ PLATFORMS DEPENDENCIES bson_ext database_cleaner (= 0.5.2) + devise (= 1.1.1) factory_girl_rails haml libxml-ruby diff --git a/README.md b/README.md index 97431ef..2779028 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ Errbit: The open source self-hosted Hoptoad Server ===================================================== -WIP +Installation +------------ -TODO: License \ No newline at end of file +1. Install MongoDB +2. Install & Run Bundler +3. Seed DB - rake db:seed \ No newline at end of file diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index e8065d9..eebd7e4 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,3 +1,6 @@ class ApplicationController < ActionController::Base protect_from_forgery + + before_filter :authenticate_user! + end diff --git a/app/mailers/mailer.rb b/app/mailers/mailer.rb index f474a7d..878bf6a 100644 --- a/app/mailers/mailer.rb +++ b/app/mailers/mailer.rb @@ -1,6 +1,5 @@ class Mailer < ActionMailer::Base default :from => Errbit::Config.email_from - default_url_options[:host] = Errbit::Config.host def err_notification(notice) @notice = notice diff --git a/app/models/user.rb b/app/models/user.rb new file mode 100644 index 0000000..2430a1b --- /dev/null +++ b/app/models/user.rb @@ -0,0 +1,13 @@ +class User + include Mongoid::Document + + devise :database_authenticatable, :registerable, + :recoverable, :rememberable, :trackable, + :validatable, :token_authenticatable + + field :name + field :admin, :type => Boolean, :default => false + + validates_presence_of :name + +end diff --git a/app/views/devise/confirmations/new.html.haml b/app/views/devise/confirmations/new.html.haml new file mode 100644 index 0000000..810b8ca --- /dev/null +++ b/app/views/devise/confirmations/new.html.haml @@ -0,0 +1,9 @@ +%h2 Resend confirmation instructions += form_for(resource, :as => resource_name, :url => confirmation_path(resource_name), :html => { :method => :post }) do |f| + = devise_error_messages! + %p + = f.label :email + %br/ + = f.text_field :email + %p= f.submit "Resend confirmation instructions" += render :partial => "devise/shared/links" diff --git a/app/views/devise/mailer/confirmation_instructions.html.haml b/app/views/devise/mailer/confirmation_instructions.html.haml new file mode 100644 index 0000000..7840b9c --- /dev/null +++ b/app/views/devise/mailer/confirmation_instructions.html.haml @@ -0,0 +1,4 @@ +%p + Welcome #{@resource.email}! +%p You can confirm your account through the link below: +%p= link_to 'Confirm my account', confirmation_url(@resource, :confirmation_token => @resource.confirmation_token) diff --git a/app/views/devise/mailer/reset_password_instructions.html.haml b/app/views/devise/mailer/reset_password_instructions.html.haml new file mode 100644 index 0000000..4fc4743 --- /dev/null +++ b/app/views/devise/mailer/reset_password_instructions.html.haml @@ -0,0 +1,6 @@ +%p + Hello #{@resource.email}! +%p Someone has requested a link to change your password, and you can do this through the link below. +%p= link_to 'Change my password', edit_password_url(@resource, :reset_password_token => @resource.reset_password_token) +%p If you didn't request this, please ignore this email. +%p Your password won't change until you access the link above and create a new one. diff --git a/app/views/devise/mailer/unlock_instructions.html.haml b/app/views/devise/mailer/unlock_instructions.html.haml new file mode 100644 index 0000000..34b0e9e --- /dev/null +++ b/app/views/devise/mailer/unlock_instructions.html.haml @@ -0,0 +1,5 @@ +%p + Hello #{@resource.email}! +%p Your account has been locked due to an excessive amount of unsuccessful sign in attempts. +%p Click the link below to unlock your account: +%p= link_to 'Unlock my account', unlock_url(@resource, :unlock_token => @resource.unlock_token) diff --git a/app/views/devise/passwords/edit.html.haml b/app/views/devise/passwords/edit.html.haml new file mode 100644 index 0000000..543d47e --- /dev/null +++ b/app/views/devise/passwords/edit.html.haml @@ -0,0 +1,14 @@ +%h2 Change your password += form_for(resource, :as => resource_name, :url => password_path(resource_name), :html => { :method => :put }) do |f| + = devise_error_messages! + = f.hidden_field :reset_password_token + %p + = f.label :password + %br/ + = f.password_field :password + %p + = f.label :password_confirmation + %br/ + = f.password_field :password_confirmation + %p= f.submit "Change my password" += render :partial => "devise/shared/links" diff --git a/app/views/devise/passwords/new.html.haml b/app/views/devise/passwords/new.html.haml new file mode 100644 index 0000000..de2274f --- /dev/null +++ b/app/views/devise/passwords/new.html.haml @@ -0,0 +1,9 @@ +%h2 Forgot your password? += form_for(resource, :as => resource_name, :url => password_path(resource_name), :html => { :method => :post }) do |f| + = devise_error_messages! + %p + = f.label :email + %br/ + = f.text_field :email + %p= f.submit "Send me reset password instructions" += render :partial => "devise/shared/links" diff --git a/app/views/devise/registrations/edit.html.haml b/app/views/devise/registrations/edit.html.haml new file mode 100644 index 0000000..d252d9a --- /dev/null +++ b/app/views/devise/registrations/edit.html.haml @@ -0,0 +1,27 @@ +%h2 + Edit #{resource_name.to_s.humanize} += form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :method => :put }) do |f| + = devise_error_messages! + %p + = f.label :email + %br/ + = f.text_field :email + %p + = f.label :password + %i (leave blank if you don't want to change it) + %br/ + = f.password_field :password + %p + = f.label :password_confirmation + %br/ + = f.password_field :password_confirmation + %p + = f.label :current_password + %i (we need your current password to confirm your changes) + %br/ + = f.password_field :current_password + %p= f.submit "Update" +%h3 Cancel my account +%p + Unhappy? #{link_to "Cancel my account", registration_path(resource_name), :confirm => "Are you sure?", :method => :delete}. += link_to "Back", :back diff --git a/app/views/devise/registrations/new.html.haml b/app/views/devise/registrations/new.html.haml new file mode 100644 index 0000000..ba1e2ac --- /dev/null +++ b/app/views/devise/registrations/new.html.haml @@ -0,0 +1,20 @@ +%h2 Sign up += form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f| + = devise_error_messages! + %p + = f.label :name + = f.text_field :name + %p + = f.label :email + %br/ + = f.text_field :email + %p + = f.label :password + %br/ + = f.password_field :password + %p + = f.label :password_confirmation + %br/ + = f.password_field :password_confirmation + %p= f.submit "Sign up" += render :partial => "devise/shared/links" diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml new file mode 100644 index 0000000..43cd659 --- /dev/null +++ b/app/views/devise/sessions/new.html.haml @@ -0,0 +1,16 @@ +%h2 Sign in += form_for(resource, :as => resource_name, :url => session_path(resource_name)) do |f| + %p + = f.label :email + %br/ + = f.text_field :email + %p + = f.label :password + %br/ + = f.password_field :password + - if devise_mapping.rememberable? + %p + = f.check_box :remember_me + = f.label :remember_me + %p= f.submit "Sign in" += render :partial => "devise/shared/links" diff --git a/app/views/devise/shared/_links.haml b/app/views/devise/shared/_links.haml new file mode 100644 index 0000000..a754735 --- /dev/null +++ b/app/views/devise/shared/_links.haml @@ -0,0 +1,15 @@ +- if controller_name != 'sessions' + = link_to "Sign in", new_session_path(resource_name) + %br/ +- if devise_mapping.registerable? && controller_name != 'registrations' + = link_to "Sign up", new_registration_path(resource_name) + %br/ +- if devise_mapping.recoverable? && controller_name != 'passwords' + = link_to "Forgot your password?", new_password_path(resource_name) + %br/ +- if devise_mapping.confirmable? && controller_name != 'confirmations' + = link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) + %br/ +- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' + = link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) + %br/ diff --git a/app/views/devise/unlocks/new.html.haml b/app/views/devise/unlocks/new.html.haml new file mode 100644 index 0000000..afb0daa --- /dev/null +++ b/app/views/devise/unlocks/new.html.haml @@ -0,0 +1,9 @@ +%h2 Resend unlock instructions += form_for(resource, :as => resource_name, :url => unlock_path(resource_name), :html => { :method => :post }) do |f| + = devise_error_messages! + %p + = f.label :email + %br/ + = f.text_field :email + %p= f.submit "Resend unlock instructions" += render :partial => "devise/shared/links" diff --git a/config/initializers/_load_config.rb b/config/initializers/_load_config.rb new file mode 100644 index 0000000..1e96265 --- /dev/null +++ b/config/initializers/_load_config.rb @@ -0,0 +1,11 @@ +require 'ostruct' + +yaml = File.read(Rails.root.join('config','config.yml')) +config = YAML.load(yaml) + +config.merge!(config.delete(Rails.env)) if config.has_key?(Rails.env) + +Errbit::Config = OpenStruct.new(config) + +# Set config specific values +ActionMailer::Base.default_url_options[:host] = Errbit::Config.host \ No newline at end of file diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb new file mode 100644 index 0000000..8acf89e --- /dev/null +++ b/config/initializers/devise.rb @@ -0,0 +1,142 @@ +# Use this hook to configure devise mailer, warden hooks and so forth. The first +# four configuration values can also be set straight in your models. +Devise.setup do |config| + # ==> Mailer Configuration + # Configure the e-mail address which will be shown in DeviseMailer. + config.mailer_sender = Errbit::Config.email_from + + # Configure the class responsible to send e-mails. + # config.mailer = "Devise::Mailer" + + # ==> ORM configuration + # Load and configure the ORM. Supports :active_record (default) and + # :mongoid (bson_ext recommended) by default. Other ORMs may be + # available as additional gems. + require 'devise/orm/mongoid' + + # ==> Configuration for any authentication mechanism + # Configure which keys are used when authenticating an user. By default is + # just :email. You can configure it to use [:username, :subdomain], so for + # authenticating an user, both parameters are required. Remember that those + # parameters are used only when authenticating and not when retrieving from + # session. If you need permissions, you should implement that in a before filter. + # config.authentication_keys = [ :email ] + + # Tell if authentication through request.params is enabled. True by default. + # config.params_authenticatable = true + + # Tell if authentication through HTTP Basic Auth is enabled. True by default. + # config.http_authenticatable = true + + # Set this to true to use Basic Auth for AJAX requests. True by default. + # config.http_authenticatable_on_xhr = true + + # The realm used in Http Basic Authentication + # config.http_authentication_realm = "Application" + + # ==> Configuration for :database_authenticatable + # For bcrypt, this is the cost for hashing the password and defaults to 10. If + # using other encryptors, it sets how many times you want the password re-encrypted. + config.stretches = 10 + + # Define which will be the encryption algorithm. Devise also supports encryptors + # from others authentication tools as :clearance_sha1, :authlogic_sha512 (then + # you should set stretches above to 20 for default behavior) and :restful_authentication_sha1 + # (then you should set stretches to 10, and copy REST_AUTH_SITE_KEY to pepper) + config.encryptor = :bcrypt + + # Setup a pepper to generate the encrypted password. + config.pepper = "425f10f555c1a4718aff3370ef9dd2d97a21622beb0400fde6b52177375ddcbe37a2dac6af9bca835c988e00c32887ee940ba111a78eab48234d8799936d36b9" + + # ==> Configuration for :confirmable + # The time you want to give your user to confirm his account. During this time + # he will be able to access your application without confirming. Default is nil. + # When confirm_within is zero, the user won't be able to sign in without confirming. + # You can use this to let your user access some features of your application + # without confirming the account, but blocking it after a certain period + # (ie 2 days). + # config.confirm_within = 2.days + + # ==> Configuration for :rememberable + # The time the user will be remembered without asking for credentials again. + config.remember_for = 2.weeks + + # If true, a valid remember token can be re-used between multiple browsers. + # config.remember_across_browsers = true + + # If true, extends the user's remember period when remembered via cookie. + # config.extend_remember_period = false + + # ==> Configuration for :validatable + # Range for password length + config.password_length = 6..20 + + # Regex to use to validate the email address + config.email_regexp = /^([\w\.%\+\-]+)@([\w\-]+\.)+([\w]{2,})$/i + + # ==> Configuration for :timeoutable + # The time you want to timeout the user session without activity. After this + # time the user will be asked for credentials again. + # config.timeout_in = 10.minutes + + # ==> Configuration for :lockable + # Defines which strategy will be used to lock an account. + # :failed_attempts = Locks an account after a number of failed attempts to sign in. + # :none = No lock strategy. You should handle locking by yourself. + # config.lock_strategy = :failed_attempts + + # Defines which strategy will be used to unlock an account. + # :email = Sends an unlock link to the user email + # :time = Re-enables login after a certain amount of time (see :unlock_in below) + # :both = Enables both strategies + # :none = No unlock strategy. You should handle unlocking by yourself. + # config.unlock_strategy = :both + + # Number of authentication tries before locking an account if lock_strategy + # is failed attempts. + # config.maximum_attempts = 20 + + # Time interval to unlock the account if :time is enabled as unlock_strategy. + # config.unlock_in = 1.hour + + # ==> Configuration for :token_authenticatable + # Defines name of the authentication token params key + config.token_authentication_key = :auth_token + + # ==> Scopes configuration + # Turn scoped views on. Before rendering "sessions/new", it will first check for + # "users/sessions/new". It's turned off by default because it's slower if you + # are using only default views. + # config.scoped_views = true + + # Configure the default scope given to Warden. By default it's the first + # devise role declared in your routes. + # config.default_scope = :user + + # Configure sign_out behavior. + # By default sign_out is scoped (i.e. /users/sign_out affects only :user scope). + # In case of sign_out_all_scopes set to true any logout action will sign out all active scopes. + # config.sign_out_all_scopes = false + + # ==> Navigation configuration + # Lists the formats that should be treated as navigational. Formats like + # :html, should redirect to the sign in page when the user does not have + # access, but formats like :xml or :json, should return 401. + # If you have any extra navigational formats, like :iphone or :mobile, you + # should add them to the navigational formats lists. Default is [:html] + # config.navigational_formats = [:html, :iphone] + + # ==> Warden configuration + # If you want to use other strategies, that are not (yet) supported by Devise, + # you can configure them inside the config.warden block. The example below + # allows you to setup OAuth, using http://github.com/roman/warden_oauth + # + # config.warden do |manager| + # manager.oauth(:twitter) do |twitter| + # twitter.consumer_secret = + # twitter.consumer_key = + # twitter.options :site => 'http://twitter.com' + # end + # manager.default_strategies(:scope => :user).unshift :twitter_oauth + # end +end diff --git a/config/initializers/load_config.rb b/config/initializers/load_config.rb deleted file mode 100644 index 52d8b4a..0000000 --- a/config/initializers/load_config.rb +++ /dev/null @@ -1,8 +0,0 @@ -require 'ostruct' - -yaml = File.read(Rails.root.join('config','config.yml')) -config = YAML.load(yaml) - -config.merge!(config.delete(Rails.env)) if config.has_key?(Rails.env) - -Errbit::Config = OpenStruct.new(config) \ No newline at end of file diff --git a/config/locales/devise.en.yml b/config/locales/devise.en.yml new file mode 100644 index 0000000..5e4e433 --- /dev/null +++ b/config/locales/devise.en.yml @@ -0,0 +1,39 @@ +en: + errors: + messages: + not_found: "not found" + already_confirmed: "was already confirmed" + not_locked: "was not locked" + + devise: + failure: + unauthenticated: 'You need to sign in or sign up before continuing.' + unconfirmed: 'You have to confirm your account before continuing.' + locked: 'Your account is locked.' + invalid: 'Invalid email or password.' + invalid_token: 'Invalid authentication token.' + timeout: 'Your session expired, please sign in again to continue.' + inactive: 'Your account was not activated yet.' + sessions: + signed_in: 'Signed in successfully.' + signed_out: 'Signed out successfully.' + passwords: + send_instructions: 'You will receive an email with instructions about how to reset your password in a few minutes.' + updated: 'Your password was changed successfully. You are now signed in.' + confirmations: + send_instructions: 'You will receive an email with instructions about how to confirm your account in a few minutes.' + confirmed: 'Your account was successfully confirmed. You are now signed in.' + registrations: + signed_up: 'You have signed up successfully. If enabled, a confirmation was sent to your e-mail.' + updated: 'You updated your account successfully.' + destroyed: 'Bye! Your account was successfully cancelled. We hope to see you again soon.' + unlocks: + send_instructions: 'You will receive an email with instructions about how to unlock your account in a few minutes.' + unlocked: 'Your account was successfully unlocked. You are now signed in.' + mailer: + confirmation_instructions: + subject: 'Confirmation instructions' + reset_password_instructions: + subject: 'Reset password instructions' + unlock_instructions: + subject: 'Unlock Instructions' diff --git a/config/routes.rb b/config/routes.rb index f021b94..7f8a2af 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,5 +1,7 @@ Errbit::Application.routes.draw do + devise_for :users + # Hoptoad Notifier Routes match '/notifier_api/v2/notices' => 'notices#create' match '/deploys.txt' => 'deploys#create' @@ -21,6 +23,8 @@ Errbit::Application.routes.draw do end end + devise_for :users + root :to => 'apps#index' end diff --git a/db/seeds.rb b/db/seeds.rb index 664d8c7..046d85a 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -1,7 +1,16 @@ -# This file should contain all the record creation needed to seed the database with its default values. -# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). -# -# Examples: -# -# cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }]) -# Mayor.create(:name => 'Daley', :city => cities.first) +# Create an initial Admin User +admin_email = "errbit@#{Errbit::Config.host}" +admin_pass = 'password' + +puts "Creating an initial admin user:" +puts "-- email: #{admin_email}" +puts "-- password: #{admin_pass}" +puts "" +puts "Be sure to change these credentials ASAP!" +User.create!({ + :name => 'Errbit Admin', + :email => admin_email, + :password => admin_pass, + :password_confirmation => admin_pass, + :admin => true +}) \ No newline at end of file diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb new file mode 100644 index 0000000..44032b4 --- /dev/null +++ b/spec/models/user_spec.rb @@ -0,0 +1,5 @@ +require 'spec_helper' + +describe User do + pending "add some examples to (or delete) #{__FILE__}" +end -- libgit2 0.21.2