Add password reset

Closes #15
This commit is contained in:
ricola 2025-06-07 18:38:33 -06:00
parent 84278741a5
commit fa4f77c365
14 changed files with 222 additions and 32 deletions

1
.gitignore vendored
View file

@ -3,3 +3,4 @@ locale
*.swp
db/*.sqlite3
db/*.sqlite3-*
config/environments/development.rb

View file

@ -11,3 +11,4 @@ gem 'forwardable', '1.3.2'
gem 'bcrypt'
gem 'gettext'
gem 'chartkick'
gem 'mail'

View file

@ -26,6 +26,7 @@ GEM
chartkick (5.1.4)
concurrent-ruby (1.3.5)
connection_pool (2.5.0)
date (3.4.1)
drb (2.2.1)
erubi (1.13.1)
forwardable (1.3.2)
@ -39,10 +40,25 @@ GEM
concurrent-ruby (~> 1.0)
locale (2.1.4)
logger (1.6.6)
mail (2.8.1)
mini_mime (>= 0.1.1)
net-imap
net-pop
net-smtp
mini_mime (1.1.5)
mini_portile2 (2.8.8)
minitest (5.25.5)
mustermann (3.0.3)
ruby2_keywords (~> 0.0.1)
net-imap (0.5.8)
date
net-protocol
net-pop (0.1.2)
net-protocol
net-protocol (0.2.2)
timeout
net-smtp (0.5.1)
net-protocol
nio4r (2.7.4)
prime (0.1.3)
forwardable
@ -76,8 +92,6 @@ GEM
singleton (0.3.0)
sqlite3 (2.6.0)
mini_portile2 (~> 2.8.0)
sqlite3 (2.6.0-arm64-darwin)
sqlite3 (2.6.0-x86_64-darwin)
text (1.3.1)
tilt (2.6.0)
timeout (0.4.3)
@ -96,6 +110,7 @@ DEPENDENCIES
chartkick
forwardable (= 1.3.2)
gettext
mail
puma
rackup
rake

View file

@ -0,0 +1,5 @@
class AddResetToUsers < ActiveRecord::Migration[7.2]
def change
add_column :users, :reset, :string
end
end

View file

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.2].define(version: 2025_03_28_014902) do
ActiveRecord::Schema[7.2].define(version: 2025_06_07_233053) do
create_table "candidates", force: :cascade do |t|
t.integer "vote_id"
t.string "name"
@ -44,6 +44,7 @@ ActiveRecord::Schema[7.2].define(version: 2025_03_28_014902) do
t.string "password"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "reset"
end
create_table "votes", force: :cascade do |t|

View file

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-06-07 15:08-0600\n"
"POT-Creation-Date: 2025-06-07 18:22-0600\n"
"PO-Revision-Date: 2025-03-29 20:41-0600\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -17,34 +17,38 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
#: ../vedia.rb:62
#: ../vedia.rb:64
msgid "Awful"
msgstr "Molt malament"
#: ../vedia.rb:63
#: ../vedia.rb:65
msgid "Very bad"
msgstr "Malament"
#: ../vedia.rb:64
#: ../vedia.rb:66
msgid "Bad"
msgstr "Poc bé"
#: ../vedia.rb:65
#: ../vedia.rb:67
msgid "Mediocre"
msgstr "Mig bé"
#: ../vedia.rb:66
#: ../vedia.rb:68
msgid "Good"
msgstr "Bé"
#: ../vedia.rb:67
#: ../vedia.rb:69
msgid "Very good"
msgstr "Molt bé"
#: ../vedia.rb:114
#: ../vedia.rb:121
msgid "Incorrect email or password."
msgstr "Correu o contrasenya incorrecte."
#: ../vedia.rb:139
msgid "Reset your password"
msgstr "Reiniciar contrasenya"
#: ../views/home.erb:1 ../views/layout.erb:14
msgid "Home"
msgstr "Inici"
@ -93,12 +97,13 @@ msgstr "Desconnexió"
msgid "Login"
msgstr "Connexió"
#: ../views/login.erb:9 ../views/signup.erb:24 ../views/votes_edit.erb:51
#: ../views/login.erb:9 ../views/reset.erb:5 ../views/reset_change.erb:13
#: ../views/signup.erb:24 ../views/votes_edit.erb:51
#: ../views/votes_show_closed.erb:85 ../views/votes_show_open.erb:58
msgid "Email"
msgstr "Correu"
#: ../views/login.erb:13 ../views/signup.erb:28
#: ../views/login.erb:13 ../views/reset_change.erb:17 ../views/signup.erb:28
msgid "Password"
msgstr "Contrasenya"
@ -106,10 +111,40 @@ msgstr "Contrasenya"
msgid "Create account"
msgstr "Crear un compte"
#: ../views/signup.erb:6
#: ../views/login.erb:19 ../views/reset.erb:1 ../views/reset.erb:8
#: ../views/reset_change.erb:1 ../views/reset_change.erb:20
#: ../views/reset_invalid.erb:1 ../views/reset_sent.erb:1
msgid "Reset password"
msgstr "Reiniciar contrasenya"
#: ../views/reset_change.erb:6 ../views/signup.erb:6
msgid "Specify a password."
msgstr "Entra una contrasenya."
#: ../views/reset_email.erb:1
msgid "Visit the following link to reset your password:"
msgstr "Visita aquest enllaç per reiniciar la teva contrasenya:"
#: ../views/reset_invalid.erb:3
msgid "This password reset link has expired or is invalid."
msgstr "Aquest enllaç per reiniciar una contrasenya ha expirat o és invàlid."
#: ../views/reset_invalid.erb:5
msgid "Try resetting your password again."
msgstr "Intenta reiniciar la teva contrasenya una altre vegada."
#: ../views/reset_sent.erb:3
msgid ""
"If an account exists for %{email}, you will get an email with a link\n"
"to reset your password."
msgstr ""
"Si existeix un compte per %{email}, rebràs un email amb un enllaç\n"
"per reiniciar la teva contrasenya."
#: ../views/reset_sent.erb:6
msgid "If you don't receive the email, please check your spam folder."
msgstr "Si no reps l'email, revisa el teu correu brossa."
#: ../views/signup.erb:14
msgid "Email is not a valid email address."
msgstr "El correu no és una direcció de correu vàlida."
@ -238,7 +273,3 @@ msgstr "Tornar a l'esborrany de votació"
#: ../views/votes_show_open.erb:51
msgid "Close votes and show results"
msgstr "Tancar la votació i veure els resultats"
#: ../views/layout.erb:5
#~ msgid "Vote"
#~ msgstr "Votació"

View file

@ -8,8 +8,8 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-06-07 15:08-0600\n"
"PO-Revision-Date: 2025-06-07 15:08-0600\n"
"POT-Creation-Date: 2025-06-07 18:22-0600\n"
"PO-Revision-Date: 2025-06-07 18:22-0600\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
@ -18,34 +18,38 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
#: ../vedia.rb:62
#: ../vedia.rb:64
msgid "Awful"
msgstr ""
#: ../vedia.rb:63
#: ../vedia.rb:65
msgid "Very bad"
msgstr ""
#: ../vedia.rb:64
#: ../vedia.rb:66
msgid "Bad"
msgstr ""
#: ../vedia.rb:65
#: ../vedia.rb:67
msgid "Mediocre"
msgstr ""
#: ../vedia.rb:66
#: ../vedia.rb:68
msgid "Good"
msgstr ""
#: ../vedia.rb:67
#: ../vedia.rb:69
msgid "Very good"
msgstr ""
#: ../vedia.rb:114
#: ../vedia.rb:121
msgid "Incorrect email or password."
msgstr ""
#: ../vedia.rb:139
msgid "Reset your password"
msgstr ""
#: ../views/home.erb:1 ../views/layout.erb:14
msgid "Home"
msgstr ""
@ -94,12 +98,13 @@ msgstr ""
msgid "Login"
msgstr ""
#: ../views/login.erb:9 ../views/signup.erb:24 ../views/votes_edit.erb:51
#: ../views/login.erb:9 ../views/reset.erb:5 ../views/reset_change.erb:13
#: ../views/signup.erb:24 ../views/votes_edit.erb:51
#: ../views/votes_show_closed.erb:85 ../views/votes_show_open.erb:58
msgid "Email"
msgstr ""
#: ../views/login.erb:13 ../views/signup.erb:28
#: ../views/login.erb:13 ../views/reset_change.erb:17 ../views/signup.erb:28
msgid "Password"
msgstr ""
@ -107,10 +112,38 @@ msgstr ""
msgid "Create account"
msgstr ""
#: ../views/signup.erb:6
#: ../views/login.erb:19 ../views/reset.erb:1 ../views/reset.erb:8
#: ../views/reset_change.erb:1 ../views/reset_change.erb:20
#: ../views/reset_invalid.erb:1 ../views/reset_sent.erb:1
msgid "Reset password"
msgstr ""
#: ../views/reset_change.erb:6 ../views/signup.erb:6
msgid "Specify a password."
msgstr ""
#: ../views/reset_email.erb:1
msgid "Visit the following link to reset your password:"
msgstr ""
#: ../views/reset_invalid.erb:3
msgid "This password reset link has expired or is invalid."
msgstr ""
#: ../views/reset_invalid.erb:5
msgid "Try resetting your password again."
msgstr ""
#: ../views/reset_sent.erb:3
msgid ""
"If an account exists for %{email}, you will get an email with a link\n"
"to reset your password."
msgstr ""
#: ../views/reset_sent.erb:6
msgid "If you don't receive the email, please check your spam folder."
msgstr ""
#: ../views/signup.erb:14
msgid "Email is not a valid email address."
msgstr ""

View file

@ -4,7 +4,9 @@ require 'bcrypt'
require 'gettext'
require 'securerandom'
require 'chartkick'
require 'mail'
require_relative 'mj'
require_relative "config/environments/#{settings.environment}"
class Vote < ActiveRecord::Base
has_many :candidates, dependent: :destroy
@ -65,6 +67,7 @@ set :values, [ { :id => 1, :label => _("Awful"), :color => '#ff4500' },
{ :id => 4, :label => _("Mediocre"), :color => '#9acd32' },
{ :id => 5, :label => _("Good"), :color => '#228b22' },
{ :id => 6, :label => _("Very good"), :color => '#006400' } ]
set :admin_email, 'vedia@potager.org'
MajorityJudgment.values = settings.values
get '/' do
@ -107,6 +110,10 @@ end
post '/login' do
user = User.find_by(email: params[:email])
if user && verify_password(params[:password], user.password)
if not user.reset.nil?
user.reset = nil
user.save
end
session.clear
session[:user_id] = user.id
redirect '/'
@ -116,6 +123,57 @@ post '/login' do
end
end
get '/reset' do
erb :reset
end
post '/reset' do
@user = User.find_by(email: params[:email])
if @user
@user.reset = SecureRandom.uuid
@user.save
mail = Mail.new
mail.from = settings.admin_email
mail.to = @user.email
mail.subject = _("Reset your password")
mail.body = erb :reset_email, :layout => false
mail.deliver
end
erb :reset_sent
end
get '/reset/:uuid' do
@user = User.find_by(reset: params[:uuid])
if @user
erb :reset_change
else
erb :reset_invalid
end
end
post '/reset/:uuid' do
@user = User.find_by(reset: params[:uuid])
if @user
@errors = []
if params[:password].empty?
@errors << OpenStruct.new(:attribute => :password, :type => :blank)
else
@user.password = hash_password(params[:password])
end
if @errors.empty? and @user.valid?
@user.reset = nil
@user.save
session.clear
session[:user_id] = @user.id
redirect '/'
else
erb :reset_change
end
else
erb :reset_invalid
end
end
get '/logout' do
session.clear
redirect '/login'

View file

@ -4,7 +4,7 @@
<p class="error"><%= @error %></p>
<% end %>
<form action="/login" method="POST">
<form action="/login" method="post">
<p>
<label for="email"><%= _("Email") %></label>
<input type="text" name="email">
@ -15,4 +15,5 @@
</p>
<button type="submit"><%= _("Login") %></button>
</form>
<a href="/signup"><%= _("Create account") %></a>
<p><a href="/signup"><%= _("Create account") %></a></p>
<p><a href="/reset"><%= _("Reset password") %></a></p>

9
views/reset.erb Normal file
View file

@ -0,0 +1,9 @@
<h1><%= _("Reset password") %></h1>
<form action="/reset" method="post">
<p>
<label for="email"><%= _("Email") %></label>
<input type="text" name="email">
</p>
<button type="submit"><%= _("Reset password") %></button>
</form>

21
views/reset_change.erb Normal file
View file

@ -0,0 +1,21 @@
<h1><%= _("Reset password") %></h1>
<% if @errors %>
<% @errors.each do |error| %>
<% if error.attribute == :password and error.type == :blank %>
<p class="error"><%= _("Specify a password.") %></p>
<% end %>
<% end %>
<% end %>
<form action="/reset/<%= params[:uuid] %>" method="post">
<p>
<label for="email"><%= _("Email") %></label>
<span><%= @user.email %></span>
</p>
<p>
<label for="password"><%= _("Password") %></label>
<input type="password" name="password" value="<%= params[:password] %>">
</p>
<button type="submit"><%= _("Reset password") %></button>
</form>

3
views/reset_email.erb Normal file
View file

@ -0,0 +1,3 @@
<%= _("Visit the following link to reset your password:") %>
<%= "#{settings.base_url}reset/#{@user.reset}" %>

5
views/reset_invalid.erb Normal file
View file

@ -0,0 +1,5 @@
<h1><%= _("Reset password") %></h1>
<p class="error"><%= _("This password reset link has expired or is invalid.") %></p>
<p><a href="/reset"><%= _("Try resetting your password again.") %></p>

6
views/reset_sent.erb Normal file
View file

@ -0,0 +1,6 @@
<h1><%= _("Reset password") %></h1>
<p><%= _("If an account exists for %{email}, you will get an email with a link
to reset your password.") % { email: params[:email] } %></p>
<p><%= _("If you don't receive the email, please check your spam folder.") %></p>