Browse Source

Init version pf password reset.

Martin Edenhofer 13 years ago
parent
commit
a2e5adc4c7

+ 67 - 0
app/assets/javascripts/app/controllers/reset_password.js.coffee

@@ -0,0 +1,67 @@
+$ = jQuery.sub()
+
+class Index extends App.Controller
+  className: 'container'
+  
+  events:
+    'submit form': 'submit',
+    'click .submit': 'submit',
+    'click .cancel': 'cancel',
+
+  constructor: ->
+    super
+    
+    # set title
+    @title 'Reset Password'
+    @navupdate '#reset_password'
+
+    @render()
+    
+  render: ->
+    
+   configure_attributes = [
+      { name: 'username', display: 'Enter your username or email address:', tag: 'input', type: 'text', limit: 100, null: false, class: 'input span4',  },
+    ]
+
+    @html App.view('reset_password')(
+      form: @formGen( model: { configure_attributes: configure_attributes } ),
+    )
+
+  cancel: ->
+    @navigate 'login'
+
+  submit: (e) ->
+    @log 'submit'
+    e.preventDefault()
+    params = @formParam(e.target)
+
+    # get data
+    ajax = new App.Ajax
+    ajax.ajax(
+      type: 'POST',
+      url:  '/users/password_reset',
+      data: JSON.stringify(params),
+      processData: true,
+      success: @success
+    )
+  
+  success: (data, status, xhr) =>
+
+    @html App.view('reset_password_sent')()
+
+  error: (xhr, statusText, error) =>
+    
+    # add notify
+    Spine.trigger 'notify:removeall'
+    Spine.trigger 'notify', {
+      type: 'warning',
+      msg: 'Wrong Username and Password combination.', 
+    }
+    
+    # rerender login page
+    @render(
+      msg: 'Wrong Username and Password combination.', 
+      username: @username
+    )
+
+Config.Routes['reset_password'] = Index

+ 1 - 1
app/assets/javascripts/app/views/login.jst.eco

@@ -14,7 +14,7 @@
             <div>
               <span class="small"><input name="remember_me" value="1" type="checkbox"/> Remember me</span>
               <span class="small">&middot;</span>
-              <a href="#resend_password" class="small">Forgot password?</a>
+              <a href="#reset_password" class="small">Forgot password?</a>
             </div>
           </form>
         </div>

+ 13 - 0
app/assets/javascripts/app/views/reset_password.jst.eco

@@ -0,0 +1,13 @@
+<div class="hero-unit">
+  <h2>Forgot your password?<small></small></h2>
+
+  <div class="container">
+    <form>
+      <p>
+        <%- @form %>
+      </p>
+      <a href="#/" class="btn cancel">Cancel</a>
+      <button class="btn btn-primary submit">Submit</button>
+    </form>
+  </div>
+</div>

+ 9 - 0
app/assets/javascripts/app/views/reset_password_sent.jst.eco

@@ -0,0 +1,9 @@
+<div class="hero-unit">
+  <h2>We've sent password reset instructions to your email address.<small></small></h2>
+
+  <div class="container">
+    <p>
+      If you don't receive instructions within a minute or two, check your email's spam and junk filters, or try <a href="#reset_password">resending your request</a>.
+    </p>
+  </div>
+</div>

+ 23 - 1
app/controllers/users_controller.rb

@@ -1,5 +1,5 @@
 class UsersController < ApplicationController
-  before_filter :authentication_check, :except => [:create]
+  before_filter :authentication_check, :except => [:create, :password_reset_send, :password_reset_verify]
 
   # GET /users
   def index
@@ -98,4 +98,26 @@ class UsersController < ApplicationController
 
     head :ok
   end
+
+  # POST /users/reset_password
+  def password_reset_send
+    puts params.inspect
+    success = User.password_reset_send( params[:username] )
+    if success
+      render :json => { :message => 'ok' }, :status => :ok
+    else
+      render :json => { :message => 'failed' }, :status => :unprocessable_entity
+    end
+  end
+
+  # get /users/verify_password/:hash
+  def password_reset_verify
+    success = User.password_reset_verify( params[:hash] )
+    if success
+      render :json => { :message => 'ok' }, :status => :ok
+    else
+      render :json => { :message => 'failed' }, :status => :unprocessable_entity
+    end
+  end
+
 end

+ 31 - 0
app/models/token.rb

@@ -0,0 +1,31 @@
+class Token < ActiveRecord::Base
+  before_create           :generate_token
+
+  belongs_to              :user
+
+  def self.check( data )
+
+    # fetch token
+    token = Token.where( :action => data[:action], :name => data[:name] ).first
+    return if !token
+    
+    # check if token is still valid
+    if token.created_at < 1.day.ago
+      
+      # delete token
+      token.delete
+      token.save
+      return
+    end
+    
+    # return token if valid
+    return token
+  end
+
+  private
+    def generate_token
+      begin
+        self.name = SecureRandom.hex(20)
+      end while Token.exists?( :name => self.name )
+    end
+end

+ 82 - 2
app/models/user.rb

@@ -8,6 +8,7 @@ class User < ApplicationModel
   has_and_belongs_to_many :groups,          :after_add => :cache_update, :after_remove => :cache_update
   has_and_belongs_to_many :roles,           :after_add => :cache_update, :after_remove => :cache_update
   has_and_belongs_to_many :organizations,   :after_add => :cache_update, :after_remove => :cache_update
+  has_many                :tokens,          :after_add => :cache_update, :after_remove => :cache_update
   has_many                :authorizations,  :after_add => :cache_update, :after_remove => :cache_update
   belongs_to              :organization,    :class_name => 'Organization'
 
@@ -47,7 +48,7 @@ class User < ApplicationModel
       url = hash['info']['urls']['Website'] || hash['info']['urls']['Twitter'] || ''
     end
     roles = Role.where( :name => 'Customer' )
-    create(
+    self.create(
       :login         => hash['info']['nickname'] || hash['uid'],
       :firstname     => hash['info']['name'],
       :email         => hash['info']['email'],
@@ -60,7 +61,86 @@ class User < ApplicationModel
     )
 
   end
-  
+
+  def self.password_reset_send(username)
+puts '2'+username.inspect
+    return if !username || username == ''
+
+    # try to find user based on login
+    user = User.where( :login => username, :active => true ).first
+    
+    # try second lookup with email
+    if !user
+      user = User.where( :email => username, :active => true ).first
+    end
+
+    # check if email address exists
+    return if !user.email
+
+    # generate token
+    token = Token.create( :action => 'PasswordReset', :user_id => user.id )
+
+    # send mail
+    data = {}
+    data[:subject] = 'Reset your #{config.product_name} password'
+    data[:body]    = 'Forgot your password?
+
+We received a request to reset the password for your #{config.product_name} account (#{user.login}).
+
+If you want to reset your password, click on the link below (or copy and paste the URL into your browser):
+
+#{config.http_type}://#{config.fqdn}/password_reset_verify/#{token.name}
+
+This link takes you to a page where you can change your password.
+
+If you don\'t want to reset your password, please ignore this message. Your password will not be reset. 
+
+Your #{config.product_name} Team
+'
+
+    # prepare subject & body
+    [:subject, :body].each { |key|
+      data[key.to_sym] = NotificationFactory.build(
+        :string  => data[key.to_sym],
+        :objects => {
+          :token => token,
+          :user  => user,
+        }
+      )
+    }
+
+    # send notification
+    NotificationFactory.send(
+      :recipient => user,
+      :subject   => data[:subject],
+      :body      => data[:body]
+    )
+    return true
+  end
+
+  def self.password_reset_check(token)
+
+    # check token
+    token = Token.check( :action => 'PasswordReset', :name => token )
+    return if !token
+    return true
+  end
+
+  def self.password_reset_via_token(token,password)
+    
+    # check token
+    token = Token.check( :action => 'PasswordReset', :name => token )
+    return if !token
+    
+    # reset password
+    token.user.update_attributes( :password => password )
+    
+    # delete token
+    token.delete
+    token.save
+    return true
+  end
+
   def self.find_fulldata(user_id)
 
     return cache_get(user_id) if cache_get(user_id)

+ 10 - 8
config/routes.rb

@@ -12,11 +12,13 @@ Zammad::Application.routes.draw do
   match '/auth/:provider/callback', :to => 'sessions#create_omniauth'
 
   # base objects
-  resources :settings,          :only => [:create, :show, :index, :update]
-  resources :users,             :only => [:create, :show, :index, :update]
-  resources :groups,            :only => [:create, :show, :index, :update]
-  resources :roles,             :only => [:create, :show, :index, :update]
-  resources :organizations,     :only => [:create, :show, :index, :update]
+  resources :settings,            :only => [:create, :show, :index, :update]
+  resources :users,               :only => [:create, :show, :index, :update]
+  match '/users/password_reset',        :to => 'users#password_reset_send'
+  match '/users/password_reset_verify', :to => 'users#password_reset_verify'
+  resources :groups,              :only => [:create, :show, :index, :update]
+  resources :roles,               :only => [:create, :show, :index, :update]
+  resources :organizations,       :only => [:create, :show, :index, :update]
 
   # overviews
   resources :overviews
@@ -49,9 +51,9 @@ Zammad::Application.routes.draw do
 
   # sessions
   resources :sessions,            :only => [:create, :destroy, :show]
-  match '/signin',   :to => 'sessions#create'
-  match '/signshow', :to => 'sessions#show'
-  match '/signout',  :to => 'sessions#destroy'
+  match '/signin',                :to => 'sessions#create'
+  match '/signshow',              :to => 'sessions#show'
+  match '/signout',               :to => 'sessions#destroy'
 
   # The priority is based upon order of creation:
   # first created -> highest priority.

+ 17 - 0
db/migrate/20120101000080_create_token.rb

@@ -0,0 +1,17 @@
+class CreateToken < ActiveRecord::Migration
+  def up
+    create_table :tokens do |t|
+      t.references :user,                 :null => false
+      t.string :name,     :limit => 100,  :null => false
+      t.string :action,   :limit => 40,   :null => false
+      t.timestamps
+    end
+    add_index :tokens, :user_id
+    add_index :tokens, [:name, :action], :unique => true
+    add_index :tokens, :created_at
+  end
+
+  def down
+    drop_table :tokens
+  end
+end