Bläddra i källkod

Add User model with secure password

Frans Bergman 7 år sedan
förälder
incheckning
61d4f54dbe

+ 1 - 1
Gemfile

@@ -34,7 +34,7 @@ gem "font-awesome-rails"
 # Use Redis adapter to run Action Cable in production
 # Use Redis adapter to run Action Cable in production
 # gem 'redis', '~> 3.0'
 # gem 'redis', '~> 3.0'
 # Use ActiveModel has_secure_password
 # Use ActiveModel has_secure_password
-# gem 'bcrypt', '~> 3.1.7'
+gem 'bcrypt', '~> 3.1.7'
 
 
 # Use Capistrano for deployment
 # Use Capistrano for deployment
 # gem 'capistrano-rails', group: :development
 # gem 'capistrano-rails', group: :development

+ 2 - 0
Gemfile.lock

@@ -43,6 +43,7 @@ GEM
     arel (8.0.0)
     arel (8.0.0)
     autoprefixer-rails (7.1.6)
     autoprefixer-rails (7.1.6)
       execjs
       execjs
+    bcrypt (3.1.11)
     bindex (0.5.0)
     bindex (0.5.0)
     bootstrap-sass (3.3.7)
     bootstrap-sass (3.3.7)
       autoprefixer-rails (>= 5.2.1)
       autoprefixer-rails (>= 5.2.1)
@@ -190,6 +191,7 @@ PLATFORMS
   ruby
   ruby
 
 
 DEPENDENCIES
 DEPENDENCIES
+  bcrypt (~> 3.1.7)
   bootstrap-sass (~> 3.3.7)
   bootstrap-sass (~> 3.3.7)
   byebug
   byebug
   capybara (~> 2.13)
   capybara (~> 2.13)

+ 3 - 0
app/assets/javascripts/users.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://coffeescript.org/

+ 3 - 0
app/assets/stylesheets/users.scss

@@ -0,0 +1,3 @@
+// Place all the styles related to the Users controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/

+ 4 - 0
app/controllers/users_controller.rb

@@ -0,0 +1,4 @@
+class UsersController < ApplicationController
+  def new
+  end
+end

+ 2 - 0
app/helpers/users_helper.rb

@@ -0,0 +1,2 @@
+module UsersHelper
+end

+ 24 - 0
app/models/user.rb

@@ -0,0 +1,24 @@
+class User < ApplicationRecord
+
+  validates :name, presence: true
+
+  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
+  validates :email, presence: true, length: { maximum: 255 },
+                    format: { with: VALID_EMAIL_REGEX },
+                    uniqueness: { case_sensitive: false }
+  before_save { email.downcase! }
+
+  validates :login, presence: true, length: { maximum: 50 },
+                    format: { with: /\A[a-zA-Z0-9_]+\Z/ },
+                    uniqueness: { case_sensitive: false }
+
+  has_secure_password
+  validates :password, presence: true, length: { minimum: 6 }
+
+  # Returns the hash digest of the given string.
+  def User.digest(string)
+    cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
+                                                  BCrypt::Engine.cost
+    BCrypt::Password.create(string, cost: cost)
+  end
+end

+ 2 - 0
app/views/users/new.html.erb

@@ -0,0 +1,2 @@
+<h1>Users#new</h1>
+<p>Find me in app/views/users/new.html.erb</p>

+ 2 - 0
config/routes.rb

@@ -1,4 +1,6 @@
 Rails.application.routes.draw do
 Rails.application.routes.draw do
+  get 'users/new'
+
   root 'static_pages#home'
   root 'static_pages#home'
 
 
   get '/home', to: 'static_pages#home'
   get '/home', to: 'static_pages#home'

+ 11 - 0
db/migrate/20171217104902_create_users.rb

@@ -0,0 +1,11 @@
+class CreateUsers < ActiveRecord::Migration[5.1]
+  def change
+    create_table :users do |t|
+      t.string :name
+      t.string :login
+      t.string :email
+
+      t.timestamps
+    end
+  end
+end

+ 5 - 0
db/migrate/20171217111120_add_index_to_users_login.rb

@@ -0,0 +1,5 @@
+class AddIndexToUsersLogin < ActiveRecord::Migration[5.1]
+  def change
+    add_index :users, :login, unique: true
+  end
+end

+ 5 - 0
db/migrate/20171217122327_add_password_digest_to_users.rb

@@ -0,0 +1,5 @@
+class AddPasswordDigestToUsers < ActiveRecord::Migration[5.1]
+  def change
+    add_column :users, :password_digest, :string
+  end
+end

+ 25 - 0
db/schema.rb

@@ -0,0 +1,25 @@
+# This file is auto-generated from the current state of the database. Instead
+# of editing this file, please use the migrations feature of Active Record to
+# incrementally modify your database, and then regenerate this schema definition.
+#
+# Note that this schema.rb definition is the authoritative source for your
+# database schema. If you need to create the application database on another
+# system, you should be using db:schema:load, not running all the migrations
+# from scratch. The latter is a flawed and unsustainable approach (the more migrations
+# you'll amass, the slower it'll run and the greater likelihood for issues).
+#
+# It's strongly recommended that you check this file into your version control system.
+
+ActiveRecord::Schema.define(version: 20171217122327) do
+
+  create_table "users", force: :cascade do |t|
+    t.string "name"
+    t.string "login"
+    t.string "email"
+    t.datetime "created_at", null: false
+    t.datetime "updated_at", null: false
+    t.string "password_digest"
+    t.index ["login"], name: "index_users_on_login", unique: true
+  end
+
+end

+ 9 - 0
test/controllers/users_controller_test.rb

@@ -0,0 +1,9 @@
+require 'test_helper'
+
+class UsersControllerTest < ActionDispatch::IntegrationTest
+  test "should get new" do
+    get users_new_url
+    assert_response :success
+  end
+
+end

+ 13 - 0
test/fixtures/users.yml

@@ -0,0 +1,13 @@
+# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
+
+daniel:
+  login: daniel_1234
+  name: Daniel Smith
+  email: daniel.smith@example.com
+  password_digest: <%= User.digest('password') %>
+
+ben:
+  login: ben_4321
+  name: Ben Jones
+  email: ben.jones@example.com
+  password_digest: <%= User.digest('password') %>

+ 104 - 0
test/models/user_test.rb

@@ -0,0 +1,104 @@
+require 'test_helper'
+
+class UserTest < ActiveSupport::TestCase
+  def setup
+    @user = User.new(login: "example", name: "Example User",
+                     email: "user@example.com",
+                     password: "foobar", password_confirmation: "foobar")
+  end
+
+  test "should be valid" do
+    assert @user.valid?
+  end
+
+  test "name should be present" do
+    @user.name = "     "
+    assert_not @user.valid?
+  end
+
+  test "login should be present" do
+    @user.name = "     "
+    assert_not @user.valid?
+  end
+
+  test "email should be present" do
+    @user.email = "     "
+    assert_not @user.valid?
+  end
+
+  test "login should not be too long" do
+    @user.login = "a" * 51
+    assert_not @user.valid?
+  end
+
+  test "email should not be too long" do
+    @user.email = "a" * 244 + "@example.com"
+    assert_not @user.valid?
+  end
+
+  test "email validation should accept valid addresses" do
+    valid_addresses = %w[user@example.com USER@foo.COM A_US-ER@foo.bar.org
+                         first.last@foo.jp alice+bob@baz.cn]
+    valid_addresses.each do |valid_address|
+      @user.email = valid_address
+      assert @user.valid?, "#{valid_address.inspect} should be valid"
+    end
+  end
+
+  test "email validation should reject invalid addresses" do
+    invalid_addresses = %w[user@example,com user_at_foo.org user.name@example.
+                           foo@bar_baz.com foo@bar+baz.com]
+    invalid_addresses.each do |invalid_address|
+      @user.email = invalid_address
+      assert_not @user.valid?, "#{invalid_address.inspect} should be invalid"
+    end
+  end
+
+  test "login validation should accept valid login" do
+    valid_logins = %w[foobar FooBar Foo_Bar 1234_Foo_Bar_5678]
+    valid_logins.each do |valid_login|
+      @user.login = valid_login
+      assert @user.valid?, "#{valid_login.inspect} should be valid"
+    end
+  end
+
+  test "login validation should reject invalid login" do
+    invalid_logins = %w[foo:bar Foo*Bar Foo_^^Bar 1234!"_Foo_Bar_-5678]
+    invalid_logins.each do |invalid_login|
+      @user.login = invalid_login
+      assert_not @user.valid?, "#{invalid_login.inspect} should be invalid"
+    end
+  end
+
+  test "login should be unique" do
+    duplicate_user = @user.dup
+    duplicate_user.email = "foo@bar.com"
+    @user.save
+    assert_not duplicate_user.valid?
+  end
+
+  test "email addresses should be unique" do
+    duplicate_user = @user.dup
+    duplicate_user.login = "bar_foo"
+    duplicate_user.email = @user.email.upcase
+    @user.save
+    assert_not duplicate_user.valid?
+  end
+
+  test "email addresses should be saved as lower-case" do
+    mixed_case_email = "Foo@ExAMPle.CoM"
+    @user.email = mixed_case_email
+    @user.save
+    assert_equal mixed_case_email.downcase, @user.reload.email
+  end
+
+  test "password should be present (nonblank)" do
+    @user.password = @user.password_confirmation = " " * 6
+    assert_not @user.valid?
+  end
+
+  test "password should have a minimum length" do
+    @user.password = @user.password_confirmation = "a" * 5
+    assert_not @user.valid?
+  end
+end