Skip to content

Commit

Permalink
Merge pull request #33 from malparty/feature/user-login-backend
Browse files Browse the repository at this point in the history
[#5][Backend] As a User, I can sign up and sign in/out with a username and password
  • Loading branch information
malparty committed Jun 16, 2021
2 parents 7139952 + ff83bd7 commit ff6f012
Show file tree
Hide file tree
Showing 35 changed files with 1,260 additions and 4 deletions.
2 changes: 2 additions & 0 deletions Gemfile
Expand Up @@ -17,6 +17,7 @@ gem 'i18n-js', '3.5.1' # A library to provide the I18n translations on the Javas
# Authentications & Authorizations
gem 'devise' # Authentication solution for Rails with Warden
gem 'pundit' # Minimal authorization through OO design and pure Ruby classes
gem 'doorkeeper' # Awesome OAuth 2 provider for your Rails / Grape app

# Assets
gem 'webpacker', '~>5.2.0' # Transpile app-like JavaScript
Expand Down Expand Up @@ -52,6 +53,7 @@ group :development, :test do
gem 'rubocop-rails', require: false # A RuboCop extension focused on enforcing Rails best practices and coding conventions.
gem 'rubocop-rspec', require: false # Code style checking for RSpec files
gem 'rubocop-performance', require: false # An extension of RuboCop focused on code performance checks.
gem 'ffaker' # used to easily generate fake data: names, addresses, phone numbers, etc.

gem 'undercover' # Report missing test coverage in new changes
gem 'danger' # Automated code review.
Expand Down
3 changes: 3 additions & 0 deletions Gemfile.lock
Expand Up @@ -157,6 +157,8 @@ GEM
warden (~> 1.2.3)
diff-lcs (1.4.4)
docile (1.4.0)
doorkeeper (5.5.1)
railties (>= 5)
erubi (1.10.0)
erubis (2.7.0)
fabrication (2.22.0)
Expand Down Expand Up @@ -480,6 +482,7 @@ DEPENDENCIES
danger-undercover
database_cleaner
devise
doorkeeper
fabrication
ffaker
figaro
Expand Down
16 changes: 16 additions & 0 deletions app/controllers/application_controller.rb
Expand Up @@ -2,4 +2,20 @@

class ApplicationController < ActionController::Base
include Localization

protect_from_forgery with: :exception

before_action :authenticate_user!
before_action :update_allowed_parameters, if: :devise_controller?

protected

def update_allowed_parameters
devise_parameter_sanitizer.permit(:sign_up) do |u|
u.permit(:first_name, :last_name, :email, :password, :password_confirmation)
end
devise_parameter_sanitizer.permit(:account_update) do |u|
u.permit(:first_name, :last_name, :email, :password, :current_password)
end
end
end
14 changes: 14 additions & 0 deletions app/models/user.rb
@@ -0,0 +1,14 @@
# frozen_string_literal: true

class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable

# the authenticate method from devise documentation
def self.authenticate(email, password)
user = User.find_for_authentication(email: email)
user&.valid_password?(password) ? user : nil
end
end
16 changes: 16 additions & 0 deletions app/views/devise/confirmations/new.html.erb
@@ -0,0 +1,16 @@
<h2>Resend confirmation instructions</h2>

<%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %>
<%= render "devise/shared/error_messages", resource: resource %>

<div class="field">
<%= f.label :email %><br />
<%= f.email_field :email, autofocus: true, autocomplete: "email", value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email) %>
</div>

<div class="actions">
<%= f.submit "Resend confirmation instructions" %>
</div>
<% end %>
<%= render "devise/shared/links" %>
5 changes: 5 additions & 0 deletions app/views/devise/mailer/confirmation_instructions.html.erb
@@ -0,0 +1,5 @@
<p>Welcome <%= @email %>!</p>

<p>You can confirm your account email through the link below:</p>

<p><%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %></p>
7 changes: 7 additions & 0 deletions app/views/devise/mailer/email_changed.html.erb
@@ -0,0 +1,7 @@
<p>Hello <%= @email %>!</p>

<% if @resource.try(:unconfirmed_email?) %>
<p>We're contacting you to notify you that your email is being changed to <%= @resource.unconfirmed_email %>.</p>
<% else %>
<p>We're contacting you to notify you that your email has been changed to <%= @resource.email %>.</p>
<% end %>
3 changes: 3 additions & 0 deletions app/views/devise/mailer/password_change.html.erb
@@ -0,0 +1,3 @@
<p>Hello <%= @resource.email %>!</p>

<p>We're contacting you to notify you that your password has been changed.</p>
8 changes: 8 additions & 0 deletions app/views/devise/mailer/reset_password_instructions.html.erb
@@ -0,0 +1,8 @@
<p>Hello <%= @resource.email %>!</p>

<p>Someone has requested a link to change your password. You can do this through the link below.</p>

<p><%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %></p>

<p>If you didn't request this, please ignore this email.</p>
<p>Your password won't change until you access the link above and create a new one.</p>
7 changes: 7 additions & 0 deletions app/views/devise/mailer/unlock_instructions.html.erb
@@ -0,0 +1,7 @@
<p>Hello <%= @resource.email %>!</p>

<p>Your account has been locked due to an excessive number of unsuccessful sign in attempts.</p>

<p>Click the link below to unlock your account:</p>

<p><%= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token) %></p>
25 changes: 25 additions & 0 deletions app/views/devise/passwords/edit.html.erb
@@ -0,0 +1,25 @@
<h2>Change your password</h2>

<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| %>
<%= render "devise/shared/error_messages", resource: resource %>
<%= f.hidden_field :reset_password_token %>

<div class="field">
<%= f.label :password, "New password" %><br />
<% if @minimum_password_length %>
<em>(<%= @minimum_password_length %> characters minimum)</em><br />
<% end %>
<%= f.password_field :password, autofocus: true, autocomplete: "new-password" %>
</div>

<div class="field">
<%= f.label :password_confirmation, "Confirm new password" %><br />
<%= f.password_field :password_confirmation, autocomplete: "new-password" %>
</div>

<div class="actions">
<%= f.submit "Change my password" %>
</div>
<% end %>
<%= render "devise/shared/links" %>
16 changes: 16 additions & 0 deletions app/views/devise/passwords/new.html.erb
@@ -0,0 +1,16 @@
<h2>Forgot your password?</h2>

<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| %>
<%= render "devise/shared/error_messages", resource: resource %>

<div class="field">
<%= f.label :email %><br />
<%= f.email_field :email, autofocus: true, autocomplete: "email" %>
</div>

<div class="actions">
<%= f.submit "Send me reset password instructions" %>
</div>
<% end %>
<%= render "devise/shared/links" %>
53 changes: 53 additions & 0 deletions app/views/devise/registrations/edit.html.erb
@@ -0,0 +1,53 @@
<h2>Edit <%= resource_name.to_s.humanize %></h2>

<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
<%= render "devise/shared/error_messages", resource: resource %>

<div class="field">
<%= f.label :first_name %><br />
<%= f.text_field :first_name, autofocus: true %>
</div>

<div class="field">
<%= f.label :last_name %><br />
<%= f.text_field :last_name %>
</div>

<div class="field">
<%= f.label :email %><br />
<%= f.email_field :email, autocomplete: "email" %>
</div>

<% if devise_mapping.confirmable? && resource.pending_reconfirmation? %>
<div>Currently waiting confirmation for: <%= resource.unconfirmed_email %></div>
<% end %>

<div class="field">
<%= f.label :password %> <i>(leave blank if you don't want to change it)</i><br />
<%= f.password_field :password, autocomplete: "new-password" %>
<% if @minimum_password_length %>
<br />
<em><%= @minimum_password_length %> characters minimum</em>
<% end %>
</div>

<div class="field">
<%= f.label :password_confirmation %><br />
<%= f.password_field :password_confirmation, autocomplete: "new-password" %>
</div>

<div class="field">
<%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i><br />
<%= f.password_field :current_password, autocomplete: "current-password" %>
</div>

<div class="actions">
<%= f.submit "Update" %>
</div>
<% end %>

<h3>Cancel my account</h3>

<p>Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete %></p>

<%= link_to "Back", :back %>
39 changes: 39 additions & 0 deletions app/views/devise/registrations/new.html.erb
@@ -0,0 +1,39 @@
<h2>Sign up</h2>

<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
<%= render "devise/shared/error_messages", resource: resource %>

<div class="field">
<%= f.label :first_name %><br />
<%= f.text_field :first_name, autofocus: true %>
</div>

<div class="field">
<%= f.label :last_name %><br />
<%= f.text_field :last_name %>
</div>

<div class="field">
<%= f.label :email %><br />
<%= f.email_field :email, autocomplete: "email" %>
</div>

<div class="field">
<%= f.label :password %>
<% if @minimum_password_length %>
<em>(<%= @minimum_password_length %> characters minimum)</em>
<% end %><br />
<%= f.password_field :password, autocomplete: "new-password" %>
</div>

<div class="field">
<%= f.label :password_confirmation %><br />
<%= f.password_field :password_confirmation, autocomplete: "new-password" %>
</div>

<div class="actions">
<%= f.submit "Sign up" %>
</div>
<% end %>
<%= render "devise/shared/links" %>
26 changes: 26 additions & 0 deletions app/views/devise/sessions/new.html.erb
@@ -0,0 +1,26 @@
<h2>Log in</h2>

<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
<div class="field">
<%= f.label :email %><br />
<%= f.email_field :email, autofocus: true, autocomplete: "email" %>
</div>

<div class="field">
<%= f.label :password %><br />
<%= f.password_field :password, autocomplete: "current-password" %>
</div>

<% if devise_mapping.rememberable? %>
<div class="field">
<%= f.check_box :remember_me %>
<%= f.label :remember_me %>
</div>
<% end %>

<div class="actions">
<%= f.submit "Log in" %>
</div>
<% end %>
<%= render "devise/shared/links" %>
15 changes: 15 additions & 0 deletions app/views/devise/shared/_error_messages.html.erb
@@ -0,0 +1,15 @@
<% if resource.errors.any? %>
<div id="error_explanation">
<h2>
<%= t("errors.messages.not_saved",
count: resource.errors.count,
resource: resource.class.model_name.human.downcase)
%>
</h2>
<ul>
<% resource.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
25 changes: 25 additions & 0 deletions app/views/devise/shared/_links.html.erb
@@ -0,0 +1,25 @@
<%- if controller_name != 'sessions' %>
<%= link_to "Log in", new_session_path(resource_name) %><br />
<% end %>
<%- if devise_mapping.registerable? && controller_name != 'registrations' %>
<%= link_to "Sign up", new_registration_path(resource_name) %><br />
<% end %>
<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
<%= link_to "Forgot your password?", new_password_path(resource_name) %><br />
<% end %>
<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
<%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %><br />
<% end %>
<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
<%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %><br />
<% end %>
<%- if devise_mapping.omniauthable? %>
<%- resource_class.omniauth_providers.each do |provider| %>
<%= link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider), method: :post %><br />
<% end %>
<% end %>
16 changes: 16 additions & 0 deletions app/views/devise/unlocks/new.html.erb
@@ -0,0 +1,16 @@
<h2>Resend unlock instructions</h2>

<%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %>
<%= render "devise/shared/error_messages", resource: resource %>

<div class="field">
<%= f.label :email %><br />
<%= f.email_field :email, autofocus: true, autocomplete: "email" %>
</div>

<div class="actions">
<%= f.submit "Resend unlock instructions" %>
</div>
<% end %>
<%= render "devise/shared/links" %>
5 changes: 5 additions & 0 deletions app/views/layouts/application.html.erb
Expand Up @@ -11,6 +11,11 @@
</head>

<body>
<% if user_signed_in? %>
<%= button_to t('logout'), destroy_user_session_path, method: :delete %>
<% end %>
<p class="notice"><%= notice %></p>
<p class="alert"><%= alert %></p>
<%= yield %>
</body>
</html>
4 changes: 2 additions & 2 deletions config/initializers/devise.rb
Expand Up @@ -24,7 +24,7 @@
# Configure the e-mail address which will be shown in Devise::Mailer,
# note that it will be overwritten if you use your own mailer class
# with default "from" parameter.
config.mailer_sender = 'please-change-me-at-config-initializers-devise@example.com'
config.mailer_sender = 'noreply@nimblehq.co'

# Configure the class responsible to send e-mails.
# config.mailer = 'Devise::Mailer'
Expand Down Expand Up @@ -183,7 +183,7 @@
# Email regex used to validate email formats. It simply asserts that
# one (and only one) @ exists in the given string. This is mainly
# to give user feedback and not to assert the e-mail validity.
config.email_regexp = /\A[^@\s]+@[^@\s]+\z/
config.email_regexp = URI::MailTo::EMAIL_REGEXP

# ==> Configuration for :timeoutable
# The time you want to timeout the user session without activity. After this
Expand Down

0 comments on commit ff6f012

Please sign in to comment.