Add Login system
authorIra W. Snyder <devel@irasnyder.com>
Sat, 24 Nov 2007 07:35:18 +0000 (23:35 -0800)
committerIra W. Snyder <devel@irasnyder.com>
Sat, 24 Nov 2007 07:35:18 +0000 (23:35 -0800)
Signed-off-by: Ira W. Snyder <devel@irasnyder.com>
18 files changed:
app/controllers/application.rb
app/controllers/login_controller.rb [new file with mode: 0644]
app/helpers/login_helper.rb [new file with mode: 0644]
app/models/user.rb [new file with mode: 0644]
app/views/layouts/admin.rhtml [new file with mode: 0644]
app/views/login/add_user.rhtml [new file with mode: 0644]
app/views/login/delete_user.rhtml [new file with mode: 0644]
app/views/login/index.rhtml [new file with mode: 0644]
app/views/login/list_users.rhtml [new file with mode: 0644]
app/views/login/login.rhtml [new file with mode: 0644]
app/views/login/logout.rhtml [new file with mode: 0644]
db/development.sqlite3
db/migrate/027_create_users.rb [new file with mode: 0644]
db/schema.rb
public/stylesheets/depot.css [new file with mode: 0644]
test/fixtures/users.yml [new file with mode: 0644]
test/functional/login_controller_test.rb [new file with mode: 0644]
test/unit/user_test.rb [new file with mode: 0644]

index 4e5f623..e9da3d0 100644 (file)
@@ -3,5 +3,14 @@
 
 class ApplicationController < ActionController::Base
   # Pick a unique cookie name to distinguish our session data from others'
-  session :session_key => '_try5_session_id'
+  session :session_key => '_prippropprix_session_id'
+
+  private
+
+  def authorize
+    unless User.find_by_id(session[:user_id])
+      flash[:notice] = "Please log in"
+      redirect_to :controller => "login", :action => "login"
+    end
+  end
 end
diff --git a/app/controllers/login_controller.rb b/app/controllers/login_controller.rb
new file mode 100644 (file)
index 0000000..9352437
--- /dev/null
@@ -0,0 +1,54 @@
+class LoginController < ApplicationController
+  layout "admin"
+
+  # Make sure that a user logs in before doing any action here
+  before_filter :authorize, :except => :login
+
+  def add_user
+    @user = User.new(params[:user])
+    if request.post? and @user.save
+      flash.now[:notice] = "User #{@user.name} created"
+      @user = User.new
+    end
+  end
+
+  def login
+    session[:user_id] = nil
+    if request.post?
+      user = User.authenticate(params[:name], params[:password])
+      if user
+        session[:user_id] = user.id
+        redirect_to :action => 'index'
+      else
+        flash[:notice] = "Invalid user/password combination"
+      end
+    end
+  end
+
+  def logout
+    session[:user_id] = nil
+    flash[:notice] = "Logged Out"
+    redirect_to :action => :login
+  end
+
+  def index
+    # No code needed
+  end
+
+  def delete_user
+    if request.post?
+      user = User.find(params[:id])
+      begin
+        user.destroy
+        flash[:notice] = "User #{user.name} deleted"
+      rescue Exception => e
+        flash[:notice] = e.message
+      end
+    end
+    redirect_to(:action => :list_users)
+  end
+
+  def list_users
+    @all_users = User.find(:all)
+  end
+end
diff --git a/app/helpers/login_helper.rb b/app/helpers/login_helper.rb
new file mode 100644 (file)
index 0000000..a0418e3
--- /dev/null
@@ -0,0 +1,2 @@
+module LoginHelper
+end
diff --git a/app/models/user.rb b/app/models/user.rb
new file mode 100644 (file)
index 0000000..1edfc41
--- /dev/null
@@ -0,0 +1,59 @@
+require 'digest/sha1'
+
+class User < ActiveRecord::Base
+  validates_presence_of :name
+  validates_uniqueness_of :name
+
+  validates_length_of :password, :minimum => 6
+
+  attr_accessor :password_confirmation
+  validates_confirmation_of :password
+
+  def validate
+    errors.add_to_base("Missing password") if hashed_password.blank?
+  end
+
+  def self.authenticate(name, password)
+    user = self.find_by_name(name)
+    if user
+      expected_password = encrypted_password(password, user.salt)
+      if user.hashed_password != expected_password
+        user = nil
+      end
+    end
+    user
+  end
+
+  # 'password' is a virtual attribute
+  def password
+    @password
+  end
+
+  def password=(pwd)
+    @password = pwd
+    create_new_salt
+    self.hashed_password = User.encrypted_password(self.password, self.salt)
+  end
+
+  def after_destroy
+    if User.count.zero?
+      raise "Can't delete last user"
+    end
+  end
+
+
+  private
+
+
+  def self.encrypted_password(password, salt)
+    # According to the RoR book, 'wibble' makes it harder to guess, which
+    # I seriously doubt given my background in crypto
+    string_to_hash = password + 'wibble' + salt
+    Digest::SHA1.hexdigest(string_to_hash)
+  end
+
+  def create_new_salt
+    self.salt = self.object_id.to_s + rand.to_s
+  end
+
+end
diff --git a/app/views/layouts/admin.rhtml b/app/views/layouts/admin.rhtml
new file mode 100644 (file)
index 0000000..c6dda9a
--- /dev/null
@@ -0,0 +1,11 @@
+<%= stylesheet_link_tag "scaffold", "depot", :media => "all" %>
+<!--<%= image_tag("logo.png") %> <%= @page_title || "Pragmatic Bookshelf" %>-->
+
+<%= link_to "Products", :controller => 'admin', :action => 'list' %>
+<%= link_to "List users", :controller => 'login', :action => 'list_users' %>
+<%= link_to "Add user", :controller => 'login', :action => 'add_user' %>
+<%= link_to "Logout", :controller => 'login', :action => 'logout' %>
+
+<% if flash[:notice] -%>
+<%= flash[:notice] %>
+<% end -%> <%= yield :layout %>
diff --git a/app/views/login/add_user.rhtml b/app/views/login/add_user.rhtml
new file mode 100644 (file)
index 0000000..f76116a
--- /dev/null
@@ -0,0 +1,28 @@
+<div class="depot-form">
+
+  <%= error_messages_for 'user' %>
+
+  <fieldset>
+    <legend>Enter User Details</legend>
+
+    <% form_for :user do |form| %>
+      <p>
+        <label for="user_name">Name:</label>
+        <%= form.text_field :name, :size => 40 %>
+      </p>
+
+      <p>
+        <label for="user_password">Password:</label>
+        <%= form.password_field :password, :size => 40 %>
+      </p>
+
+      <p>
+        <label for="user_password_confirmation">Confirm:</label>
+        <%= form.password_field :password_confirmation, :size => 40 %>
+      </p>
+
+      <%= submit_tag "Add User", :class => "submit" %>
+
+    <% end %>
+  </fieldset>
+</div>
diff --git a/app/views/login/delete_user.rhtml b/app/views/login/delete_user.rhtml
new file mode 100644 (file)
index 0000000..054c9bd
--- /dev/null
@@ -0,0 +1,2 @@
+<h1>Login#delete_user</h1>
+<p>Find me in app/views/login/delete_user.rhtml</p>
diff --git a/app/views/login/index.rhtml b/app/views/login/index.rhtml
new file mode 100644 (file)
index 0000000..872918c
--- /dev/null
@@ -0,0 +1,4 @@
+<h1>Welcome</h1>
+It's <%= Time.now %>.
+
+FIXME: Add menu here!
diff --git a/app/views/login/list_users.rhtml b/app/views/login/list_users.rhtml
new file mode 100644 (file)
index 0000000..d730d82
--- /dev/null
@@ -0,0 +1,15 @@
+<h1>Employees</h1>
+<ul>
+  <% for user in @all_users %>
+    <li><%=link_to "[X]", { # link_to options
+                            :controller => 'login',
+                            :action => 'delete_user',
+                            :id => user },
+                          { # html options
+                            :method => :post,
+                            :confirm => "Really delete #{user.name}?"
+                          } %>
+        <%=h user.name %>
+    </li>
+  <% end %>
+</ul>
diff --git a/app/views/login/login.rhtml b/app/views/login/login.rhtml
new file mode 100644 (file)
index 0000000..926bbc7
--- /dev/null
@@ -0,0 +1,21 @@
+<div class="depot-form">
+  <fieldset>
+    <legend>Please Log In</legend>
+
+    <% form_tag do %>
+      <p>
+        <label for="name">Name:</label>
+        <%= text_field_tag :name, params[:name] %>
+      </p>
+
+      <p>
+        <label for="password">Password:</label>
+        <%= password_field_tag :password, params[:password] %>
+      </p>
+
+      <p>
+        <%= submit_tag "Login" %>
+      </p>
+    <% end %>
+  </fieldset>
+</div>
diff --git a/app/views/login/logout.rhtml b/app/views/login/logout.rhtml
new file mode 100644 (file)
index 0000000..2afdc9c
--- /dev/null
@@ -0,0 +1,2 @@
+<h1>Login#logout</h1>
+<p>Find me in app/views/login/logout.rhtml</p>
index 11394f6..e6ad50f 100644 (file)
Binary files a/db/development.sqlite3 and b/db/development.sqlite3 differ
diff --git a/db/migrate/027_create_users.rb b/db/migrate/027_create_users.rb
new file mode 100644 (file)
index 0000000..805a573
--- /dev/null
@@ -0,0 +1,14 @@
+class CreateUsers < ActiveRecord::Migration
+  def self.up
+    create_table :users do |t|
+      t.column :name, :string, :null => false
+      t.column :hashed_password, :string
+      t.column :salt, :string
+      t.column :manager, :boolean, :default => false
+    end
+  end
+
+  def self.down
+    drop_table :users
+  end
+end
index 192232a..f9d22a0 100644 (file)
@@ -2,7 +2,7 @@
 # migrations feature of ActiveRecord to incrementally modify your database, and
 # then regenerate this schema definition.
 
-ActiveRecord::Schema.define(:version => 26) do
+ActiveRecord::Schema.define(:version => 27) do
 
   create_table "coitems", :force => true do |t|
     t.column "customer_id", :integer
@@ -77,6 +77,13 @@ ActiveRecord::Schema.define(:version => 26) do
 # Could not dump table "sqlite_sequence" because of following StandardError
 #   Unknown type '' for column 'name'
 
+  create_table "users", :force => true do |t|
+    t.column "name",            :string,                     :null => false
+    t.column "hashed_password", :string
+    t.column "salt",            :string
+    t.column "manager",         :boolean, :default => false
+  end
+
   create_table "video_policies", :force => true do |t|
     t.column "day",         :integer,                               :null => false
     t.column "fee",         :decimal, :precision => 8, :scale => 2, :null => false
diff --git a/public/stylesheets/depot.css b/public/stylesheets/depot.css
new file mode 100644 (file)
index 0000000..247c343
--- /dev/null
@@ -0,0 +1,227 @@
+/* Global styles */
+
+/* START:notice */
+#notice {
+  border: 2px solid red;
+  padding: 1em;
+  margin-bottom: 2em;
+  background-color: #f0f0f0;
+  font: bold smaller sans-serif;
+}
+/* END:notice */
+
+/* Styles for admin/list */
+
+#product-list .list-title {
+       color:        #244;
+       font-weight:  bold;
+       font-size:    larger;
+}
+
+#product-list .list-image {
+  width:        60px;
+  height:       70px;
+}
+
+
+#product-list .list-actions {
+  font-size:    x-small;
+  text-align:   right;
+  padding-left: 1em;
+}
+
+#product-list .list-line-even {
+  background:   #e0f8f8;
+}
+
+#product-list .list-line-odd {
+  background:   #f8b0f8;
+}
+
+
+/* Styles for main page */
+
+#banner {
+  background: #9c9;
+  padding-top: 10px;
+  padding-bottom: 10px;
+  border-bottom: 2px solid;
+  font: small-caps 40px/40px "Times New Roman", serif;
+  color: #282;
+  text-align: center;
+}
+
+#banner img {
+  float: left;
+}
+
+#columns {
+  background: #141;
+}
+
+#main {
+  margin-left: 15em;
+  padding-top: 4ex;
+  padding-left: 2em;
+  background: white;
+}
+
+#side {
+  float: left;
+  padding-top: 1em;
+  padding-left: 1em;
+  padding-bottom: 1em;
+  width: 14em;
+  background: #141;
+}
+
+#side a {
+  color: #bfb;
+  font-size: small;
+}
+
+h1 {
+  font:  150% sans-serif;
+  color: #226;
+  border-bottom: 3px dotted #77d;
+}
+
+/* An entry in the store catalog */
+
+#store  .entry {
+  border-bottom: 1px dotted #77d;
+}
+
+#store  .title {
+  font-size: 120%;
+  font-family: sans-serif;
+}
+
+#store .entry img {
+  width: 75px;
+  float: left;
+}
+
+
+#store .entry h3 {
+ margin-bottom: 2px;
+ color: #227;
+}
+
+#store .entry p {
+ margin-top: 0px; 
+ margin-bottom: 0.8em; 
+}
+
+#store .entry .price-line {
+}
+
+#store .entry .add-to-cart {
+  position: relative;
+}
+
+#store .entry  .price {
+  color: #44a;
+  font-weight: bold;
+  margin-right: 2em;
+}
+
+/* START:inline */
+#store .entry form, #store .entry form div {
+  display: inline;
+}
+/* END:inline */
+
+/* START:cart */
+/* Styles for the cart in the main page and the sidebar */
+
+.cart-title {
+  font: 120% bold; 
+}
+
+.item-price, .total-line {
+  text-align: right;   
+}
+
+.total-line .total-cell {
+  font-weight: bold;
+  border-top: 1px solid #595;
+}
+
+
+/* Styles for the cart in the sidebar */
+
+#cart, #cart table {
+  font-size: smaller;  
+  color:     white;
+}
+
+#cart table {
+  border-top:    1px dotted #595;
+  border-bottom: 1px dotted #595;
+  margin-bottom: 10px;
+}
+/* END:cart */
+
+/* Styles for order form */
+
+.depot-form fieldset {
+  background: #efe;
+}
+
+.depot-form legend {
+  color: #dfd;
+  background: #141;
+  font-family: sans-serif;
+  padding: 0.2em 1em;
+}
+
+.depot-form label {
+  width: 5em;
+  float: left;
+  text-align: right;
+  margin-right: 0.5em;
+  display: block;
+}
+
+.depot-form .submit {
+  margin-left: 5.5em;
+}
+
+/* The error box */
+
+.fieldWithErrors {
+  padding: 2px;
+  background-color: red;
+  display: table;
+}
+
+#errorExplanation {
+  width: 400px;
+  border: 2px solid red;
+  padding: 7px;
+  padding-bottom: 12px;
+  margin-bottom: 20px;
+  background-color: #f0f0f0;
+}
+
+#errorExplanation h2 {
+  text-align: left;
+  font-weight: bold;
+  padding: 5px 5px 5px 15px;
+  font-size: 12px;
+  margin: -7px;
+  background-color: #c00;
+  color: #fff;
+}
+
+#errorExplanation p {
+  color: #333;
+  margin-bottom: 0;
+  padding: 5px;
+}
+
+#errorExplanation ul li {
+  font-size: 12px;
+  list-style: square;
+}
diff --git a/test/fixtures/users.yml b/test/fixtures/users.yml
new file mode 100644 (file)
index 0000000..b49c4eb
--- /dev/null
@@ -0,0 +1,5 @@
+# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
+one:
+  id: 1
+two:
+  id: 2
diff --git a/test/functional/login_controller_test.rb b/test/functional/login_controller_test.rb
new file mode 100644 (file)
index 0000000..a21d438
--- /dev/null
@@ -0,0 +1,18 @@
+require File.dirname(__FILE__) + '/../test_helper'
+require 'login_controller'
+
+# Re-raise errors caught by the controller.
+class LoginController; def rescue_action(e) raise e end; end
+
+class LoginControllerTest < Test::Unit::TestCase
+  def setup
+    @controller = LoginController.new
+    @request    = ActionController::TestRequest.new
+    @response   = ActionController::TestResponse.new
+  end
+
+  # Replace this with your real tests.
+  def test_truth
+    assert true
+  end
+end
diff --git a/test/unit/user_test.rb b/test/unit/user_test.rb
new file mode 100644 (file)
index 0000000..5468f7a
--- /dev/null
@@ -0,0 +1,10 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+class UserTest < Test::Unit::TestCase
+  fixtures :users
+
+  # Replace this with your real tests.
+  def test_truth
+    assert true
+  end
+end