Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions app/assets/stylesheets/messages.css
Original file line number Diff line number Diff line change
Expand Up @@ -593,3 +593,9 @@ img.message__attachment {
inline-size: 1.4em;
}
}

/* Reset password */
.reset-password-alert {
color: var(--color-negative);
visibility: hidden;
}
55 changes: 55 additions & 0 deletions app/controllers/sessions/password_resets_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
class Sessions::PasswordResetsController < ApplicationController
allow_unauthenticated_access

before_action :require_smpt

def index
end
def new
end

def show
@password_reset_id = params[:id]
@user = User.find_by_password_reset_id(@password_reset_id)

redirect_to root_url unless @user
end

def update
@user = User.find_by_password_reset_id(password_reset_params[:password_reset_id])

redirect_to root_url unless @user
redirect_to root_url unless password_match?

@user.update(password: password_reset_params[:new_password])

redirect_to new_session_path
end

def create
email = params[:email_address]
password_reset_url = session_password_reset_url(find_user_by_email(email).password_reset_id)

PasswordResetMailer.with(email: email, url: password_reset_url).password_reset_email.deliver_later

redirect_to new_session_password_reset_path
end

private

def require_smpt
redirect_to root_url unless helpers.smtp_enabled?
end

def find_user_by_email(email)
User.find_by(email_address: email)
end

def password_match?
password_reset_params[:new_password] == password_reset_params[:confirm_new_password]
end

def password_reset_params
params.require(:user).permit(:new_password, :confirm_new_password, :password_reset_id)
end
end
11 changes: 11 additions & 0 deletions app/helpers/application_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,17 @@ def link_back_to(destination)
end
end

def smtp_enabled?
Rails.application.config.respond_to?(:feature_enable_smtp) &&
Rails.application.config.feature_enable_smtp.present? &&
Rails.application.config.action_mailer.smtp_settings.present? &&
Rails.application.config.action_mailer.smtp_settings[:address].present? &&
Rails.application.config.action_mailer.smtp_settings[:port].present? &&
Rails.application.config.action_mailer.smtp_settings[:domain].present? &&
Rails.application.config.action_mailer.smtp_settings[:user_name].present? &&
Rails.application.config.action_mailer.smtp_settings[:password].present?
end

private
def admin_body_class
"admin" if Current.user&.can_administer?
Expand Down
38 changes: 38 additions & 0 deletions app/javascript/controllers/password_reset_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
static targets = [ "resetPasswordInput", "resetPasswordConfirmInput", "resetPasswordError", "resetPasswordSubmit"]

resetPasswordCheckInputs() {
if(this.#checkResetInputsValues()) {
this.#hideErrorMessage()
this.#enableSubmitButton()
} else {
this.#showErrorMessage()
this.#disableSubmitButton()
}
}

#showErrorMessage() {
this.resetPasswordErrorTarget.style.visibility = "visible"
}

#hideErrorMessage() {
this.resetPasswordErrorTarget.style.visibility = "hidden"
}

#enableSubmitButton() {
this.resetPasswordSubmitTarget.disabled = false
}

#disableSubmitButton() {
this.resetPasswordSubmitTarget.disabled = true
}

#checkResetInputsValues() {
if (this.resetPasswordInputTarget.value.length < 0 || this.resetPasswordConfirmInputTarget.value.length < 0) return false
if (this.resetPasswordInputTarget.value !== this.resetPasswordConfirmInputTarget.value) return false

return true
}
}
6 changes: 6 additions & 0 deletions app/mailers/application_mailer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class ApplicationMailer < ActionMailer::Base
DEFAULT_BASE_FROM="[email protected]"

default from: ENV.fetch("SMTP_INFO_EMAIL_FROM", DEFAULT_BASE_FROM)
layout "mailer"
end
9 changes: 9 additions & 0 deletions app/mailers/password_reset_mailer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class PasswordResetMailer < ApplicationMailer
default from: ENV.fetch("SMTP_PASSWORD_RESET_EMAIL_FROM", DEFAULT_BASE_FROM)

def password_reset_email
@email = params[:email]
@url = params[:url]
mail(to: @email, subject: "Campfire Reset Password")
end
end
2 changes: 1 addition & 1 deletion app/models/user.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
class User < ApplicationRecord
include Avatar, Bannable, Bot, Mentionable, Role, Transferable
include Avatar, Bannable, Bot, Mentionable, Role, Transferable, Resettable

has_many :memberships, dependent: :delete_all
has_many :rooms, through: :memberships
Expand Down
15 changes: 15 additions & 0 deletions app/models/user/resettable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module User::Resettable
extend ActiveSupport::Concern

RESET_PASSWORD_LINK_EXPIRY_DURATION = 5.hours

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe you forgot to delete the comment in line 4?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep thx


class_methods do
def find_by_password_reset_id(id)
find_signed(id, purpose: :password_reset)
end
end

def password_reset_id
signed_id(purpose: :password_reset, expires_in: RESET_PASSWORD_LINK_EXPIRY_DURATION)
end
end
9 changes: 9 additions & 0 deletions app/views/password_reset_mailer/password_reset_email.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<h1>Reset Your Campfire Password</h1>
<p>Hello,</p>
<br>
<p>You have requested a password reset for your Campfire account.</p>
<p>
To reset your Campfire password, please click on this: <%= link_to 'link', @url %>.
</p>
<br>
<p>Campfire</p>
9 changes: 9 additions & 0 deletions app/views/password_reset_mailer/password_reset_email.text.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Reset Your Campfire Password
===============================================
Hello,

You have requested a password reset for your Campfire account.

To reset your Campfire password, please click on this: <%= link_to 'link', @url %>.

Campfire
9 changes: 9 additions & 0 deletions app/views/sessions/new.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@
</label>
</div>

<% if smtp_enabled? %>
<div>
<%= link_to session_password_resets_path, class: "btn flex-item-justify-end" do %>
<div> Forgot your password? </div>
<span class="for-screen-reader">Forgot password?</span>
<% end %>
</div>
<% end %>

<%= form.button class: "btn btn--reversed center txt-large", type: "submit", name: "log_in" do %>
<%= image_tag "arrow-right.svg", aria: { hidden: "true" } %>
<span class="for-screen-reader">Go</span>
Expand Down
30 changes: 30 additions & 0 deletions app/views/sessions/password_resets/index.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<% @page_title = "Reset password" %>
<% turbo_page_requires_reload %>

<section class="txt-align-center">
<div class="panel <%= "shake" if flash[:alert] %>">
<%= account_logo_tag style: "center margin-block-end txt-xx-large" %>

<%= form_with url: session_password_resets_url, class: "flex flex-column gap" do |form| %>
<fieldset class="flex flex-column gap center-block upad">
<legend class="txt-large txt-align-center"><strong><%= Current.account.name %></strong></legend>

<div>Enter your email to receive reset password link</div>

<div class="flex align-center gap">
<%= translation_button(:email_address) %>
<label class="flex align-center gap input input--actor txt-large">
<%= form.email_field :email_address, required: true, class: "input", autofocus: true, autocomplete: "username", placeholder: "Enter your email address", value: params[:email_address] %>
<%= image_tag "email.svg", aria: { hidden: "true" }, size: 24, class: "colorize--black" %>
</label>
</div>
<%= form.button class: "btn btn--reversed center txt-large", type: "submit", name: "reset_password_email" do %>
<%= image_tag "arrow-right.svg", aria: { hidden: "true" } %>
<span class="for-screen-reader">Email reset password link</span>
<% end %>
</fieldset>
<% end %>
</div>

<%= render "accounts/help_contact" %>
</section>
28 changes: 28 additions & 0 deletions app/views/sessions/password_resets/new.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<% @page_title = "Reset password" %>
<% turbo_page_requires_reload %>

<section class="txt-align-center">
<div class="panel <%= "shake" if flash[:alert] %>">
<%= account_logo_tag style: "center margin-block-end txt-xx-large" %>

<%= form_with url: session_password_resets_url, class: "flex flex-column gap" do |form| %>
<fieldset class="flex flex-column gap center-block upad">
<legend class="txt-large txt-align-center"><strong><%= Current.account.name %></strong></legend>
<p>Reset link has been sent to your email.</p>

<br>

<div> Didn't receive an email?</div>

<div>
<%= link_to session_password_resets_path, class: "btn flex-item-justify-end" do %>
<div> Return to the reset password page </div>
<span class="for-screen-reader">Return to the reset password page</span>
<% end %>
</div>
</fieldset>
<% end %>
</div>

<%= render "accounts/help_contact" %>
</section>
65 changes: 65 additions & 0 deletions app/views/sessions/password_resets/show.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<% @page_title = "Reset password" %>
<% turbo_page_requires_reload %>

<section class="txt-align-center">
<div class="panel <%= "shake" if flash[:alert] %>">
<%= account_logo_tag style: "center margin-block-end txt-xx-large" %>

<%= form_with model: @user, url: session_password_reset_url, class: "flex flex-column gap", data: { controller: "password-reset" }, method: :patch do |form| %>
<fieldset class="flex flex-column gap center-block upad">
<legend class="txt-large txt-align-center"><strong><%= Current.account.name %></strong></legend>
<p>Please enter the new password</p>

<div class="flex align-center gap">
<%= translation_button(:password) %>
<label class="flex align-center gap flex-item-grow txt-large input input--actor">
<%= form.password_field :new_password,
class: "input",
autocomplete: "new-password",
placeholder: "New password",
required: true,
maxlength: 72,
data: {
password_reset_target: "resetPasswordInput",
action: "input->password-reset#resetPasswordCheckInputs"
} %>
<%= image_tag "password.svg", aria: { hidden: "true" }, size: 24, class: "colorize--black" %>
</label>
</div>

<div class="flex align-center gap">
<%= translation_button(:password) %>
<label class="flex align-center gap flex-item-grow txt-large input input--actor">
<%= form.password_field :confirm_new_password,
class: "input",
autocomplete: "new-password",
placeholder: "Confirm new password",
required: true,
maxlength: 72,
data: {
password_reset_target: "resetPasswordConfirmInput",
action: "input->password-reset#resetPasswordCheckInputs"
} %>
<%= image_tag "password.svg", aria: { hidden: "true" }, size: 24, class: "colorize--black" %>
</label>
</div>
<%= form.text_field :password_reset_id, value: @password_reset_id, hidden: true,required: true %>

<p id="reset_password_not_matched" class="reset-password-alert" data-password-reset-target="resetPasswordError">Passwords do not match!</p>

<div>
<%= form.button class: "btn btn--reversed center txt-large",
type: "submit",
name: "reset_password",
disabled: true,
data: { password_reset_target: "resetPasswordSubmit" } do %>
<%= image_tag "arrow-right.svg", aria: { hidden: "true" } %>
<span class="for-screen-reader">Confirm new password</span>
<% end %>
</div>
</fieldset>
<% end %>
</div>

<%= render "accounts/help_contact" %>
</section>
15 changes: 15 additions & 0 deletions config/environments/development.rb
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,19 @@

# Visit /rails/locks to see the locks
config.middleware.insert_before Rack::Sendfile, ActionDispatch::DebugLocks

# SMTP mailer setup
config.feature_enable_smtp = ENV.fetch("SMTP_ENABLED", false)
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
address: ENV.fetch("SMTP_ADDRESS", nil),
port: ENV.fetch("SMTP_PORT", nil),
domain: ENV.fetch("SMTP_DOMAIN", nil),
user_name: ENV.fetch("SMTP_USER_NAME", nil),
password: ENV.fetch("SMTP_PASSWORD", nil),
authentication: "plain",
open_timeout: 5,
read_timeout: 5,
openssl_verify_mode: "none"
}
end
15 changes: 15 additions & 0 deletions config/environments/production.rb
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,19 @@
config.active_record.attributes_for_inspect = [ :id ]

config.active_job.queue_adapter = :resque

# SMTP mailer setup
config.feature_enable_smtp = ENV.fetch("SMTP_ENABLED", false)
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
address: ENV.fetch("SMTP_ADDRESS", nil),
port: ENV.fetch("SMTP_PORT", nil),
domain: ENV.fetch("SMTP_DOMAIN", nil),
user_name: ENV.fetch("SMTP_USER_NAME", nil),
password: ENV.fetch("SMTP_PASSWORD", nil),
authentication: "plain",
enable_starttls: true,
open_timeout: 5,
read_timeout: 5
}
end
4 changes: 4 additions & 0 deletions config/environments/test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,8 @@

# Load test helpers
config.autoload_paths += %w[ test/test_helpers ]

# Action Mailer test setup
config.action_mailer.delivery_method = :test
config.action_mailer.perform_deliveries = true
end
2 changes: 2 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
resource :session do
scope module: "sessions" do
resources :transfers, only: %i[ show update ]

resources :password_resets, only: %i[ new show create update index]
end
end

Expand Down
Loading