sms_auth 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: de10f5bbb91f0b302693b99ddcfc23756c82e724
4
+ data.tar.gz: 4955d39950f401c97b7a1eb1c689bb2354b7a3eb
5
+ SHA512:
6
+ metadata.gz: 4b0bcf6fc489d114886a4bdb9e6f9d7e2edcc6fa94e199a960761001caadf854037787690e05373d035520d28a15a2a0e0197930268f3566b2b3b3882bb90f98
7
+ data.tar.gz: 8656a19532f5aa21b0fd9805218f9d53c29670b76ac741163cd1fef81317d24bacd0757ada25eb7bbb2a5e8aca2d9964e3db72c63ad709b3baaf766f2e269b15
@@ -0,0 +1,20 @@
1
+ Copyright 2017 Andrew Platkin
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.
@@ -0,0 +1,26 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'SmsAuth'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("../spec/test_app/Rakefile", __FILE__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+
21
+ load 'rails/tasks/statistics.rake'
22
+
23
+
24
+
25
+ Bundler::GemHelper.install_tasks
26
+
@@ -0,0 +1,4 @@
1
+ class ApplicationController < ActionController::Base
2
+ include SmsAuth::Engine::AuthControllerHelper
3
+ protect_from_forgery with: :null_session
4
+ end
@@ -0,0 +1,20 @@
1
+ class RegistrationsController < ApplicationController
2
+ respond_to :json
3
+
4
+ def create
5
+ status, message = RegistrationService.new(params[:phone_number]).register_user
6
+ standard_return(status, message)
7
+ end
8
+
9
+ def verify
10
+ status, message, user_id, auth_token, new_user = RegistrationService.new(params[:phone_number], params[:verification_token]).verify_user
11
+
12
+ respond_to do |format|
13
+ if status
14
+ format.json { render json: { message: '', success: true, authentication_token: auth_token, user_id: user_id, new_user: new_user }.to_json, status: 200 }
15
+ else
16
+ format.json { render json: { message: message, success: false }.to_json, status: 400 }
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,9 @@
1
+ class TokensController < ApplicationController
2
+ before_action :authenticate_with_token!
3
+ respond_to :json
4
+
5
+ def logout
6
+ status, message = TokenService.new(current_token).logout
7
+ standard_return(status, message)
8
+ end
9
+ end
@@ -0,0 +1,27 @@
1
+ module AuthControllerHelper
2
+ def current_user
3
+ @current_user ||= AuthenticationToken.find_user_by_token(current_token)
4
+ end
5
+
6
+ def current_token
7
+ @current_token ||= request.headers['Authorization']
8
+ end
9
+
10
+ def authenticate_with_token!
11
+ render json: { errors: 'Endpoint requires authentication' }, status: :unauthorized unless user_signed_in?
12
+ end
13
+
14
+ def user_signed_in?
15
+ current_user.present?
16
+ end
17
+
18
+ def standard_return(status, message)
19
+ respond_to do |format|
20
+ if status
21
+ format.json { render json: { message: '', success: true }.to_json, status: 200 }
22
+ else
23
+ format.json { render json: { message: message, success: false }.to_json, status: 400 }
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,49 @@
1
+ class AuthenticationToken < ActiveRecord::Base
2
+ belongs_to :user
3
+
4
+ before_create :generate_token!
5
+
6
+ def self.expired_tokens
7
+ where('expired_at <= ? OR deleted_at <= ?', Time.zone.now.utc, Time.zone.now.utc)
8
+ end
9
+
10
+ def self.find_by_body(auth_token)
11
+ where(body: auth_token).first
12
+ end
13
+
14
+ def self.find_user_by_token(auth_token)
15
+ return nil if auth_token.nil?
16
+ User.find_by_id(find_user_id_by_token(auth_token))
17
+ end
18
+
19
+ def generate_token!
20
+ self.body = friendly_token if body.nil?
21
+ self.expired_at = default_expired_at if expired_at.nil?
22
+ end
23
+
24
+ def soft_delete
25
+ self.deleted_at = Time.zone.now.utc
26
+ save!
27
+ end
28
+
29
+ private
30
+
31
+ def self.find_user_id_by_token(auth_token)
32
+ result = where(body: auth_token)
33
+ .where('deleted_at IS NULL')
34
+ .where('expired_at > ?', Time.now.utc)
35
+ .select(:user_id)
36
+ .first
37
+
38
+ return result.user_id unless result.nil?
39
+ nil
40
+ end
41
+
42
+ def friendly_token
43
+ SecureRandom.urlsafe_base64((40 * 3) / 4).tr('lIO0', 'sxyz')
44
+ end
45
+
46
+ def default_expired_at
47
+ Time.zone.now + SmsAuth::Engine.default_token_expiration_days.days
48
+ end
49
+ end
@@ -0,0 +1,60 @@
1
+ class PhoneVerification < ActiveRecord::Base
2
+ belongs_to :user
3
+
4
+ before_save :cleanup_phone_number
5
+ before_create :generate_verification_token
6
+
7
+ def self.find_by_phone_number(phone_num)
8
+ where('phone_number = ?', PhoneNumberUtils.cleanup_phone_number(phone_num)).first
9
+ end
10
+
11
+ def reset_verification_token
12
+ generate_verification_token
13
+ self.expired_at = (Time.zone.now.utc + SmsAuth::Engine.verification_token_time_limit_minutes.minutes)
14
+ save!
15
+ end
16
+
17
+ def reset_after_successful_login
18
+ self.login_attempts = []
19
+ self.expired_at = nil
20
+ self.unlocked_at = nil
21
+ save!
22
+ end
23
+
24
+ def has_user?
25
+ !user.nil?
26
+ end
27
+
28
+ def locked?
29
+ !unlocks_in_minutes.nil?
30
+ end
31
+
32
+ def expired?
33
+ return false if expired_at.nil?
34
+ Time.zone.now.utc >= expired_at
35
+ end
36
+
37
+ def lock_account
38
+ self.unlocked_at = (Time.zone.now.utc + SmsAuth::Engine.lock_min_duration.minutes)
39
+ save!
40
+ end
41
+
42
+ def above_maximum_attempts?
43
+ login_attempts.select { |attempt| attempt > (Time.now.utc - SmsAuth::Engine.max_login_attempt_within_minutes.minutes) }.count >= SmsAuth::Engine.max_login_attempts
44
+ end
45
+
46
+ def unlocks_in_minutes
47
+ return nil if unlocked_at.nil? || Time.zone.now.utc >= unlocked_at
48
+ ((unlocked_at - Time.zone.now.utc) / 60).round
49
+ end
50
+
51
+ def generate_verification_token
52
+ self.verification_token = rand(100_000..999_999)
53
+ end
54
+
55
+ private
56
+
57
+ def cleanup_phone_number
58
+ self.phone_number = PhoneNumberUtils.cleanup_phone_number(phone_number)
59
+ end
60
+ end
@@ -0,0 +1,4 @@
1
+ class User < ActiveRecord::Base
2
+ has_many :authentication_tokens, dependent: :destroy
3
+ has_one :phone_verification, dependent: :destroy
4
+ end
@@ -0,0 +1,120 @@
1
+ class RegistrationService
2
+ attr_reader :phone_number, :verification_token
3
+
4
+ def initialize(phone_number, verification_token = nil)
5
+ @phone_number = PhoneNumberUtils.cleanup_phone_number(phone_number)
6
+ @verification_token = verification_token
7
+ end
8
+
9
+ def register_user
10
+ unless valid_phone_number?(phone_number)
11
+ return [false, 'The phone number provided is not valid']
12
+ end
13
+
14
+ unless allowable_phone_number?(phone_number)
15
+ return [false, 'The phone number provided is not allowed to be interacted']
16
+ end
17
+
18
+ verification = PhoneVerification.find_by_phone_number(phone_number)
19
+ if verification.nil?
20
+ verification = PhoneVerification.create(phone_number: phone_number)
21
+ send_verification_token(verification)
22
+ return [true, '']
23
+ end
24
+
25
+ return [false, "Login attempts have been locked for this phone number. Try again in #{verification.unlocks_in_minutes} minutes."] if verification.locked?
26
+
27
+ send_verification_token(verification)
28
+ [true, '']
29
+ end
30
+
31
+ def send_verification_token(phone_verification)
32
+ phone_verification.reset_verification_token
33
+ send_out_message_synchronously(phone_number, "Your verification code is #{phone_verification.verification_token}")
34
+ end
35
+
36
+ def verify_user
37
+ verification = PhoneVerification.find_by_phone_number(phone_number)
38
+ if verification_invalid?(verification)
39
+ return verification_error(verification)
40
+ end
41
+
42
+ # Determine if this is the correct token
43
+ # If it is not then log the attempt
44
+ unless token_matches?(verification)
45
+ append_login_attempt(verification)
46
+ if verification.above_maximum_attempts?
47
+ verification.lock_account
48
+ return [false, 'This phone number has been temporarily locked due to unsuccessful login attempts. Please try again in a few minutes.']
49
+ end
50
+ return [false, 'The verification token provided does not match', nil, nil, false]
51
+ end
52
+
53
+ # If a user doesnt exist, create one, associate it with the verification, and set verified_at
54
+ user = create_and_verify_user(verification) unless verification.has_user?
55
+
56
+ token = create_authentication_token(verification.user_id || user.id)
57
+ verification.reset_verification_token
58
+
59
+ [true, '', token.user_id, token.body, !user.nil?]
60
+ end
61
+
62
+ def create_authentication_token(user_id)
63
+ AuthenticationToken.create(user_id: user_id)
64
+ end
65
+
66
+ def verification_invalid?(verification)
67
+ verification.nil? || verification.locked? || verification.expired?
68
+ end
69
+
70
+ def token_matches?(verification)
71
+ verification.verification_token == verification_token
72
+ end
73
+
74
+ def verification_error(verification)
75
+ if verification.nil?
76
+ return verification_error_response('The information provided does not match. Please try again.')
77
+ end
78
+
79
+ if verification.locked?
80
+ return verification_error_response("Login attempts have been locked for this phone number. Try again in #{verification.unlocks_in_minutes} minutes.")
81
+ end
82
+
83
+ if verification.expired?
84
+ return verification_error_response('The verification token used is expired. Please request a new one and try again.')
85
+ end
86
+
87
+ verification_error_response('The information provided does not match. Please try again.')
88
+ end
89
+
90
+ def append_login_attempt(verification)
91
+ verification.login_attempts.push(Time.zone.now.utc)
92
+ verification.save!
93
+ end
94
+
95
+ def create_and_verify_user(verification)
96
+ user = User.create
97
+ verification.user_id = user.id
98
+ verification.verified_at = Time.zone.now.utc
99
+ verification.save!
100
+ user
101
+ end
102
+
103
+ private
104
+
105
+ def valid_phone_number?(phone_num)
106
+ !phone_num.nil? && phone_num.gsub(/\D/, '').length == 10
107
+ end
108
+
109
+ def allowable_phone_number?(phone_num)
110
+ SmsAuth::Engine.limited_recipients.empty? || SmsAuth::Engine.limited_recipients.include?(phone_num)
111
+ end
112
+
113
+ def send_out_message_synchronously(phone_num, message)
114
+ TextMessageService.new(phone_num, message).send
115
+ end
116
+
117
+ def verification_error_response(error_message)
118
+ [false, error_message, nil, nil, false]
119
+ end
120
+ end
@@ -0,0 +1,47 @@
1
+ require 'twilio-ruby'
2
+
3
+ class TextMessageService
4
+ attr_reader :phone_number, :message
5
+
6
+ def initialize(phone_number, message)
7
+ @phone_number = phone_number
8
+ @message = message
9
+ end
10
+
11
+ def send
12
+ return false if recipient_trap? && !allowed_recipient?
13
+ twilio_client.account.messages.create(
14
+ from: from_phone_number,
15
+ to: phone_number,
16
+ body: formatted_message
17
+ )
18
+ end
19
+
20
+ def formatted_message
21
+ has_prefix? ? "#{prefix}: #{message}" : message
22
+ end
23
+
24
+ def has_prefix?
25
+ !prefix.nil?
26
+ end
27
+
28
+ def from_phone_number
29
+ SmsAuth::Engine.twilio_from_number
30
+ end
31
+
32
+ def prefix
33
+ SmsAuth::Engine.message_prefix
34
+ end
35
+
36
+ def recipient_trap?
37
+ SmsAuth::Engine.limited_recipients.count > 0
38
+ end
39
+
40
+ def allowed_recipient?
41
+ !SmsAuth::Engine.limited_recipients.index(phone_number).nil?
42
+ end
43
+
44
+ def twilio_client
45
+ @twilio_client ||= Twilio::REST::Client.new
46
+ end
47
+ end
@@ -0,0 +1,14 @@
1
+ class TokenService
2
+ attr_reader :token_body
3
+
4
+ def initialize(token_body)
5
+ @token_body = token_body
6
+ end
7
+
8
+ def logout
9
+ token = AuthenticationToken.find_by_body(token_body)
10
+ return [false, 'Authentication token could not be found'] if token.nil?
11
+ token.soft_delete
12
+ [true, '']
13
+ end
14
+ end
@@ -0,0 +1,5 @@
1
+ class PhoneNumberUtils
2
+ def self.cleanup_phone_number(phone_num)
3
+ !phone_num.nil? ? phone_num.gsub(/\D/, '') : nil
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ Rails.application.routes.draw do
2
+ post 'auth/login', to: 'registrations#create'
3
+ post 'auth/verify', to: 'registrations#verify'
4
+ delete 'auth/logout', to: 'tokens#logout'
5
+ end
@@ -0,0 +1,24 @@
1
+ class CreateUsers < ActiveRecord::Migration
2
+ def up
3
+ unless users_table_exists?
4
+ create_table :users do |t|
5
+ t.timestamps null: false
6
+ end
7
+ end
8
+ end
9
+
10
+ def down
11
+ if users_table_exists? && !user_table_has_more_than_base_columns?
12
+ drop_table :users
13
+ end
14
+ end
15
+
16
+ def users_table_exists?
17
+ ActiveRecord::Base.connection.tables.include?('users')
18
+ end
19
+
20
+ # 3 Includes id, created_at, updated_at
21
+ def user_table_has_more_than_base_columns?
22
+ ActiveRecord::Base.connection.columns('users').count > 3
23
+ end
24
+ end
@@ -0,0 +1,20 @@
1
+ class CreatePhoneVerifications < ActiveRecord::Migration
2
+ def change
3
+ create_table :phone_verifications do |t|
4
+ t.string :phone_number, null: false
5
+ t.string :verification_token, null: false
6
+ t.datetime :verified_at
7
+ t.datetime :expired_at
8
+ t.datetime :unlocked_at
9
+ t.string :login_attempts, array: true, default: []
10
+ t.integer :user_id
11
+
12
+ t.timestamps null: false
13
+ end
14
+
15
+ add_foreign_key :phone_verifications, :users
16
+ add_index :phone_verifications, :phone_number, unique: true
17
+ add_index :phone_verifications, :verification_token
18
+ add_index :phone_verifications, :user_id, unique: true
19
+ end
20
+ end
@@ -0,0 +1,16 @@
1
+ class CreateAuthenticationTokens < ActiveRecord::Migration
2
+ def change
3
+ create_table :authentication_tokens do |t|
4
+ t.string :body
5
+ t.integer :user_id
6
+ t.datetime :expired_at
7
+ t.datetime :deleted_at
8
+
9
+ t.timestamps null: false
10
+ end
11
+
12
+ add_foreign_key :authentication_tokens, :users
13
+ add_index :authentication_tokens, :user_id
14
+ add_index :authentication_tokens, :body, unique: true
15
+ end
16
+ end
@@ -0,0 +1,4 @@
1
+ require "sms_auth/engine"
2
+
3
+ module SmsAuth
4
+ end
@@ -0,0 +1,45 @@
1
+ require 'twilio-ruby'
2
+
3
+ module SmsAuth
4
+ class Engine < ::Rails::Engine
5
+ engine_name 'sms_auth'
6
+
7
+ config.generators do |g|
8
+ g.test_framework :rspec
9
+ g.fixture_replacement :factory_girl, dir: 'spec/factories'
10
+ end
11
+
12
+ class << self
13
+ mattr_accessor :twilio_auth_token, :twilio_account_sid, :twilio_from_number, :message_prefix,
14
+ :token_length, :max_login_attempts, :max_login_attempt_within_minutes,
15
+ :verification_token_time_limit_minutes, :lock_min_duration,
16
+ :default_token_expiration_days, :limited_recipients
17
+
18
+ # Set Default Values
19
+ self.token_length = 6
20
+ self.max_login_attempts = 3
21
+ self.max_login_attempt_within_minutes = 15
22
+ self.verification_token_time_limit_minutes = 5
23
+ self.lock_min_duration = 60
24
+ self.default_token_expiration_days = 90
25
+ self.limited_recipients = []
26
+ end
27
+
28
+ def self.setup
29
+ yield self
30
+
31
+ # Raise exception if required values are not set
32
+ [:twilio_auth_token, :twilio_account_sid, :twilio_from_number].each do |field|
33
+ if send(field).nil?
34
+ raise Exception, "Missing `#{field}` in initialization"
35
+ end
36
+ end
37
+
38
+ # Setup Twilio
39
+ Twilio.configure do |config|
40
+ config.account_sid = twilio_account_sid
41
+ config.auth_token = twilio_auth_token
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,3 @@
1
+ module SmsAuth
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :sms_auth do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,247 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sms_auth
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Andrew Platkin
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-01-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 4.2.7.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 4.2.7.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: twilio-ruby
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 4.11.1
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 4.11.1
41
+ - !ruby/object:Gem::Dependency
42
+ name: pg
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: responders
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: railties
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rspec-rails
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: faker
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: factory_girl_rails
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: database_cleaner
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: timecop
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: simplecov
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
181
+ - !ruby/object:Gem::Dependency
182
+ name: codeclimate-test-reporter
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - "~>"
186
+ - !ruby/object:Gem::Version
187
+ version: 1.0.0
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - "~>"
193
+ - !ruby/object:Gem::Version
194
+ version: 1.0.0
195
+ description: A Rails engine for quickly adding SMS authentication to a Rails API
196
+ email:
197
+ - andrew.platkin@gmail.com
198
+ executables: []
199
+ extensions: []
200
+ extra_rdoc_files: []
201
+ files:
202
+ - MIT-LICENSE
203
+ - Rakefile
204
+ - app/controllers/application_controller.rb
205
+ - app/controllers/registrations_controller.rb
206
+ - app/controllers/tokens_controller.rb
207
+ - app/helpers/auth_controller_helper.rb
208
+ - app/models/authentication_token.rb
209
+ - app/models/phone_verification.rb
210
+ - app/models/user.rb
211
+ - app/services/registration_service.rb
212
+ - app/services/text_message_service.rb
213
+ - app/services/token_service.rb
214
+ - app/utils/phone_number_utils.rb
215
+ - config/routes.rb
216
+ - db/migrate/20170112024558_create_users.rb
217
+ - db/migrate/20170112030513_create_phone_verifications.rb
218
+ - db/migrate/20170112031103_create_authentication_tokens.rb
219
+ - lib/sms_auth.rb
220
+ - lib/sms_auth/engine.rb
221
+ - lib/sms_auth/version.rb
222
+ - lib/tasks/sms_auth_tasks.rake
223
+ homepage: https://github.com/plattyp/sms_auth
224
+ licenses:
225
+ - MIT
226
+ metadata: {}
227
+ post_install_message:
228
+ rdoc_options: []
229
+ require_paths:
230
+ - lib
231
+ required_ruby_version: !ruby/object:Gem::Requirement
232
+ requirements:
233
+ - - ">="
234
+ - !ruby/object:Gem::Version
235
+ version: '0'
236
+ required_rubygems_version: !ruby/object:Gem::Requirement
237
+ requirements:
238
+ - - ">="
239
+ - !ruby/object:Gem::Version
240
+ version: '0'
241
+ requirements: []
242
+ rubyforge_project:
243
+ rubygems_version: 2.5.1
244
+ signing_key:
245
+ specification_version: 4
246
+ summary: A Rails engine for quickly adding SMS authentication to a Rails API
247
+ test_files: []