sms_auth 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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: []