├── Rakefile ├── .rspec ├── lib ├── onfido │ ├── version.rb │ ├── errors │ │ ├── server_error.rb │ │ ├── request_error.rb │ │ ├── connection_error.rb │ │ └── onfido_error.rb │ ├── null_logger.rb │ ├── resources │ │ ├── sdk_token.rb │ │ ├── address.rb │ │ ├── report_type_group.rb │ │ ├── live_video.rb │ │ ├── report.rb │ │ ├── applicant.rb │ │ ├── document.rb │ │ ├── live_photo.rb │ │ ├── check.rb │ │ └── webhook.rb │ ├── api.rb │ ├── configuration.rb │ └── resource.rb └── onfido.rb ├── spec ├── support │ ├── fixtures │ │ ├── sdk_token.json │ │ ├── unexpected_error_format.json │ │ ├── applicants.json │ │ ├── not_scheduled_for_deletion_error.json │ │ ├── 4xx_response.json │ │ ├── report.json │ │ ├── document.json │ │ ├── check.json │ │ ├── live_photo.json │ │ ├── webhook.json │ │ ├── checks.json │ │ ├── addresses.json │ │ ├── report_type_group.json │ │ ├── live_video.json │ │ ├── webhooks.json │ │ ├── report_type_groups.json │ │ ├── documents.json │ │ ├── reports.json │ │ ├── live_videos.json │ │ ├── live_photos.json │ │ ├── check_with_expanded_reports.json │ │ ├── applicant.json │ │ └── checks_with_expanded_reports.json │ └── fake_onfido_api.rb ├── integrations │ ├── address_spec.rb │ ├── sdk_token_spec.rb │ ├── report_type_group_spec.rb │ ├── live_video_spec.rb │ ├── report_spec.rb │ ├── live_photo_spec.rb │ ├── document_spec.rb │ ├── exceptions_spec.rb │ ├── webhook_spec.rb │ ├── check_spec.rb │ └── applicant_spec.rb ├── onfido │ ├── connection_error_spec.rb │ ├── api_spec.rb │ ├── request_error_spec.rb │ └── resource_spec.rb ├── spec_helper.rb └── onfido_spec.rb ├── Gemfile ├── .gitignore ├── .travis.yml ├── .rubocop.yml ├── LICENSE ├── onfido.gemspec ├── CHANGELOG.md └── README.md /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --require spec_helper 3 | -------------------------------------------------------------------------------- /lib/onfido/version.rb: -------------------------------------------------------------------------------- 1 | module Onfido 2 | VERSION = '0.15.0'.freeze 3 | end 4 | -------------------------------------------------------------------------------- /spec/support/fixtures/sdk_token.json: -------------------------------------------------------------------------------- 1 | { 2 | "token": "header.payload.signature" 3 | } 4 | -------------------------------------------------------------------------------- /spec/support/fixtures/unexpected_error_format.json: -------------------------------------------------------------------------------- 1 | { 2 | "unexpected": "format" 3 | } 4 | -------------------------------------------------------------------------------- /lib/onfido/errors/server_error.rb: -------------------------------------------------------------------------------- 1 | module Onfido 2 | class ServerError < OnfidoError 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /lib/onfido/errors/request_error.rb: -------------------------------------------------------------------------------- 1 | module Onfido 2 | class RequestError < OnfidoError 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /lib/onfido/null_logger.rb: -------------------------------------------------------------------------------- 1 | module Onfido 2 | class NullLogger 3 | def <<(*args); end 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /lib/onfido/errors/connection_error.rb: -------------------------------------------------------------------------------- 1 | module Onfido 2 | class ConnectionError < OnfidoError 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | gemspec 3 | 4 | group :development, :test do 5 | gem 'pry' 6 | gem 'pry-byebug' 7 | gem 'pry-stack_explorer' 8 | end 9 | -------------------------------------------------------------------------------- /lib/onfido/resources/sdk_token.rb: -------------------------------------------------------------------------------- 1 | module Onfido 2 | class SdkToken < Resource 3 | def create(payload) 4 | post( 5 | url: url_for("sdk_token"), 6 | payload: payload 7 | ) 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .DS_Store 6 | .rspec 7 | .yardoc 8 | Gemfile.lock 9 | InstalledFiles 10 | _yardoc 11 | coverage 12 | doc/ 13 | lib/bundler/man 14 | pkg 15 | rdoc 16 | spec/reports 17 | test/tmp 18 | test/version_tmp 19 | tmp 20 | -------------------------------------------------------------------------------- /lib/onfido/resources/address.rb: -------------------------------------------------------------------------------- 1 | module Onfido 2 | class Address < Resource 3 | def all(postcode) 4 | get( 5 | url: url_for('addresses/pick'), 6 | payload: { postcode: postcode.delete(' ') } 7 | ) 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/support/fixtures/applicants.json: -------------------------------------------------------------------------------- 1 | { 2 | "applicants": [ 3 | { 4 | "id": "1030303-123123-123123", 5 | "first_name": "John" 6 | }, 7 | { 8 | "id": "1030303-123123-123145", 9 | "first_name": "Jane" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /spec/support/fixtures/not_scheduled_for_deletion_error.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": { 3 | "type": "validation_error", 4 | "message": "There was a validation error on this request", 5 | "fields": "Applicant a2fb9c62-ab10-4898-a8ec-342c4b552ad5 is not scheduled for deletion" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | 3 | rvm: 4 | - 2.3 5 | - 2.4 6 | - 2.5 7 | - 2.6 8 | 9 | before_install: 10 | - gem update --system 11 | - gem install bundler 12 | 13 | script: 14 | - bundle exec rubocop 15 | - bundle exec rspec spec 16 | 17 | # safelist 18 | branches: 19 | only: 20 | - master 21 | -------------------------------------------------------------------------------- /lib/onfido/resources/report_type_group.rb: -------------------------------------------------------------------------------- 1 | module Onfido 2 | class ReportTypeGroup < Resource 3 | def find(report_type_group_id) 4 | get(url: url_for("report_type_groups/#{report_type_group_id}")) 5 | end 6 | 7 | def all 8 | get(url: url_for("report_type_groups")) 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/integrations/address_spec.rb: -------------------------------------------------------------------------------- 1 | describe Onfido::Address do 2 | subject(:api) { Onfido::API.new } 3 | 4 | describe '#all' do 5 | it 'returns the addresses matching the postcode' do 6 | response = api.address.all('SW1 4NG') 7 | expect(response['addresses'].count).to eq(2) 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/support/fixtures/4xx_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": { 3 | "id": "544f95539648607d03001542", 4 | "type": "validation_error", 5 | "message": "Something went wrong", 6 | "fields": { 7 | "email": { 8 | "messages": [ 9 | "invalid format" 10 | ] 11 | } 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /spec/support/fixtures/report.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "6951786-123123-422221", 3 | "name": "identity", 4 | "created_at": "2014-05-23T13:50:33Z", 5 | "status": "awaiting_applicant", 6 | "result": "pending", 7 | "href": "/v2/checks/8546921-123123-123123/reports/6951786-123123-422221", 8 | "breakdown": {}, 9 | "properties": {} 10 | } 11 | -------------------------------------------------------------------------------- /spec/support/fixtures/document.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "7568415-123123-123123", 3 | "created_at": "2014-05-23 13:50:33Z", 4 | "href": "/v2/applicants/1030303-123123-123123/documents/7568415-123123-123123", 5 | "file_name": "passport.jpg", 6 | "file_type": "png", 7 | "file_size": 282870, 8 | "type": "passport", 9 | "side": "back" 10 | } 11 | -------------------------------------------------------------------------------- /spec/support/fixtures/check.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "8546921-123123-123123", 3 | "created_at": "2014-05-23T13:50:33Z", 4 | "href": "/v2/applicants/61f659cb-c90b-4067-808a-6136b5c01351/checks/8546921-123123-123123", 5 | "type": "standard", 6 | "status": "pending", 7 | "result": "pending", 8 | "reports": [ 9 | "1030303-123123-375629", 10 | "1030303-123123-456789" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /spec/support/fixtures/live_photo.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "3538c8f6-fdce-4745-9d34-fc246bc05aa1", 3 | "created_at": "2014-05-23 13:50:33Z", 4 | "file_name": "onfido_captured_image.jpg", 5 | "file_type": "image/jpeg", 6 | "file_size": 47544, 7 | "href": "/v2/live_photos/3538c8f6-fdce-4745-9d34-fc246bc05aa1", 8 | "download_href": "/v2/live_photos/3538c8f6-fdce-4745-9d34-fc246bc05aa1/download" 9 | } 10 | -------------------------------------------------------------------------------- /spec/support/fixtures/webhook.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "fcb73186-0733-4f6f-9c57-d9d5ef979443", 3 | "url": "https://webhookendpoint.url", 4 | "token": "yV85IsmuYwmjQGlZ", 5 | "enabled": true, 6 | "href": "/v2/webhooks/fcb73186-0733-4f6f-9c57-d9d5ef979443", 7 | "events": [ 8 | "report completion", 9 | "report withdrawal", 10 | "check completion", 11 | "check in progress" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /spec/integrations/sdk_token_spec.rb: -------------------------------------------------------------------------------- 1 | describe Onfido::SdkToken do 2 | subject(:sdk_token) { described_class.new } 3 | let(:applicant_id) { '61f659cb-c90b-4067-808a-6136b5c01351' } 4 | let(:referrer) { 'http://*.mywebsite.com/*' } 5 | 6 | describe '#create' do 7 | let(:params) { { applicant_id: applicant_id, referrer: referrer } } 8 | 9 | it 'creates a new SDK token for the applicant' do 10 | response = sdk_token.create(params) 11 | expect(response['token']).not_to be_nil 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/support/fixtures/checks.json: -------------------------------------------------------------------------------- 1 | { 2 | "checks": [ 3 | { 4 | "id": "8546921-123123-123123", 5 | "created_at": "2014-05-23T13:50:33Z", 6 | "href": "/v2/applicants/61f659cb-c90b-4067-808a-6136b5c01351/checks/8546921-123123-123123", 7 | "type": "standard", 8 | "status": "pending", 9 | "result": "pending", 10 | "reports": [ 11 | "1030303-123123-375629", 12 | "1030303-123123-456789" 13 | ] 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /spec/onfido/connection_error_spec.rb: -------------------------------------------------------------------------------- 1 | describe Onfido::ConnectionError do 2 | subject(:error) do 3 | described_class.new( 4 | "Invalid response object from API", 5 | response_code: response_code, 6 | response_body: response_body 7 | ) 8 | end 9 | 10 | let(:response_code) { nil } 11 | let(:response_body) { nil } 12 | 13 | context "without a response_body" do 14 | its(:json_body) { is_expected.to be_nil } 15 | its(:type) { is_expected.to be_nil } 16 | its(:fields) { is_expected.to be_nil } 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/integrations/report_type_group_spec.rb: -------------------------------------------------------------------------------- 1 | describe Onfido::ReportTypeGroup do 2 | subject(:report_type_group) { described_class.new } 3 | 4 | describe '#find' do 5 | let(:id) { '8546921-123123-123123' } 6 | 7 | it 'return a retport type group' do 8 | response = report_type_group.find(id) 9 | expect(response['id']).not_to be_nil 10 | end 11 | end 12 | 13 | describe '#all' do 14 | it 'return a list of report type group' do 15 | response = report_type_group.all 16 | expect(response['report_type_groups']).not_to be_empty 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/support/fixtures/addresses.json: -------------------------------------------------------------------------------- 1 | { 2 | "addresses": [ 3 | { 4 | "flat_number": "", 5 | "building_number": "100", 6 | "building_name": "", 7 | "street": "Main Street", 8 | "sub_street": "", 9 | "town": "London", 10 | "postcode": "SW4 6EH", 11 | "country": "GBR" 12 | }, 13 | { 14 | "flat_number": "", 15 | "building_number": "101", 16 | "building_name": "", 17 | "street": "Main Street", 18 | "sub_street": "", 19 | "town": "London", 20 | "postcode": "SW4 6EH", 21 | "country": "GBR" 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /spec/support/fixtures/report_type_group.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "8546921-123123-123123", 3 | "name": "Senior level", 4 | "group_only": false, 5 | "report_types": [ 6 | { 7 | "id": "4566764-567657-234445", 8 | "name": "identity" 9 | }, 10 | { 11 | "id": "2344556-236767-786868", 12 | "name": "right_to_work" 13 | }, 14 | { 15 | "id": "4523434-345133-234424", 16 | "name": "directorship" 17 | }, 18 | { 19 | "id": "7857345-234234-565345", 20 | "name": "criminal_history", 21 | "variant": "basic", 22 | "options": [] 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /lib/onfido/resources/live_video.rb: -------------------------------------------------------------------------------- 1 | module Onfido 2 | class LiveVideo < Resource 3 | def find(applicant_id, live_video_id) 4 | query_string = "applicant_id=#{applicant_id}" 5 | get(url: url_for("live_videos/#{live_video_id}?#{query_string}")) 6 | end 7 | 8 | def download(applicant_id, live_video_id) 9 | query_string = "applicant_id=#{applicant_id}" 10 | get(url: url_for("live_videos/#{live_video_id}/download?#{query_string}")) 11 | end 12 | 13 | def all(applicant_id) 14 | query_string = "applicant_id=#{applicant_id}" 15 | get(url: url_for("live_videos?#{query_string}")) 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/support/fixtures/live_video.json: -------------------------------------------------------------------------------- 1 | { 2 | "id" : "c9701e9b-83aa-442f-995b-20320ee8fb01", 3 | "challenge" : [ 4 | { 5 | "type" : "movement", 6 | "query" : "turnLeft" 7 | }, 8 | { 9 | "type" : "recite", 10 | "query" : [ 11 | 9, 12 | 5, 13 | 9 14 | ] 15 | } 16 | ], 17 | "created_at" : "2018-05-29T09:00:39Z", 18 | "file_name" : "output-29-05-2018_11_00_24.mov", 19 | "file_type" : "video/quicktime", 20 | "file_size" : 3348421, 21 | "href" : "/v2/live_videos/c9701e9b-83aa-442f-995b-20320ee8fb01", 22 | "download_href" : "/v2/live_videos/c9701e9b-83aa-442f-995b-20320ee8fb01/download" 23 | } 24 | -------------------------------------------------------------------------------- /lib/onfido/resources/report.rb: -------------------------------------------------------------------------------- 1 | module Onfido 2 | class Report < Resource 3 | def find(check_id, report_id) 4 | get(url: url_for("checks/#{check_id}/reports/#{report_id}")) 5 | end 6 | 7 | def all(check_id, page: 1, per_page: 20) 8 | querystring = "page=#{page}&per_page=#{per_page}" 9 | get(url: url_for("checks/#{check_id}/reports?#{querystring}")) 10 | end 11 | 12 | def resume(check_id, report_id) 13 | post(url: url_for("checks/#{check_id}/reports/#{report_id}/resume")) 14 | end 15 | 16 | def cancel(check_id, report_id) 17 | post(url: url_for("checks/#{check_id}/reports/#{report_id}/cancel")) 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/support/fixtures/webhooks.json: -------------------------------------------------------------------------------- 1 | { 2 | "webhooks": [ 3 | { 4 | "id": "dd0a89e4-d44e-417a-aec4-01137d01ae59", 5 | "url": "https://demo.com","enabled":false, 6 | "href": "/v2/webhooks/dd0a89e4-d44e-417a-aec4-01137d01ae59", 7 | "events": 8 | [ 9 | "check in progress" 10 | ] 11 | }, 12 | { 13 | "id": "bfc727a8-f7bf-4073-b123-ed70a70f58e5", 14 | "url": "https://demo2.com", 15 | "enabled": true, 16 | "href": "/v2/webhooks/bfc727a8-f7bf-4073-b123-ed70a70f58e5", 17 | "events": 18 | [ 19 | "report completion", 20 | "report withdrawal", 21 | "check in progress", 22 | "check completion" 23 | ] 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /spec/support/fixtures/report_type_groups.json: -------------------------------------------------------------------------------- 1 | { 2 | "report_type_groups": [ 3 | { 4 | "id": "8546921-123123-123123", 5 | "name": "Senior level", 6 | "group_only": false, 7 | "report_types": [ 8 | { 9 | "id": "121212-567657-234445", 10 | "name": "identity" 11 | } 12 | ] 13 | }, 14 | { 15 | "id": "8546921-123123-123123", 16 | "name": "Senior level", 17 | "group_only": false, 18 | "report_types": [ 19 | { 20 | "id": "4566764-567657-234445", 21 | "name": "identity" 22 | }, 23 | { 24 | "id": "2344556-236767-786868", 25 | "name": "right_to_work" 26 | } 27 | ] 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /lib/onfido/errors/onfido_error.rb: -------------------------------------------------------------------------------- 1 | module Onfido 2 | class OnfidoError < StandardError 3 | attr_accessor :response_code, :response_body 4 | 5 | def initialize(message = nil, 6 | response_code: nil, 7 | response_body: nil) 8 | @response_code = response_code 9 | @response_body = response_body 10 | 11 | super(message) 12 | end 13 | 14 | def json_body 15 | JSON.parse(response_body.to_s) 16 | rescue JSON::ParserError 17 | nil 18 | end 19 | 20 | def type 21 | json_body && json_body['error'] && json_body['error']['type'] 22 | end 23 | 24 | def fields 25 | json_body && json_body['error'] && json_body['error']['fields'] 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/support/fixtures/documents.json: -------------------------------------------------------------------------------- 1 | { 2 | "documents": [ 3 | { 4 | "id": "7568415-123123-123123", 5 | "created_at": "2014-05-23 13:50:33Z", 6 | "href": "/v2/applicants/1030303-123123-123123/documents/7568415-123123-123123", 7 | "file_name": "passport.jpg", 8 | "file_type": "png", 9 | "file_size": 282870, 10 | "type": "passport", 11 | "side": "back" 12 | }, 13 | { 14 | "id": "121122-123123-123123", 15 | "created_at": "2014-05-23 13:50:33Z", 16 | "href": "/v2/applicants/1030303-123123-123123/documents/7568415-123123-123123", 17 | "file_name": "right_to_work.png", 18 | "file_type": "png", 19 | "file_size": 282870, 20 | "type": "right_to_work" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /spec/support/fixtures/reports.json: -------------------------------------------------------------------------------- 1 | { 2 | "reports": [ 3 | { 4 | "id": "6951786-123123-422221", 5 | "name": "identity", 6 | "created_at": "2014-05-23T13:50:33Z", 7 | "status": "awaiting_applicant", 8 | "result": "pending", 9 | "href": "/v2/checks/8546921-123123-123123/reports/6951786-123123-422221", 10 | "breakdown": {}, 11 | "properties": {} 12 | }, 13 | { 14 | "id": "6951786-123123-316712", 15 | "name": "document", 16 | "created_at": "2014-05-23T13:50:33Z", 17 | "status": "awaiting_applicant", 18 | "result": "pending", 19 | "href": "/v2/checks/8546921-123123-123123/reports/6951786-123123-316712", 20 | "breakdown": {}, 21 | "properties": {} 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /spec/support/fixtures/live_videos.json: -------------------------------------------------------------------------------- 1 | { 2 | "live_videos": [ 3 | { 4 | "challenge": [ 5 | { 6 | "query": "turnLeft", 7 | "type": "movement" 8 | }, 9 | { 10 | "query": [ 11 | 9, 12 | 5, 13 | 9 14 | ], 15 | "type": "recite" 16 | } 17 | ], 18 | "created_at": "2018-05-29T09:00:39Z", 19 | "download_href": "/v2/live_videos/c9701e9b-83aa-442f-995b-20320ee8fb01/download", 20 | "file_name": "output-29-05-2018_11_00_24.mov", 21 | "file_size": 3348421, 22 | "file_type": "video/quicktime", 23 | "href": "/v2/live_videos/c9701e9b-83aa-442f-995b-20320ee8fb01", 24 | "id": "c9701e9b-83aa-442f-995b-20320ee8fb01" 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /lib/onfido/resources/applicant.rb: -------------------------------------------------------------------------------- 1 | module Onfido 2 | class Applicant < Resource 3 | def create(payload) 4 | post(url: url_for('applicants'), payload: payload) 5 | end 6 | 7 | def update(applicant_id, payload) 8 | put(url: url_for("applicants/#{applicant_id}"), payload: payload) 9 | end 10 | 11 | def destroy(applicant_id) 12 | delete(url: url_for("applicants/#{applicant_id}")) 13 | end 14 | 15 | def find(applicant_id) 16 | get(url: url_for("applicants/#{applicant_id}")) 17 | end 18 | 19 | def all(page: 1, per_page: 20) 20 | get(url: url_for("applicants?page=#{page}&per_page=#{per_page}")) 21 | end 22 | 23 | def restore(applicant_id) 24 | post(url: url_for("applicants/#{applicant_id}/restore")) 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/onfido/resources/document.rb: -------------------------------------------------------------------------------- 1 | module Onfido 2 | class Document < Resource 3 | # with open-uri the file can be a link or an actual file 4 | 5 | def create(applicant_id, payload) 6 | validate_file!(payload.fetch(:file)) 7 | 8 | post( 9 | url: url_for("applicants/#{applicant_id}/documents"), 10 | payload: payload 11 | ) 12 | end 13 | 14 | def find(applicant_id, document_id) 15 | get(url: url_for("applicants/#{applicant_id}/documents/#{document_id}")) 16 | end 17 | 18 | def download(applicant_id, document_id) 19 | get(url: url_for("applicants/#{applicant_id}/documents/#{document_id}/download")) 20 | end 21 | 22 | def all(applicant_id) 23 | get(url: url_for("applicants/#{applicant_id}/documents")) 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/onfido.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | require 'rack' 3 | require 'rest-client' 4 | require 'openssl' 5 | 6 | require 'onfido/version' 7 | require 'onfido/configuration' 8 | require 'onfido/errors/onfido_error' 9 | require 'onfido/errors/request_error' 10 | require 'onfido/errors/server_error' 11 | require 'onfido/errors/connection_error' 12 | require 'onfido/null_logger' 13 | require 'onfido/api' 14 | require 'onfido/resource' 15 | require 'onfido/resources/address' 16 | require 'onfido/resources/applicant' 17 | require 'onfido/resources/check' 18 | require 'onfido/resources/document' 19 | require 'onfido/resources/live_photo' 20 | require 'onfido/resources/live_video' 21 | require 'onfido/resources/report' 22 | require 'onfido/resources/report_type_group' 23 | require 'onfido/resources/sdk_token' 24 | require 'onfido/resources/webhook' 25 | 26 | module Onfido 27 | extend Configuration 28 | end 29 | -------------------------------------------------------------------------------- /spec/support/fixtures/live_photos.json: -------------------------------------------------------------------------------- 1 | { 2 | "live_photos": [ 3 | { 4 | "id": "3538c8f6-fdce-4745-9d34-fc246bc05aa1", 5 | "created_at": "2014-05-23 13:50:33Z", 6 | "file_name": "onfido_captured_image.jpg", 7 | "file_type": "image/jpeg", 8 | "file_size": 47544, 9 | "href": "/v2/live_photos/3538c8f6-fdce-4745-9d34-fc246bc05aa1", 10 | "download_href": "/v2/live_photos/3538c8f6-fdce-4745-9d34-fc246bc05aa1/download" 11 | }, 12 | { 13 | "id": "5134c12a-555a-1234-5e12-9d34fcbc11ba", 14 | "created_at": "2014-05-23 13:50:33Z", 15 | "file_name": "onfido_captured_image.jpg", 16 | "file_type": "image/jpeg", 17 | "file_size": 47544, 18 | "href": "/v2/live_photos/5134c12a-555a-1234-5e12-9d34fcbc11ba", 19 | "download_href": "/v2/live_photos/5134c12a-555a-1234-5e12-9d34fcbc11ba/download" 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /lib/onfido/api.rb: -------------------------------------------------------------------------------- 1 | module Onfido 2 | class API 3 | def initialize(options = {}) 4 | @api_key = options[:api_key] 5 | end 6 | 7 | def applicant 8 | Onfido::Applicant.new(@api_key) 9 | end 10 | 11 | def check 12 | Onfido::Check.new(@api_key) 13 | end 14 | 15 | def document 16 | Onfido::Document.new(@api_key) 17 | end 18 | 19 | def live_photo 20 | Onfido::LivePhoto.new(@api_key) 21 | end 22 | 23 | def live_video 24 | Onfido::LiveVideo.new(@api_key) 25 | end 26 | 27 | def report 28 | Onfido::Report.new(@api_key) 29 | end 30 | 31 | def report_type_group 32 | Onfido::ReportTypeGroup.new(@api_key) 33 | end 34 | 35 | def sdk_token 36 | Onfido::SdkToken.new(@api_key) 37 | end 38 | 39 | def webhook 40 | Onfido::Webhook.new(@api_key) 41 | end 42 | 43 | def address 44 | Onfido::Address.new(@api_key) 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/onfido/resources/live_photo.rb: -------------------------------------------------------------------------------- 1 | module Onfido 2 | class LivePhoto < Resource 3 | # with open-uri the file can be a link or an actual file 4 | 5 | def create(applicant_id, payload) 6 | validate_file!(payload.fetch(:file)) 7 | payload[:applicant_id] = applicant_id 8 | 9 | post( 10 | url: url_for("/live_photos"), 11 | payload: payload 12 | ) 13 | end 14 | 15 | def find(applicant_id, live_photo_id) 16 | query_string = "applicant_id=#{applicant_id}" 17 | get(url: url_for("live_photos/#{live_photo_id}?#{query_string}")) 18 | end 19 | 20 | def download(applicant_id, live_photo_id) 21 | query_string = "applicant_id=#{applicant_id}" 22 | get(url: url_for("live_photos/#{live_photo_id}/download?#{query_string}")) 23 | end 24 | 25 | def all(applicant_id) 26 | query_string = "applicant_id=#{applicant_id}" 27 | get(url: url_for("live_photos?#{query_string}")) 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /spec/onfido/api_spec.rb: -------------------------------------------------------------------------------- 1 | describe Onfido::API do 2 | subject(:api) { described_class.new } 3 | 4 | describe 'given a single-word resource' do 5 | specify { expect(api.address).to be_a(Onfido::Address) } 6 | end 7 | 8 | describe 'given a multi-word resource' do 9 | specify { expect(api.live_photo).to be_a(Onfido::LivePhoto) } 10 | end 11 | 12 | describe 'given an unknown resource' do 13 | specify { expect { api.blood_test }.to raise_error(NameError) } 14 | end 15 | 16 | describe 'given no API key' do 17 | it 'uses nil for the resource API key' do 18 | expect(Onfido::Address).to receive(:new).with(nil) 19 | api.address 20 | end 21 | end 22 | 23 | describe 'given an API key' do 24 | let(:api_key) { 'some_key' } 25 | 26 | subject(:api) { described_class.new(api_key: api_key) } 27 | 28 | it 'uses that key to create the resource' do 29 | expect(Onfido::Address).to receive(:new).with(api_key) 30 | api.address 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /spec/support/fixtures/check_with_expanded_reports.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "8546921-123123-123123", 3 | "created_at": "2014-05-23T13:50:33Z", 4 | "href": "/v2/applicants/61f659cb-c90b-4067-808a-6136b5c01351/checks/8546921-123123-123123", 5 | "type": "standard", 6 | "status": "pending", 7 | "result": "pending", 8 | "reports": [ 9 | { 10 | "id": "6951786-123123-422221", 11 | "name": "identity", 12 | "created_at": "2014-05-23T13:50:33Z", 13 | "status": "awaiting_applicant", 14 | "result": "pending", 15 | "href": "/v2/checks/8546921-123123-123123/reports/6951786-123123-422221", 16 | "breakdown": {}, 17 | "properties": {} 18 | }, 19 | { 20 | "id": "6951786-123123-316712", 21 | "name": "document", 22 | "created_at": "2014-05-23T13:50:33Z", 23 | "status": "awaiting_applicant", 24 | "result": "pending", 25 | "href": "/v2/checks/8546921-123123-123123/reports/6951786-123123-316712", 26 | "breakdown": {}, 27 | "properties": {} 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /spec/integrations/live_video_spec.rb: -------------------------------------------------------------------------------- 1 | require 'tempfile' 2 | 3 | describe Onfido::LiveVideo do 4 | subject(:live_video) { described_class.new } 5 | 6 | describe '#find' do 7 | let(:applicant_id) { '1030303-123123-123123' } 8 | let(:live_video_id) { 'c9701e9b-83aa-442f-995b-20320ee8fb01' } 9 | 10 | it 'returns the expected live photo' do 11 | response = live_video.find(applicant_id, live_video_id) 12 | expect(response['id']).to eq(live_video_id) 13 | end 14 | end 15 | 16 | describe '#all' do 17 | let(:applicant_id) { '1030303-123123-123123' } 18 | 19 | it 'returns list of documents' do 20 | response = live_video.all(applicant_id) 21 | expect(response['live_videos']).not_to be_empty 22 | end 23 | end 24 | 25 | describe '#download' do 26 | let(:applicant_id) { '1030303-123123-123123' } 27 | let(:live_video_id) { 'c9701e9b-83aa-442f-995b-20320ee8fb01' } 28 | 29 | it 'returns the file data' do 30 | response = live_video.download(applicant_id, live_video_id) 31 | expect(response).not_to be_nil 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/onfido/resources/check.rb: -------------------------------------------------------------------------------- 1 | module Onfido 2 | class Check < Resource 3 | def create(applicant_id, payload) 4 | post( 5 | url: url_for("applicants/#{applicant_id}/checks"), 6 | payload: payload 7 | ) 8 | end 9 | 10 | def find(applicant_id, check_id, expand: nil) 11 | querystring = "&expand=#{expand}" if expand 12 | get(url: url_for("applicants/#{applicant_id}/checks/#{check_id}?" \ 13 | "#{querystring}")) 14 | end 15 | 16 | def find_by_url(url, expand: nil) 17 | url_path = url.sub(%r/^https\:\/\/api\.onfido\.com\/v2\//, '') 18 | querystring = "&expand=#{expand}" if expand 19 | get(url: url_for("#{url_path}?#{querystring}")) 20 | end 21 | 22 | def all(applicant_id, page: 1, per_page: 20, expand: nil) 23 | querystring = "page=#{page}&per_page=#{per_page}" 24 | querystring += "&expand=#{expand}" if expand 25 | get(url: url_for("applicants/#{applicant_id}/checks?#{querystring}")) 26 | end 27 | 28 | def resume(check_id) 29 | post(url: url_for("checks/#{check_id}/resume")) 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | # For all options see https://github.com/bbatsov/rubocop/tree/master/config 2 | 3 | AllCops: 4 | DisplayCopNames: true 5 | Exclude: 6 | - vendor/**/* 7 | - .*/** 8 | - spec/fixtures/**/* 9 | TargetRubyVersion: 2.2.0 10 | 11 | Style/StringLiterals: 12 | Enabled: false 13 | 14 | Style/PercentLiteralDelimiters: 15 | Enabled: false 16 | 17 | Style/Documentation: 18 | Enabled: false 19 | 20 | Style/SignalException: 21 | EnforcedStyle: only_raise 22 | 23 | Naming/FileName: 24 | Exclude: 25 | - Gemfile 26 | 27 | Metrics/MethodLength: 28 | CountComments: false 29 | Max: 25 30 | 31 | Metrics/BlockLength: 32 | Exclude: 33 | - spec/**/* 34 | 35 | Metrics/AbcSize: 36 | Max: 25 37 | 38 | # Don't require utf-8 encoding comment 39 | Style/Encoding: 40 | Enabled: false 41 | 42 | Metrics/LineLength: 43 | Max: 90 44 | 45 | Metrics/ClassLength: 46 | Enabled: false 47 | 48 | Layout/DotPosition: 49 | EnforcedStyle: trailing 50 | 51 | # Allow class and message or instance raises 52 | Style/RaiseArgs: 53 | Enabled: false 54 | 55 | Lint/AmbiguousBlockAssociation: 56 | Exclude: 57 | - "spec/**/*" 58 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Hassle 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /spec/onfido/request_error_spec.rb: -------------------------------------------------------------------------------- 1 | describe Onfido::RequestError do 2 | subject(:error) do 3 | described_class.new( 4 | failed_response['error']['message'], 5 | response_code: 401, 6 | response_body: failed_response.to_json 7 | ) 8 | end 9 | 10 | let(:failed_response) do 11 | { 12 | 'error' => 13 | { 14 | 'id' => '551722cc964860653c00c202', 15 | 'type' => 'authorization_error', 16 | 'message' => 'Authorization error: please re-check your credentials', 17 | 'fields' => { 'name' => { 'messages' => ['cannot be blank'] } } 18 | } 19 | } 20 | end 21 | 22 | it 'returns the right message' do 23 | expect { raise error }. 24 | to raise_error('Authorization error: please re-check your credentials') 25 | end 26 | 27 | its(:type) { is_expected.to eq('authorization_error') } 28 | its(:response_code) { is_expected.to eq(401) } 29 | its(:response_body) { is_expected.to eq(failed_response.to_json) } 30 | its(:json_body) { is_expected.to eq(failed_response) } 31 | its(:fields) do 32 | is_expected.to eq('name' => { 'messages' => ['cannot be blank'] }) 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /spec/integrations/report_spec.rb: -------------------------------------------------------------------------------- 1 | describe Onfido::Report do 2 | subject(:report) { described_class.new } 3 | let(:check_id) { '8546921-123123-123123' } 4 | 5 | describe '#find' do 6 | let(:report_id) { '6951786-123123-422221' } 7 | 8 | it 'returns a report for an existing check' do 9 | response = report.find(check_id, report_id) 10 | expect(response['id']).to eq(report_id) 11 | end 12 | end 13 | 14 | describe '#all' do 15 | it 'lists all reports for an existing check' do 16 | response = report.all(check_id) 17 | expect(response['reports'].count).to eq(2) 18 | end 19 | end 20 | 21 | describe '#resume' do 22 | let(:report_id) { '6951786-123123-422221' } 23 | let(:check_id) { '1212121-123123-422221' } 24 | 25 | it 'returns a success response' do 26 | expect { report.resume(check_id, report_id) }.not_to raise_error 27 | end 28 | end 29 | 30 | describe '#cancel' do 31 | let(:report_id) { '6951786-123123-422221' } 32 | let(:check_id) { '1212121-123123-422221' } 33 | 34 | it 'returns a success response' do 35 | expect { report.cancel(check_id, report_id) }.not_to raise_error 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/onfido/configuration.rb: -------------------------------------------------------------------------------- 1 | module Onfido 2 | module Configuration 3 | REGION_HOSTS = { 4 | us: "api.us.onfido.com" 5 | }.freeze 6 | 7 | attr_accessor :api_key, :region, :open_timeout, :read_timeout, :api_version 8 | 9 | def self.extended(base) 10 | base.reset 11 | end 12 | 13 | def configure 14 | yield self 15 | end 16 | 17 | def reset 18 | self.api_key = nil 19 | self.region = nil 20 | self.open_timeout = 30 21 | self.read_timeout = 80 22 | self.api_version = 'v2' 23 | RestClient.log = nil 24 | end 25 | 26 | def logger=(log) 27 | unless log.respond_to?(:<<) 28 | raise "#{log.class} doesn't seem to behave like a logger!" 29 | end 30 | 31 | RestClient.log = log 32 | end 33 | 34 | def logger 35 | RestClient.log ||= NullLogger.new 36 | end 37 | 38 | def endpoint 39 | region_host = region ? REGION_HOSTS[region.downcase.to_sym] : "api.onfido.com" 40 | unless region_host 41 | raise "The region \"#{region.downcase}\" is not currently supported" 42 | end 43 | 44 | "https://#{region_host}/#{api_version}/" 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/onfido/resources/webhook.rb: -------------------------------------------------------------------------------- 1 | module Onfido 2 | class Webhook < Resource 3 | def create(payload) 4 | post( 5 | url: url_for('webhooks'), 6 | payload: payload 7 | ) 8 | end 9 | 10 | def find(webhooks_id) 11 | get(url: url_for("webhooks/#{webhooks_id}")) 12 | end 13 | 14 | def all(page: 1, per_page: 20) 15 | get(url: url_for("webhooks?page=#{page}&per_page=#{per_page}")) 16 | end 17 | 18 | # As well as being a normal resource, Onfido::Webhook also supports 19 | # verifying the authenticity of a webhook by comparing the signature on the 20 | # request to one computed from the body 21 | def self.valid?(request_body, request_signature, token) 22 | if [request_body, request_signature, token].any?(&:nil?) 23 | raise ArgumentError, "A request body, request signature and token " \ 24 | "must be provided" 25 | end 26 | 27 | computed_signature = generate_signature(request_body, token) 28 | Rack::Utils.secure_compare(request_signature, computed_signature) 29 | end 30 | 31 | def self.generate_signature(request_body, token) 32 | OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha1'), token, request_body) 33 | end 34 | private_class_method :generate_signature 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /spec/support/fixtures/applicant.json: -------------------------------------------------------------------------------- 1 | { 2 | "id":"61f659cb-c90b-4067-808a-6136b5c01351", 3 | "created_at":"2015-04-10T13:16:01Z", 4 | "title":"Mr", 5 | "first_name":"Chandler", 6 | "middle_name":"Muriel", 7 | "last_name":"Bing", 8 | "email":"chandler_bing_6@friends.com", 9 | "gender":"Male", 10 | "dob":"1968-04-08", 11 | "telephone":"555555555", 12 | "mobile":"77777777", 13 | "country":"gbr", 14 | "href":"/v2/applicants/61f659cb-c90b-4067-808a-6136b5c01351", 15 | "id_numbers":[], 16 | "addresses": [ 17 | { 18 | "flat_number":"4", 19 | "building_number":"100", 20 | "building_name":"Awesome Building", 21 | "street":"Main Street", 22 | "sub_street":"A sub street", 23 | "town":"London", 24 | "state":"", 25 | "postcode":"SW4 6EH", 26 | "country":"GBR", 27 | "start_date":"", 28 | "end_date":"" 29 | }, 30 | { 31 | "flat_number":"1", 32 | "building_number":"10", 33 | "building_name":"Great Building", 34 | "street":"Old Street", 35 | "sub_street":"Sub Street", 36 | "town":"London", 37 | "state":"", 38 | "postcode":"SW1 4NG", 39 | "country":"GBR", 40 | "start_date":"", 41 | "end_date":"" 42 | } 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /spec/support/fixtures/checks_with_expanded_reports.json: -------------------------------------------------------------------------------- 1 | { 2 | "checks": [ 3 | { 4 | "id": "8546921-123123-123123", 5 | "created_at": "2014-05-23T13:50:33Z", 6 | "href": "/v2/applicants/61f659cb-c90b-4067-808a-6136b5c01351/checks/8546921-123123-123123", 7 | "type": "standard", 8 | "status": "pending", 9 | "result": "pending", 10 | "reports": [ 11 | { 12 | "id": "6951786-123123-422221", 13 | "name": "identity", 14 | "created_at": "2014-05-23T13:50:33Z", 15 | "status": "awaiting_applicant", 16 | "result": "pending", 17 | "href": "/v2/checks/8546921-123123-123123/reports/6951786-123123-422221", 18 | "breakdown": {}, 19 | "properties": {} 20 | }, 21 | { 22 | "id": "6951786-123123-316712", 23 | "name": "document", 24 | "created_at": "2014-05-23T13:50:33Z", 25 | "status": "awaiting_applicant", 26 | "result": "pending", 27 | "href": "/v2/checks/8546921-123123-123123/reports/6951786-123123-316712", 28 | "breakdown": {}, 29 | "properties": {} 30 | } 31 | ] 32 | } 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /onfido.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | lib = File.expand_path('lib', __dir__) 4 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 5 | require 'onfido/version' 6 | 7 | Gem::Specification.new do |spec| 8 | spec.name = 'onfido' 9 | spec.version = Onfido::VERSION 10 | spec.authors = ['Pericles Theodorou', 'Grey Baker'] 11 | spec.email = ['periclestheo@gmail.com', 'grey@gocardless.com'] 12 | spec.summary = 'A wrapper for Onfido API' 13 | spec.description = "A thin wrapper for Onfido's API. This gem supports "\ 14 | "both v1 and v2 of the Onfido API. Refer to Onfido's "\ 15 | "API documentation for details of the expected "\ 16 | "requests and responses for both." 17 | spec.homepage = 'http://github.com/hvssle/onfido' 18 | spec.license = 'MIT' 19 | 20 | spec.files = `git ls-files -z`.split("\x0") 21 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 22 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 23 | spec.require_paths = ['lib'] 24 | spec.required_ruby_version = ">= 2.2.0" 25 | 26 | spec.add_development_dependency 'bundler', '~> 2.0' 27 | spec.add_development_dependency 'rake', '~> 12.0' 28 | spec.add_development_dependency 'rspec', '~> 3.1' 29 | spec.add_development_dependency 'rspec-its', '~> 1.2' 30 | spec.add_development_dependency 'rubocop', '~> 0.57.0' 31 | spec.add_development_dependency 'sinatra', '~> 1.4' 32 | spec.add_development_dependency 'webmock', '~> 3.0' 33 | 34 | spec.add_dependency 'rack', '>= 1.6.0' 35 | spec.add_dependency 'rest-client', '~> 2.0' 36 | end 37 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) 2 | $LOAD_PATH.unshift(File.dirname(__FILE__)) 3 | Dir[File.dirname(__FILE__).concat("/support/**/*.rb")].each { |f| require f } 4 | 5 | require 'onfido' 6 | require 'webmock/rspec' 7 | require 'rspec/its' 8 | 9 | WebMock.disable_net_connect!(allow_localhost: true) 10 | 11 | RSpec.configure do |config| 12 | config.expect_with :rspec do |expectations| 13 | # This option will default to `true` in RSpec 4. It makes the `description` 14 | # and `failure_message` of custom matchers include text for helper methods 15 | # defined using `chain`, e.g.: 16 | # be_bigger_than(2).and_smaller_than(4).description 17 | # # => "be bigger than 2 and smaller than 4" 18 | # ...rather than: 19 | # # => "be bigger than 2" 20 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 21 | end 22 | 23 | config.mock_with :rspec do |mocks| 24 | # Prevents you from mocking or stubbing a method that does not exist on 25 | # a real object. This is generally recommended, and will default to 26 | # `true` in RSpec 4. 27 | mocks.verify_partial_doubles = true 28 | end 29 | 30 | config.order = :random 31 | config.filter_run :focus 32 | config.run_all_when_everything_filtered = true 33 | config.disable_monkey_patching! 34 | config.raise_errors_for_deprecations! 35 | config.warnings = false 36 | config.expose_dsl_globally = true 37 | 38 | # Seed global randomization in this process using the `--seed` CLI option. 39 | # Setting this allows you to use `--seed` to deterministically reproduce 40 | # test failures related to randomization by passing the same `--seed` value 41 | # as the one that triggered the failure. 42 | Kernel.srand config.seed 43 | 44 | config.before(:each) do 45 | stub_request(:any, /onfido.com/).to_rack(FakeOnfidoAPI) 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /spec/integrations/live_photo_spec.rb: -------------------------------------------------------------------------------- 1 | require 'tempfile' 2 | 3 | describe Onfido::LivePhoto do 4 | subject(:live_photo) { described_class.new } 5 | 6 | describe '#create' do 7 | let(:params) { { file: file } } 8 | 9 | context 'with a File-like object to upload' do 10 | let(:file) { Tempfile.new(['passport', '.jpg']) } 11 | 12 | after do 13 | file.close 14 | file.unlink 15 | end 16 | 17 | it 'creates a new photo' do 18 | response = live_photo.create('foobar', params) 19 | expect(response['id']).not_to be_nil 20 | end 21 | end 22 | 23 | context 'passing in a non-File-like file to upload' do 24 | let(:file) { 'https://onfido.com/images/photo.jpg' } 25 | 26 | it 'raises an ArgumentError' do 27 | expect { live_photo.create('foobar', params) }. 28 | to raise_error(ArgumentError, /must be a `File`-like object/) 29 | end 30 | end 31 | end 32 | 33 | describe '#find' do 34 | let(:applicant_id) { '1030303-123123-123123' } 35 | let(:live_photo_id) { '3538c8f6-fdce-4745-9d34-fc246bc05aa1' } 36 | 37 | it 'returns the expected live photo' do 38 | response = live_photo.find(applicant_id, live_photo_id) 39 | expect(response['id']).to eq(live_photo_id) 40 | end 41 | end 42 | 43 | describe '#all' do 44 | let(:applicant_id) { '1030303-123123-123123' } 45 | 46 | it 'returns list of documents' do 47 | response = live_photo.all(applicant_id) 48 | expect(response['live_photos']).not_to be_empty 49 | end 50 | end 51 | 52 | describe '#download' do 53 | let(:applicant_id) { '1030303-123123-123123' } 54 | let(:live_photo_id) { '3538c8f6-fdce-4745-9d34-fc246bc05aa1' } 55 | 56 | it 'returns the file data' do 57 | response = live_photo.download(applicant_id, live_photo_id) 58 | expect(response).not_to be_nil 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /spec/integrations/document_spec.rb: -------------------------------------------------------------------------------- 1 | require 'tempfile' 2 | 3 | describe Onfido::Document do 4 | subject(:document) { described_class.new } 5 | 6 | describe '#create' do 7 | let(:params) do 8 | { 9 | type: 'passport', 10 | side: 'back', 11 | file: file 12 | } 13 | end 14 | let(:applicant_id) { '1030303-123123-123123' } 15 | 16 | context 'with a File-like object to upload' do 17 | let(:file) { Tempfile.new(['passport', '.jpg']) } 18 | 19 | after do 20 | file.close 21 | file.unlink 22 | end 23 | 24 | it 'creates a new document' do 25 | response = document.create('foobar', params) 26 | expect(response['id']).not_to be_nil 27 | end 28 | end 29 | 30 | context 'passing in a non-File-like file to upload' do 31 | let(:file) { 'https://onfido.com/images/logo.png' } 32 | 33 | it 'raises an ArgumentError' do 34 | expect { document.create('foobar', params) }. 35 | to raise_error(ArgumentError, /must be a `File`-like object/) 36 | end 37 | end 38 | end 39 | 40 | describe '#find' do 41 | let(:applicant_id) { '1030303-123123-123123' } 42 | let(:document_id) { '7568415-123123-123123' } 43 | 44 | it 'returns the expected document' do 45 | response = document.find(applicant_id, document_id) 46 | expect(response['id']).to eq(document_id) 47 | end 48 | end 49 | 50 | describe '#all' do 51 | let(:applicant_id) { '1030303-123123-123123' } 52 | 53 | it 'returns list of documents' do 54 | response = document.all(applicant_id) 55 | expect(response['documents']).not_to be_empty 56 | end 57 | end 58 | 59 | describe '#download' do 60 | let(:applicant_id) { '1030303-123123-123123' } 61 | let(:document_id) { '1212121-123123-123123' } 62 | 63 | it 'returns the file data' do 64 | response = document.download(applicant_id, document_id) 65 | expect(response).not_to be_nil 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /spec/integrations/exceptions_spec.rb: -------------------------------------------------------------------------------- 1 | describe Onfido::Resource do 2 | let(:resource) { described_class.new } 3 | let(:url) { Onfido.endpoint + path } 4 | let(:api_key) { 'some_key' } 5 | let(:payload) { { postcode: 'SE1 4NG' } } 6 | 7 | before { allow(Onfido).to receive(:api_key).and_return(api_key) } 8 | 9 | context '4xx response' do 10 | let(:path) { '4xx_response' } 11 | 12 | it 'raises a custom error' do 13 | expect { resource.get(url: url, payload: payload) }. 14 | to raise_error(Onfido::RequestError, 'Something went wrong') 15 | end 16 | end 17 | 18 | context 'unexpected error format' do 19 | let(:path) { 'unexpected_error_format' } 20 | 21 | it 'raises a custom error' do 22 | expect { resource.get(url: url, payload: payload) }. 23 | to raise_error(Onfido::RequestError, /response code was 400/) 24 | end 25 | end 26 | 27 | context 'unparseable JSON 5xx' do 28 | let(:path) { 'unparseable_response' } 29 | 30 | it 'raises a server error' do 31 | expect { resource.get(url: url, payload: payload) }. 32 | to raise_error(Onfido::ServerError, /response code was 504/) 33 | end 34 | end 35 | 36 | context 'timeout' do 37 | before do 38 | allow(RestClient::Request). 39 | to receive(:execute). 40 | and_raise(RestClient::RequestTimeout) 41 | end 42 | 43 | it 'raises a ConnectionError' do 44 | expect { resource.get(url: Onfido.endpoint, payload: payload) }. 45 | to raise_error(Onfido::ConnectionError, /Could not connect/) 46 | end 47 | end 48 | 49 | context 'broken connection' do 50 | before do 51 | allow(RestClient::Request). 52 | to receive(:execute). 53 | and_raise(RestClient::ServerBrokeConnection) 54 | end 55 | 56 | it 'raises a ConnectionError' do 57 | expect { resource.get(url: Onfido.endpoint, payload: payload) }. 58 | to raise_error(Onfido::ConnectionError, /connection to the server/) 59 | end 60 | end 61 | 62 | context "bad SSL certificate" do 63 | before do 64 | allow(RestClient::Request). 65 | to receive(:execute). 66 | and_raise(RestClient::SSLCertificateNotVerified.new(nil)) 67 | end 68 | 69 | it 'raises a ConnectionError' do 70 | expect { resource.get(url: Onfido.endpoint, payload: payload) }. 71 | to raise_error(Onfido::ConnectionError, /SSL certificate/) 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /spec/onfido_spec.rb: -------------------------------------------------------------------------------- 1 | describe Onfido do 2 | subject(:onfido) { described_class } 3 | after(:each) { onfido.reset } 4 | 5 | context 'configuration' do 6 | describe "default values" do 7 | describe ".api_key" do 8 | subject { onfido.api_key } 9 | it { is_expected.to be_nil } 10 | end 11 | 12 | describe ".endpoint" do 13 | subject { onfido.endpoint } 14 | it { is_expected.to eq('https://api.onfido.com/v2/') } 15 | end 16 | 17 | describe ".logger" do 18 | subject { onfido.logger } 19 | it { is_expected.to be_an_instance_of(Onfido::NullLogger) } 20 | end 21 | end 22 | 23 | describe "setting an API key" do 24 | it 'changes the configuration to the new value' do 25 | onfido.api_key = 'some_key' 26 | expect(onfido.api_key).to eq('some_key') 27 | end 28 | end 29 | 30 | describe "setting the API version" do 31 | it 'changes the configuration to the new value' do 32 | onfido.api_version = 'v1' 33 | expect(onfido.api_version).to eq('v1') 34 | expect(onfido.endpoint).to eq('https://api.onfido.com/v1/') 35 | end 36 | end 37 | 38 | describe 'using the US region' do 39 | it 'should change endpoint' do 40 | onfido.region = 'us' 41 | expect(onfido.endpoint).to eq('https://api.us.onfido.com/v2/') 42 | end 43 | end 44 | 45 | describe 'using an unsupported region' do 46 | it 'should change endpoint' do 47 | onfido.region = 'de' 48 | expect { onfido.endpoint }. 49 | to raise_error('The region "de" is not currently supported') 50 | end 51 | end 52 | 53 | describe 'using an old API token' do 54 | it 'should use old endpoint' do 55 | onfido.api_key = "live_asdfghjkl1234567890qwertyuiop" 56 | expect(onfido.endpoint).to eq('https://api.onfido.com/v2/') 57 | end 58 | end 59 | 60 | describe '.logger' do 61 | context 'when an option is passed' do 62 | context 'when the option passed behaves like a logger' do 63 | let(:logger_like) { double('LoggerLike', :<< => nil) } 64 | 65 | it 'returns the option' do 66 | onfido.logger = logger_like 67 | expect(onfido.logger).to eq(logger_like) 68 | end 69 | end 70 | 71 | context 'when the option passed does not behave like a logger' do 72 | let(:non_logger) { double('NotLogger') } 73 | 74 | it 'raises an error' do 75 | expect { onfido.logger = non_logger }. 76 | to raise_error( 77 | "#{non_logger.class} doesn't seem to behave like a logger!" 78 | ) 79 | end 80 | end 81 | end 82 | end 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /spec/integrations/webhook_spec.rb: -------------------------------------------------------------------------------- 1 | describe Onfido::Webhook do 2 | subject(:webhook) { described_class.new } 3 | let(:params) do 4 | { 5 | "url" => "https://webhookendpoint.url", 6 | "enabled" => true, 7 | "events" => [ 8 | "report completion", 9 | "report withdrawal", 10 | "check completion", 11 | "check in progress" 12 | ] 13 | } 14 | end 15 | 16 | describe "#create" do 17 | it "cretes the webhook" do 18 | response = webhook.create(params) 19 | expect(response['id']).to_not be_nil 20 | end 21 | 22 | it "responds with the right url" do 23 | response = webhook.create(params) 24 | expect(response["url"]).to eq params["url"] 25 | end 26 | end 27 | 28 | describe '#find' do 29 | let(:webhook_id) { 'fcb73186-0733-4f6f-9c57-d9d5ef979443' } 30 | 31 | it 'returns the webhook' do 32 | response = webhook.find(webhook_id) 33 | expect(response['id']).to eq(webhook_id) 34 | end 35 | end 36 | 37 | describe "#all" do 38 | it "returns all the registered webhooks" do 39 | response = webhook.all 40 | expect(response["webhooks"].count).to eq 2 41 | end 42 | 43 | it "returns with id" do 44 | response = webhook.all 45 | expect(response["webhooks"][0]["id"]).to_not be_nil 46 | expect(response["webhooks"][1]["id"]).to_not be_nil 47 | end 48 | end 49 | 50 | describe ".valid?" do 51 | subject(:valid?) do 52 | described_class.valid?(request_body, request_signature, token) 53 | end 54 | 55 | let(:request_body) { '{"foo":"bar"}' } 56 | let(:request_signature) { 'fdab9db604d33297741b43b9fc9536028d09dca3' } 57 | let(:token) { 'very_secret_token' } 58 | 59 | it { is_expected.to be(true) } 60 | 61 | context "with an invalid signature" do 62 | let(:request_signature) { '2f3d7727ff9a32a7c87072ce514df1f6d3228bec' } 63 | it { is_expected.to be(false) } 64 | end 65 | 66 | context "with a nil request signature" do 67 | let(:request_signature) { nil } 68 | specify { expect { valid? }.to raise_error(ArgumentError) } 69 | end 70 | 71 | context "with a token other than the one used to sign the request" do 72 | let(:token) { "quite_secret_token" } 73 | it { is_expected.to be(false) } 74 | end 75 | 76 | context "with a nil token" do 77 | let(:token) { nil } 78 | specify { expect { valid? }.to raise_error(ArgumentError) } 79 | end 80 | 81 | context "with a modified request body" do 82 | let(:request_body) { '{"bar":"baz"}' } 83 | it { is_expected.to be(false) } 84 | end 85 | 86 | context "with a nil request body" do 87 | let(:request_body) { nil } 88 | specify { expect { valid? }.to raise_error(ArgumentError) } 89 | end 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /spec/integrations/check_spec.rb: -------------------------------------------------------------------------------- 1 | describe Onfido::Check do 2 | subject(:check) { described_class.new } 3 | let(:applicant_id) { '61f659cb-c90b-4067-808a-6136b5c01351' } 4 | 5 | describe '#create' do 6 | let(:params) { { type: 'express', reports: [{ name: 'identity' }] } } 7 | 8 | it 'creates a new check for an applicant' do 9 | response = check.create(applicant_id, params) 10 | expect(response['id']).not_to be_nil 11 | end 12 | end 13 | 14 | describe '#find' do 15 | let(:check_id) { '8546921-123123-123123' } 16 | 17 | it 'returns an existing check for the applicant' do 18 | response = check.find(applicant_id, check_id) 19 | expect(response['id']).to eq(check_id) 20 | end 21 | 22 | it "returns unexpanded reports" do 23 | response = check.find(applicant_id, check_id) 24 | expect(response['reports'].first).to be_a(String) 25 | end 26 | 27 | it 'allows you to expand the reports' do 28 | response = check.find(applicant_id, check_id, expand: "reports") 29 | expect(response['reports'].first).to be_a(Hash) 30 | end 31 | end 32 | 33 | describe '#find_by_url' do 34 | let(:check_id) { '8546921-123123-123123' } 35 | 36 | context 'partial url' do 37 | let(:url) { "applicants/#{applicant_id}/checks/#{check_id}" } 38 | 39 | it 'returns an existing check for the applicant with the partial url' do 40 | response = check.find_by_url(url) 41 | expect(response['id']).to eq(check_id) 42 | end 43 | 44 | it "returns unexpanded reports" do 45 | response = check.find_by_url(url) 46 | expect(response['reports'].first).to be_a(String) 47 | end 48 | 49 | it 'allows you to expand the reports' do 50 | response = check.find_by_url(url, expand: "reports") 51 | expect(response['reports'].first).to be_a(Hash) 52 | end 53 | end 54 | 55 | context 'full url' do 56 | let(:url) do 57 | "https://api.onfido.com/v2/applicants/#{applicant_id}/checks/#{check_id}" 58 | end 59 | 60 | it 'returns an existing check for the applicant with the partial url' do 61 | response = check.find_by_url(url) 62 | expect(response['id']).to eq(check_id) 63 | end 64 | 65 | it "returns unexpanded reports" do 66 | response = check.find_by_url(url) 67 | expect(response['reports'].first).to be_a(String) 68 | end 69 | 70 | it 'allows you to expand the reports' do 71 | response = check.find_by_url(url, expand: "reports") 72 | expect(response['reports'].first).to be_a(Hash) 73 | end 74 | end 75 | end 76 | 77 | describe '#all' do 78 | let(:check_id) { '8546921-123123-123123' } 79 | 80 | context 'with the default page and per page params' do 81 | it 'returns all existing checks for the applicant' do 82 | response = check.all(applicant_id) 83 | expect(response['checks'].size).to eq(1) 84 | end 85 | end 86 | 87 | it "returns unexpanded reports" do 88 | response = check.all(applicant_id) 89 | expect(response['checks'].first['reports'].first).to be_a(String) 90 | end 91 | 92 | it 'allows you to expand the reports' do 93 | response = check.all(applicant_id, expand: "reports") 94 | expect(response['checks'].first['reports'].first).to be_a(Hash) 95 | end 96 | end 97 | 98 | describe "#resume" do 99 | let(:check_id) { '8546921-123123-123123' } 100 | 101 | it 'returns success response' do 102 | expect { check.resume(check_id) }.not_to raise_error 103 | end 104 | end 105 | end 106 | -------------------------------------------------------------------------------- /spec/integrations/applicant_spec.rb: -------------------------------------------------------------------------------- 1 | describe Onfido::Applicant do 2 | subject(:applicant) { described_class.new } 3 | let(:params) do 4 | { 5 | 'title' => 'Mr', 6 | 'first_name' => 'Chandler', 7 | 'last_name' => 'Bing', 8 | 'gender' => 'male', 9 | 'middle_name' => 'Muriel', 10 | 'dob' => '1968-04-08', 11 | 'telephone' => '555555555', 12 | 'mobile' => '77777777', 13 | 'email' => 'chandler_bing_6@friends.com', 14 | 'addresses' => [ 15 | { 16 | 'flat_number' => '4', 17 | 'building_number' => '100', 18 | 'building_name' => 'Awesome Building', 19 | 'street' => 'Main Street', 20 | 'sub_street' => 'A sub street', 21 | 'town' => 'London', 22 | 'postcode' => 'SW4 6EH', 23 | 'country' => 'GBR' 24 | }, 25 | { 26 | 'flat_number' => '1', 27 | 'building_number' => '10', 28 | 'building_name' => 'Great Building', 29 | 'street' => 'Old Street', 30 | 'sub_street' => 'Sub Street', 31 | 'town' => 'London', 32 | 'postcode' => 'SW1 4NG', 33 | 'country' => 'GBR' 34 | } 35 | ] 36 | } 37 | end 38 | 39 | describe '#create' do 40 | # Need to find a better way of testing that the request is not malformed. 41 | # Currently this runs for every feature spec. The fact that it's under here 42 | # is only for semantic reasons 43 | 44 | it 'serializes the payload correctly' do 45 | WebMock.after_request do |request_signature, _response| 46 | if request_signature.uri.path == 'v2/applicants' 47 | expect(Rack::Utils.parse_nested_query(request_signature.body)). 48 | to eq(params) 49 | end 50 | end 51 | end 52 | 53 | it 'creates an applicant' do 54 | response = applicant.create(params) 55 | expect(response['id']).not_to be_nil 56 | end 57 | end 58 | 59 | describe '#update' do 60 | let(:applicant_id) { '61f659cb-c90b-4067-808a-6136b5c01351' } 61 | 62 | it 'updates an applicant' do 63 | response = applicant.update(applicant_id, params) 64 | expect(response['id']).to eq(applicant_id) 65 | end 66 | end 67 | 68 | describe '#find' do 69 | let(:applicant_id) { '61f659cb-c90b-4067-808a-6136b5c01351' } 70 | 71 | it 'returns the applicant' do 72 | response = applicant.find(applicant_id) 73 | expect(response['id']).to eq(applicant_id) 74 | end 75 | end 76 | 77 | describe '#destroy' do 78 | let(:applicant_id) { '61f659cb-c90b-4067-808a-6136b5c01351' } 79 | 80 | it 'returns success code' do 81 | expect { applicant.destroy(applicant_id) }.not_to raise_error 82 | end 83 | end 84 | 85 | describe '#all' do 86 | context 'with the default page and per page params' do 87 | it 'returns all the applicants' do 88 | response = applicant.all 89 | expect(response['applicants'].size).to eq(2) 90 | end 91 | end 92 | 93 | context 'with specific range of results for a page' do 94 | it 'returns the specified applicants' do 95 | response = applicant.all(page: 1, per_page: 1) 96 | expect(response['applicants'].size).to eq(1) 97 | end 98 | end 99 | end 100 | 101 | describe '#restore' do 102 | context 'an applicant scheduled for deletion' do 103 | let(:applicant_id) { '61f659cb-c90b-4067-808a-6136b5c01351' } 104 | 105 | it 'returns nil' do 106 | expect(applicant.restore(applicant_id)).to be_nil 107 | end 108 | end 109 | 110 | context 'an applicant not scheduled for deletion' do 111 | let(:applicant_id) { 'a2fb9c62-ab10-4898-a8ec-342c4b552ad5' } 112 | 113 | it 'returns an error' do 114 | expect { applicant.restore(applicant_id) }.to raise_error { |error| 115 | expect(error).to be_a(Onfido::RequestError) 116 | expect(error.message).to eq('There was a validation error on this request') 117 | expect(error.fields).to eq( 118 | "Applicant a2fb9c62-ab10-4898-a8ec-342c4b552ad5 is not scheduled for deletion" 119 | ) 120 | } 121 | end 122 | end 123 | end 124 | end 125 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## v0.15.0, 4 Feb 2019 2 | 3 | - Add Check#find_by_url method (@ctrlaltdylan) 4 | 5 | ## v0.14.0, 28 Jan 2019 6 | 7 | - Add support for applicant deletion and restore endpoints 8 | 9 | ## v0.13.0, 5 Nov 2018 10 | 11 | - Add support for region-specific environments (@stephencookdev) 12 | 13 | ## v0.12.0, 29 May 2018 14 | 15 | - Add support for the Live Video resource (#55) (@Intrepidd) 16 | 17 | ## v0.11.0, 18 April 2018 18 | 19 | - Automatically detect the correct Onfido API region to use based on the provided API 20 | key (#49) (@tpinto) 21 | 22 | ## v0.10.0, 3 April 2018 23 | 24 | - Add support for download, find and retrieve all for live_photo resource (@kirsis) 25 | 26 | ## v0.9.0, 8 March 2018 27 | 28 | - Remove the ability to create Documents and Live Photos from a remote URL or local path, mitigating a potential security vulnerability (see #45 for details) (@timrogers) 29 | - Drop support for Ruby versions earlier than 2.2.0, since they have [reached end-of-life](https://www.ruby-lang.org/en/news/2017/04/01/support-of-ruby-2-1-has-ended/) (@timrogers) 30 | 31 | ## v0.8.4, 29 January 2018 32 | 33 | - Replace use of `method_missing` with explicitly-defined accessors when accessing 34 | resources from an `Onfido::API` instance (@hmac) 35 | 36 | ## v0.8.3, 16 January 2018 37 | 38 | - Add support for `Onfido::SdkToken` resource (@robotmay) 39 | 40 | ## v0.8.2, 21 April 2017 41 | 42 | - Relax [Rack](https://github.com/rack/rack) dependency to allow all versions where `Rack::Utils.secure_compare` is available (@metade) 43 | 44 | ## v0.8.1, 5 April 2017 45 | 46 | - Fix `Content-Type` header handling to be more permissive when detecting JSON (e.g. still recognise the type as JSON when the charset is appended to the header) (@sponomarev) 47 | - Update [`rest-client`](https://github.com/rest-client/rest-client) dependency to 2.x (@timrogers) 48 | - Update [`WebMock`](https://github.com/bblimke/webmock) dependendency to 2.x (@timrogers) 49 | 50 | ## v0.8.0, 16 November 2016 51 | 52 | - Add support for `put` and `delete` http methods to `Onfido::Resource` 53 | - Support no content and binary responses at `Onfido::Resource` 54 | - Add support for `Onfido::ReportTypeGroup` resource 55 | - Add support for update and destroy applicant resource 56 | - Add support for download, find and retrieve all for document resource 57 | - Add support for resume checks 58 | - Add support for resume and cancel report 59 | 60 | ## v0.7.1, 5 September 2016 61 | 62 | - Fix creation of live photos through `Onfido::LivePhoto` resource 63 | 64 | ## v0.7.0, 8 August 2016 65 | 66 | - Add support for dynamic API keys 67 | (see https://github.com/hvssle/onfido#usage) 68 | - Fix `Onfido::Resource` so it raises `Onfido::ConnectionError` on 69 | HTTP Error 408 Request Timeout 70 | 71 | ## v0.6.1, 15 July 2016 72 | 73 | - Fix `Onfido::API.live_photo` so it returns an `Onfido::LivePhoto` instance 74 | (see https://github.com/hvssle/onfido/pull/14) 75 | 76 | ## v0.6.0, 7 July 2016 77 | 78 | - Add `expand` option to `Onfido::Check.find` and `Onfido::Check.all`. See 79 | https://github.com/hvssle/onfido/pull/11 for details. 80 | 81 | ## v0.5.0, 7 June 2016 82 | 83 | - Add `Onfido::Webhook.valid?` method, for checking the signature of a webhook 84 | from Onfido 85 | 86 | ## v0.4.0, 12 May 2016 87 | 88 | - BREAKING: target v2 of the Onfido API. To continue using v1, specify this 89 | version in `Onfido.configure` 90 | - Add `api_version` configuration option 91 | (see https://github.com/hvssle/onfido#configuration) 92 | - Add support for `Onfido::LivePhoto` resource 93 | 94 | ## v0.3.0, 16 March 2016 95 | 96 | - Add support for `Onfido::Webhook` resource 97 | 98 | ## v0.2.0, 9 February 2016 99 | 100 | - BREAKING: adds `Onfido::ServerError`, which is raised whenever Onfido responds 101 | with a 5xx code. Previously `Onfido::RequestError` would have been raised, but 102 | this is now reserved for non-5xx responses. 103 | 104 | ## v0.1.0, 11 November 2015 105 | 106 | - BREAKING: remove `throws_exceptions` option. We now always throw exceptions 107 | for errors. This option had become confusing since `Onfido::ConnectionError` 108 | was added in v0.0.4, since timeout errors were raised regardless of its value. 109 | - Add base errors class (`Onfido::OnfidoError`) 110 | - Add rubocop, and fix style inconsistencies 111 | 112 | ## v0.0.4, 10 November 2015 113 | 114 | - Split out connection errors so they can be automatically retried 115 | -------------------------------------------------------------------------------- /spec/onfido/resource_spec.rb: -------------------------------------------------------------------------------- 1 | require 'onfido/errors/connection_error' 2 | 3 | describe Onfido::Resource do 4 | subject(:resource) { described_class.new } 5 | 6 | let(:endpoint) { 'https://api.onfido.com/v2/' } 7 | let(:path) { 'addresses/pick' } 8 | let(:url) { endpoint + path } 9 | let(:payload) { { postcode: 'SE1 4NG' } } 10 | let(:api_key) { 'some_key' } 11 | 12 | let(:response) do 13 | { 14 | 'addresses' => [ 15 | { 16 | 'street' => 'Main Street', 17 | 'town' => 'London', 18 | 'postcode' => 'SW4 6EH', 19 | 'country' => 'GBR' 20 | } 21 | ] 22 | } 23 | end 24 | 25 | before { allow(Onfido).to receive(:endpoint).and_return(endpoint) } 26 | before { allow(Onfido).to receive(:api_key).and_return(api_key) } 27 | 28 | describe '#url_for' do 29 | it 'composes the full api url' do 30 | expect(resource.url_for(path)).to eq(endpoint + path) 31 | end 32 | end 33 | 34 | describe '#method_missing' do 35 | %i(patch).each do |method| 36 | context "for unsupported HTTP method: #{method}" do 37 | it 'raises an error' do 38 | expect do 39 | resource.public_send(method, url: endpoint) 40 | end.to raise_error(NoMethodError) 41 | end 42 | end 43 | end 44 | end 45 | 46 | describe "API key" do 47 | subject(:resource) { described_class.new(specific_api_key) } 48 | 49 | before do 50 | expect(RestClient::Request).to receive(:execute).with( 51 | url: url, 52 | payload: Rack::Utils.build_query(payload), 53 | method: :get, 54 | headers: resource.send(:headers), 55 | open_timeout: 30, 56 | timeout: 80 57 | ).and_call_original 58 | 59 | WebMock.stub_request(:get, url). 60 | to_return(body: response.to_json, status: 200) 61 | end 62 | 63 | context "when using a specific key" do 64 | let(:specific_api_key) { "specific_key" } 65 | 66 | it "uses that key when making the request" do 67 | resource.get(url: url, payload: payload) 68 | 69 | expect(WebMock).to have_requested(:get, url).with( 70 | headers: { 71 | 'Authorization' => "Token token=#{specific_api_key}", 72 | 'Accept' => "application/json" 73 | } 74 | ) 75 | end 76 | end 77 | 78 | context "when not using a specific key" do 79 | let(:specific_api_key) { nil } 80 | 81 | it "uses the general config key when making the request" do 82 | resource.get(url: url, payload: payload) 83 | 84 | expect(WebMock).to have_requested(:get, url).with( 85 | headers: { 86 | 'Authorization' => "Token token=#{api_key}", 87 | 'Accept' => "application/json" 88 | } 89 | ) 90 | end 91 | end 92 | end 93 | 94 | describe "valid http methods" do 95 | %i(get post put delete).each do |method| 96 | context "for supported HTTP method: #{method}" do 97 | context "with a success response" do 98 | before do 99 | expect(RestClient::Request).to receive(:execute). 100 | with( 101 | url: url, 102 | payload: Rack::Utils.build_query(payload), 103 | method: method, 104 | headers: resource.send(:headers), 105 | open_timeout: 30, 106 | timeout: 80 107 | ).and_call_original 108 | 109 | WebMock.stub_request(method, url). 110 | to_return(body: response.to_json, 111 | status: 200, 112 | headers: { "Content-Type" => "application/json" }) 113 | end 114 | 115 | it 'makes a request to an endpoint' do 116 | expect(resource.public_send(method, url: url, payload: payload)). 117 | to eq(response) 118 | end 119 | end 120 | 121 | context "with a timeout error response" do 122 | before do 123 | allow_any_instance_of(RestClient::ExceptionWithResponse). 124 | to receive(:response).and_return(double(body: "", code: "408")) 125 | expect(RestClient::Request).to receive(:execute). 126 | and_raise(RestClient::ExceptionWithResponse) 127 | end 128 | 129 | it "raises a ConnectionError" do 130 | expect { resource.public_send(method, url: url, payload: payload) }. 131 | to raise_error(Onfido::ConnectionError) 132 | end 133 | end 134 | end 135 | end 136 | end 137 | end 138 | -------------------------------------------------------------------------------- /lib/onfido/resource.rb: -------------------------------------------------------------------------------- 1 | module Onfido 2 | class Resource 3 | VALID_HTTP_METHODS = %i(get post put delete).freeze 4 | REQUEST_TIMEOUT_HTTP_CODE = 408 5 | 6 | def initialize(api_key = nil) 7 | @api_key = api_key || Onfido.api_key 8 | end 9 | 10 | def url_for(path) 11 | Onfido.endpoint + path 12 | end 13 | 14 | VALID_HTTP_METHODS.each do |method| 15 | define_method method do |*args| 16 | make_request( 17 | method: method.to_sym, 18 | url: args.first.fetch(:url), 19 | payload: build_query(args.first.fetch(:payload, {})) 20 | ) 21 | end 22 | end 23 | 24 | private 25 | 26 | def make_request(options) 27 | url = options.fetch(:url) 28 | payload = options.fetch(:payload) 29 | method = options.fetch(:method) 30 | 31 | request_options = { 32 | url: url, 33 | payload: payload, 34 | method: method, 35 | headers: headers, 36 | open_timeout: Onfido.open_timeout, 37 | timeout: Onfido.read_timeout 38 | } 39 | 40 | response = RestClient::Request.execute(request_options) 41 | 42 | # response should be parsed only when there is a response expected 43 | parse(response) unless response.code == 204 # no_content 44 | rescue RestClient::ExceptionWithResponse => error 45 | if error.response && !timeout_response?(error.response) 46 | handle_api_error(error.response) 47 | else 48 | handle_restclient_error(error, url) 49 | end 50 | rescue RestClient::Exception, Errno::ECONNREFUSED => error 51 | handle_restclient_error(error, url) 52 | end 53 | 54 | def parse(response) 55 | content_type = response.headers[:content_type] 56 | if content_type && content_type.include?("application/json") 57 | JSON.parse(response.body.to_s) 58 | else 59 | response.body 60 | end 61 | rescue JSON::ParserError 62 | general_api_error(response.code, response.body) 63 | end 64 | 65 | def timeout_response?(response) 66 | response.code.to_i == REQUEST_TIMEOUT_HTTP_CODE 67 | end 68 | 69 | def headers 70 | { 71 | 'Authorization' => "Token token=#{@api_key}", 72 | 'Accept' => "application/json" 73 | } 74 | end 75 | 76 | # There seems to be a serialization issue with the HTTP client 77 | # which does not serialize the payload properly. 78 | # Have a look here https://gist.github.com/PericlesTheo/cb35139c57107ab3c84a 79 | 80 | def build_query(payload) 81 | if payload[:file] 82 | payload 83 | else 84 | Rack::Utils.build_nested_query(payload) 85 | end 86 | end 87 | 88 | def handle_api_error(response) 89 | parsed_response = parse(response) 90 | 91 | general_api_error(response.code, response.body) unless parsed_response["error"] 92 | 93 | error_class = response.code.to_i >= 500 ? ServerError : RequestError 94 | 95 | raise error_class.new( 96 | parsed_response["error"]['message'], 97 | response_code: response.code, 98 | response_body: response.body 99 | ) 100 | end 101 | 102 | def general_api_error(response_code, response_body) 103 | error_class = response_code.to_i >= 500 ? ServerError : RequestError 104 | 105 | raise error_class.new( 106 | "Invalid response object from API: #{response_body} " \ 107 | "(HTTP response code was #{response_code})", 108 | response_code: response_code, 109 | response_body: response_body 110 | ) 111 | end 112 | 113 | def handle_restclient_error(error, url) 114 | connection_message = 115 | "Please check your internet connection and try again. " \ 116 | "If this problem persists, you should let us know at info@onfido.com." 117 | 118 | message = 119 | case error 120 | when RestClient::RequestTimeout 121 | "Could not connect to Onfido (#{url}). #{connection_message}" 122 | 123 | when RestClient::ServerBrokeConnection 124 | "The connection to the server (#{url}) broke before the " \ 125 | "request completed. #{connection_message}" 126 | 127 | when RestClient::SSLCertificateNotVerified 128 | "Could not verify Onfido's SSL certificate. Please make sure " \ 129 | "that your network is not intercepting certificates. " \ 130 | "(Try going to #{Onfido.endpoint} in your browser.) " \ 131 | "If this problem persists, let us know at info@onfido.com." 132 | 133 | when SocketError 134 | "Unexpected error when trying to connect to Onfido. " \ 135 | "You may be seeing this message because your DNS is not working. " \ 136 | "To check, try running 'host onfido.com' from the command line." 137 | 138 | else 139 | "Unexpected error communicating with Onfido. " \ 140 | "If this problem persists, let us know at info@onfido.com." 141 | end 142 | 143 | full_message = message + "\n\n(Network error: #{error.message})" 144 | 145 | raise ConnectionError.new(full_message) 146 | end 147 | 148 | def validate_file!(file) 149 | return if file.respond_to?(:read) && file.respond_to?(:path) 150 | 151 | raise ArgumentError, "File must be a `File`-like object which responds to " \ 152 | "`#read` and `#path`" 153 | end 154 | end 155 | end 156 | -------------------------------------------------------------------------------- /spec/support/fake_onfido_api.rb: -------------------------------------------------------------------------------- 1 | require 'sinatra/base' 2 | 3 | class FakeOnfidoAPI < Sinatra::Base 4 | get '/v2/addresses/pick' do 5 | json_response(200, 'addresses.json') 6 | end 7 | 8 | post '/v2/applicants' do 9 | json_response(201, 'applicant.json') 10 | end 11 | 12 | put '/v2/applicants/:id' do 13 | json_response(200, 'applicant.json') 14 | end 15 | 16 | get '/v2/applicants/:id' do 17 | json_response(200, 'applicant.json') 18 | end 19 | 20 | get '/v2/applicants' do 21 | response = json_response(200, 'applicants.json') 22 | { applicants: JSON.parse(response)['applicants'][pagination_range] }.to_json 23 | end 24 | 25 | delete '/v2/applicants/:id' do 26 | status 204 27 | end 28 | 29 | post '/v2/applicants/:id/restore' do 30 | if params["id"] == "a2fb9c62-ab10-4898-a8ec-342c4b552ad5" 31 | json_response(422, 'not_scheduled_for_deletion_error.json') 32 | else 33 | status 204 34 | end 35 | end 36 | 37 | post '/v2/applicants/:id/documents' do 38 | json_response(201, 'document.json') 39 | end 40 | 41 | get '/v2/applicants/:id/documents/:id' do 42 | json_response(200, 'document.json') 43 | end 44 | 45 | get '/v2/applicants/:id/documents' do 46 | json_response(200, 'documents.json') 47 | end 48 | 49 | get '/v2/applicants/:id/documents/:id/download' do 50 | status 200 51 | content_type 'application/octet-stream' 52 | "\x01\x02\x03" # acts as binary file data 53 | end 54 | 55 | post '/v2/live_photos' do 56 | json_response(201, 'live_photo.json') 57 | end 58 | 59 | get '/v2/live_photos/:id' do 60 | if params["applicant_id"] != "1030303-123123-123123" 61 | status 404 62 | else 63 | json_response(200, 'live_photo.json') 64 | end 65 | end 66 | 67 | get '/v2/live_photos' do 68 | if params["applicant_id"] != "1030303-123123-123123" 69 | status 404 70 | else 71 | json_response(200, 'live_photos.json') 72 | end 73 | end 74 | 75 | get '/v2/live_photos/:id/download' do 76 | if params["applicant_id"] != "1030303-123123-123123" 77 | status 404 78 | else 79 | status 200 80 | content_type 'image/jpeg' 81 | "\x01\x02\x03" # acts as binary file data 82 | end 83 | end 84 | 85 | get '/v2/live_videos/:id' do 86 | if params["applicant_id"] != "1030303-123123-123123" 87 | status 404 88 | else 89 | json_response(200, 'live_video.json') 90 | end 91 | end 92 | 93 | get '/v2/live_videos' do 94 | if params["applicant_id"] != "1030303-123123-123123" 95 | status 404 96 | else 97 | json_response(200, 'live_videos.json') 98 | end 99 | end 100 | 101 | get '/v2/live_videos/:id/download' do 102 | if params["applicant_id"] != "1030303-123123-123123" 103 | status 404 104 | else 105 | status 200 106 | content_type 'video/quicktime' 107 | "\x01\x02\x03" # acts as binary file data 108 | end 109 | end 110 | 111 | post '/v2/applicants/:id/checks' do 112 | json_response(201, 'check.json') 113 | end 114 | 115 | get '/v2/applicants/:id/checks/:id' do 116 | if params["expand"] == "reports" 117 | json_response(200, "check_with_expanded_reports.json") 118 | else 119 | json_response(200, "check.json") 120 | end 121 | end 122 | 123 | get '/v2/applicants/:id/checks' do 124 | response = if params["expand"] == "reports" 125 | json_response(200, "checks_with_expanded_reports.json") 126 | else 127 | json_response(200, "checks.json") 128 | end 129 | 130 | { checks: JSON.parse(response)['checks'][pagination_range] }.to_json 131 | end 132 | 133 | post '/v2/checks/:id/resume' do 134 | status 204 # no_content 135 | end 136 | 137 | get '/v2/checks/:id/reports' do 138 | json_response(200, 'reports.json') 139 | end 140 | 141 | get '/v2/checks/:id/reports/:id' do 142 | json_response(200, 'report.json') 143 | end 144 | 145 | post '/v2/checks/:id/reports/:id/resume' do 146 | status 204 147 | end 148 | 149 | post '/v2/checks/:id/reports/:id/cancel' do 150 | status 204 151 | end 152 | 153 | get '/v2/report_type_groups/:id' do 154 | json_response(200, 'report_type_group.json') 155 | end 156 | 157 | get '/v2/report_type_groups' do 158 | json_response(200, 'report_type_groups.json') 159 | end 160 | 161 | post '/v2/sdk_token' do 162 | json_response(201, 'sdk_token.json') 163 | end 164 | 165 | post '/v2/webhooks' do 166 | json_response(201, 'webhook.json') 167 | end 168 | 169 | get '/v2/webhooks/:id' do 170 | json_response(200, 'webhook.json') 171 | end 172 | 173 | get '/v2/webhooks' do 174 | json_response(200, 'webhooks.json') 175 | end 176 | 177 | get '/v2/4xx_response' do 178 | json_response(422, '4xx_response.json') 179 | end 180 | 181 | get '/v2/unexpected_error_format' do 182 | json_response(400, 'unexpected_error_format.json') 183 | end 184 | 185 | get '/v2/unparseable_response' do 186 | content_type :json 187 | status 504 188 | '' 189 | end 190 | 191 | private 192 | 193 | def json_response(response_code, file_name) 194 | content_type "application/json; charset=utf-8" 195 | status response_code 196 | File.open(File.dirname(__FILE__) + '/fixtures/' + file_name, 'rb').read 197 | end 198 | 199 | def pagination_range 200 | start = (params.fetch('page').to_i - 1) * 20 201 | limit = start + params.fetch('per_page').to_i - 1 202 | start..limit 203 | end 204 | end 205 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Onfido 2 | 3 | A thin wrapper for Onfido's API. 4 | 5 | [![Gem Version](https://badge.fury.io/rb/onfido.svg)](http://badge.fury.io/rb/onfido) 6 | [![Build Status](https://travis-ci.org/hvssle/onfido.svg?branch=master)](https://travis-ci.org/hvssle/onfido) 7 | 8 | This gem supports both `v1` and `v2` of the Onfido API. Refer to Onfido's [API documentation](https://onfido.com/documentation#introduction) for details of the expected requests and responses for both. 9 | 10 | ## Installation 11 | 12 | Add this line to your application's Gemfile: 13 | 14 | ```ruby 15 | gem 'onfido', '~> 0.15.0' 16 | ``` 17 | 18 | The gem is compatible with Ruby 2.2.0 and onwards. Earlier versions of Ruby have [reached end-of-life](https://www.ruby-lang.org/en/news/2017/04/01/support-of-ruby-2-1-has-ended/), are no longer supported and no longer receive security fixes. 19 | 20 | ## Configuration 21 | 22 | There are 6 configuration options: 23 | 24 | ```ruby 25 | Onfido.configure do |config| 26 | config.api_key = 'MY_API_KEY' 27 | config.api_version = 'v2' 28 | config.logger = Logger.new(STDOUT) 29 | config.open_timeout = 30 30 | config.read_timeout = 80 31 | config.region = nil 32 | end 33 | ``` 34 | 35 | ### Regions 36 | 37 | The gem will use the default region if no region is specified. 38 | 39 | To specify the US region do: 40 | `config.region = :us` 41 | 42 | See https://documentation.onfido.com/#regions for supported regions. 43 | 44 | ## Usage 45 | 46 | You can make API calls by using an instance of the `API` class: 47 | 48 | ```ruby 49 | api = Onfido::API.new 50 | ``` 51 | 52 | Alternatively, you can set an API key here instead of in the initializer: 53 | 54 | ```ruby 55 | api = Onfido::API.new(api_key: 'API_KEY') 56 | ``` 57 | 58 | ### Resources 59 | 60 | All resources share the same interface when making API calls. Use `.create` to create a resource, `.find` to find one, and `.all` to fetch all resources. 61 | 62 | **Note:** *All param keys should be a symbol e.g. `{ type: 'express', reports: [{ name: 'identity' }] }`* 63 | 64 | #### Applicants 65 | 66 | Applicants are the object upon which Onfido checks are performed. 67 | 68 | ```ruby 69 | api.applicant.create(params) # => Creates an applicant 70 | api.applicant.update('applicant_id', params) # => Updates an applicant 71 | api.applicant.destroy('applicant_id') # => Schedule an applicant for deletion 72 | api.applicant.restore('applicant_id') # => Restore an applicant scheduled for deletion 73 | api.applicant.find('applicant_id') # => Finds a single applicant 74 | api.applicant.all # => Returns all applicants 75 | ``` 76 | 77 | **Note:** Calling `api.applicant.destroy` adds the applicant and all associated documents, photos, videos, checks, and reports to the deletion queue. They will be deleted 20 days after the request is made. An applicant that is scheduled for deletion can be restored but applicants that have been permanently deleted cannot. 78 | See https://documentation.onfido.com/#delete-applicant for more information. 79 | 80 | #### Documents 81 | 82 | Documents provide supporting evidence for Onfido checks. 83 | 84 | ```ruby 85 | api.document.create('applicant_id', file: 'http://example.com', type: 'passport') # => Creates a document 86 | api.document.find('applicant_id', 'document_id') # => Finds a document 87 | api.document.download('applicant_id', 'document_id') # => Downloads a document as a binary data 88 | api.document.all('applicant_id') # => Returns all applicant's documents 89 | ``` 90 | 91 | **Note:** The file parameter must be a `File`-like object which responds to `#read` and `#path`. 92 | Previous versions of this gem supported providing a URL to a file accessible over HTTP or a path 93 | to a file in the local filesystem. You should instead load the file yourself and then pass it in 94 | to `#create`. 95 | 96 | #### Live Photos 97 | 98 | Live Photos, like documents, can provide supporting evidence for Onfido checks. 99 | 100 | ```ruby 101 | api.live_photo.create('applicant_id', file: 'http://example.com') 102 | api.live_photo.find(applicant_id, live_photo_id) # => Finds a live photo 103 | api.live_photo.download(applicant_id, live_photo_id) # => Downloads a live photo as binary data 104 | api.live_photo.all(applicant_id) # => Returns all applicant's live photos 105 | ``` 106 | 107 | **Note:** The file parameter must be a `File`-like object which responds to `#read` and `#path`. 108 | Previous versions of this gem supported providing a URL to a file accessible over HTTP or a path 109 | to a file in the local filesystem. You should instead load the file yourself and then pass it in 110 | to `#create`. 111 | 112 | #### Checks 113 | 114 | Checks are requests for Onfido to check an applicant, by commissioning one or 115 | more "reports" on them. 116 | 117 | ```ruby 118 | api.check.create('applicant_id', type: 'express', reports: [{ name: 'identity' }]) 119 | api.check.find('applicant_id', 'check_id') 120 | api.check.find_by_url('applicants/a90e7a17-677a-49ab-a171-281f96c77bde/checks/c9f41bef-0610-4d2f-9982-ae9387876edc') 121 | api.check.resume('check_id') 122 | api.check.all('applicant_id') 123 | ``` 124 | 125 | #### Reports 126 | 127 | Reports provide details of the results of some part of a "check". They are 128 | created when a check is created, so the Onfido API only provides support for 129 | finding and listing them. For paused reports specifically, additional support for resuming and 130 | cancelling reports is also available. 131 | 132 | ```ruby 133 | api.report.find('check_id', 'report_id') 134 | api.report.all('check_id') 135 | api.report.resume('check_id', 'report_id') 136 | api.report.cancel('check_id', 'report_id') 137 | ``` 138 | 139 | #### Report Type Groups 140 | 141 | Report type groups provide a convenient way to group and organize different types of reports. 142 | The Onfido API only provides support for finding and listing them. 143 | 144 | ```ruby 145 | api.report_type_group.find('report_type_group_id') 146 | api.report_type_group.all() 147 | ``` 148 | 149 | #### Address Lookups 150 | 151 | Onfido provides an address lookup service, to help ensure well-formatted 152 | addresses are provided when creating "applicants". To search for addresses 153 | by postcode, use: 154 | 155 | ```ruby 156 | api.address.all('SE1 4NG') 157 | ``` 158 | 159 | #### Webhook Endpoints 160 | 161 | Onfido allows you to set up and view your webhook endpoints via the API, as well 162 | as through the dashboard. 163 | 164 | ```ruby 165 | api.webhook.create(params) # => Creates a webhook endpoint 166 | api.webhook.find('webhook_id') # => Finds a single webhook endpoint 167 | api.webhook.all # => Returns all webhook endpoints 168 | ``` 169 | 170 | #### SDK Tokens 171 | 172 | Onfido allows you to generate JSON Web Tokens via the API in order to authenticate 173 | with Onfido's [JavaScript SDK](https://github.com/onfido/onfido-sdk-ui). 174 | 175 | ```ruby 176 | api.sdk_token.create(applicant_id: 'applicant_id', referrer: 'referrer') 177 | ``` 178 | 179 | ### Pagination 180 | 181 | All resources that support an `all` method also support pagination. By default, 182 | the first 20 records are fetched. 183 | 184 | ### Error Handling 185 | 186 | There are three classes of errors raised by the library, all of which subclass `Onfido::OnfidoError`: 187 | - `Onfido::ServerError` is raised whenever Onfido returns a `5xx` response 188 | - `Onfido::RequestError` is raised whenever Onfido returns any other kind of error 189 | - `Onfido::ConnectionError` is raised whenever a network error occurs (e.g., a timeout) 190 | 191 | All three error classes provide the `response_code`, `response_body`, `json_body`, `type` and `fields` of the error (although for `Onfido::ServerError` and `Onfido::ConnectionError` the last three are likely to be `nil`). 192 | 193 | ```ruby 194 | def create_applicant 195 | api.applicant.create(params) 196 | rescue Onfido::RequestError => e 197 | e.type # => 'validation_error' 198 | e.fields # => { "email": { "messages": ["invalid format"] } } 199 | e.response_code # => '422' 200 | end 201 | ``` 202 | 203 | ## Webhooks 204 | 205 | Each webhook endpoint has a secret token, generated automatically and [exposed](https://onfido.com/documentation#register-webhook) in the API. When sending a request, Onfido includes a signature computed using the request body and this token in the `X-Signature` header. 206 | 207 | This provided signature [should](https://onfido.com/documentation#webhook-security) be compared to one you generate yourself with the token to check that a webhook is a genuine request from Onfido. 208 | 209 | ```ruby 210 | if Onfido::Webhook.valid?(request.raw_post, 211 | request.headers["X-Signature"], 212 | ENV['ONFIDO_WEBHOOK_TOKEN']) 213 | process_webhook 214 | else 215 | render status: 498, text: "498 Token expired/invalid" 216 | end 217 | ``` 218 | 219 | ## Roadmap 220 | 221 | - Improve test coverage with more scenarios 222 | - Add custom errors based on the response code 223 | - Improve pagination handling (use information passed in link header) 224 | 225 | ## Contributing 226 | 227 | 1. Fork it ( https://github.com/hvssle/onfido/fork ) 228 | 2. Create your feature branch (`git checkout -b my-new-feature`) 229 | 3. Commit your changes (`git commit -am 'Add some feature'`) 230 | 4. Push to the branch (`git push origin my-new-feature`) 231 | 5. Create a new Pull Request 232 | --------------------------------------------------------------------------------