Fixed login, new_password and invitation views.

This commit is contained in:
benni 2012-10-08 11:51:56 +02:00
parent f20ae890dd
commit 1708df3f6c
18 changed files with 200 additions and 122 deletions

View File

@ -10,8 +10,10 @@ class InvitesController < ApplicationController
def create
@invite = Invite.new(params[:invite])
if @invite.save
Mailer.delay.invite(FoodsoftConfig.scope, @invite.id)
redirect_to back_or_default_path, notice: "Benutzerin wurde erfolgreich eingeladen."
else
logger.debug "[debug] errors: #{@invite.errors.messages}"
render action: :new
end
end

View File

@ -1,7 +1,7 @@
# encoding: utf-8
class LoginController < ApplicationController
skip_before_filter :authenticate # no authentication since this is the login page
before_filter :validate_token, :only => [:password, :update_password]
before_filter :validate_token, :only => [:new_password, :update_password]
# Display the form to enter an email address requesting a token to set a new password.
def forgot_password
@ -14,7 +14,7 @@ class LoginController < ApplicationController
user.reset_password_token = user.new_random_password(16)
user.reset_password_expires = Time.now.advance(:days => 2)
if user.save
email = Mailer.reset_password(user).deliver
Mailer.delay.reset_password(FoodsoftConfig.scope, user.id)
logger.debug("Sent password reset email to #{user.email}.")
end
end
@ -23,7 +23,7 @@ class LoginController < ApplicationController
# Set a new password with a token from the password reminder email.
# Called with params :id => User.id and :token => User.reset_password_token to specify a new password.
def password
def new_password
end
# Sets a new password.
@ -36,13 +36,13 @@ class LoginController < ApplicationController
@user.save
redirect_to login_url, :notice => "Dein Passwort wurde aktualisiert. Du kannst Dich jetzt anmelden."
else
render :action => 'password'
render :new_password
end
end
# Invited users.
def invite
@invite = Invite.find_by_token(params[:id])
# For invited users.
def accept_invitation
@invite = Invite.find_by_token(params[:token])
if (@invite.nil? || @invite.expires_at < Time.now)
flash[:error] = "Deine Einladung ist nicht (mehr) gültig."
render :action => 'login'
@ -71,7 +71,7 @@ class LoginController < ApplicationController
def validate_token
@user = User.find_by_id_and_reset_password_token(params[:id], params[:token])
if (@user.nil? || @user.reset_password_expires < Time.now)
flash[:error] = "Ungültiger oder abgelaufener Token. Bitte versuch es erneut."
flash.now.error = "Ungültiger oder abgelaufener Token. Bitte versuch es erneut."
render :action => 'forgot_password'
end
end

View File

@ -130,4 +130,17 @@ module ApplicationHelper
:target => "_blank"
end
def bootstrap_flash
flash_messages = []
flash.each do |type, message|
type = :success if type == :notice
type = :error if type == :alert
text = content_tag(:div,
content_tag(:button, raw("&times;"), :class => "close", "data-dismiss" => "alert") +
message, :class => "alert fade in alert-#{type}")
flash_messages << text if message
end
flash_messages.join("\n").html_safe
end
end

View File

@ -19,20 +19,22 @@ class Mailer < ActionMailer::Base
# Sends an email with instructions on how to reset the password.
# Assumes user.setResetPasswordToken has been successfully called already.
def reset_password(user)
@user = user
@link = url_for(:controller => "login", :action => "password", :id => user.id, :token => user.reset_password_token)
def reset_password(foodcoop, user_id)
set_foodcoop_scope(foodcoop)
@user = User.find(user_id)
@link = new_password_url(id: @user.id, token: @user.reset_password_token)
mail :to => user.email,
:subject => "[#{FoodsoftConfig[:name]}] Neues Passwort für/ New password for #{user.nick}"
mail :to => @user.email,
:subject => "[#{FoodsoftConfig[:name]}] Neues Passwort für/ New password for #{@user.nick}"
end
# Sends an invite email.
def invite(invite)
@invite = invite
@link = url_for(:controller => "login", :action => "invite", :id => invite.token)
def invite(foodcoop, invite_id)
set_foodcoop_scope(foodcoop)
@invite = Invite.find(invite_id)
@link = accept_invitation_url(token: @invite.token)
mail :to => invite.email,
mail :to => @invite.email,
:subject => "Einladung in die Foodcoop #{FoodsoftConfig[:name]} - Invitation to the Foodcoop"
end
@ -81,5 +83,11 @@ class Mailer < ActionMailer::Base
mail :to => user.email,
:subject => "[#{FoodsoftConfig[:name]}] \"#{task.name}\" braucht noch Leute!"
end
private
def set_foodcoop_scope(foodcoop)
ActionMailer::Base.default_url_options[:foodcoop] = foodcoop
end
end

View File

@ -12,20 +12,16 @@ class Invite < ActiveRecord::Base
validates_presence_of :expires_at
validate :email_not_already_registered, :on => :create
before_validation :set_token_and_expires_at
protected
# Before validation, set token and expires_at.
def before_validation
def set_token_and_expires_at
self.token = Digest::SHA1.hexdigest(Time.now.to_s + rand(100).to_s)
self.expires_at = Time.now.advance(:days => 7)
end
# Sends an email to the invited user.
def after_create
Mailer.invite(self).deliver
end
private
# Custom validation: check that email does not already belong to a registered user.

View File

@ -0,0 +1,29 @@
!!! 5
%html(lang="en")
%head
%meta(charset="utf-8")
%meta(http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1")
%meta(name="viewport" content="width=device-width, initial-scale=1.0")
%title= content_for?(:title) ? yield(:title) : "Foodsoft"
= csrf_meta_tags
/ Le HTML5 shim, for IE6-8 support of HTML elements
/[if lt IE 9]
= javascript_include_tag "http://html5shim.googlecode.com/svn/trunk/html5.js"
= stylesheet_link_tag "application", :media => "all"
%link(href="images/apple-touch-icon-144x144.png" rel="apple-touch-icon-precomposed" sizes="144x144")
%link(href="images/apple-touch-icon-114x114.png" rel="apple-touch-icon-precomposed" sizes="114x114")
%link(href="images/apple-touch-icon-72x72.png" rel="apple-touch-icon-precomposed" sizes="72x72")
%link(href="images/apple-touch-icon.png" rel="apple-touch-icon-precomposed")
//%link(href="images/favicon.ico" rel="shortcut icon")
= yield(:head)
%body
= yield
/
Javascripts
\==================================================
/ Placed at the end of the document so the pages load faster
= javascript_include_tag "application"
= yield(:javascript)

View File

@ -1,58 +1,35 @@
!!! 5
%html(lang="en")
%head
%meta(charset="utf-8")
%meta(http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1")
%meta(name="viewport" content="width=device-width, initial-scale=1.0")
%title= content_for?(:title) ? yield(:title) : "Foodsoft"
= csrf_meta_tags
/ Le HTML5 shim, for IE6-8 support of HTML elements
/[if lt IE 9]
= javascript_include_tag "http://html5shim.googlecode.com/svn/trunk/html5.js"
= stylesheet_link_tag "application", :media => "all"
%link(href="images/apple-touch-icon-144x144.png" rel="apple-touch-icon-precomposed" sizes="144x144")
%link(href="images/apple-touch-icon-114x114.png" rel="apple-touch-icon-precomposed" sizes="114x114")
%link(href="images/apple-touch-icon-72x72.png" rel="apple-touch-icon-precomposed" sizes="72x72")
%link(href="images/apple-touch-icon.png" rel="apple-touch-icon-precomposed")
%link(href="images/favicon.ico" rel="shortcut icon")
= render layout: 'layouts/header' do
.row-fluid
.navbar.navbar-fixed-top
.navbar-inner
.container-fluid
%a.btn.btn-navbar(data-target=".nav-collapse" data-toggle="collapse")
%span.icon-bar
%span.icon-bar
%span.icon-bar
= link_to 'Foodsoft', root_path(anchor: ''), class: 'brand'
.container.nav-collapse
= render_navigation expand_all: true, renderer: :bootstrap
%body
.container-fluid
.row-fluid
.navbar.navbar-fixed-top
.navbar-inner
.container-fluid
%a.btn.btn-navbar(data-target=".nav-collapse" data-toggle="collapse")
%span.icon-bar
%span.icon-bar
%span.icon-bar
= link_to 'Foodsoft', root_path(anchor: ''), class: 'brand'
.container.nav-collapse
= render_navigation expand_all: true, renderer: :bootstrap
.container-fluid
.row-fluid
- if content_for?(:sidebar)
.span2
= yield(:sidebar)
.span10
- if show_title?
.page-header
%h1= yield(:title)
= yield
- else
- if content_for?(:sidebar)
.span2
= yield(:sidebar)
.span10
= bootstrap_flash
- if show_title?
.page-header
%h1= yield(:title)
= yield
%footer
%p
Foodsoft, open source software to manage a non-profit food coop.
- else
= bootstrap_flash
- if show_title?
.page-header
%h1= yield(:title)
= yield
/
Javascripts
\==================================================
/ Placed at the end of the document so the pages load faster
= javascript_include_tag "application"
%footer
%p
Foodsoft, open source software to manage a non-profit food coop.

View File

@ -1,19 +1,13 @@
!!!
%html
%head
%meta{"http-equiv" => "content-type", :content => "text/html;charset=UTF-8"}
%title= "FoodSoft - " + (yield(:title) or controller.controller_name)
= stylesheet_link_tag 'application'
= javascript_include_tag 'application'
= csrf_meta_tags
= yield(:head)
%body
#login
- if yield(:title)
%h1= yield(:title)
- flash.each do |name, msg|
= content_tag :div, msg, :class => "flash #{name}"
= yield
#meta
Foodcoop
= link_to_if FoodsoftConfig[:homepage], FoodsoftConfig[:name], FoodsoftConfig[:homepage]
= render layout: 'layouts/header' do
.container
.row
.span6.offset3
= bootstrap_flash
- if show_title?
.page-header
%h1= yield(:title)
= yield
%footer
%p
Foodsoft, open source software to manage a non-profit food coop.

View File

@ -1,3 +1,7 @@
- content_for :javascript do
:javascript
$('user_nick').focus();
- title "Einladung in die #{FoodsoftConfig[:name]}"
%p
Du bist eingeladen worden als Mitglied der Gruppe
@ -12,7 +16,8 @@
Gründen, weitergeben. Du kannst auch entscheiden, wieviel deiner persönlichen
Daten für alle einsehbar sein sollen. 'Alle' bedeutet hier alle Foodcoop-Mitglieder.
Die Administratoren haben aber jederzeit Zugriff auf deine Daten.
= simple_form_for @user, url: {action: 'invite'} do |form|
= simple_form_for @user, url: accept_invitation_path do |form|
= render partial: 'shared/user_form_fields', locals: {f: form}
= submit_tag "Absenden"
= javascript_tag("$('user_nick').focus()")
.form-actions
= submit_tag "Foodsoft Account erstellen", class: 'btn'

View File

@ -7,5 +7,6 @@
= simple_form_for User.new, url: {action: 'reset_password'} do |form|
= form.input :email
= form.submit "Neues Passwort anfordern"
= link_to "Abbrechen", :back
.form-actions
= form.submit "Neues Passwort anfordern", class: 'btn'
= link_to "oder abbrechen", :back

View File

@ -3,4 +3,5 @@
= simple_form_for @user, :url => {:action => 'update_password', :id => @user.id, :token => @user.reset_password_token} do |form|
= form.input :password
= form.input :password_confirmation
= form.submit
.form-actions
= form.submit 'Neues Passwort speichern', class: 'btn'

View File

@ -1,7 +1,7 @@
Hallo <%= @user.nick %>,
du (oder jemand anderes) hat auf der FoodSoft-Website ein neues Passwort angefordert.
Um ein neues Passwort einzugeben, gehe zu: <%= @link %>
Um ein neues Passwort einzugeben, gehe zu: <%= raw @link %>
Dieser Link kann nur einmal aufgerufen werden und läuft am <%= I18n.l @user.reset_password_expires %> ab.
Wenn du das Passwort nicht ändern möchtest oder diese Email nicht ausgelöst hast, brauchst du nichts zu tun. Dein bisheriges Passwort wurde nicht geändert.
@ -11,7 +11,7 @@ Grüße sendet die Foodsoft! :)
Hi <%= @user.nick %>,
you have (or someone else has) requested a new password.
In order to choose a new password follow this link: <%= @link %>
In order to choose a new password follow this link: <%= raw @link %>
This link works only once and expires on <%= I18n.l @user.reset_password_expires, locale: :en %>.
If you don't want to change your password, just ignore this message. Your password hasn't been changed yet.

View File

@ -1,4 +1,4 @@
- content_for :head do
- content_for :javascript do
:javascript
$(function() {
$('#nick').focus();
@ -7,21 +7,23 @@
- title "FoodSoft login"
%noscript
#javascript-warn.error(style="font-size:1.5em")
.alert.alert-error
Achtung, Cookies und Javascript müssen aktiviert sein!
= link_to "NoScript", "http://noscript.net/"
bitte abschalten.
#login-form.edit_form(style="width:25em")
= form_tag sessions_path do
%p
%label{:for => 'user'} Benutzerin
%br/
= form_tag sessions_path, class: 'form-horizontal' do
.control-group
%label(for='nick' class='control-label') Benutzerin
.controls
= text_field_tag 'nick'
%p
%label{:for => 'password'} Passwort
%br/
.control-group
%label(for='password' class='control-label') Passwort
.controls
= password_field_tag 'password'
= submit_tag "Anmelden"
|
= link_to "Passwort vergessen?", :controller => 'login', :action => 'forgot_password'
.control-group
.controls
= submit_tag "Anmelden", class: 'btn'
= link_to "Passwort vergessen?", :controller => 'login', :action => 'forgot_password'

View File

@ -3,11 +3,12 @@
= f.input :last_name
= f.input :email
= f.input :phone
= f.input :password, :required => @user.new_record?
= f.input :password, :required => f.object.new_record?
= f.input :password_confirmation
- for setting in User::setting_keys.keys
.input.boolean
= check_box_tag "user[setting_attributes][#{setting}]",
'1', @user.settings[setting] == '1' || @user.settings_default(setting),
:class => 'boolean'
%label.boolean{:for => "user[setting_attributes][#{setting}]"}= h User::setting_keys[setting]
.control-group
.controls
- for setting in User::setting_keys.keys
%label.checkbox{:for => "user[setting_attributes][#{setting}]"}
= check_box_tag "user[setting_attributes][#{setting}]", '1',
f.object.settings[setting] == '1' || f.object.settings_default(setting)
= User::setting_keys[setting]

View File

@ -1,2 +1,3 @@
Delayed::Worker.max_attempts = 5
Delayed::Worker.delay_jobs = !Rails.env.test?
Delayed::Worker.destroy_failed_jobs = false

View File

@ -98,7 +98,7 @@ SimpleForm.setup do |config|
config.label_class = 'control-label'
# You can define the class to use on all forms. Default is simple_form.
# config.form_class = :simple_form
config.form_class = 'form-horizontal'
# You can define which elements should obtain additional classes
# config.generate_additional_classes_for = [:wrapper, :label, :input]
@ -108,7 +108,7 @@ SimpleForm.setup do |config|
# Tell browsers whether to use default HTML5 validations (novalidate option).
# Default is enabled.
config.browser_validations = false
config.browser_validations = true
# Collection of methods to detect if a file type was given.
# config.file_methods = [ :mounted_as, :file?, :public_filename ]

View File

@ -0,0 +1,45 @@
# Use this setup block to configure all options available in SimpleForm.
SimpleForm.setup do |config|
config.wrappers :bootstrap, :tag => 'div', :class => 'control-group', :error_class => 'error' do |b|
b.use :html5
b.use :placeholder
b.use :label
b.wrapper :tag => 'div', :class => 'controls' do |ba|
ba.use :input
ba.use :error, :wrap_with => { :tag => 'span', :class => 'help-inline' }
ba.use :hint, :wrap_with => { :tag => 'p', :class => 'help-block' }
end
end
config.wrappers :prepend, :tag => 'div', :class => "control-group", :error_class => 'error' do |b|
b.use :html5
b.use :placeholder
b.use :label
b.wrapper :tag => 'div', :class => 'controls' do |input|
input.wrapper :tag => 'div', :class => 'input-prepend' do |prepend|
prepend.use :input
end
input.use :hint, :wrap_with => { :tag => 'span', :class => 'help-block' }
input.use :error, :wrap_with => { :tag => 'span', :class => 'help-inline' }
end
end
config.wrappers :append, :tag => 'div', :class => "control-group", :error_class => 'error' do |b|
b.use :html5
b.use :placeholder
b.use :label
b.wrapper :tag => 'div', :class => 'controls' do |input|
input.wrapper :tag => 'div', :class => 'input-append' do |append|
append.use :input
end
input.use :hint, :wrap_with => { :tag => 'span', :class => 'help-block' }
input.use :error, :wrap_with => { :tag => 'span', :class => 'help-inline' }
end
end
# Wrappers for forms and inputs using the Twitter Bootstrap toolkit.
# Check the Bootstrap docs (http://twitter.github.com/bootstrap)
# to learn about the different styles for forms and inputs,
# buttons and other elements.
config.default_wrapper = :bootstrap
end

View File

@ -17,6 +17,8 @@ Foodsoft::Application.routes.draw do
match '/login' => 'sessions#new', :as => 'login'
match '/logout' => 'sessions#destroy', :as => 'logout'
get '/login/new_password' => 'login#new_password', as: :new_password
match '/login/accept_invitation/:token' => 'login#accept_invitation', as: :accept_invitation
resources :sessions, :only => [:new, :create, :destroy]
########### User specific
@ -174,6 +176,7 @@ Foodsoft::Application.routes.draw do
resources :users, :only => [:index]
# TODO: This is very error prone. Better deactivate this catch all route
match ':controller(/:action(/:id))(.:format)'
end # End of /:foodcoop scope