-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathapp.rb
More file actions
122 lines (97 loc) · 3.89 KB
/
app.rb
File metadata and controls
122 lines (97 loc) · 3.89 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
require 'sinatra'
require 'sinatra/json'
unless ENV['RACK_ENV'] == 'production'
require 'dotenv'
Dotenv.load
end
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! 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