Code Monkey home page Code Monkey logo

challah's Introduction

Challah

CircleCI Code Climate Gem Version

Challah (pronounced HAH-lah) is a simple Rails authentication gem that provides users a way to authenticate with your app. Most of the functionality within the gem lives within a Rails engine and tries to stay out of the way of your app.

Challah doesn't provide any fancy controllers or views that clutter your app or force you to display information a certain way. That part is up to you. The functionality within Challah is designed to be a starting point for users and sign-ins you can tweak the rest to your app's needs.

Requirements

  • Ruby 2.5.0+
  • Rails 5.2+

Installation

In your Gemfile

gem "challah"

Set up

Once the gem has been set up and installed, run the following command to set up the database migrations:

rails challah:setup

This will copy over the necessary migrations to your app and migrate the database. You will be prompted to add the first user as the last step in this process.

Manual set up

If you would prefer to handle these steps manually, you can do so by using these rake tasks instead:

rails generate challah
rails challah:unpack:user
rails db:migrate

Creating users

Since Challah doesn't provide any controller and views for users there are a few handy rake tasks you can use to create new records.

Use the following task to create a new user:

# Creates a new User record
rails challah:users:create

User Model

Challah provides the core User model for your app, and a database migration to go along with it. You can do anything you want with the model, just leave the Challah::Userable concern intact to keep Challah's standard user methods included.

A user is anyone that needs to be able to authenticate (sign in) to the application. Each user requires a first name, last name, email address, username, and password.

By default a user is marked as "active" and is able to log in to your application. If the active status column is toggled to inactive, then this user is no longer able to log in. The active status column can be used as a soft-delete function for users.

Checking for a current user

The basic way to restrict functionality within your app is to require that someone authenticate (log in) before they can see it. From within your controllers and views you can call the current_user? method to determine if someone has authenticated. This method doesn't care about who the user is, or what it has access to, just that it has successfully authenticated and is a valid user.

For example, restrict the second list item to only users that have logged in:

<ul>
  <li><a href="/">Home</a></li>

  <% if current_user? %>
    <li><a href="/secret-stuff">Secret Stuff</a></li>
  <% end %>

  <li><a href="/public-stuff">Not-so-secret Stuff</a></li>
</ul>

Controllers can also be restricted using before_action:

class WidgetsController < ApplicationController
  before_action :signin_required

  # ...
end

Or, you can call restrict_to_authenticated instead, which does the same thing:

class WidgetsController < ApplicationController
  restrict_to_authenticated

  # ...
end

All normal Rails before_action options apply, so you can always limit this restriction to a specific action:

class WidgetsController < ApplicationController
  restrict_to_authenticated only: [ :edit, :update, :destroy ]

  # ...
end

Default Routes

By default, there are a few routes included with the Challah engine. These routes provide a basic method for a username and password sign in page. These routes are:

GET   /sign-in      # => SessionsController#new
POST  /sign-in      # => SessionsController#create
GET   /sign-out     # => SessionsController#new

Feel free to override the SessionsController with something more appropriate for your app.

If you'd prefer to set up your own "sign in" and "sign out" actions, you can skip the inclusion of the default routes by adding the following line to an initializer file in your app:

# in config/initializers/challah.rb
Challah.options[:skip_routes] = true

Sign In Form

By default, the sign in form is tucked away within the Challah gem. If you'd like to customize the markup or functionality of the sign in form, you can unpack it into your app by running:

# Copy the sign in view into your app
rails challah:unpack:views

If necessary, the sessions controller which handles creating new sessions and signing users out can also be unpacked into your app. This is really only recommended if you need to add some custom behavior or have advanced needs.

# Copy the sessions controller into your app
rails challah:unpack:signin

API Controllers

For apps that use JSON API controllers, Challah can be used to authenticate a user with a url parameter or an HTTP request header. This feature is disabled by default, so to use it you will need to change the token_enabled setting to true:

# in config/initializers/challah.rb
Challah.options[:token_enabled] = true

Once enabled, this setting will allow the api_key for the user to be used to authenticate them via the token parameter, or X-Auth-Token HTTP header.

For example, the following request would authenticate a valid active user that has the api_key value of abc123:

curl -H "X-Auth-Token: abc123" \
  -H 'Content-Type: application/json' \
  http://localhost:3000/api/test.json

Using the token param, you could write the same thing as:

curl -H 'Content-Type: application/json' \
  http://localhost:3000/api/test.json?token=abc123

If you'd like to change the HTTP header used to fetch the user's api key from, you can change it using the token_header setting:

# in config/initializers/challah.rb
Challah.options[:token_enabled] = true
Challah.options[:token_header] = "X-App-User"

Then:

curl -H "X-App-User: abc123" \
  -H 'Content-Type: application/json' \
  http://localhost:3000/api/test.json

Note: Custom HTTP headers should always start with X-

ActionCable

Challah works well with securing your ActionCable channels since Rails 5. Here is a sample ApplicationCable::Connection file to secure connections to a valid signed-in user:

module ApplicationCable
  class Connection < ActionCable::Connection::Base

    identified_by :current_user

    def connect
      self.current_user = find_current_user
    end

    private

    def find_current_user
      if user = Challah::Session.find(request)
        user
      else
        reject_unauthorized_connection
      end
    end

  end
end

User Validations

By default, the first_name, last_name, and email fields are required on the user model. If you'd prefer to add your own validations and leave the defaults off, you can use the following option within an initializer:

# in config/initializers/challah.rb
Challah.options[:skip_user_validations] = true

Authorization Model

The Authorization model can be used to store user credentials for a variety of different sources. By default, usernames and passwords are hashed and stored in this table.

In addition to the username/password, you can also use the authorizations table to store credentials or tokens for other services as well. For example, you could store a successful Facebook session using the following method:

Authorization.set({
  # provider is just a key and can be anything to denote this service
  provider: :facebook,

  # the user's Facebook UID
  uid: "000000",

  # the user's Facebook-provided access token
  token: "abc123",

  # the user ID to link to this authorization
  user_id: user.id,

  # (optional, when this token expires)
  expires_at: 60.minutes.from_now
})

Then, to remove an authorization, just provide the user'd ID and the provider:

Authorization.del({
  provider: :facebook,
  user_id: user.id
})

Full documentation

Documentation is available at: http://rubydoc.info/gems/challah

Issues

If you have any issues or find bugs running Challah, please report them on Github. While most functions should be stable, Challah is still in its infancy and certain issues may be present.

Testing

Challah is fully tested using RSpec. To run the test suite, bundle install then run:

rspec

License

Challah is released under the MIT license

Contributions and pull-requests are more than welcome.

challah's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

challah's Issues

Error installing

Hi,
I get the following error when I run "bundle exec rake challah:setup".

Populating seed data...
rake aborted!
Can't mass-assign protected attributes: name, key, description, locked

I get the same when trying to setup manually.

Do you have an example app I can view to see this implementation?

Cheers

Not requiring a password for inactive users.

I had a requirement to be able to create and manage inactive users, but not set a password until they activated their accounts. This is what I ended up doing. I don't know if you have any interest in something like this, but here is where my implementation shook out.

I don't really have the time right now to dig into this to add it as a pull request, so feel free to take it or leave it. Just food for thought 😊

config/initializers/challah.rb
Challah.options[:skip_user_validations] = true
app/models/user.rb
class User < ActiveRecord::Base
  include Challah::Userable

  # I have here a custom email validation but could just as easily use Challah's
  validates :email, presence: true, uniqueness: true, format: { with: /@/ }

  with_options if: :active? do |active|
    active.validates :first_name, presence: true
    active.validates :last_name, presence: true

    # This is my forked version of Challah::PasswordValidator
    active.validates_with ::PasswordValidator
  end
end
app/validators/password_validator.rb
class PasswordValidator < ActiveModel::Validator
  def validate(record)
    if record.password_changed?
      if record.password.to_s.size < 4
        record.errors.add :password, :invalid_password
      elsif record.password.to_s != record.password_confirmation.to_s
        record.errors.add :password, :no_match_password
      end
    elsif ((record.status_changed? && record.active?) || record.new_record?)
      # This line right here is kind of icky, since we're hitting the authorizations table.
      if record.password.to_s.blank? && Authorization.where(user_id: record.id, provider: "password").none?
        record.errors.add :password, :blank
      end
    end
  end
end

The main thing I don't like about what I went with is the Authorization lookup in the validator. It could probably be tracked by a separate activated_at column or something.

Curious what your thoughts are on this? Do you think it might be better to use SecureRandom.urlsafe_base64 for a password instead?

Load Errors with Challah 1.3.0

From my deployment output via Heroku:

remote:        LoadError: cannot load such file -- challah

I'm calling require: 'challah' in my lib/users.rb file within my engine.

Not quite sure of the issue - I'll have to do some digging.


Full trace:

LoadError: cannot load such file -- challah
/app/vendor/bundle/ruby/2.2.0/gems/activesupport-4.2.5.1/lib/active_support/dependencies.rb:274:in `require'
/app/vendor/bundle/ruby/2.2.0/gems/activesupport-4.2.5.1/lib/active_support/dependencies.rb:274:in `block in require'
/app/vendor/bundle/ruby/2.2.0/gems/activesupport-4.2.5.1/lib/active_support/dependencies.rb:240:in `load_dependency'
/app/vendor/bundle/ruby/2.2.0/gems/activesupport-4.2.5.1/lib/active_support/dependencies.rb:274:in `require'
/app/components/users/lib/users.rb:4:in `<top (required)>'
/app/vendor/bundle/ruby/2.2.0/gems/bundler-1.9.7/lib/bundler/runtime.rb:76:in `require'
/app/vendor/bundle/ruby/2.2.0/gems/bundler-1.9.7/lib/bundler/runtime.rb:76:in `block (2 levels) in require'
/app/vendor/bundle/ruby/2.2.0/gems/bundler-1.9.7/lib/bundler/runtime.rb:72:in `each'
/app/vendor/bundle/ruby/2.2.0/gems/bundler-1.9.7/lib/bundler/runtime.rb:72:in `block in require'
/app/vendor/bundle/ruby/2.2.0/gems/bundler-1.9.7/lib/bundler/runtime.rb:61:in `each'
/app/vendor/bundle/ruby/2.2.0/gems/bundler-1.9.7/lib/bundler/runtime.rb:61:in `require'
/app/vendor/bundle/ruby/2.2.0/gems/bundler-1.9.7/lib/bundler.rb:134:in `require'
/app/config/application.rb:16:in `<top (required)>'
/app/Rakefile:5:in `require'
/app/Rakefile:5:in `<top (required)>'

my lib/users.rb file:

# ... deps
require "users/engine"
require "challah"
# ... more deps

module Users
  # ... stuff
end

Strip spaces?

@jdtornow Would you be interested in a PR that stripped leading/trailing spaces from usernames/email addresses?

LoadError in SessionsController#new

I literally did nothing more than create a brand new 4.2.6 Rails project, install the gem, run rake challah:setup and visit localhost:3000/sign-in and I got this:

What does `uid` represent?

https://github.com/jdtornow/challah/blob/master/lib/challah/concerns/user/findable.rb#L31

For the Authorizations, what does uid stand for?

In my experience, a uid is the user's identification id. Ex: Twitter's API considers the uid the User's ID in the database.

I've looked in other projects' authorizations tables and seen that the uid is a string (but also generally blank).

Based on the #find_by_authorization method on UserFindable (and how it's being used in that class) - it seems like the uid is a User's username?

If that's the case, that's no problem - I'm just working to understand how Challah is operating under the hood.

Challah breaks shoulda/matchers uniqueness?

Context:

my comment and subsequent comments and pip's comment

Background:

For some reason - shoulda/matchers validation of uniqueness is failing (just in specs) when challah is included in a project.

Example App:

https://github.com/thewatts/uniqueness

( models provided are User and Trait )

To Reproduce:

clone, bundle, db creation/migration, etc

run: bundle exec rspec spec -> all tests will pass.

Add challah to Gemfile and bundle.

run: bundle exec rspec spec -> uniqueness specs fail.

Failing Specs Details

.F..F

Failures:

  1) Trait should validate that :color is case-sensitively unique
     Failure/Error: it { should validate_uniqueness_of :color }
       Trait did not properly validate that :color is case-sensitively unique.
         After taking the given Trait, whose :color is ‹"red-2"›, and saving it
         as the existing record, then making a new Trait and setting its :color
         to ‹"red-2"› as well, the matcher expected the new Trait to be
         invalid, but it was valid instead.
     # ./spec/models/trait_spec.rb:7:in `block (2 levels) in <top (required)>'

  2) User should validate that :email is case-sensitively unique
     Failure/Error: it { should validate_uniqueness_of :email }
       User did not properly validate that :email is case-sensitively unique.
         After taking the given User, whose :email is ‹"[email protected]"›,
         and saving it as the existing record, then making a new User and
         setting its :email to ‹"[email protected]"› as well, the matcher
         expected the new User to be invalid, but it was valid instead.
     # ./spec/models/user_spec.rb:8:in `block (2 levels) in <top (required)>'

Finished in 0.06393 seconds (files took 1.51 seconds to load)
5 examples, 2 failures

Failed examples:

rspec ./spec/models/trait_spec.rb:7 # Trait should validate that :color is case-sensitively unique
rspec ./spec/models/user_spec.rb:8 # User should validate that :email is case-sensitively unique

Looks like @philtr just added an issue as well, #28

Change User#active boolean to status enum

Now that we're dropping pre 4.2 support, would be nice to use an enum in User instead of the older active flag.

Proposed states:

class User < ActiveRecord::Base

  include Challah::Userable

  enum status: %w( active inactive )

end

This would keep the active? attribute as before, but use the new data type. This would be a breaking database change so it will need to provide an upgrade path or keep backward compatibility.

Adding a registration mailer

How would I add a mailer to your create method?

new_user = User.create(
email: params[:session][:username],
password: params[:session][:password],
password_confirmation: params[:session][:password]
) if !existing_user && params[:customer_type] == "new"

Not entirely sure where the best place to put this would be:
UserRegistration.new_registration(@user.id).deliver

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.