diff --git a/Gemfile b/Gemfile
index f542974..8b3fc29 100644
--- a/Gemfile
+++ b/Gemfile
@@ -27,6 +27,7 @@ group :development, :test do
end
group :development do
+ gem "letter_opener"
gem "rubocop-rails-omakase"
gem "web-console"
end
diff --git a/Gemfile.lock b/Gemfile.lock
index c9ef976..be52154 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -99,6 +99,7 @@ GEM
rack-test (>= 0.6.3)
regexp_parser (>= 1.5, < 3.0)
xpath (~> 3.2)
+ childprocess (5.0.0)
concurrent-ruby (1.2.3)
connection_pool (2.4.1)
crass (1.0.6)
@@ -134,6 +135,11 @@ GEM
activesupport (>= 5.0.0)
json (2.7.2)
language_server-protocol (3.17.0.3)
+ launchy (3.0.1)
+ addressable (~> 2.8)
+ childprocess (~> 5.0)
+ letter_opener (1.10.0)
+ launchy (>= 2.2, < 4)
loofah (2.22.0)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
@@ -327,6 +333,7 @@ DEPENDENCIES
image_processing (~> 1.2)
importmap-rails
jbuilder
+ letter_opener
mission_control-jobs
pg
propshaft
diff --git a/app/assets/stylesheets/colors.css b/app/assets/stylesheets/colors.css
index 116e64d..48c1727 100644
--- a/app/assets/stylesheets/colors.css
+++ b/app/assets/stylesheets/colors.css
@@ -1,9 +1,9 @@
:root {
- --background-color: #111;
+ --background-color: #333;
--main-background-color:
--header-color: #15345b;
--header-text-color: #fff;
- --header-height: 150px;
+ --header-height: 200px;
--inset-bg-color: #eee;
diff --git a/app/assets/stylesheets/layout.css b/app/assets/stylesheets/layout.css
index 6d8a710..68a1fcc 100644
--- a/app/assets/stylesheets/layout.css
+++ b/app/assets/stylesheets/layout.css
@@ -63,7 +63,7 @@ header:before {
}
header h1 {
- padding: .5em 0;
+ padding: 1em 0;
}
header h1 a:link, header h1 a:visited {
diff --git a/app/controllers/password_resets_controller.rb b/app/controllers/password_resets_controller.rb
new file mode 100644
index 0000000..f27a976
--- /dev/null
+++ b/app/controllers/password_resets_controller.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+class PasswordResetsController < ApplicationController
+ skip_before_action :authenticate
+
+ def new
+ reset_session
+ end
+
+ def create
+ user = User.find_by(username: params[:username])
+ if user
+ token = user.generate_token_for(:password_reset)
+ UserMailer.with(user: user, token: token).password_reset.deliver_later
+ redirect_to new_session_path, notice: t(".success") and return
+ end
+
+ redirect_to :root, notice: t(".error")
+ end
+
+ def edit
+ reset_session
+ @user = User.find_by(username: params[:id])
+ @token = params[:token]
+ unless @user == User.find_by_token_for(:password_reset, params[:token])
+ redirect_to :root, notice: t(".invalid_token") and return
+ end
+ end
+
+ def update
+ user = User.find_by(username: params[:id])
+ unless user == User.find_by_token_for(:password_reset, params[:token])
+ redirect_to :root, notice: t(".invalid_token") and return
+ end
+
+ if user.update(password: params[:password], password_confirmation: params[:password_confirmation])
+ redirect_to new_session_path, notice: t(".success")
+ else
+ redirect_to :root, notice: t(".error")
+ end
+ end
+end
diff --git a/app/mailers/table_invite_mailer.rb b/app/mailers/table_invite_mailer.rb
index d16116f..8eef87c 100644
--- a/app/mailers/table_invite_mailer.rb
+++ b/app/mailers/table_invite_mailer.rb
@@ -6,12 +6,12 @@ class TableInviteMailer < ApplicationMailer
def invite_user
@table_invite = params[:table_invite]
- mail(to: @table_invite.email, subject: "[Tabletop Companion] Invite to join a table")
+ mail(to: @table_invite.email, subject: t(".invite_user.subject"))
end
def invite_new_user
@table_invite = params[:table_invite]
- mail(to: @table_invite.email, subject: "[Tabletop Companion] You’ve been invited to a game!")
+ mail(to: @table_invite.email, subject: t(".invite_new_user.subject"))
end
end
diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb
index 94d48f8..08d945f 100644
--- a/app/mailers/user_mailer.rb
+++ b/app/mailers/user_mailer.rb
@@ -7,12 +7,19 @@ class UserMailer < ApplicationMailer
@user = params[:user]
@token = params[:token]
- mail(to: @user.email, subject: "[Tabletop Companion] Verify your email")
+ mail(to: @user.email, subject: t(".email_verification.subject"))
end
def email_verified
@user = params[:user]
- mail(to: @user.email, subject: "[Tabletop Companion] Your email has been verified")
+ mail(to: @user.email, subject: t(".email_verified.subject"))
+ end
+
+ def password_reset
+ @user = params[:user]
+ @token = params[:token]
+
+ mail(to: @user.email, subject: t(".password_reset.subject"))
end
end
diff --git a/app/views/password_resets/edit.html.erb b/app/views/password_resets/edit.html.erb
new file mode 100644
index 0000000..2559cde
--- /dev/null
+++ b/app/views/password_resets/edit.html.erb
@@ -0,0 +1,17 @@
+<%= content_for :title, t(".reset_password") %>
+
+
<%= t(".reset_password") %>
+
+
+ <%= form_with url: password_reset_path(id: @user.username), method: :patch do |f| %>
+ <%= f.hidden_field :token, value: @token %>
+
+ <%= f.label :password %>
+ <%= f.password_field :password %>
+
+ <%= f.label :password_confirmation %>
+ <%= f.password_field :password_confirmation %>
+
+ <%= f.submit t(".reset_password_button") %>
+ <% end %>
+
diff --git a/app/views/password_resets/new.html.erb b/app/views/password_resets/new.html.erb
new file mode 100644
index 0000000..afb73a5
--- /dev/null
+++ b/app/views/password_resets/new.html.erb
@@ -0,0 +1,15 @@
+<%= content_for :title, t(".reset_password") %>
+
+<%= t(".reset_password") %>
+
+ <%= t(".forgotten_password_intro") %>
+
+ <%= form_with url: password_resets_path do |f| %>
+ <%= f.label :username %>
+ <%= f.text_field :username %>
+
+ <%= f.submit t(".reset_password_button") %>
+ <% end %>
+
+
+
diff --git a/app/views/sessions/new.html.erb b/app/views/sessions/new.html.erb
index 33427d2..bac1db4 100644
--- a/app/views/sessions/new.html.erb
+++ b/app/views/sessions/new.html.erb
@@ -14,3 +14,4 @@
<% end %>
+<%= link_to(t(".forgot_password"), new_password_reset_path) %>
diff --git a/app/views/user_mailer/password_reset.html.erb b/app/views/user_mailer/password_reset.html.erb
new file mode 100644
index 0000000..7b0035d
--- /dev/null
+++ b/app/views/user_mailer/password_reset.html.erb
@@ -0,0 +1,3 @@
+<%= htmlify_email(t(".content")) %>
+
+<%= link_to t(".reset_password"), edit_password_reset_url(id: @user, token: @token) %>
diff --git a/app/views/user_mailer/password_reset.text.erb b/app/views/user_mailer/password_reset.text.erb
new file mode 100644
index 0000000..ad321d2
--- /dev/null
+++ b/app/views/user_mailer/password_reset.text.erb
@@ -0,0 +1,3 @@
+<%= t(".content") %>
+
+<%= edit_password_reset_url(id: @user, token: @token) %>
diff --git a/config/environments/development.rb b/config/environments/development.rb
index af49d87..dd1699a 100644
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -40,6 +40,7 @@ Rails.application.configure do
# Don't care if the mailer can't send.
config.action_mailer.raise_delivery_errors = false
+ config.action_mailer.delivery_method = :letter_opener
config.action_mailer.perform_caching = false
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 9a8ebd2..87f966a 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -54,22 +54,42 @@ en:
destroy:
success: Successfully deleted “%{name}”.
error: “%{name}” could not be deleted.
+ password_resets:
+ new:
+ reset_password: Reset your password
+ forgotten_password_intro: |-
+ If you have forgotten your password, enter your information below to reset it.
+ reset_password_button: Reset password
+ create:
+ success: Please check your email to reset your password.
+ error: Could not reset your password.
+ edit:
+ reset_password: Reset your password
+ invalid_token: That token seems to have expired, please try resettting your password again.
+ reset_password_button: Update password
+ update:
+ invalid_token: That token seems to have expired, please try resettting your password again.
+ success: Your password has been reset, you may now log in.
+ error: Failed to reset password. Please try again or contact us for help.
sessions:
create:
success: "Hello, %{name}!"
error: "Could not sign in. Please check your username and password."
new:
log_in: Log in
+ forgot_password: Forgotten your password?
destroy:
log_out: Log out
success: "You have signed out."
table_invite_mailer:
invite_new_user:
+ subject: You’ve been invited to join a game on Tabletop Companion!
content: |-
You’ve been invited to join a game on Tabletop Companion. To start playing, sign up at the
link below and accept your invitation!
sign_up: Sign up now
invite_user:
+ subject: You’ve been invited to a new game!
content: |-
You have been invited to join the table “%{table_name}” on Tabletop Companion. To
respond, visit the link below.
@@ -145,13 +165,22 @@ en:
error: Failed to update profile
user_mailer:
email_verified:
+ subject: Email verified on Tabletop Companion
content: |-
Thanks for verifying your email address, and welcome to Tabletop Companion!
You can now go ahead and log in. Enjoy!
email_verification:
+ subject: Verify your email on Tabletop Companion
content: |-
If you did not sign up for Tabletop Companion, please ignore this email.
Otherwise, please visit the link below to verify your email address.
+ password_reset:
+ subject: Reset your password on Tabletop Companion
+ reset_password: Reset your password
+ content: |-
+ If you did not request a password reset, please ignore this email.
+
+ Otherwise, please visit the link below to reset your password.
diff --git a/config/routes.rb b/config/routes.rb
index 1dc2a8c..a21bb2f 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -10,6 +10,7 @@ Rails.application.routes.draw do
resources :users, only: [ :new, :create, :show, :edit, :update ]
resources :account_verifications, only: [ :show ]
+ resources :password_resets, only: [ :new, :create, :edit, :update ]
resources :sessions, only: [ :new, :create, :destroy ]
resources :table_invites, only: [ :index, :edit, :update ]
diff --git a/test/controllers/password_resets_controller_test.rb b/test/controllers/password_resets_controller_test.rb
new file mode 100644
index 0000000..dac4a47
--- /dev/null
+++ b/test/controllers/password_resets_controller_test.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require "test_helper"
+
+class PasswordResetsControllerTest < ActionDispatch::IntegrationTest
+ test "should get new" do
+ get new_password_reset_path
+ assert_response :success
+ end
+
+ test "should send a password reset email" do
+ user = users(:trevor)
+ assert_emails(+1) do
+ post password_resets_path, params: { username: user.username }
+ assert_redirected_to new_session_path
+ end
+ end
+
+ test "should get edit" do
+ user = users(:trevor)
+ token = user.generate_token_for(:password_reset)
+ get edit_password_reset_path(token: token, id: user.username)
+ assert_response :success
+ end
+
+ test "should update password" do
+ user = users(:trevor)
+ token = user.generate_token_for(:password_reset)
+ put password_reset_path(id: user.username, token: token),
+ params: { password: "password", password_confirmation: "password" }
+ assert_redirected_to new_session_path
+ assert user.reload.authenticate("password")
+ end
+end
diff --git a/todo.md b/todo.md
index 6cf5e1a..5648ce7 100644
--- a/todo.md
+++ b/todo.md
@@ -2,6 +2,9 @@
- delete avatar
- default avatars
- discrete password page
+- shared/private notes
- notifications
- Add characters to users/tables
- Character sheets/prototypes
+- chat
+- maps