Browse Source

Configure login via username or email

Andrew Swistak 6 years ago
parent
commit
86a9655ac5

+ 20 - 0
app/controllers/users/registrations_controller.rb

@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+class Users::RegistrationsController < Devise::RegistrationsController
+  # rubocop:disable LexicallyScopedActionFilter
+  before_action :configure_sign_up_params, only: [:create]
+  before_action :configure_account_update_params, only: [:update]
+  # rubocop:enable LexicallyScopedActionFilter
+
+  protected
+
+  # If you have extra params to permit, append them to the sanitizer.
+  def configure_sign_up_params
+    devise_parameter_sanitizer.permit(:sign_up, keys: %i[email username])
+  end
+
+  # If you have extra params to permit, append them to the sanitizer.
+  def configure_account_update_params
+    devise_parameter_sanitizer.permit(:account_update, keys: %i[email username])
+  end
+end

+ 17 - 0
app/models/user.rb

@@ -6,10 +6,27 @@ class User < ApplicationRecord
 
   devise :omniauthable, omniauth_providers: %i[reddit google_oauth2 discord]
 
+  attr_accessor :login
+
   # Note: devise :validatable above adds validations for :email and :password
   # validates :username, presence: true, length: {maximum: 128}
   validates :email, confirmation: true
 
+  class << self
+    # Devise method overridden to allow sign in with email or username
+    def find_for_database_authentication(warden_conditions)
+      conditions = warden_conditions.dup
+      if (login = conditions.delete(:login))
+        where(conditions).find_by(
+          'lower(username) = :value OR lower(email) = :value',
+          value: login.downcase.strip,
+        )
+      else
+        find_by(conditions)
+      end
+    end
+  end
+
   def self.from_omniauth(auth)
     where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
       user.email = auth.info.email

+ 48 - 0
app/views/devise/registrations/edit.html.erb

@@ -0,0 +1,48 @@
+<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 :username %><br />
+    <%= f.text_field :username, autofocus: true, autocomplete: "username" %>
+  </div>
+
+  <div class="field">
+    <%= f.label :email %><br />
+    <%= f.email_field :email, autofocus: true, 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 %>

+ 34 - 0
app/views/devise/registrations/new.html.erb

@@ -0,0 +1,34 @@
+<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 :username %><br />
+    <%= f.text_field :username, autofocus: true, autocomplete: "username" %>
+  </div>
+
+  <div class="field">
+    <%= f.label :email %><br />
+    <%= f.email_field :email, autofocus: true, 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 - 0
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 :login %><br />
+    <%= f.text_field :login, autofocus: true, autocomplete: "username" %>
+  </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" %>

+ 2 - 2
config/initializers/devise.rb

@@ -45,7 +45,7 @@ Devise.setup do |config|
   # filter.  You can also supply a hash where the value is a boolean determining
   # whether or not authentication should be aborted when the value is not
   # present.
-  config.authentication_keys = [:username]
+  config.authentication_keys = [:login]
 
   # Configure parameters from the request object used for authentication. Each
   # entry given should be a request method and it will automatically be passed
@@ -248,7 +248,7 @@ Devise.setup do |config|
 
   # Configure the default scope given to Warden. By default it's the first
   # devise role declared in your routes (usually :user).
-  # config.default_scope = :user
+  config.default_scope = :user
 
   # Set this configuration to false if you want /users/sign_out to sign out
   # only the current scope. By default, Devise signs out all scopes.

+ 4 - 1
config/routes.rb

@@ -8,7 +8,10 @@ Rails.application.routes.draw do
   end
 
   devise_for :users,
-             controllers: {omniauth_callbacks: 'users/omniauth_callbacks'}
+             controllers: {
+               omniauth_callbacks: 'users/omniauth_callbacks',
+               registrations: 'users/registrations',
+             }
 
   root to: 'welcome#root'