├── VERSION ├── test ├── rails_app │ ├── app │ │ ├── mailers │ │ │ └── .gitkeep │ │ ├── models │ │ │ ├── .gitkeep │ │ │ ├── post.rb │ │ │ └── user.rb │ │ ├── helpers │ │ │ ├── posts_helper.rb │ │ │ └── application_helper.rb │ │ ├── views │ │ │ ├── posts │ │ │ │ ├── new.html.erb │ │ │ │ ├── edit.html.erb │ │ │ │ ├── show.html.erb │ │ │ │ ├── index.html.erb │ │ │ │ └── _form.html.erb │ │ │ ├── layouts │ │ │ │ └── application.html.erb │ │ │ └── devise │ │ │ │ ├── checkga │ │ │ │ └── show.html.erb │ │ │ │ └── displayqr │ │ │ │ └── show.html.erb │ │ ├── assets │ │ │ ├── images │ │ │ │ └── rails.png │ │ │ ├── stylesheets │ │ │ │ ├── posts.css.scss │ │ │ │ ├── application.css │ │ │ │ └── scaffolds.css.scss │ │ │ └── javascripts │ │ │ │ ├── posts.js.coffee │ │ │ │ └── application.js │ │ └── controllers │ │ │ ├── application_controller.rb │ │ │ └── posts_controller.rb │ ├── config │ │ ├── routes.rb │ │ ├── environment.rb │ │ ├── initializers │ │ │ ├── mime_types.rb │ │ │ ├── inflections.rb │ │ │ ├── backtrace_silencers.rb │ │ │ ├── session_store.rb │ │ │ ├── secret_token.rb │ │ │ ├── wrap_parameters.rb │ │ │ └── devise.rb │ │ ├── locales │ │ │ └── en.yml │ │ ├── boot.rb │ │ ├── application.rb │ │ ├── database.yml │ │ └── environments │ │ │ ├── development.rb │ │ │ ├── test.rb │ │ │ └── production.rb │ ├── config.ru │ ├── db │ │ └── migrate │ │ │ ├── 20120122052320_create_posts.rb │ │ │ ├── 20120122053518_devise_google_authenticator_add_to_users.rb │ │ │ └── 20120122052528_devise_create_users.rb │ ├── Rakefile │ └── script │ │ └── rails ├── orm │ └── active_record.rb ├── model_tests_helper.rb ├── test_helper.rb ├── lib │ ├── patches │ │ └── check_ga_test.rb │ └── devise_google_authenticatable │ │ └── controllers │ │ └── helpers_test.rb ├── generators_test.rb ├── integration_tests_helper.rb ├── models_test.rb ├── models │ └── google_authenticatable_test.rb └── integration │ └── gauth_test.rb ├── .document ├── lib ├── devise_google_authenticatable │ ├── rails.rb │ ├── patches.rb │ ├── routes.rb │ ├── orm │ │ └── active_record.rb │ ├── controllers │ │ └── helpers.rb │ ├── schema.rb │ ├── patches │ │ ├── display_qr.rb │ │ └── check_ga.rb │ └── models │ │ └── google_authenticatable.rb ├── generators │ ├── active_record │ │ ├── devise_google_authenticator_generator.rb │ │ └── templates │ │ │ └── migration.rb │ ├── devise_google_authenticator │ │ ├── views_generator.rb │ │ ├── devise_google_authenticator_generator.rb │ │ └── install_generator.rb │ └── mongoid │ │ └── devise_google_authenticator_generator.rb └── devise_google_authenticator.rb ├── app ├── views │ └── devise │ │ ├── checkga │ │ └── show.html.erb │ │ └── displayqr │ │ └── show.html.erb └── controllers │ └── devise │ ├── checkga_controller.rb │ └── displayqr_controller.rb ├── Gemfile ├── Rakefile ├── config └── locales │ └── en.yml ├── LICENSE.txt ├── .gitignore ├── devise_google_authenticator.gemspec └── README.rdoc /VERSION: -------------------------------------------------------------------------------- 1 | 0.3.16 -------------------------------------------------------------------------------- /test/rails_app/app/mailers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/rails_app/app/models/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/rails_app/app/helpers/posts_helper.rb: -------------------------------------------------------------------------------- 1 | module PostsHelper 2 | end 3 | -------------------------------------------------------------------------------- /test/rails_app/app/models/post.rb: -------------------------------------------------------------------------------- 1 | class Post < PARENT_MODEL_CLASS 2 | end 3 | -------------------------------------------------------------------------------- /.document: -------------------------------------------------------------------------------- 1 | lib/**/*.rb 2 | bin/* 3 | - 4 | features/**/*.feature 5 | LICENSE.txt 6 | -------------------------------------------------------------------------------- /test/rails_app/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /test/rails_app/app/views/posts/new.html.erb: -------------------------------------------------------------------------------- 1 |
<%= notice %>
2 | 3 |4 | Title: 5 | <%= @post.title %> 6 |
7 | 8 |9 | Body: 10 | <%= @post.body %> 11 |
12 | 13 | 14 | <%= link_to 'Edit', edit_post_path(@post) %> | 15 | <%= link_to 'Back', posts_path %> 16 | -------------------------------------------------------------------------------- /test/rails_app/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |<%= f.submit I18n.t('submit_token', {:scope => 'devise'}) %>
7 | <% end %> -------------------------------------------------------------------------------- /test/rails_app/app/views/devise/checkga/show.html.erb: -------------------------------------------------------------------------------- 1 |<%= f.submit I18n.t('submit_token', {:scope => 'devise'}) %>
7 | <% end %> 8 | -------------------------------------------------------------------------------- /test/rails_app/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format 4 | # (all these examples are active by default): 5 | # ActiveSupport::Inflector.inflections do |inflect| 6 | # inflect.plural /^(ox)$/i, '\1en' 7 | # inflect.singular /^(ox)en/i, '\1' 8 | # inflect.irregular 'person', 'people' 9 | # inflect.uncountable %w( fish sheep ) 10 | # end 11 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | 5 | group :test do 6 | gem 'activerecord', '~> 3.0' 7 | gem "sqlite3", "~> 1.3.5" 8 | gem "bson_ext", "~> 1.3" 9 | gem "capybara", "~> 1.1.0" 10 | gem 'shoulda', '~> 2.11.3' 11 | gem 'mocha', '~> 0.13.0' 12 | gem 'factory_girl_rails', '~> 1.2' 13 | gem 'nokogiri', '< 1.6.0', :platforms => :ruby_18 14 | gem 'timecop' 15 | gem 'railties' 16 | gem 'actionmailer' 17 | # gem 'debugger' 18 | end 19 | -------------------------------------------------------------------------------- /test/rails_app/config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /test/rails_app/config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | RailsApp::Application.config.session_store :cookie_store, key: '_gauth-app2_session' 4 | 5 | # Use the database for sessions instead of the cookie-based default, 6 | # which shouldn't be used to store highly confidential information 7 | # (create the session table with "rails generate session_migration") 8 | # GauthApp2::Application.config.session_store :active_record_store 9 | -------------------------------------------------------------------------------- /lib/devise_google_authenticatable/patches.rb: -------------------------------------------------------------------------------- 1 | module DeviseGoogleAuthenticator 2 | module Patches 3 | autoload :DisplayQR, 'devise_google_authenticatable/patches/display_qr' 4 | autoload :CheckGA, 'devise_google_authenticatable/patches/check_ga' 5 | 6 | class << self 7 | def apply 8 | Devise::RegistrationsController.send(:include, Patches::DisplayQR) 9 | Devise::SessionsController.send(:include, Patches::CheckGA) 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/generators/active_record/devise_google_authenticator_generator.rb: -------------------------------------------------------------------------------- 1 | require 'rails/generators/active_record' 2 | 3 | module ActiveRecord 4 | module Generators 5 | class DeviseGoogleAuthenticatorGenerator < ActiveRecord::Generators::Base 6 | source_root File.expand_path("../templates", __FILE__) 7 | 8 | def copy_devise_migration 9 | migration_template "migration.rb", "db/migrate/devise_google_authenticator_add_to_#{table_name}.rb" 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /test/rails_app/app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into including all the files listed below. 2 | // Add new JavaScript/Coffee code in separate files in this directory and they'll automatically 3 | // be included in the compiled file accessible from http://example.com/assets/application.js 4 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 5 | // the compiled file. 6 | // 7 | //= require jquery 8 | //= require jquery_ujs 9 | //= require_tree . 10 | -------------------------------------------------------------------------------- /test/rails_app/config/initializers/secret_token.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | # Make sure the secret is at least 30 characters and all random, 6 | # no regular words or you'll be exposed to dictionary attacks. 7 | RailsApp::Application.config.secret_token = '4ca65f35554ff587273162868e8912766dbfad18a51a3674df31e4cad80b0b6d51c8d66500c81b838ebbf279309ec9c56097510f7111c9d28c5266a9d0af769d' 8 | -------------------------------------------------------------------------------- /test/rails_app/config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | # 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters(:format => [:json]) 9 | end 10 | 11 | # Disable root element in JSON by default. 12 | ActiveSupport.on_load(:active_record) do 13 | self.include_root_in_json = false 14 | end 15 | -------------------------------------------------------------------------------- /test/rails_app/app/models/user.rb: -------------------------------------------------------------------------------- 1 | class User < PARENT_MODEL_CLASS 2 | # Include default devise modules. Others available are: 3 | # :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable 4 | devise :google_authenticatable, :database_authenticatable, :registerable, 5 | :recoverable, :rememberable, :trackable, :validatable 6 | 7 | # Setup accessible (or protected) attributes for your model 8 | attr_accessible :gauth_enabled, :gauth_tmp, :gauth_tmp_datetime, :email, :password, :password_confirmation, :remember_me 9 | end 10 | -------------------------------------------------------------------------------- /lib/generators/active_record/templates/migration.rb: -------------------------------------------------------------------------------- 1 | class DeviseGoogleAuthenticatorAddTo<%= table_name.camelize %> < ActiveRecord::Migration 2 | def self.up 3 | change_table :<%= table_name %> do |t| 4 | t.string :gauth_secret 5 | t.string :gauth_enabled, :default => "f" 6 | t.string :gauth_tmp 7 | t.datetime :gauth_tmp_datetime 8 | end 9 | 10 | end 11 | 12 | def self.down 13 | change_table :<%= table_name %> do |t| 14 | t.remove :gauth_secret, :gauth_enabled, :gauth_tmp, :gauth_tmp_datetime 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/rails_app/db/migrate/20120122053518_devise_google_authenticator_add_to_users.rb: -------------------------------------------------------------------------------- 1 | class DeviseGoogleAuthenticatorAddToUsers < ActiveRecord::Migration 2 | def self.up 3 | change_table :users do |t| 4 | t.string :gauth_secret, :gauth_token 5 | t.string :gauth_enabled, :default => "f" 6 | t.string :gauth_tmp 7 | t.datetime :gauth_tmp_datetime 8 | end 9 | 10 | end 11 | 12 | def self.down 13 | change_table :users do |t| 14 | t.remove :gauth_secret, :gauth_enabled, :gauth_tmp, :gauth_tmp_datetime 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/rails_app/config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | require "action_controller/railtie" 4 | require "action_mailer/railtie" 5 | require "rails/test_unit/railtie" 6 | 7 | begin 8 | require "#{DEVISE_ORM}/railtie" 9 | rescue LoadError 10 | end 11 | 12 | PARENT_MODEL_CLASS = DEVISE_ORM == :active_record ? ActiveRecord::Base : Object 13 | 14 | require "devise" 15 | require "devise_google_authenticator" 16 | 17 | module RailsApp 18 | class Application < Rails::Application 19 | #config.filter_parameters << :password 20 | 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /test/model_tests_helper.rb: -------------------------------------------------------------------------------- 1 | class ActiveSupport::TestCase 2 | 3 | # Helpers for creating new users 4 | # 5 | def generate_unique_email 6 | @@email_count ||= 0 7 | @@email_count += 1 8 | "test#{@@email_count}@email.com" 9 | end 10 | 11 | def valid_attributes(attributes={}) 12 | { :email => generate_unique_email, 13 | :password => '123456', 14 | :password_confirmation => '123456' }.update(attributes) 15 | end 16 | 17 | def new_user(attributes={}) 18 | user = User.new(valid_attributes(attributes)) 19 | user.save 20 | user 21 | end 22 | 23 | end 24 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake' 2 | require 'bundler' 3 | Bundler::GemHelper.install_tasks 4 | 5 | require 'rake/testtask' 6 | Rake::TestTask.new(:test) do |test| 7 | test.libs << 'lib' << 'test' 8 | test.pattern = 'test/**/*_test.rb' 9 | test.verbose = true 10 | end 11 | 12 | desc 'Default: run tests for all ORMs.' 13 | task :default => :tests 14 | 15 | desc 'Run Devise tests for all ORMs.' 16 | task :tests do 17 | Dir[File.join(File.dirname(__FILE__), 'test', 'orm', '*.rb')].each do |file| 18 | orm = File.basename(file).split(".").first 19 | system "rake test DEVISE_ORM=#{orm}" 20 | end 21 | end -------------------------------------------------------------------------------- /test/rails_app/app/views/posts/index.html.erb: -------------------------------------------------------------------------------- 1 || Title | 6 |Body | 7 |8 | | 9 | | 10 | |
|---|---|---|---|---|
| <%= post.title %> | 15 |<%= post.body %> | 16 |<%= link_to 'Show', post %> | 17 |<%= link_to 'Edit', edit_post_path(post) %> | 18 |<%= link_to 'Destroy', post, confirm: 'Are you sure?', method: :delete %> | 19 |
<%= f.submit I18n.t('newtoken', {:scope => 'devise.registration'}) %>
7 | <% end %> 8 | 9 | <%= form_for(resource, :as => resource_name, :url => [resource_name, :displayqr], :html => { :method => :put }) do |f| %> 10 | <%= devise_error_messages! %> 11 |<%= f.label :gauth_enabled, I18n.t('qrstatus', {:scope => 'devise.registration'}) %>
13 | <%= f.check_box :gauth_enabled %>
<%= f.label :gauth_token, I18n.t('enter_token', {:scope => 'devise.registration'}) %>
16 | <%= f.number_field :gauth_token, :autocomplete => :off %>
17 |
18 |
<%= f.submit I18n.t('submit', {:scope => 'devise.registration'}) %>
19 | <% end %> 20 | 21 | -------------------------------------------------------------------------------- /lib/devise_google_authenticatable/schema.rb: -------------------------------------------------------------------------------- 1 | module DeviseGoogleAuthenticator 2 | # add schema helper for migrations 3 | module Schema 4 | # Add gauth_secret columns in the resource's database tables 5 | # 6 | # Examples 7 | # 8 | # # For a new resource migration: 9 | # create_table :the_resources do |t| 10 | # t.gauth_secret 11 | # t.gauth_enabled 12 | # ... 13 | # end 14 | # 15 | # # or if the resource's table already exists, define a migration and put this in: 16 | # change_table :the_resources do |t| 17 | # t.string :gauth_secret 18 | # t.boolean :gauth_enabled 19 | # end 20 | # 21 | def gauth_secret 22 | apply_devise_schema :gauth_secret, String 23 | end 24 | 25 | def gauth_enabled 26 | apply_devise_schema :gauth_enabled, Integer, {:default => 0} 27 | end 28 | 29 | def gauth_tmp 30 | apply_devise_schema :gauth_tmp, String 31 | end 32 | 33 | def gauth_tmp_datetime 34 | apply_devise_schema :gauth_tmp_datetime, Datetime 35 | end 36 | 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /test/rails_app/app/views/devise/displayqr/show.html.erb: -------------------------------------------------------------------------------- 1 |<%= f.submit I18n.t('newtoken', {:scope => 'devise.registration'}) %>
7 | <% end %> 8 | 9 | <%= form_for(resource, :as => resource_name, :url => [resource_name, :displayqr], :html => { :method => :put }) do |f| %> 10 | <%= devise_error_messages! %> 11 |<%= f.label :gauth_enabled, I18n.t('qrstatus', {:scope => 'devise.registration'}) %>
13 | <%= f.check_box :gauth_enabled %>
<%= f.label :gauth_token, I18n.t('enter_token', {:scope => 'devise.registration'}) %>
16 | <%= f.number_field :gauth_token, :autocomplete => :off %>
17 |
18 |
<%= f.submit I18n.t('submit', {:scope => 'devise.registration'}) %>
19 | <% end %> 20 | 21 | -------------------------------------------------------------------------------- /test/rails_app/config/environments/development.rb: -------------------------------------------------------------------------------- 1 | RailsApp::Application.configure do 2 | # Settings specified here will take precedence over those in config/environment.rb 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the webserver when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Log error messages when you accidentally call methods on nil. 10 | config.whiny_nils = true 11 | 12 | # Show full error reports and disable caching 13 | config.consider_all_requests_local = true 14 | config.action_view.debug_rjs = true 15 | config.action_controller.perform_caching = false 16 | 17 | # Don't care if the mailer can't send 18 | config.action_mailer.raise_delivery_errors = false 19 | 20 | # Print deprecation notices to the Rails logger 21 | config.active_support.deprecation = :log 22 | 23 | # Only use best-standards-support built into browsers 24 | config.action_dispatch.best_standards_support = :builtin 25 | end 26 | 27 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Christian Frichot 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # rcov generated 2 | coverage 3 | 4 | # rdoc generated 5 | rdoc 6 | 7 | # yard generated 8 | doc 9 | .yardoc 10 | 11 | # bundler 12 | .bundle 13 | 14 | # jeweler generated 15 | pkg 16 | 17 | # Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore: 18 | # 19 | # * Create a file at ~/.gitignore 20 | # * Include files you want ignored 21 | # * Run: git config --global core.excludesfile ~/.gitignore 22 | # 23 | # After doing this, these files will be ignored in all your git projects, 24 | # saving you from having to 'pollute' every project you touch with them 25 | # 26 | # Not sure what to needs to be ignored for particular editors/OSes? Here's some ideas to get you started. (Remember, remove the leading # of the line) 27 | # 28 | # For MacOS: 29 | # 30 | #.DS_Store 31 | 32 | # For TextMate 33 | #*.tmproj 34 | #tmtags 35 | 36 | # For emacs: 37 | #*~ 38 | #\#* 39 | #.\#* 40 | 41 | # For vim: 42 | #*.swp 43 | 44 | # For redcar: 45 | #.redcar 46 | 47 | # For rubinius: 48 | #*.rbc 49 | 50 | # For Project 51 | test/rails_app/log/ 52 | test/rails_app/db/*.sqlite3 53 | Gemfile.lock 54 | .ruby-version 55 | .ruby-gemset 56 | -------------------------------------------------------------------------------- /devise_google_authenticator.gemspec: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.expand_path('../lib', __FILE__) 2 | 3 | Gem::Specification.new do |s| 4 | s.name = "devise_google_authenticator" 5 | s.version = "0.3.16" 6 | s.authors = ["Christian Frichot"] 7 | s.date = "2015-02-08" 8 | s.description = "Devise Google Authenticator Extension, for adding Google's OTP to your Rails apps!" 9 | s.email = "xntrik@gmail.com" 10 | s.extra_rdoc_files = [ 11 | "LICENSE.txt", 12 | "README.rdoc" 13 | ] 14 | s.files = Dir["{app,config,lib}/**/*"] + %w[LICENSE.txt README.rdoc] 15 | s.homepage = "http://github.com/AsteriskLabs/devise_google_authenticator" 16 | s.licenses = ["MIT"] 17 | s.require_paths = ["lib"] 18 | s.summary = "Devise Google Authenticator Extension" 19 | 20 | s.required_ruby_version = '>= 1.9.2' 21 | # s.required_rubygems_version = '>= 2.1.0' 22 | 23 | { 24 | # 'railties' => '~> 3.0', 25 | # removed the following to try and get past this bundle update not finding compatible versions for gem issue 26 | # 'actionmailer' => '>= 3.0', 27 | #'actionmailer' => '~> 3.2',# '>= 3.2.12', 28 | 'devise' => '~> 3.2', 29 | 'rotp' => '~> 1.6' 30 | }.each do |lib, version| 31 | s.add_runtime_dependency(lib, *version) 32 | end 33 | 34 | end 35 | -------------------------------------------------------------------------------- /test/integration_tests_helper.rb: -------------------------------------------------------------------------------- 1 | class ActionController::IntegrationTest 2 | 3 | def warden 4 | request.env['warden'] 5 | end 6 | 7 | def create_full_user 8 | @@user ||= begin 9 | user = User.create!( 10 | :username => 'usertest', 11 | :email => 'fulluser@test.com', 12 | :password => '123456', 13 | :password_confirmation => '123456' 14 | ) 15 | @@user = user 16 | user 17 | end 18 | end 19 | 20 | def create_and_signin_gauth_user 21 | testuser = create_full_user 22 | sign_in_as_user(testuser) 23 | visit user_displayqr_path 24 | check 'user_gauth_enabled' 25 | fill_in('user_gauth_token', :with => ROTP::TOTP.new(testuser.get_qr).at(Time.now)) 26 | click_button 'Continue...' 27 | 28 | Capybara.reset_sessions! 29 | 30 | sign_in_as_user(testuser) 31 | testuser 32 | end 33 | 34 | def sign_in_as_user(user = nil) 35 | user ||= create_full_user 36 | resource_name = user.class.name.underscore 37 | visit send("new_#{resource_name}_session_path") 38 | fill_in "#{resource_name}_email", :with => user.email 39 | fill_in "#{resource_name}_password", :with => user.password 40 | click_button 'Log in' 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/generators/devise_google_authenticator/devise_google_authenticator_generator.rb: -------------------------------------------------------------------------------- 1 | module DeviseGoogleAuthenticator 2 | module Generators 3 | class DeviseGoogleAuthenticatorGenerator < Rails::Generators::NamedBase 4 | 5 | namespace "devise_google_authenticator" 6 | 7 | desc "Add :google_authenticatable directive in the given model, plus accessors. Also generate migration for ActiveRecord" 8 | 9 | def inject_devise_google_authenticator_content 10 | path = File.join("app","models","#{file_path}.rb") 11 | 12 | if File.exists?(path) 13 | inject_into_file(path, "google_authenticatable, :", :after => "devise :") 14 | inject_into_file(path, "gauth_enabled, :gauth_tmp, :gauth_tmp_datetime, :", :after => "attr_accessible :") if needs_attr_accessible? 15 | inject_into_class(path, class_name, "\tattr_accessor :gauth_token\n") 16 | end 17 | end 18 | 19 | hook_for :orm 20 | 21 | private 22 | 23 | def needs_attr_accessible? 24 | rails_3? && !strong_parameters_enabled? 25 | end 26 | 27 | def rails_3? 28 | Rails::VERSION::MAJOR == 3 29 | end 30 | 31 | def strong_parameters_enabled? 32 | defined?(ActionController::StrongParameters) 33 | end 34 | 35 | end 36 | end 37 | end -------------------------------------------------------------------------------- /test/rails_app/app/assets/stylesheets/scaffolds.css.scss: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #fff; 3 | color: #333; 4 | font-family: verdana, arial, helvetica, sans-serif; 5 | font-size: 13px; 6 | line-height: 18px; } 7 | 8 | p, ol, ul, td { 9 | font-family: verdana, arial, helvetica, sans-serif; 10 | font-size: 13px; 11 | line-height: 18px; } 12 | 13 | pre { 14 | background-color: #eee; 15 | padding: 10px; 16 | font-size: 11px; } 17 | 18 | a { 19 | color: #000; 20 | &:visited { 21 | color: #666; } 22 | &:hover { 23 | color: #fff; 24 | background-color: #000; } } 25 | 26 | div { 27 | &.field, &.actions { 28 | margin-bottom: 10px; } } 29 | 30 | #notice { 31 | color: green; } 32 | 33 | .field_with_errors { 34 | padding: 2px; 35 | background-color: red; 36 | display: table; } 37 | 38 | #error_explanation { 39 | width: 450px; 40 | border: 2px solid red; 41 | padding: 7px; 42 | padding-bottom: 0; 43 | margin-bottom: 20px; 44 | background-color: #f0f0f0; 45 | h2 { 46 | text-align: left; 47 | font-weight: bold; 48 | padding: 5px 5px 5px 15px; 49 | font-size: 12px; 50 | margin: -7px; 51 | margin-bottom: 0px; 52 | background-color: #c00; 53 | color: #fff; } 54 | ul li { 55 | font-size: 12px; 56 | list-style: square; } } 57 | -------------------------------------------------------------------------------- /lib/devise_google_authenticator.rb: -------------------------------------------------------------------------------- 1 | require 'active_record/connection_adapters/abstract/schema_definitions' 2 | require 'active_support/core_ext/integer' 3 | require 'active_support/core_ext/string' 4 | require 'active_support/ordered_hash' 5 | require 'active_support/concern' 6 | require 'devise' 7 | 8 | module Devise # :nodoc: 9 | mattr_accessor :ga_timeout 10 | @@ga_timeout = 3.minutes 11 | 12 | mattr_accessor :ga_timedrift 13 | @@ga_timedrift = 3 14 | 15 | mattr_accessor :ga_remembertime 16 | @@ga_remembertime = 1.month 17 | 18 | mattr_accessor :ga_appname 19 | @@ga_appname = Rails.application.class.parent_name 20 | 21 | mattr_accessor :ga_bypass_signup 22 | @@ga_bypass_signup = false 23 | end 24 | 25 | # a security extension for devise 26 | module DeviseGoogleAuthenticator 27 | autoload :Schema, 'devise_google_authenticatable/schema' 28 | autoload :Patches, 'devise_google_authenticatable/patches' 29 | end 30 | 31 | 32 | 33 | require 'devise_google_authenticatable/routes' 34 | require 'devise_google_authenticatable/rails' 35 | require 'devise_google_authenticatable/orm/active_record' 36 | require 'devise_google_authenticatable/controllers/helpers' 37 | ActionView::Base.send :include, DeviseGoogleAuthenticator::Controllers::Helpers 38 | 39 | Devise.add_module :google_authenticatable, :controller => :google_authenticatable, :model => 'devise_google_authenticatable/models/google_authenticatable', :route => :displayqr -------------------------------------------------------------------------------- /lib/devise_google_authenticatable/patches/display_qr.rb: -------------------------------------------------------------------------------- 1 | module DeviseGoogleAuthenticator::Patches 2 | # patch Registrations controller to display the QR code 3 | module DisplayQR 4 | extend ActiveSupport::Concern 5 | included do 6 | 7 | #arrr be the patch 8 | alias_method :create_original, :create 9 | 10 | define_method :create do 11 | build_resource(sign_up_params) 12 | 13 | if resource.save 14 | yield resource if block_given? 15 | if resource.active_for_authentication? 16 | set_flash_message :notice, :signed_up if is_flashing_format? 17 | sign_in(resource_name, resource) 18 | 19 | if resource.respond_to? :gauth_enabled? 20 | if resource.class.ga_bypass_signup 21 | respond_with resource, location: after_sign_up_path_for(resource) 22 | else 23 | respond_with resource, :location => {:controller => 'displayqr', :action => 'show'} 24 | end 25 | else 26 | respond_with resource, location: after_sign_up_path_for(resource) 27 | end 28 | 29 | else 30 | set_flash_message :notice, :"signed_up_but_#{resource.inactive_message}" if is_flashing_format? 31 | expire_data_after_sign_in! 32 | respond_with resource, :location => after_inactive_sign_up_path_for(resource) 33 | end 34 | else 35 | clean_up_passwords resource 36 | respond_with resource 37 | end 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /app/controllers/devise/checkga_controller.rb: -------------------------------------------------------------------------------- 1 | class Devise::CheckgaController < Devise::SessionsController 2 | prepend_before_filter :devise_resource, :only => [:show] 3 | prepend_before_filter :require_no_authentication, :only => [ :show, :update ] 4 | 5 | include Devise::Controllers::Helpers 6 | 7 | def show 8 | @tmpid = params[:id] 9 | if @tmpid.nil? 10 | redirect_to :root 11 | else 12 | render :show 13 | end 14 | end 15 | 16 | def update 17 | resource = resource_class.find_by_gauth_tmp(params[resource_name]['tmpid']) 18 | 19 | if not resource.nil? 20 | 21 | if resource.validate_token(params[resource_name]['gauth_token'].to_i) 22 | set_flash_message(:notice, :signed_in) if is_navigational_format? 23 | sign_in(resource_name,resource) 24 | warden.manager._run_callbacks(:after_set_user, resource, warden, {:event => :authentication}) 25 | respond_with resource, :location => after_sign_in_path_for(resource) 26 | 27 | if not resource.class.ga_remembertime.nil? 28 | cookies.signed[:gauth] = { 29 | :value => resource.email << "," << Time.now.to_i.to_s, 30 | :secure => !(Rails.env.test? || Rails.env.development?), 31 | :expires => (resource.class.ga_remembertime + 1.days).from_now 32 | } 33 | end 34 | else 35 | set_flash_message(:error, :error) 36 | redirect_to :root 37 | end 38 | 39 | else 40 | set_flash_message(:error, :error) 41 | redirect_to :root 42 | end 43 | end 44 | 45 | private 46 | 47 | def devise_resource 48 | self.resource = resource_class.new 49 | end 50 | end -------------------------------------------------------------------------------- /test/rails_app/config/environments/test.rb: -------------------------------------------------------------------------------- 1 | RailsApp::Application.configure do 2 | # Settings specified here will take precedence over those in config/environment.rb 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Log error messages when you accidentally call methods on nil. 11 | config.whiny_nils = true 12 | 13 | # Show full error reports and disable caching 14 | config.consider_all_requests_local = true 15 | config.action_controller.perform_caching = false 16 | 17 | # Raise exceptions instead of rendering exception templates 18 | config.action_dispatch.show_exceptions = false 19 | 20 | # Disable request forgery protection in test environment 21 | config.action_controller.allow_forgery_protection = false 22 | 23 | # Tell Action Mailer not to deliver emails to the real world. 24 | # The :test delivery method accumulates sent emails in the 25 | # ActionMailer::Base.deliveries array. 26 | config.action_mailer.delivery_method = :test 27 | 28 | # Use SQL instead of Active Record's schema dumper when creating the test database. 29 | # This is necessary if your schema can't be completely dumped by the schema dumper, 30 | # like if you have constraints or database-specific column types 31 | # config.active_record.schema_format = :sql 32 | 33 | # Print deprecation notices to the stderr 34 | config.active_support.deprecation = :stderr 35 | 36 | end 37 | -------------------------------------------------------------------------------- /test/lib/devise_google_authenticatable/controllers/helpers_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'devise_google_authenticatable/controllers/helpers' 3 | 4 | class HelpersTest < ActiveSupport::TestCase 5 | include DeviseGoogleAuthenticator::Controllers::Helpers 6 | 7 | def setup 8 | @user = User.new(valid_attributes({:email => 'helpers_test@test.com' })) 9 | end 10 | 11 | test "can get username from user's email" do 12 | assert_equal 'helpers_test', username_from_email(@user.email) 13 | end 14 | 15 | test 'can get otpauth_user' do 16 | assert_equal "username@app", otpauth_user('username', 'app') 17 | end 18 | 19 | test 'can get otpauth_user with a qualifier' do 20 | assert_equal "username@app-qualifier", otpauth_user('username', 'app', '-qualifier') 21 | end 22 | 23 | # fake image tag 24 | def image_tag(src, *args) 25 | src 26 | end 27 | test 'generate qrcode' do 28 | assert_equal "https://chart.googleapis.com/chart?chs=200x200&chld=M|0&cht=qr&chl=otpauth%3A%2F%2Ftotp%2Fhelpers_test%40RailsApp%3Fsecret%3D", google_authenticator_qrcode(@user) 29 | assert_equal "https://chart.googleapis.com/chart?chs=200x200&chld=M|0&cht=qr&chl=otpauth%3A%2F%2Ftotp%2Fhelpers_test%40RailsAppMyQualifier%3Fsecret%3D", google_authenticator_qrcode(@user, 'MyQualifier') 30 | assert_equal "https://chart.googleapis.com/chart?chs=200x200&chld=M|0&cht=qr&chl=otpauth%3A%2F%2Ftotp%2Fhelpers_test%40RailsApp%3Fsecret%3D%26issuer%3DMyIssuer", google_authenticator_qrcode(@user, nil, 'MyIssuer') 31 | assert_equal "https://chart.googleapis.com/chart?chs=200x200&chld=M|0&cht=qr&chl=otpauth%3A%2F%2Ftotp%2Fhelpers_test%40RailsAppMyQualifier%3Fsecret%3D%26issuer%3DMyIssuer", google_authenticator_qrcode(@user, 'MyQualifier', 'MyIssuer') 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/devise_google_authenticatable/patches/check_ga.rb: -------------------------------------------------------------------------------- 1 | module DeviseGoogleAuthenticator::Patches 2 | # patch Sessions controller to check that the OTP is accurate 3 | module CheckGA 4 | extend ActiveSupport::Concern 5 | included do 6 | # here the patch 7 | 8 | alias_method :create_original, :create 9 | 10 | define_method :checkga_resource_path_name do |resource, id| 11 | name = resource.class.name.singularize.underscore 12 | name = name.split('/').last 13 | "#{name}_checkga_path(id:'#{id}')" 14 | end 15 | 16 | define_method :create do 17 | 18 | resource = warden.authenticate!(:scope => resource_name, :recall => "#{controller_path}#new") 19 | 20 | if resource.respond_to?(:get_qr) and resource.gauth_enabled? and resource.require_token?(cookies.signed[:gauth]) #Therefore we can quiz for a QR 21 | tmpid = resource.assign_tmp #assign a temporary key and fetch it 22 | warden.logout #log the user out 23 | 24 | #we head back into the checkga controller with the temporary id 25 | #Because the model used for google auth may not always be the same, and may be a sub-model, the eval will evaluate the appropriate path name 26 | #This change addresses https://github.com/AsteriskLabs/devise_google_authenticator/issues/7 27 | respond_with resource, :location => eval(checkga_resource_path_name(resource, tmpid)) 28 | 29 | else #It's not using, or not enabled for Google 2FA, OR is remembering token and therefore not asking for the moment - carry on, nothing to see here. 30 | set_flash_message(:notice, :signed_in) if is_flashing_format? 31 | sign_in(resource_name, resource) 32 | respond_with resource, :location => after_sign_in_path_for(resource) 33 | end 34 | 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/generators/devise_google_authenticator/install_generator.rb: -------------------------------------------------------------------------------- 1 | module DeviseGoogleAuthenticator 2 | module Generators # :nodoc: 3 | # Install Generator 4 | class InstallGenerator < Rails::Generators::Base 5 | source_root File.expand_path("../../templates", __FILE__) 6 | 7 | desc "Install the devise google authenticator extension" 8 | 9 | def add_configs 10 | inject_into_file "config/initializers/devise.rb", "\n # ==> Devise Google Authenticator Extension\n # Configure extension for devise\n\n" + 11 | " # How long should the user have to enter their token. To change the default, uncomment and change the below:\n" + 12 | " # config.ga_timeout = 3.minutes\n\n" + 13 | " # Change time drift settings for valid token values. To change the default, uncomment and change the below:\n" + 14 | " # config.ga_timedrift = 3\n\n" + 15 | " # Change setting to how long to remember device before requiring another token. Change to nil to turn feature off.\n" + 16 | " # To change the default, uncomment and change the below:\n" + 17 | " # config.ga_remembertime = 1.month\n\n" + 18 | " # Change setting to assign the application name used by code generator. Defaults to Rails.application.class.parent_name.\n" + 19 | " # To change the default, uncomment and change the below:\n" + 20 | " # config.ga_appname = 'example.com'\n\n" + 21 | " # Change setting to bypass the Display QR page immediately after a user sign's up\n" + 22 | " # To change the default, uncomment and change the below. Defaults to false:\n" + 23 | " # config.ga_bypass_signup = true\n\n" + 24 | "\n", :before => /end[ |\n|]+\Z/ 25 | end 26 | 27 | def copy_locale 28 | copy_file "../../../config/locales/en.yml", "config/locales/devise.google_authenticator.en.yml" 29 | end 30 | end 31 | end 32 | end -------------------------------------------------------------------------------- /test/rails_app/config/environments/production.rb: -------------------------------------------------------------------------------- 1 | RailsApp::Application.configure do 2 | # Settings specified here will take precedence over those in config/environment.rb 3 | 4 | # The production environment is meant for finished, "live" apps. 5 | # Code is not reloaded between requests 6 | config.cache_classes = true 7 | 8 | # Full error reports are disabled and caching is turned on 9 | config.consider_all_requests_local = false 10 | config.action_controller.perform_caching = true 11 | 12 | # Specifies the header that your server uses for sending files 13 | config.action_dispatch.x_sendfile_header = "X-Sendfile" 14 | 15 | # For nginx: 16 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' 17 | 18 | # If you have no front-end server that supports something like X-Sendfile, 19 | # just comment this out and Rails will serve the files 20 | 21 | # See everything in the log (default is :info) 22 | # config.log_level = :debug 23 | 24 | # Use a different logger for distributed setups 25 | # config.logger = SyslogLogger.new 26 | 27 | # Use a different cache store in production 28 | # config.cache_store = :mem_cache_store 29 | 30 | # Disable Rails's static asset server 31 | # In production, Apache or nginx will already do this 32 | config.serve_static_assets = false 33 | 34 | # Enable serving of images, stylesheets, and javascripts from an asset server 35 | # config.action_controller.asset_host = "http://assets.example.com" 36 | 37 | # Disable delivery errors, bad email addresses will be ignored 38 | # config.action_mailer.raise_delivery_errors = false 39 | 40 | # Enable threaded mode 41 | # config.threadsafe! 42 | 43 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 44 | # the I18n.default_locale when a translation can not be found) 45 | config.i18n.fallbacks = true 46 | 47 | # Send deprecation notices to registered listeners 48 | config.active_support.deprecation = :notify 49 | end 50 | -------------------------------------------------------------------------------- /test/models_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ModelsTest < ActiveSupport::TestCase 4 | def include_module?(klass, mod) 5 | klass.devise_modules.include?(mod) && 6 | klass.included_modules.include?(Devise::Models::const_get(mod.to_s.classify)) 7 | end 8 | 9 | def assert_include_modules(klass, *modules) 10 | modules.each do |mod| 11 | assert include_module?(klass, mod), "#{klass} not include #{mod}" 12 | end 13 | 14 | (Devise::ALL - modules).each do |mod| 15 | assert !include_module?(klass, mod), "#{klass} include #{mod}" 16 | end 17 | end 18 | 19 | test 'should include Devise modules' do 20 | assert_include_modules User, :database_authenticatable, :registerable, :validatable, :rememberable, :trackable, :google_authenticatable, :recoverable 21 | end 22 | 23 | test 'should have a default value for ga_timeout' do 24 | assert_equal 3.minutes, User.ga_timeout 25 | end 26 | 27 | test 'should have a default value for ga_timedrift' do 28 | assert_equal 3, User.ga_timedrift 29 | end 30 | 31 | test 'should have a new value for ga_appname' do 32 | old_ga_appname = User.ga_appname 33 | User.ga_appname = "test.app" 34 | 35 | assert_equal "test.app", User.ga_appname 36 | 37 | User.ga_appname = old_ga_appname 38 | end 39 | 40 | test 'should set a new value for ga_timeout' do 41 | old_ga_timeout = User.ga_timeout 42 | User.ga_timeout = 1.minutes 43 | 44 | assert_equal 1.minutes, User.ga_timeout 45 | 46 | User.ga_timeout = old_ga_timeout 47 | end 48 | 49 | test 'should set a new value for ga_timedrift' do 50 | old_ga_timedrift = User.ga_timedrift 51 | User.ga_timedrift = 2 52 | 53 | assert_equal 2, User.ga_timedrift 54 | 55 | User.ga_timedrift = old_ga_timedrift 56 | end 57 | 58 | test 'google_authenticatable attributes' do 59 | assert_equal 'f', User.new.gauth_enabled 60 | assert_nil User.new.gauth_tmp 61 | assert_nil User.new.gauth_tmp_datetime 62 | end 63 | end -------------------------------------------------------------------------------- /app/controllers/devise/displayqr_controller.rb: -------------------------------------------------------------------------------- 1 | class Devise::DisplayqrController < DeviseController 2 | prepend_before_filter :authenticate_scope!, :only => [:show, :update, :refresh] 3 | 4 | include Devise::Controllers::Helpers 5 | 6 | # GET /resource/displayqr 7 | def show 8 | if resource.nil? || resource.gauth_secret.nil? 9 | sign_in resource_class.new, resource 10 | redirect_to stored_location_for(scope) || :root 11 | else 12 | @tmpid = resource.assign_tmp 13 | render :show 14 | end 15 | end 16 | 17 | def update 18 | if resource.gauth_tmp != params[resource_name]['tmpid'] || !resource.validate_token(params[resource_name]['gauth_token'].to_i) 19 | set_flash_message(:error, :invalid_token) 20 | render :show 21 | return 22 | end 23 | 24 | if resource.set_gauth_enabled(params[resource_name]['gauth_enabled']) 25 | set_flash_message :notice, (resource.gauth_enabled? ? :enabled : :disabled) 26 | sign_in scope, resource, :bypass => true 27 | redirect_to stored_location_for(scope) || :root 28 | else 29 | render :show 30 | end 31 | end 32 | 33 | def refresh 34 | unless resource.nil? 35 | resource.send(:assign_auth_secret) 36 | resource.save 37 | set_flash_message :notice, :newtoken 38 | sign_in scope, resource, :bypass => true 39 | redirect_to [resource_name, :displayqr] 40 | else 41 | redirect_to :root 42 | end 43 | end 44 | 45 | private 46 | def scope 47 | resource_name.to_sym 48 | end 49 | 50 | def authenticate_scope! 51 | send(:"authenticate_#{resource_name}!") 52 | self.resource = send("current_#{resource_name}") 53 | end 54 | 55 | # 7/2/15 - Unsure if this is used anymore - @xntrik 56 | def resource_params 57 | return params.require(resource_name.to_sym).permit(:gauth_enabled) if strong_parameters_enabled? 58 | params 59 | end 60 | 61 | def strong_parameters_enabled? 62 | defined?(ActionController::StrongParameters) 63 | end 64 | end -------------------------------------------------------------------------------- /test/rails_app/db/migrate/20120122052528_devise_create_users.rb: -------------------------------------------------------------------------------- 1 | class DeviseCreateUsers < ActiveRecord::Migration 2 | def change 3 | create_table(:users) do |t| 4 | 5 | ## Database authenticatable 6 | t.string :email, :null => false, :default => "" 7 | t.string :encrypted_password, :null => false, :default => "" 8 | 9 | ## Recoverable 10 | t.string :reset_password_token 11 | t.datetime :reset_password_sent_at 12 | 13 | ## Rememberable 14 | t.datetime :remember_created_at 15 | 16 | ## Trackable 17 | t.integer :sign_in_count, :default => 0 18 | t.datetime :current_sign_in_at 19 | t.datetime :last_sign_in_at 20 | t.string :current_sign_in_ip 21 | t.string :last_sign_in_ip 22 | 23 | ## Encryptable 24 | # t.string :password_salt 25 | 26 | ## Confirmable 27 | # t.string :confirmation_token 28 | # t.datetime :confirmed_at 29 | # t.datetime :confirmation_sent_at 30 | # t.string :unconfirmed_email # Only if using reconfirmable 31 | 32 | ## Lockable 33 | # t.integer :failed_attempts, :default => 0 # Only if lock strategy is :failed_attempts 34 | # t.string :unlock_token # Only if unlock strategy is :email or :both 35 | # t.datetime :locked_at 36 | 37 | ## Token authenticatable 38 | # t.string :authentication_token 39 | 40 | ## This is the old migration stuff 41 | #t.database_authenticatable :null => false 42 | #t.recoverable 43 | #t.rememberable 44 | #t.trackable 45 | 46 | 47 | # t.encryptable 48 | # t.confirmable 49 | # t.lockable :lock_strategy => :failed_attempts, :unlock_strategy => :both 50 | # t.token_authenticatable 51 | 52 | 53 | t.timestamps 54 | end 55 | 56 | add_index :users, :email, :unique => true 57 | add_index :users, :reset_password_token, :unique => true 58 | # add_index :users, :confirmation_token, :unique => true 59 | # add_index :users, :unlock_token, :unique => true 60 | # add_index :users, :authentication_token, :unique => true 61 | end 62 | 63 | end 64 | -------------------------------------------------------------------------------- /test/rails_app/app/controllers/posts_controller.rb: -------------------------------------------------------------------------------- 1 | class PostsController < ApplicationController 2 | # GET /posts 3 | # GET /posts.json 4 | def index 5 | @posts = Post.all 6 | 7 | respond_to do |format| 8 | format.html # index.html.erb 9 | format.json { render json: @posts } 10 | end 11 | end 12 | 13 | # GET /posts/1 14 | # GET /posts/1.json 15 | def show 16 | @post = Post.find(params[:id]) 17 | 18 | respond_to do |format| 19 | format.html # show.html.erb 20 | format.json { render json: @post } 21 | end 22 | end 23 | 24 | # GET /posts/new 25 | # GET /posts/new.json 26 | def new 27 | @post = Post.new 28 | 29 | respond_to do |format| 30 | format.html # new.html.erb 31 | format.json { render json: @post } 32 | end 33 | end 34 | 35 | # GET /posts/1/edit 36 | def edit 37 | @post = Post.find(params[:id]) 38 | end 39 | 40 | # POST /posts 41 | # POST /posts.json 42 | def create 43 | @post = Post.new(params[:post]) 44 | 45 | respond_to do |format| 46 | if @post.save 47 | format.html { redirect_to @post, notice: 'Post was successfully created.' } 48 | format.json { render json: @post, status: :created, location: @post } 49 | else 50 | format.html { render action: "new" } 51 | format.json { render json: @post.errors, status: :unprocessable_entity } 52 | end 53 | end 54 | end 55 | 56 | # PUT /posts/1 57 | # PUT /posts/1.json 58 | def update 59 | @post = Post.find(params[:id]) 60 | 61 | respond_to do |format| 62 | if @post.update_attributes(params[:post]) 63 | format.html { redirect_to @post, notice: 'Post was successfully updated.' } 64 | format.json { head :ok } 65 | else 66 | format.html { render action: "edit" } 67 | format.json { render json: @post.errors, status: :unprocessable_entity } 68 | end 69 | end 70 | end 71 | 72 | # DELETE /posts/1 73 | # DELETE /posts/1.json 74 | def destroy 75 | @post = Post.find(params[:id]) 76 | @post.destroy 77 | 78 | respond_to do |format| 79 | format.html { redirect_to posts_url } 80 | format.json { head :ok } 81 | end 82 | end 83 | end 84 | -------------------------------------------------------------------------------- /test/models/google_authenticatable_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'model_tests_helper' 3 | 4 | class GoogleAuthenticatableTest < ActiveSupport::TestCase 5 | 6 | def setup 7 | new_user 8 | end 9 | 10 | test 'new users have a non-nil secret set' do 11 | assert_not_nil User.find(1).gauth_secret 12 | end 13 | 14 | test 'new users should have gauth_enabled disabled by default' do 15 | assert_equal 0, User.find(1).gauth_enabled.to_i 16 | end 17 | 18 | test 'get_qr method works' do 19 | assert_not_nil User.find(1).get_qr 20 | end 21 | 22 | test 'updating gauth_enabled to true' do 23 | User.find(1).set_gauth_enabled(1) 24 | assert_equal 1, User.find(1).gauth_enabled.to_i 25 | end 26 | 27 | test 'updating gauth_enabled back to false' do 28 | User.find(1).set_gauth_enabled(0) 29 | assert_equal 0, User.find(1).gauth_enabled.to_i 30 | end 31 | 32 | test 'updating the gauth_tmp key' do 33 | User.find(1).assign_tmp 34 | 35 | assert_not_nil User.find(1).gauth_tmp 36 | assert_not_nil User.find(1).gauth_tmp_datetime 37 | 38 | sleep(1) 39 | 40 | old_tmp = User.find(1).gauth_tmp 41 | old_dt = User.find(1).gauth_tmp_datetime 42 | 43 | User.find(1).assign_tmp 44 | 45 | assert_not_equal old_tmp, User.find(1).gauth_tmp 46 | assert_not_equal old_dt, User.find(1).gauth_tmp_datetime 47 | end 48 | 49 | test 'testing class method for finding by tmp key' do 50 | assert User.find_by_gauth_tmp('invalid').nil? 51 | assert !User.find_by_gauth_tmp(User.find(1).gauth_tmp).nil? 52 | end 53 | 54 | test 'testing token validation' do 55 | assert !User.find(1).validate_token('1') 56 | assert !User.find(1).validate_token(ROTP::TOTP.new(User.find(1).get_qr).at(Time.now)) 57 | 58 | User.find(1).assign_tmp 59 | 60 | assert !User.find(1).validate_token('1') 61 | assert User.find(1).validate_token(ROTP::TOTP.new(User.find(1).get_qr).at(Time.now)) 62 | end 63 | 64 | test 'requiring token after remembertime' do 65 | u = User.find(1) 66 | assert u.require_token?(nil) 67 | assert u.require_token?(u.email + "," + 2.months.ago.to_i.to_s) 68 | assert !u.require_token?(u.email + "," + 1.day.ago.to_i.to_s) 69 | assert u.require_token?("testxx@test.com" + "," + 1.day.ago.to_i.to_s) 70 | end 71 | 72 | end -------------------------------------------------------------------------------- /lib/devise_google_authenticatable/models/google_authenticatable.rb: -------------------------------------------------------------------------------- 1 | require 'rotp' 2 | 3 | module Devise # :nodoc: 4 | module Models # :nodoc: 5 | 6 | module GoogleAuthenticatable 7 | 8 | def self.included(base) # :nodoc: 9 | base.extend ClassMethods 10 | 11 | base.class_eval do 12 | before_validation :assign_auth_secret, :on => :create 13 | include InstanceMethods 14 | end 15 | end 16 | 17 | module InstanceMethods # :nodoc: 18 | def get_qr 19 | self.gauth_secret 20 | end 21 | 22 | def set_gauth_enabled(param) 23 | #self.update_without_password(params[gauth_enabled]) 24 | self.update_attributes(:gauth_enabled => param) 25 | end 26 | 27 | def assign_tmp 28 | self.update_attributes(:gauth_tmp => ROTP::Base32.random_base32(32), :gauth_tmp_datetime => DateTime.now) 29 | self.gauth_tmp 30 | end 31 | 32 | def validate_token(token) 33 | return false if self.gauth_tmp_datetime.nil? 34 | if self.gauth_tmp_datetime < self.class.ga_timeout.ago 35 | return false 36 | else 37 | 38 | valid_vals = [] 39 | valid_vals << ROTP::TOTP.new(self.get_qr).at(Time.now) 40 | (1..self.class.ga_timedrift).each do |cc| 41 | valid_vals << ROTP::TOTP.new(self.get_qr).at(Time.now.ago(30*cc)) 42 | valid_vals << ROTP::TOTP.new(self.get_qr).at(Time.now.in(30*cc)) 43 | end 44 | 45 | if valid_vals.include?(token.to_i) 46 | return true 47 | else 48 | return false 49 | end 50 | end 51 | end 52 | 53 | def gauth_enabled? 54 | # Active_record seems to handle determining the status better this way 55 | if self.gauth_enabled.respond_to?("to_i") 56 | if self.gauth_enabled.to_i != 0 57 | return true 58 | else 59 | return false 60 | end 61 | # Mongoid does NOT have a .to_i for the Boolean return value, hence, we can just return it 62 | else 63 | return self.gauth_enabled 64 | end 65 | end 66 | 67 | def require_token?(cookie) 68 | if self.class.ga_remembertime.nil? || cookie.blank? 69 | return true 70 | end 71 | array = cookie.to_s.split ',' 72 | if array.count != 2 73 | return true 74 | end 75 | last_logged_in_email = array[0] 76 | last_logged_in_time = array[1].to_i 77 | return last_logged_in_email != self.email || (Time.now.to_i - last_logged_in_time) > self.class.ga_remembertime.to_i 78 | end 79 | 80 | private 81 | 82 | def assign_auth_secret 83 | self.gauth_secret = ROTP::Base32.random_base32(64) 84 | end 85 | 86 | end 87 | 88 | module ClassMethods # :nodoc: 89 | def find_by_gauth_tmp(gauth_tmp) 90 | where(gauth_tmp: gauth_tmp).first 91 | end 92 | ::Devise::Models.config(self, :ga_timeout, :ga_timedrift, :ga_remembertime, :ga_appname, :ga_bypass_signup) 93 | end 94 | end 95 | end 96 | end 97 | -------------------------------------------------------------------------------- /test/integration/gauth_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'integration_tests_helper' 3 | 4 | class InvitationTest < ActionDispatch::IntegrationTest 5 | self.use_transactional_fixtures = false 6 | 7 | def teardown 8 | Capybara.reset_sessions! 9 | Timecop.return 10 | end 11 | 12 | test 'register new user - confirm that we get a display qr page after registering' do 13 | visit new_user_registration_path 14 | fill_in('user_email', :with => 'test@test.com') 15 | fill_in('user_password', :with => 'Password1') 16 | fill_in('user_password_confirmation', :with => 'Password1') 17 | click_link_or_button 'Sign up' 18 | 19 | assert_equal user_displayqr_path, current_path 20 | 21 | # Get the user we just signed up's token 22 | testuser = User.find_by_email("test@test.com") 23 | fill_in('user_gauth_token', :with => ROTP::TOTP.new(testuser.get_qr).at(Time.now)) 24 | click_button 'Continue...' 25 | 26 | assert_equal root_path, current_path 27 | end 28 | 29 | test 'a new user should be able to sign in without using their token' do 30 | create_full_user 31 | User.find_by_email("fulluser@test.com").update_attributes(:gauth_enabled => 0) # force this off - unsure why sometimes it flicks on possible race condition 32 | 33 | visit new_user_session_path 34 | fill_in 'user_email', :with => 'fulluser@test.com' 35 | fill_in 'user_password', :with => '123456' 36 | click_button 'Log in' 37 | assert_equal root_path, current_path 38 | end 39 | 40 | test 'a new user should be able to sign in and change their qr code to enabled' do 41 | # sign_in_as_user 42 | create_full_user 43 | User.find_by_email("fulluser@test.com").update_attributes(:gauth_enabled => 0) # force this off - unsure why sometimes it flicks on possible race condition 44 | visit new_user_session_path 45 | fill_in 'user_email', :with => 'fulluser@test.com' 46 | fill_in 'user_password', :with => '123456' 47 | click_button 'Log in' 48 | 49 | visit user_displayqr_path 50 | 51 | check 'user_gauth_enabled' 52 | # Get the user we just signed up's token 53 | testuser = User.find_by_email("fulluser@test.com") 54 | fill_in('user_gauth_token', :with => ROTP::TOTP.new(testuser.get_qr).at(Time.now)) 55 | click_button 'Continue...' 56 | 57 | assert_equal root_path, current_path 58 | end 59 | 60 | test 'a new user should be able to sign in change their qr to enabled and be prompted for their token' do 61 | create_full_user 62 | User.find_by_email("fulluser@test.com").update_attributes(:gauth_enabled => 0) # force this off - unsure why sometimes it flicks on possible race condition 63 | visit new_user_session_path 64 | fill_in 'user_email', :with => 'fulluser@test.com' 65 | fill_in 'user_password', :with => '123456' 66 | click_button 'Log in' 67 | 68 | visit user_displayqr_path 69 | check 'user_gauth_enabled' 70 | # Get the user we just signed up's token 71 | testuser = User.find_by_email("fulluser@test.com") 72 | fill_in('user_gauth_token', :with => ROTP::TOTP.new(testuser.get_qr).at(Time.now)) 73 | click_button 'Continue...' 74 | 75 | Capybara.reset_sessions! 76 | 77 | visit new_user_session_path 78 | fill_in 'user_email', :with => 'fulluser@test.com' 79 | fill_in 'user_password', :with => '123456' 80 | click_button 'Log in' 81 | 82 | assert_equal user_checkga_path, current_path 83 | 84 | end 85 | 86 | test 'fail token authentication' do 87 | create_and_signin_gauth_user 88 | fill_in 'user_gauth_token', :with => '1' 89 | click_button 'Check Token' 90 | 91 | assert_equal new_user_session_path, current_path 92 | Capybara.reset_sessions! 93 | end 94 | 95 | test 'successfull token authentication' do 96 | testuser = User.find_by_email("fulluser@test.com") 97 | visit new_user_session_path 98 | fill_in 'user_email', :with => 'fulluser@test.com' 99 | fill_in 'user_password', :with => "123456" 100 | click_button "Log in" 101 | fill_in 'user_gauth_token', :with => ROTP::TOTP.new(testuser.get_qr).at(Time.now) 102 | click_button 'Check Token' 103 | 104 | assert_equal root_path, current_path 105 | Capybara.reset_sessions! 106 | end 107 | 108 | test 'unsuccessful login - if ga_timeout is short' do 109 | old_ga_timeout = User.ga_timeout 110 | User.ga_timeout = 1.second 111 | 112 | # testuser = create_and_signin_gauth_user 113 | testuser = User.find_by_email("fulluser@test.com") 114 | visit new_user_session_path 115 | fill_in 'user_email', :with => 'fulluser@test.com' 116 | fill_in 'user_password', :with => "123456" 117 | click_button "Log in" 118 | 119 | sleep(5) 120 | 121 | fill_in 'user_gauth_token', :with => ROTP::TOTP.new(testuser.get_qr).at(Time.now) 122 | click_button 'Check Token' 123 | 124 | User.ga_timeout = old_ga_timeout 125 | 126 | assert_equal new_user_session_path, current_path 127 | Capybara.reset_sessions! 128 | end 129 | 130 | test 'unsuccessful login - if ga_timedrift is short' do 131 | old_ga_timedrift = User.ga_timedrift 132 | User.ga_timedrift = 1 133 | 134 | # testuser = create_and_signin_gauth_user 135 | testuser = User.find_by_email("fulluser@test.com") 136 | visit new_user_session_path 137 | fill_in 'user_email', :with => 'fulluser@test.com' 138 | fill_in 'user_password', :with => "123456" 139 | click_button "Log in" 140 | fill_in 'user_gauth_token', :with => ROTP::TOTP.new(testuser.get_qr).at(Time.now.in(60)) 141 | click_button 'Check Token' 142 | 143 | User.ga_timedrift = old_ga_timedrift 144 | 145 | assert_equal new_user_session_path, current_path 146 | Capybara.reset_sessions! 147 | end 148 | 149 | test 'user is not prompted for token again after first login until remembertime is up' do 150 | # testuser = create_and_signin_gauth_user 151 | testuser = User.find_by_email("fulluser@test.com") 152 | visit new_user_session_path 153 | fill_in 'user_email', :with => 'fulluser@test.com' 154 | fill_in 'user_password', :with => "123456" 155 | click_button "Log in" 156 | fill_in 'user_gauth_token', :with => ROTP::TOTP.new(testuser.get_qr).at(Time.now) 157 | click_button 'Check Token' 158 | 159 | assert_equal root_path, current_path 160 | 161 | visit destroy_user_session_path 162 | # sign_in_as_user(testuser) 163 | testuser = User.find_by_email("fulluser@test.com") 164 | visit new_user_session_path 165 | fill_in 'user_email', :with => 'fulluser@test.com' 166 | fill_in 'user_password', :with => "123456" 167 | click_button "Log in" 168 | assert_equal root_path, current_path 169 | visit destroy_user_session_path 170 | 171 | Timecop.travel(1.month.to_i + 1.day.to_i) 172 | # sign_in_as_user(testuser) 173 | testuser = User.find_by_email("fulluser@test.com") 174 | visit new_user_session_path 175 | fill_in 'user_email', :with => 'fulluser@test.com' 176 | fill_in 'user_password', :with => "123456" 177 | click_button "Log in" 178 | assert_equal user_checkga_path, current_path 179 | 180 | Timecop.return 181 | end 182 | end -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | This project is no longer maintained. 2 | If you have a more recent fork and you'd like us to link to it from here, please get in touch. 3 | 4 | 5 | = Devise Google Authenticator 6 | 7 | This is a devise[https://github.com/plataformatec/devise] extension to allow your app to utilise Google Authenticator[http://code.google.com/p/google-authenticator/] for Time-based One Time Passwords (TOTP). 8 | 9 | == Installation 10 | 11 | Add the gem to your Gemfile (don't forget devise too): 12 | 13 | * gem 'devise' 14 | * gem 'devise_google_authenticator', '0.3.16' 15 | 16 | Don't forget to "bundle install" 17 | 18 | === Devise Installation (In case you haven't done it) 19 | 20 | Before you can setup Devise Google Authenticator you need to setup Devise first, you need to do the following (but refer to https://github.com/plataformatec/devise for more information) 21 | 22 | Install Devise: 23 | * rails g devise:install 24 | 25 | Setup the User or Admin model 26 | * rails g devise MODEL 27 | 28 | Configure your app for authorisation, edit your Controller and add this before_filter: 29 | 30 | * before_filter :authenticate_user! 31 | 32 | Make sure your "root" route is configured in config/routes.rb 33 | 34 | === Automatic Installation (Lets assume this is a bare bones app) 35 | 36 | Run the following generator to add the necessary configuration options to Devise's config file: 37 | 38 | * rails g devise_google_authenticator:install 39 | 40 | After you've created your Devise user models (which is usually done with a "rails g devise MODEL"), set up your Google Authenticator additions: 41 | 42 | * rails g devise_google_authenticator MODEL 43 | 44 | Don't forget to migrate if you're NOT using Mongoid as your database ORM, Mongoid installations will have appropriate fields added to the model after the command above: 45 | 46 | * rake db:migrate 47 | 48 | === Installation With Existing Users 49 | 50 | After the above steps have been performed, you'll need to generate secrets for each user: 51 | User.where(:gauth_secret => nil).find_each do |user| 52 | user.send(:assign_auth_secret) 53 | user.save! 54 | end 55 | By default, users won't need to perform two-factor authentication (gauth_enabled='f'). By visiting /MODEL/displayqr (eg: /users/displayqr) 56 | and submitting the form, two-factor authentication will then be turned on (gauth_enabled=1) and required for subsequent logins. 57 | 58 | == Configuration Options 59 | 60 | The install generator adds some options to the end of your Devise config file (config/initializers/devise.rb) 61 | 62 | * config.ga_timeout - how long should the user be able to authenticate with their Google Authenticator token 63 | * config.ga_timedrift - a multiplier which provides for drift between a user's clock (and therefore their OTP) and the system clock. This should be fine at 3. 64 | * config.ga_remembertime - how long to remember the token for before requiring another. By default this is 1 month. To disable this setting change it to nil. 65 | * config.ga_appname - If you want to set a custom application name instead of using the name of the Rails app. 66 | * config.ga_bypass_signup - If you don't want to immediately forward newly registered or signed-up users to the Display QR page. If this is enabled, users will have to visit the /displayqr page to enable Google Authenticator. 67 | 68 | == Custom Views 69 | 70 | If you want to customise your views (which you likely will want to, as they're pretty ugly right now), you can use the generator: 71 | 72 | * rails g devise_google_authenticator:views 73 | 74 | == Usage 75 | 76 | With this extension enabled, the following is expected behaviour: 77 | 78 | * When a user registers, they are forwarded onto the Display QR page (unless ga_bypass_signup is set to true). This allows them to add their new "token" to their mobile device, and enable, or disable, the functionality. To enable/disable the functionality, the user has to enter the current token. 79 | * If users can't self-register, they're still able to visit this page by visiting /MODEL/displayqr (eg: /users/displayqr). 80 | * If the function is enabled (for that user), when they sign in, they'll be prompted for their password (as per normal), but then redirected into the Check QR page. They have to enter their token (from their device) to then successfully authenticate. 81 | * If configured (by default to 1 month), the user will only be prompted for the token every 1 month. 82 | 83 | == I18n 84 | 85 | The install generator also installs an english copy of a Devise Google Authenticator i18n file. This can be modified (or used to create other language versions) and is located at: config/locales/devise.google_authenticator.en.yml 86 | 87 | == Changes 88 | * Version 0.3.16 - A few bug-fixes. Test-cases are now passing in Ruby 1.9.3 and 2.1.5 89 | * Version 0.3.15 - Can now configure whether the displayqr page is displayed during sign-up. Can customise the app's name (thanks Felipe Lima). Require the users to enter the token when enabling or disabling the token (thanks again Felipe Lima). Handle namespaced Devise models (thanks Mikkel Garcia). Ability to set an Issuer within the OTP generation (thanks Sylvain UTARD). 90 | * Version 0.3.14 - Users can now generate a new token if they wish. This is available from the displayqr page. 91 | * Version 0.3.13 - Merged a feature to allow a qualifier for the Google Authenticator token display. This allows you to specify in your view a qualifier for the name of the OTP when it's enrolled into the Google Authenticator app. Thanks Michael Guymon for the pull. 92 | * Version 0.3.12 - Re-introduced Warden's after_authentication callback. Thanks Sunny Ng for the pull. 93 | * Version 0.3.11 - Fixed a bug where if the Devise module was included within something else, such as Active Admin, rewriting back to the CheckGA functionality was broken. This update addresses https://github.com/AsteriskLabs/devise_google_authenticator/issues/7 94 | * Version 0.3.10 - Added support for Mongoid ORM in addition to ActiveRecord. (Still no appropriate testing for, but I've run this on vanilla Rails 4.0.4 and Devise 3.2.3 apps) 95 | * Version 0.3.9 - Merging fix from zhenyuchen (deprecated ActiveRecord query grammar) - also, re-tested against Rails 4.0.4 and Devise 3.2.3 96 | * Version 0.3.8 - Support for remembering the token authentication. (i.e. don't request the token for a configurable amount of time Thanks https://github.com/blahblahblah-) - and seriously, I'm going to try and refactor all the integration tests with Rspec. 97 | * Version 0.3.7 - Support for current Devise (3.2.0) and Rails4 (Thanks https://github.com/ronald05arias) - integration test still broke - need to address this 98 | * Version 0.3.6 - Slight updates - increased key size, more open gemspec, updated en.yml. (Thanks Michael Guymon) 99 | * Version 0.3.5 - Updated README for Rails apps with existing users. (Thanks Jon Collier) 100 | * Version 0.3.4 - Updated test cases to function properly, and tested working with Devise 2.2 (up to at least Devise 2.2.4) 101 | * Version 0.3.3 - Updated some of the redirect methods to proper align with Devise 2.1.0. Also tidied up some of the test routines to proper replicate Devise 2.1.0 102 | * Version 0.3.2 - Updated to include support for Devise 2.0.0 and above (no longer supports 1.5.3 or lower), you'll need version 0.3.1 to use older Devise 103 | * Version 0.3.1 - Slight updated in the dependencies. 104 | * Version 0.3 - first working version! With working generators, tests, and doesnt require changes to Devise's Sign In view 105 | * Version 0.2 - tidied up some of the code - changed the references to AsteriskLabs 106 | * Version 0.1 - initial release, just to push it up, is still very early and requires a bit work 107 | 108 | == Thanks (and unknown contributors) 109 | 110 | This extension would not exist without the following other projects and associated authors (Whom I have turned to for inspiration and definitely have helped contributing by providing awesome Devise extensions. A lot of this code has been refactored from various sources, in particular these - in particular Sergio and Devise_invitable for his excellent unit test code): 111 | 112 | * Devise (José Valim, Carlos Antônio da Silva, Rodrigo Flores) https://github.com/plataformatec/devise 113 | * Devise_invitable (Sergio Cambra) https://github.com/scambra/devise_invitable 114 | * Devise_openid_authenticatable (Nat Budin) https://github.com/nbudin/devise_openid_authenticatable 115 | * Devise_security_extension (Team Phatworx, Marco Scholl, Alexander Dreher) https://github.com/phatworx/devise_security_extension 116 | * Ronald Arias https://github.com/ronald05arias 117 | * Sunny Ng https://github.com/blahblahblah- 118 | * Michael Guymon https://github.com/mguymon 119 | * Mikkel Garcia https://github.com/mikkel 120 | * Ricky Reusser https://github.com/rreusser 121 | * Felipe Lima https://github.com/felipecsl 122 | * Sylvain Utard https://github.com/redox 123 | 124 | 125 | == Contributing to devise_google_authenticator 126 | 127 | * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet 128 | * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it 129 | * Fork the project 130 | * Start a feature/bugfix branch 131 | * Commit and push until you are happy with your contribution 132 | * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally. 133 | * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it. 134 | 135 | == Copyright 136 | 137 | Copyright (c) 2014 Christian Frichot. See LICENSE.txt for 138 | further details. 139 | 140 | -------------------------------------------------------------------------------- /test/rails_app/config/initializers/devise.rb: -------------------------------------------------------------------------------- 1 | # Use this hook to configure devise mailer, warden hooks and so forth. 2 | # Many of these configuration options can be set straight in your model. 3 | Devise.setup do |config| 4 | # ==> Mailer Configuration 5 | # Configure the e-mail address which will be shown in Devise::Mailer, 6 | # note that it will be overwritten if you use your own mailer class with default "from" parameter. 7 | config.mailer_sender = "please-change-me-at-config-initializers-devise@example.com" 8 | 9 | config.secret_key = '4ae92aa404e426dcfbf44b65a8e7a876f26b4598c6b87e522147241960ce3c9f84be2707d34350e9de48e5418d116b0063020af96c6b1d4e0c826845666912fb' 10 | 11 | # Configure the class responsible to send e-mails. 12 | # config.mailer = "Devise::Mailer" 13 | 14 | # ==> ORM configuration 15 | # Load and configure the ORM. Supports :active_record (default) and 16 | # :mongoid (bson_ext recommended) by default. Other ORMs may be 17 | # available as additional gems. 18 | require "devise/orm/#{DEVISE_ORM}" 19 | 20 | # ==> Configuration for any authentication mechanism 21 | # Configure which keys are used when authenticating a user. The default is 22 | # just :email. You can configure it to use [:username, :subdomain], so for 23 | # authenticating a user, both parameters are required. Remember that those 24 | # parameters are used only when authenticating and not when retrieving from 25 | # session. If you need permissions, you should implement that in a before filter. 26 | # You can also supply a hash where the value is a boolean determining whether 27 | # or not authentication should be aborted when the value is not present. 28 | # config.authentication_keys = [ :email ] 29 | 30 | # Configure parameters from the request object used for authentication. Each entry 31 | # given should be a request method and it will automatically be passed to the 32 | # find_for_authentication method and considered in your model lookup. For instance, 33 | # if you set :request_keys to [:subdomain], :subdomain will be used on authentication. 34 | # The same considerations mentioned for authentication_keys also apply to request_keys. 35 | # config.request_keys = [] 36 | 37 | # Configure which authentication keys should be case-insensitive. 38 | # These keys will be downcased upon creating or modifying a user and when used 39 | # to authenticate or find a user. Default is :email. 40 | config.case_insensitive_keys = [ :email ] 41 | 42 | # Configure which authentication keys should have whitespace stripped. 43 | # These keys will have whitespace before and after removed upon creating or 44 | # modifying a user and when used to authenticate or find a user. Default is :email. 45 | config.strip_whitespace_keys = [ :email ] 46 | 47 | # Tell if authentication through request.params is enabled. True by default. 48 | # config.params_authenticatable = true 49 | 50 | # Tell if authentication through HTTP Basic Auth is enabled. False by default. 51 | # config.http_authenticatable = false 52 | 53 | # If http headers should be returned for AJAX requests. True by default. 54 | # config.http_authenticatable_on_xhr = true 55 | 56 | # The realm used in Http Basic Authentication. "Application" by default. 57 | # config.http_authentication_realm = "Application" 58 | 59 | # It will change confirmation, password recovery and other workflows 60 | # to behave the same regardless if the e-mail provided was right or wrong. 61 | # Does not affect registerable. 62 | # config.paranoid = true 63 | 64 | # ==> Configuration for :database_authenticatable 65 | # For bcrypt, this is the cost for hashing the password and defaults to 10. If 66 | # using other encryptors, it sets how many times you want the password re-encrypted. 67 | # 68 | # Limiting the stretches to just one in testing will increase the performance of 69 | # your test suite dramatically. However, it is STRONGLY RECOMMENDED to not use 70 | # a value less than 10 in other environments. 71 | config.stretches = Rails.env.test? ? 1 : 10 72 | 73 | # Setup a pepper to generate the encrypted password. 74 | # config.pepper = "ed18b256145867415005697cf697dbf89510f4ec1ee235639edcf742bd6ca0021997f8c7069e75685a46449b3c72a35038e56573542b896f8aba09468540a5a1" 75 | 76 | # ==> Configuration for :confirmable 77 | # A period that the user is allowed to access the website even without 78 | # confirming his account. For instance, if set to 2.days, the user will be 79 | # able to access the website for two days without confirming his account, 80 | # access will be blocked just in the third day. Default is 0.days, meaning 81 | # the user cannot access the website without confirming his account. 82 | # config.confirm_within = 2.days 83 | 84 | # Defines which key will be used when confirming an account 85 | # config.confirmation_keys = [ :email ] 86 | 87 | # ==> Configuration for :rememberable 88 | # The time the user will be remembered without asking for credentials again. 89 | # config.remember_for = 2.weeks 90 | 91 | # If true, a valid remember token can be re-used between multiple browsers. 92 | # config.remember_across_browsers = true 93 | 94 | # If true, extends the user's remember period when remembered via cookie. 95 | # config.extend_remember_period = false 96 | 97 | # If true, uses the password salt as remember token. This should be turned 98 | # to false if you are not using database authenticatable. 99 | # config.use_salt_as_remember_token = true 100 | 101 | # Options to be passed to the created cookie. For instance, you can set 102 | # :secure => true in order to force SSL only cookies. 103 | # config.cookie_options = {} 104 | 105 | # ==> Configuration for :validatable 106 | # Range for password length. Default is 6..128. 107 | # config.password_length = 6..128 108 | 109 | # Email regex used to validate email formats. It simply asserts that 110 | # an one (and only one) @ exists in the given string. This is mainly 111 | # to give user feedback and not to assert the e-mail validity. 112 | # config.email_regexp = /\A[^@]+@[^@]+\z/ 113 | 114 | # ==> Configuration for :timeoutable 115 | # The time you want to timeout the user session without activity. After this 116 | # time the user will be asked for credentials again. Default is 30 minutes. 117 | # config.timeout_in = 30.minutes 118 | 119 | # ==> Configuration for :lockable 120 | # Defines which strategy will be used to lock an account. 121 | # :failed_attempts = Locks an account after a number of failed attempts to sign in. 122 | # :none = No lock strategy. You should handle locking by yourself. 123 | # config.lock_strategy = :failed_attempts 124 | 125 | # Defines which key will be used when locking and unlocking an account 126 | # config.unlock_keys = [ :email ] 127 | 128 | # Defines which strategy will be used to unlock an account. 129 | # :email = Sends an unlock link to the user email 130 | # :time = Re-enables login after a certain amount of time (see :unlock_in below) 131 | # :both = Enables both strategies 132 | # :none = No unlock strategy. You should handle unlocking by yourself. 133 | # config.unlock_strategy = :both 134 | 135 | # Number of authentication tries before locking an account if lock_strategy 136 | # is failed attempts. 137 | # config.maximum_attempts = 20 138 | 139 | # Time interval to unlock the account if :time is enabled as unlock_strategy. 140 | # config.unlock_in = 1.hour 141 | 142 | # ==> Configuration for :recoverable 143 | # 144 | # Defines which key will be used when recovering the password for an account 145 | # config.reset_password_keys = [ :email ] 146 | 147 | # Time interval you can reset your password with a reset password key. 148 | # Don't put a too small interval or your users won't have the time to 149 | # change their passwords. 150 | config.reset_password_within = 2.hours 151 | 152 | # ==> Configuration for :encryptable 153 | # Allow you to use another encryption algorithm besides bcrypt (default). You can use 154 | # :sha1, :sha512 or encryptors from others authentication tools as :clearance_sha1, 155 | # :authlogic_sha512 (then you should set stretches above to 20 for default behavior) 156 | # and :restful_authentication_sha1 (then you should set stretches to 10, and copy 157 | # REST_AUTH_SITE_KEY to pepper) 158 | # config.encryptor = :sha512 159 | 160 | # ==> Configuration for :token_authenticatable 161 | # Defines name of the authentication token params key 162 | # config.token_authentication_key = :auth_token 163 | 164 | # If true, authentication through token does not store user in session and needs 165 | # to be supplied on each request. Useful if you are using the token as API token. 166 | # config.stateless_token = false 167 | 168 | # ==> Scopes configuration 169 | # Turn scoped views on. Before rendering "sessions/new", it will first check for 170 | # "users/sessions/new". It's turned off by default because it's slower if you 171 | # are using only default views. 172 | # config.scoped_views = false 173 | 174 | # Configure the default scope given to Warden. By default it's the first 175 | # devise role declared in your routes (usually :user). 176 | # config.default_scope = :user 177 | 178 | # Configure sign_out behavior. 179 | # Sign_out action can be scoped (i.e. /users/sign_out affects only :user scope). 180 | # The default is true, which means any logout action will sign out all active scopes. 181 | # config.sign_out_all_scopes = true 182 | 183 | # ==> Navigation configuration 184 | # Lists the formats that should be treated as navigational. Formats like 185 | # :html, should redirect to the sign in page when the user does not have 186 | # access, but formats like :xml or :json, should return 401. 187 | # 188 | # If you have any extra navigational formats, like :iphone or :mobile, you 189 | # should add them to the navigational formats lists. 190 | # 191 | # The :"*/*" and "*/*" formats below is required to match Internet 192 | # Explorer requests. 193 | # config.navigational_formats = [:"*/*", "*/*", :html] 194 | 195 | # The default HTTP method used to sign out a resource. Default is :delete. 196 | config.sign_out_via = :get 197 | 198 | # ==> OmniAuth 199 | # Add a new OmniAuth provider. Check the wiki for more information on setting 200 | # up on your models and hooks. 201 | # config.omniauth :github, 'APP_ID', 'APP_SECRET', :scope => 'user,public_repo' 202 | 203 | # ==> Warden configuration 204 | # If you want to use other strategies, that are not supported by Devise, or 205 | # change the failure app, you can configure them inside the config.warden block. 206 | # 207 | # config.warden do |manager| 208 | # manager.intercept_401 = false 209 | # manager.default_strategies(:scope => :user).unshift :some_external_strategy 210 | # end 211 | 212 | # ==> Devise Google Authenticator Extension 213 | # Configure extension for devise 214 | 215 | # How long should the user have to enter their token. To change the default, uncomment and change the below: 216 | # config.ga_timeout = 3.minutes 217 | 218 | # Change time drift settings for valid token values. To change the default, uncomment and change the below: 219 | # config.ga_timedrift = 3 220 | 221 | # Change setting to how long to remember device before requiring another token. Change to nil to turn feature off. 222 | # To change the default, uncomment and change the below: 223 | # config.ga_remembertime = 1.month 224 | 225 | 226 | end 227 | --------------------------------------------------------------------------------