├── .document ├── .github └── workflows │ └── build.yml ├── .gitignore ├── .rspec ├── CHANGELOG.md ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── authy.gemspec ├── bin └── authy-api-console ├── examples ├── Gemfile ├── README.md └── demo.rb ├── lib ├── authy.rb └── authy │ ├── api.rb │ ├── config.rb │ ├── models │ └── user.rb │ ├── onetouch.rb │ ├── phone_verification.rb │ ├── response.rb │ ├── url_helpers.rb │ └── version.rb ├── spec ├── authy │ ├── api_spec.rb │ ├── config_spec.rb │ ├── onetouch_spec.rb │ ├── phone_verification_spec.rb │ ├── response_spec.rb │ └── url_helpers_spec.rb └── spec_helper.rb └── verify-legacy-v1.md /.document: -------------------------------------------------------------------------------- 1 | lib/**/*.rb 2 | bin/* 3 | - 4 | features/**/*.feature 5 | LICENSE.txt 6 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | fail-fast: false 10 | matrix: 11 | ruby: [2.5, 2.6, 2.7, "3.0", 3.1, head] 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: Set up Ruby ${{ matrix.ruby }} 15 | uses: ruby/setup-ruby@v1 16 | with: 17 | ruby-version: ${{ matrix.ruby }} 18 | - name: Install dependencies 19 | run: bundle install 20 | - name: Run tests 21 | run: bundle exec rspec 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # rcov generated 2 | coverage 3 | coverage.data 4 | 5 | # rdoc generated 6 | rdoc 7 | 8 | # yard generated 9 | doc 10 | .yardoc 11 | 12 | # bundler 13 | .bundle 14 | 15 | # jeweler generated 16 | pkg 17 | 18 | # Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore: 19 | # 20 | # * Create a file at ~/.gitignore 21 | # * Include files you want ignored 22 | # * Run: git config --global core.excludesfile ~/.gitignore 23 | # 24 | # After doing this, these files will be ignored in all your git projects, 25 | # saving you from having to 'pollute' every project you touch with them 26 | # 27 | # 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) 28 | # 29 | # For MacOS: 30 | # 31 | .DS_Store 32 | 33 | # For TextMate 34 | *.tmproj 35 | tmtags 36 | 37 | # For emacs: 38 | *~ 39 | \#* 40 | .\#* 41 | 42 | # For vim: 43 | *.swp 44 | 45 | # For redcar: 46 | #.redcar 47 | 48 | # For rubinius: 49 | #*.rbc 50 | 51 | *.kdev4 52 | 53 | examples/db 54 | .ruby-version 55 | .ruby-gemset 56 | .rvmrc 57 | 58 | Gemfile.lock -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --order random -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog for `Authy` 2 | 3 | ## Final release [☰](https://github.com/twilio/authy-ruby/compare/v3.0.0...v3.0.1) 4 | 5 | * Major updates 6 | * Added deprecation notices 7 | 8 | ## 3.0.0 (14 December, 2020) [☰](https://github.com/twilio/authy-ruby/compare/v2.7.5...v3.0.0) 9 | 10 | * Major updates 11 | * Removed Phone Intelligence APIs 12 | * Deprecated Phone Verification APIs (use the Twilio Verify API instead: https://twil.io/verify-start-ruby) 13 | * Mocked API requests in tests, removed Sandbox API 14 | * Minor updates 15 | * Added API calls for requesting an email and updating a user's email address (#68) 16 | * Removed polyfills for try_convert 17 | * Updates to insecure dependencies 18 | 19 | ## 2.7.5 and older 20 | 21 | Please check the [commit log](https://github.com/twilio/authy-ruby/compare/f9e9236...v2.7.5). -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gemspec -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011-2021 Authy Inc 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Gem Version](https://badge.fury.io/rb/authy.svg)](https://rubygems.org/gems/authy/) [![Build Status](https://github.com/twilio/authy-ruby/workflows/build/badge.svg)](https://github.com/twilio/authy-ruby/actions) [![Code Climate](https://codeclimate.com/github/authy/authy-ruby.png)](https://codeclimate.com/github/authy/authy-ruby) 2 | 3 | --- 4 | 5 | 🚨🚨🚨 6 | 7 | **This library is no longer actively maintained.** The Authy API has been replaced with the [Twilio Verify API](https://www.twilio.com/docs/verify). Twilio will support the Authy API through November 1, 2022 for SMS/Voice. After this date, we’ll start to deprecate the service for SMS/Voice. Any requests sent to the API after May 1, 2023, will automatically receive an error. Push and TOTP will continue to be supported through July 2023. 8 | 9 | [Learn more about migrating from Authy to Verify.](https://www.twilio.com/blog/migrate-authy-to-verify) 10 | 11 | Please visit the Twilio Docs for: 12 | * [Verify + Ruby (Rails) quickstart](https://www.twilio.com/docs/verify/quickstarts/ruby-rails) 13 | * [Twilio Ruby helper library](https://www.twilio.com/docs/libraries/ruby) 14 | * [Verify API reference](https://www.twilio.com/docs/verify/api) 15 | 16 | Please direct any questions to [Twilio Support](https://support.twilio.com/hc/en-us). Thank you! 17 | 18 | 🚨🚨🚨 19 | 20 | --- 21 | 22 | 23 | 24 | # Ruby Client for Twilio Authy Two-Factor Authentication (2FA) API 25 | 26 | Documentation for Ruby usage of the Authy API lives in the [official Twilio documentation](https://www.twilio.com/docs/authy/api/). 27 | 28 | The Authy API supports multiple channels of 2FA: 29 | * One-time passwords via SMS and voice. 30 | * Soft token ([TOTP](https://www.twilio.com/docs/glossary/totp) via the Authy App) 31 | * Push authentication via the Authy App 32 | 33 | If you only need SMS and Voice support for one-time passwords, we recommend using the [Twilio Verify API](https://www.twilio.com/docs/verify/api) instead. 34 | 35 | [More on how to choose between Authy and Verify here.](https://www.twilio.com/docs/verify/authy-vs-verify) 36 | 37 | ### Authy Quickstart 38 | 39 | For a full tutorial, check out either of the Ruby Authy Quickstart in our docs: 40 | 41 | * [Ruby/Rails Authy Quickstart](https://www.twilio.com/docs/authy/quickstart/two-factor-authentication-ruby-rails) 42 | 43 | ## Authy Ruby Installation 44 | 45 | ``` 46 | gem install authy 47 | ``` 48 | 49 | ## Usage 50 | 51 | To use the Authy client, require the `authy` gem and initialize it with our API URI and your production API Key found in the [Twilio Console](https://www.twilio.com/console/authy/applications/): 52 | 53 | ```ruby 54 | require 'authy' 55 | 56 | Authy.api_uri = 'https://api.authy.com' 57 | Authy.api_key = 'your-api-key' 58 | ``` 59 | 60 | Rails users can put this in config/initializers and create a new file called `authy.rb`. 61 | 62 | ![authy api key in console](https://s3.amazonaws.com/com.twilio.prod.twilio-docs/images/account-security-api-key.width-800.png) 63 | 64 | ## 2FA Workflow 65 | 66 | 1. [Create a user](https://www.twilio.com/docs/authy/api/users#enabling-new-user) 67 | 2. [Send a one-time password](https://www.twilio.com/docs/authy/api/one-time-passwords) 68 | 3. [Verify a one-time password](https://www.twilio.com/docs/authy/api/one-time-passwords#verify-a-one-time-password) 69 | 70 | **OR** 71 | 72 | 1. [Create a user](https://www.twilio.com/docs/authy/api/users#enabling-new-user) 73 | 2. [Send a push authentication](https://www.twilio.com/docs/authy/api/push-authentications) 74 | 3. [Check a push authentication status](https://www.twilio.com/docs/authy/api/push-authentications#check-approval-request-status) 75 | 76 | 77 | ## Phone Verification 78 | 79 | [Phone verification now lives in the Twilio API](https://www.twilio.com/docs/verify/api) and has [Ruby support through the official Twilio helper libraries](https://www.twilio.com/docs/libraries/ruby). 80 | 81 | [Legacy (V1) documentation here.](verify-legacy-v1.md) **Verify V1 is not recommended for new development. Please consider using [Verify V2](https://www.twilio.com/docs/verify/api)**. 82 | 83 | ## Demo 84 | 85 | See the [`./examples`](./examples) directory for a demo CLI application that uses the Authy API. 86 | 87 | ## Contributing 88 | * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet. 89 | * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it. 90 | * Fork the project. 91 | * Start a feature/bugfix branch. 92 | * Commit and push until you are happy with your contribution. 93 | * Add tests. 94 | * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit. 95 | 96 | ## Notice: Twilio Authy API’s Sandbox feature will stop working on Sep 30, 2021 97 | Twilio is discontinuing the Authy API’s Sandbox, a feature that allows customers to run continuous integration tests against a mock Authy API for free. The Sandbox is no longer being maintained, so we will be taking the final deprecation step of shutting it down on September 30, 2021. The rest of the Authy API product will continue working as-is. 98 | 99 | This repo previously used the sandbox API as part of the test suite, but that has been since removed. 100 | 101 | You will only be affected if you are using the sandbox API in your own application or test suite. 102 | 103 | For more information please read this article on [how we are discontinuing the Twilio Authy sandbox API](https://support.authy.com/hc/en-us/articles/1260803396889-Notice-Twilio-Authy-API-s-Sandbox-feature-will-stop-working-on-Sep-30-2021). 104 | 105 | ## Copyright 106 | 107 | Copyright (c) 2011-2021 Authy Inc. See [LICENSE](https://github.com/twilio/authy-ruby/blob/master/LICENSE.txt) for further details. 108 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | require 'rake' 3 | require 'rspec/core' 4 | require 'rspec/core/rake_task' 5 | 6 | RSpec::Core::RakeTask.new(:spec) do |spec| 7 | spec.pattern = FileList['spec/**/*_spec.rb'] 8 | end 9 | 10 | task :default => :spec 11 | 12 | RSpec::Core::RakeTask.new(:rcov) do |spec| 13 | spec.pattern = 'spec/**/*_spec.rb' 14 | spec.rcov = true 15 | end 16 | 17 | require 'reek/rake/task' 18 | Reek::Rake::Task.new do |t| 19 | t.fail_on_error = true 20 | t.verbose = false 21 | t.source_files = 'lib/**/*.rb' 22 | end 23 | 24 | require 'yard' 25 | YARD::Rake::YardocTask.new 26 | 27 | task :simplecov do 28 | require 'simplecov' 29 | ENV['COVERAGE'] = "true" 30 | Rake::Task['spec'].execute 31 | end 32 | -------------------------------------------------------------------------------- /authy.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | $:.push File.expand_path("../lib", __FILE__) 3 | 4 | require 'authy/version' 5 | 6 | Gem::Specification.new do |s| 7 | s.name = "authy" 8 | s.version = Authy::VERSION 9 | s.authors = ["Authy Inc"] 10 | s.email = ["support@authy.com"] 11 | s.homepage = "https://github.com/authy/authy-ruby" 12 | s.summary = %q{Deprecated: please see README for details} 13 | s.description = %q{Ruby library to access Authy services. This gem is deprecated, please see the README for details.} 14 | s.license = 'MIT' 15 | 16 | s.files = `git ls-files`.split("\n") 17 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 18 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 19 | s.require_paths = ["lib"] 20 | 21 | s.add_dependency('httpclient', '>= 2.5.3.3') 22 | 23 | s.add_development_dependency('rake') 24 | s.add_development_dependency('rspec') 25 | s.add_development_dependency('pry') 26 | s.add_development_dependency('yard') 27 | s.add_development_dependency('rdoc') 28 | s.add_development_dependency('simplecov') 29 | s.add_development_dependency('reek') 30 | end 31 | -------------------------------------------------------------------------------- /bin/authy-api-console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'pry' 4 | require 'bundler/setup' 5 | Bundler.require 6 | 7 | require File.expand_path('../../lib/authy', __FILE__) 8 | 9 | Pry.start(TOPLEVEL_BINDING) 10 | 11 | -------------------------------------------------------------------------------- /examples/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "authy" 4 | gem "sqlite3" 5 | gem "activerecord" 6 | gem "highline" 7 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Demo 2 | 3 | In this `example` directory is a demo of using the Twilio Authy Two-Factor Authentication (2FA) API with the Authy gem. 4 | 5 | ## How does it work? 6 | 7 | The demo works on the command line and allows you to register a user in your Authy application, request a token for that user and verify a token for a user. Registered users and their authy_id token are stored in a local SQLite database. 8 | 9 | ## How to use the demo 10 | 11 | Change into the `examples` directory and install the dependencies with: 12 | 13 | ```bash 14 | bundle install 15 | ``` 16 | 17 | Then, run the demo with: 18 | 19 | ```bash 20 | ./demo.rb 21 | ``` 22 | 23 | You will be asked for your Authy API key then presented a list of options. 24 | 25 | ```bash 26 | $ ./demo.rb 27 | 28 | Enter your Authy API Key (won't be displayed): 29 | 1. Register a user 30 | 2. Request token 31 | 3. Verify token 32 | What do you want to do? 33 | ``` 34 | 35 | On the first run, choose to register a user. This will ask you for an email address, country code and phone number. With those details a user will be registered with your Authy application and stored in your local database. 36 | 37 | Then you can run the application and either request a token, to have a token sent by SMS, or verify a token, you can use the token from an SMS or the Authy app to verify. 38 | 39 | ### API keys 40 | 41 | You will be asked for your Authy API key each time you run the demo. You can avoid this by [adding your Authy API key to your environment variables](https://www.twilio.com/blog/2017/01/how-to-set-environment-variables.html). 42 | 43 | ``` 44 | export AUTHY_API_KEY=ABC123xxxxx 45 | ./demo.rb 46 | ``` 47 | 48 | Check out the code in `./examples/demo.rb` to see how the API and this gem is used. 49 | -------------------------------------------------------------------------------- /examples/demo.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'authy' # gem install authy 4 | require 'sqlite3' 5 | require 'active_record' 6 | require 'highline/import' # gem install highline 7 | 8 | trap("SIGINT") { exit! } 9 | 10 | # setup db 11 | ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => "db") 12 | class AddUsers < ActiveRecord::Migration[6.0] 13 | def self.up 14 | create_table :users do |t| 15 | t.string :email 16 | t.string :authy_id 17 | end 18 | end 19 | 20 | def self.down 21 | drop_table :users 22 | end 23 | end 24 | AddUsers.migrate(:up) if !File.exist?("db") 25 | 26 | class User < ActiveRecord::Base 27 | validates_uniqueness_of :email 28 | end 29 | 30 | api_key = ENV["AUTHY_API_KEY"] || ask("Enter your Authy API Key (won't be displayed): "){ |q| q.echo = false } 31 | 32 | Authy.api_url = "https://api.authy.com" 33 | Authy.api_key = api_key 34 | 35 | choose do |menu| 36 | menu.prompt = "What do you want to do? " 37 | 38 | menu.choice("Register a user") do 39 | loop do 40 | email = ask("email: ") 41 | country_code = ask("country code: ") 42 | cellphone = ask("cellphone: ") 43 | 44 | if user = User.where(:email => email).first 45 | puts "You're already registered with authy id: #{user.authy_id}" 46 | break 47 | end 48 | 49 | # register user on authy. email, country code and cellphone are mandatory 50 | authy_user = Authy::API.register_user(:email => email, :country_code => country_code, :cellphone => cellphone) 51 | if authy_user.ok? 52 | puts "User was registered. its authy id is #{authy_user.id}" 53 | User.create!(:authy_id => authy_user.id, :email => email) 54 | break 55 | else 56 | puts "Failed to register user: #{authy_user.errors}" 57 | end 58 | end 59 | end 60 | 61 | menu.choice("Request token") do 62 | email = ask("email: ") 63 | 64 | user = User.where(:email => email).first 65 | if !user 66 | puts "User is not registered yet" 67 | return 68 | end 69 | 70 | # send sms to the user. `force` makes it send the sms even if the user uses a smartphone 71 | # this api call will return an error if the account doesn't have the SMS addon enabled 72 | response = Authy::API.request_sms(:id => user.authy_id, :force => true) 73 | 74 | if response.ok? 75 | puts "Message was sent" 76 | else 77 | puts "Failed to send message: #{response.errors.inspect}" 78 | end 79 | end 80 | 81 | menu.choice("Verify token") do 82 | email = ask("email: ") 83 | 84 | user = User.where(:email => email).first 85 | if !user 86 | puts "User is not registered yet" 87 | return 88 | end 89 | 90 | token = ask("token: ") 91 | 92 | # verify if the given token is correct. `force` makes it validate the code even if the user has not confirmed its account 93 | otp = Authy::API.verify(:id => user.authy_id, :token => token, :force => true) 94 | 95 | if otp.ok? 96 | puts "Welcome back!" 97 | else 98 | puts "Wrong email or token :(" 99 | end 100 | end 101 | end 102 | -------------------------------------------------------------------------------- /lib/authy.rb: -------------------------------------------------------------------------------- 1 | $:.unshift File.expand_path("..", __FILE__) 2 | 3 | require 'httpclient' 4 | require 'httpclient/include_client' 5 | require 'json' 6 | 7 | require 'authy/version' 8 | require 'authy/url_helpers' 9 | require 'authy/response' 10 | require 'authy/models/user' 11 | require 'authy/config' 12 | require 'authy/api' 13 | require 'authy/phone_verification' 14 | require 'authy/onetouch' 15 | 16 | warn "DEPRECATION WARNING: The authy-ruby library is no longer actively maintained. The Authy API is being replaced by the Twilio Verify API. Please see the README for more details." -------------------------------------------------------------------------------- /lib/authy/api.rb: -------------------------------------------------------------------------------- 1 | require "logger" 2 | 3 | module Authy 4 | class API 5 | MIN_TOKEN_SIZE = 6 6 | MAX_TOKEN_SIZE = 12 7 | 8 | include Authy::URL 9 | 10 | extend HTTPClient::IncludeClient 11 | include_http_client 12 | 13 | def self.register_user(attributes) 14 | api_key = attributes.delete(:api_key) || Authy.api_key 15 | send_install_link_via_sms = attributes.delete(:send_install_link_via_sms) { true } 16 | params = { 17 | :user => attributes, 18 | :send_install_link_via_sms => send_install_link_via_sms 19 | } 20 | 21 | url = "#{Authy.api_uri}/protected/json/users/new" 22 | response = http_client.post(url, :body => escape_query(params), :header => default_header(params: { api_key: api_key })) 23 | 24 | Authy::User.new(response) 25 | end 26 | 27 | # options: 28 | # :id user id 29 | # :token authy token entered by the user 30 | # :force (true|false) force to check even if the cellphone is not confirmed 31 | # 32 | def self.verify(params) 33 | token = params.delete(:token) || params.delete("token") 34 | user_id = params.delete(:id) || params.delete("id") 35 | 36 | return invalid_response("Token format is invalid") unless token_is_safe?(token) 37 | return invalid_response("User id is invalid") unless is_digit?(user_id) 38 | 39 | params[:force] = true if params[:force].nil? && params["force"].nil? 40 | 41 | response = get_request("protected/json/verify/:token/:user_id", params.merge({ 42 | "token" => token, 43 | "user_id" => user_id 44 | })) 45 | 46 | return verify_response(response) if response.ok? 47 | return response 48 | end 49 | 50 | # options: 51 | # :id user id 52 | # :force force sms 53 | def self.request_sms(params) 54 | user_id = params.delete(:id) || params.delete("id") 55 | 56 | get_request("protected/json/sms/:user_id", params.merge({"user_id" => user_id})) 57 | end 58 | 59 | # options: 60 | # :id user id 61 | # :qr_size qr size 62 | # :qr_label context for qr code 63 | def self.request_qr_code(params) 64 | user_id = params.delete(:id) || params.delete("id") 65 | qr_size = params.delete(:qr_size) || params.delete("qr_size") || 300 66 | qr_label = params.delete(:qr_label) || params.delete("qr_label") || "" 67 | 68 | return invalid_response("User id is invalid") unless is_digit?(user_id) 69 | return invalid_response("Qr image size is invalid") unless is_digit?(qr_size) 70 | 71 | response = post_request("protected/json/users/:user_id/secret", params.merge({ 72 | "user_id" => user_id, 73 | "qr_size" => qr_size, 74 | "label" => qr_label 75 | })) 76 | end 77 | 78 | # options: 79 | # :id user id 80 | # :force force phone_call 81 | def self.request_phone_call(params) 82 | user_id = params.delete(:id) || params.delete("id") 83 | 84 | get_request("protected/json/call/:user_id", params.merge({"user_id" => user_id})) 85 | end 86 | 87 | # options: 88 | # :id user id 89 | def self.request_email(params) 90 | user_id = params.delete(:id) || params.delete('id') 91 | 92 | post_request("protected/json/email/:user_id", params.merge({"user_id" => user_id})) 93 | end 94 | 95 | # options: 96 | # :id user id 97 | # :email user's new email 98 | def self.update_user(params) 99 | user_id = params.delete(:id) || params.delete('id') 100 | 101 | post_request("protected/json/users/:user_id/update", params.merge({"user_id" => user_id})) 102 | end 103 | 104 | # options: 105 | # :id user id 106 | def self.delete_user(params) 107 | user_id = params.delete(:id) || params.delete("id") 108 | 109 | post_request("protected/json/users/delete/:user_id", params.merge({"user_id" => user_id})) 110 | end 111 | 112 | # options: 113 | # :id user id 114 | def self.user_status(params) 115 | user_id = params.delete(:id) || params.delete("id") 116 | get_request("protected/json/users/:user_id/status", params.merge({"user_id" => user_id})) 117 | end 118 | 119 | private 120 | 121 | def self.post_request(uri, params = {}) 122 | header_ = default_header(params: params) 123 | 124 | uri_params = keys_to_verify(uri, params) 125 | state, error = validate_for_url(uri_params, params) 126 | 127 | response = if state 128 | url = "#{Authy.api_uri}/#{eval_uri(uri, params)}" 129 | params = clean_uri_params(uri_params, params) 130 | http_client.post(url, :body => escape_query(params), header: header_) 131 | else 132 | build_error_response(error) 133 | end 134 | Authy::Response.new(response) 135 | end 136 | 137 | def self.get_request(uri, params = {}) 138 | header_ = default_header(params: params) 139 | 140 | uri_params = keys_to_verify(uri, params) 141 | state, error = validate_for_url(uri_params, params) 142 | response = if state 143 | url = "#{Authy.api_uri}/#{eval_uri(uri, params)}" 144 | params = clean_uri_params(uri_params, params) 145 | http_client.get(url, params, header_) 146 | else 147 | build_error_response(error) 148 | end 149 | Authy::Response.new(response) 150 | end 151 | 152 | def self.build_error_response(error = "blank uri param found") 153 | OpenStruct.new({ 154 | "status" => 400, 155 | "body" => { 156 | "success" => false, 157 | "message" => error, 158 | "errors" => { 159 | "message" => error, 160 | }, 161 | }.to_json 162 | }) 163 | end 164 | 165 | def self.token_is_safe?(token) 166 | !!(/\A\d{#{MIN_TOKEN_SIZE},#{MAX_TOKEN_SIZE}}\Z/.match token) 167 | end 168 | 169 | def self.is_digit?(str) 170 | !!(/^\d+$/.match str.to_s) 171 | end 172 | 173 | def self.invalid_response(str = "Invalid resonse") 174 | response = build_error_response(str) 175 | return Authy::Response.new(response) 176 | end 177 | 178 | def self.verify_response(response) 179 | return response if response["token"] == "is valid" 180 | response = build_error_response("Token is invalid") 181 | return Authy::Response.new(response) 182 | end 183 | 184 | def self.default_header(params: {}) 185 | api_key = params.delete(:api_key) || params.delete("api_key") 186 | 187 | header = { 188 | "X-Authy-API-Key" => api_key || Authy.api_key, 189 | "User-Agent" => Authy.user_agent 190 | } 191 | 192 | return header 193 | end 194 | end 195 | end 196 | -------------------------------------------------------------------------------- /lib/authy/config.rb: -------------------------------------------------------------------------------- 1 | module Authy 2 | class << self 3 | def api_key=(key) 4 | @api_key = key 5 | end 6 | 7 | def api_key 8 | @api_key || ENV["AUTHY_API_KEY"] 9 | end 10 | 11 | def api_uri=(uri) 12 | @api_uri = uri 13 | end 14 | alias :api_url= :api_uri= 15 | 16 | def api_uri 17 | @api_uri || "https://api.authy.com" 18 | end 19 | alias :api_url :api_uri 20 | 21 | def user_agent 22 | @user_agent || "AuthyRuby/#{Authy::VERSION} (#{RUBY_PLATFORM}, Ruby #{RUBY_VERSION})" 23 | end 24 | 25 | def user_agent=(user_agent) 26 | @user_agent = user_agent 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/authy/models/user.rb: -------------------------------------------------------------------------------- 1 | module Authy 2 | class User < Authy::Response 3 | def id 4 | self['id'] 5 | end 6 | 7 | def errors 8 | case 9 | when self.ok? 10 | {} 11 | when !@errors.empty? 12 | @errors 13 | else 14 | {"error" => error_msg} 15 | end 16 | end 17 | 18 | protected 19 | def parse_body 20 | begin 21 | body = JSON.parse(@raw_response.body) 22 | body = body['user'] if body['user'] 23 | 24 | if self.ok? 25 | body.each do |k,v| 26 | self[k] = v 27 | end 28 | else 29 | if body.has_key?('errors') 30 | @errors = body['errors'] 31 | else 32 | @errors = body 33 | end 34 | end 35 | rescue Exception => e 36 | end 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/authy/onetouch.rb: -------------------------------------------------------------------------------- 1 | module Authy 2 | class OneTouch < Authy::API 3 | 4 | # Maximum String size for the parameters 5 | MAX_STRING_SIZE = 200 6 | 7 | # options: 8 | # :id 9 | # :details Hash containing the approval request details 10 | # :hidden_details 11 | # :phone_number The persons phone number. 12 | def self.send_approval_request(params) 13 | user_id = params.delete(:id) || params.delete('id') 14 | message = (params.delete(:message) || params.delete('message')).to_s 15 | details = params.delete(:details) || params.delete('details') 16 | hidden_details = params.delete(:hidden_details) || params.delete('hidden_details') 17 | logos = params.delete(:logos) || params.delete('logos') 18 | seconds_to_expire = params.delete(:seconds_to_expire) || params.delete('seconds_to_expire') 19 | 20 | return invalid_response("message cannot be blank") if message.nil? || message.empty? 21 | return invalid_response('user id is invalid') unless is_digit?(user_id) 22 | 23 | begin 24 | self.clean_hash!(details) 25 | self.clean_hash!(hidden_details) 26 | logos = self.clean_logos!(logos) 27 | rescue => e 28 | return invalid_response("Invalid parameters: #{e.message}") 29 | end 30 | 31 | params = { message: message[0, MAX_STRING_SIZE] } 32 | params[:details] = details unless details.nil? 33 | params[:hidden_details] = hidden_details unless hidden_details.nil? 34 | params[:logos] = logos unless logos.nil? 35 | params[:seconds_to_expire] = seconds_to_expire unless seconds_to_expire.nil? 36 | 37 | post_request("onetouch/json/users/#{user_id}/approval_requests", params) 38 | end 39 | 40 | def self.approval_request_status(params) 41 | uuid = params.delete(:uuid) || params.delete('uuid') 42 | 43 | get_request("onetouch/json/approval_requests/#{uuid}") 44 | end 45 | 46 | private 47 | def self.clean_hash!(test_hash) 48 | return if test_hash.nil? # Allow nil hash 49 | 50 | raise "Hash expected. Got: #{test_hash.class}" unless test_hash.is_a? Hash 51 | test_hash = test_hash.map { |k, v| [k, v.to_s] }.to_h 52 | end 53 | 54 | def self.clean_logos!(logos) 55 | return if logos.nil? # Allow nil logos 56 | 57 | raise "Array expected. Got #{logos.class}" unless logos.is_a? Array 58 | logos = logos.map do |logo| 59 | raise "Invalid logo format: #{logo}" unless logo.is_a? Hash 60 | res = logo.delete(:res) || logo.delete('res') 61 | url = logo.delete(:url) || logo.delete('url') 62 | 63 | raise "Logo should include res and url" if res.nil? || url.nil? 64 | 65 | # We ignore any additional parameter on the logos, and truncate 66 | # string size to the maximum allowed. 67 | { res: res[0, MAX_STRING_SIZE], url: url[0, MAX_STRING_SIZE] } 68 | end 69 | end 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /lib/authy/phone_verification.rb: -------------------------------------------------------------------------------- 1 | module Authy 2 | class PhoneVerification < Authy::API 3 | # options: 4 | # :via (sms|call) 5 | # :country_code Numeric calling country code of the country. 6 | # :phone_number The persons phone number. 7 | # :custom_code Pass along any generated custom code. 8 | # :custom_message Custom Message. 9 | # :code_length Length of code to be sent(4-10). 10 | # :locale The language of the message received by user. 11 | def self.start(params) 12 | warn "Authy Phone Verification has been superseded by the Twilio Verify API. Check https://twil.io/verify-start-ruby to see how to start a verification with the Twilio Verify API." 13 | params[:via] = "sms" unless %w(sms call).include?(params[:via]) 14 | 15 | post_request("protected/json/phones/verification/start", params) 16 | end 17 | 18 | # options: 19 | # :country_code Numeric calling country code of the country. 20 | # :phone_number The persons phone number. 21 | # :verification_code The verification code entered by the user. 22 | def self.check(params) 23 | warn "Authy Phone Verification has been superseded by the Twilio Verify API. Check https://twil.io/verify-check-ruby to see how to check a verification the Twilio Verify API." 24 | get_request("protected/json/phones/verification/check", params) 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/authy/response.rb: -------------------------------------------------------------------------------- 1 | module Authy 2 | class Response < Hash 3 | attr_reader :raw_response 4 | def initialize(response) 5 | @raw_response = response 6 | @errors = {} 7 | parse_body 8 | end 9 | 10 | def id 11 | self["id"] 12 | end 13 | 14 | def ok? 15 | @raw_response.status == 200 16 | end 17 | alias :success? :ok? 18 | 19 | def body 20 | @raw_response.body 21 | end 22 | 23 | def code 24 | @raw_response.status 25 | end 26 | 27 | def error_msg 28 | if ok? 29 | "No error" 30 | elsif self.empty? 31 | self.body 32 | else 33 | self["message"] || "No error" 34 | end 35 | end 36 | 37 | def errors 38 | self["errors"] || @errors 39 | end 40 | 41 | protected 42 | 43 | def method_missing(name, *args, &block) 44 | if self.include?(name.to_s) 45 | self[name.to_s] 46 | else 47 | super(name, *args, &block) 48 | end 49 | end 50 | 51 | def parse_body 52 | body = JSON.parse(@raw_response.body) 53 | body.each do |k,v| 54 | self[k] = v 55 | end 56 | rescue 57 | self['message'] = 'invalid json' 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /lib/authy/url_helpers.rb: -------------------------------------------------------------------------------- 1 | module Authy 2 | module URL 3 | def self.included receiver 4 | receiver.extend ClassMethods 5 | end 6 | 7 | module ClassMethods 8 | def keys_to_verify(uri, params) 9 | uri.scan(/:(\w+)/).flatten 10 | end 11 | 12 | def clean_uri_params(uri_params, params) 13 | params.reject { |k, v| uri_params.include? k} 14 | end 15 | 16 | def eval_uri(uri, params = {}) 17 | uri.gsub(/:\w+/) do |s| 18 | param_name = s.gsub(":", "") 19 | HTTP::Message.escape(params[param_name].to_s) 20 | end 21 | end 22 | 23 | def validate_for_url(names, to_validate = {}) 24 | names.each do |name| 25 | value = to_validate[name] 26 | if value.nil? or value.to_s.empty? or value.to_s.split(" ").size == 0 27 | return [ false, "#{name} is blank." ] 28 | end 29 | end 30 | [ true, ""] 31 | end 32 | 33 | def to_param(left, right) 34 | HTTP::Message.escape(left) + '=' + HTTP::Message.escape(right.to_s) 35 | end 36 | 37 | def params_from_array(left, values) 38 | values.map do |value| 39 | if value.respond_to?(:read) 40 | value = value.read 41 | end 42 | 43 | if value.kind_of?(Hash) 44 | escape_query(value, left+"[]") 45 | else 46 | to_param(left + '[]', value) 47 | end 48 | end 49 | end 50 | 51 | def escape_params(params) 52 | params.each do |attr, value| 53 | if value.kind_of?(String) 54 | params[attr] = HTTP::Message.escape(value) 55 | elsif value.kind_of?(Hash) 56 | escape_params(value) 57 | end 58 | end 59 | end 60 | 61 | # Copied and extended from httpclient's HTTP::Message#escape_query() 62 | def escape_query(query, namespace = nil) # :nodoc: 63 | pairs = [] 64 | 65 | query.each do |attr, value| 66 | left = namespace ? "#{namespace}[#{attr.to_s}]" : attr.to_s 67 | if values = Array.try_convert(value) 68 | pairs += params_from_array(left, values) 69 | elsif values = Hash.try_convert(value) 70 | pairs.push(escape_query(values, left.dup)) 71 | else 72 | if value.respond_to?(:read) 73 | value = value.read 74 | end 75 | pairs.push(to_param(left, value)) 76 | end 77 | end 78 | pairs.join('&') 79 | end 80 | end 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /lib/authy/version.rb: -------------------------------------------------------------------------------- 1 | module Authy 2 | VERSION = "3.0.1" 3 | end 4 | -------------------------------------------------------------------------------- /spec/authy/api_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | class Utils 4 | include Authy::URL 5 | end 6 | 7 | describe "Authy::API" do 8 | let(:headers) { { "X-Authy-API-Key" => Authy.api_key, "User-Agent" => "AuthyRuby/#{Authy::VERSION} (#{RUBY_PLATFORM}, Ruby #{RUBY_VERSION})" } } 9 | let(:user_id) { 81547 } 10 | let(:invalid_api_key) { "invalid_api_key" } 11 | 12 | describe "request headers" do 13 | it "contains api key and user agent in header" do 14 | expect_any_instance_of(HTTPClient).to receive(:request).twice.with(any_args, hash_including(header: headers)) { double(ok?: true, body: "", status: 200) } 15 | 16 | url = "protected/json/foo/2" 17 | Authy::API.get_request(url, {}) 18 | Authy::API.post_request(url, {}) 19 | end 20 | end 21 | 22 | describe "Registering users" do 23 | let(:register_user_url) { "#{Authy.api_uri}/protected/json/users/new" } 24 | 25 | it "should register a user successfully" do 26 | user_attributes = { 27 | email: generate_email, 28 | cellphone: generate_cellphone, 29 | country_code: 1 30 | } 31 | response_json = { 32 | "message" => "User created successfully.", 33 | "user" => { "id" => user_id }, 34 | "success" => true 35 | }.to_json 36 | expect(Authy::API.http_client).to receive(:request) 37 | .once 38 | .with(:post, register_user_url, { 39 | :body => Utils.escape_query( 40 | :user => user_attributes, 41 | :send_install_link_via_sms => true 42 | ), 43 | :header => headers 44 | }) 45 | .and_return(double(:status => 200, :body => response_json)) 46 | 47 | user = Authy::API.register_user(user_attributes) 48 | 49 | expect(user).to be_kind_of(Authy::Response) 50 | expect(user).to be_kind_of(Authy::User) 51 | expect(user).to_not be_nil 52 | expect(user.id).to_not be_nil 53 | expect(user.id).to be(user_id) 54 | end 55 | 56 | it "should return the error messages as a hash" do 57 | user_attributes = { 58 | email: generate_email, 59 | cellphone: "abc-1234", 60 | country_code: 1 61 | } 62 | response_json = { 63 | "cellphone" => "is invalid", 64 | "message" => "User was not valid", 65 | "success" => false, 66 | "errors" => { 67 | "cellphone" => "is invalid", 68 | "message" => "User was not valid" 69 | }, 70 | "error_code" => "60027" 71 | }.to_json 72 | 73 | expect(Authy::API.http_client).to receive(:request) 74 | .once 75 | .with(:post, register_user_url, { 76 | :body => Utils.escape_query( 77 | :user => user_attributes, 78 | :send_install_link_via_sms => true 79 | ), 80 | :header => headers 81 | }) 82 | .and_return(double(:status => 400, :body => response_json)) 83 | 84 | user = Authy::API.register_user(user_attributes) 85 | 86 | expect(user.errors).to be_kind_of(Hash) 87 | expect(user.errors["cellphone"]).to include "is invalid" 88 | end 89 | 90 | it "should allow to override the API key" do 91 | response_json = { 92 | "error_code" => "60001", 93 | "message" => "Invalid API key", 94 | "errors" => {"message" => "Invalid API key"}, 95 | "success" => false 96 | }.to_json 97 | user_attributes = { 98 | email: generate_email, 99 | cellphone: generate_cellphone, 100 | country_code: 1, 101 | api_key: invalid_api_key 102 | } 103 | 104 | headers["X-Authy-API-Key"] = invalid_api_key 105 | 106 | expect(Authy::API.http_client).to receive(:request) 107 | .once 108 | .with(:post, register_user_url, { 109 | :body => Utils.escape_query( 110 | :user => user_attributes.reject { |k,v| k === :api_key }, 111 | :send_install_link_via_sms => true 112 | ), 113 | :header => headers 114 | }) 115 | .and_return(double(:status => 401, :body => response_json)) 116 | 117 | user = Authy::API.register_user(user_attributes) 118 | 119 | expect(user).to_not be_ok 120 | expect(user.errors["message"]).to match(/invalid api key/i) 121 | end 122 | 123 | it "should allow overriding send_install_link_via_sms default" do 124 | response_json = { 125 | "message" => "User created successfully.", 126 | "user" => { "id" => user_id }, 127 | "success" => true 128 | }.to_json 129 | user_attributes = { 130 | email: generate_email, 131 | cellphone: generate_cellphone, 132 | country_code: 1, 133 | send_install_link_via_sms: false 134 | } 135 | expect(Authy::API.http_client).to receive(:request) 136 | .once 137 | .with(:post, register_user_url, { 138 | :body => Utils.escape_query( 139 | :user => user_attributes.reject { |k,v| k === :send_install_link_via_sms }, 140 | :send_install_link_via_sms => false 141 | ), 142 | :header => headers 143 | }) 144 | .and_return(double(:status => 200, :body => response_json)) 145 | 146 | user = Authy::API.register_user(user_attributes) 147 | 148 | expect(user).to be_kind_of(Authy::Response) 149 | expect(user).to be_kind_of(Authy::User) 150 | expect(user).to_not be_nil 151 | expect(user.id).to_not be_nil 152 | expect(user.id).to be_kind_of(Integer) 153 | end 154 | end 155 | 156 | describe "verifying tokens" do 157 | let(:token) { "123456" } 158 | let(:verify_url) { "#{Authy.api_uri}/protected/json/verify/#{token}/#{user_id}" } 159 | 160 | it "should fail to validate a given token if the token is the wrong format" do 161 | expect(Authy::API.http_client).to receive(:request).never 162 | response = Authy::API.verify(token: "invalid_token", id: user_id) 163 | 164 | expect(response).to be_kind_of(Authy::Response) 165 | expect(response.ok?).to be_falsey 166 | expect(response.errors["message"]).to include "Token format is invalid" 167 | end 168 | 169 | it "should allow to override the API key" do 170 | response_json = { 171 | "error_code" => "60001", 172 | "message" => "Invalid API key", 173 | "errors" => {"message" => "Invalid API key"}, 174 | "success" => false 175 | }.to_json 176 | headers["X-Authy-API-Key"] = invalid_api_key 177 | expect(Authy::API.http_client).to receive(:request) 178 | .once 179 | .with(:get, verify_url, { 180 | :query => { :force => true }, 181 | :header => headers, 182 | :follow_redirect => nil 183 | }) 184 | .and_return(double(:status => 401, :body => response_json)) 185 | 186 | response = Authy::API.verify(token: "123456", id: user_id, api_key: invalid_api_key) 187 | 188 | expect(response).to_not be_ok 189 | expect(response.errors["message"]).to match(/invalid api key/i) 190 | end 191 | 192 | it "should escape the params" do 193 | expect(Authy::API.http_client).to receive(:request).never 194 | expect do 195 | Authy::API.verify(token: "[=#%@$&#(!@);.,", id: user_id) 196 | end.to_not raise_error 197 | end 198 | 199 | it "should escape the params if have white spaces" do 200 | expect(Authy::API.http_client).to receive(:request).never 201 | expect do 202 | Authy::API.verify(token: "token with space", id: user_id) 203 | end.to_not raise_error 204 | end 205 | 206 | it "should fail if a param is missing" do 207 | expect(Authy::API.http_client).to receive(:request).never 208 | response = Authy::API.verify(id: user_id) 209 | expect(response).to be_kind_of(Authy::Response) 210 | expect(response).to_not be_ok 211 | expect(response["message"]).to include("Token format is invalid") 212 | end 213 | 214 | it "fails when token format is invalid" do 215 | expect(Authy::API.http_client).to receive(:request).never 216 | response = Authy::API.verify(token: "0000", id: user_id) 217 | 218 | expect(response.ok?).to be_falsey 219 | expect(response).to be_kind_of(Authy::Response) 220 | expect(response.errors["message"]).to eq "Token format is invalid" 221 | end 222 | end 223 | 224 | describe "requesting qr code for other authenticator apps" do 225 | it "should request qrcode" do 226 | url = "#{Authy.api_uri}/protected/json/users/#{user_id}/secret" 227 | expect_any_instance_of(HTTPClient).to receive(:request).with(:post, url, body: "qr_size=300&label=example+app+name", header: { "X-Authy-API-Key" => Authy.api_key, "User-Agent" => "AuthyRuby/#{Authy::VERSION} (#{RUBY_PLATFORM}, Ruby #{RUBY_VERSION})" }) { double(ok?: true, body: "", status: 200) } 228 | response = Authy::API.send("request_qr_code", id: user_id, qr_size: 300, qr_label: "example app name") 229 | expect(response).to be_ok 230 | end 231 | 232 | context "user id is not a number" do 233 | it "should not be ok" do 234 | expect(Authy::API.http_client).to receive(:request).never 235 | response = Authy::API.send("request_qr_code", id: "tony") 236 | expect(response.errors["message"]).to eq "User id is invalid" 237 | expect(response).to_not be_ok 238 | end 239 | end 240 | 241 | context "qr size is not a number" do 242 | it "should return the right error" do 243 | expect(Authy::API.http_client).to receive(:request).never 244 | response = Authy::API.send("request_qr_code", id: user_id, qr_size: "notanumber") 245 | expect(response.errors["message"]).to eq "Qr image size is invalid" 246 | expect(response).to_not be_ok 247 | end 248 | end 249 | end 250 | 251 | ["sms", "phone_call"].each do |kind| 252 | title = kind.upcase 253 | describe "Requesting #{title}" do 254 | let(:uri_param) { kind == "phone_call" ? "call" : kind } 255 | let(:url) { "#{Authy.api_uri}/protected/json/#{uri_param}/#{user_id}" } 256 | 257 | it "should request a #{title} token" do 258 | expect_any_instance_of(HTTPClient).to receive(:request).with(:get, url, { query: {}, header: { "X-Authy-API-Key" => Authy.api_key, "User-Agent" => "AuthyRuby/#{Authy::VERSION} (#{RUBY_PLATFORM}, Ruby #{RUBY_VERSION})" }, follow_redirect: nil }) { double(ok?: true, body: "", status: 200) } 259 | response = Authy::API.send("request_#{kind}", id: user_id) 260 | expect(response).to be_ok 261 | end 262 | 263 | it "should allow to override the API key" do 264 | response_json = { 265 | "error_code" => "60001", 266 | "message" => "Invalid API key", 267 | "errors" => {"message" => "Invalid API key"}, 268 | "success" => false 269 | }.to_json 270 | headers["X-Authy-API-Key"] = invalid_api_key 271 | expect(Authy::API.http_client).to receive(:request) 272 | .once 273 | .with(:get, url, { 274 | :header => headers, 275 | :query => {}, 276 | :follow_redirect => nil 277 | }) 278 | .and_return(double(:status => 401, :body => response_json)) 279 | response = Authy::API.send("request_#{kind}", id: user_id, api_key: invalid_api_key) 280 | expect(response).to_not be_ok 281 | expect(response.errors["message"]).to match(/invalid api key/i) 282 | end 283 | 284 | it "should request a #{title} token using custom actions" do 285 | response_json = { 286 | :success => true 287 | }.to_json 288 | expect(Authy::API.http_client).to receive(:request) 289 | .once 290 | .with(:get, url, { 291 | :header => headers, 292 | :query => { 293 | :action => "custom action?", 294 | :action_message => "Action message $%^?@#" 295 | }, 296 | :follow_redirect => nil 297 | }) 298 | .and_return(double(:status => 200, :body => response_json)) 299 | response = Authy::API.send("request_#{kind}", id: user_id, action: "custom action?", action_message: "Action message $%^?@#") 300 | expect(response).to be_ok 301 | end 302 | 303 | context "user doesn't exist" do 304 | it "should not be ok" do 305 | url = "#{Authy.api_uri}/protected/json/#{uri_param}/tony" 306 | response_json = { 307 | "message" => "User not found.", 308 | "success" => false, 309 | "errors" => { "message" => "User not found." }, 310 | "error_code" => "60026" 311 | }.to_json 312 | expect(Authy::API.http_client).to receive(:request) 313 | .once 314 | .with(:get, url, { 315 | :header => headers, 316 | :query => { }, 317 | :follow_redirect => nil 318 | }) 319 | .and_return(double(:status => 404, :body => response_json)) 320 | response = Authy::API.send("request_#{kind}", id: "tony") 321 | expect(response.errors["message"]).to eq "User not found." 322 | expect(response).to_not be_ok 323 | end 324 | end 325 | end 326 | end 327 | 328 | describe "Requesting email" do 329 | it "should request an email token" do 330 | response_json = { 331 | "success" => true, 332 | "message" => "Email token was sent", 333 | "email" => "recipient@foo.com", 334 | "email_id" => "EMa364aa751cc280d8c22772307e2c5760" 335 | }.to_json 336 | url = "#{Authy.api_uri}/protected/json/email/#{user_id}" 337 | 338 | expect(Authy::API.http_client).to receive(:request) 339 | .once 340 | .with(:post, url, { body: "", header: headers }) 341 | .and_return(double(ok?: true, body: response_json, status: 200)) 342 | response = Authy::API.request_email(id: user_id) 343 | expect(response).to be_ok 344 | end 345 | 346 | context "user doesn't exist" do 347 | it "should not be ok" do 348 | url = "#{Authy.api_uri}/protected/json/email/tony" 349 | response_json = { 350 | "message" => "User not found.", 351 | "success" => false, 352 | "errors" => { 353 | "message" => "User not found." 354 | }, 355 | "error_code" => "60026" 356 | }.to_json 357 | expect(Authy::API.http_client).to receive(:request) 358 | .once 359 | .with(:post, url, { 360 | :header => headers, 361 | :body => "" 362 | }) 363 | .and_return(double(:status => 404, :body => response_json)) 364 | response = Authy::API.request_email(id: "tony") 365 | expect(response.errors['message']).to eq "User not found." 366 | expect(response).to_not be_ok 367 | end 368 | end 369 | end 370 | 371 | describe "update user email" do 372 | context "user doesn't exist" do 373 | it "should not be ok" do 374 | url = "#{Authy.api_uri}/protected/json/users/tony/update" 375 | response_json = { 376 | "message" => "User not found.", 377 | "success" => false, 378 | "errors" => { 379 | "message" => "User not found." 380 | }, 381 | "error_code" => "60026" 382 | }.to_json 383 | new_email = generate_email 384 | expect(Authy::API.http_client).to receive(:request) 385 | .once 386 | .with(:post, url, { 387 | :header => headers, 388 | :body => Utils.escape_query(:email => new_email) 389 | }) 390 | .and_return(double(:status => 404, :body => response_json)) 391 | 392 | response = Authy::API.update_user(id: "tony", email: new_email) 393 | expect(response.errors['message']).to eq "User not found." 394 | expect(response).to_not be_ok 395 | end 396 | end 397 | 398 | context "user exists" do 399 | it "should be ok" do 400 | response_json = { 401 | "message" => "User was updated successfully", 402 | "success" => true 403 | }.to_json 404 | url = "#{Authy.api_uri}/protected/json/users/#{user_id}/update" 405 | new_email = generate_email 406 | expect(Authy::API.http_client).to receive(:request) 407 | .once 408 | .with(:post, url, { 409 | body: Utils.escape_query({ 410 | email: new_email 411 | }), 412 | header: headers 413 | }) 414 | .and_return(double(ok?: true, body: response_json, status: 200)) 415 | response = Authy::API.update_user(id: user_id, email: new_email) 416 | expect(response.message).to eq "User was updated successfully" 417 | expect(response).to be_ok 418 | end 419 | end 420 | end 421 | 422 | describe "delete users" do 423 | context "user doesn't exist" do 424 | it "should not be ok" do 425 | url = "#{Authy.api_uri}/protected/json/users/delete/tony" 426 | response_json = { 427 | "message" => "User not found.", 428 | "success" => false, 429 | "errors" => { 430 | "message" => "User not found." 431 | }, 432 | "error_code" => "60026" 433 | }.to_json 434 | expect(Authy::API.http_client).to receive(:request) 435 | .once 436 | .with(:post, url, { 437 | :header => headers, 438 | :body => "" 439 | }) 440 | .and_return(double(:status => 404, :body => response_json)) 441 | response = Authy::API.delete_user(id: "tony") 442 | expect(response.errors["message"]).to eq "User not found." 443 | expect(response).to_not be_ok 444 | end 445 | end 446 | 447 | context "user exists" do 448 | let(:url) { "#{Authy.api_uri}/protected/json/users/delete/31567" } 449 | it "should be ok" do 450 | response_json = { 451 | "message" => "User removed from application", 452 | "success" => false 453 | }.to_json 454 | expect(Authy::API.http_client).to receive(:request) 455 | .once 456 | .with(:post, url, { 457 | :header => headers, 458 | :body => "" 459 | }) 460 | .and_return(double(:status => 200, :body => response_json)) 461 | response = Authy::API.delete_user(id: 31567) 462 | expect(response.message).to eq "User removed from application" 463 | expect(response).to be_ok 464 | end 465 | end 466 | end 467 | 468 | describe "user status" do 469 | context "user doesn't exist" do 470 | it "should not be ok" do 471 | url = "#{Authy.api_uri}/protected/json/users/tony/status" 472 | response_json = { 473 | "message" => "User not found.", 474 | "success" => false, 475 | "errors" => { 476 | "message" => "User not found." 477 | }, 478 | "error_code" => "60026" 479 | }.to_json 480 | expect(Authy::API.http_client).to receive(:request) 481 | .once 482 | .with(:get, url, { 483 | :header => headers, 484 | :query => {}, 485 | :follow_redirect => nil 486 | }) 487 | .and_return(double(:status => 404, :body => response_json)) 488 | response = Authy::API.user_status(id: "tony") 489 | expect(response.errors["message"]).to eq "User not found." 490 | expect(response).to_not be_ok 491 | end 492 | end 493 | 494 | context "user exists" do 495 | it "should be ok" do 496 | url = "#{Authy.api_uri}/protected/json/users/290907/status" 497 | response_json = { 498 | "status" => { 499 | "authy_id" => 290907, 500 | "confirmed" => true, 501 | "registered" => false, 502 | "country_code" => 1, 503 | "phone_number" => "XXX-XXX-1118", 504 | "email" => "alfvawmu@authy.com", 505 | "devices" => [], 506 | "has_hard_token" => false, 507 | "account_disabled" => false, 508 | "detailed_devices" => [{ 509 | "device_type" => "authy", 510 | "os_type" => "unknown", 511 | "registration_device_id" => nil, 512 | "registration_method" => nil, 513 | "device_id" => 290908, 514 | "last_sync_date" => 0, 515 | "creation_date" => 1433868212 516 | }], 517 | "deleted_devices" => [] 518 | }, 519 | "message" => "User status.", 520 | "success" => true 521 | }.to_json 522 | expect(Authy::API.http_client).to receive(:request) 523 | .once 524 | .with(:get, url, { 525 | :header => headers, 526 | :query => {}, 527 | :follow_redirect => nil 528 | }) 529 | .and_return(double(:status => 200, :body => response_json)) 530 | response = Authy::API.user_status(id: 290907) 531 | expect(response.status).to be_kind_of(Hash) 532 | expect(response).to be_ok 533 | end 534 | end 535 | end 536 | 537 | describe "blank params" do 538 | [:request_sms, :request_phone_call, :delete_user].each do |method| 539 | it "should return a proper response with the errors for #{method}" do 540 | expect(Authy::API.http_client).to receive(:request).never 541 | response = Authy::API.send(method, id: nil) 542 | expect(response).to_not be_ok 543 | expect(response.message).to eq "user_id is blank." 544 | end 545 | end 546 | 547 | it "should return a prope response with the errors for verify" do 548 | expect(Authy::API.http_client).to receive(:request).never 549 | response = Authy::API.verify({}) 550 | expect(response).to_not be_ok 551 | expect(response.message).to eq "Token format is invalid" 552 | end 553 | end 554 | 555 | describe ".token_is_safe?" do 556 | it "checks minimum token size" do 557 | expect(Authy::API.send(:token_is_safe?, "1")).to be false 558 | end 559 | 560 | it "checks valid characters" do 561 | expect(Authy::API.send(:token_is_safe?, "123456")).to be true 562 | expect(Authy::API.send(:token_is_safe?, "123456a")).to be false 563 | end 564 | 565 | it "checks maximum token size" do 566 | expect(Authy::API.send(:token_is_safe?, "123456789098")).to be true 567 | expect(Authy::API.send(:token_is_safe?, "1234567890987")).to be false 568 | end 569 | end 570 | end 571 | -------------------------------------------------------------------------------- /spec/authy/config_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'Authy' do 4 | describe 'api_key' do 5 | before do 6 | @default_api_key = Authy.api_key 7 | Authy.api_key = nil 8 | ENV['AUTHY_API_KEY'] = nil 9 | end 10 | 11 | after do 12 | Authy.api_key = @default_api_key 13 | end 14 | 15 | it "should set and read instance variable" do 16 | Authy.api_key = 'foo' 17 | expect(Authy.api_key).to eq 'foo' 18 | end 19 | 20 | it "should fallback to ENV variable" do 21 | ENV['AUTHY_API_KEY'] = 'bar' 22 | expect(Authy.api_key).to eq 'bar' 23 | end 24 | end 25 | 26 | describe "api_url" do 27 | before do 28 | @default_api_url = Authy.api_url 29 | Authy.api_url = nil 30 | end 31 | 32 | after do 33 | Authy.api_url = @default_api_url 34 | end 35 | 36 | it "should set and read instance variable" do 37 | Authy.api_url = 'https://example.com/' 38 | expect(Authy.api_url).to eq 'https://example.com/' 39 | end 40 | 41 | it "should fallback to default value" do 42 | Authy.api_url = nil 43 | expect(Authy.api_url).to eq 'https://api.authy.com' 44 | end 45 | end 46 | 47 | describe "user_agent" do 48 | before do 49 | @default_user_agent = Authy.user_agent 50 | Authy.user_agent = nil 51 | end 52 | 53 | after do 54 | Authy.user_agent = @default_user_agent 55 | end 56 | 57 | it "should set and read instance variable" do 58 | Authy.user_agent = 'AuthyRuby NewUserAgent' 59 | expect(Authy.user_agent).to eq 'AuthyRuby NewUserAgent' 60 | end 61 | 62 | it "should fallback to default value" do 63 | Authy.user_agent = nil 64 | expect(Authy.user_agent).to eq "AuthyRuby/#{Authy::VERSION} (#{RUBY_PLATFORM}, Ruby #{RUBY_VERSION})" 65 | end 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /spec/authy/onetouch_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | class Utils 4 | include Authy::URL 5 | end 6 | 7 | describe Authy::OneTouch do 8 | let(:headers) { { "X-Authy-API-Key" => Authy.api_key, "User-Agent" => "AuthyRuby/#{Authy::VERSION} (#{RUBY_PLATFORM}, Ruby #{RUBY_VERSION})" } } 9 | let(:user_id) { 81547 } 10 | 11 | describe ".send_approval_request" do 12 | let(:url) { "#{Authy.api_url}/onetouch/json/users/#{user_id}/approval_requests" } 13 | it 'creates a new approval_request for user' do 14 | response_json = { 15 | "approval_request" => { 16 | "uuid" => "550e8400-e29b-41d4-a716-446655440000" 17 | }, 18 | "success" => true 19 | }.to_json 20 | params = { 21 | message: 'You are moving 10 BTC from your account', 22 | details: { 23 | 'Bank account' => '23527922', 24 | 'Amount' => '10 BTC', 25 | }, 26 | hidden_details: { 27 | 'IP Address' => '192.168.0.3' 28 | }, 29 | seconds_to_expire: 150 30 | } 31 | 32 | expect(Authy::API.http_client).to receive(:request) 33 | .once 34 | .with(:post, url, { 35 | :body => Utils.escape_query(params), 36 | :header => headers 37 | }) 38 | .and_return(double(:status => 200, :body => response_json)) 39 | 40 | params[:id] = user_id 41 | response = Authy::OneTouch.send_approval_request(params) 42 | 43 | expect(response).to be_kind_of(Authy::Response) 44 | expect(response).to be_ok 45 | end 46 | 47 | it 'requires message as mandatory' do 48 | expect(Authy::API.http_client).to receive(:request).never 49 | response = Authy::OneTouch.send_approval_request( 50 | id: user_id, 51 | details: { 52 | 'Bank account' => '23527922', 53 | 'Amount' => '10 BTC', 54 | }, 55 | hidden_details: { 56 | 'IP Address' => '192.168.0.3' 57 | } 58 | ) 59 | 60 | expect(response).to be_kind_of(Authy::Response) 61 | expect(response).to_not be_ok 62 | expect(response.message).to eq 'message cannot be blank' 63 | end 64 | 65 | it 'does not require other fields as mandatory' do 66 | response_json = { 67 | "approval_request" => { 68 | "uuid" => "550e8400-e29b-41d4-a716-446655440000" 69 | }, 70 | "success" => true 71 | }.to_json 72 | params = { 73 | message: 'You are moving 10 BTC from your account' 74 | } 75 | 76 | expect(Authy::API.http_client).to receive(:request) 77 | .once 78 | .with(:post, url, { 79 | :body => Utils.escape_query(params), 80 | :header => headers 81 | }) 82 | .and_return(double(:status => 200, :body => response_json)) 83 | 84 | params[:id] = user_id 85 | response = Authy::OneTouch.send_approval_request(params) 86 | 87 | expect(response).to be_kind_of(Authy::Response) 88 | expect(response).to be_ok 89 | end 90 | 91 | it 'checks logos format' do 92 | response_json = { 93 | "approval_request" => { 94 | "uuid" => "550e8400-e29b-41d4-a716-446655440000" 95 | }, 96 | "success" => true 97 | }.to_json 98 | params = { 99 | message: 'You are moving 10 BTC from your account', 100 | details: { 101 | 'Bank account' => '23527922', 102 | 'Amount' => '10 BTC', 103 | }, 104 | hidden_details: { 105 | 'IP Address' => '192.168.0.3' 106 | }, 107 | logos: [{res: 'low', url: 'http://foo.bar'}], 108 | seconds_to_expire: 150 109 | } 110 | expect(Authy::API.http_client).to receive(:request) 111 | .once 112 | .with(:post, url, { 113 | :body => Utils.escape_query(params), 114 | :header => headers 115 | }) 116 | .and_return(double(:status => 200, :body => response_json)) 117 | 118 | params[:id] = user_id 119 | response = Authy::OneTouch.send_approval_request(params) 120 | 121 | expect(response).to be_kind_of(Authy::Response) 122 | expect(response).to be_ok 123 | end 124 | end 125 | 126 | describe '.approval_request_status' do 127 | it 'returns approval request status' do 128 | uuid = '550e8400-e29b-41d4-a716-446655440000' 129 | url = "#{Authy.api_url}/onetouch/json/approval_requests/#{uuid}" 130 | response_json = { 131 | "approval_request" => { 132 | "status" => "pending" 133 | }, 134 | "success" => true 135 | }.to_json 136 | expect(Authy::API.http_client).to receive(:request) 137 | .once 138 | .with(:get, url, { 139 | :header => headers, 140 | :query => {}, 141 | :follow_redirect => nil 142 | }) 143 | .and_return(double(:status => 200, :body => response_json)) 144 | response = Authy::OneTouch.approval_request_status(uuid: uuid) 145 | 146 | expect(response).to be_kind_of(Authy::Response) 147 | expect(response).to be_ok 148 | end 149 | end 150 | end 151 | -------------------------------------------------------------------------------- /spec/authy/phone_verification_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | class Utils 4 | include Authy::URL 5 | end 6 | 7 | describe "Authy::PhoneVerification" do 8 | let(:valid_phone_number) { '201-555-0123' } 9 | let(:invalid_phone_number) { '123' } 10 | let(:headers) { { "X-Authy-API-Key" => Authy.api_key, "User-Agent" => "AuthyRuby/#{Authy::VERSION} (#{RUBY_PLATFORM}, Ruby #{RUBY_VERSION})" } } 11 | let(:start_url) { "#{Authy.api_url}/protected/json/phones/verification/start" } 12 | let(:check_url) { "#{Authy.api_url}/protected/json/phones/verification/check"} 13 | 14 | describe "Sending the verification code" do 15 | it "should send the code via SMS" do 16 | response_json = { 17 | "carrier" => "Fixed Line Operators and Other Networks", 18 | "is_cellphone" => true, 19 | "is_ported" => false, 20 | "message" => "Text message sent to +1 201-555-0123.", 21 | "seconds_to_expire" => 0, 22 | "uuid" => nil, 23 | "success" => true 24 | }.to_json 25 | params = { 26 | via: "sms", 27 | country_code: "1", 28 | phone_number: valid_phone_number 29 | } 30 | expect(Authy::API.http_client).to receive(:request) 31 | .once 32 | .with(:post, start_url, { 33 | :body => Utils.escape_query(params), 34 | :header => headers 35 | }) 36 | .and_return(double(:status => 200, :body => response_json)) 37 | response = Authy::PhoneVerification.start(params) 38 | expect(response).to be_kind_of(Authy::Response) 39 | expect(response).to be_ok 40 | expect(response.message).to eq "Text message sent to +1 #{valid_phone_number}." 41 | end 42 | 43 | it "should send the code via SMS with code length" do 44 | response_json = { 45 | "carrier" => "Fixed Line Operators and Other Networks", 46 | "is_cellphone" => true, 47 | "is_ported" => false, 48 | "message" => "Text message sent to +1 201-555-0123.", 49 | "seconds_to_expire" => 0, 50 | "uuid" => nil, 51 | "success" => true 52 | }.to_json 53 | params = { 54 | via: "sms", 55 | country_code: "1", 56 | phone_number: valid_phone_number, 57 | code_length: "4" 58 | } 59 | expect(Authy::API.http_client).to receive(:request) 60 | .once 61 | .with(:post, start_url, { 62 | :body => Utils.escape_query(params), 63 | :header => headers 64 | }) 65 | .and_return(double(:status => 200, :body => response_json)) 66 | response = Authy::PhoneVerification.start(params) 67 | 68 | expect(response).to be_kind_of(Authy::Response) 69 | expect(response).to be_ok 70 | expect(response.message).to eq "Text message sent to +1 #{valid_phone_number}." 71 | end 72 | end 73 | 74 | describe "validate the fields required" do 75 | it "should return an error. Country code is required" do 76 | response_json = { 77 | "error_code" => "60004", 78 | "message" => "Invalid parameter: country_code - Parameter is required", 79 | "errors" => { 80 | "message" => "Invalid parameter: country_code - Parameter is required" 81 | }, 82 | "success" => false 83 | }.to_json 84 | params = { 85 | via: "sms", 86 | phone_number: valid_phone_number 87 | } 88 | expect(Authy::API.http_client).to receive(:request) 89 | .once 90 | .with(:post, start_url, { 91 | :body => Utils.escape_query(params), 92 | :header => headers 93 | }) 94 | .and_return(double(:status => 400, :body => response_json)) 95 | response = Authy::PhoneVerification.start(params) 96 | 97 | expect(response).to_not be_ok 98 | expect(response.errors['message']).to match(/country_code - Parameter is required/) 99 | end 100 | 101 | it "should return an error. Cellphone is invalid" do 102 | response_json = { 103 | "error_code" => "60033", 104 | "message" => "Phone number is invalid", 105 | "errors" => { 106 | "message" => "Phone number is invalid" 107 | }, 108 | "success" => false 109 | }.to_json 110 | params = { 111 | via: "sms", 112 | country_code: "1", 113 | phone_number: invalid_phone_number 114 | } 115 | expect(Authy::API.http_client).to receive(:request) 116 | .once 117 | .with(:post, start_url, { 118 | :body => Utils.escape_query(params), 119 | :header => headers 120 | }) 121 | .and_return(double(:status => 400, :body => response_json)) 122 | response = Authy::PhoneVerification.start(params) 123 | 124 | expect(response).to_not be_ok 125 | expect(response.errors['message']).to eq('Phone number is invalid') 126 | end 127 | end 128 | 129 | describe "Check the verification code" do 130 | it "should return success true if code is correct" do 131 | response_json = { 132 | "message" => "Verification code is correct.", 133 | "success" => true 134 | }.to_json 135 | params = { 136 | country_code: "1", 137 | phone_number: valid_phone_number, 138 | verification_code: "0000" 139 | } 140 | expect(Authy::API.http_client).to receive(:request) 141 | .once 142 | .with(:get, check_url, { 143 | :query => params, 144 | :header => headers, 145 | :follow_redirect => nil 146 | }) 147 | .and_return(double(:status => 200, :body => response_json)) 148 | response = Authy::PhoneVerification.check(params) 149 | 150 | expect(response).to be_ok 151 | expect(response.message).to eq('Verification code is correct.') 152 | end 153 | 154 | it "should return an error if code is incorrect" do 155 | response_json = { 156 | "error_code" => "60022", 157 | "message" => "Verification code is incorrect", 158 | "errors" => { 159 | "message" => "Verification code is incorrect" 160 | }, 161 | "success" => false 162 | }.to_json 163 | params = { 164 | country_code: "1", 165 | phone_number: valid_phone_number, 166 | verification_code: "1234" 167 | } 168 | expect(Authy::API.http_client).to receive(:request) 169 | .once 170 | .with(:get, check_url, { 171 | :query => params, 172 | :header => headers, 173 | :follow_redirect => nil 174 | }) 175 | .and_return(double(:status => 401, :body => response_json)) 176 | response = Authy::PhoneVerification.check(params) 177 | 178 | expect(response).not_to be_ok 179 | expect(response.message).to eq('Verification code is incorrect') 180 | end 181 | 182 | it "should return an error if there are no active verifications" do 183 | response_json = { 184 | "message" => "No pending verifications for #{valid_phone_number} found.", 185 | "success" => false, 186 | "errors" => { 187 | "message" => "No pending verifications for #{valid_phone_number} found." 188 | }, 189 | "error_code" => "60023" 190 | }.to_json 191 | params = { 192 | :country_code => "1", 193 | :phone_number => valid_phone_number, 194 | :verification_code => "1234" 195 | } 196 | expect(Authy::API.http_client).to receive(:request) 197 | .once 198 | .with(:get, check_url, { 199 | :query => params, 200 | :header => headers, 201 | :follow_redirect => nil 202 | }) 203 | .and_return(double(:status => 404, :body => response_json)) 204 | 205 | response = Authy::PhoneVerification.check(params) 206 | 207 | expect(response).not_to be_ok 208 | expect(response.message).to eq("No pending verifications for #{valid_phone_number} found.") 209 | end 210 | end 211 | 212 | describe 'Check the phone number' do 213 | it "should return an error if phone number is invalid" do 214 | response_json = { 215 | "error_code" => "60033", 216 | "message" => "Phone number is invalid", 217 | "errors" => { 218 | "message" => "Phone number is invalid" 219 | }, 220 | "success" => false 221 | }.to_json 222 | params = { 223 | country_code: "1", 224 | phone_number: invalid_phone_number, 225 | verification_code: "1234" 226 | } 227 | expect(Authy::API.http_client).to receive(:request) 228 | .once 229 | .with(:get, check_url, { 230 | :query => params, 231 | :header => headers, 232 | :follow_redirect => nil 233 | }) 234 | .and_return(double(:status => 400, :body => response_json)) 235 | 236 | response = Authy::PhoneVerification.check(params) 237 | expect(response).not_to be_ok 238 | expect(response.message).to eq('Phone number is invalid') 239 | end 240 | end 241 | 242 | end 243 | -------------------------------------------------------------------------------- /spec/authy/response_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "Authy::Response" do 4 | let(:response) { Authy::Response.new(fake_response) } 5 | 6 | context 'response is valid' do 7 | let(:fake_response) do 8 | OpenStruct.new.tap do |struct| 9 | struct.body = { 'v1' => 'r1','v2' => 42 }.to_json 10 | struct.status = 200 11 | end 12 | end 13 | 14 | it "should parse to json the body" do 15 | expect(response['v2']).to eq 42 16 | expect(response.error_msg).to eq "No error" 17 | expect(response.ok?).to be_truthy 18 | end 19 | end 20 | 21 | context 'response returns a 401 status code' do 22 | let(:fake_response) do 23 | OpenStruct.new.tap do |struct| 24 | struct.status = 401 25 | end 26 | end 27 | 28 | it 'response is not ok' do 29 | expect(response.ok?).to be_falsey 30 | end 31 | end 32 | 33 | context 'response returns invalid json' do 34 | let(:fake_response) do 35 | OpenStruct.new.tap do |struct| 36 | struct.body = 'invalid json' 37 | struct.status = 401 38 | end 39 | end 40 | 41 | it "should return the error message" do 42 | expect(response.error_msg).to eq "invalid json" 43 | expect(response.message).to eq "invalid json" 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /spec/authy/url_helpers_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "Authy::URL" do 4 | class Dummy 5 | include Authy::URL 6 | end 7 | 8 | describe "to_param" do 9 | it "should user HTTP::Message.escape" do 10 | expect(HTTP::Message).to receive(:escape).exactly(2).times.and_return "basic string" 11 | Dummy.to_param('key', 'value') 12 | end 13 | 14 | it "should paramify the left to right" do 15 | expect(Dummy.to_param('key', 'value')).to eq 'key=value' 16 | end 17 | end 18 | 19 | describe "params_from_array" do 20 | it "should return an array of params" do 21 | expect(Dummy.params_from_array("da key", ["one", "two"])).to eq([HTTP::Message.escape("da key[]") + "=one", HTTP::Message.escape("da key[]") + "=two"]) 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) 2 | $LOAD_PATH.unshift(File.dirname(__FILE__)) 3 | 4 | require 'rspec' 5 | require 'authy' 6 | require 'pry' 7 | 8 | # Requires supporting files with custom matchers and macros, etc, 9 | # in ./support/ and its subdirectories. 10 | Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f} 11 | 12 | RSpec.configure do |config| 13 | config.before(:suite) do 14 | Authy.api_key = 'valid_api_key' 15 | end 16 | 17 | def generate_email 18 | domain = "@authy.com" 19 | user = (0...8).map{97.+(rand(25)).chr}.join 20 | user + domain 21 | end 22 | 23 | def generate_cellphone 24 | n = rand(999) + 1000 25 | "192-967-#{n}" 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /verify-legacy-v1.md: -------------------------------------------------------------------------------- 1 | # Phone Verification V1 2 | 3 | [Version 2 of the Verify API is now available!](https://www.twilio.com/docs/verify/api) V2 has an improved developer experience and new features. Some of the features of the V2 API include: 4 | 5 | * Twilio helper libraries in JavaScript, Java, C#, Python, Ruby, and PHP 6 | * PSD2 Secure Customer Authentication Support 7 | * Improved Visibility and Insights 8 | 9 | You are currently viewing Version 1. V1 of the API will be maintained for the time being, but any new features and development will be on Version 2. We strongly encourage you to do any new development with API V2. Check out the migration guide or the API Reference for more information. 10 | 11 | ### API Reference 12 | 13 | API Reference is available at https://www.twilio.com/docs/verify/api/v1 14 | 15 | ### Starting a phone verification 16 | 17 | `Authy::PhoneVerification.start` takes a country code, phone number and a method (sms or call) to deliver the code. 18 | 19 | ```ruby 20 | response = Authy::PhoneVerification.start(via: "sms", country_code: 1, phone_number: "111-111-1111") 21 | if response.ok? 22 | # verification was started 23 | end 24 | ``` 25 | 26 | ### Checking a phone verification 27 | 28 | `Authy::PhoneVerification.check` takes a country code, phone number and a verification code. 29 | 30 | ```ruby 31 | response = Authy::PhoneVerification.check(verification_code: "1234", country_code: 1, phone_number: "111-111-1111") 32 | if response.ok? 33 | # verification was successful 34 | end 35 | ``` --------------------------------------------------------------------------------