├── .gitignore ├── .travis.yml ├── Gemfile ├── Gemfile.lock ├── Procfile ├── README.md ├── Rakefile ├── app.json ├── app ├── assets │ ├── images │ │ └── .keep │ ├── javascripts │ │ ├── account.coffee │ │ ├── application.js │ │ ├── main.coffee │ │ ├── sessions.js │ │ └── users.coffee │ └── stylesheets │ │ ├── account.scss │ │ ├── application.css │ │ ├── main.scss │ │ ├── sessions.scss │ │ └── users.scss ├── controllers │ ├── accounts_controller.rb │ ├── application_controller.rb │ ├── authy_controller.rb │ ├── concerns │ │ └── .keep │ ├── main_controller.rb │ ├── sessions_controller.rb │ └── users_controller.rb ├── helpers │ ├── account_helper.rb │ ├── application_helper.rb │ ├── main_helper.rb │ ├── sessions_helper.rb │ └── users_helper.rb ├── mailers │ └── .keep ├── models │ ├── .keep │ ├── concerns │ │ └── .keep │ └── user.rb └── views │ ├── accounts │ └── show.html.erb │ ├── layouts │ └── application.html.erb │ ├── main │ └── index.html.erb │ ├── sessions │ ├── _authy_modal.html.erb │ ├── new.html.erb │ └── two_factor.html.erb │ └── users │ └── new.html.erb ├── bin ├── bundle ├── rails ├── rake ├── setup └── spring ├── config.ru ├── config ├── application.rb ├── boot.rb ├── database.yml ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ └── test.rb ├── initializers │ ├── assets.rb │ ├── authy.rb │ ├── backtrace_silencers.rb │ ├── cookies_serializer.rb │ ├── filter_parameter_logging.rb │ ├── inflections.rb │ ├── mime_types.rb │ ├── session_store.rb │ └── wrap_parameters.rb ├── locales │ └── en.yml ├── routes.rb ├── secrets.yml └── unicorn.rb ├── db ├── migrate │ ├── 20150219231715_create_users.rb │ ├── 20150220155337_adds_user_name_to_users.rb │ ├── 20150220161843_add_phone_number_and_country_code_to_users.rb │ ├── 20150220171049_add_authy_token_to_users.rb │ └── 20151014174632_add_status_and_uuid_to_users.rb ├── schema.rb └── seeds.rb ├── lib ├── assets │ └── .keep └── tasks │ └── .keep ├── log └── .keep ├── public ├── 404.html ├── 422.html ├── 500.html ├── favicon.ico ├── landing.html └── robots.txt ├── test ├── controllers │ ├── .keep │ ├── accounts_controller_test.rb │ ├── authy_controller_test.rb │ ├── main_controller_test.rb │ ├── sessions_controller_test.rb │ └── users_controller_test.rb ├── fixtures │ ├── .keep │ └── users.yml ├── helpers │ └── .keep ├── integration │ └── .keep ├── mailers │ └── .keep ├── models │ ├── .keep │ └── user_test.rb └── test_helper.rb └── vendor └── assets ├── javascripts └── .keep └── stylesheets └── .keep /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | log/* 3 | tmp/* 4 | 5 | # Ignore the default SQLite database. 6 | /db/*.sqlite3 7 | /db/*.sqlite3-journal 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - rvm 2.2.2 4 | - rvm 2.3.3 5 | 6 | before_script: 7 | - RAILS_ENV=test rake db:create db:migrate 8 | 9 | install: 10 | - bundle 11 | 12 | env: 13 | global: 14 | - AUTHY_API_KEY=TWILIOAPIKEY00000 15 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | 4 | # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' 5 | gem 'rails', '5.1.5' 6 | # Use sqlite3 as the database for Active Record 7 | gem 'sqlite3' 8 | # Use SCSS for stylesheets 9 | gem 'sass-rails', '~> 5.0' 10 | # Use Uglifier as compressor for JavaScript assets 11 | gem 'uglifier', '>= 4.1' 12 | # Use CoffeeScript for .coffee assets and views 13 | gem 'coffee-rails', '~> 4.2' 14 | # See https://github.com/sstephenson/execjs#readme for more supported runtimes 15 | # gem 'therubyracer', platforms: :ruby 16 | 17 | # Use jquery as the JavaScript library 18 | gem 'jquery-rails' 19 | # Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks 20 | gem 'turbolinks' 21 | # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder 22 | gem 'jbuilder', '~> 2.7' 23 | # bundle exec rake doc:rails generates the API under doc/api. 24 | gem 'sdoc', '~> 1.0', group: :doc 25 | 26 | # Use ActiveModel has_secure_password 27 | gem 'bcrypt', '~> 3.1' 28 | 29 | # Use Authy for 2FA 30 | gem 'authy', '~> 2.7', '>= 2.7.2' 31 | 32 | # Use Unicorn as the app server 33 | gem 'unicorn' 34 | 35 | gem 'rack-cors', :require => 'rack/cors' 36 | 37 | # Access an IRB console on exception pages or by using <%= console %> in views 38 | gem 'web-console', '3.5.0', group: :development 39 | 40 | group :production do 41 | gem 'rails_12factor' 42 | end 43 | 44 | group :development, :test do 45 | # Call 'byebug' anywhere in the code to stop execution and get a debugger console 46 | gem 'byebug' 47 | 48 | # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring 49 | gem 'spring' 50 | 51 | # Mocha for mocking 52 | gem 'mocha' 53 | 54 | gem 'rails-controller-testing' 55 | end 56 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | actioncable (5.1.5) 5 | actionpack (= 5.1.5) 6 | nio4r (~> 2.0) 7 | websocket-driver (~> 0.6.1) 8 | actionmailer (5.1.5) 9 | actionpack (= 5.1.5) 10 | actionview (= 5.1.5) 11 | activejob (= 5.1.5) 12 | mail (~> 2.5, >= 2.5.4) 13 | rails-dom-testing (~> 2.0) 14 | actionpack (5.1.5) 15 | actionview (= 5.1.5) 16 | activesupport (= 5.1.5) 17 | rack (~> 2.0) 18 | rack-test (>= 0.6.3) 19 | rails-dom-testing (~> 2.0) 20 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 21 | actionview (5.1.5) 22 | activesupport (= 5.1.5) 23 | builder (~> 3.1) 24 | erubi (~> 1.4) 25 | rails-dom-testing (~> 2.0) 26 | rails-html-sanitizer (~> 1.0, >= 1.0.3) 27 | activejob (5.1.5) 28 | activesupport (= 5.1.5) 29 | globalid (>= 0.3.6) 30 | activemodel (5.1.5) 31 | activesupport (= 5.1.5) 32 | activerecord (5.1.5) 33 | activemodel (= 5.1.5) 34 | activesupport (= 5.1.5) 35 | arel (~> 8.0) 36 | activesupport (5.1.5) 37 | concurrent-ruby (~> 1.0, >= 1.0.2) 38 | i18n (~> 0.7) 39 | minitest (~> 5.1) 40 | tzinfo (~> 1.1) 41 | arel (8.0.0) 42 | authy (2.7.2) 43 | httpclient (>= 2.5.3.3) 44 | bcrypt (3.1.11) 45 | bindex (0.5.0) 46 | builder (3.2.3) 47 | byebug (10.0.0) 48 | coffee-rails (4.2.2) 49 | coffee-script (>= 2.2.0) 50 | railties (>= 4.0.0) 51 | coffee-script (2.4.1) 52 | coffee-script-source 53 | execjs 54 | coffee-script-source (1.12.2) 55 | concurrent-ruby (1.0.5) 56 | crass (1.0.3) 57 | erubi (1.7.1) 58 | execjs (2.7.0) 59 | ffi (1.9.23) 60 | globalid (0.4.1) 61 | activesupport (>= 4.2.0) 62 | httpclient (2.8.3) 63 | i18n (0.9.5) 64 | concurrent-ruby (~> 1.0) 65 | jbuilder (2.7.0) 66 | activesupport (>= 4.2.0) 67 | multi_json (>= 1.2) 68 | jquery-rails (4.3.1) 69 | rails-dom-testing (>= 1, < 3) 70 | railties (>= 4.2.0) 71 | thor (>= 0.14, < 2.0) 72 | kgio (2.11.2) 73 | loofah (2.2.0) 74 | crass (~> 1.0.2) 75 | nokogiri (>= 1.5.9) 76 | mail (2.7.0) 77 | mini_mime (>= 0.1.1) 78 | metaclass (0.0.4) 79 | method_source (0.9.0) 80 | mini_mime (1.0.0) 81 | mini_portile2 (2.3.0) 82 | minitest (5.11.3) 83 | mocha (1.3.0) 84 | metaclass (~> 0.0.1) 85 | multi_json (1.13.1) 86 | nio4r (2.2.0) 87 | nokogiri (1.8.2) 88 | mini_portile2 (~> 2.3.0) 89 | rack (2.0.4) 90 | rack-cors (1.0.2) 91 | rack-test (0.8.3) 92 | rack (>= 1.0, < 3) 93 | rails (5.1.5) 94 | actioncable (= 5.1.5) 95 | actionmailer (= 5.1.5) 96 | actionpack (= 5.1.5) 97 | actionview (= 5.1.5) 98 | activejob (= 5.1.5) 99 | activemodel (= 5.1.5) 100 | activerecord (= 5.1.5) 101 | activesupport (= 5.1.5) 102 | bundler (>= 1.3.0) 103 | railties (= 5.1.5) 104 | sprockets-rails (>= 2.0.0) 105 | rails-controller-testing (1.0.2) 106 | actionpack (~> 5.x, >= 5.0.1) 107 | actionview (~> 5.x, >= 5.0.1) 108 | activesupport (~> 5.x) 109 | rails-dom-testing (2.0.3) 110 | activesupport (>= 4.2.0) 111 | nokogiri (>= 1.6) 112 | rails-html-sanitizer (1.0.3) 113 | loofah (~> 2.0) 114 | rails_12factor (0.0.3) 115 | rails_serve_static_assets 116 | rails_stdout_logging 117 | rails_serve_static_assets (0.0.5) 118 | rails_stdout_logging (0.0.5) 119 | railties (5.1.5) 120 | actionpack (= 5.1.5) 121 | activesupport (= 5.1.5) 122 | method_source 123 | rake (>= 0.8.7) 124 | thor (>= 0.18.1, < 2.0) 125 | raindrops (0.19.0) 126 | rake (12.3.0) 127 | rb-fsevent (0.10.3) 128 | rb-inotify (0.9.10) 129 | ffi (>= 0.5.0, < 2) 130 | rdoc (6.0.1) 131 | sass (3.5.5) 132 | sass-listen (~> 4.0.0) 133 | sass-listen (4.0.0) 134 | rb-fsevent (~> 0.9, >= 0.9.4) 135 | rb-inotify (~> 0.9, >= 0.9.7) 136 | sass-rails (5.0.7) 137 | railties (>= 4.0.0, < 6) 138 | sass (~> 3.1) 139 | sprockets (>= 2.8, < 4.0) 140 | sprockets-rails (>= 2.0, < 4.0) 141 | tilt (>= 1.1, < 3) 142 | sdoc (1.0.0) 143 | rdoc (>= 5.0) 144 | spring (2.0.2) 145 | activesupport (>= 4.2) 146 | sprockets (3.7.1) 147 | concurrent-ruby (~> 1.0) 148 | rack (> 1, < 3) 149 | sprockets-rails (3.2.1) 150 | actionpack (>= 4.0) 151 | activesupport (>= 4.0) 152 | sprockets (>= 3.0.0) 153 | sqlite3 (1.3.13) 154 | thor (0.20.0) 155 | thread_safe (0.3.6) 156 | tilt (2.0.8) 157 | turbolinks (5.1.0) 158 | turbolinks-source (~> 5.1) 159 | turbolinks-source (5.1.0) 160 | tzinfo (1.2.5) 161 | thread_safe (~> 0.1) 162 | uglifier (4.1.7) 163 | execjs (>= 0.3.0, < 3) 164 | unicorn (5.4.0) 165 | kgio (~> 2.6) 166 | raindrops (~> 0.7) 167 | web-console (3.5.0) 168 | actionview (>= 5.0) 169 | activemodel (>= 5.0) 170 | bindex (>= 0.4.0) 171 | railties (>= 5.0) 172 | websocket-driver (0.6.5) 173 | websocket-extensions (>= 0.1.0) 174 | websocket-extensions (0.1.3) 175 | 176 | PLATFORMS 177 | ruby 178 | 179 | DEPENDENCIES 180 | authy (~> 2.7, >= 2.7.2) 181 | bcrypt (~> 3.1) 182 | byebug 183 | coffee-rails (~> 4.2) 184 | jbuilder (~> 2.7) 185 | jquery-rails 186 | mocha 187 | rack-cors 188 | rails (= 5.1.5) 189 | rails-controller-testing 190 | rails_12factor 191 | sass-rails (~> 5.0) 192 | sdoc (~> 1.0) 193 | spring 194 | sqlite3 195 | turbolinks 196 | uglifier (>= 4.1) 197 | unicorn 198 | web-console (= 3.5.0) 199 | 200 | BUNDLED WITH 201 | 1.16.1 202 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: bundle exec unicorn -p $PORT -c ./config/unicorn.rb 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > ❗ This tutorial is no longer maintained, please see the [updated version](https://github.com/TwilioDevEd/verify-v2-quickstart-rails) 2 | 3 | # Two-Factor Authentication with Ruby on Rails 4 | 5 | Use Authy to add Two Factor Auth to your Rails app. 6 | 7 | [Read the full tutorial here](https://www.twilio.com/docs/tutorials/walkthrough/two-factor-authentication/ruby/rails)! 8 | 9 | [![Build Status](https://travis-ci.org/TwilioDevEd/authy2fa-rails.svg?branch=master)](https://travis-ci.org/TwilioDevEd/authy2fa-rails) 10 | 11 | ## Quickstart 12 | 13 | ### Create an Authy app 14 | 15 | Create a free [Twilio account](https://www.twilio.com/console/authy), if you don't have one already, and then connect it to your Twilio account. 16 | 17 | ### Local development 18 | 19 | This project is built using the [Ruby on Rails](http://rubyonrails.org/) web framework. 20 | 21 | 1. First clone this repository and `cd` into it. 22 | 23 | ```bash 24 | $ git clone git@github.com:TwilioDevEd/authy2fa-rails.git 25 | $ cd authy2fa-rails 26 | ``` 27 | 28 | 1. Install the dependencies. 29 | 30 | ```bash 31 | $ bundle install 32 | ``` 33 | 34 | 1. Export the environment variables. 35 | 36 | You can find your **Authy Api Key** for your application at https://www.twilio.com/console/authy. 37 | 38 | ```bash 39 | $ export AUTHY_API_KEY=Your Authy API Key 40 | ``` 41 | 42 | 1. Create a SQLite3 database and run migrations. 43 | 44 | ```bash 45 | $ bundle exec rake db:setup 46 | ``` 47 | 48 | 1. Make sure the tests succeed. 49 | 50 | ```bash 51 | $ bundle exec rake test 52 | ``` 53 | 54 | 1. Run the server. 55 | 56 | ```bash 57 | $ bundle exec rails s 58 | ``` 59 | 60 | 1. Expose your application to the wider internet using [ngrok](http://ngrok.com). 61 | 62 | You can click [here](https://www.twilio.com/blog/2015/09/6-awesome-reasons-to-use-ngrok-when-testing-webhooks.html) for more details. This step is important because the application won't work as expected if you run it through localhost. 63 | 64 | ```bash 65 | $ ngrok http 3000 66 | ``` 67 | 68 | Once ngrok is running, open up your browser and go to your ngrok URL. It will look something like this: `http://9a159ccf.ngrok.io` 69 | 70 | 1. Go to your [Console](https://www.twilio.com/console/authy/) and create a new application. In the menu to the you'll find the **Push Authentication**. Look for **Webhooks** and update the _Endpoint/URL_ with the endpoint you created. Something like this: 71 | 72 | `http://[your-ngrok-subdomain].ngrok.io/authy/callback` 73 | 74 | If you deployed this application to production, the Endpoint/URL should look 75 | like this: 76 | 77 | `http://[your-domain].com/authy/callback` 78 | 79 | ## Meta 80 | 81 | * No warranty expressed or implied. Software is as is. Diggity. 82 | * [MIT License](http://www.opensource.org/licenses/mit-license.html) 83 | * Lovingly crafted by Twilio Developer Education. 84 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require File.expand_path('../config/application', __FILE__) 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Two Factor Authy", 3 | "description": "Two Factor Authentication in Rails with Authy", 4 | "repository": "https://github.com/TwilioDevEd/authy2fa-rails", 5 | "keywords": [ 6 | "security", "2fa", "twilio" 7 | ], 8 | "success_url": "/landing.html", 9 | "addons": ["heroku-postgresql:hobby-dev"], 10 | "env": { 11 | "AUTHY_API_KEY": { 12 | "description": "Your Authy API key", 13 | "value": "xxxxxxxxxxxxxxxxx", 14 | "required": true 15 | }, 16 | "SECRET_KEY_BASE": { 17 | "description": "Your Rails session secret key", 18 | "generator": "secret" 19 | } 20 | }, 21 | "scripts": { 22 | "postdeploy": "bundle exec rake db:create db:migrate" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/assets/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/authy2fa-rails/6e00015449f00fb9e90b2d0b2dc7607503f9dd47/app/assets/images/.keep -------------------------------------------------------------------------------- /app/assets/javascripts/account.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, 5 | // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // compiled file. 9 | // 10 | // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details 11 | // about supported directives. 12 | // 13 | //= require jquery 14 | //= require jquery_ujs 15 | //= require turbolinks 16 | -------------------------------------------------------------------------------- /app/assets/javascripts/main.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/sessions.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | 3 | var showTokenForm = function() { 4 | $('.auth-ot').fadeOut(function() { 5 | $('.auth-token').fadeIn('slow'); 6 | }); 7 | }; 8 | 9 | var triggerSMSToken = function() { 10 | $.get('/authy/send_token'); 11 | }; 12 | 13 | var checkForOneTouch = function() { 14 | $.get('/authy/status', function(data) { 15 | if (data === 'approved') { 16 | window.location.href = '/account'; 17 | } else if (data === 'denied') { 18 | showTokenForm(); 19 | triggerSMSToken(); 20 | } else { 21 | setTimeout(checkForOneTouch, 2000); 22 | } 23 | }); 24 | }; 25 | 26 | var attemptOneTouchVerification = function(form) { 27 | $.post('/sessions', form, function(data) { 28 | $('#authy-modal').modal({backdrop:'static'},'show'); 29 | if (data.success) { 30 | $('.auth-ot').fadeIn(); 31 | checkForOneTouch(); 32 | } else { 33 | $('.auth-token').fadeIn(); 34 | } 35 | }); 36 | }; 37 | 38 | $('#login-form').submit(function(e) { 39 | e.preventDefault(); 40 | var formData = $(e.currentTarget).serialize(); 41 | attemptOneTouchVerification(formData); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /app/assets/javascripts/users.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /app/assets/stylesheets/account.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the account controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, 6 | * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the 9 | * compiled file so the styles you add here take precedence over styles defined in any styles 10 | * defined in the other CSS/SCSS files in this directory. It is generally better to create a new 11 | * file per style scope. 12 | * 13 | *= require_tree . 14 | *= require_self 15 | */ 16 | 17 | html { 18 | box-sizing:border-box; 19 | } 20 | 21 | *, *:before, *:after { 22 | box-sizing:inherit; 23 | } 24 | 25 | footer { 26 | font-size:12px; 27 | color:#787878; 28 | margin-top:20px; 29 | padding-top:20px; 30 | text-align:center; 31 | } 32 | 33 | footer i { 34 | color:#ff0000; 35 | } 36 | 37 | nav a:focus { 38 | text-decoration:none; 39 | color:#337ab7; 40 | } 41 | 42 | td { 43 | padding:10px; 44 | } 45 | 46 | #main { 47 | margin-top:10px; 48 | } 49 | 50 | #messages p { 51 | padding:10px; 52 | border:1px solid #eee; 53 | } 54 | 55 | #messages i { 56 | float:right; 57 | margin-left:5px; 58 | cursor:pointer; 59 | } 60 | 61 | #countries-input-0{ 62 | display: block; 63 | width: 100%; 64 | height: 34px; 65 | padding: 6px 12px; 66 | font-size: 14px; 67 | line-height: 1.42857; 68 | color: #555; 69 | background-color: #FFF; 70 | background-image: none; 71 | border: 1px solid #CCC; 72 | border-radius: 4px; 73 | box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.075) inset; 74 | transition: border-color 0.15s ease-in-out 0s, box-shadow 0.15s ease-in-out 0s; 75 | } 76 | .auth-ot, .auth-token { 77 | display:none; 78 | } 79 | -------------------------------------------------------------------------------- /app/assets/stylesheets/main.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the main controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /app/assets/stylesheets/sessions.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the sessions controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /app/assets/stylesheets/users.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the users controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /app/controllers/accounts_controller.rb: -------------------------------------------------------------------------------- 1 | class AccountsController < ApplicationController 2 | before_action:authenticate! 3 | 4 | def show 5 | @user = current_user 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | # Prevent CSRF attacks by raising an exception. 3 | # For APIs, you may want to use :null_session instead. 4 | protect_from_forgery with: :exception 5 | 6 | def current_user 7 | User.find_by(id: session[:user_id]) 8 | end 9 | helper_method :current_user 10 | 11 | def signed_in? 12 | current_user.present? 13 | end 14 | helper_method :signed_in? 15 | 16 | protected 17 | 18 | def authenticate! 19 | redirect_to new_session_path and return unless signed_in? 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /app/controllers/authy_controller.rb: -------------------------------------------------------------------------------- 1 | require 'openssl' 2 | require 'base64' 3 | 4 | class AuthyController < ApplicationController 5 | # Before we allow the incoming request to callback, verify 6 | # that it is an Authy request 7 | before_action :authenticate_authy_request, :only => [ 8 | :callback 9 | ] 10 | 11 | protect_from_forgery except: [:callback, :send_token] 12 | 13 | # The webhook setup for our Authy application this is where 14 | # the response from a OneTouch request will come 15 | def callback 16 | authy_id = params[:authy_id] 17 | if authy_id != 1234 18 | begin 19 | @user = User.find_by! authy_id: authy_id 20 | @user.update(authy_status: params[:status]) 21 | rescue => e 22 | puts e.message 23 | end 24 | end 25 | render plain: 'ok' 26 | end 27 | 28 | def one_touch_status 29 | @user = User.find(session[:pre_2fa_auth_user_id]) 30 | session[:user_id] = @user.approved? ? @user.id : nil 31 | render plain: @user.authy_status 32 | end 33 | 34 | def send_token 35 | @user = User.find(session[:pre_2fa_auth_user_id]) 36 | Authy::API.request_sms(id: @user.authy_id) 37 | render plain: 'sending token' 38 | end 39 | 40 | def verify 41 | @user = User.find(session[:pre_2fa_auth_user_id]) 42 | token = Authy::API.verify(id: @user.authy_id, token: params[:token]) 43 | if token.ok? 44 | session[:user_id] = @user.id 45 | session[:pre_2fa_auth_user_id] = nil 46 | redirect_to account_path 47 | else 48 | flash.now[:danger] = "Incorrect code, please try again" 49 | redirect_to new_session_path 50 | end 51 | end 52 | 53 | # Authenticate that all requests to our public-facing callback is 54 | # coming from Authy. Adapted from the example at 55 | # https://docs.authy.com/new_doc/authy_onetouch_api#authenticating-callbacks-from-authy-onetouch 56 | private 57 | def authenticate_authy_request 58 | url = request.url 59 | raw_params = JSON.parse(request.raw_post) 60 | nonce = request.headers["X-Authy-Signature-Nonce"] 61 | sorted_params = (Hash[raw_params.sort]).to_query 62 | 63 | # data format of Authy digest 64 | data = nonce + "|" + request.method + "|" + url + "|" + sorted_params 65 | 66 | digest = OpenSSL::HMAC.digest('sha256', Authy.api_key, data) 67 | digest_in_base64 = Base64.encode64(digest) 68 | 69 | theirs = (request.headers['X-Authy-Signature']).strip 70 | mine = digest_in_base64.strip 71 | 72 | unless theirs == mine 73 | render plain: 'invalid request signature' 74 | end 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/authy2fa-rails/6e00015449f00fb9e90b2d0b2dc7607503f9dd47/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /app/controllers/main_controller.rb: -------------------------------------------------------------------------------- 1 | class MainController < ApplicationController 2 | def index 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/controllers/sessions_controller.rb: -------------------------------------------------------------------------------- 1 | class SessionsController < ApplicationController 2 | def new 3 | @user = User.new 4 | end 5 | 6 | def create 7 | @user = User.find_by(email: params[:email]) 8 | if @user && @user.authenticate(params[:password]) 9 | session[:pre_2fa_auth_user_id] = @user.id 10 | 11 | # Try to verify with OneTouch 12 | one_touch = Authy::OneTouch.send_approval_request( 13 | id: @user.authy_id, 14 | message: "Request to Login to Twilio demo app", 15 | details: { 16 | 'Email Address' => @user.email, 17 | } 18 | ) 19 | status = one_touch['success'] ? :onetouch : :sms 20 | @user.update(authy_status: status) 21 | 22 | # Respond to the ajax call that requested this with the approval request body 23 | render json: { success: one_touch['success'] } 24 | else 25 | @user ||= User.new(email: params[:email]) 26 | render :new 27 | end 28 | end 29 | 30 | def destroy 31 | session[:user_id] = nil 32 | flash[:notice] = "You have been logged out" 33 | redirect_to root_path 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /app/controllers/users_controller.rb: -------------------------------------------------------------------------------- 1 | class UsersController < ApplicationController 2 | def new 3 | @user = User.new 4 | end 5 | 6 | def create 7 | @user = User.new(user_params) 8 | if @user.save 9 | session[:user_id] = @user.id 10 | 11 | authy = Authy::API.register_user( 12 | email: @user.email, 13 | cellphone: @user.phone_number, 14 | country_code: @user.country_code 15 | ) 16 | @user.update(authy_id: authy.id) 17 | 18 | redirect_to account_path 19 | else 20 | render :new 21 | end 22 | end 23 | 24 | private 25 | 26 | def user_params 27 | params.require(:user).permit( 28 | :email, :password, :name, :country_code, :phone_number 29 | ) 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /app/helpers/account_helper.rb: -------------------------------------------------------------------------------- 1 | module AccountHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | def bootstrap_class_for flash_type 3 | { success: "alert-success", error: "alert-danger", alert: "alert-warning", notice: "alert-info" }[flash_type.to_sym] || flash_type.to_s 4 | end 5 | 6 | def flash_messages(opts = {}) 7 | flash.each do |msg_type, message| 8 | concat(content_tag(:div, message, class: "alert #{bootstrap_class_for(msg_type)}", role: 'alert') do 9 | message 10 | end) 11 | end 12 | nil 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /app/helpers/main_helper.rb: -------------------------------------------------------------------------------- 1 | module MainHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/sessions_helper.rb: -------------------------------------------------------------------------------- 1 | module SessionsHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/users_helper.rb: -------------------------------------------------------------------------------- 1 | module UsersHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/mailers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/authy2fa-rails/6e00015449f00fb9e90b2d0b2dc7607503f9dd47/app/mailers/.keep -------------------------------------------------------------------------------- /app/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/authy2fa-rails/6e00015449f00fb9e90b2d0b2dc7607503f9dd47/app/models/.keep -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/authy2fa-rails/6e00015449f00fb9e90b2d0b2dc7607503f9dd47/app/models/concerns/.keep -------------------------------------------------------------------------------- /app/models/user.rb: -------------------------------------------------------------------------------- 1 | require "net/http" 2 | require "uri" 3 | 4 | class User < ActiveRecord::Base 5 | has_secure_password 6 | 7 | enum authy_status: [:unverified, :onetouch, :sms, :token, :approved, :denied] 8 | validates :email, presence: true, format: { with: /\A.+@.+$\Z/ }, uniqueness: true 9 | validates :name, presence: true 10 | validates :country_code, presence: true 11 | validates :phone_number, presence: true 12 | 13 | end -------------------------------------------------------------------------------- /app/views/accounts/show.html.erb: -------------------------------------------------------------------------------- 1 |

<%= @user.name %>

2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
E-Mail<%= @user.email %>
Phone<%= @user.phone_number %>
12 | -------------------------------------------------------------------------------- /app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | TwoFactorAuthy 5 | <%= stylesheet_link_tag '//maxcdn.bootstrapcdn.com/bootswatch/3.3.5/flatly/bootstrap.min.css', media: 'all', 'data-turbolinks-track' => true %> 6 | <%= stylesheet_link_tag '//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css', media: 'all', 'data-turbolinks-track' => true %> 7 | <%= stylesheet_link_tag '//cdnjs.cloudflare.com/ajax/libs/authy-forms.css/2.2/form.authy.min.css', media: 'all', 'data-turbolinks-track' => true %> 8 | <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %> 9 | <%= javascript_include_tag '//cdnjs.cloudflare.com/ajax/libs/authy-forms.js/2.2/form.authy.min.js', 'data-turbolinks-track' => true %> 10 | <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %> 11 | <%= csrf_meta_tags %> 12 | 13 | 14 |
15 | 16 | 28 | 29 | <%= flash_messages %> 30 | 31 | <%= yield %> 32 | 33 |
34 | 35 | 39 | <%= javascript_include_tag '//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js', 'data-turbolinks-track' => true %> 40 | <%= yield :javascript %> 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /app/views/main/index.html.erb: -------------------------------------------------------------------------------- 1 |

Let's Do The 2FA with Authy!

2 | 3 |

Doing two-factor authentication (2FA) at a level of excellence can be tricky. Let's demonstrate how to use <%= link_to "Authy", "http://authy.com" %> (powered by <%= link_to "Twilio", "http://twilio.com" %>) to make implementing 2FA a snap.

4 | 5 |

<%= link_to "Sign up", new_user_path %> or <%= link_to "Log in", new_session_path %> to get started.

6 | -------------------------------------------------------------------------------- /app/views/sessions/_authy_modal.html.erb: -------------------------------------------------------------------------------- 1 | 35 | -------------------------------------------------------------------------------- /app/views/sessions/new.html.erb: -------------------------------------------------------------------------------- 1 | <%= render 'authy_modal' %> 2 |

Welcome Back!

3 |

You never write. You never call. But we're glad you came, regardless! Be a pal and confirm your username and password, would you?

4 | 5 | <%= form_tag sessions_path, id: "login-form" do %> 6 |
7 | <%= label_tag :email %> 8 | <%= email_field_tag :email, @user.email, class: "form-control", placeholder: "you@yourdomain.com" %> 9 |
10 |
11 | <%= label_tag :password %> 12 | <%= password_field_tag :password, @user.password, class: "form-control" %> 13 |
14 | 15 | <% end -%> 16 | 17 | <% content_for :javascript do %> 18 | <%= javascript_include_tag 'sessions', 'data-turbolinks-track' => true %> 19 | <% end %> -------------------------------------------------------------------------------- /app/views/sessions/two_factor.html.erb: -------------------------------------------------------------------------------- 1 |

Just To Be Safe...

2 |

3 | We've sent you a one-time password to verify your posession of a mobile phone tied to this account. Can you enter the code we sent? 4 |

5 | <%= form_tag authy_verify_path do %> 6 |
7 | <%= label_tag :code, "Verification Code:" %> 8 | <%= text_field_tag :token, '', class: "form-control" %> 9 |
10 | 11 | <% end -%> 12 | 13 |
14 | <%= form_tag authy_send_token_path do %> 15 | 16 | <% end -%> -------------------------------------------------------------------------------- /app/views/users/new.html.erb: -------------------------------------------------------------------------------- 1 |

We're going to be *BEST* friends

2 |

Thanks for your interest in signing up! Can you tell us a bit about yourself?

3 | 4 | <% if @user.errors.any? %> 5 |

Oops, something went wrong!

6 | 7 | 12 | <% end -%> 13 | 14 | <%= form_for @user do |f| %> 15 |
16 | <%= f.label :name, "Tell us your name:" %> 17 | <%= f.text_field :name, class: "form-control", placeholder: "Zingelbert Bembledack" %> 18 |
19 |
20 | <%= f.label :email, "Enter Your E-mail Address:" %> 21 | <%= f.email_field :email, class: "form-control", placeholder: "me@mydomain.com" %> 22 |
23 |
24 | <%= f.label :password, "Enter a password:" %> 25 | <%= f.password_field :password, class: "form-control" %> 26 |
27 |
28 | <%= f.label :country_code %> 29 | <%= f.text_field :country_code, class: "form-control", id: "authy-countries" %> 30 |
31 |
32 | <%= f.label :phone_number %> 33 | <%= f.text_field :phone_number, class: "form-control", id: "authy-cellphone" %> 34 |
35 | 36 | <% end -%> 37 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path("../spring", __FILE__) 4 | rescue LoadError 5 | end 6 | APP_PATH = File.expand_path('../../config/application', __FILE__) 7 | require_relative '../config/boot' 8 | require 'rails/commands' 9 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path("../spring", __FILE__) 4 | rescue LoadError 5 | end 6 | require_relative '../config/boot' 7 | require 'rake' 8 | Rake.application.run 9 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'pathname' 3 | 4 | # path to your application root. 5 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) 6 | 7 | Dir.chdir APP_ROOT do 8 | # This script is a starting point to setup your application. 9 | # Add necessary setup steps to this file: 10 | 11 | puts "== Installing dependencies ==" 12 | system "gem install bundler --conservative" 13 | system "bundle check || bundle install" 14 | 15 | # puts "\n== Copying sample files ==" 16 | # unless File.exist?("config/database.yml") 17 | # system "cp config/database.yml.sample config/database.yml" 18 | # end 19 | 20 | puts "\n== Preparing database ==" 21 | system "bin/rake db:setup" 22 | 23 | puts "\n== Removing old logs and tempfiles ==" 24 | system "rm -f log/*" 25 | system "rm -rf tmp/cache" 26 | 27 | puts "\n== Restarting application server ==" 28 | system "touch tmp/restart.txt" 29 | end 30 | -------------------------------------------------------------------------------- /bin/spring: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # This file loads spring without using Bundler, in order to be fast. 4 | # It gets overwritten when you run the `spring binstub` command. 5 | 6 | unless defined?(Spring) 7 | require "rubygems" 8 | require "bundler" 9 | 10 | if match = Bundler.default_lockfile.read.match(/^GEM$.*?^ (?: )*spring \((.*?)\)$.*?^$/m) 11 | Gem.paths = { "GEM_PATH" => Bundler.bundle_path.to_s } 12 | gem "spring", match[1] 13 | require "spring/binstub" 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run Rails.application 5 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | require 'rails/all' 4 | 5 | # Require the gems listed in Gemfile, including any gems 6 | # you've limited to :test, :development, or :production. 7 | Bundler.require(*Rails.groups) 8 | 9 | module TwoFactorAuthy 10 | class Application < Rails::Application 11 | # Initialize configuration defaults for originally generated Rails version. 12 | config.load_defaults 5.1 13 | 14 | config.middleware.insert_before 0, Rack::Cors do 15 | allow do 16 | origins '*' 17 | resource '*', :headers => :any, :methods => [:get, :post, :options, :patch] 18 | end 19 | end 20 | 21 | # Settings in config/environments/* take precedence over those specified here. 22 | # Application configuration should go into files in config/initializers 23 | # -- all .rb files in that directory are automatically loaded. 24 | 25 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 26 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 27 | # config.time_zone = 'Central Time (US & Canada)' 28 | 29 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 30 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 31 | # config.i18n.default_locale = :de 32 | 33 | # Do not swallow errors in after_commit/after_rollback callbacks. 34 | # config.active_record.raise_in_transactional_callbacks = true 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 2 | 3 | require 'bundler/setup' # Set up gems listed in the Gemfile. 4 | -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite version 3.x 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem 'sqlite3' 6 | # 7 | default: &default 8 | adapter: sqlite3 9 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> 10 | timeout: 5000 11 | 12 | development: 13 | <<: *default 14 | database: db/development.sqlite3 15 | 16 | # Warning: The database defined as "test" will be erased and 17 | # re-generated from your development database when you run "rake". 18 | # Do not set this db to the same as development or production. 19 | test: 20 | <<: *default 21 | database: db/test.sqlite3 22 | 23 | production: 24 | <<: *default 25 | database: db/production.sqlite3 26 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | # Show full error reports and disable caching. 13 | config.consider_all_requests_local = true 14 | config.action_controller.perform_caching = false 15 | 16 | # Don't care if the mailer can't send. 17 | config.action_mailer.raise_delivery_errors = false 18 | 19 | # Print deprecation notices to the Rails logger. 20 | config.active_support.deprecation = :log 21 | 22 | # Raise an error on page load if there are pending migrations. 23 | config.active_record.migration_error = :page_load 24 | 25 | # Debug mode disables concatenation and preprocessing of assets. 26 | # This option may cause significant delays in view rendering with a large 27 | # number of complex assets. 28 | config.assets.debug = true 29 | 30 | # Asset digests allow you to set far-future HTTP expiration dates on all assets, 31 | # yet still be able to expire them through the digest params. 32 | config.assets.digest = true 33 | 34 | # Adds additional error checking when serving assets at runtime. 35 | # Checks for improperly declared sprockets dependencies. 36 | # Raises helpful error messages. 37 | config.assets.raise_runtime_errors = true 38 | 39 | # Raises error for missing translations 40 | # config.action_view.raise_on_missing_translations = true 41 | end 42 | -------------------------------------------------------------------------------- /config/environments/production.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # Code is not reloaded between requests. 5 | config.cache_classes = true 6 | 7 | # Eager load code on boot. This eager loads most of Rails and 8 | # your application in memory, allowing both threaded web servers 9 | # and those relying on copy on write to perform better. 10 | # Rake tasks automatically ignore this option for performance. 11 | config.eager_load = true 12 | 13 | # Full error reports are disabled and caching is turned on. 14 | config.consider_all_requests_local = false 15 | config.action_controller.perform_caching = true 16 | 17 | # Enable Rack::Cache to put a simple HTTP cache in front of your application 18 | # Add `rack-cache` to your Gemfile before enabling this. 19 | # For large-scale production use, consider using a caching reverse proxy like 20 | # NGINX, varnish or squid. 21 | # config.action_dispatch.rack_cache = true 22 | 23 | # Disable serving static files from the `/public` folder by default since 24 | # Apache or NGINX already handles this. 25 | config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'].present? 26 | 27 | # Compress JavaScripts and CSS. 28 | config.assets.js_compressor = :uglifier 29 | # config.assets.css_compressor = :sass 30 | 31 | # Do not fallback to assets pipeline if a precompiled asset is missed. 32 | config.assets.compile = false 33 | 34 | # Asset digests allow you to set far-future HTTP expiration dates on all assets, 35 | # yet still be able to expire them through the digest params. 36 | config.assets.digest = true 37 | 38 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb 39 | 40 | # Specifies the header that your server uses for sending files. 41 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache 42 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX 43 | 44 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 45 | # config.force_ssl = true 46 | 47 | # Use the lowest log level to ensure availability of diagnostic information 48 | # when problems arise. 49 | config.log_level = :debug 50 | 51 | # Prepend all log lines with the following tags. 52 | # config.log_tags = [ :subdomain, :uuid ] 53 | 54 | # Use a different logger for distributed setups. 55 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) 56 | 57 | # Use a different cache store in production. 58 | # config.cache_store = :mem_cache_store 59 | 60 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 61 | # config.action_controller.asset_host = 'http://assets.example.com' 62 | 63 | # Ignore bad email addresses and do not raise email delivery errors. 64 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 65 | # config.action_mailer.raise_delivery_errors = false 66 | 67 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 68 | # the I18n.default_locale when a translation cannot be found). 69 | config.i18n.fallbacks = true 70 | 71 | # Send deprecation notices to registered listeners. 72 | config.active_support.deprecation = :notify 73 | 74 | # Use default logging formatter so that PID and timestamp are not suppressed. 75 | config.log_formatter = ::Logger::Formatter.new 76 | 77 | # Do not dump schema after migrations. 78 | config.active_record.dump_schema_after_migration = false 79 | end 80 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Do not eager load code on boot. This avoids loading your whole application 11 | # just for the purpose of running a single test. If you are using a tool that 12 | # preloads Rails for running tests, you may have to set it to true. 13 | config.eager_load = false 14 | 15 | # Configure static file server for tests with Cache-Control for performance. 16 | config.serve_static_files = true 17 | config.static_cache_control = 'public, max-age=3600' 18 | 19 | # Show full error reports and disable caching. 20 | config.consider_all_requests_local = true 21 | config.action_controller.perform_caching = false 22 | 23 | # Raise exceptions instead of rendering exception templates. 24 | config.action_dispatch.show_exceptions = false 25 | 26 | # Disable request forgery protection in test environment. 27 | config.action_controller.allow_forgery_protection = false 28 | 29 | # Tell Action Mailer not to deliver emails to the real world. 30 | # The :test delivery method accumulates sent emails in the 31 | # ActionMailer::Base.deliveries array. 32 | config.action_mailer.delivery_method = :test 33 | 34 | # Randomize the order test cases are executed. 35 | config.active_support.test_order = :random 36 | 37 | # Print deprecation notices to the stderr. 38 | config.active_support.deprecation = :stderr 39 | 40 | # Raises error for missing translations 41 | # config.action_view.raise_on_missing_translations = true 42 | end 43 | -------------------------------------------------------------------------------- /config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = '1.0' 5 | 6 | # Add additional assets to the asset load path 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | 9 | # Precompile additional assets. 10 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. 11 | Rails.application.config.assets.precompile += %w( sessions.js ) 12 | -------------------------------------------------------------------------------- /config/initializers/authy.rb: -------------------------------------------------------------------------------- 1 | require 'yaml' 2 | Authy.api_key = Rails.application.secrets.authy_key 3 | Authy.api_uri = 'https://api.authy.com/' -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.action_dispatch.cookies_serializer = :json 4 | -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password] 5 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | -------------------------------------------------------------------------------- /config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.session_store :cookie_store, key: '_two_factor_authy_session' 4 | -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] if respond_to?(:wrap_parameters) 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # To learn more, please read the Rails Internationalization guide 20 | # available at http://guides.rubyonrails.org/i18n.html. 21 | 22 | en: 23 | hello: "Hello world" 24 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | # Account section 3 | resource :account, only: [:show] 4 | 5 | # Sessions 6 | resources :sessions, only: [:new, :create] 7 | get "sessions/destroy" 8 | get "sessions/two_factor" 9 | 10 | post "authy/callback" => 'authy#callback' 11 | get "authy/status" => 'authy#one_touch_status' 12 | post "authy/send_token" 13 | post "authy/verify" 14 | 15 | # Create users 16 | resources :users, only: [:new, :create] 17 | 18 | # Home page 19 | root 'main#index' 20 | end 21 | -------------------------------------------------------------------------------- /config/secrets.yml: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key is used for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | 6 | # Make sure the secret is at least 30 characters and all random, 7 | # no regular words or you'll be exposed to dictionary attacks. 8 | # You can use `rake secret` to generate a secure secret key. 9 | 10 | # Make sure the secrets in this file are kept private 11 | # if you're sharing your code publicly. 12 | 13 | development: 14 | secret_key_base: 2995cd200a475082070d5ad7b11c69407a6219b0b9bf1f747f62234709506c097da19f731ecf125a3fb53694ee103798d6962c199603b92be8f08b00bf6dbb18 15 | authy_key: <%= ENV["AUTHY_API_KEY"] %> 16 | 17 | test: 18 | secret_key_base: deff24bab059bbcceeee98afac8df04814c44dd87d30841f8db532a815b64387d69cfd3d09a78417869c4846f133ba7978068882ca0a96626136ebd084b70732 19 | authy_key: <%= ENV["AUTHY_API_KEY"] %> 20 | 21 | # Do not keep production secrets in the repository, 22 | # instead read values from the environment. 23 | production: 24 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> 25 | authy_key: <%= ENV["AUTHY_API_KEY"] %> 26 | -------------------------------------------------------------------------------- /config/unicorn.rb: -------------------------------------------------------------------------------- 1 | worker_processes Integer(ENV["WEB_CONCURRENCY"] || 3) 2 | timeout 15 3 | preload_app true 4 | 5 | before_fork do |server, worker| 6 | Signal.trap 'TERM' do 7 | puts 'Unicorn master intercepting TERM and sending myself QUIT instead' 8 | Process.kill 'QUIT', Process.pid 9 | end 10 | 11 | defined?(ActiveRecord::Base) and 12 | ActiveRecord::Base.connection.disconnect! 13 | end 14 | 15 | after_fork do |server, worker| 16 | Signal.trap 'TERM' do 17 | puts 'Unicorn worker intercepting TERM and doing nothing. Wait for master to send QUIT' 18 | end 19 | 20 | defined?(ActiveRecord::Base) and 21 | ActiveRecord::Base.establish_connection 22 | end 23 | -------------------------------------------------------------------------------- /db/migrate/20150219231715_create_users.rb: -------------------------------------------------------------------------------- 1 | class CreateUsers < ActiveRecord::Migration[5.1] 2 | def change 3 | create_table :users do |t| 4 | t.string :email, null: false 5 | t.string :password_digest, null: false 6 | 7 | t.timestamps null: false 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20150220155337_adds_user_name_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddsUserNameToUsers < ActiveRecord::Migration[5.1] 2 | def change 3 | add_column :users, :name, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20150220161843_add_phone_number_and_country_code_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddPhoneNumberAndCountryCodeToUsers < ActiveRecord::Migration[5.1] 2 | def change 3 | add_column :users, :phone_number, :string 4 | add_column :users, :country_code, :string 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20150220171049_add_authy_token_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddAuthyTokenToUsers < ActiveRecord::Migration[5.1] 2 | def change 3 | add_column :users, :authy_id, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20151014174632_add_status_and_uuid_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddStatusAndUuidToUsers < ActiveRecord::Migration[5.1] 2 | def change 3 | add_column :users, :authy_status, :integer, :null => false, :default => 0 4 | add_column :users, :uuid, :string 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/schema.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # This file is auto-generated from the current state of the database. Instead 3 | # of editing this file, please use the migrations feature of Active Record to 4 | # incrementally modify your database, and then regenerate this schema definition. 5 | # 6 | # Note that this schema.rb definition is the authoritative source for your 7 | # database schema. If you need to create the application database on another 8 | # system, you should be using db:schema:load, not running all the migrations 9 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations 10 | # you'll amass, the slower it'll run and the greater likelihood for issues). 11 | # 12 | # It's strongly recommended that you check this file into your version control system. 13 | 14 | ActiveRecord::Schema.define(version: 20151014174632) do 15 | 16 | # These are extensions that must be enabled in order to support this database 17 | enable_extension "plpgsql" 18 | 19 | create_table "users", force: :cascade do |t| 20 | t.string "email", null: false 21 | t.string "password_digest", null: false 22 | t.datetime "created_at", null: false 23 | t.datetime "updated_at", null: false 24 | t.string "name" 25 | t.string "phone_number" 26 | t.string "country_code" 27 | t.string "authy_id" 28 | t.integer "authy_status", default: 0, null: false 29 | t.string "uuid" 30 | end 31 | 32 | end 33 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should contain all the record creation needed to seed the database with its default values. 2 | # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). 3 | # 4 | # Examples: 5 | # 6 | # cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }]) 7 | # Mayor.create(name: 'Emanuel', city: cities.first) 8 | -------------------------------------------------------------------------------- /lib/assets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/authy2fa-rails/6e00015449f00fb9e90b2d0b2dc7607503f9dd47/lib/assets/.keep -------------------------------------------------------------------------------- /lib/tasks/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/authy2fa-rails/6e00015449f00fb9e90b2d0b2dc7607503f9dd47/lib/tasks/.keep -------------------------------------------------------------------------------- /log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/authy2fa-rails/6e00015449f00fb9e90b2d0b2dc7607503f9dd47/log/.keep -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The page you were looking for doesn't exist.

62 |

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

63 |
64 |

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

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The change you wanted was rejected.

62 |

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

63 |
64 |

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

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

We're sorry, but something went wrong.

62 |
63 |

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

64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/authy2fa-rails/6e00015449f00fb9e90b2d0b2dc7607503f9dd47/public/favicon.ico -------------------------------------------------------------------------------- /public/landing.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Two Factor Authentication Tutorial 10 | 19 | 20 | 87 | 88 | 89 |
90 | 119 | 120 | 121 | 125 | 126 | 127 | 139 | 140 | 141 | 142 | 143 | 162 | 163 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /test/controllers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/authy2fa-rails/6e00015449f00fb9e90b2d0b2dc7607503f9dd47/test/controllers/.keep -------------------------------------------------------------------------------- /test/controllers/accounts_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class AccountsControllerTest < ActionController::TestCase 4 | test "should redirect when not logged in" do 5 | get :show 6 | assert_response :redirect 7 | assert_redirected_to new_session_path 8 | end 9 | 10 | test "should succeed when logged in" do 11 | session["user_id"] = users(:one).id 12 | get :show 13 | assert_response :success 14 | assert_equal assigns(:user), users(:one) 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /test/controllers/authy_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'ostruct' 3 | 4 | class AuthyControllerTest < ActionController::TestCase 5 | setup do 6 | @user = User.create(user_params(authy_id: "123")) 7 | end 8 | 9 | teardown do 10 | @user.destroy 11 | end 12 | 13 | test "should post to callback successfully" do 14 | skip 15 | post :callback, "{\"authy_id\":\"123\",\"status\":\"approved\"}" 16 | @user.update(authy_status: @request.params[:status]) 17 | assert @user.approved?, "User should be updated" 18 | assert_response :success 19 | end 20 | 21 | test "should get one_touch_status successfully" do 22 | session["pre_2fa_auth_user_id"] = @user.id 23 | get :one_touch_status 24 | assert_response :success 25 | end 26 | 27 | test "should post to send_token successfully" do 28 | session["pre_2fa_auth_user_id"] = @user.id 29 | Authy::API.expects(:request_sms).with(id: "123").once 30 | post :send_token 31 | assert_response :success 32 | end 33 | 34 | test "should post to verify successfully" do 35 | session["pre_2fa_auth_user_id"] = @user.id 36 | verify = OpenStruct.new(:ok? => true) 37 | Authy::API.expects(:verify).with( 38 | id: @user.authy_id, 39 | token: '123456' 40 | ).once.returns(verify) 41 | post :verify, params: { token: '123456' } 42 | assert_response :redirect 43 | assert_redirected_to account_path 44 | assert_nil session["pre_2fa_auth_user_id"] 45 | assert_equal session[:user_id], @user.id 46 | end 47 | 48 | test "should post to verify unsuccessfully" do 49 | session["pre_2fa_auth_user_id"] = @user.id 50 | verify = OpenStruct.new(:ok? => false) 51 | Authy::API.expects(:verify).with( 52 | id: @user.authy_id, 53 | token: '123456' 54 | ).once.returns(verify) 55 | post :verify, params: { token: '123456' } 56 | assert_response :redirect 57 | assert_redirected_to new_session_path 58 | end 59 | 60 | end 61 | -------------------------------------------------------------------------------- /test/controllers/main_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class MainControllerTest < ActionController::TestCase 4 | test "should get index" do 5 | get :index 6 | assert_response :success 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/controllers/sessions_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'ostruct' 3 | 4 | class SessionsControllerTest < ActionController::TestCase 5 | setup do 6 | @user = User.create(user_params(authy_id: "123")) 7 | end 8 | 9 | teardown do 10 | @user.destroy 11 | end 12 | 13 | test "should get new" do 14 | get :new 15 | assert_response :success 16 | assert assigns(:user) 17 | end 18 | 19 | test "should post to create successfully" do 20 | Authy::OneTouch 21 | .expects(:send_approval_request) 22 | .with(id: '123', message: 'Request to Login to Twilio demo app', details: {'Email Address' => 'blah@example.com'}) 23 | .returns('success' => true) 24 | .once 25 | 26 | post :create, params: { email: @user.email, password: user_params[:password] } 27 | assert_response :success 28 | assert_equal @user.id, session["pre_2fa_auth_user_id"] 29 | 30 | json_response = JSON.parse(response.body) 31 | assert_equal true, json_response['success'] 32 | end 33 | 34 | test "should post to create unsuccessfully" do 35 | Authy::OneTouch.expects(:send_approval_request).never 36 | post :create, params: { email: @user.email, password: "blah" } 37 | assert_response :success 38 | assert_template :new 39 | assert_nil session["pre_2fa_auth_user_id"] 40 | end 41 | 42 | test "should get destroy" do 43 | session["user_id"] = @user.id 44 | assert session["user_id"], "Precondition: user should be logged in" 45 | get :destroy 46 | assert_response :redirect 47 | assert_redirected_to root_path 48 | assert_nil session["user_id"] 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /test/controllers/users_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'ostruct' 3 | 4 | class UsersControllerTest < ActionController::TestCase 5 | test "should get new" do 6 | get :new 7 | assert_response :success 8 | assert assigns(:user) 9 | assert assigns(:user).new_record? 10 | end 11 | 12 | test "should post successfully to create" do 13 | authy = OpenStruct.new(:id => '123') 14 | Authy::API.expects(:register_user). 15 | with(authy_params(user_params)).once.returns(authy) 16 | assert_difference "User.count" do 17 | post :create, params: { user: user_params } 18 | assert_response :redirect 19 | assert_redirected_to account_path 20 | end 21 | end 22 | 23 | test "should post unsuccessfully to create" do 24 | Authy::API.expects(:register_user).never 25 | assert_no_difference "User.count" do 26 | post :create, params: { user: user_params(email: "blah") } 27 | assert_response :success 28 | assert_template :new 29 | assert assigns(:user) 30 | end 31 | end 32 | 33 | def authy_params(user_params) 34 | { 35 | email: user_params[:email], 36 | cellphone: user_params[:phone_number], 37 | country_code: user_params[:country_code] 38 | } 39 | end 40 | 41 | end 42 | -------------------------------------------------------------------------------- /test/fixtures/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/authy2fa-rails/6e00015449f00fb9e90b2d0b2dc7607503f9dd47/test/fixtures/.keep -------------------------------------------------------------------------------- /test/fixtures/users.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | email: blah@blah.com 5 | password_digest: secret 6 | name: blah 7 | country_code: +44 8 | phone_number: 07712345678 9 | 10 | two: 11 | email: hello@hello.com 12 | password_digest: secret 13 | name: blah 14 | country_code: +44 15 | phone_number: 07712345678 16 | -------------------------------------------------------------------------------- /test/helpers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/authy2fa-rails/6e00015449f00fb9e90b2d0b2dc7607503f9dd47/test/helpers/.keep -------------------------------------------------------------------------------- /test/integration/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/authy2fa-rails/6e00015449f00fb9e90b2d0b2dc7607503f9dd47/test/integration/.keep -------------------------------------------------------------------------------- /test/mailers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/authy2fa-rails/6e00015449f00fb9e90b2d0b2dc7607503f9dd47/test/mailers/.keep -------------------------------------------------------------------------------- /test/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/authy2fa-rails/6e00015449f00fb9e90b2d0b2dc7607503f9dd47/test/models/.keep -------------------------------------------------------------------------------- /test/models/user_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class UserTest < ActiveSupport::TestCase 4 | test "user requires email" do 5 | assert !User.new(user_params(email: nil)).valid? 6 | end 7 | 8 | test "email looks like an email" do 9 | assert !User.new(user_params(email: "blah")).valid? 10 | assert User.new(user_params).valid? 11 | end 12 | 13 | test "email should be unique" do 14 | user = users(:one) 15 | user2 = User.new(user_params(email: user.email)) 16 | assert !user2.valid? 17 | end 18 | 19 | test "user requires password" do 20 | assert !User.new(user_params(password: nil)).valid? 21 | end 22 | 23 | test "user requires name" do 24 | assert !User.new(user_params(name: nil)).valid? 25 | end 26 | 27 | test "user requires country code" do 28 | assert !User.new(user_params(country_code: nil)).valid? 29 | end 30 | 31 | test "user requires phone number" do 32 | assert !User.new(user_params(phone_number: nil)).valid? 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | ENV['RAILS_ENV'] ||= 'test' 2 | require File.expand_path('../../config/environment', __FILE__) 3 | require 'rails/test_help' 4 | require 'mocha/mini_test' 5 | 6 | class ActiveSupport::TestCase 7 | # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. 8 | fixtures :all 9 | 10 | # Add more helper methods to be used by all tests here... 11 | 12 | def user_params(params={}) 13 | { 14 | password: "hello", 15 | email: "blah@example.com", 16 | name: "Phil", 17 | phone_number: "07712345678", 18 | country_code: "+44" 19 | }.merge(params) 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/authy2fa-rails/6e00015449f00fb9e90b2d0b2dc7607503f9dd47/vendor/assets/javascripts/.keep -------------------------------------------------------------------------------- /vendor/assets/stylesheets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/authy2fa-rails/6e00015449f00fb9e90b2d0b2dc7607503f9dd47/vendor/assets/stylesheets/.keep --------------------------------------------------------------------------------