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 @@
Remember me · - Forgot password? + Forgot password?
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?

+ +
+
+

+ <%- @form %> +

+ Cancel + +
+
+
\ 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