Skip to content

Commit

Permalink
Merge pull request #11 from jayywolff/specs
Browse files Browse the repository at this point in the history
raise exception when Twilio credentials are blank
  • Loading branch information
jayywolff authored Jan 2, 2025
2 parents 0281fcf + 4cf1268 commit a1cc81a
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 5 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [0.2.5] - 2025-01-02

### Changed

- Raise "Missing Twilio Credentials" when Twilio credentials are nil or empty strings.
- Added test coverage for TwilioVerifyService, (the abstraction layer for Twilio Verify API calls)
- Updated gemspec summary/description again

## [0.2.4] - 2024-12-29

### Changed
Expand Down
2 changes: 1 addition & 1 deletion app/services/twilio_verify_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def initialize
@twilio_auth_token = Rails.application.credentials.twilio_auth_token || ENV['TWILIO_AUTH_TOKEN']
@twilio_verify_service_sid = Rails.application.credentials.twilio_verify_service_sid || ENV['TWILIO_VERIFY_SERVICE_SID']

raise 'Missing Twilio credentials' unless @twilio_account_sid && @twilio_auth_token && @twilio_verify_service_sid
raise 'Missing Twilio credentials' if @twilio_account_sid.blank? || @twilio_auth_token.blank? || @twilio_verify_service_sid.blank?

@twilio_client = Twilio::REST::Client.new(@twilio_account_sid, @twilio_auth_token)
end
Expand Down
6 changes: 3 additions & 3 deletions devise-twilio-verify.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ Gem::Specification.new do |spec|
spec.name = "devise-twilio-verify"
spec.version = DeviseTwilioVerify::VERSION
spec.authors = ["Jay Wolff"]

spec.summary = %q{Devise-Twilio-Verify is a Devise extension that adds two-factor authentication (2FA) support using SMS and/or TOTP via the Twilio Verify API}
spec.description = %q{The devise-twilio-verify gem is an extension for the Devise authentication system that adds extra security with two-factor authentication (2FA). It leverages the Twilio Verify API to send verification codes to users via SMS or TOTP (time-based codes). This gem makes it easy to set up 2FA in your Devise-powered Rails app, helping to keep user accounts secure. This gem is meant to make migrating from authy to twilio verify as simple as possible, please see the README for details.}
spec.summary = %q{Devise plugin for two-factor authentication (2FA) using SMS/TOTP with Twilio Verify API}
spec.description = %q{The devise-twilio-verify gem extends the Devise authentication system to provide enhanced security through two-factor authentication (2FA). It integrates with the Twilio Verify API to send verification codes via SMS or TOTP (time-based one-time passwords). This gem simplifies adding 2FA to Devise-powered Rails applications, ensuring better protection for user accounts. For instructions on migrating from the legacy Authy API (devise-authy) to Twilio Verify, please refer to the README.}
spec.homepage = "https://github.com/jayywolff/twilio-verify-devise"
spec.license = "MIT"
spec.required_ruby_version = '>= 2.5.0'

spec.metadata = {
"bug_tracker_uri" => "https://github.com/jayywolff/twilio-verify-devise/issues",
Expand Down
2 changes: 1 addition & 1 deletion lib/devise-twilio-verify/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# frozen_string_literal: true

module DeviseTwilioVerify
VERSION = '0.2.4'
VERSION = '0.2.5'
end
125 changes: 125 additions & 0 deletions spec/services/twilio_verify_service_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# frozen_string_literal: true

RSpec.describe TwilioVerifyService, type: :service do
let(:user) { create :twilio_verify_user }
let(:phone_number) { user.mobile_phone }
let(:formatted_phone_number) { "+1#{phone_number}" }
let(:twilio_account_sid) { '123456789' }
let(:twilio_auth_token) { '123456789' }
let(:twilio_verify_service_sid) { '123456789' }

before do
allow(Rails.application.credentials).to receive(:twilio_account_sid).and_return twilio_account_sid
allow(Rails.application.credentials).to receive(:twilio_auth_token).and_return twilio_auth_token
allow(Rails.application.credentials).to receive(:twilio_verify_service_sid).and_return twilio_verify_service_sid
end

describe 'Missing Twilio credentials' do
let(:twilio_auth_token) { '' }

it "raises 'Missing Twilio credentials' exception if any credentials are missing" do
expect { described_class.new }.to raise_error 'Missing Twilio credentials'
end
end

# https://www.twilio.com/docs/verify/sms
describe 'SMS 2FA' do
let(:twilio_client) { Twilio::REST::Client.new(twilio_account_sid, twilio_auth_token) }
let(:twilio_client_verify_service) { double('Twilio::REST::Verify::V2::ServiceContext') }

before do
allow(Twilio::REST::Client).to receive(:new).with(twilio_account_sid, twilio_auth_token).and_return twilio_client
allow(twilio_client).to receive_message_chain(:verify, :services).with(twilio_verify_service_sid).and_return twilio_client_verify_service
end

describe '.send_sms_token' do
it 'calls on the Twilio Verify API to send a one-time code to the given phone number via SMS' do
expect(twilio_client_verify_service).to receive_message_chain(:verifications, :create).with(to: formatted_phone_number, channel: 'sms')
described_class.send_sms_token(phone_number)
end
end

describe '.verify_sms_token' do
let(:token) { '123456' }

it 'calls on the Twilio Verify API to verify a one-time code that was previously sent to the given phone number via SMS' do
expect(twilio_client_verify_service).to receive_message_chain(:verification_checks, :create).with(to: formatted_phone_number, code: token)
described_class.verify_sms_token(phone_number, token)
end
end
end

# https://www.twilio.com/docs/verify/quickstarts/totp
describe 'TOTP 2FA' do
let(:twilio_client) { Twilio::REST::Client.new(twilio_account_sid, twilio_auth_token) }
let(:twilio_client_verify_service) { double('Twilio::REST::Verify::V2::ServiceContext') }
let(:twilio_client_entity) { double('Twilio::REST::Verify::V2::ServiceContext::EntityContext') }

before do
allow(Twilio::REST::Client).to receive(:new).with(twilio_account_sid, twilio_auth_token).and_return twilio_client
allow(twilio_client).to receive_message_chain(:verify, :v2, :services).with(twilio_verify_service_sid).and_return twilio_client_verify_service
allow(twilio_client_verify_service).to receive(:entities).with("test-#{user.id}").and_return twilio_client_entity
end

describe '.setup_totp_service' do
it 'calls on the Twilio Verify API to setup TOTP for a given user' do
new_factor = double(:twilio_verify_totp_new_factor, sid: '123ABC')
expect(twilio_client_entity).to receive_message_chain(:new_factors, :create).with(friendly_name: user.to_s, factor_type: 'totp').and_return new_factor

expect(user.twilio_totp_factor_sid).to be_nil
result = described_class.setup_totp_service(user)

expect(user.reload.twilio_totp_factor_sid).to eq result.sid
end
end

describe '.register_totp_service' do
let(:token) { '123456' }
let(:new_factor) { double(:twilio_verify_totp_new_factor, status: 'verified') }

before do
user.update!(twilio_totp_factor_sid: '123ABC')
allow(twilio_client_entity).to receive(:factors).with(user.twilio_totp_factor_sid).and_return new_factor
end

it 'calls on the Twilio Verify API to register TOTP for a given user' do
expect(new_factor).to receive(:update).with(auth_payload: token).and_return new_factor

result = described_class.register_totp_service(user, token)
expect(result.status).to eq 'verified'
end

context 'when an invalid token is provided' do
let(:new_factor) { double(:twilio_verify_totp_new_factor, status: 'unverified') }

it 'returns an unverified status' do
expect(new_factor).to receive(:update).with(auth_payload: token).and_return new_factor

result = described_class.register_totp_service(user, token)
expect(result.status).to eq 'unverified'
end
end
end

describe '.verify_totp_token' do
let(:token) { '123456' }

before { user.update!(twilio_totp_factor_sid: '123ABC') }

it 'calls on the Twilio Verify API to verify a TOTP based one time code for a given user' do
expect(twilio_client_entity).to receive_message_chain(:challenges, :create).with(auth_payload: token, factor_sid: user.twilio_totp_factor_sid)
described_class.verify_totp_token(user, token)
end
end
end

describe '.e164_format' do
# https://en.wikipedia.org/wiki/E.164
it 'formats supplied phone number to the e164 format' do
expect(described_class.e164_format(phone_number)).to eq formatted_phone_number
expect(described_class.e164_format('(123) 456-7890')).to eq '+11234567890'
expect(described_class.e164_format('123-456-7890')).to eq '+11234567890'
expect(described_class.e164_format('1234567890')).to eq '+11234567890'
end
end
end

0 comments on commit a1cc81a

Please sign in to comment.