Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

first commit

  • Loading branch information...
commit e4c812ad6dabfef6d34e2d829fa30590a24cd7c2 0 parents
@Houdini authored
20 .gitignore
@@ -0,0 +1,20 @@
+*.gem
+.bundle
+Gemfile.lock
+pkg/*
+
+# Temporary files of every sort
+.DS_Store
+.idea
+.rvmrc
+.stgit*
+*.swap
+*.swo
+*.swp
+*~
+bin/*
+nbproject
+patches-*
+capybara-*.html
+dump.rdb
+*.ids
4 Gemfile
@@ -0,0 +1,4 @@
+source "http://rubygems.org"
+
+# Specify your gem's dependencies in devise_ip_filter.gemspec
+gemspec
19 LICENSE
@@ -0,0 +1,19 @@
+Copyright (C) 2012 Dmitrii Golub
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
48 README.md
@@ -0,0 +1,48 @@
+## Two factor authentication for Devise
+
+## Features
+
+* control sms code pattern
+* configure max login attempts
+* per user level control if he really need two factor authentication
+* your own sms logic
+
+## Configuration
+
+To enable two factor authentication for User model, you should add two_factor_authentication to your devise line, like:
+
+```ruby
+ devise :database_authenticatable, :registerable,
+ :recoverable, :rememberable, :trackable, :validatable, :two_factor_authenticatable
+```
+
+Two default parameters
+
+```ruby
+ config.login_code_random_pattern = /\w+/
+ config.max_login_attempts = 3
+```
+
+Possible random patterns
+```ruby
+/\d{5}/
+/\w{4,8}/
+```
+
+see more https://github.com/benburkert/randexp
+
+By default second factor authentication enabled for each user, you can change it with this method in your User mdoel:
+```ruby
+ def need_two_factor_authentication?(request)
+ request.ip != '127.0.0.1'
+ end
+```
+this will disable two factor authentication for local users
+
+Your send sms logic should be in this method in your User model:
+```ruby
+ def send_two_factor_authentication_code(code)
+ puts code
+ end
+```
+This example just puts code in logs
1  Rakefile
@@ -0,0 +1 @@
+require "bundler/gem_tasks"
43 app/controllers/devise/two_factor_authentication_controller.rb
@@ -0,0 +1,43 @@
+class Devise::TwoFactorAuthenticationController < DeviseController
+ prepend_before_filter :authenticate_scope!
+ before_filter :prepare_and_validate, :handle_two_factor_authentication
+
+ def show
+ end
+
+ def update
+ render :show and return if params[:code].nil?
+ md5 = Digest::MD5.hexdigest(params[:code])
+ if md5.eql?(resource.second_factor_pass_code)
+ warden.session(resource_name)[:need_two_factor_authentication] = false
+ sign_in resource_name, resource, :bypass => true
+ redirect_to stored_location_for(resource_name) || :root
+ resource.update_attribute(:second_factor_attempts_count, 0)
+ else
+ resource.second_factor_attempts_count += 1
+ resource.save
+ set_flash_message :notice, :attempt_failed
+ if resource.max_login_attempts?
+ sign_out(resource)
+ render :template => 'devise/two_factor_authentication/max_login_attempts_reached' and return
+ else
+ render :show
+ end
+ end
+ end
+
+ private
+
+ def authenticate_scope!
+ self.resource = send("current_#{resource_name}")
+ end
+
+ def prepare_and_validate
+ redirect_to :root and return if resource.nil?
+ @limit = resource.class.max_login_attempts
+ if resource.max_login_attempts?
+ sign_out(resource)
+ render :template => 'devise/two_factor_authentication/max_login_attempts_reached' and return
+ end
+ end
+end
3  app/views/devise/two_factor_authentication/max_login_attempts_reached.html.erb
@@ -0,0 +1,3 @@
+<h2>Access completly denied as you have reached your attempts limit = <%= @limit %></h2>
+<p>Please contact your system administrator</p>
+
10 app/views/devise/two_factor_authentication/show.html.erb
@@ -0,0 +1,10 @@
+<h2>Enter your personal code</h2>
+
+<p><%= flash[:notice] %></p>
+
+<%= form_tag([resource_name, :two_factor_authentication], :method => :put) do %>
+ <%= text_field_tag :code %>
+ <%= submit_tag "Submit" %>
+<% end %>
+
+<%= link_to "Sign out", destroy_user_session_path, :method => :delete %>
4 config/locales/en.yml
@@ -0,0 +1,4 @@
+en:
+ devise:
+ two_factor_authentication:
+ attempt_failed: "Attemp failed"
27 lib/two_factor_authentication.rb
@@ -0,0 +1,27 @@
+require 'two_factor_authentication/version'
+require 'randexp'
+require 'devise'
+require 'digest'
+require 'active_support/concern'
+
+module Devise
+ mattr_accessor :login_code_random_pattern
+ @@login_code_random_pattern = /\w+/
+
+ mattr_accessor :max_login_attempts
+ @@max_login_attempts = 3
+end
+
+module TwoFactorAuthentication
+ autoload :Schema, 'two_factor_authentication/schema'
+ module Controllers
+ autoload :Helpers, 'two_factor_authentication/controllers/helpers'
+ end
+end
+
+Devise.add_module :two_factor_authenticatable, :model => 'two_factor_authentication/models/two_factor_authenticatable', :controller => :two_factor_authentication, :route => :two_factor_authentication
+
+require 'two_factor_authentication/orm/active_record'
+require 'two_factor_authentication/routes'
+require 'two_factor_authentication/models/two_factor_authenticatable'
+require 'two_factor_authentication/rails'
34 lib/two_factor_authentication/controllers/helpers.rb
@@ -0,0 +1,34 @@
+module TwoFactorAuthentication
+ module Controllers
+ module Helpers
+ extend ActiveSupport::Concern
+
+ included do
+ before_filter :handle_two_factor_authentication
+ end
+
+ module InstanceMethods
+ private
+
+ def handle_two_factor_authentication
+ if not request.format.nil? and request.format.html? and not devise_controller?
+ Devise.mappings.keys.flatten.any? do |scope|
+ if signed_in?(scope) and warden.session(scope)[:need_two_factor_authentication]
+ session["#{scope}_return_tor"] = request.path if request.get?
+ redirect_to two_factor_authentication_path_for(scope)
+ return
+ end
+ end
+ end
+ end
+
+ def two_factor_authentication_path_for(resource_or_scope = nil)
+ scope = Devise::Mapping.find_scope!(resource_or_scope)
+ change_path = "#{scope}_two_factor_authentication_path"
+ send(change_path)
+ end
+
+ end
+ end
+ end
+end
10 lib/two_factor_authentication/hooks/two_factor_authenticatable.rb
@@ -0,0 +1,10 @@
+Warden::Manager.after_authentication do |user, auth, options|
+ if user.respond_to?(:need_two_factor_authentication?)
+ if auth.session(options[:scope])[:need_two_factor_authentication] = user.need_two_factor_authentication?(auth.request)
+ code = user.generate_two_factor_code
+ user.second_factor_pass_code = Digest::MD5.hexdigest(code)
+ user.save
+ user.send_two_factor_authentication_code(code)
+ end
+ end
+end
30 lib/two_factor_authentication/models/two_factor_authenticatable.rb
@@ -0,0 +1,30 @@
+require 'two_factor_authentication/hooks/two_factor_authenticatable'
+module Devise
+ module Models
+ module TwoFactorAuthenticatable
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ ::Devise::Models.config(self, :login_code_random_pattern, :max_login_attempts)
+ end
+
+ module InstanceMethods
+ def need_two_factor_authentication?
+ true
+ end
+
+ def generate_two_factor_code
+ self.class.login_code_random_pattern.gen
+ end
+
+ def send_two_factor_authentication_code(code)
+ p "Code is #{code}"
+ end
+
+ def max_login_attempts?
+ second_factor_attempts_count >= self.class.max_login_attempts
+ end
+ end
+ end
+ end
+end
14 lib/two_factor_authentication/orm/active_record.rb
@@ -0,0 +1,14 @@
+module TwoFactorAuthentication
+ module Orm
+
+ module ActiveRecord
+ module Schema
+ include TwoFactorAuthentication::Schema
+
+ end
+ end
+ end
+end
+
+ActiveRecord::ConnectionAdapters::Table.send :include, TwoFactorAuthentication::Orm::ActiveRecord::Schema
+ActiveRecord::ConnectionAdapters::TableDefinition.send :include, TwoFactorAuthentication::Orm::ActiveRecord::Schema
7 lib/two_factor_authentication/rails.rb
@@ -0,0 +1,7 @@
+module TwoFactorAuthentication
+ class Engine < ::Rails::Engine
+ ActiveSupport.on_load(:action_controller) do
+ include TwoFactorAuthentication::Controllers::Helpers
+ end
+ end
+end
9 lib/two_factor_authentication/routes.rb
@@ -0,0 +1,9 @@
+module ActionDispatch::Routing
+ class Mapper
+ protected
+
+ def devise_two_factor_authentication(mapping, controllers)
+ resource :two_factor_authentication, :only => [:show, :update], :path => mapping.path_names[:two_factor_authentication], :controller => controllers[:two_factor_authentication]
+ end
+ end
+end
8 lib/two_factor_authentication/schema.rb
@@ -0,0 +1,8 @@
+module TwoFactorAuthentication
+ module Schema
+ def two_factor_authenticatable
+ apply_devise_schema :second_factor_pass_code, String, :limit => 32
+ apply_devise_schema :second_factor_attempts_count, Integer, :default => 0
+ end
+ end
+end
3  lib/two_factor_authentication/version.rb
@@ -0,0 +1,3 @@
+module TwoFactorAuthentication
+ VERSION = "0.0.1"
+end
26 two_factor_authentication.gemspec
@@ -0,0 +1,26 @@
+# -*- encoding: utf-8 -*-
+$:.push File.expand_path("../lib", __FILE__)
+require "two_factor_authentication/version"
+
+Gem::Specification.new do |s|
+ s.name = "two_factor_authentication"
+ s.version = TwoFactorAuthentication::VERSION
+ s.authors = ["Dmitrii Golub"]
+ s.email = ["dmitrii.golub@gmail.com"]
+ s.homepage = ""
+ s.summary = %q{Two factor authentication plugin for devise}
+ s.description = s.summary
+
+ s.rubyforge_project = "two_factor_authentication"
+
+ s.files = `git ls-files`.split("\n")
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
+ s.require_paths = ["lib"]
+
+ s.add_runtime_dependency 'rails', '>= 3.1.1'
+ s.add_runtime_dependency 'devise'
+ s.add_runtime_dependency 'randexp'
+
+ s.add_development_dependency 'bundler'
+end
Please sign in to comment.
Something went wrong with that request. Please try again.