diff --git a/app/assets/javascripts/app/controllers/reset_password.js.coffee b/app/assets/javascripts/app/controllers/reset_password.js.coffee
new file mode 100644
index 000000000..9523a9dbd
--- /dev/null
+++ b/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
diff --git a/app/assets/javascripts/app/views/login.jst.eco b/app/assets/javascripts/app/views/login.jst.eco
index 25aa7364f..58cf18727 100644
--- a/app/assets/javascripts/app/views/login.jst.eco
+++ b/app/assets/javascripts/app/views/login.jst.eco
@@ -14,7 +14,7 @@
diff --git a/app/assets/javascripts/app/views/reset_password.jst.eco b/app/assets/javascripts/app/views/reset_password.jst.eco
new file mode 100644
index 000000000..dd08b711c
--- /dev/null
+++ b/app/assets/javascripts/app/views/reset_password.jst.eco
@@ -0,0 +1,13 @@
+
+
Forgot your password?
+
+
+
\ No newline at end of file
diff --git a/app/assets/javascripts/app/views/reset_password_sent.jst.eco b/app/assets/javascripts/app/views/reset_password_sent.jst.eco
new file mode 100644
index 000000000..caeacf67c
--- /dev/null
+++ b/app/assets/javascripts/app/views/reset_password_sent.jst.eco
@@ -0,0 +1,9 @@
+
+
We've sent password reset instructions to your email address.
+
+
+
+ If you don't receive instructions within a minute or two, check your email's spam and junk filters, or try resending your request.
+
+
+
\ No newline at end of file
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index c1b9234e5..712934abc 100644
--- a/app/controllers/users_controller.rb
+++ b/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
diff --git a/app/models/token.rb b/app/models/token.rb
new file mode 100644
index 000000000..d7145cf5f
--- /dev/null
+++ b/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
\ No newline at end of file
diff --git a/app/models/user.rb b/app/models/user.rb
index a55041967..3fb2545a4 100644
--- a/app/models/user.rb
+++ b/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)
diff --git a/config/routes.rb b/config/routes.rb
index 5499d5695..38039cd83 100644
--- a/config/routes.rb
+++ b/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.
diff --git a/db/migrate/20120101000080_create_token.rb b/db/migrate/20120101000080_create_token.rb
new file mode 100644
index 000000000..0dcc2c5f3
--- /dev/null
+++ b/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