diff options
author | Azul <azul@leap.se> | 2012-12-07 08:28:23 +0100 |
---|---|---|
committer | Azul <azul@leap.se> | 2012-12-07 08:28:23 +0100 |
commit | 1ec55c4f562a4fdd57c50077ff286ef08e9978a1 (patch) | |
tree | 16203d2ca4f32e24d38fef6062aa9534cecb3bfe /users/app | |
parent | effa6b0f84cfe954cc9dd73f592663b743b0d857 (diff) | |
parent | a3dce077881c7e97090e5e560b1fb004952d5b23 (diff) |
Merge branch 'develop'
Diffstat (limited to 'users/app')
m--------- | users/app/assets/javascripts/srp | 0 | ||||
-rw-r--r-- | users/app/assets/javascripts/users.js.coffee | 62 | ||||
-rw-r--r-- | users/app/controllers/controller_extension/authentication.rb | 38 | ||||
-rw-r--r-- | users/app/controllers/sessions_controller.rb | 27 | ||||
-rw-r--r-- | users/app/controllers/users_controller.rb | 42 | ||||
-rw-r--r-- | users/app/models/session.rb | 34 | ||||
-rw-r--r-- | users/app/models/user.rb | 41 | ||||
-rw-r--r-- | users/app/views/sessions/_admin_nav.html.haml | 6 | ||||
-rw-r--r-- | users/app/views/sessions/_nav.html.haml | 13 | ||||
-rw-r--r-- | users/app/views/sessions/new.html.haml | 15 | ||||
-rw-r--r-- | users/app/views/sessions/new.json.erb | 3 | ||||
-rw-r--r-- | users/app/views/users/_cancel_account.html.haml | 6 | ||||
-rw-r--r-- | users/app/views/users/_form.html.haml | 15 | ||||
-rw-r--r-- | users/app/views/users/_user.html.haml | 10 | ||||
-rw-r--r-- | users/app/views/users/edit.html.haml | 5 | ||||
-rw-r--r-- | users/app/views/users/index.html.haml | 17 | ||||
-rw-r--r-- | users/app/views/users/new.html.haml | 13 |
17 files changed, 257 insertions, 90 deletions
diff --git a/users/app/assets/javascripts/srp b/users/app/assets/javascripts/srp -Subproject d6a78049f3356d9d645143362eca74434410bf6 +Subproject fff770a866b44abce6fe0fc5d5ffde034225436 diff --git a/users/app/assets/javascripts/users.js.coffee b/users/app/assets/javascripts/users.js.coffee index 160a7f0..76a6d79 100644 --- a/users/app/assets/javascripts/users.js.coffee +++ b/users/app/assets/javascripts/users.js.coffee @@ -1,41 +1,35 @@ -# 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://jashkenas.github.com/coffee-script/ -# +preventDefault = (event) -> + event.preventDefault() -validate_password = (event) -> +srp.session = new srp.Session() +srp.signedUp = -> + srp.login - password = $('#srp_password').val() - confirmation = $('#srp_password_confirmation').val() - login = $('#srp_username').val() +srp.loggedIn = -> + window.location = '/' - if password != confirmation - alert "Password and Confirmation do not match!" - $('#srp_password').focus() - return false - if password == login - alert "Password and Login may not match!" - $('#srp_password').focus() - return false - if password.length < 8 - alert "Password needs to be at least 8 characters long!" - $('#srp_password').focus() - return false - - return true - +#// TODO: not sure this is what we want. +srp.updated = -> + window.location = '/' -insert_verifier = (event) -> - # TODO: verify password confimation - srp = new SRP - salt = srp.session.getSalt() - $('#srp_salt').val(salt) - $('#srp_password_verifier').val(srp.session.getV().toString(16)) - # clear the password so we do not submit it - $('#srp_password').val('cleared out - use verifier instead') - $('#srp_password_confirmation').val('using srp - store verifier') +srp.error = (message) -> + if $.isPlainObject(message) && message.errors + for field, error of message.errors + element = $('form input[name$="['+field+']"]') + next unless element + element.trigger('element:validate:fail.ClientSideValidations', error).data('valid', false) + else + alert(message) + +pollUsers = (query, process) -> + $.get( "/users.json", query: query).done(process) $(document).ready -> - $('#new_user').submit validate_password - $('#new_user').submit insert_verifier + $('#new_user').submit preventDefault + $('#new_user').submit srp.signup + $('#new_session').submit preventDefault + $('#new_session').submit srp.login + $('.user.form.edit').submit srp.update + $('.user.form.edit').submit preventDefault + $('.user.typeahead').typeahead({source: pollUsers}); diff --git a/users/app/controllers/controller_extension/authentication.rb b/users/app/controllers/controller_extension/authentication.rb new file mode 100644 index 0000000..6ac7a5b --- /dev/null +++ b/users/app/controllers/controller_extension/authentication.rb @@ -0,0 +1,38 @@ +module ControllerExtension::Authentication + extend ActiveSupport::Concern + + private + + included do + helper_method :current_user, :logged_in?, :admin? + end + + def authentication_errors + return unless errors = warden.winning_strategy.try(:message) + errors.inject({}) do |translated,err| + translated[err.first] = I18n.t(err.last) + translated + end + end + + def logged_in? + !!current_user + end + + def authorize + access_denied unless logged_in? + end + + def access_denied + redirect_to login_url, :alert => "Not authorized" + end + + def admin? + current_user && current_user.is_admin? + end + + def authorize_admin + access_denied unless admin? + end + +end diff --git a/users/app/controllers/sessions_controller.rb b/users/app/controllers/sessions_controller.rb index 284c0e2..bc910b5 100644 --- a/users/app/controllers/sessions_controller.rb +++ b/users/app/controllers/sessions_controller.rb @@ -3,33 +3,24 @@ class SessionsController < ApplicationController skip_before_filter :verify_authenticity_token def new + @session = Session.new + if authentication_errors + @errors = authentication_errors + render :status => 422 + end end def create - @user = User.find_by_param(params[:login]) - session[:handshake] = @user.initialize_auth(params['A'].hex) - User.current = @user #? - render :json => session[:handshake] - rescue RECORD_NOT_FOUND - render :json => {:errors => {:login => ["unknown user"]}} + authenticate! end def update - # TODO: validate the id belongs to the session - @user = User.find_by_param(params[:id]) - @srp_session = session.delete(:handshake) - @srp_session.authenticate!(params[:client_auth].hex) - session[:user_id] = @user.id - User.current = @user #? - render :json => @srp_session - rescue WRONG_PASSWORD - session[:handshake] = nil - render :json => {:errors => {"password" => ["wrong password"]}} + authenticate! + render :json => session.delete(:handshake) end def destroy - session[:user_id] = nil - User.current = nil #? + logout redirect_to root_path end end diff --git a/users/app/controllers/users_controller.rb b/users/app/controllers/users_controller.rb index 82d2eac..cffc8c6 100644 --- a/users/app/controllers/users_controller.rb +++ b/users/app/controllers/users_controller.rb @@ -1,18 +1,48 @@ class UsersController < ApplicationController - skip_before_filter :verify_authenticity_token + skip_before_filter :verify_authenticity_token, :only => [:create] + + before_filter :fetch_user, :only => [:edit, :update, :destroy] + before_filter :authorize_admin, :only => [:index] respond_to :json, :html + def index + if params[:query] + @users = User.by_login.startkey(params[:query]).endkey(params[:query].succ) + else + @users = User.by_created_at.descending + end + @users = @users.limit(10) + respond_with @users.map(&:login).sort + end + def new @user = User.new end def create - @user = User.create!(params[:user]) - respond_with(@user, :location => root_url, :notice => "Signed up!") - rescue VALIDATION_FAILED => e - @user = e.document - respond_with(@user, :location => new_user_path) + @user = User.create(params[:user]) + respond_with @user + end + + def edit + end + + def update + @user.update_attributes(params[:user]) + respond_with @user + end + + def destroy + @user.destroy + redirect_to admin? ? users_path : login_path + end + + protected + + def fetch_user + @user = User.find_by_param(params[:id]) + access_denied unless admin? or (@user == current_user) end end diff --git a/users/app/models/session.rb b/users/app/models/session.rb new file mode 100644 index 0000000..a9fdb1b --- /dev/null +++ b/users/app/models/session.rb @@ -0,0 +1,34 @@ +class Session < SRP::Session + include ActiveModel::Validations + + attr_accessor :login + + validates :login, + :presence => true, + :format => { :with => /\A[A-Za-z\d_]+\z/, + :message => "Only letters, digits and _ allowed" } + + def initialize(user = nil, aa = nil) + super(user, aa) if user + end + + def persisted? + false + end + + def new_record? + true + end + + def to_model + self + end + + def to_key + [object_id] + end + + def to_param + nil + end +end diff --git a/users/app/models/user.rb b/users/app/models/user.rb index 1afb9db..325c981 100644 --- a/users/app/models/user.rb +++ b/users/app/models/user.rb @@ -9,42 +9,46 @@ class User < CouchRest::Model::Base :presence => true validates :login, - :uniqueness => true + :uniqueness => true, + :if => :serverside? validates :login, :format => { :with => /\A[A-Za-z\d_]+\z/, :message => "Only letters, digits and _ allowed" } validates :password_salt, :password_verifier, - :format => { :with => /\A[\dA-Fa-f]+\z/, - :message => "Only hex numbers allowed" } + :format => { :with => /\A[\dA-Fa-f]+\z/, :message => "Only hex numbers allowed" } + + validates :password, :presence => true, + :confirmation => true, + :format => { :with => /.{8}.*/, :message => "needs to be at least 8 characters long" } timestamps! design do view :by_login + view :by_created_at end class << self - def find_by_param(login) - return find_by_login(login) || raise(RECORD_NOT_FOUND) - end + alias_method :find_by_param, :find # valid set of attributes for testing def valid_attributes_hash { :login => "me", - :password_verifier => "1234ABC", + :password_verifier => "1234ABCD", :password_salt => "4321AB" } end end - def to_param - self.login - end + alias_method :to_param, :id def to_json(options={}) - super(options.merge(:only => ['login', 'password_salt'])) + { + :login => login, + :ok => valid? + }.to_json(options) end def initialize_auth(aa) @@ -63,11 +67,18 @@ class User < CouchRest::Model::Base login end - def self.current - Thread.current[:user] + # Since we are storing admins by login, we cannot allow admins to change their login. + def is_admin? + APP_CONFIG['admins'].include? self.login end - def self.current=(user) - Thread.current[:user] = user + + protected + def password + password_verifier end + # used as a condition for validations that are server side only + def serverside? + true + end end diff --git a/users/app/views/sessions/_admin_nav.html.haml b/users/app/views/sessions/_admin_nav.html.haml new file mode 100644 index 0000000..14dfbdc --- /dev/null +++ b/users/app/views/sessions/_admin_nav.html.haml @@ -0,0 +1,6 @@ +%a#admin-menu{"data-toggle" => "dropdown", :role => :button} + Admin +%ul.dropdown-menu{:role => "menu", "aria-labelledby" => "admin-menu"} + %li + = link_to Ticket.model_name.human(:count => ""), tickets_path, {:tabindex => -1} + = link_to User.model_name.human(:count => ""), users_path, {:tabindex => -1} diff --git a/users/app/views/sessions/_nav.html.haml b/users/app/views/sessions/_nav.html.haml new file mode 100644 index 0000000..5306d0e --- /dev/null +++ b/users/app/views/sessions/_nav.html.haml @@ -0,0 +1,13 @@ +- if logged_in? + - if admin? + %li.dropdown + = render 'sessions/admin_nav' + %li + = link_to current_user.login, edit_user_path(current_user) + %li + = link_to t(:logout), logout_path +- else + %li + = link_to t(:login), login_path + %li + = link_to t(:signup), signup_path diff --git a/users/app/views/sessions/new.html.haml b/users/app/views/sessions/new.html.haml index 39ee7bf..a04f584 100644 --- a/users/app/views/sessions/new.html.haml +++ b/users/app/views/sessions/new.html.haml @@ -1,7 +1,8 @@ -%h2=t :login -= simple_form_for :session, :url => sessions_path, :html => { :id => :new_session } do |f| - %legend=t :login_message - = f.input :login, :input_html => { :id => :srp_username } - = f.input :password, :required => true, :input_html => { :id => :srp_password } - = f.button :submit, :value => t(:login), :class => 'btn-primary' - = link_to t(:cancel), root_url, :class => :btn +.span8.offset2 + %h2=t :login + = simple_form_for @session, :validate => true, :html => { :id => :new_session, :class => 'form-horizontal' } do |f| + %legend=t :login_message + = f.input :login, :input_html => { :id => :srp_username } + = f.input :password, :required => true, :input_html => { :id => :srp_password } + = f.button :submit, :value => t(:login), :class => 'btn-primary' + = link_to t(:cancel), root_url, :class => :btn diff --git a/users/app/views/sessions/new.json.erb b/users/app/views/sessions/new.json.erb new file mode 100644 index 0000000..36154b8 --- /dev/null +++ b/users/app/views/sessions/new.json.erb @@ -0,0 +1,3 @@ +{ +"errors": <%= raw @errors.to_json %> +} diff --git a/users/app/views/users/_cancel_account.html.haml b/users/app/views/users/_cancel_account.html.haml new file mode 100644 index 0000000..41580b0 --- /dev/null +++ b/users/app/views/users/_cancel_account.html.haml @@ -0,0 +1,6 @@ +%legend + =t :cancel_account + %small You will not be able to login anymore. += link_to user_path(@user), :method => :delete, :class => "btn btn-danger" do + %i.icon-remove.icon-white + Remove my Account diff --git a/users/app/views/users/_form.html.haml b/users/app/views/users/_form.html.haml new file mode 100644 index 0000000..39e26a6 --- /dev/null +++ b/users/app/views/users/_form.html.haml @@ -0,0 +1,15 @@ +- only = local_assigns[:only] +- html = {:class => 'form-horizontal user form ' + (@user.new_record? ? 'new' : 'edit')} += simple_form_for @user, :validate => true, :format => :json, :html => html do |f| + %legend + = t(only || :signup_message) + - if !only || only == :change_login + = f.input :login, :input_html => { :id => :srp_username } + - if !only || only == :change_password + = f.input :password, :required => true, :validate => true, :input_html => { :id => :srp_password } + = f.input :password_confirmation, :required => true, :input_html => { :id => :srp_password_confirmation } + .pull-right + = f.button :submit, :class => 'btn-primary' + - unless only + = link_to t(:cancel), root_url, :class => :btn + .clearfix diff --git a/users/app/views/users/_user.html.haml b/users/app/views/users/_user.html.haml new file mode 100644 index 0000000..7db0041 --- /dev/null +++ b/users/app/views/users/_user.html.haml @@ -0,0 +1,10 @@ +%tr + %td= user.login + %td= time_ago_in_words(user.created_at) + " ago" + %td + = link_to edit_user_path(user), :class => "btn btn-mini btn-primary" do + %i.icon-edit.icon-white + Edit + = link_to user_path(user), :method => :delete, :class => "btn btn-danger btn-mini" do + %i.icon-remove.icon-white + Remove diff --git a/users/app/views/users/edit.html.haml b/users/app/views/users/edit.html.haml new file mode 100644 index 0000000..25da71a --- /dev/null +++ b/users/app/views/users/edit.html.haml @@ -0,0 +1,5 @@ +.span8.offset2 + %h2=t :settings + = render :partial => 'form', :locals => {:only => :change_login} + = render :partial => 'form', :locals => {:only => :change_password} + = render 'cancel_account' if @user == current_user diff --git a/users/app/views/users/index.html.haml b/users/app/views/users/index.html.haml new file mode 100644 index 0000000..9e6a179 --- /dev/null +++ b/users/app/views/users/index.html.haml @@ -0,0 +1,17 @@ +.page-header + %h1= User.model_name.human(:count =>User.count) +.row + .span8 + %h2= params[:query] ? "Users starting with '#{params[:query]}'" : "Last users who signed up" + %table.table.table-hover + %tr + %th Login + %th Created + %th Action + = render @users.all + .span4 + %h4 Find user + = form_tag users_path, :method => :get, :class => "form-search" do + .input-append + = text_field_tag :query, "", :class => "user typeahead span2 search-query", :autocomplete => :off + %button.btn{:type => :submit} Search diff --git a/users/app/views/users/new.html.haml b/users/app/views/users/new.html.haml index f6ece3a..c1c4208 100644 --- a/users/app/views/users/new.html.haml +++ b/users/app/views/users/new.html.haml @@ -1,10 +1,3 @@ -%h2=t :signup -= simple_form_for @user do |f| - %legend=t :signup_message - = f.input :login, :input_html => { :id => :srp_username } - = f.input :password, :required => true, :input_html => { :id => :srp_password } - = f.input :password_confirmation, :required => true, :input_html => { :id => :srp_password_confirmation } - = f.input :password_verifier, :as => :hidden, :input_html => { :id => :srp_password_verifier } - = f.input :password_salt, :as => :hidden, :input_html => { :id => :srp_salt } - = f.button :submit, :value => t(:signup), :class => 'btn-primary' - = link_to t(:cancel), root_url, :class => :btn +.span8.offset2 + %h2=t :signup + = render 'form' |