Implemented Device Check API call
Implemented Device Check API call
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
DEVICE_CHECK_KEY_FILE="AuthKey_ABCDEFGHI.p8"
|
||||
DEVICE_CHECK_KEY_ID=ABCDEFGHI
|
||||
DEVICE_CHECK_TEAM_ID=HZZZZZZZZZ
|
||||
@@ -0,0 +1,4 @@
|
||||
.DS_Store
|
||||
/.env
|
||||
.env
|
||||
*.p8
|
||||
@@ -0,0 +1,7 @@
|
||||
source 'https://rubygems.org'
|
||||
|
||||
gem 'dotenv'
|
||||
gem 'jwt'
|
||||
gem 'http'
|
||||
gem 'sinatra'
|
||||
gem 'sinatra-contrib'
|
||||
@@ -0,0 +1,67 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
activesupport (5.2.1)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 0.7, < 2)
|
||||
minitest (~> 5.1)
|
||||
tzinfo (~> 1.1)
|
||||
addressable (2.5.2)
|
||||
public_suffix (>= 2.0.2, < 4.0)
|
||||
backports (3.11.4)
|
||||
concurrent-ruby (1.0.5)
|
||||
domain_name (0.5.20180417)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
dotenv (2.5.0)
|
||||
http (4.0.0)
|
||||
addressable (~> 2.3)
|
||||
http-cookie (~> 1.0)
|
||||
http-form_data (~> 2.0)
|
||||
http_parser.rb (~> 0.6.0)
|
||||
http-cookie (1.0.3)
|
||||
domain_name (~> 0.5)
|
||||
http-form_data (2.1.1)
|
||||
http_parser.rb (0.6.0)
|
||||
i18n (1.1.1)
|
||||
concurrent-ruby (~> 1.0)
|
||||
jwt (2.1.0)
|
||||
minitest (5.11.3)
|
||||
multi_json (1.13.1)
|
||||
mustermann (1.0.3)
|
||||
public_suffix (3.0.3)
|
||||
rack (2.0.5)
|
||||
rack-protection (2.0.4)
|
||||
rack
|
||||
sinatra (2.0.4)
|
||||
mustermann (~> 1.0)
|
||||
rack (~> 2.0)
|
||||
rack-protection (= 2.0.4)
|
||||
tilt (~> 2.0)
|
||||
sinatra-contrib (2.0.4)
|
||||
activesupport (>= 4.0.0)
|
||||
backports (>= 2.8.2)
|
||||
multi_json
|
||||
mustermann (~> 1.0)
|
||||
rack-protection (= 2.0.4)
|
||||
sinatra (= 2.0.4)
|
||||
tilt (>= 1.3, < 3)
|
||||
thread_safe (0.3.6)
|
||||
tilt (2.0.8)
|
||||
tzinfo (1.2.5)
|
||||
thread_safe (~> 0.1)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.7.5)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
dotenv
|
||||
http
|
||||
jwt
|
||||
sinatra
|
||||
sinatra-contrib
|
||||
|
||||
BUNDLED WITH
|
||||
1.16.4
|
||||
@@ -0,0 +1,3 @@
|
||||
require 'sinatra/activerecord'
|
||||
require 'sinatra/activerecord/rake'
|
||||
require './app'
|
||||
@@ -0,0 +1,95 @@
|
||||
require 'sinatra'
|
||||
require 'sinatra/json'
|
||||
require 'dotenv'
|
||||
Dotenv.load
|
||||
require 'openssl'
|
||||
require 'http'
|
||||
require 'jwt'
|
||||
require 'SecureRandom'
|
||||
|
||||
configure do
|
||||
# change to https://api.devicecheck.apple.com for production app, ie. App in App Store / Testflight
|
||||
set :device_check_api_url, 'https://api.development.devicecheck.apple.com'
|
||||
set :query_url, settings.device_check_api_url + '/v1/query_two_bits'
|
||||
set :update_url, settings.device_check_api_url + '/v1/update_two_bits'
|
||||
end
|
||||
|
||||
get '/' do
|
||||
"Please send the base 64 encoded device check token in JSON parameter key 'token' to POST /redeem"
|
||||
end
|
||||
|
||||
post '/redeem' do
|
||||
begin
|
||||
request_payload = JSON.parse request.body.read
|
||||
rescue JSON::ParserError
|
||||
return json({ message: 'please supply a valid token parameter', redeemable: false })
|
||||
end
|
||||
|
||||
# request_payload['token'] is the 'token' parameter we sent in the iOS app
|
||||
unless request_payload.key? 'token'
|
||||
return json({ message: 'please supply a token', redeemable: false })
|
||||
end
|
||||
|
||||
response = query_two_bits(request_payload['token'])
|
||||
|
||||
unless response.status == 200
|
||||
return json({ message: 'Error communicating with Apple server', redeemable: false })
|
||||
end
|
||||
|
||||
begin
|
||||
response_hash = JSON.parse response.body
|
||||
rescue JSON::ParserError
|
||||
# if status 200 and no json returned, means the state was not set previously, we set them to nil / null
|
||||
response_hash = { bit0: nil, bit1: nil }
|
||||
end
|
||||
|
||||
# if the bit0 has been set and set to true, means user has already redeemed using their phone
|
||||
if response_hash.key? 'bit0'
|
||||
if response_hash['bit0'] == true
|
||||
return json({ message: 'You have already redeemed it previously', redeemable: false })
|
||||
end
|
||||
end
|
||||
|
||||
# update the first bit to true, and tell the iOS app user can redeem the free gift
|
||||
update_two_bits(request_payload['token'], true, false)
|
||||
|
||||
json({ message: 'Congratulations!', redeemable: true })
|
||||
end
|
||||
|
||||
def jwt_token
|
||||
private_key = File.read(ENV['DEVICE_CHECK_KEY_FILE'])
|
||||
key_id = ENV['DEVICE_CHECK_KEY_ID']
|
||||
team_id = ENV['DEVICE_CHECK_TEAM_ID']
|
||||
|
||||
# Elliptic curve key, similar to login password, used for communication with apple server
|
||||
ec_key = OpenSSL::PKey::EC.new(private_key)
|
||||
jwt_token = JWT.encode({iss: team_id, iat: Time.now.to_i}, ec_key, 'ES256', {kid: key_id,})
|
||||
end
|
||||
|
||||
def query_two_bits(device_token)
|
||||
payload = {
|
||||
'device_token' => device_token,
|
||||
'timestamp' => (Time.now.to_f * 1000).to_i,
|
||||
'transaction_id' => SecureRandom.uuid
|
||||
}
|
||||
|
||||
response = HTTP.auth("Bearer #{jwt_token}").post(settings.query_url, json: payload)
|
||||
|
||||
# if there is no bit state set before, apple will return the string 'Bit State Not Found' instead of json
|
||||
|
||||
# if the bit state was set before, below will be returned
|
||||
#{"bit0":false,"bit1":false,"last_update_time":"2018-10"}
|
||||
end
|
||||
|
||||
def update_two_bits(device_token, bit_zero, bit_one)
|
||||
payload = {
|
||||
'device_token' => device_token,
|
||||
'timestamp' => (Time.now.to_f * 1000).to_i,
|
||||
'transaction_id' => SecureRandom.uuid,
|
||||
'bit0': bit_zero,
|
||||
'bit1': bit_one
|
||||
}
|
||||
|
||||
response = HTTP.auth("Bearer #{jwt_token}").post(settings.update_url, json: payload)
|
||||
# Apple will return status 200 with blank response body if the update is successful
|
||||
end
|
||||
Reference in New Issue
Block a user