I have a ruby on rails project that is using the devise gem for authentication. I'm trying to use a captcha form in the sign up page to prevent bots from creating thousands of dummy logins
I'm not interested in using the recaptcha gem that works with devise as I haven't had any luck setting it up correctly.
I trying to set it up using this as a reference https://recaptchainrubyonrails.blogspot.com/
Here's my sign up page
<h2>Sign up</h2>
<%= form_for(resource, as: resource_name, url:
registration_path(resource_name)) do |f| %>
<%= devise_error_messages! %>
<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: "off" %>
</div>
<div class="field">
<%= f.label :password_confirmation %><br />
<%= f.password_field :password_confirmation, autocomplete: "off" %>
</div>
<div class="g-recaptcha" data
sitekey="6LeeL5gUAAAAADbq0vt4d9biAs8oegkPx9ttUkKb"></div>
<div class="actions">
<%= f.submit "Sign up" %>
</div>
<% end %>
<br>
I do have my routes file point to a separate registrations controller instead of the one created within the devise registrations
devise_for :users, path: '', path_names: { sign_in: 'login', sign_out: 'logout' }, :controllers => {:registrations => "registrations"}
I can create a user regardless if I click on the captcha form. When I do click on it I get the parameter, g-recaptcha-response, in the create method that has a long text string such as
"g-recaptcha-response"=>"1234567890abcdefghi..."
I tried a create method that would look at that parameter and see if it is not nil. When it is nil I would like to back one page to the sign up page.
class RegistrationsController < Devise::RegistrationsController
def create
if not params[:g-recaptcha-response].present?
redirect_to :back
end
end
end
I can't even get to the parameter as it errors out saying
undefined local variable or method `recaptcha' for #<RegistrationsController:0x7219e5a0> Did you mean? catch"
I'm trying to capture this parameter, look at the value of g-recaptcha-response and somehow cancel the creation of a user if the response was nil or empty string
Alrighty, this is what I figured out that worked. Thanks to everyone who helped
#response = params[:"g-recaptcha-response"]
if #response == nil
flash[:notice] = "Please click on captcha form!"
redirect_back(fallback_location: new_user_registration_path)
else
flash[:notice] = "You signed up successfully!"
redirect_to root_path
end
Use 'g-recaptcha-response' instead of :g-recaptcha-response. You need to use string syntax because you cannot use - in symbol naming
Related
I have an application. I'm using ruby api as back end for the app. I'm using react native as front end. I'm using devise for authentication. We currently have the devise standard forget password page. I want to customize the page and make the page looks better. after my new implementation i get the email and when i click on the forget password link a blank tab opens up instead of the new page.
below is the current page.
I created view new.html.erb file in the below path
app/views/devise/passwords/new.html.erb
<h2>Change your passwordTest</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" %>
in the route.rb file, i have the below code
devise_for :users, controllers: { registrations: 'users/registrations', sessions: 'users/sessions', invitations: 'users/invitations', confirmations: 'users/confirmations', omniauth_callbacks: 'users/omniauth_callbacks', passwords: 'users/passwords'}
here is my passwords_controller.rb
app/controllers/users/passwords_controller.rb
class Users::PasswordsController < Devise::PasswordsController
end
here is the development.log logs
Started GET "/users/password/edit?reset_password_token=[FILTERED]" for ::1 at 2021-02-25 21:29:36 -0700
Processing by Users::PasswordsController#edit as HTML
Parameters: {"reset_password_token"=>"[FILTERED]"}
Completed 204 No Content in 1ms (ActiveRecord: 0.0ms)
The link you clicking is:
http://localhost:3000/users/password/edit?reset_password_token=xz_gdihd8NBvLp36xD-U
this redirects you to the edit method of Users::PasswordsController
the edit method by default refers the edit.html.erb instead of new.html.erb view. So you just need to customize this view.
You can check how this view is implemented on the devise github page: https://github.com/heartcombo/devise/blob/master/app/views/devise/passwords/edit.html.erb just remember to include the reset_password_token that is being passed in the query params
I am using devise gem for authentication in Rails. I want to create a admin dashboard that can add user into User model. I am able to render form into admin#index view, but I am not able to insert data in user model.
routes.rb
# when i use post method in this route i get routing error
get '/admin' => "admin#index", as: :create_user
Admin index.html.erb
<%= form_for User.new, url: create_user_path do |f| %>
<div class="log-in-form">
<h2 class="login-header text-center">Sign up</h2>
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</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 %>
admin_controller.rb
def index
end
def new
#user = User.new
end
def create
#user = User.new(user_params)
if User.save
redirect_to root_path
else
render :new
end
end
private
def user_params
params.require(:user).permit(:name,:password)
end
Sir, you badly need to read and understand this: https://guides.rubyonrails.org/routing.html
Answer:
You have defined the route as a get method yourself and you are expecting to post to it. Do you see something very odd here?
As a beginner, try not to deviate from basic CRUD routes that rails provides for you. In your case, do this in routes.rb:
resources :users
# OR:
# resources :users, only: %i[index show create update destroy]
# any names of CRUD functions that you probably will use (to avoid extra routes)
Now, in your html file change url: create_user_path to url: users_path.
BTW, at least make it a habit to check your routes in terminal like in your case: rails routes | grep user will give you the rails routes having word user in them.
It should work now.
Add following line in your config/routes.rb
post '/admin' => 'admin#index'
I used devise inthe login system.I want create two new html under the registrations, which is two different roles' pages. And I want to go to that page by choosing roles in registrations/new, which is the original signup file. What should I do next?
my files order in the view/user/registrations: enter image description here.
edit.html.erb
new.html.erb
new_librarian.html.erb
new_student.html.erb
I want to signup in the new two pages through registrations/new
<!-- what i changed in the registrations/new -->
<h2>Sign up</h2>
<p>Sign up as a Student<%= link_to 'Student', users_registrations_new_student_path, :method => 'get' %></p>
<p>Sign up as a Librarian<%= link_to 'Librarian', users_registrations_new_librarian_path, :method => 'get'%></p>
<!-- what is in the registrations/new_student -->
<h2>Sign up</h2>
<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
<%= render "devise/shared/error_messages", adminv: resource %>
<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="field">
<%= f.label :select_role %><br />
<%= f.collection_select :role, User::ROLES, :to_s, :humanize %>
</div>
<div class="actions">
<%= f.submit "Sign up" %>
</div>
<% end %>
<%= render "devise/shared/links" %>
<!-- what i changed in the routes.rb -->
get 'users/registrations/new_librarian'
get 'users/registrations/new_student'
Could not find devise mapping for path "/users/registrations/new_student". This may happen for two reasons:
1) You forgot to wrap your route inside the scope block. For example: devise_scope :user do get "/some/route" => "some_devise_controller" end
2) You are testing a Devise controller bypassing the router. If so, you can explicitly tell Devise which mapping to use: #request.env["devise.mapping"] = Devise.mappings[:user]
You can achieve this by overriding the devise registration controller. In your overrided registration controller, in the new method once the user is saved,
#app/config/routes.rb
devise_for :users, :controllers => {:registrations => "registrations"}
# app/controllers/registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
def new
if #user.save!
if #user.role == "librarian"
redirect_to librarian_profile_path
elsif #user.role == "student"
redirect_to student_profile_path
end
end
end
end
You can define librarian_profile_path and student_profile_path in your routes
#app/config/routes.rb
get 'librarian/new' => 'librarian_profile#new', :as => 'librarian_profile'
get 'student/new' => 'student_profile#new', :as => 'student_profile'
I'm trying to put Forgot password field with the sign in page, but if user is not registered (and not in apps database), then it redirects to the original devise Forgot password page with errors (http://localhost:3000/users/password). How do I make the errors appear in the same page as the sign in page (http://localhost:3000/users/sign_in)?
In app/views/devise/sessions/new.html.erb file
<%= simple_form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
<%= f.email_field :email, required: false, autofocus: true, placeholder: "Username" %>
<%= f.password_field :password, required: false, placeholder: "Password" %>
<%= f.button :submit, "Sign In", class: "btn btn-success btn-sm" %>
<div class="remember-forgot">
<div class="row">
<div class="col-md-6">
<%= f.input :remember_me, as: :boolean if devise_mapping.rememberable? %>
</div>
<div class="col-md-6 forgot-pass-content">
Forgot Password
</div>
</div>
</div>
<% end %>
<!-- where reset is -->
<div class="pass-reset">
<%= simple_form_for(resource, as: resource_name, url: password_path(resource_name), namespace: 'forgot', html: { method: :post }) do |f| %>
<%= f.error_notification %>
<label>Enter the email you signed up with</label>
<%= f.email_field :email, required: true, autofocus: true, placeholder: "Email" %>
<%= f.submit "Submit", class: "pass-reset-submit btn btn-success btn-sm" %>
<% end %>
</div>
So there's a javascript link where an input field will show up if a user forgets their sign in credentials.
apparently two forms for same object having same fields should not be one page, one should stay with the new page. but still i have tried your question, and following things needs to be done
1) I have to override the passwords controller for devise under user scope.
class Users::PasswordsController < Devise::PasswordsController
# GET /resource/password/new
def new
super
end
# POST /resource/password
def create
self.resource = resource_class.send_reset_password_instructions(resource_params)
if successfully_sent?(resource)
flash[:notice] = "sent password"
redirect_to :root
else
render "devise/sessions/new"
end
end
# GET /resource/password/edit?reset_password_token=abcdef
def edit
super
end
# PUT /resource/password
def update
super
end
protected
def after_resetting_password_path_for(resource)
super(resource)
end
# The path used after sending reset password instructions
def after_sending_reset_password_instructions_path_for(resource_name)
super(resource_name)
end
end
then my devise/sessions/new page will look like this(you can add the logic of showing form only when one clicks he forget password button. that should be simple. just add hide class and on click remove hide class.)
Log in
<%= 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 %>
</div>
<div class="field">
<%= f.label :password %><br />
<%= f.password_field :password, autocomplete: "off" %>
</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 %>
#assuming that devise_mapping has recoverable? option. you can also keep the below form in if condition
<h2>Forgot your password?</h2>
<%= form_for(resource, as: resource_name, url: password_path(resource_name), namespace: "forget", html: { method: :post }) do |f| %>
<%= devise_error_messages! %>
<div class="field">
<%= f.label :email %><br />
<%= f.email_field :email, autofocus: true %>
</div>
<div class="actions">
<%= f.submit "Send me reset password instructions" %>
</div>
<% end %>
need to tell the routes to use my my passwords controller.
devise_for :users, controllers: {
passwords: 'users/passwords'
}
These things will result in showing the errors under the user sign in form but the path will remain http://localhost:3000/users/password. why because we are rendering the page and not redirecting. render just show the views without going to the controller action. now even if one tries to send the errors messages to the sessions controller somehow(after overriding that controller as well) somehow like this
redirect_to new_user_session_path, :messages => resource.errors
still that wont help, why because in session#new we are re initializing the resource as it is new action and all the errors would be gone.
I'm not sure if this is satisfactory to you or if this is not even close to your requirements. i tried to cover all things. i would be glad if some credible or official sources will provide even better response. that would definitely increase my knowledge as well.
I have a rails app using devise for registrations and rolify for roles. I would like to have an index page that has edit links for each of the users that can be accessed by an admin. This edit page should also work without having to use a password. Right now the edit_user_path goes to the edit page of the current user, which is not what i want.
What is the best way to implement this sort of sitation? i've read a few of the posts on here about this but none seem to give me what i want.
Please point me in the right direction!
EDITED
I'm attempting to do it this way, still running into "Current password can't be blank"
From Users_controller:
def update
#user = User.find(params[:id])
if params[:user][:password].blank?
params[:user].delete(:password)
params[:user].delete(:password_confirmation)
end
if #user.update_attributes(user_params)
redirect_to users_path, :notice => "User updated."
else
redirect_to users_path, :alert => "Unable to update user."
end
end
And in my views i have an edit.html.erb file that is rendering the following form:
<div class="panel-body">
<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :method => :put }) do |f| %>
<%= devise_error_messages! %>
<div class="form-group">
<%= f.label :email %>
<%= f.email_field :email, class: "form-control", :autofocus => true %>
</div>
<div class="form-group">
<%= f.label :username %>
<%= f.text_field :username, class: "form-control" %>
</div>
<div class="form-group">
<%= f.label :firstname %>
<%= f.text_field :firstname, class: "form-control" %>
</div>
<div class="form-group">
<%= f.label :lastname %>
<%= f.text_field :lastname, class: "form-control" %>
</div>
<div class="form-group">
<%= f.label :city %>
<%= f.text_field :city, class: "form-control" %>
</div>
<div class="form-group">
<%= f.label :zip %>
<%= f.text_field :zip, class: "form-control" %>
</div>
<div class="form-group">
<%= f.label :state %>
<%= f.text_field :state, class: "form-control" %>
</div>
<div class="form-group">
<%= f.label :country %>
<%= f.text_field :country, class: "form-control" %>
</div>
<div class="form-group">
<%= f.submit "Update", class: "btn btn-primary" %>
</div>
<% end %>
</div>
and finally in my routes.rb file i have this line to render the edit page. I can get the edit page to show up but entering info and then hitting update just shoots me to /users with the error "Current Password can't be blank"
get 'pressroom/accounts/:id/edit' => 'users#edit', :as => :admin_edit_user
Devise doesn't come with any sort of Admin interface. If you are the only administrator and don't mind a little crudeness - there is always the console and/or scaffolding. You could create a UserController which inherits from ApplicationController and execute basic view, edit methods in the same controller. By placing the appropriate new.html.erb, edit.html.erb etc files in the User Views folder, adding/editing/deleting Users should work no differently as any other CRUD, as Devise's User is another model like any. Use a scaffold on the user and you could get what you are looking for.
There are also a lot of good gems that make setting up admin interfaces a cinch: https://github.com/gregbell/active_admin Active Admin, https://github.com/sferik/rails_admin Rails Admin and I'm sure there are a bunch more out there.
It looks like i got it working by adding:
<div class="panel-body">
<% #user = User.find(params[:id]) %>
<%= form_for(#user) do |f| %>
to the top of my _form.html.erb file
Thanks for the help everyone!
If the only thing you need is for the admin to EDIT an existing user, you can have the edit, show and update actions in a separate UsersController (and leave new and create actions up to devise). That way you can move that #user = User.find(params[:id] logic out of your form, into the controller, as #Saurabh Lodha mentioned.
I just thought one thing was missing from the answers though: Make sure to also edit your routes.rb. Use a path prefix so your routing doesn't get confusing, kind of like this:
devise_for :users, :path_prefix => 'my'
resources :users
this means that when you call edit on a current_user, it will go to my/users/edit, and when you call edit on any selected user from your user list in the admin panel, it will take you to users/user_id/edit.
I hope that clarified it a bit more! good luck! :)