I have an admin namespaced route for a custom dashboard in my app. I have at least 8 models working, with error free crud operations--all except one. I am using the Devise Gem for user management and a User model. I have the User model in the admin namespace and the only operation I can get to work is changing role and destroy, but I can't create a new user from the dashboard. When I try to create a new user; I get the error "You are already signed in.".
controllers/admin/users_controller.rb
class Admin::UsersController < ApplicationController
before_action :authenticate_user!
before_action :admin_only, :except => :show
def index
#users = User.all
end
def show
#user = User.find(params[:id])
unless current_user.admin?
unless #user == current_user
redirect_to root_path, :alert => 'Access denied.'
end
end
end
def create
#user = User.new
end
def update
#user = User.find(params[:id])
if #user.update_attributes(secure_params)
redirect_to users_path, :notice => 'User updated.'
else
redirect_to admin_users_path, :alert => 'Unable to update user.'
end
end
def destroy
#user = User.find(params[:id])
user.destroy
redirect_to admin_users_path, :notice => 'User deleted.'
end
private
def admin_only
unless current_user.admin?
redirect_to root_path, :alert => 'Access denied.'
end
end
def secure_params
params.require(:user).permit!
end
end
models/user.rb
class User < ApplicationRecord
enum role: [:user, :admin]
def set_default_role
self.role ||= :user
end
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
views/admin/users/new.html.erb
<%= form_for User.new do |f| %>
<div class="form-group">
<%= f.label :first_name %><br/>
<%= f.text_field :first_name, autofocus: true %>
</div>
<div class="form-group">
<%= f.label :last_name %><br/>
<%= f.text_field :last_name %>
</div>
<div class="form-group">
<%= f.label :email %><br/>
<%= f.email_field :email %>
</div>
<div class="form-group">
<%= f.label :password %>
<% if #minimum_password_length %>
<em>(<%= #minimum_password_length %> characters minimum)</em>
<% end %><br/>
<%= f.password_field :password, autocomplete: "off" %>
</div>
<div class="from-group">
<%= f.label :password_confirmation %><br/>
<%= f.password_field :password_confirmation, autocomplete: "off" %>
</div>
<div class="form-group" style="margin-top: 1em">
<%= f.submit 'Sign up', class: 'btn btn--primary type--uppercase inner-link' %>
</div>
<% end %>
views/admin/users/new.html.erb (other method)
<%= form_for([:admin, #user]) do |f| %>
This is how all my name spaced forms are setup but this gives me the error First argument in form cannot contain nil or be empty
routes.rb
...
namespace :admin do
get '', to: 'dashboard#index', as: '/'
resources :pages
resources :articles
resources :categories
resources :tags
devise_for :users
resources :users
resources :albums
resources :banners
resources :products do
resources :variations
end
end
...
using the registration#create action is the appropriate architecture for this?
Because that action is built on a different concept, that the user is not logged in and there are a series of checks for that.
views/admin/users/new.html.erb
you are using registration#new controller action, which does not have the #admin valorized.
def new
#admin = Admin.find(params[:admin_id])
....
end
That is why you get the error, also this approach is not correct.
User will need to register with the traditional devise router, then you will also have to create a nested router for admins to create new users.
this is your link to the admin user registration#new
Your current view/controller#action has the #admin = Admin.find(params[:id]) valorized and the view has the following link
link_to new_admin_user_path(:admin, :user)
the new_admin_user_path is for url /admin/:admin_id/users/sign_up(.:format) that you define in your nested routes.
Step 2) decide which controller#action to use
Do you want to use the standard users/registrations#create or use a new action for this?
I believe you should enhance users/registrations#create by generating the controllers actions in your app as described in devise guide
then in devise controller registrations
def new
#admin = Admin.new(:params[:admin_id]) if params[:admin_id].present?
end
your registrations#new and #create will still trigger errors, you will have to read how devise creates this users by reading their amazing sourcecode and rdoc documentation, modify the process accordingly so that admins can create users by using that action otherwise the less DRY alternative is creating a new controller#action and using it to call an existing User method or a method you will create in the User model to create users. In the end it a User is just an entry in your users database table. Just creating in a similar fashion to other users. As the admin is creating the temporary password, encryption/security issues are not anymore that important. The User will have to change the password anyway.
Related
When a user clicks on "Forgot My Password" on the login screen, they are redirected to a route '/password-reset'. Right now, I'm trying to understand how to right the form for entering your email to receive and SMS from Twilio with a code.
<div class="form-group", style="width:50%;">
<%= form_for #user, url: password_patch_path(current_user) do |f| %>
<div class="form-group">
<%= f.label :email %>
<%= f.email_field :email, class: "form-control" %>
</div>
<%= f.submit "Get Confirmation Code", class: "btn btn-default" %>
<% end %>
</div>
The issue I'm running into is that #user is nil, and I'm uncertain if the url is correct at the beginning of the form for. It makes sense to me that #user is nil because no one is logged in, so I'm not sure what that should be.
My routes are
get '/password-reset', :to => 'passwords#edit', as: :password_reset
post '/password-reset', :to => 'passwords#reset', as: :password_edit
patch '/password-confirmation', :to => 'passwords#update', as: :password_patch
and my passwords controller looks like
class PasswordsController < ApplicationController
before_action :authenticated?, only: [:edit, :update]
def reset
ConfirmationSender.send_confirmation_to(current_user)
redirect_to new_confirmation_path
end
def edit
#user = current_user
end
def update
if passwords_not_empty? && passwords_equal?
current_user.update(password_params)
redirect_to users_dashboard_path(current_user.username), success: "Password Updated"
session[:authenticated] = false
else
redirect_to password_edit_path(current_user.username), warning: "Error, please try again."
end
end
private
def password_params
params.require(:user).permit(:password, :password_confirmation)
end
def passwords_not_empty?
params[:user][:password].length > 0 && params[:user][:password_confirmation].length > 0
end
def passwords_equal?
params[:user][:password] == params[:user][:password_confirmation]
end
def authenticated?
render :file => "#{Rails.root}/public/404.html", :status => 404 unless session[:authenticated]
end
end
You are right that there will be no current_user if a user forgot his/her password. I would redesign as follows:
PasswordsContoller
class PasswordsController < ApplicationController
before_action :authenticated?, only: [:update]
def reset
#user = User.find_by(email: params[:user][:email])
if #user.present?
ConfirmationSender.send_confirmation_to(#user)
redirect_to new_confirmation_path
else
redirect_to password_reset_path, warning: "Email not found."
end
end
def edit
#user = User.new
end
...
end
Form
<div class="form-group", style="width:50%;">
<%= form_for #user, url: password_edit_path do |f| %>
<div class="form-group">
<%= f.label :email %>
<%= f.email_field :email, class: "form-control" %>
</div>
<%= f.submit "Get Confirmation Code", class: "btn btn-default" %>
<% end %>
</div>
The new edit method seeds the form with a blank user. The new reset method looks up the user by email and sends the token if the user is found. If not, it displays an email not found flash message and redirects back to the forgotten password form.
This also makes the form use the correct path for requesting a password confirmation.
I have done a phone number verification via Twilio, but I can't find a way how to implement a feature that sends pin code again (if user didn't received it) but also does it not more that 3 times (so users couldn't keep sending codes over and over again). Also, my code looks a bit anti-pattern, so feel free to suggest a better implementation.
When Devise User registers itself, I send him to create a Profile that belongs_to User. Profile holds all user info (and phone number). Here is the form:
<%= form_for #profile, remote: true do |f| %>
<%= f.label 'Your name' %><br />
<%= f.text_field :first_name, autofocus: true, class: 'form-control' %>
<%= f.label 'Phone number' %><br />
<%= f.text_field :phone, class: 'form-control' %>
</br>
<div id="hideAfterSubmit">
<%= f.submit 'Save', class: 'btn btn-lg btn-primary btn-block' %>
</div>
<% end %>
<div id="verify-pin">
<h3>Enter your PIN</h3>
<%= form_tag profiles_verify_path, remote: true do |f| %>
<div class="form-group">
<%= text_field_tag :pin %>
</div>
<%= submit_tag "Verify PIN", class: "btn btn-primary" %>
<% end %>
</div>
<div id="status-box" class="alert alert-success">
<p id="status-message">Status: Haven’t done anything yet</p>
</div>
#verify-pin and #status-box are display: none. I unhide them with responding create.js.erb.
Create action:
def create
if user_signed_in? && current_user.profile
redirect_to profile_path(current_user), notice: 'Jūs jau sukūrėte paskyrą'
else
#profile = Profile.new(profile_params)
#phone_number = params[:profile][:phone]
#profile.user_id = current_user.id
SmsTool.generate_pin
SmsTool.send_pin(phone_number: #phone_number)
if #profile.save
respond_to do |format|
format.js
end
else
render :new
end
end
end
So at this point profile been created, saved and pin code generated and sent to phone number that user just added.
SmsTool:
def self.generate_pin
##pin = rand(0000..9999).to_s.rjust(4, "0")
puts "#{##pin}, Generated"
end
def self.send_pin(phone_number:)
#client.messages.create(
from: ENV['TWILIO_PHONE_NUMBER'],
to: "+370#{phone_number}",
body: "Your pin is #{##pin}"
)
end
def self.verify(entered_pin)
puts "#{##pin}, pin #{entered_pin} entered"
if ##pin == entered_pin
Current.user.profile.update(verified: true)
else
return
end
end
And Profiles#verify :
def verify
SmsTool.verify(params[:pin])
#profile = current_user.profile
respond_to do |format|
format.js
end
if #profile.verified
redirect_to root_path, notice: 'Account created'
end
end
So what I dont like is SmsTool - as you see I use class variable - couldn't find another way. Also I created a separate Current module just to access Devise current_user object.. :
module Current
thread_mattr_accessor :user
end
ApplicationController:
around_action :set_current_user
def set_current_user
Current.user = current_user
yield
ensure
# to address the thread variable leak issues in Puma/Thin webserver
Current.user = nil
end
And as I mentioned above - I can't find a way how to implement a feature that sends pin code again (if user didn't received it).
And please - feel free to suggest elegant implementations.
p.s. this is my longest post yet. Sorry for that, but I think all info was needed to show you.
UPDATE:
So to resend pin was easy, I just added:
<div id="hiddenUnlessWrongPin">
<%= button_to "Re-send pin", action: "send_pin_again" %>
</div>
and action:
def send_pin_again
#phone_number = current_user.profile.phone
SmsTool.generate_pin
SmsTool.send_pin(phone_number: #phone_number)
end
But I still don't know how to stop sending pin if user already sent three of them. Only way I see is to make new row in db with integer value and increment it every time user sends pin. Is it the only way?
A good starting point would be to look at the Devise::Confirmable module which handles email confirmation. What I really like about it is that it models confirmations as a plain old resource.
I would try something similar but with a seperate model as it makes it really easy to add a time based limit.
class User < ApplicationRecord
has_one :profile
has_many :activations, through: :profiles
end
class Profile < ApplicationRecord
belongs_to :user
has_many :activations
end
# columns:
# - pin [int or string]
# - profile_id [int] - foreign_key
# - confirmed_at [datetime]
class Activation < ApplicationRecord
belongs_to :profile
has_one :user, through: :profile
delegate :phone_number, to: :profile
authenticate :resend_limit, if: :new_record?
authenticate :valid_pin, unless: :new_record?
attr_accessor :response_pin
after_initialize :set_random_pin!, if: :new_record?
def set_random_pin!
self.pin = rand(0000..9999).to_s.rjust(4, "0")
end
def resend_limit
if self.profile.activations.where(created_at: (1.day.ago..Time.now)).count >= 3
errors.add(:base, 'You have reached the maximum allow number of reminders!')
end
end
def valid_pin
unless response_pin.present? && response_pin == pin
errors.add(:response_pin, 'Incorrect pin number')
end
end
def send_sms!
// #todo add logic to send sms
end
end
Feel free to come up with a better name. Additionally this allows you to use plain old rails validations to handle the logic.
You can then CRUD it like any other resource:
devise_scope :user do
resources :activations, only: [:new, :create, :edit, :update]
end
class ActivationsController < ApplicationController
before_action :authenticate_user!
before_action :set_profile
before_action :set_activation, only: [:edit, :update]
# Form to resend a pin notification.
# GET /users/activations/new
def new
#activation = #profile.phone_authentication.new
end
# POST /users/activations/new
def create
#activation = #profile.phone_authentication.new
if #activation.save
#activation.send_sms!
redirect_to edit_user_phone_activations_path(#activation)
else
render :new
end
end
# Renders form where user enters the activation code
# GET /users/activations/:id/edit
def edit
end
# confirms the users entered the correct pin number.
# PATCH /users/activations/:id
def update
if #activation.update(update_params)
# cleans up
#profile.activations.where.not(id: #activation.id).destroy_all
redirect_to profile_path(#profile), success: 'Your account was activated'
else
render :edit
end
end
private
def update_params
params.require(:activation)
.permit(:response_pin)
.merge(confirmed_at: Time.now)
end
def set_profile
#profile = current_user.profile
end
def set_activation
#profile.activations.find(params[:id])
end
end
app/views/activations/new.html.erb:
<%= form_for(#activation) do |f| %>
<%= f.submit("Send activation to #{#activation.phone_number}") %>
<% end %>
No activation SMS? <%= link_ to "Resend", new_user_activation_path %>
app/views/activations/edit.html.erb:
<%= form_for(#activation) do |f| %>
<%= f.text_field :response_pin %>
<%= f.submit("Confirm") %>
<% end %>
I have a new application that the root user create another users, when i create the user with the model.
#user = User.new(:email => params[:email], :password => params[:password], :password_confirmation => params[:password_confirmation])
#user.save!
The users saves "correctly" on the table on "encrypted_password" the value and the others fields too, but when i try to login i have an error
"Invalid Email or Password" so when i read the record the password is.
encrypted_password: $2a$11$wFnpiA.l9HezNXfnAGkttuu2IGIXByETytLrEkdDsa8sBFrc8Bdmq
But i used another password the root password that actually works.
But on the table is :
encrypted_password: $2a$11$VKOAUk5pjILU1QHYmkpJSem9KKm70QJPS7Oj.nPM/pTuyu1tqZaQO
So, my model of the devise is not saving the correct encryption of the password.
My TestController:
class UsersController < ApplicationController
before_action :authenticate_user!
def index
#Users = User.all
end
def create
begin
#user = User.new(:email => params[:email], :password => params[:password_first], :password_confirmation => params[:password_confirmation])
#user.save!
flash[:notice] = "Usuario creado correctamente"
redirect_to action: 'index'
rescue Exception => e
flash[:alert] = "Error al crear al Usuario: " + e.message
redirect_to action: 'index'
end
end
end
What am i doing wrong?
Regards
BCrypt goes out of its way to scramble the password as much as possible to make it extremely difficult to reverse that hashing operation. For any given input string to BCrypt::Password.create there are around 680 trillion-trillion-trillion possible output strings (2128), so it's unlikely you'll ever get the same one twice:
hashes = 10.times.map { BCrypt::Password.create('test') }
# => [
# "$2a$10$vMrgjJHqvwnEKIs0fZ76pO3gbWL/0C3ExqK9HOpi/mHYu2.4GAO2K",
# "$2a$10$KxBOarDzRPHp7QF1GGqNnuplRs1B5rNVfp21IHx1/HzQ0YIcIkLRW",
# "$2a$10$emCdZAA.GU8GwQZkeJLfAuUTY2aEnhFmZ.GQAhDpJ.JGSh/m6s/k2",
# "$2a$10$6R6xmGyK7Tb1MKsQb00vpOJKwpi56aj98JLoBJhBN4vWSQb7zagQm",
# "$2a$10$r4qmb.C.vm88pL2nJK5TdOaWIboYaO6a1xHIRH.QDER6qYR6Ajvo.",
# "$2a$10$mlVWz4IHTgYHSf3tAgEgpenpDHtGWYev4EUENLs7hnLlm6ikPhUxy",
# "$2a$10$ixXdZZuc9rIVAozO8tyq5.wlsVOWBc6QWetNh3PvjPj2pGlqh.XOy",
# "$2a$10$zLzuevtOl.g4RbaHpdeTZ.k4qjE/1m4nh6gN4mhcIKQPSa5sBcG5u",
# "$2a$10$F/F71.DYEuzxS4W0w5m/a.IRpaVJxeh9sKUJ7DyQb5xU3SvFu1Ib.",
# "$2a$10$ILXg8R52ZtHHbQbT0FxSFOj8YNqpNLmrH.6FhM3RGMwIuBeP1YXHa" ]
This is alright though since the verification routine can handle checking password:
hashes.map { |h| BCrypt::Password.new(h) == 'test' }
# => [true, true, true, true, true, true, true, true, true, true]
They all match. It's important to note that verifying ten passwords does take a small but noticeable amount of time. This is what make BCrypt particularly well suited to password storage: Guessing is expensive so throwing huge dictionaries of words at a hash to see which match is extremely difficult. This is not the case with weaker hashes like MD5 or SHA1 where a million operations per second is completely feasible. BCrypt is deliberately about a half million times slower.
Start with a regular decent Rails crud setup and see if the issue does not solve itself.
# routes.rb
resources :users, only: [:create, :index]
class UsersController < ApplicationController
before_action :authenticate_user!
# GET /users
def index
#Users = User.all
end
# POST /users
def create
#user = User.new(user_params)
if #user.save
flash[:success] = "Usuario creado correctamente"
redirect_to action: :index
else
render :new
end
end
def user_params
params.require(:user)
.permit(:email, :password, :password_confirmation)
end
end
# app/views/users/_form.html.erb
<% form_for(#user || User.new) do |f| %>
<% if f.object.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(f.object.errors.count, "error") %> prohibited this article from being saved:</h2>
<ul>
<% f.object.errors.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :email %>
<%= f.email_field :email %>
</div>
<div class="field">
<%= f.label :password %>
<%= f.password_field :password %>
</div>
<div class="field">
<%= f.label :password_confirmation %>
<%= f.password_field :password_confirmation %>
</div>
</div>
<% end %>
# app/views/users/new.html.erb
<%= render partial: 'form' %>
You can embed the partial in your root view or the users index by:
<%= render partial: 'users/form' %>
Note that you should render and not redirect if the record is invalid. If you redirect the users input and any validation messages you want to show are gone.
I've searched everywhere for a solution but haven't come up with any.
The part that works: My app allows customers to create an account using a nested form. The data collected creates records in four models - accounts, users, accounts_users (because a user can be associated with many accounts), and profile (to store the user's fname, lname, phone, etc).
That part that doesn't work: Once logged in, I want the users to be able to add more users to their account using the form below. I don't receive any errors upon submit but I am brought back to the same form with no additional records created. Any help would be awesome!
Here is the nested form...
<%= form_for #user, :validate => true do |f| %>
<fieldset>
<%= f.fields_for :profile do |p| %>
<div class="field">
<%= p.label :first_name %>
<%= p.text_field :first_name %>
</div>
<div class="field">
<%= p.label :last_name %>
<%= p.text_field :last_name %>
</div>
<div class="field">
<%= p.label :phone %>
<%= p.text_field :phone %>
</div>
<% end %>
<div class="field">
<%= f.label :email %>
<%= f.text_field :email %>
</div>
<div class="actions">
<%= f.submit 'Create New User', :class => "btn btn-large btn-success" %>
<%= cancel %>
</div>
</fieldset>
The ApplicationController scopes everything to the current_account like so:
def current_account
#current_account ||= Account.find_by_subdomain(request.subdomain) if request.subdomain
end
The UsersController
def new
#user = User.new
#user.build_profile()
#current_account.accounts_users.build() #Edit2: This line was removed
respond_to do |format|
format.html # new.html.erb
format.json { render json: #user }
end
def create
#user = User.new(params[:user])
#user.accounts_users.build(:account_id => current_account.id) #Edit2: This line was added
if #user.save
# Send Email and show 'success' message
flash[:success] = 'An email has been sent to the user'
else
# Render form again
render 'new'
end
end
Models look like this:
class Account < ActiveRecord::Base
attr_accessible :name, :subdomain, :users_attributes
has_many :accounts_users
has_many :users, :through => :accounts_users
accepts_nested_attributes_for :users
end
class User < ActiveRecord::Base
attr_accessible :email, :password, :password_confirmation, :profile_attributes
has_many :accounts_users
has_many :accounts, :through => :accounts_users
has_one :profile
accepts_nested_attributes_for :profile
end
class AccountsUser < ActiveRecord::Base
belongs_to :account
belongs_to :user
end
class Profile < ActiveRecord::Base
belongs_to :user
attr_accessible :first_name, :last_name, :phone
end
Edit2: It turns out that I had required a password + password_comfirmation validation in the User model which prevented me from adding another user without these fields. I commented out these validations plus removed the line: current_account.accounts_users.build() in the 'new' action and added the line: #user.accounts_users.build(:account_id => current_account.id) in the 'create' action.
"I want the users to be able to add more users to their account using the form below." I assume you mean profiles (since your nested form is on profiles)?
If that's the case, I think your UsersController's create action isn't associating the profiles with users by using new.
Try this...
def new
#user = User.build
#profile = #user.profiles.build #build adds the profile to user's associated collection of profiles, but new doesn't
...
end
def create
#user = User.build(params[:user])
if #user.save
....
end
end
If you want the user to be associated with account, then you need to put the new and create actions in the AccountsController and do something similar to nest association of the users and profiles records.
Btw, the reason that it went back to new is because you render new at the end of the create, in case that's also part of the question. Hope that helps!
Right now I'm building a project management app in rails, here is some background info:
Right now i have 2 models, one is User and the other one is Client. Clients and Users have a one-to-one relationship (client -> has_one and user -> belongs_to which means that the foreign key it's in the users table)
So what I'm trying to do it's once you add a client you can actually add credentials (add an user) to that client, in order to do so all the clients are being displayed with a link next to that client's name meaning that you can actually create credentials for that client.
So in order to do that I'm using a helper the link to helper like this.
<%= link_to "Credentials",
{:controller => 'user', :action => 'new', :client_id => client.id} %>
Meaning that he url will be constructed like this:
http://localhost:3000/clients/2/user/new
By creating the user for the client with he ID of 2.
And then capturing the info into the controller like this:
#user = User.new(:client_id => params[:client_id])
EDIT: This is what i currently have in my View/Controller and Routes
I keep getting this error: No route matches "/clients//user" with {:method=>:post}
Routes
ActionController::Routing::Routes.draw do |map|
map.resources :users
map.resources :clients, :has_one => :user
map.connect ':controller/:action/:id'
map.connect ':controller/:action/:id.:format'
end
Controller
class UsersController < ApplicationController
before_filter :load_client
def new
#user = User.new
#client = Client.new
end
def load_client
#client = Client.find(params[:client_id])
end
def create
#user = User.new(params[:user])
#user.client_id = #client.id
if #user.save
flash[:notice] = "Credentials created"
render :new
else
flash[:error] = "Credentials created failed"
render :new
end
end
View
<% form_for #user, :url => client_user_url(#client) do |f| %>
<p>
<%= f.label :login, "Username" %>
<%= f.text_field :login %>
</p>
<p>
<%= f.label :password, "Password" %>
<%= f.password_field :password %>
</p>
<p>
<%= f.label :password_confirmation, "Password Confirmation" %>
<%= f.password_field :password_confirmation %>
</p>
<%= f.submit "Create", :disable_with => 'Please Wait...' %>
<% end %>
Your form tag is wrong, you are posting to /users without the :client_id.
Try this:
<% form_for #user, :url => {:controller => 'users', :action => 'new', :client_id => #client.id} do |f| >
Alternatively, you could use nested resources:
config/routes.rb
map.resources :clients do |clients|
clients.resources :users
end
Controller
class UsersController < ApplicationController
before_filter :load_client
def load_client
#client = Client.find(params[:client_id])
end
# Your stuff here
end
View
<% form_for [#client, #user] do |f| %>
I solved this by using nested attributes, by including the user model, when creating the client. And it works flawlessly.
In case any of you guys need more info here's the two screencasts that helped me come up with as solution:
http://railscasts.com/episodes/196-nested-model-form-part-1
http://railscasts.com/episodes/196-nested-model-form-part-2