122 lines
3.9 KiB
Ruby
122 lines
3.9 KiB
Ruby
unless ENV['RACK_ENV'] == 'production'
|
|
require 'dotenv'
|
|
Dotenv.load
|
|
end
|
|
require 'openssl'
|
|
require 'http'
|
|
require 'jwt'
|
|
require 'securerandom'
|
|
|
|
require 'sinatra'
|
|
require 'sinatra/json'
|
|
|
|
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! You have redeemed the reward', redeemable: true })
|
|
end
|
|
|
|
post '/reset' do
|
|
begin
|
|
request_payload = JSON.parse request.body.read
|
|
rescue JSON::ParserError
|
|
return json({ message: 'please supply a valid token parameter' })
|
|
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' })
|
|
end
|
|
|
|
# reset the first bit to false
|
|
update_two_bits(request_payload['token'], false, false)
|
|
|
|
json({ message: 'First bit reseted to false, you can redeem reward now' })
|
|
end
|
|
|
|
def jwt_token
|
|
private_key = ENV['DEVICE_CHECK_KEY_STRING']
|
|
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 |