Rails allowing user signup only if invite code entered is valid? - ruby-on-rails

So I'm creating a simple app for myself and a small group of people. I would like to restrict access to people that I hand-generate codes for by typing them in myself.
User sign up, log in, logout works, but I don't just want anyone to be able to be register.
TL;DR
User can sign up but how do I go about setting up a hand-generated code
Should be some way to invalidate that code after sign up and see who
the code is associated with
How can I do this in rails?
I know the user_model would have to add some sort of field to it, the view/form for it as well, and it would have to be validated (by the controller?). Just stuck.
My thought process is as follows (what i've thought so far)
All the invite codes should be kept as an array in a file in the
rails app?
I will have to add a migration that adds invite_code to the model/db/view form
The controller should validate the presence of the invite code in the view form?
user/new.html.erb
<%= form_for(#user) do |f| %>
<%= render 'shared/error_messages' %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.label :email %>
<%= f.email_field :email %>
<%= f.label :password %>
<%= f.password_field :password %>
<%= f.label :password_confirmation, "Retype Password" %>
<%= f.password_field :password_confirmation %>
<%= f.submit "Create My Account" %>
<% end %>
users_controller.rb
class UsersController < ApplicationController
def show
#user = User.find(params[:id])
end
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
log_in #user
flash[:success] = "Welcome!"
redirect_to #user
# Handle a successful save.
else
render 'new'
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
end
user.rb model
class User < ActiveRecord::Base
# links this to the question.rb model
has_many :questions, dependent: :destroy
attr_accessor :remember_token
before_save { email.downcase! }
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
has_secure_password
validates :password, length: { minimum: 6 }
# Returns the hash digest of the given string.
def User.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
# returns a random token
def User.new_token
SecureRandom.urlsafe_base64
end
# remembers a user in the database for use in persistent sessions
def remember
self.remember_token = User.new_token
update_attribute(:remember_digest, User.digest(remember_token))
end
# returns true if the given token matches the digest
def authenticated?(remember_token)
return false if remember_digest.nil?
BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
# Forgets a user.
def forget
update_attribute(:remember_digest, nil)
end
end

Well, In that case, you can have a model say Token created as below:
def self.up
create_table :tokens do |t
t.string :code, null: false
t.boolean :used, default: false
t.timestamps
end
end
And a migration in your User table for token_id
You can generate new code like, Token.generate_new_code
def self.generate_new_code
token = Token.new(code: Digest::SHA1.hexdigest Time.now.to_s)
token.code if token.save
end
And you can understand the rest i.e. accept code from registration form, validate for unused code and set it true and token_id if the registration is successful.

Related

Updating a single attribute for a user

I have a user table, after the user is created I want to edit one attribute using the below code. This is my user edit view:
<h1>Please select below</h1>
<%= form_for #user do |f| %>
<div class="form-group">
<%= f.label :extra_activity %>
<%= f.select(:extra_activity, [['P_Act', 1],['Ph_Act', 2], ['C_Act', 3]], class: 'form-control', required: true) %></br>
</div>
<%= f.submit 'Submit', class: 'btn btn-primary btn-lg' %>
<% end %>
In my user controller I have the following methods for edit and update :
def edit
#user = User.find(params[:id])
#users = User.all
#user_id = params[:user_id]
end
def update
#user = User.find(params[:id])
if #user.update_attributes(user_params)
redirect_to new_user_activity_path(#user)
else
redirect_to home_path
end
end
user_params are listed below:
def user_params
params.require(:user).permit(:first_name, :surname, :previous_award, :chosen_award, :email, :password, :password_confirmation, :extra_activity)
end
When i initially create the user, :extra_activity is set to 0. When the user clicks submit on the edit form, nothing happens, the user is redirected to home_path. I just need to update the user's :extra_activity attribute to whatever they select in the form. Can anybody suggest where I am going wrong here? Not sure how to save the selected number, as the updated value for :extra_activity
UPDATE:
class User < ActiveRecord::Base
has_many :activities, dependent: :destroy
has_many :weeks, dependent: :destroy
authenticates_with_sorcery!
validates :password, length: { minimum: 3 }
validates :password, confirmation: true
validates :email, uniqueness: true, email_format: { message: 'has invalid format' }
end
After reading your comments and checking the update, i guess the solution is to add:
validates :password, length: { minimum: 3 }, if: :password
The password is most likely stored in hashed form and not in password column. So if you reload the user the attribute is not set.
The validation should be: IF the password is set, then make sure that it is at least of length 3 (which is pretty short).
You should make sure that the password that is stored is not changed when you update the user through this controller.

Why signing in doesn't work?

When I'm trying to sign in with an actual user information who's already in database, an error's being rendered "Invalid email/password confirmation.", however it's not supposed.
Why is that?
controllers/sessions_controller.rb
class SessionsController < ApplicationController
def new
end
def create
user = User.authenticate(params[:session][:email], params[:session][:password])
if user.nil?
flash.now[:error] = "Invalid email/password confirmation."
render :new
else
sign_in user
redirect_to user
end
end
def destroy
sign_out
redirect_to signin_path
end
end
app/views/sessions/new.html
<h1>Sign in</h1>
<% if flash[:error] %>
<p><%= flash[:error] %></p>
<% end %>
<%= simple_form_for(:session, url: sessions_path) do |f| %>
<%= f.input :email %>
<%= f.input :password%>
<%= f.button :submit %>
<% end %>
<p>New user? <%= link_to "Sign up now!", signup_path %></p>
app/helpers/sessions_helper.rb
module SessionsHelper
def sign_in(user)
session[:user_id] = user.id
self.current_user = user
end
def current_user=(user)
#current_user = user
end
def current_user
if session[:user_id]
#current_user ||= User.find(session[:user_id])
end
end
def signed_in?
!current_user.nil?
end
def sign_out
session[:user_id] = nil
self.current_user = nil
end
def current_user?(user)
user == current_user
end
def deny_access
redirect_to signin_path, notice: "Please sign in to access this page."
end
end
models/user.rb
class User < ActiveRecord::Base
has_many :tasks
attr_accessor :password, :salt, :encrypted_password
EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :first_name, presence: true,
length: {maximum: 20}
validates :last_name, presence: true,
length: {maximum: 40}
validates :email, presence: true,
format: {with: EMAIL_REGEX},
uniqueness: {case_sensitive: false}
validates :password, presence: true,
confirmation: true,
length: {within: 6..40}
before_save :encrypt_password
def has_password?(submitted_password)
encrypted_password == encrypt(submitted_password)
end
def self.authenticate(email, submitted_password)
user = find_by_email(email)
return nil if user.nil?
return user if user.has_password?(submitted_password)
end
private
def encrypt_password
self.salt = Digest::SHA2.hexdigest("#{Time.now.utc}--#{password}")
self.encrypted_password = encrypt(password)
end
def encrypt(pass)
Digest::SHA2.hexdigest("#{self.salt}--#{pass}")
end
end
Can somebody help?
From your code, what I see is there is a confirmation :true for password but you don't have password_confirmation field in your form. Try including it
<%= f.input :password_confirmation %>
and also include it as attr_accessor in User model.
If you don't want a confirmation field for password, then remove confirmation: true for password in validations.
validates :password, presence: true, length: {within: 6..40}
Try removing :salt and :encrypted_password from the following line in models/user.rb
attr_accessor :password, :salt, :encrypted_password
change to:
attr_accessor :password
Those values are stored in the database so chances are you're overwriting the methods created my active-record for those two fields and they're never being initialised.

Rails Sorcery update attributes of model without password

I use sorcery for user authentication in a rails 4.1 application. Everything works fine. But when I try to update specific attributes of the user model (which is authenticated by sorcery), I get an error that the password is blank and is too short.
Here's a snippet from the console
> user = User.last
=> # I get the user
> user.update(about_me: "I'm a user")
=> false
> user.update(about_me: "I'm a user", password: "secret")
=> true
Here's my model code
app/models/user.rb
class User < ActiveRecord::Base
authenticates_with_sorcery!
validates :password, presence: true, length: { minimum: 6 }
.....
end
My controller code
app/controllers/users_controller.rb
class UsersController < ApplicationController
.....
def edit
#user = User.find(params[:id])
end
def update
#user = User.find(params[:id])
if #user.update(user_params)
redirect_to #user
flash[:notice] = "Profile successfully updated"
else
render 'edit'
end
end
private
def user_params
params.require(:user).permit(:username, :name, :email, :password, :about_me)
end
end
And my update form
app/views/users/edit.html.erb
<%= form_for #user, method: :put do |f| %>
<% if #user.errors.any? %>
<div class="alert">
<p><%= pluralize(#user.errors.count, 'error') %></p>
<ul>
<% #user.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<%= f.text_field :username, placeholder: 'Username' %>
<%= f.text_field :name, placeholder: 'Name' %>
<%= f.email_field :email, placeholder: 'Email' %>
<%= f.text_area :about_me, placeholder: 'About me' %>
<%= f.password_field :password, placeholder: 'Password' %>
<%= f.submit 'Save Changes', class: 'button' %>
<% end %>
If I remove the password field from the form, I get errors about the password being blank and about it's length.
Is this something to do with sorcery or is it something I'm missing with rails itself?
Is there a better way to update let's say only the email field without affecting anything else?
class User < ActiveRecord::Base
authenticates_with_sorcery!
validates :password, presence: true, length: { minimum: 6 }, if: :new_user?
private
def new_user?
new_record?
end
end
The validation will be checked only if it's a new_record, for which we have added our own private validation method new_user?. This function will return true during your normal signups/registrations. Hence, at those signups only the password validation will be needed.
During the edit, off course the user will be an existing user / new_record? will return false. Hence the validation for password will be skipped.
2nd way:
class User < ActiveRecord::Base
attr_accessor :skip_password
validates :password, presence: true, length: { minimum: 6 }, unless: :skip_password
end
#users_controller.rb
def update
#user = User.find(params[:id])
#user.skip_password = true
if #user.update(user_params)
redirect_to #user
else
render 'edit'
end
end
Here we have added our own custom attr_accessor skip_password. If the skip_password value is set to true, then during edit/update the password validation will be skipped.
I hope both of those ways will help you :)
If someone looks for this topic in future, it is possible to use changes map of ActiveRecord model:
class User < ActiveRecord::Base
authenticates_with_sorcery!
validates :password, presence: true, length: { minimum: 6 }, if: -> {new_record? || changes[:crypted_password]}
.....
end
where :crypted_password is the value of sorcery_config.crypted_password_attribute_name.
Also currently such condition of validates pointed in Simple Password Authentication sorcery wiki article.

rails 3.2 model convert to rails 4

I try to change my rails-3.2-model to rails 4, but I dont understand it.
Maybe you can help me to change it.
3.2:
class User < ActiveRecord::Base
attr_accessible :email, :username, :password, :password_confirmation
attr_accessor :password
before_save :encrypt_password
validates_confirmation_of :password
validates_presence_of :password, :on => :create
validates_presence_of :email, :on => :create
validates_presence_of :username, :on => :create
validates_uniqueness_of :email
validates_uniqueness_of :username
def self.authenticate(email, password)
user = find_by_email(email)
if user && user.password_hash == BCrypt::Engine.hash_secret(password, user.password_salt)
user
else
nil
end
end
def encrypt_password
if password.present?
self.password_salt = BCrypt::Engine.generate_salt
self.password_hash = BCrypt::Engine.hash_secret(password, password_salt)
end
end
end
4.0.4:
class User < ActiveRecord::Base
validates :name, presence: true, uniqueness: {case_sensitive: true}, length: {minimum: 3, too_short: "must have at least %{count} characters"}
validates :email, presence: true, uniqueness: {case_sensitive: true}
validates :password_hash
end
I tried to get rid off the attr_accessible and attr_accessor, but I don't know how.
attr_accessor :password and attr_accessible [...] :password_confirmation are not stored in the database, so how can I use it in my view?
EDIT:
View:
<p>Sign Up</p>
<%= form_for #user, :as => :user, :url => auth_sign_up_path, :html => {:class => 'navbar-form', :role => 'login'} do |user_form_builder| %>
<p>
<%= user_form_builder.label 'name:' %><br/>
<%= user_form_builder.text_field :name %>
<%= show_field_error(#user, :name) %>
</p>
<p>
<%= user_form_builder.label 'email:' %><br/>
<%= user_form_builder.text_field :email %>
<%= show_field_error(#user, :email) %>
</p>
<p>
<%= user_form_builder.label 'password:' %><br/>
<%= user_form_builder.password_field :password %>
<%= show_field_error(#user, :password) %>
</p>
<p>
<%= user_form_builder.label 'password confirmation:' %><br/>
<%= user_form_builder.password_field :password_confirmation %>
<%= show_field_error(#user, :password_confirmation) %>
</p>
<p>
<%= user_form_builder.submit 'Sign Up' %>
<%= user_form_builder.submit 'Clear Form', :type => 'reset' %>
</p>
<% end %>
Controller:
def sign_up
#user = User.new
end
def register
#user = User.new(user_params)
if #user.valid?
#user.save
session[:user_id] = #user.id
flash[:notice] = 'Welcome.'
redirect_to :root
else
render :action => "sign_up"
end
end
private
def user_params
params.require(:user).permit(:email, :name, :password, :password_confirmation)
end
Model:
require 'bcrypt'
class User < ActiveRecord::Base
attr_accessor :name, :email, :password, :password_confirmation
before_save :encrypt_password
after_save :clear_password
validates :name, presence: true, uniqueness: {case_sensitive: true}, length: {minimum: 3, too_short: "must have at least %{count} characters"}
validates :email, presence: true, uniqueness: {case_sensitive: true}
validates :password, presence: true, length: {minimum: 8, too_short: "must have at least %{count} characters"}, :confirmation => true #password_confirmation attr
def initialize(attributes = {})
super # must allow the active record to initialize!
attributes.each do |name, value|
send("#{name}=", value)
end
end
def self.authenticate_by_email(email, password)
user = find_by_email(email)
if user && user.password_hash == BCrypt::Engine.hash_secret(password, user.password_salt)
user
else
nil
end
end
def self.authenticate_by_name(name, password)
user = find_by_username(name)
if user && user.password_hash == BCrypt::Engine.hash_secret(password, user.password_salt)
user
else
nil
end
end
def encrypt_password
if password.present?
self.password_salt = BCrypt::Engine.generate_salt
self.password_hash = BCrypt::Engine.hash_secret(password, password_salt)
end
end
def clear_password
self.password = nil
end
end
Migration:
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :name
t.string :email
t.string :password_hash
t.string :password_salt
t.string :cal_owner, :array => true, :default => '{}'
t.string :cal_joined, :array => true, :default => '{}'
t.timestamps
end
end
end
Routes:
Calendar::Application.routes.draw do
# You can have the root of your site routed with "root"
root 'welcome#index'
get "auth/sign_up" => "auth#sign_up"
get "auth/sign_in" => "auth#sign_in"
get "auth/sign_out" => "auth#sign_out"
get "auth/settings"
get "auth/pwd_reset"
get "welcome/index"
post "auth/sign_in" => "auth#login"
post "auth/sign_up" => "auth#register"
end
I used a tutorial, but I didnt know why the author add this:
def initialize(attributes = {})
super # must allow the active record to initialize!
attributes.each do |name, value|
send("#{name}=", value)
end
end
The author wrote:
For each key-value pair (hash) we assign the value to the attribute by
calling the "send" function (all method calls in Ruby are actually
messages.)
Important:
We don't actually need to do this for the User class because the
constructor provided by Rails will allow us to do a "mass assign" from
a hash as long as the fields that we are assigning have been
designated as "attr_accessible", which they have. However, there are
cases when one wants to initialize several fields (such as ID's in a
many-to-many table) that are not intended to be accessible to a view
but instead are designated with an "attr_accessor" instead. The above
function is a simple way of providing safe mass assign capability for
internal constructors.
From what I now understand, your user table has effectively 4 columns in addition to the ID, created at etc.:
name
email
password_hash
password_salt
On your view you have fields:
name
email
password
password_confirmation
Your controller looks correct. The user_params method is whitelisting the 4 fields from the view and letting them be passed through to the User model for creating a new user.
In your model you need to make 2 changes.
Firstly you should remove name and email from the attr_accessor line:
attr_accessor :password, :password_confirmation
You only need password and password_confirmation. The reason for this is since name and email are columns on the database, Rails will automatically give you the getter and setter methods for these attributes. attr_accessor then saves you having to explicitly write getter and setter methods for password and password_confirmation and lets them be populated automatically on creating a new user with the values that come through from the view.
Secondly you should remove the initialize method. User inherits from ActiveRecord::Base and will be able to build new user records quite happily without the need for a constructor.
The author of the tutorial included the constructor for doing mass-assignment. However in Rails 4 this has been changed to use strong parameters so that it is now the responsibility of the controller to state which parameters can be passed to the model. You do this correctly in your user_params method in the controller.
I hope this helps.

ActiveModel::ForbiddenAttributesError using update_attributes having created params hash myself

I’m trying to edit/update a model record using simple_form, but the form is not going to directly change a model field. Instead, I offer a couple of check_box_tag fields that tell update what fields need changed. As a result, update is not receiving a params[:device] hash that I can use to update the attributes. I am attempting to create this hash, but am getting ForbiddenAttributesError when I issue the #device.update_attributes(params[:device]).
I believe my strong parameters list is correct. If I allow one model field (name) to be processed in the edit view, I receive the expected params[:device] hash and everything works. If I disable that field, because I don’t want it to be changed, then I need to create that hash myself and I receive the error. When I look at the hash I created, it looks to me as equivalent to the one passed by the view. I don’t understand why it is failing.
Environment is Ruby 2.0.0, Rails 4.1 on Windows 8.1 with RubyMine 6.3.
The form is: <... needs correct formatting once working ...>
<%= simple_form_for #device do |f| %>
<legend><%= controller.action_name.capitalize %> Device:</legend>
<%= f.input :name, disabled: true %>
<%= check_box_tag(:is_admin, 0, #device.admin?) %>
<%= label_tag(:is_admin, "Make admin?") %>
<%= check_box_tag(:chg_pwd) %>
<%= label_tag(:chg_pwd, "Change password?") %>
<%= f.button :submit %>
<% end %>
The params[:device] that I receive when I sent f.input :name, disabled: false and allow the view to generate params[:device] is:
ActionController::Parameters (3 element(s))
"{"name"=>"D105", "password"=>"D105Dvgr", "password_confirmation"=>"D105Dvgr"}"
And, everything works.
The params[:device] that I create is:
ActionController::Parameters (3 element(s))
"{"name"=>"D106", "password"=>"D106VdAd", "password_confirmation"=>"D106VdAd"}"
And, I receive Forbidden Attribute Error, even though I see no difference between the two.
The update is: <... Code needs refactored, once it is working...>
class DevicesController < ApplicationController
before_filter :authenticate_device!
... other methods removed here ...
def edit
#device = Device.find(params[:id])
# my_page = render_to_string controller: 'devices', action: 'edit', layout: "application"
end
def update
authorize! :update, #device, :message => 'Not authorized as an administrator.'
#device = Device.find(params[:id])
pwd_msg = ""
if params[:chg_pwd]
pwd_gen = #device.device + SecureRandom.urlsafe_base64(15).tr('lIO0=_\-', 'sxyzEUM').first(4)
params[:device] = {name: #device.name} if params[:device].nil?
params[:device][:password] = pwd_gen
params[:device][:password_confirmation] = pwd_gen
pwd_msg = ", new password is #{pwd_gen}"
end
if #device.update_attributes(params[:device])
params[:is_admin] ? #device.add_role(:admin) : #device.remove_role(:admin)
flash[:notice] = ["Device updated" + pwd_msg]
redirect_to devices_path
else
#device.errors.messages.each do |key, value|
flash[:alert] = ["Unable to update device"]
#device.errors.messages.each do |key, value|
flash[:alert] << key.to_s.capitalize + " " + value[0]
end
end
redirect_to devices_path
end
end
private
def device_params
params.require(:device).permit(:device, :name, :email, :password, :password_confirmation, :encrypted_password, :salt, :role_ids, :is_admin, :chg_pwd) # TODO minimize when update is working
end
end
The model is:
class Device < ActiveRecord::Base
rolify
devise :database_authenticatable, :rememberable, :trackable, :validatable
validates :device,
presence: true,
length: {minimum: 4 },
uniqueness: {case_sensitive: false }
validates :name,
presence: true
def remember_me
true unless self.admin?
end
def admin
self.add_role :admin
end
def not_admin
self.remove_role :admin
end
def admin?
self.has_role? :admin
end
def device?
self.has_role? :device
end
def vip?
self.has_role? :vip
end
def login=(login)
#login = login
end
def login
#login || self.device || self.email
end
def self.find_first_by_auth_conditions(warden_conditions)
conditions = warden_conditions.dup
if login = conditions.delete(:login) # Note one equal sign. Strange but true.
where(conditions).where(["lower(device) = :value OR lower(email) = :value", { :value => login.downcase }]).first
else
where(conditions).first
end
end
end
NEW INFORMATION: I neglected to provide information I have in the ApplicationController. This fix from Anton Trapp handles strong parameters for gems that aren't yet fully Rails 4 compatible:
before_filter do
resource = controller_name.singularize.to_sym
method = "#{resource}_params"
params[resource] &&= send(method) if respond_to?(method, true)
end
I have found that using the proposed solution of:
#device.update_attributes(device_params)
does not work if a model field is updated. The result is "param not found: device". It does work if no model field is update. So, the whole issue begs the question of what is truly wrong.
In DevicesController#update action, change
#device.update_attributes(params[:device])
To
#device.update_attributes(device_params)
As you are using Rails 4.1, you need to whitelist the attributes which you would like to be inserted/updated in database. As you passed the attributes directly to update_attributes method without permitting them you received ActiveModel::ForbiddenAttributesError
UPDATE
To resolve param not found: device:
def device_params
if params[:device]
params.require(:device).permit(:device, :name, :email, :password, :password_confirmation, :encrypted_password, :salt, :role_ids, :is_admin, :chg_pwd) # TODO minimize when update is working
end
end
The fix was to add the fields as attr_accessor to the model, but not the database, so that it could be used correctly within the form.
attr_accessor :is_admin, :chg_pwd
And then modify the view to:
<%= simple_form_for #device do |f| %>
<legend><%= controller.action_name.capitalize %> Device:</legend>
<%= f.input :name, disabled: true %>
<%= f.input :is_admin, as: :boolean, checked_value: true, unchecked_value: false %>
<%= f.input :chg_pwd, as: :boolean, checked_value: true, unchecked_value: false %>
<%= f.button :submit %>
<% end %>
Then, due to the Application Controller code from Anton Trapp:
before_filter do
resource = controller_name.singularize.to_sym
method = "#{resource}_params"
params[resource] &&= send(method) if respond_to?(method, true)
end
I was able to update the fields in Device Controller as follows:
#device.update_attributes(params[:device])

Resources