pax_global_header00006660000000000000000000000064125761326120014517gustar00rootroot0000000000000052 comment=db3d5d568c4a411d5348fa55f75a31c2a2a5b4c7 devise-two-factor-2.0.0/000077500000000000000000000000001257613261200150605ustar00rootroot00000000000000devise-two-factor-2.0.0/.gitignore000066400000000000000000000014601257613261200170510ustar00rootroot00000000000000Gemfile.lock *.gem # rcov generated coverage coverage.data # yard generated doc .yardoc # bundler .bundle # jeweler generated pkg # Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore: # # * Create a file at ~/.gitignore # * Include files you want ignored # * Run: git config --global core.excludesfile ~/.gitignore # # After doing this, these files will be ignored in all your git projects, # saving you from having to 'pollute' every project you touch with them # # 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) # # For MacOS: # #.DS_Store # For TextMate #*.tmproj #tmtags # For emacs: #*~ #\#* #.\#* # For vim: #*.swp # For redcar: #.redcar # For rubinius: #*.rbc devise-two-factor-2.0.0/.rspec000066400000000000000000000000101257613261200161640ustar00rootroot00000000000000--color devise-two-factor-2.0.0/.travis.yml000066400000000000000000000001641257613261200171720ustar00rootroot00000000000000language: ruby cache: bundler rvm: - "1.9.3" - "2.0.0" - "2.1" - "2.2" - jruby-19mode # JRuby in 1.9 mode devise-two-factor-2.0.0/CONTRIBUTING.md000066400000000000000000000024701257613261200173140ustar00rootroot00000000000000We love pull requests. Here's a quick guide: 1. Fork the repo. 2. Run the tests. We only take pull requests with passing tests, and it's great to know that you have a clean slate: `bundle && rake` 3. Add a test for your change. Only refactoring and documentation changes require no new tests. If you are adding functionality or fixing a bug, we need a test! 4. Make the test pass. 5. Push to your fork and submit a pull request. At this point you're waiting on us. We like to at least comment on, if not accept, pull requests within three business days (and, typically, one business day). We may suggest some changes or improvements or alternatives. Some things that will increase the chance that your pull request is accepted, taken straight from the Ruby on Rails guide: * Use Rails idioms and helpers * Include tests that fail without your code, and pass with it * Update the documentation, the surrounding one, examples elsewhere, guides, whatever is affected by your contribution Syntax: * Two spaces, no tabs. * No trailing whitespace. Blank lines should not have any space. * Prefer &&/|| over and/or. * MyClass.my_method(my_arg) not my_method( my_arg ) or my_method my_arg. * a = b and not a=b. * Follow the conventions you see used in the source already. And in case we didn't emphasize it enough: we love tests! devise-two-factor-2.0.0/Gemfile000066400000000000000000000000461257613261200163530ustar00rootroot00000000000000source 'https://rubygems.org' gemspec devise-two-factor-2.0.0/LICENSE000066400000000000000000000021011257613261200160570ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2014 Tinfoil Security, Inc. 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. devise-two-factor-2.0.0/README.md000066400000000000000000000244311257613261200163430ustar00rootroot00000000000000# Devise-Two-Factor Authentication By [Tinfoil Security](http://tinfoilsecurity.com/) [![Build Status](https://travis-ci.org/tinfoil/devise-two-factor.svg?branch=master)](https://travis-ci.org/tinfoil/devise-two-factor) Devise-two-factor is a minimalist extension to Devise which offers support for two-factor authentication, through the [TOTP](https://en.wikipedia.org/wiki/Time-based_One-time_Password_Algorithm) scheme. It: * Allows you to incorporate two-factor authentication into your existing models * Is opinionated about security, so you don't have to be * Integrates easily with two-factor applications like Google Authenticator and Authy * Is extensible, and includes two-factor backup codes as an example of how plugins can be structured ## Example App An example Rails 4 application is provided in demo/. It showcases a minimal example of devise-two-factor in action, and can act as a reference for integrating the gem into your own application. For the demo app to work, create an encryption key and store it as an environment variable. One way to do this is to create a file named `local_env.yml` in the application root. Set the value of 'ENCRYPTION_KEY' in the YML file. That value will be loaded into the application environment by `application.rb`. ## Getting Started Devise-two-factor doesn't require much to get started, but there are a few prerequisites before you can start using it in your application. First, you'll need a Rails application setup with Devise. Visit the Devise [homepage](https://github.com/plataformatec/devise) for instructions. Next, since devise-two-factor encrypts its secrets before storing them in the database, you'll need to generate an encryption key, and store it in an environment variable of your choice. Set the encryption key in the model that uses devise: ``` devise :two_factor_authenticatable, :otp_secret_encryption_key => ENV['YOUR_ENCRYPTION_KEY_HERE'] ``` Finally, you can automate all of the required setup by simply running: ```ruby rails generate devise_two_factor MODEL ENVIRONMENT_VARIABLE ``` Where MODEL is the name of the model you wish to add two-factor functionality to, and ENVIRONMENT_VARIABLE is the name of the variable you're storing your encryption key in. This generator will add a few columns to the specified model: * encrypted_otp_secret * encrypted_otp_secret_iv * encrypted_otp_secret_salt * otp_required_for_login It also adds the :two_factor_authenticatable directive to your model, and sets up your encryption key. If present, it will remove :database_authenticatable from the model, as the two strategies are incompatible. Lastly, the generator will add a Warden config block to your Devise initializer, which enables the strategies required for two-factor authenticatation. If you're running Rails 3, or do not have strong parameters enabled, the generator will also setup the required mass-assignment security options in your model. If you're running Rails 4, you'll also need to whitelist `:otp_attempt` as a permitted parameter in Devise `:sign_in` controller. You can do this by adding the following to your `application_controller.rb` ```ruby before_action :configure_permitted_parameters, if: :devise_controller? ... protected def configure_permitted_parameters devise_parameter_sanitizer.for(:sign_in) << :otp_attempt end ``` **After running the generator, verify that :database_authenticatable is not being loaded by your model. The generator will try to remove it, but if you have a non-standard Devise setup, this step may fail. Loading both :database_authenticatable and :two_factor_authenticatable in a model will allow users to bypass two-factor authenticatable due to the way Warden handles cascading strategies.** ## Designing Your Workflow Devise-two-factor only worries about the backend, leaving the details of the integration up to you. This means that you're responsible for building the UI that drives the gem. While there is an example Rails application included in the gem, it is important to remember that this gem is intentionally very open-ended, and you should build a user experience which fits your individual application. There are two key workflows you'll have to think about: 1. Logging in with two-factor authentication 2. Enabling two-factor authentication for a given user We chose to keep things as simple as possible, and our implementation can be found by registering at [Tinfoil Security](https://tinfoilsecurity.com/), and enabling two-factor authentication from the [security settings page](https://www.tinfoilsecurity.com/account/security). ### Logging In Logging in with two-factor authentication works extremely similarly to regular database authentication in Devise. The TwoFactorAuthenticatable strategy accepts three parameters: 1. email 2. password 3. otp_attempt (Their one-time password for this session) These parameters can be submitted to the standard Devise login route, and the strategy will handle the authentication of the user for you. ### Disabling Automatic Login After Password Resets If you use the Devise ```recoverable``` strategy, the default behavior after a password reset is to automatically authenticate the user and log them in. This is obviously a problem if a user has two-factor authentication enabled, as resetting the password would get around the 2FA requirement. Because of this, you need to set `sign_in_after_reset_password` to false (either globally in your Devise initializer or via `devise_for`) ### Enabling Two-Factor Authentication Enabling two-factor authentication for a user is easy. For example, if my user model were named User, I could do the following: ```ruby current_user.otp_required_for_login = true current_user.otp_secret = User.generate_otp_secret current_user.save! ``` Before you can do this however, you need to decide how you're going to transmit two-factor tokens to a user. Common strategies include sending an SMS, or using a mobile application such as Google Authenticator. At Tinfoil Security, we opted to use the excellent [rqrcode-rails3](https://github.com/samvincent/rqrcode-rails3) gem to generate a QR-code representing the user's secret key, which can then be scanned by any mobile two-factor authentication client. If you instead to decide to send the one-time password to the user directly, such as via SMS, you'll need a mechanism for generating the one-time password on the server: ```ruby current_user.current_otp ``` The generated code will be valid for the duration specified by otp_allowed_drift. However you decide to handle enrollment, there are a few important considerations to be made: * Whether you'll force the use of two-factor authentication, and if so, how you'll migrate existing users to system, and what your onboarding experience will look like * If you authenticate using SMS, you'll want to verify the user's ownership of the phone, in much the same way you're probably verifying their email address * How you'll handle device revocation in the event that a user loses access to their device, or that device is rendered temporarily unavailable (This gem includes TwoFactorBackupable as an example extension meant to solve this problem) It sounds like a lot of work, but most of these problems have been very elegantly solved by other people. We recommend taking a look at the excellent workflows used by Heroku and Google for inspiration. ## Backup Codes Devise-two-factor is designed with extensibility in mind. One such extension, TwoFactorBackupable, is included and serves as a good example of how to extend this gem. This plugin allows you to add the ability to generate single-use backup codes for a user, which they may use to bypass two-factor authentication, in the event that they lose access to their device. To install it, you need to add the :two_factor_backupable directive to your model. ```ruby devise :two_factor_backupable ``` You'll also be required to enable the :two_factor_backupable strategy, by adding the following line to your Warden config in your Devise initializer, substituting :user for the name of your Devise scope. ```ruby manager.default_strategies(:scope => :user).unshift :two_factor_backupable ``` The final installation step is dependent on your version of Rails. If you're not running Rails 4, skip to the next section. Otherwise, create the following migration: ```ruby class AddDeviseTwoFactorBackupableToUsers < ActiveRecord::Migration def change add_column :users, :otp_backup_codes, :string, array: true end end ``` You can then generate backup codes for a user: ```ruby codes = current_user.generate_otp_backup_codes! current_user.save! # Display codes to the user somehow! ``` The backup codes are stored in the database as bcrypt hashes, so be sure to display them to the user at this point. If all went well, the user should be able to login using each of the generated codes in place of their two-factor token. Each code is single-use, and generating a new set of backup codes for that user will invalidate all of the old ones. You can customize the length of each code, and the number of codes generated by passing the options into `:two_factor_backupable` in the Devise directive: ```ruby devise :two_factor_backupable, otp_backup_code_length: 32, otp_number_of_backup_codes: 10 ``` ### Help! I'm not using Rails 4.0! Don't worry! TwoFactorBackupable stores the backup codes as an array of strings in the database. In Rails 4.0 this is supported natively, but in earlier versions you can use a gem to emulate this behaviour: we recommend [activerecord-postgres-array](https://github.com/tlconnor/activerecord-postgres-array). You'll then simply have to create a migration to add an array named `otp_backup_codes` to your model. If you use the above gem, this migration might look like: ```ruby class AddTwoFactorBackupCodesToUsers < ActiveRecord::Migration def change add_column :users, :otp_backup_codes, :string_array end end ``` Now just continue with the setup in the previous section, skipping the generator step. ## Testing Devise-two-factor includes shared-examples for both TwoFactorAuthenticatable and TwoFactorBackupable. Adding the following two lines to the specs for your two-factor enabled models will allow you to test your models for two-factor functionality: ```ruby require 'devise_two_factor/spec_helpers' it_behaves_like "two_factor_authenticatable" it_behaves_like "two_factor_backupable" ``` devise-two-factor-2.0.0/Rakefile000066400000000000000000000010361257613261200165250ustar00rootroot00000000000000# encoding: utf-8 require 'rubygems' require 'bundler' begin Bundler.setup(:default, :development) rescue Bundler::BundlerError => e $stderr.puts e.message $stderr.puts "Run `bundle install` to install missing gems" exit e.status_code end require 'rake' require 'rspec/core' require 'rspec/core/rake_task' RSpec::Core::RakeTask.new(:spec) do |spec| spec.pattern = FileList['spec/**/*_spec.rb'] end desc "Code coverage detail" task :simplecov do ENV['COVERAGE'] = "true" Rake::Task['spec'].execute end task :default => :spec devise-two-factor-2.0.0/UPGRADING.md000066400000000000000000000005431257613261200167240ustar00rootroot00000000000000# Guide to upgrading from 1.x to 2.x Pull request #43 added a new field to protect against "shoulder-surfing" attacks. If upgrading, you'll need to add the `:consumed_timestep` column to your `Users` model. ```ruby class AddConsumedTimestepToUsers < ActiveRecord::Migration def change add_column :users, :consumed_timestep, :integer end end ``` devise-two-factor-2.0.0/certs/000077500000000000000000000000001257613261200162005ustar00rootroot00000000000000devise-two-factor-2.0.0/certs/tinfoil-cacert.pem000066400000000000000000000050351257613261200216110ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIHSjCCBTKgAwIBAgIJAK2u0LojMCNgMA0GCSqGSIb3DQEBBQUAMIGcMQswCQYD VQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVBhbG8gQWx0bzEfMB0GA1UE ChMWVGluZm9pbCBTZWN1cml0eSwgSW5jLjEfMB0GA1UEAxMWVGluZm9pbCBTZWN1 cml0eSwgSW5jLjEqMCgGCSqGSIb3DQEJARYbc3VwcG9ydEB0aW5mb2lsc2VjdXJp dHkuY29tMB4XDTExMTIyNzA1MDc0N1oXDTIxMTIyNDA1MDc0N1owgZwxCzAJBgNV BAYTAlVTMQswCQYDVQQIEwJDQTESMBAGA1UEBxMJUGFsbyBBbHRvMR8wHQYDVQQK ExZUaW5mb2lsIFNlY3VyaXR5LCBJbmMuMR8wHQYDVQQDExZUaW5mb2lsIFNlY3Vy aXR5LCBJbmMuMSowKAYJKoZIhvcNAQkBFhtzdXBwb3J0QHRpbmZvaWxzZWN1cml0 eS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCqbHvsSj0H0FB1 0gLYoDK1BKugkSB2DZeZZHP6B1UdWRahJXJP9oT1lhfQxx8iX4cgEi7JU3NqA6NR cIRFQ50eH/qlmgs7909gaf8pDaeC0vR3wd0GeRg6qr1eDEnkzIyr/D1AMiX6H1eP Y7J3SfrdaL3gft2iPRKGkgqsXR7oBNLA3n/ShiNgPXqRDl1CCj6aMY0cn5ROFScz vT2FUB4DEwPD2l18m1p99OnXqsOLL2J65qA2+cI8FtgFmlwIi5oSf+URvIdNx+cH lInlAtVHCvAKYLY0dlQ7czMQBcRpYjp2rwPt9f2ksq9b/voMTBABYHFV+IVn8svv GZ5e1+icjtr/R7dCGmCdEdFLXVxafmZhukymG9USv9DKuv1qh7r4q8KaPIE8n7nQ m97jENFfsgnwv+nUmIJ3tzuW5ZxO7A0tIIYdwzt0UjrO3ya4R5bTFXr4bnzZ/g/s CLknWqg1BCRlPd6LnpVGPT0gNDV1pEO25wE3A3Yy0Ujxudcgay/CgUhnlU11qOAc xmar2fhNZsviUhndd/220Ad5QMV2XzcAiopJIeu0juIVGRQM7x2h19Hsp0m6sOEF jfhvbdUa4nvmIFeYFY+hr/YkTmG9ZjyBa8YaZXhwjhSmKCQ374J7mn5e0Cryuvi5 tYhwJn8rdwYZF/h2qqfEu8vaLoD09QIDAQABo4IBizCCAYcwHQYDVR0OBBYEFMmT /x412UH+5OHqgleeTjLOv6iHMIHRBgNVHSMEgckwgcaAFMmT/x412UH+5OHqglee TjLOv6iHoYGipIGfMIGcMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNV BAcTCVBhbG8gQWx0bzEfMB0GA1UEChMWVGluZm9pbCBTZWN1cml0eSwgSW5jLjEf MB0GA1UEAxMWVGluZm9pbCBTZWN1cml0eSwgSW5jLjEqMCgGCSqGSIb3DQEJARYb c3VwcG9ydEB0aW5mb2lsc2VjdXJpdHkuY29tggkAra7QuiMwI2AwDwYDVR0TAQH/ BAUwAwEB/zARBglghkgBhvhCAQEEBAMCAQYwCQYDVR0SBAIwADArBglghkgBhvhC AQ0EHhYcVGlueUNBIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAmBgNVHREEHzAdgRtz dXBwb3J0QHRpbmZvaWxzZWN1cml0eS5jb20wDgYDVR0PAQH/BAQDAgEGMA0GCSqG SIb3DQEBBQUAA4ICAQAD7nsmdg1vStFTi8/P2rgSFxlXxZT0aaVVB1bFBe/m5phb MjvKQ7VAuiFZxEp3oBNdXTi4FzT1QjhRKdlYMgKZQnU+XDLLIYuoi+atxr5qGD4B m58eCGO6ZEutVs3Z7s63UOm5rG0zJ+IEWh8VHMvxgSwiX88QyJuhOtqeiKhIeSGZ 2/qGGMWgsScnPg3J/ZVOIKUn/4ljEDlC64Gh5Zz5PZUbGSXPMhdYbSD3EknDvEGA omYW4jlPMeK3GJgwAZu9yWC8hHGFpiMca/6W0W622cg7MX+CByOd+24dvWFnOHur NHBqI+kZo/7Sjdm8x7TWEOz9Rfh5RPMeVNRTj4iq0B6GzfaecT3Yn8y7HTRRiWns IYpP+iHCFYnZhDZsFi4ccKqxKtj6BGmhLf00FuNpgkvrsU3cXrhidkCaYGYj1SME 1CMfy0PPKVDpDKeFb6y0NvLf4d57vi99dZAvSJEO18rrNEHN2VUfCKRPA/mBSMLY RxKWAby1YVT/8iC9JWix9yvgsEUtTLyOFxLGtgj3PRiQSvbNe/jK4G9WAIFe6R9E 9+HUO2owcmyFXyU3rC/z/lBfDP+2pIRFdUVRGlYCMeUqR08PXpfva5+NQz21fC69 FPRMZvXh70ntnFaWAq+j6NCss+AauC8ckECiQsTgbzJvJd6C3mJXYHkNCQODhg== -----END CERTIFICATE----- devise-two-factor-2.0.0/certs/tinfoilsecurity-gems-cert.pem000066400000000000000000000040251257613261200240240ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIFyjCCA7KgAwIBAgIJAK2u0LojMCNtMA0GCSqGSIb3DQEBDQUAMIGcMQswCQYD VQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVBhbG8gQWx0bzEfMB0GA1UE ChMWVGluZm9pbCBTZWN1cml0eSwgSW5jLjEfMB0GA1UEAxMWVGluZm9pbCBTZWN1 cml0eSwgSW5jLjEqMCgGCSqGSIb3DQEJARYbc3VwcG9ydEB0aW5mb2lsc2VjdXJp dHkuY29tMB4XDTE0MDUyMDIxMTAwMFoXDTE2MDUyMDIxMTAwMFowgZwxCzAJBgNV BAYTAlVTMQswCQYDVQQIEwJDQTESMBAGA1UEBxMJUGFsbyBBbHRvMR8wHQYDVQQK ExZUaW5mb2lsIFNlY3VyaXR5LCBJbmMuMR0wGwYDVQQDExR0aW5mb2lsc2VjdXJp dHktZ2VtczEsMCoGCSqGSIb3DQEJARYdZW5naW5lZXJzQHRpbmZvaWxzZWN1cml0 eS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDNJYNH8D+8lACL t3KzjEIPs3XVBCPaMm2eD/Xk9OOTuDV/NqgMK0icD9MRxMUtS3SCrC9QcPocXT76 f2LQ3yVJuK+rBUasymEES47PIx2czC4n4Hga0xPPuBpioO26oaRFsobyzh9RPOIb nYfpjyqtdrbm+YyM3sPR4XzFirv9xomT4E9T4RCLgOQHTcLKL9K9m+EN7PeVdVUX V0Pa7cVs2vJUKedsd7vnr6Lzbn8ToPk/7J/4W931PbaeI5yg9ZuaRa9K2IaY1TkP I67NW4qKitBVepRlXw6Sb7TYcUncWEQ/eC5CpnOmqUrG5tfGD8cc5aGZOkitW/VX ZgVj81xgCv1hk4HjErrqq4FBNAaCSNyBfwR0TUYqg1lN1nbNjOKwfb6YRn06R2ov cFJG0tmGhsQULCr6fW8u2TfSM+U9WFSIJx2griureY7EZPwg/MgsUiWUWMFemz3G VYXWJR3dN2pW9Uqr3rkjKZbA0bstGWahJO9HuFdDakQxoaTPYPtTQDC+kskkO6lK G1KLIoZ1iLZzB1Ks1vEeyE7lp1imWgpUq+q23PFkt1gIBi/4tGvzsLZye25QU2Y+ XLzldCNm+DyRFXZ+Q+bK33IveUeUWEOv4T1qTXHAOypyzmgodVRG/PrlsSMOBfE5 15kG1mDMGjRcCpEtlskgxUbf7qM7hQIDAQABow0wCzAJBgNVHRMEAjAAMA0GCSqG SIb3DQEBDQUAA4ICAQCLXzGJOr0isGHscTvm73ReEAOv4g0IOSXjfHfHAOWKhzdM g+D8nrzhy8wnARqAt2Ob4I+R9scEIfI5MPp/C5HHqWed4m0W0Uygx3V3qyixavkc nVUJMZ4TPS6W48IHdGGVD45hopx7ycFy+iPm7QUFk4sg044dO53mkScTetm2AvIE xDTotsFNpn/hrAnzXlH4MQQ7LKXAedPmFi8Jv5nJwv9BnEGJQJhvzQtdx7le4S8J a78vwAq429N2gVnfSdeU9v9QqdHOF1215lC6qaDI4bk8hVE4RyMxSBZmWKhnPLAm jFXGc6Wj2yF7HQ2YtEGAAEyjH+tAwF35STv+J3eweUFhyGZPH8Sf+b+UKZc9UF1D J/VmzQd9D9RaB+pOclDYR3Uiji5EBq1La2FPg48hA0uCd4KJ9dBGwNrxvjDNSBW0 FiM1vjR6AVxIAhOwICeWG6QTvH5Jrnq/UDBnnK/KALCbFw3YbbhOyy+295jta7xf r32d4cJAvbDF1C6t2JRjNwi0ANgPw0cytl+8yvCyXpXMZpT0YpDk76XICo97SOHI 5C31v4YyRBnNCp0pN66nxYX2avEiQ8riTBP5mlkPPOhsIoYQHHe2Uj75aVpu0LZ3 cdFzuO4GC1dV0Wv+dsDm+MyF7DT5E9pUPXpnMJuPvPrFpCb+wrFlszW9hGjXbQ== -----END CERTIFICATE----- devise-two-factor-2.0.0/demo/000077500000000000000000000000001257613261200160045ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/.gitignore000066400000000000000000000007221257613261200177750ustar00rootroot00000000000000# See https://help.github.com/articles/ignoring-files for more about ignoring files. # # If you find yourself ignoring temporary files generated by your text editor # or operating system, you probably want to add a global ignore instead: # git config --global core.excludesfile '~/.gitignore_global' # Ignore bundler config. /.bundle # Ignore the default SQLite database. /db/*.sqlite3 /db/*.sqlite3-journal # Ignore all logfiles and tempfiles. /log/*.log /tmp devise-two-factor-2.0.0/demo/Gemfile000066400000000000000000000023301257613261200172750ustar00rootroot00000000000000source 'https://rubygems.org' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' gem 'rails', '4.1.0' # Use postgresql as the database for Active Record gem 'pg' # Use SCSS for stylesheets gem 'sass-rails', '~> 4.0.3' # Use Uglifier as compressor for JavaScript assets gem 'uglifier', '>= 1.3.0' # Use CoffeeScript for .js.coffee assets and views gem 'coffee-rails', '~> 4.0.0' # See https://github.com/sstephenson/execjs#readme for more supported runtimes # gem 'therubyracer', platforms: :ruby # Use jquery as the JavaScript library gem 'jquery-rails' # Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks gem 'turbolinks' # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder gem 'jbuilder', '~> 2.0' # bundle exec rake doc:rails generates the API under doc/api. gem 'sdoc', '~> 0.4.0', group: :doc gem 'devise' gem 'devise-two-factor', :path => '../' gem 'rqrcode-rails3' # Use ActiveModel has_secure_password # gem 'bcrypt', '~> 3.1.7' # Use unicorn as the app server # gem 'unicorn' # Use Capistrano for deployment # gem 'capistrano-rails', group: :development # Use debugger # gem 'debugger', group: [:development, :test] devise-two-factor-2.0.0/demo/README.rdoc000066400000000000000000000007361257613261200176200ustar00rootroot00000000000000== README This README would normally document whatever steps are necessary to get the application up and running. Things you may want to cover: * Ruby version * System dependencies * Configuration * Database creation * Database initialization * How to run the test suite * Services (job queues, cache servers, search engines, etc.) * Deployment instructions * ... Please feel free to use a different markup language if you do not plan to run rake doc:app. devise-two-factor-2.0.0/demo/Rakefile000066400000000000000000000003711257613261200174520ustar00rootroot00000000000000# Add your own tasks in files placed in lib/tasks ending in .rake, # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. require File.expand_path('../config/application', __FILE__) Rails.application.load_tasks devise-two-factor-2.0.0/demo/app/000077500000000000000000000000001257613261200165645ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/app/assets/000077500000000000000000000000001257613261200200665ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/app/assets/images/000077500000000000000000000000001257613261200213335ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/app/assets/images/.keep000066400000000000000000000000001257613261200222460ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/app/assets/javascripts/000077500000000000000000000000001257613261200224175ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/app/assets/javascripts/application.js000066400000000000000000000012301257613261200252540ustar00rootroot00000000000000// This is a manifest file that'll be compiled into application.js, which will include all the files // listed below. // // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. // // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the // compiled file. // // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details // about supported directives. // //= require jquery //= require jquery_ujs //= require turbolinks //= require_tree . devise-two-factor-2.0.0/demo/app/assets/javascripts/home.js.coffee000066400000000000000000000003231257613261200251310ustar00rootroot00000000000000# Place all the behaviors and hooks related to the matching controller here. # All this logic will automatically be available in application.js. # You can use CoffeeScript in this file: http://coffeescript.org/ devise-two-factor-2.0.0/demo/app/assets/stylesheets/000077500000000000000000000000001257613261200224425ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/app/assets/stylesheets/application.css000066400000000000000000000012531257613261200254600ustar00rootroot00000000000000/* * This is a manifest file that'll be compiled into application.css, which will include all the files * listed below. * * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. * * You're free to add application-wide styles to this file and they'll appear at the bottom of the * compiled file so the styles you add here take precedence over styles defined in any styles * defined in the other CSS/SCSS files in this directory. It is generally better to create a new * file per style scope. * *= require_tree . *= require_self */ devise-two-factor-2.0.0/demo/app/assets/stylesheets/home.css.scss000066400000000000000000000002571257613261200250620ustar00rootroot00000000000000// Place all the styles related to the home controller here. // They will automatically be included in application.css. // You can use Sass (SCSS) here: http://sass-lang.com/ devise-two-factor-2.0.0/demo/app/controllers/000077500000000000000000000000001257613261200211325ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/app/controllers/application_controller.rb000066400000000000000000000006141257613261200262260ustar00rootroot00000000000000class ApplicationController < ActionController::Base # Prevent CSRF attacks by raising an exception. # For APIs, you may want to use :null_session instead. protect_from_forgery with: :exception before_action :configure_permitted_parameters, if: :devise_controller? protected def configure_permitted_parameters devise_parameter_sanitizer.for(:sign_in) << :otp_attempt end end devise-two-factor-2.0.0/demo/app/controllers/concerns/000077500000000000000000000000001257613261200227445ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/app/controllers/concerns/.keep000066400000000000000000000000001257613261200236570ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/app/controllers/home_controller.rb000066400000000000000000000001031257613261200246440ustar00rootroot00000000000000class HomeController < ApplicationController def index end end devise-two-factor-2.0.0/demo/app/controllers/users_controller.rb000066400000000000000000000005461257613261200250700ustar00rootroot00000000000000class UsersController < ApplicationController def disable_otp current_user.otp_required_for_login = false current_user.save! redirect_to home_index_path end def enable_otp current_user.otp_secret = User.generate_otp_secret current_user.otp_required_for_login = true current_user.save! redirect_to home_index_path end end devise-two-factor-2.0.0/demo/app/helpers/000077500000000000000000000000001257613261200202265ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/app/helpers/application_helper.rb000066400000000000000000000000351257613261200244130ustar00rootroot00000000000000module ApplicationHelper end devise-two-factor-2.0.0/demo/app/helpers/home_helper.rb000066400000000000000000000000261257613261200230400ustar00rootroot00000000000000module HomeHelper end devise-two-factor-2.0.0/demo/app/mailers/000077500000000000000000000000001257613261200202205ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/app/mailers/.keep000066400000000000000000000000001257613261200211330ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/app/models/000077500000000000000000000000001257613261200200475ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/app/models/.keep000066400000000000000000000000001257613261200207620ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/app/models/concerns/000077500000000000000000000000001257613261200216615ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/app/models/concerns/.keep000066400000000000000000000000001257613261200225740ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/app/models/user.rb000066400000000000000000000011331257613261200213500ustar00rootroot00000000000000class User < ActiveRecord::Base devise :two_factor_authenticatable, :otp_secret_encryption_key => ENV['ENCRYPTION_KEY'] # Set a unique encryption key for your app. # Store your key as an ENV variable and # remember to add it to .gitignore # if you plan to share your code publicly. devise :registerable, :recoverable, :rememberable, :trackable, :validatable end devise-two-factor-2.0.0/demo/app/views/000077500000000000000000000000001257613261200177215ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/app/views/devise/000077500000000000000000000000001257613261200212005ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/app/views/devise/confirmations/000077500000000000000000000000001257613261200240535ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/app/views/devise/confirmations/new.html.erb000066400000000000000000000006101257613261200262760ustar00rootroot00000000000000

Resend confirmation instructions

<%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %> <%= devise_error_messages! %>
<%= f.label :email %>
<%= f.email_field :email, autofocus: true %>
<%= f.submit "Resend confirmation instructions" %>
<% end %> <%= render "devise/shared/links" %> devise-two-factor-2.0.0/demo/app/views/devise/mailer/000077500000000000000000000000001257613261200224515ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/app/views/devise/mailer/confirmation_instructions.html.erb000066400000000000000000000003061257613261200314210ustar00rootroot00000000000000

Welcome <%= @email %>!

You can confirm your account email through the link below:

<%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %>

devise-two-factor-2.0.0/demo/app/views/devise/mailer/reset_password_instructions.html.erb000066400000000000000000000006111257613261200317740ustar00rootroot00000000000000

Hello <%= @resource.email %>!

Someone has requested a link to change your password. You can do this through the link below.

<%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %>

If you didn't request this, please ignore this email.

Your password won't change until you access the link above and create a new one.

devise-two-factor-2.0.0/demo/app/views/devise/mailer/unlock_instructions.html.erb000066400000000000000000000004241257613261200302250ustar00rootroot00000000000000

Hello <%= @resource.email %>!

Your account has been locked due to an excessive number of unsuccessful sign in attempts.

Click the link below to unlock your account:

<%= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token) %>

devise-two-factor-2.0.0/demo/app/views/devise/passwords/000077500000000000000000000000001257613261200232255ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/app/views/devise/passwords/edit.html.erb000066400000000000000000000011411257613261200256040ustar00rootroot00000000000000

Change your password

<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| %> <%= devise_error_messages! %> <%= f.hidden_field :reset_password_token %>
<%= f.label :password, "New password" %>
<%= f.password_field :password, autofocus: true, autocomplete: "off" %>
<%= f.label :password_confirmation, "Confirm new password" %>
<%= f.password_field :password_confirmation, autocomplete: "off" %>
<%= f.submit "Change my password" %>
<% end %> <%= render "devise/shared/links" %> devise-two-factor-2.0.0/demo/app/views/devise/passwords/new.html.erb000066400000000000000000000005741257613261200254610ustar00rootroot00000000000000

Forgot your password?

<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| %> <%= devise_error_messages! %>
<%= f.label :email %>
<%= f.email_field :email, autofocus: true %>
<%= f.submit "Send me reset password instructions" %>
<% end %> <%= render "devise/shared/links" %> devise-two-factor-2.0.0/demo/app/views/devise/registrations/000077500000000000000000000000001257613261200240755ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/app/views/devise/registrations/edit.html.erb000066400000000000000000000022301257613261200264540ustar00rootroot00000000000000

Edit <%= resource_name.to_s.humanize %>

<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %> <%= devise_error_messages! %>
<%= f.label :email %>
<%= f.email_field :email, autofocus: true %>
<% if devise_mapping.confirmable? && resource.pending_reconfirmation? %>
Currently waiting confirmation for: <%= resource.unconfirmed_email %>
<% end %>
<%= f.label :password %> (leave blank if you don't want to change it)
<%= f.password_field :password, autocomplete: "off" %>
<%= f.label :password_confirmation %>
<%= f.password_field :password_confirmation, autocomplete: "off" %>
<%= f.label :current_password %> (we need your current password to confirm your changes)
<%= f.password_field :current_password, autocomplete: "off" %>
<%= f.submit "Update" %>
<% end %>

Cancel my account

Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete %>

<%= link_to "Back", :back %> devise-two-factor-2.0.0/demo/app/views/devise/registrations/new.html.erb000066400000000000000000000010471257613261200263250ustar00rootroot00000000000000

Sign up

<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %> <%= devise_error_messages! %>
<%= f.label :email %>
<%= f.email_field :email, autofocus: true %>
<%= f.label :password %>
<%= f.password_field :password, autocomplete: "off" %>
<%= f.label :password_confirmation %>
<%= f.password_field :password_confirmation, autocomplete: "off" %>
<%= f.submit "Sign up" %>
<% end %> <%= render "devise/shared/links" %> devise-two-factor-2.0.0/demo/app/views/devise/sessions/000077500000000000000000000000001257613261200230465ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/app/views/devise/sessions/new.html.erb000066400000000000000000000011271257613261200252750ustar00rootroot00000000000000

Sign in

<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
<%= f.label :email %>
<%= f.email_field :email, autofocus: true %>
<%= f.label :password %>
<%= f.password_field :password, autocomplete: "off" %>
<%= f.label :otp_attempt %>
<%= f.text_field :otp_attempt %>
<% if devise_mapping.rememberable? -%>
<%= f.check_box :remember_me %> <%= f.label :remember_me %>
<% end -%>
<%= f.submit "Sign in" %>
<% end %> <%= render "devise/shared/links" %> devise-two-factor-2.0.0/demo/app/views/devise/shared/000077500000000000000000000000001257613261200224465ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/app/views/devise/shared/_links.erb000066400000000000000000000021621257613261200244200ustar00rootroot00000000000000<%- if controller_name != 'sessions' %> <%= link_to "Sign in", new_session_path(resource_name) %>
<% end -%> <%- if devise_mapping.registerable? && controller_name != 'registrations' %> <%= link_to "Sign up", new_registration_path(resource_name) %>
<% end -%> <%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %> <%= link_to "Forgot your password?", new_password_path(resource_name) %>
<% end -%> <%- if devise_mapping.confirmable? && controller_name != 'confirmations' %> <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %>
<% end -%> <%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %> <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %>
<% end -%> <%- if devise_mapping.omniauthable? %> <%- resource_class.omniauth_providers.each do |provider| %> <%= link_to "Sign in with #{provider.to_s.titleize}", omniauth_authorize_path(resource_name, provider) %>
<% end -%> <% end -%> devise-two-factor-2.0.0/demo/app/views/devise/unlocks/000077500000000000000000000000001257613261200226565ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/app/views/devise/unlocks/new.html.erb000066400000000000000000000005661257613261200251130ustar00rootroot00000000000000

Resend unlock instructions

<%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %> <%= devise_error_messages! %>
<%= f.label :email %>
<%= f.email_field :email, autofocus: true %>
<%= f.submit "Resend unlock instructions" %>
<% end %> <%= render "devise/shared/links" %> devise-two-factor-2.0.0/demo/app/views/home/000077500000000000000000000000001257613261200206515ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/app/views/home/index.html.erb000066400000000000000000000014241257613261200234160ustar00rootroot00000000000000<% if !current_user %> <%= link_to "Sign up", new_user_registration_path %> <%= link_to "Login", new_user_session_path %> <% end %> <% if current_user %> <% if !current_user.otp_required_for_login %> <%= button_to "Enable 2FA", users_enable_otp_path, :method => :post %> <% end %> <% if current_user.otp_required_for_login %> <%= button_to "Disable 2FA", users_disable_otp_path, :method => :post %> <%= raw RQRCode::render_qrcode(current_user.otp_provisioning_uri(current_user.email, issuer: "Devise-Two-Factor-Demo"), :svg, :level => :l, :unit => 2) %>
<% end %> <%= link_to "Log out", destroy_user_session_path, :method => :delete %> <% end %> devise-two-factor-2.0.0/demo/app/views/layouts/000077500000000000000000000000001257613261200214215ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/app/views/layouts/application.html.erb000066400000000000000000000004651257613261200253660ustar00rootroot00000000000000 DeviseTwoFactorDemo <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %> <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %> <%= csrf_meta_tags %> <%= yield %> devise-two-factor-2.0.0/demo/bin/000077500000000000000000000000001257613261200165545ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/bin/bundle000077500000000000000000000002011257613261200177440ustar00rootroot00000000000000#!/usr/bin/env ruby ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) load Gem.bin_path('bundler', 'bundle') devise-two-factor-2.0.0/demo/bin/rails000077500000000000000000000002221257613261200176100ustar00rootroot00000000000000#!/usr/bin/env ruby APP_PATH = File.expand_path('../../config/application', __FILE__) require_relative '../config/boot' require 'rails/commands' devise-two-factor-2.0.0/demo/bin/rake000077500000000000000000000001321257613261200174200ustar00rootroot00000000000000#!/usr/bin/env ruby require_relative '../config/boot' require 'rake' Rake.application.run devise-two-factor-2.0.0/demo/config.ru000066400000000000000000000002321257613261200176160ustar00rootroot00000000000000# This file is used by Rack-based servers to start the application. require ::File.expand_path('../config/environment', __FILE__) run Rails.application devise-two-factor-2.0.0/demo/config/000077500000000000000000000000001257613261200172515ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/config/application.rb000066400000000000000000000022271257613261200221040ustar00rootroot00000000000000require File.expand_path('../boot', __FILE__) require 'rails/all' # Require the gems listed in Gemfile, including any gems # you've limited to :test, :development, or :production. Bundler.require(*Rails.groups) module DeviseTwoFactorDemo class Application < Rails::Application # Settings in config/environments/* take precedence over those specified here. # Application configuration should go into files in config/initializers # -- all .rb files in that directory are automatically loaded. env_file = File.join(Rails.root, 'config', 'local_env.yml') YAML.load(File.open(env_file)).each do |key, value| ENV[key.to_s] = value end if File.exists?(env_file) # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. # config.time_zone = 'Central Time (US & Canada)' # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] # config.i18n.default_locale = :de end end devise-two-factor-2.0.0/demo/config/boot.rb000066400000000000000000000002521257613261200205400ustar00rootroot00000000000000# Set up gems listed in the Gemfile. ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) devise-two-factor-2.0.0/demo/config/database.yml000066400000000000000000000056611257613261200215500ustar00rootroot00000000000000# PostgreSQL. Versions 8.2 and up are supported. # # Install the pg driver: # gem install pg # On OS X with Homebrew: # gem install pg -- --with-pg-config=/usr/local/bin/pg_config # On OS X with MacPorts: # gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config # On Windows: # gem install pg # Choose the win32 build. # Install PostgreSQL and put its /bin directory on your path. # # Configure Using Gemfile # gem 'pg' # default: &default adapter: postgresql encoding: unicode # For details on connection pooling, see rails configuration guide # http://guides.rubyonrails.org/configuring.html#database-pooling pool: 5 development: <<: *default database: DeviseTwoFactorDemo_development # The specified database role being used to connect to postgres. # To create additional roles in postgres see `$ createuser --help`. # When left blank, postgres will use the default role. This is # the same name as the operating system user that initialized the database. #username: DeviseTwoFactorDemo # The password associated with the postgres role (username). #password: # Connect on a TCP socket. Omitted by default since the client uses a # domain socket that doesn't need configuration. Windows does not have # domain sockets, so uncomment these lines. #host: localhost # The TCP port the server listens on. Defaults to 5432. # If your server runs on a different port number, change accordingly. #port: 5432 # Schema search path. The server defaults to $user,public #schema_search_path: myapp,sharedapp,public # Minimum log levels, in increasing order: # debug5, debug4, debug3, debug2, debug1, # log, notice, warning, error, fatal, and panic # Defaults to warning. #min_messages: notice # Warning: The database defined as "test" will be erased and # re-generated from your development database when you run "rake". # Do not set this db to the same as development or production. test: <<: *default database: DeviseTwoFactorDemo_test # As with config/secrets.yml, you never want to store sensitive information, # like your database password, in your source code. If your source code is # ever seen by anyone, they now have access to your database. # # Instead, provide the password as a unix environment variable when you boot # the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database # for a full rundown on how to provide these environment variables in a # production deployment. # # On Heroku and other platform providers, you may have a full connection URL # available as an environment variable. For example: # # DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase" # # You can use this database configuration with: # # production: # url: <%= ENV['DATABASE_URL'] %> # production: <<: *default database: DeviseTwoFactorDemo_production username: DeviseTwoFactorDemo password: <%= ENV['DEVISETWOFACTORDEMO_DATABASE_PASSWORD'] %> devise-two-factor-2.0.0/demo/config/environment.rb000066400000000000000000000002261257613261200221420ustar00rootroot00000000000000# Load the Rails application. require File.expand_path('../application', __FILE__) # Initialize the Rails application. Rails.application.initialize! devise-two-factor-2.0.0/demo/config/environments/000077500000000000000000000000001257613261200220005ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/config/environments/development.rb000066400000000000000000000026161257613261200246540ustar00rootroot00000000000000Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. # In the development environment your application's code is reloaded on # every request. This slows down response time but is perfect for development # since you don't have to restart the web server when you make code changes. config.cache_classes = false # Do not eager load code on boot. config.eager_load = false # Show full error reports and disable caching. config.consider_all_requests_local = true config.action_controller.perform_caching = false # Don't care if the mailer can't send. config.action_mailer.raise_delivery_errors = false # Print deprecation notices to the Rails logger. config.active_support.deprecation = :log # Raise an error on page load if there are pending migrations. config.active_record.migration_error = :page_load # Debug mode disables concatenation and preprocessing of assets. # This option may cause significant delays in view rendering with a large # number of complex assets. config.assets.debug = true # Adds additional error checking when serving assets at runtime. # Checks for improperly declared sprockets dependencies. # Raises helpful error messages. config.assets.raise_runtime_errors = true # Raises error for missing translations # config.action_view.raise_on_missing_translations = true end devise-two-factor-2.0.0/demo/config/environments/production.rb000066400000000000000000000064261257613261200245230ustar00rootroot00000000000000Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. # Code is not reloaded between requests. config.cache_classes = true # Eager load code on boot. This eager loads most of Rails and # your application in memory, allowing both threaded web servers # and those relying on copy on write to perform better. # Rake tasks automatically ignore this option for performance. config.eager_load = true # Full error reports are disabled and caching is turned on. config.consider_all_requests_local = false config.action_controller.perform_caching = true # Enable Rack::Cache to put a simple HTTP cache in front of your application # Add `rack-cache` to your Gemfile before enabling this. # For large-scale production use, consider using a caching reverse proxy like nginx, varnish or squid. # config.action_dispatch.rack_cache = true # Disable Rails's static asset server (Apache or nginx will already do this). config.serve_static_assets = false # Compress JavaScripts and CSS. config.assets.js_compressor = :uglifier # config.assets.css_compressor = :sass # Do not fallback to assets pipeline if a precompiled asset is missed. config.assets.compile = false # Generate digests for assets URLs. config.assets.digest = true # Version of your assets, change this if you want to expire all your assets. config.assets.version = '1.0' # Specifies the header that your server uses for sending files. # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. # config.force_ssl = true # Set to :debug to see everything in the log. config.log_level = :info # Prepend all log lines with the following tags. # config.log_tags = [ :subdomain, :uuid ] # Use a different logger for distributed setups. # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) # Use a different cache store in production. # config.cache_store = :mem_cache_store # Enable serving of images, stylesheets, and JavaScripts from an asset server. # config.action_controller.asset_host = "http://assets.example.com" # Precompile additional assets. # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. # config.assets.precompile += %w( search.js ) # Ignore bad email addresses and do not raise email delivery errors. # Set this to true and configure the email server for immediate delivery to raise delivery errors. # config.action_mailer.raise_delivery_errors = false # Enable locale fallbacks for I18n (makes lookups for any locale fall back to # the I18n.default_locale when a translation cannot be found). config.i18n.fallbacks = true # Send deprecation notices to registered listeners. config.active_support.deprecation = :notify # Disable automatic flushing of the log to improve performance. # config.autoflush_log = false # Use default logging formatter so that PID and timestamp are not suppressed. config.log_formatter = ::Logger::Formatter.new # Do not dump schema after migrations. config.active_record.dump_schema_after_migration = false end devise-two-factor-2.0.0/demo/config/environments/test.rb000066400000000000000000000031751257613261200233120ustar00rootroot00000000000000Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. # The test environment is used exclusively to run your application's # test suite. You never need to work with it otherwise. Remember that # your test database is "scratch space" for the test suite and is wiped # and recreated between test runs. Don't rely on the data there! config.cache_classes = true # Do not eager load code on boot. This avoids loading your whole application # just for the purpose of running a single test. If you are using a tool that # preloads Rails for running tests, you may have to set it to true. config.eager_load = false # Configure static asset server for tests with Cache-Control for performance. config.serve_static_assets = true config.static_cache_control = 'public, max-age=3600' # Show full error reports and disable caching. config.consider_all_requests_local = true config.action_controller.perform_caching = false # Raise exceptions instead of rendering exception templates. config.action_dispatch.show_exceptions = false # Disable request forgery protection in test environment. config.action_controller.allow_forgery_protection = false # Tell Action Mailer not to deliver emails to the real world. # The :test delivery method accumulates sent emails in the # ActionMailer::Base.deliveries array. config.action_mailer.delivery_method = :test # Print deprecation notices to the stderr. config.active_support.deprecation = :stderr # Raises error for missing translations # config.action_view.raise_on_missing_translations = true end devise-two-factor-2.0.0/demo/config/initializers/000077500000000000000000000000001257613261200217575ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/config/initializers/backtrace_silencers.rb000066400000000000000000000006241257613261200262740ustar00rootroot00000000000000# Be sure to restart your server when you modify this file. # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. # Rails.backtrace_cleaner.remove_silencers! devise-two-factor-2.0.0/demo/config/initializers/cookies_serializer.rb000066400000000000000000000002001257613261200261610ustar00rootroot00000000000000# Be sure to restart your server when you modify this file. Rails.application.config.action_dispatch.cookies_serializer = :jsondevise-two-factor-2.0.0/demo/config/initializers/devise.rb000066400000000000000000000011161257613261200235620ustar00rootroot00000000000000Devise.setup do |config| config.warden do |manager| manager.default_strategies(:scope => :user).unshift :two_factor_authenticatable end config.mailer_sender = 'please-change-me-at-config-initializers-devise@example.com' require 'devise/orm/active_record' config.case_insensitive_keys = [ :email ] config.strip_whitespace_keys = [ :email ] config.skip_session_storage = [:http_auth] config.stretches = Rails.env.test? ? 1 : 10 config.reconfirmable = true config.password_length = 8..128 config.reset_password_within = 6.hours config.sign_out_via = :delete end devise-two-factor-2.0.0/demo/config/initializers/filter_parameter_logging.rb000066400000000000000000000003021257613261200273320ustar00rootroot00000000000000# Be sure to restart your server when you modify this file. # Configure sensitive parameters which will be filtered from the log file. Rails.application.config.filter_parameters += [:password] devise-two-factor-2.0.0/demo/config/initializers/inflections.rb000066400000000000000000000012071257613261200246210ustar00rootroot00000000000000# Be sure to restart your server when you modify this file. # Add new inflection rules using the following format. Inflections # are locale specific, and you may define rules for as many different # locales as you wish. All of these examples are active by default: # ActiveSupport::Inflector.inflections(:en) do |inflect| # inflect.plural /^(ox)$/i, '\1en' # inflect.singular /^(ox)en/i, '\1' # inflect.irregular 'person', 'people' # inflect.uncountable %w( fish sheep ) # end # These inflection rules are supported but not enabled by default: # ActiveSupport::Inflector.inflections(:en) do |inflect| # inflect.acronym 'RESTful' # end devise-two-factor-2.0.0/demo/config/initializers/mime_types.rb000066400000000000000000000002341257613261200244560ustar00rootroot00000000000000# Be sure to restart your server when you modify this file. # Add new mime types for use in respond_to blocks: # Mime::Type.register "text/richtext", :rtf devise-two-factor-2.0.0/demo/config/initializers/session_store.rb000066400000000000000000000002271257613261200252040ustar00rootroot00000000000000# Be sure to restart your server when you modify this file. Rails.application.config.session_store :cookie_store, key: '_DeviseTwoFactorDemo_session' devise-two-factor-2.0.0/demo/config/initializers/wrap_parameters.rb000066400000000000000000000010051257613261200254740ustar00rootroot00000000000000# Be sure to restart your server when you modify this file. # This file contains settings for ActionController::ParamsWrapper which # is enabled by default. # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. ActiveSupport.on_load(:action_controller) do wrap_parameters format: [:json] if respond_to?(:wrap_parameters) end # To enable root element in JSON for ActiveRecord objects. # ActiveSupport.on_load(:active_record) do # self.include_root_in_json = true # end devise-two-factor-2.0.0/demo/config/locales/000077500000000000000000000000001257613261200206735ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/config/locales/devise.en.yml000066400000000000000000000075121257613261200233030ustar00rootroot00000000000000# Additional translations at https://github.com/plataformatec/devise/wiki/I18n en: devise: confirmations: confirmed: "Your account was successfully confirmed." send_instructions: "You will receive an email with instructions about how to confirm your account in a few minutes." send_paranoid_instructions: "If your email address exists in our database, you will receive an email with instructions about how to confirm your account in a few minutes." failure: already_authenticated: "You are already signed in." inactive: "Your account is not activated yet." invalid: "Invalid email or password." locked: "Your account is locked." last_attempt: "You have one more attempt before your account will be locked." not_found_in_database: "Invalid email or password." timeout: "Your session expired. Please sign in again to continue." unauthenticated: "You need to sign in or sign up before continuing." unconfirmed: "You have to confirm your account before continuing." mailer: confirmation_instructions: subject: "Confirmation instructions" reset_password_instructions: subject: "Reset password instructions" unlock_instructions: subject: "Unlock Instructions" omniauth_callbacks: failure: "Could not authenticate you from %{kind} because \"%{reason}\"." success: "Successfully authenticated from %{kind} account." passwords: no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided." send_instructions: "You will receive an email with instructions on how to reset your password in a few minutes." send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes." updated: "Your password was changed successfully. You are now signed in." updated_not_active: "Your password was changed successfully." registrations: destroyed: "Bye! Your account was successfully cancelled. We hope to see you again soon." signed_up: "Welcome! You have signed up successfully." signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated." signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked." signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please open the link to activate your account." update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and click on the confirm link to finalize confirming your new email address." updated: "You updated your account successfully." sessions: signed_in: "Signed in successfully." signed_out: "Signed out successfully." unlocks: send_instructions: "You will receive an email with instructions about how to unlock your account in a few minutes." send_paranoid_instructions: "If your account exists, you will receive an email with instructions about how to unlock it in a few minutes." unlocked: "Your account has been unlocked successfully. Please sign in to continue." errors: messages: already_confirmed: "was already confirmed, please try signing in" confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one" expired: "has expired, please request a new one" not_found: "not found" not_locked: "was not locked" not_saved: one: "1 error prohibited this %{resource} from being saved:" other: "%{count} errors prohibited this %{resource} from being saved:" devise-two-factor-2.0.0/demo/config/locales/en.yml000066400000000000000000000011721257613261200220210ustar00rootroot00000000000000# Files in the config/locales directory are used for internationalization # and are automatically loaded by Rails. If you want to use locales other # than English, add the necessary files in this directory. # # To use the locales, use `I18n.t`: # # I18n.t 'hello' # # In views, this is aliased to just `t`: # # <%= t('hello') %> # # To use a different locale, set it with `I18n.locale`: # # I18n.locale = :es # # This would use the information in config/locales/es.yml. # # To learn more, please read the Rails Internationalization guide # available at http://guides.rubyonrails.org/i18n.html. en: hello: "Hello world" devise-two-factor-2.0.0/demo/config/routes.rb000066400000000000000000000002571257613261200211230ustar00rootroot00000000000000Rails.application.routes.draw do get 'home/index' post 'users/enable_otp' post 'users/disable_otp' devise_for :users root to: "home#index", via: [:get, :post] end devise-two-factor-2.0.0/demo/config/secrets.yml000066400000000000000000000017041257613261200214460ustar00rootroot00000000000000# Be sure to restart your server when you modify this file. # Your secret key is used for verifying the integrity of signed cookies. # If you change this key, all old signed cookies will become invalid! # Make sure the secret is at least 30 characters and all random, # no regular words or you'll be exposed to dictionary attacks. # You can use `rake secret` to generate a secure secret key. # Make sure the secrets in this file are kept private # if you're sharing your code publicly. development: secret_key_base: c8c845b85a69d2b67ec99c1912ddcde63f0fcd05a74052f396daf68a2688d576c203bdc239c9423a0cf88218e201d30616959d5eeceea00eedf915677ecd373b test: secret_key_base: df468dd66c4b0f143354d216d96cdb8542ffe215afd78ebf977f7a8bfdb93010db134320cb738d27385bd7884cd73f3f22f90b147d4d176c3c8d0d63d5427fa9 # Do not keep production secrets in the repository, # instead read values from the environment. production: secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> devise-two-factor-2.0.0/demo/db/000077500000000000000000000000001257613261200163715ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/db/migrate/000077500000000000000000000000001257613261200200215ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/db/migrate/20140515190128_devise_create_users.rb000066400000000000000000000025001257613261200260340ustar00rootroot00000000000000class DeviseCreateUsers < ActiveRecord::Migration def change create_table(:users) do |t| ## Database authenticatable t.string :email, null: false, default: "" t.string :encrypted_password, null: false, default: "" ## Recoverable t.string :reset_password_token t.datetime :reset_password_sent_at ## Rememberable t.datetime :remember_created_at ## Trackable t.integer :sign_in_count, default: 0, null: false t.datetime :current_sign_in_at t.datetime :last_sign_in_at t.string :current_sign_in_ip t.string :last_sign_in_ip ## Confirmable # t.string :confirmation_token # t.datetime :confirmed_at # t.datetime :confirmation_sent_at # t.string :unconfirmed_email # Only if using reconfirmable ## Lockable # t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts # t.string :unlock_token # Only if unlock strategy is :email or :both # t.datetime :locked_at t.timestamps end add_index :users, :email, unique: true add_index :users, :reset_password_token, unique: true # add_index :users, :confirmation_token, unique: true # add_index :users, :unlock_token, unique: true end end devise-two-factor-2.0.0/demo/db/migrate/20140516191259_add_devise_two_factor_to_users.rb000066400000000000000000000005501257613261200302640ustar00rootroot00000000000000class AddDeviseTwoFactorToUsers < ActiveRecord::Migration def change add_column :users, :encrypted_otp_secret, :string add_column :users, :encrypted_otp_secret_iv, :string add_column :users, :encrypted_otp_secret_salt, :string add_column :users, :consumed_timestep, :integer add_column :users, :otp_required_for_login, :boolean end end devise-two-factor-2.0.0/demo/db/schema.rb000066400000000000000000000035361257613261200201650ustar00rootroot00000000000000# encoding: UTF-8 # This file is auto-generated from the current state of the database. Instead # of editing this file, please use the migrations feature of Active Record to # incrementally modify your database, and then regenerate this schema definition. # # Note that this schema.rb definition is the authoritative source for your # database schema. If you need to create the application database on another # system, you should be using db:schema:load, not running all the migrations # from scratch. The latter is a flawed and unsustainable approach (the more migrations # you'll amass, the slower it'll run and the greater likelihood for issues). # # It's strongly recommended that you check this file into your version control system. ActiveRecord::Schema.define(version: 20140516191259) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" create_table "users", force: true do |t| t.string "email", default: "", null: false t.string "encrypted_password", default: "", null: false t.string "reset_password_token" t.datetime "reset_password_sent_at" t.datetime "remember_created_at" t.integer "sign_in_count", default: 0, null: false t.datetime "current_sign_in_at" t.datetime "last_sign_in_at" t.string "current_sign_in_ip" t.string "last_sign_in_ip" t.datetime "created_at" t.datetime "updated_at" t.string "encrypted_otp_secret" t.string "encrypted_otp_secret_iv" t.string "encrypted_otp_secret_salt" t.integer "consumed_timestep" t.boolean "otp_required_for_login" end add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree end devise-two-factor-2.0.0/demo/db/seeds.rb000066400000000000000000000005271257613261200200250ustar00rootroot00000000000000# This file should contain all the record creation needed to seed the database with its default values. # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). # # Examples: # # cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }]) # Mayor.create(name: 'Emanuel', city: cities.first) devise-two-factor-2.0.0/demo/lib/000077500000000000000000000000001257613261200165525ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/lib/assets/000077500000000000000000000000001257613261200200545ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/lib/assets/.keep000066400000000000000000000000001257613261200207670ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/lib/tasks/000077500000000000000000000000001257613261200176775ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/lib/tasks/.keep000066400000000000000000000000001257613261200206120ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/log/000077500000000000000000000000001257613261200165655ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/log/.keep000066400000000000000000000000001257613261200175000ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/public/000077500000000000000000000000001257613261200172625ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/public/404.html000066400000000000000000000030341257613261200204570ustar00rootroot00000000000000 The page you were looking for doesn't exist (404)

The page you were looking for doesn't exist.

You may have mistyped the address or the page may have moved.

If you are the application owner check the logs for more information.

devise-two-factor-2.0.0/demo/public/422.html000066400000000000000000000030131257613261200204540ustar00rootroot00000000000000 The change you wanted was rejected (422)

The change you wanted was rejected.

Maybe you tried to change something you didn't have access to.

If you are the application owner check the logs for more information.

devise-two-factor-2.0.0/demo/public/500.html000066400000000000000000000027051257613261200204600ustar00rootroot00000000000000 We're sorry, but something went wrong (500)

We're sorry, but something went wrong.

If you are the application owner check the logs for more information.

devise-two-factor-2.0.0/demo/public/favicon.ico000066400000000000000000000000001257613261200213710ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/public/robots.txt000066400000000000000000000003121257613261200213270ustar00rootroot00000000000000# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file # # To ban all spiders from the entire site uncomment the next two lines: # User-agent: * # Disallow: / devise-two-factor-2.0.0/demo/test/000077500000000000000000000000001257613261200167635ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/test/controllers/000077500000000000000000000000001257613261200213315ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/test/controllers/.keep000066400000000000000000000000001257613261200222440ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/test/controllers/home_controller_test.rb000066400000000000000000000002411257613261200261050ustar00rootroot00000000000000require 'test_helper' class HomeControllerTest < ActionController::TestCase test "should get index" do get :index assert_response :success end end devise-two-factor-2.0.0/demo/test/fixtures/000077500000000000000000000000001257613261200206345ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/test/fixtures/.keep000066400000000000000000000000001257613261200215470ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/test/fixtures/users.yml000066400000000000000000000005431257613261200225220ustar00rootroot00000000000000# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html # This model initially had no columns defined. If you add columns to the # model remove the '{}' from the fixture names and add the columns immediately # below each fixture, per the syntax in the comments below # one: {} # column: value # two: {} # column: value devise-two-factor-2.0.0/demo/test/helpers/000077500000000000000000000000001257613261200204255ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/test/helpers/.keep000066400000000000000000000000001257613261200213400ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/test/helpers/home_helper_test.rb000066400000000000000000000001071257613261200242760ustar00rootroot00000000000000require 'test_helper' class HomeHelperTest < ActionView::TestCase end devise-two-factor-2.0.0/demo/test/integration/000077500000000000000000000000001257613261200213065ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/test/integration/.keep000066400000000000000000000000001257613261200222210ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/test/mailers/000077500000000000000000000000001257613261200204175ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/test/mailers/.keep000066400000000000000000000000001257613261200213320ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/test/models/000077500000000000000000000000001257613261200202465ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/test/models/.keep000066400000000000000000000000001257613261200211610ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/test/models/user_test.rb000066400000000000000000000001661257613261200226130ustar00rootroot00000000000000require 'test_helper' class UserTest < ActiveSupport::TestCase # test "the truth" do # assert true # end end devise-two-factor-2.0.0/demo/test/test_helper.rb000066400000000000000000000007021257613261200216250ustar00rootroot00000000000000ENV['RAILS_ENV'] ||= 'test' require File.expand_path('../../config/environment', __FILE__) require 'rails/test_help' class ActiveSupport::TestCase # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. # # Note: You'll currently still have to declare fixtures explicitly in integration tests # -- they do not yet inherit this setting fixtures :all # Add more helper methods to be used by all tests here... end devise-two-factor-2.0.0/demo/vendor/000077500000000000000000000000001257613261200173015ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/vendor/assets/000077500000000000000000000000001257613261200206035ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/vendor/assets/javascripts/000077500000000000000000000000001257613261200231345ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/vendor/assets/javascripts/.keep000066400000000000000000000000001257613261200240470ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/vendor/assets/stylesheets/000077500000000000000000000000001257613261200231575ustar00rootroot00000000000000devise-two-factor-2.0.0/demo/vendor/assets/stylesheets/.keep000066400000000000000000000000001257613261200240720ustar00rootroot00000000000000devise-two-factor-2.0.0/devise-two-factor.gemspec000066400000000000000000000027621257613261200217760ustar00rootroot00000000000000$:.push File.expand_path('../lib', __FILE__) require 'devise_two_factor/version' Gem::Specification.new do |s| s.name = 'devise-two-factor' s.version = DeviseTwoFactor::VERSION.dup s.platform = Gem::Platform::RUBY s.licenses = ['MIT'] s.summary = 'Barebones two-factor authentication with Devise' s.email = 'engineers@tinfoilsecurity.com' s.homepage = 'https://github.com/tinfoil/devise-two-factor' s.description = 'Barebones two-factor authentication with Devise' s.authors = ['Shane Wilton'] s.cert_chain = [ 'certs/tinfoil-cacert.pem', 'certs/tinfoilsecurity-gems-cert.pem' ] s.signing_key = File.expand_path("~/.ssh/tinfoilsecurity-gems-key.pem") if $0 =~ /gem\z/ s.rubyforge_project = 'devise-two-factor' s.files = `git ls-files`.split("\n").delete_if { |x| x.match('demo/*') } s.test_files = `git ls-files -- spec/*`.split("\n") s.require_paths = ['lib'] s.add_runtime_dependency 'railties' s.add_runtime_dependency 'activesupport' s.add_runtime_dependency 'attr_encrypted', '~> 1.3.2' s.add_runtime_dependency 'devise', '~> 3.5.0' s.add_runtime_dependency 'rotp', '~> 2' s.add_development_dependency 'activemodel' s.add_development_dependency 'bundler', '> 1.0' s.add_development_dependency 'rspec', '> 3' s.add_development_dependency 'simplecov' s.add_development_dependency 'faker' s.add_development_dependency 'timecop' end devise-two-factor-2.0.0/lib/000077500000000000000000000000001257613261200156265ustar00rootroot00000000000000devise-two-factor-2.0.0/lib/devise-two-factor.rb000066400000000000000000000021041257613261200215120ustar00rootroot00000000000000require 'devise' require 'devise_two_factor/models' require 'devise_two_factor/strategies' module Devise # The length of generated OTP secrets mattr_accessor :otp_secret_length @@otp_secret_length = 128 # The number of seconds before and after the current # time for which codes will be accepted mattr_accessor :otp_allowed_drift @@otp_allowed_drift = 30 # The key used to encrypt OTP secrets in the database mattr_accessor :otp_secret_encryption_key @@otp_secret_encryption_key = nil # The length of all generated OTP backup codes mattr_accessor :otp_backup_code_length @@otp_backup_code_length = 16 # The number of backup codes generated by a call to # generate_otp_backup_codes! mattr_accessor :otp_number_of_backup_codes @@otp_number_of_backup_codes = 5 end Devise.add_module(:two_factor_authenticatable, :route => :session, :strategy => true, :controller => :sessions, :model => true) Devise.add_module(:two_factor_backupable, :route => :session, :strategy => true, :controller => :sessions, :model => true) devise-two-factor-2.0.0/lib/devise_two_factor/000077500000000000000000000000001257613261200213345ustar00rootroot00000000000000devise-two-factor-2.0.0/lib/devise_two_factor/models.rb000066400000000000000000000001671257613261200231500ustar00rootroot00000000000000require 'devise_two_factor/models/two_factor_authenticatable' require 'devise_two_factor/models/two_factor_backupable' devise-two-factor-2.0.0/lib/devise_two_factor/models/000077500000000000000000000000001257613261200226175ustar00rootroot00000000000000devise-two-factor-2.0.0/lib/devise_two_factor/models/two_factor_authenticatable.rb000066400000000000000000000044721257613261200305370ustar00rootroot00000000000000require 'attr_encrypted' require 'rotp' module Devise module Models module TwoFactorAuthenticatable extend ActiveSupport::Concern include Devise::Models::DatabaseAuthenticatable included do attr_encrypted :otp_secret, :key => self.otp_secret_encryption_key, :mode => :per_attribute_iv_and_salt unless self.attr_encrypted?(:otp_secret) attr_accessor :otp_attempt end def self.required_fields(klass) [:encrypted_otp_secret, :encrypted_otp_secret_iv, :encrypted_otp_secret_salt, :consumed_timestep] end # This defaults to the model's otp_secret # If this hasn't been generated yet, pass a secret as an option def validate_and_consume_otp!(code, options = {}) otp_secret = options[:otp_secret] || self.otp_secret return false unless otp_secret.present? totp = self.otp(otp_secret) return consume_otp! if totp.verify_with_drift(code, self.class.otp_allowed_drift) false end def otp(otp_secret = self.otp_secret) ROTP::TOTP.new(otp_secret) end def current_otp otp.at(Time.now) end # ROTP's TOTP#timecode is private, so we duplicate it here def current_otp_timestep Time.now.utc.to_i / otp.interval end def otp_provisioning_uri(account, options = {}) otp_secret = options[:otp_secret] || self.otp_secret ROTP::TOTP.new(otp_secret, options).provisioning_uri(account) end def clean_up_passwords self.otp_attempt = nil end protected # An OTP cannot be used more than once in a given timestep # Storing timestep of last valid OTP is sufficient to satisfy this requirement def consume_otp! if self.consumed_timestep != current_otp_timestep self.consumed_timestep = current_otp_timestep return save(validate: false) end false end module ClassMethods Devise::Models.config(self, :otp_secret_length, :otp_allowed_drift, :otp_secret_encryption_key) def generate_otp_secret(otp_secret_length = self.otp_secret_length) ROTP::Base32.random_base32(otp_secret_length) end end end end end devise-two-factor-2.0.0/lib/devise_two_factor/models/two_factor_backupable.rb000066400000000000000000000033331257613261200274660ustar00rootroot00000000000000module Devise module Models # TwoFactorBackupable allows a user to generate backup codes which # provide one-time access to their account in the event that they have # lost access to their two-factor device module TwoFactorBackupable extend ActiveSupport::Concern def self.required_fields(klass) [:otp_backup_codes] end # 1) Invalidates all existing backup codes # 2) Generates otp_number_of_backup_codes backup codes # 3) Stores the hashed backup codes in the database # 4) Returns a plaintext array of the generated backup codes def generate_otp_backup_codes! codes = [] number_of_codes = self.class.otp_number_of_backup_codes code_length = self.class.otp_backup_code_length number_of_codes.times do codes << SecureRandom.hex(code_length / 2) # Hexstring has length 2*n end hashed_codes = codes.map { |code| Devise::Encryptor.digest(self.class, code) } self.otp_backup_codes = hashed_codes codes end # Returns true and invalidates the given code # iff that code is a valid backup code. def invalidate_otp_backup_code!(code) codes = self.otp_backup_codes || [] codes.each do |backup_code| next unless Devise::Encryptor.compare(self.class, backup_code, code) codes.delete(backup_code) self.otp_backup_codes = codes return true end false end protected module ClassMethods Devise::Models.config(self, :otp_backup_code_length, :otp_number_of_backup_codes, :pepper) end end end end devise-two-factor-2.0.0/lib/devise_two_factor/spec_helpers.rb000066400000000000000000000002431257613261200243340ustar00rootroot00000000000000require 'devise_two_factor/spec_helpers/two_factor_authenticatable_shared_examples' require 'devise_two_factor/spec_helpers/two_factor_backupable_shared_examples' devise-two-factor-2.0.0/lib/devise_two_factor/spec_helpers/000077500000000000000000000000001257613261200240105ustar00rootroot00000000000000two_factor_authenticatable_shared_examples.rb000066400000000000000000000066121257613261200350730ustar00rootroot00000000000000devise-two-factor-2.0.0/lib/devise_two_factor/spec_helpersshared_examples 'two_factor_authenticatable' do before :each do subject.otp_secret = subject.class.generate_otp_secret end describe 'required_fields' do it 'should have the attr_encrypted fields for otp_secret' do expect(Devise::Models::TwoFactorAuthenticatable.required_fields(subject.class)).to contain_exactly(:encrypted_otp_secret, :encrypted_otp_secret_iv, :encrypted_otp_secret_salt, :consumed_timestep) end end describe '#otp_secret' do it 'should be of the configured length' do expect(subject.otp_secret.length).to eq(subject.class.otp_secret_length) end it 'stores the encrypted otp_secret' do expect(subject.encrypted_otp_secret).to_not be_nil end it 'stores an iv for otp_secret' do expect(subject.encrypted_otp_secret_iv).to_not be_nil end it 'stores a salt for otp_secret' do expect(subject.encrypted_otp_secret_salt).to_not be_nil end end describe '#validate_and_consume_otp!' do let(:otp_secret) { '2z6hxkdwi3uvrnpn' } before :each do Timecop.freeze(Time.current) subject.otp_secret = otp_secret end after :each do Timecop.return end context 'with a stored consumed_timestep' do context 'given a precisely correct OTP' do let(:consumed_otp) { ROTP::TOTP.new(otp_secret).at(Time.now) } before do subject.validate_and_consume_otp!(consumed_otp) end it 'fails to validate' do expect(subject.validate_and_consume_otp!(consumed_otp)).to be false end end context 'given a previously valid OTP within the allowed drift' do let(:consumed_otp) { ROTP::TOTP.new(otp_secret).at(Time.now + subject.class.otp_allowed_drift, true) } before do subject.validate_and_consume_otp!(consumed_otp) end it 'fails to validate' do expect(subject.validate_and_consume_otp!(consumed_otp)).to be false end end end it 'validates a precisely correct OTP' do otp = ROTP::TOTP.new(otp_secret).at(Time.now) expect(subject.validate_and_consume_otp!(otp)).to be true end it 'validates an OTP within the allowed drift' do otp = ROTP::TOTP.new(otp_secret).at(Time.now + subject.class.otp_allowed_drift, true) expect(subject.validate_and_consume_otp!(otp)).to be true end it 'does not validate an OTP above the allowed drift' do otp = ROTP::TOTP.new(otp_secret).at(Time.now + subject.class.otp_allowed_drift * 2, true) expect(subject.validate_and_consume_otp!(otp)).to be false end it 'does not validate an OTP below the allowed drift' do otp = ROTP::TOTP.new(otp_secret).at(Time.now - subject.class.otp_allowed_drift * 2, true) expect(subject.validate_and_consume_otp!(otp)).to be false end end describe '#otp_provisioning_uri' do let(:otp_secret_length) { subject.class.otp_secret_length } let(:account) { Faker::Internet.email } let(:issuer) { "Tinfoil" } it "should return uri with specified account" do expect(subject.otp_provisioning_uri(account)).to match(%r{otpauth://totp/#{account}\?secret=\w{#{otp_secret_length}}}) end it 'should return uri with issuer option' do expect(subject.otp_provisioning_uri(account, issuer: issuer)).to match(%r{otpauth://totp/#{account}\?secret=\w{#{otp_secret_length}}&issuer=#{issuer}$}) end end end devise-two-factor-2.0.0/lib/devise_two_factor/spec_helpers/two_factor_backupable_shared_examples.rb000066400000000000000000000053451257613261200341100ustar00rootroot00000000000000shared_examples 'two_factor_backupable' do describe 'required_fields' do it 'has the attr_encrypted fields for otp_backup_codes' do expect(Devise::Models::TwoFactorBackupable.required_fields(subject.class)).to contain_exactly(:otp_backup_codes) end end describe '#generate_otp_backup_codes!' do context 'with no existing recovery codes' do before do @plaintext_codes = subject.generate_otp_backup_codes! end it 'generates the correct number of new recovery codes' do expect(subject.otp_backup_codes.length).to eq(subject.class.otp_number_of_backup_codes) end it 'generates recovery codes of the correct length' do @plaintext_codes.each do |code| expect(code.length).to eq(subject.class.otp_backup_code_length) end end it 'generates distinct recovery codes' do expect(@plaintext_codes.uniq).to contain_exactly(*@plaintext_codes) end it 'stores the codes as BCrypt hashes' do subject.otp_backup_codes.each do |code| # $algorithm$cost$(22 character salt + 31 character hash) expect(code).to match(/\A\$[0-9a-z]{2}\$[0-9]{2}\$[A-Za-z0-9\.\/]{53}\z/) end end end context 'with existing recovery codes' do let(:old_codes) { Faker::Lorem.words } let(:old_codes_hashed) { old_codes.map { |x| Devise::Encryptor.digest(subject.class, x) } } before do subject.otp_backup_codes = old_codes_hashed @plaintext_codes = subject.generate_otp_backup_codes! end it 'invalidates the existing recovery codes' do expect((subject.otp_backup_codes & old_codes_hashed)).to match [] end end end describe '#invalidate_otp_backup_code!' do before do @plaintext_codes = subject.generate_otp_backup_codes! end context 'given an invalid recovery code' do it 'returns false' do expect(subject.invalidate_otp_backup_code!('password')).to be false end end context 'given a valid recovery code' do it 'returns true' do @plaintext_codes.each do |code| expect(subject.invalidate_otp_backup_code!(code)).to be true end end it 'invalidates that recovery code' do code = @plaintext_codes.sample subject.invalidate_otp_backup_code!(code) expect(subject.invalidate_otp_backup_code!(code)).to be false end it 'does not invalidate the other recovery codes' do code = @plaintext_codes.sample subject.invalidate_otp_backup_code!(code) @plaintext_codes.delete(code) @plaintext_codes.each do |code| expect(subject.invalidate_otp_backup_code!(code)).to be true end end end end end devise-two-factor-2.0.0/lib/devise_two_factor/strategies.rb000066400000000000000000000001771257613261200240400ustar00rootroot00000000000000require 'devise_two_factor/strategies/two_factor_authenticatable' require 'devise_two_factor/strategies/two_factor_backupable' devise-two-factor-2.0.0/lib/devise_two_factor/strategies/000077500000000000000000000000001257613261200235065ustar00rootroot00000000000000devise-two-factor-2.0.0/lib/devise_two_factor/strategies/two_factor_authenticatable.rb000066400000000000000000000021641257613261200314220ustar00rootroot00000000000000module Devise module Strategies class TwoFactorAuthenticatable < Devise::Strategies::DatabaseAuthenticatable def authenticate! resource = mapping.to.find_for_database_authentication(authentication_hash) # We authenticate in two cases: # 1. The password and the OTP are correct # 2. The password is correct, and OTP is not required for login # We check the OTP, then defer to DatabaseAuthenticatable if validate(resource) { validate_otp(resource) } super end fail(:not_found_in_database) unless resource # We want to cascade to the next strategy if this one fails, # but database authenticatable automatically halts on a bad password @halted = false if @result == :failure end def validate_otp(resource) return true unless resource.otp_required_for_login return if params[scope]['otp_attempt'].nil? resource.validate_and_consume_otp!(params[scope]['otp_attempt']) end end end end Warden::Strategies.add(:two_factor_authenticatable, Devise::Strategies::TwoFactorAuthenticatable) devise-two-factor-2.0.0/lib/devise_two_factor/strategies/two_factor_backupable.rb000066400000000000000000000015771257613261200303650ustar00rootroot00000000000000module Devise module Strategies class TwoFactorBackupable < Devise::Strategies::DatabaseAuthenticatable def authenticate! resource = mapping.to.find_for_database_authentication(authentication_hash) if validate(resource) { resource.invalidate_otp_backup_code!(params[scope]['otp_attempt']) } # Devise fails to authenticate invalidated resources, but if we've # gotten here, the object changed (Since we deleted a recovery code) resource.save! super end fail(:not_found_in_database) unless resource # We want to cascade to the next strategy if this one fails, # but database authenticatable automatically halts on a bad password @halted = false if @result == :failure end end end end Warden::Strategies.add(:two_factor_backupable, Devise::Strategies::TwoFactorBackupable) devise-two-factor-2.0.0/lib/devise_two_factor/version.rb000066400000000000000000000000671257613261200233510ustar00rootroot00000000000000module DeviseTwoFactor VERSION = '2.0.0'.freeze end devise-two-factor-2.0.0/lib/generators/000077500000000000000000000000001257613261200177775ustar00rootroot00000000000000devise-two-factor-2.0.0/lib/generators/devise_two_factor/000077500000000000000000000000001257613261200235055ustar00rootroot00000000000000devise-two-factor-2.0.0/lib/generators/devise_two_factor/devise_two_factor_generator.rb000066400000000000000000000053401257613261200316100ustar00rootroot00000000000000require 'rails/generators' module DeviseTwoFactor module Generators class DeviseTwoFactorGenerator < Rails::Generators::NamedBase argument :encryption_key_env, :type => :string, :required => true desc 'Creates a migration to add the required attributes to NAME, and ' \ 'adds the necessary Devise directives to the model' def install_devise_two_factor create_devise_two_factor_migration inject_strategies_into_warden_config inject_devise_directives_into_model end private def create_devise_two_factor_migration migration_arguments = [ "add_devise_two_factor_to_#{plural_name}", "encrypted_otp_secret:string", "encrypted_otp_secret_iv:string", "encrypted_otp_secret_salt:string", "consumed_timestep:integer", "otp_required_for_login:boolean" ] Rails::Generators.invoke('active_record:migration', migration_arguments) end def inject_strategies_into_warden_config config_path = File.join('config', 'initializers', 'devise.rb') content = " config.warden do |manager|\n" \ " manager.default_strategies(:scope => :#{singular_table_name}).unshift :two_factor_authenticatable\n" \ " end\n\n" inject_into_file(config_path, content, after: "Devise.setup do |config|\n") end def inject_devise_directives_into_model model_path = File.join('app', 'models', "#{file_path}.rb") class_path = if namespaced? class_name.to_s.split("::") else [class_name] end indent_depth = class_path.size content = [ "devise :two_factor_authenticatable,", " :otp_secret_encryption_key => ENV['#{encryption_key_env}']\n" ] content << "attr_accessible :otp_attempt\n" if needs_attr_accessible? content = content.map { |line| " " * indent_depth + line }.join("\n") << "\n" inject_into_class(model_path, class_path.last, content) # Remove :database_authenticatable from the list of loaded models gsub_file(model_path, /(devise.*):(, )?database_authenticatable(, )?/, '\1\2') end def needs_attr_accessible? !strong_parameters_enabled? && mass_assignment_security_enabled? end def strong_parameters_enabled? defined?(ActionController::StrongParameters) end def mass_assignment_security_enabled? defined?(ActiveModel::MassAssignmentSecurity) end end end end devise-two-factor-2.0.0/spec/000077500000000000000000000000001257613261200160125ustar00rootroot00000000000000devise-two-factor-2.0.0/spec/devise/000077500000000000000000000000001257613261200172715ustar00rootroot00000000000000devise-two-factor-2.0.0/spec/devise/models/000077500000000000000000000000001257613261200205545ustar00rootroot00000000000000devise-two-factor-2.0.0/spec/devise/models/two_factor_authenticatable_spec.rb000066400000000000000000000010411257613261200274730ustar00rootroot00000000000000require 'spec_helper' require 'active_model' class TwoFactorAuthenticatableDouble include ::ActiveModel::Validations::Callbacks extend ::Devise::Models devise :two_factor_authenticatable, :otp_secret_encryption_key => 'test-key' attr_accessor :consumed_timestep def save(validate) # noop for testing true end end describe ::Devise::Models::TwoFactorAuthenticatable do context 'When included in a class' do subject { TwoFactorAuthenticatableDouble.new } it_behaves_like 'two_factor_authenticatable' end end devise-two-factor-2.0.0/spec/devise/models/two_factor_backupable_spec.rb000066400000000000000000000007321257613261200264350ustar00rootroot00000000000000require 'spec_helper' class TwoFactorBackupableDouble include ::ActiveModel::Validations::Callbacks extend ::Devise::Models devise :two_factor_authenticatable, :two_factor_backupable, :otp_secret_encryption_key => 'test-key' attr_accessor :otp_backup_codes end describe ::Devise::Models::TwoFactorBackupable do context 'When included in a class' do subject { TwoFactorBackupableDouble.new } it_behaves_like 'two_factor_backupable' end end devise-two-factor-2.0.0/spec/spec_helper.rb000066400000000000000000000013051257613261200206270ustar00rootroot00000000000000require 'simplecov' module SimpleCov::Configuration def clean_filters @filters = [] end end SimpleCov.configure do clean_filters load_profile 'test_frameworks' end ENV["COVERAGE"] && SimpleCov.start do add_filter "/.rvm/" end $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) $LOAD_PATH.unshift(File.dirname(__FILE__)) require 'rspec' require 'faker' require 'timecop' require 'devise-two-factor' require 'devise_two_factor/spec_helpers' # Requires supporting files with custom matchers and macros, etc, # in ./support/ and its subdirectories. Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f} RSpec.configure do |config| config.order = 'random' end