Creating an account with a username - ruby-on-rails

I have followed the devise wiki to enable username login with devise (instead of email). But I can't figure out how to enable username signup/registration with devise.
I have changed my registration view to prompt for a first and last name and a password.
app/views/devise/registrations/new.html.erb:
<h3>Sign up</h3>
<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f| %>
<%= devise_error_messages! %>
<div><%= f.label :firstname, "First Name", :autofocus => true %><br />
<script type="text/javascript">window.onload = function() {document.getElementById('user_firstname').focus();}</script>
<%= f.text_field :firstname %></div>
<div><%= f.label :lastname, "Last Name", :autofocus => true %><br />
<%= f.text_field :lastname %></div>
<div><%= f.label :password %><br />
<%= f.password_field :password %></div>
<div><%= f.label :password_confirmation %><br />
<%= f.password_field :password_confirmation %></div>
<div><%= f.submit "Sign up" %></div>
<% end %>
<%= render :partial => "devise/shared/links" %>
I've edited my model to generate a username from the first and last name:
app/models/user.rb
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable, :authentication_keys => [:username]
# Setup accessible (or protected) attributes for your model
#attr_accessor :username
validates_presence_of :firstname, :lastname, :unique => true
after_validation :createUsername
attr_accessible :username, :password, :password_confirmation, :remember_me, :firstname, :lastname
#attr_protected :password
def createUsername
# create username from first-last name
firstnamePart=self.firstname[0,1].downcase
lastnamePart=self.lastname[0,5].downcase
username=lastnamePart + firstnamePart
count=0
while username.length != 7
username=username + count.to_s
count +=1
end
self.username=username
#verify username uniqueness?
end
def self.find_first_by_auth_conditions(warden_conditions)
conditions = warden_conditions.dup
if login = conditions.delete(:login)
where(conditions).where(["lower(username) = :value OR lower(email) = :value", { :value => login.downcase }]).first
else
where(conditions).first
end
end
end
But when I try to sign up I get the following error. Text will have to do because I don't have enough 'reputation' to post images:
Sign up
1 error prohibited this user from being saved:
Email can't be blank
so, devise is still looking for an email during the signup/registration phase. I assume I have to edit devise's controller but that didn't get installed in the following commands
rails g devise:install
rails g devise user
rails g devise:views
So my rails app is probably using a default devise controller (/usr/local/rvm/gems/ruby-2.0.0-p195/gems/devise-1.5.4/app/controllers/devise/registrations_controller.rb) but I didn't see any references to 'email' in there.
How do I change devise to accept a username during signup/registration? Any help would greatly be appreciated.

Looks like you discard email at all in your login system. That's quite different from the Devise article which still have email attribute but use uername to login only.
Before fixing the issue, double think it. This is REALLY NOT A GOOD PRACTICE.
Most of the common Devise modules relying on email won't work, confirmable, password recovery, password confirmation etc. Do you really want that? What if an user lost password and recover? No way in this system!
There is no way you can inform users any important notice in your app, say a major TOS change.
You are inviting spam in your system.

Did you "Modify config/initializers/devise.rb to have:"
config.authentication_keys = [ :username ]

Related

Username can't be blank

So I enter a username, email, and password (+password confirmation) and it once I hit submit, it says: "Username can't be blank" and I clearly entered a username.
Here is my user.rb
class User < ActiveRecord::Base
validates :user_name, presence: true, length: { minimum: 4, maximum: 16
}
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
This is my registrations_controller:
class RegistrationsController < Devise::RegistrationsController
private
def sign_up_params
params.require(:user).permit(:email, :user_name, :password, :password_confirmation)
end
def account_update_params
params.require(:user).permit(:email, :user_name, :password, :password_confirmation, :current_password)
end
end
And the following is my new.html.erb
<h2>Sign up</h2>
<%= simple_form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
<%= f.error_notification %>
<div class="form-inputs">
<%= f.input :user_name %>
<%= f.input :email, required: true, autofocus: true %>
<%= f.input :password, required: true, hint: ("#{#minimum_password_length} characters minimum" if #minimum_password_length) %>
<%= f.input :password_confirmation, required: true %>
</div>
<div class="form-actions">
<%= f.button :submit, "Sign up" %>
</div>
<% end %>
<%= render "devise/shared/links" %>
add_username_to_users.rb (migration)
class AddUsernameToUsers < ActiveRecord::Migration
def change
add_column :users, :username, :string
add_index :users, :username, unique: true
end
end
If there's any other needed to be posted, to find more about the error, please comment. Also, note that I have devise gem (properly installed) and I'm using a cloud 9 editor. Thanks in advance!
When reviewing your code on c9.io there is definitely an issue with your migrations file where you have this:
class CreateAddUserNameToUsers < ActiveRecord::Migration
def change
create_table :add_user_name_to_users do |t|
t.string :user_name
t.timestamps null: false
end
end
end
That actually created a table called add_user_name_to_users, which you can find in your schema.rb file. So when you are creating your User with the sign up form, there is no user_name to validate on the User's table since it does not exist. You should remove your migration file 20171231162016_create_add_user_name_to_users.rb since it is not what you are looking to do or drop the table (here are some docs for changing your migrations).
Instead make sure you follow this devise documentation on adding a user_name as a sign up/ sign in parameter here.
You also have a models table that has the same fields as your users table (also assuming that this was put here by accident. Make sure you double check your migrations as it looks like that was one of the results of the error you are seeing.

Adding an "Account" that has many "Users"

I'm trying to implement a top-level account that can have one or more users. My app uses Devise for authentication so I would like to keep the Signup form as part of the User model.
I believe that I've got the models set up correctly, but I am having some trouble figuring out how the registration form should work.
Here's what I've got so far...
User.rb
class User < ActiveRecord::Base
rolify
# Include default devise modules. Others available are:
# :token_authenticatable, :confirmable,
# :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable,
:validatable, :omniauthable
# Setup accessible (or protected) attributes for your model
attr_accessible :role_ids, :as => :admin
attr_accessible :name, :email, :password, :password_confirmation, :remember_me, :account, :company
# Association with service accounts
has_many :service_accounts
# Association with company account
belongs_to :account
end
Account.rb
class Account < ActiveRecord::Base
attr_accessible :company
# Association with users
has_many :users, :dependent => :destroy
end
Registrations/new.html.erb
<h2>Sign up</h2>
<%= simple_form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => {:class => 'form-vertical' }) do |f| %>
<%= f.error_notification %>
<%= f.input :name, :autofocus => true, :placeholder => "Name" %>
<%= f.input :email, :placeholder => "Email Address" %>
<%= f.input :password, :placeholder => "Password" %>
<%= f.input :password_confirmation, :placeholder => "Confirm Password" %>
<%= f.button :submit, 'Sign up', :class => 'btn btn-large btn-primary' %>
<% end %>
<%= render "devise/shared/links" %>
Here is what I could use some help with
I want to add a "company" field to the above registration form. Company is a column in the Account table. When a new user registers, I'd like to create a new account for that user and set the company attribute to whatever they provided in the company field.
I'm having trouble with the code needed for both the form (to add the company field) and the controller (to create a new account and update the company field when a user submits the form).
Thanks!
I just finished working on an app with the similar requirements. The relationship looks good. It's likely that you'll want a before_save filter on User that gets the data to be associated with Account and adds/updates the association at the same time.
You should also look into accepts_nested_attributes_for, although I think it's designed to handle the more common situation where the parent (Account, in your case) is driving the creation of children (Users). I suspect you are creating a User with Devise, and either then or later creating the association, and adding associated data to the Account model.
A thing to think about is whether Company is a model of its own, in which case Users have Accounts, and Accounts have Companies. You might want to check out the recent RailsCasts on "multi-tenant" applications -- while they may not apply to your case, they did in mine, and there were a few things I hadn't thought about when managing and structuring my data when I first started the app.

Security - Filter attributes when creating user with Devise

My concern is about the possibility for a malicious user to add an extra parameter when he registers on my site. I'm using Rails 3.2.8 and Devise 2.1.2. I have a class User with an admin attribute
user.rb
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable, :recoverable, :trackable,
:validatable, :token_authenticatable, :lockable
end
schema.rb
create_table "users", :force => true do |t|
t.boolean "admin"
t.string "email", :null => false
t.string "encrypted_password", :default => "", :null => false
# other devise columns are not relevant for the question...
end
I'm using the provided Devise::RegistrationsController for the registration of a User, and a custom view with email and password:
<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f| %>
<%= devise_error_messages! %>
<div class="form_line">
<%= f.label :email, 'Email' %>
<%= f.email_field :email %>
</div>
<div class="form_line">
<%= f.label :password, 'Password' %>
<%= f.password_field :password %>
</div>
<div class="form_line">
<%= f.label :password_confirmation, 'Password confirmation' %>
<%= f.password_field :password_confirmation %>
</div>
<div class="actions">
<%= f.submit "Register", class: 'button' %>
</div>
<% end %>
I works perfectly, but a if a malicious user adds a parameter admin with value true in the post request, the user is created with the admin privileges.
Started POST "/users" for 127.0.0.1 at 2012-09-10 18:46:15 +0200
Processing by RegistrationsController#create as HTML
Parameters: { "user"=>{"email"=>"test#example.com", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]", "admin"=>"true" }
That's a security weakness in my site. Can I configure Devise to ignore extra parameters (I need only email and password) when a user registers (or updates its profile, I think I'll have the same problem) ? If it's not possible, is there another solution ?
You need to protect the admin field from mass-assignment. You should add this to your model:
attr_protected :admin
Read more here: http://apidock.com/rails/v3.2.8/ActiveModel/MassAssignmentSecurity/ClassMethods/attr_protected
devise provide the possibility to configure multiple models for authentication, which I think is a better way to get rid of the problem.
"Devise allows you to set up as many roles as you want. For example, you may have a User model and also want an Admin model with just authentication and timeoutable features.If so, just follow these steps:"
# Create a migration with the required fields
create_table :admins do |t|
t.string :email
t.string :encrypted_password
t.timestamps
end
# Inside your Admin model
devise :database_authenticatable, :timeoutable
# Inside your routes
devise_for :admins
# Inside your protected controller
before_filter :authenticate_admin!
# Inside your controllers and views
admin_signed_in?
current_admin
admin_session
If you and anyone with similar concern, take some time to check out the documentation of devise and see if you like it.devise documentation

rails extending devise registration form

I'm working on a system where it has 2 models, a user model, a school model.
I'm using devise registration for a regular user sign_up with 'role' as an additional field to indicate whether user is a regular user or a school_user.
For now there is a admin user who creates a new school record while user will register using devise/sign_up action. School doesn't have login information yet.
I can call schools/new action (as school signup link) to add a new school.
Instead I want to extend the devise registration for school which will sign up using 'new school signup link' as a new user (use email, password, role='school' for a user model) and other fields like name, address, etc. going into the regular schools table. This way school admin gets a login account as well.
How do I extend devise/registration form and create these 2 records?
I really appreciate few thoughts.
Override devise registrations route:
routes.rb :
devise_for :users, controllers: {registrations: 'registrations'}
Create controllers/registrations_controller.rb :
class RegistrationsController < Devise::RegistrationsController
after_filter :add_school
protected
def add_school
if resource.persisted? # user is created successfuly
# resource holds all your form data.
resource.schools.build do |school|
school.name = resource.school_name # form fields...
end
#school.save
end
end
end
To validate school fields add validations to your user.rb model
eg:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
validates :name,
presence: true,
length: {in: 1..50}
validates :school_name,
presence: true,
length: {in: 1..50}
end
Just for reference:
<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
<%= devise_error_messages! %>
<div><%= f.label :school_name %>
<br/>
<%= f.text_field :name, autofocus: true %></div>
<div><%= f.label :email %>
<br/>
<%= f.email_field :email %></div>
# ...
<div><%= f.label :school_name %>
<br/>
<%= f.text_field :school_name %></div>
<div><%= f.submit "Sign up" %></div>
<% end %>

Use both Account and User tables with Devise

I'm working with Rails 3.1.0 and Devise 1.4.8, and am new to both.
I want to allow multiple users for an account. The first user to sign up creates the account (probably for their company), then that user can add more users. A user is always linked to exactly one account.
I have Users and Accounts tables. Abbreviated models:
class User < ActiveRecord::Base
belongs_to :account
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:confirmable, :lockable, :timeoutable
attr_accessible :email, :password, :password_confirmation, :remember_me
end
class Account < ActiveRecord::Base
has_many :users, :dependent => :destroy
attr_accessible :name, :account_type
end
The question is, when the first user signs up, how do I create both the Account and User?
Do I need to modify/override the Devise registrations_controller,
something like the answer here? I couldn't figure out how to
create the Account then pass it to Devise for creating the User.
account_id is already in the User model. Should I add account_name
and account_type to the User model, and create a new the Account
record if account_id is not passed in? In this case, I'm trying to hide Account creation from Devise, but not sure that will work since I still need to prompt for account_name and account_type on the registration page.
Finally got this to work using nested attributes. As discussed in the comments on Kenton's answer, that example is reversed. If you want multiple users per account, you have to create the Account first, then the User--even if you only create one user to start with. Then you write your own Accounts controller and view, bypassing the Devise view. The Devise functionality for sending confirmation emails etc. still seems to work if you just create a user directly, i.e. that functionality must be part of automagical stuff in the Devise model; it doesn't require using the Devise controller.
Excerpts from the relevant files:
Models in app/models
class Account < ActiveRecord::Base
has_many :users, :inverse_of => :account, :dependent => :destroy
accepts_nested_attributes_for :users
attr_accessible :name, :users_attributes
end
class User < ActiveRecord::Base
belongs_to :account, :inverse_of => :users
validates :account, :presence => true
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:confirmable, :lockable, :timeoutable
attr_accessible :email, :password, :password_confirmation, :remember_me
end
spec/models/account_spec.rb RSpec model test
it "should create account AND user through accepts_nested_attributes_for" do
#AccountWithUser = { :name => "Test Account with User",
:users_attributes => [ { :email => "user#example.com",
:password => "testpass",
:password_confirmation => "testpass" } ] }
au = Account.create!(#AccountWithUser)
au.id.should_not be_nil
au.users[0].id.should_not be_nil
au.users[0].account.should == au
au.users[0].account_id.should == au.id
end
config/routes.rb
resources :accounts, :only => [:index, :new, :create, :destroy]
controllers/accounts_controller.rb
class AccountsController < ApplicationController
def new
#account = Account.new
#account.users.build # build a blank user or the child form won't display
end
def create
#account = Account.new(params[:account])
if #account.save
flash[:success] = "Account created"
redirect_to accounts_path
else
render 'new'
end
end
end
views/accounts/new.html.erb view
<h2>Create Account</h2>
<%= form_for(#account) do |f| %>
<%= render 'shared/error_messages', :object => f.object %>
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
<%= f.fields_for :users do |user_form| %>
<div class="field"><%= user_form.label :email %><br />
<%= user_form.email_field :email %></div>
<div class="field"><%= user_form.label :password %><br />
<%= user_form.password_field :password %></div>
<div class="field"><%= user_form.label :password_confirmation %><br />
<%= user_form.password_field :password_confirmation %></div>
<% end %>
<div class="actions">
<%= f.submit "Create account" %>
</div>
<% end %>
Rails is quite picky about plural vs. singular. Since we say Account has_many Users:
it expects users_attributes (not user_attributes) in the model and tests
it expects an array of hashes for the test, even if there is only one element in the array, hence the [] around the {user attributes}.
it expects #account.users.build in the controller. I was not able to get the f.object.build_users syntax to work directly in the view.
Couldn't you use something like what's covered in these RailsCasts?:
http://railscasts.com/episodes/196-nested-model-form-part-1
http://railscasts.com/episodes/197-nested-model-form-part-2
You could setup your models as described in those screencasts, using accepts_nested_attributes_for.
Then, your views/devise/registrations/new.html.erb form would be for :user like normal, and could include a nested form for :account.
So something like this within that default form:
<%= f.fields_for :account do |account_form| %>
<div>
<p>
<%= account_form.label :name, "Account Name", :class => "label" %>
<%= account_form.text_field :name, :class => "text_field" %>
<span class="description">(e.g., enter your account name here)</span>
</p>
</div>
<div>
<p>
<%= account_form.label :company, "Company Name", :class => "label" %>
<%= account_form.text_field :company, :class => "text_field" %>
</p>
</div>
<% end %>
This is sample code from an app I'm working on and I'm using the simple_form gem, so the helpers used in your app may be different, but you'll probably get the idea.
So when a user is created (when they register), they can also fill in the info that'll be used by the Account model to create their account once they hit the "Sign Up" button.
And you may want to set an attribute on that user like "admin" too...sounds like this initial user will be the "admin" user for the company, though other users may have access too.
Hope that helps.
The best solution would be to use a gem.
Easy way: milia gem
Subdomain way: apartment gem

Resources