peatio 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3697d323f43f4c9387766cae5aeb0a77acb29d47f933f84f95fa68ad95a4cf97
4
- data.tar.gz: f345354fdaa1eb1660394c7d2373ced0d34aa80e09a336cf4541fd6ce58a5511
3
+ metadata.gz: e0654fc5cd21783e8e22ca88db407d01f1c268032f09b347dfed2f49ca3aae8e
4
+ data.tar.gz: 3346353884ce7fd78c47d8b6f8bad7f2a3d360a4366ba1b1f635e8eb750b87ba
5
5
  SHA512:
6
- metadata.gz: fc2e62d12ef1bf3b58cbc378fe1af25dfc0fcd6a79270eff490bde22cf5deff4b9674d44e786be4fe943d699bb5ac16e062ba45a6d6b2a822a73c32213a6b182
7
- data.tar.gz: 18d134f48a68fcbdfb2598d5dd7da95c844668ec1c550fcd9535e1193631f83f2ace108a96257cb392e22c4b9cf17d99d160f465f8eac3bf144900c3dce0d16f
6
+ metadata.gz: d5d538caf05569e94c3f812d5f15cfa49a5cbcfa0d23c1ccbc2f76a3c66fbb56c8c4f0e83e61dba96c1550bdc9b7a816bd7c3093e5b0e50b6fc0f5cce47f34e7
7
+ data.tar.gz: 9a5304ac15012c2488aadb4990bc62c331bb7e7f0f8cbd3f044ad3dd7ae1a0cc51f524f062b0abd46f7df31a8003486bcd70b0d0d2b9690547ecc4b8bfb0916e
data/.gitignore CHANGED
@@ -6,6 +6,7 @@
6
6
  /pkg/
7
7
  /spec/reports/
8
8
  /tmp/
9
+ /*.gem
9
10
 
10
11
  # rspec failure tracking
11
12
  .rspec_status
data/Gemfile.lock CHANGED
@@ -3,9 +3,11 @@ PATH
3
3
  specs:
4
4
  peatio (0.2.0)
5
5
  amqp
6
+ bunny
6
7
  clamp
7
8
  em-websocket
8
9
  eventmachine
10
+ jwt
9
11
  mysql2
10
12
 
11
13
  GEM
@@ -16,49 +18,59 @@ GEM
16
18
  amq-protocol (>= 2.2.0)
17
19
  eventmachine
18
20
  ast (2.4.0)
21
+ bunny (2.11.0)
22
+ amq-protocol (~> 2.3.0)
23
+ bunny-mock (1.7.0)
24
+ bunny (>= 1.7)
19
25
  clamp (1.3.0)
20
26
  diff-lcs (1.3)
21
27
  docile (1.3.1)
28
+ em-spec (0.2.7)
29
+ eventmachine
22
30
  em-websocket (0.5.1)
23
31
  eventmachine (>= 0.12.9)
24
32
  http_parser.rb (~> 0.6.0)
33
+ em-websocket-client (0.1.2)
34
+ eventmachine
35
+ websocket
25
36
  eventmachine (1.2.7)
26
37
  http_parser.rb (0.6.0)
27
38
  jaro_winkler (1.5.1)
28
39
  json (2.1.0)
40
+ jwt (2.1.0)
29
41
  mysql2 (0.5.2)
30
42
  parallel (1.12.1)
31
- parser (2.5.1.0)
43
+ parser (2.5.1.2)
32
44
  ast (~> 2.4.0)
33
45
  powerpack (0.1.2)
34
46
  rainbow (3.0.0)
35
47
  rake (10.5.0)
36
- rspec (3.7.0)
37
- rspec-core (~> 3.7.0)
38
- rspec-expectations (~> 3.7.0)
39
- rspec-mocks (~> 3.7.0)
40
- rspec-core (3.7.1)
41
- rspec-support (~> 3.7.0)
42
- rspec-expectations (3.7.0)
48
+ rspec (3.8.0)
49
+ rspec-core (~> 3.8.0)
50
+ rspec-expectations (~> 3.8.0)
51
+ rspec-mocks (~> 3.8.0)
52
+ rspec-core (3.8.0)
53
+ rspec-support (~> 3.8.0)
54
+ rspec-expectations (3.8.1)
43
55
  diff-lcs (>= 1.2.0, < 2.0)
44
- rspec-support (~> 3.7.0)
45
- rspec-mocks (3.7.0)
56
+ rspec-support (~> 3.8.0)
57
+ rspec-mocks (3.8.0)
46
58
  diff-lcs (>= 1.2.0, < 2.0)
47
- rspec-support (~> 3.7.0)
48
- rspec-support (3.7.1)
59
+ rspec-support (~> 3.8.0)
60
+ rspec-support (3.8.0)
49
61
  rspec_junit_formatter (0.4.1)
50
62
  rspec-core (>= 2, < 4, != 2.12.0)
51
- rubocop (0.57.2)
63
+ rubocop (0.58.2)
52
64
  jaro_winkler (~> 1.5.1)
53
65
  parallel (~> 1.10)
54
- parser (>= 2.5)
66
+ parser (>= 2.5, != 2.5.1.1)
55
67
  powerpack (~> 0.1)
56
68
  rainbow (>= 2.2.2, < 4.0)
57
69
  ruby-progressbar (~> 1.7)
58
70
  unicode-display_width (~> 1.0, >= 1.0.1)
59
71
  rubocop-github (0.10.0)
60
72
  rubocop (~> 0.51)
61
- ruby-progressbar (1.9.0)
73
+ ruby-progressbar (1.10.0)
62
74
  simplecov (0.16.1)
63
75
  docile (~> 1.1)
64
76
  json (>= 1.8, < 3)
@@ -68,12 +80,16 @@ GEM
68
80
  json
69
81
  simplecov
70
82
  unicode-display_width (1.4.0)
83
+ websocket (1.2.8)
71
84
 
72
85
  PLATFORMS
73
86
  ruby
74
87
 
75
88
  DEPENDENCIES
76
89
  bundler (~> 1.16)
90
+ bunny-mock
91
+ em-spec
92
+ em-websocket-client
77
93
  peatio!
78
94
  rake (~> 10.0)
79
95
  rspec (~> 3.0)
data/lib/peatio.rb CHANGED
@@ -1,15 +1,19 @@
1
1
  require "logger"
2
2
  require "json"
3
3
  require "mysql2"
4
- require "amqp"
4
+ require "bunny"
5
5
  require "eventmachine"
6
6
  require "em-websocket"
7
7
 
8
- require "peatio/logger"
9
- require "peatio/version"
10
- require "peatio/sql/client"
11
- require "peatio/sql/schema"
12
- require "peatio/mq/client"
13
- require "peatio/mq/events"
14
- require "peatio/ranger"
15
- require "peatio/injectors/peatio_events"
8
+ module Peatio
9
+ require_relative "peatio/error"
10
+ require_relative "peatio/logger"
11
+ require_relative "peatio/version"
12
+ require_relative "peatio/sql/client"
13
+ require_relative "peatio/sql/schema"
14
+ require_relative "peatio/mq/client"
15
+ require_relative "peatio/mq/events"
16
+ require_relative "peatio/ranger"
17
+ require_relative "peatio/injectors/peatio_events"
18
+ require_relative "peatio/auth/jwt_authenticator"
19
+ end
@@ -0,0 +1,19 @@
1
+ module Peatio::Auth
2
+ # Error repesent all errors that can be returned from Auth module.
3
+ class Error < Peatio::Error
4
+ # @return [String, JWT::*] Reason store underlying reason for given error.
5
+ #
6
+ # @see https://github.com/jwt/ruby-jwt/blob/master/lib/jwt/error.rb List of JWT::* errors.
7
+ attr_reader :reason
8
+
9
+ def initialize(reason = nil)
10
+ @reason = reason
11
+
12
+ super(
13
+ code: 2001,
14
+ text: "Authorization failed: #{reason}",
15
+ status: 401,
16
+ )
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,127 @@
1
+ require "jwt"
2
+
3
+ require_relative "error"
4
+
5
+ module Peatio::Auth
6
+ # JWTAuthenticator used to authenticate user using JWT.
7
+ #
8
+ # It allows configuration of JWT verification through following ENV
9
+ # variables (all optional):
10
+ # * JWT_ISSUER
11
+ # * JWT_AUDIENCE
12
+ # * JWT_ALGORITHM (default: RS256)
13
+ # * JWT_DEFAULT_LEEWAY
14
+ # * JWT_ISSUED_AT_LEEWAY
15
+ # * JWT_EXPIRATION_LEEWAY
16
+ # * JWT_NOT_BEFORE_LEEWAY
17
+ #
18
+ # @see https://github.com/jwt/ruby-jwt JWT validation parameters.
19
+ #
20
+ # @example Token validation
21
+ # rsa_private = OpenSSL::PKey::RSA.generate(2048)
22
+ # rsa_public = rsa_private.public_key
23
+ #
24
+ # payload = {
25
+ # iat: Time.now.to_i,
26
+ # exp: (Time.now + 60).to_i,
27
+ # sub: "session",
28
+ # iss: "barong",
29
+ # aud: [
30
+ # "peatio",
31
+ # "barong",
32
+ # ],
33
+ # jti: "BEF5617B7B2762DDE61702F5",
34
+ # uid: "TEST123",
35
+ # email: "user@example.com",
36
+ # role: "admin",
37
+ # level: 4,
38
+ # state: "active",
39
+ # }
40
+ #
41
+ # token = JWT.encode(payload, rsa_private, "RS256")
42
+ #
43
+ # auth = Peatio::Auth::JWTAuthenticator.new(rsa_public)
44
+ # auth.authenticate!("Bearer #{token}")
45
+ class JWTAuthenticator
46
+ @@verify_options = {
47
+ verify_expiration: true,
48
+ verify_not_before: true,
49
+ iss: ENV["JWT_ISSUER"],
50
+ verify_iss: !ENV["JWT_ISSUER"].nil?,
51
+ verify_iat: true,
52
+ verify_jti: true,
53
+ aud: ENV["JWT_AUDIENCE"].to_s.split(",").reject(&:empty?),
54
+ verify_aud: !ENV["JWT_AUDIENCE"].nil?,
55
+ sub: "session",
56
+ verify_sub: true,
57
+ algorithms: [ENV["JWT_ALGORITHM"] || "RS256"],
58
+ leeway: ENV["JWT_DEFAULT_LEEWAY"].yield_self { |n| n.to_i unless n.nil? },
59
+ iat_leeway: ENV["JWT_ISSUED_AT_LEEWAY"].yield_self { |n| n.to_i unless n.nil? },
60
+ exp_leeway: ENV["JWT_EXPIRATION_LEEWAY"].yield_self { |n| n.to_i unless n.nil? },
61
+ nbf_leeway: ENV["JWT_NOT_BEFORE_LEEWAY"].yield_self { |n| n.to_i unless n.nil? },
62
+ }.compact
63
+
64
+ @@encode_options = {
65
+ algorithm: @@verify_options[:algorithms].first,
66
+ }.compact
67
+
68
+ # Creates new authenticator with given public key.
69
+ #
70
+ # @param public_key [OpenSSL::PKey::PKey] Public key object to verify
71
+ # signature.
72
+ # @param private_key [OpenSSL::PKey::PKey] Optional private key that used
73
+ # only to encode new tokens.
74
+ def initialize(public_key, private_key = nil)
75
+ @public_key = public_key
76
+ @private_key = private_key
77
+ end
78
+
79
+ # Decodes and verifies JWT.
80
+ # Returns payload from JWT or raises an exception
81
+ #
82
+ # @param token [String] Token string. Must start from <tt>"Bearer "</tt>.
83
+ # @return [Hash] Payload Hash from JWT without any changes.
84
+ #
85
+ # @raise [Peatio::Auth::Error] If token is invalid or can't be verified.
86
+ def authenticate!(token)
87
+ token_type, token_value = token.to_s.split(" ")
88
+
89
+ unless token_type == "Bearer"
90
+ raise(Peatio::Auth::Error, "Token type is not provided or invalid.")
91
+ end
92
+
93
+ decode_and_verify_token(token_value)
94
+ rescue => error
95
+ case error
96
+ when Peatio::Auth::Error
97
+ raise(error)
98
+ else
99
+ raise(Peatio::Auth::Error, e.inspect)
100
+ end
101
+ end
102
+
103
+ # Encodes given payload and produces JWT.
104
+ #
105
+ # @param payload [String, Hash] Payload to encode.
106
+ # @return [String] JWT token string.
107
+ #
108
+ # @raise [ArgumentError] If no private key was passed to constructor.
109
+ def encode(payload)
110
+ raise(::ArgumentError, "No private key given.") if @private_key.nil?
111
+
112
+ JWT.encode(payload, @private_key, @@encode_options[:algorithm])
113
+ end
114
+
115
+ private
116
+
117
+ def decode_and_verify_token(token)
118
+ payload, header = JWT.decode(token, @public_key, true, @@verify_options)
119
+
120
+ payload.keys.each { |k| payload[k.to_sym] = payload.delete(k) }
121
+
122
+ payload
123
+ rescue JWT::DecodeError => e
124
+ raise(Peatio::Auth::Error, "Failed to decode and verify JWT: #{e.inspect}.")
125
+ end
126
+ end
127
+ end
@@ -1,9 +1,19 @@
1
1
  module Peatio::Command::Service
2
-
3
2
  class Start < Peatio::Command::Base
4
3
  class Ranger < Peatio::Command::Base
5
4
  def execute
6
- ::Peatio::Ranger.run!
5
+ if ENV["JWT_PUBLIC_KEY"].nil?
6
+ raise ArgumentError, "JWT_PUBLIC_KEY was not specified."
7
+ end
8
+
9
+ key_decoded = Base64.urlsafe_decode64(ENV["JWT_PUBLIC_KEY"])
10
+
11
+ jwt_public_key = OpenSSL::PKey.read(key_decoded)
12
+ if jwt_public_key.private?
13
+ raise ArgumentError, "JWT_PUBLIC_KEY was set to private key, however it should be public."
14
+ end
15
+
16
+ ::Peatio::Ranger.run!(jwt_public_key)
7
17
  end
8
18
  end
9
19
 
@@ -13,5 +23,4 @@ module Peatio::Command::Service
13
23
  class Root < Peatio::Command::Base
14
24
  subcommand "start", "Start a service", Start
15
25
  end
16
-
17
26
  end
@@ -0,0 +1,27 @@
1
+ class Peatio::Error < ::StandardError
2
+ @@default_code = 2000
3
+ @@default_status = 400
4
+
5
+ attr :code, :text
6
+
7
+ def initialize(opts = {})
8
+ @code = opts[:code] || @@default_code
9
+ @text = opts[:text] || ""
10
+
11
+ @status = opts[:status] || @@default_status
12
+ @message = {error: {code: @code, message: @text}}
13
+
14
+ if @text != ""
15
+ super("#{@code}: #{text}")
16
+ else
17
+ super("#{@code}")
18
+ end
19
+ end
20
+
21
+ def inspect
22
+ message = @text
23
+ message += " (#{@reason})" unless @reason.nil?
24
+
25
+ %[#<#{self.class.name}: #{message}>]
26
+ end
27
+ end
@@ -13,21 +13,18 @@ module Peatio::Injectors
13
13
 
14
14
  EventMachine.run do
15
15
  Peatio::MQ::Client.new
16
- AMQP::Exchange.new(Peatio::MQ::Client.channel, :topic, @exchange_name) do |exchange, declare_ok|
17
- logger.info "Exchange #{exchange.name} is ready to go"
18
- next_message(exchange)
19
- end
16
+ Peatio::MQ::Client.connect!
17
+ Peatio::MQ::Client.create_channel!
18
+ inject_message
20
19
  end
21
20
  end
22
21
 
23
- def next_message(exchange)
22
+ def inject_message
24
23
  if message = @messages.shift
25
- event_name, data = message
26
- serialized_data = JSON.dump(data)
27
- exchange.publish(serialized_data, routing_key: event_name) do
28
- logger.debug { "event #{event_name} sent with data: #{serialized_data}" }
29
- next_message(exchange)
30
- end
24
+ type, id, event, data = message
25
+ Peatio::MQ::Events.publish(type, id, event, data) {
26
+ inject_message
27
+ }
31
28
  else
32
29
  Peatio::MQ::Client.disconnect { EventMachine.stop }
33
30
  end
@@ -35,6 +32,7 @@ module Peatio::Injectors
35
32
 
36
33
  def create_messages
37
34
  [
35
+ private_trade,
38
36
  order_created,
39
37
  order_canceled,
40
38
  order_completed,
@@ -50,141 +48,162 @@ module Peatio::Injectors
50
48
  def updated_at
51
49
  Time.now
52
50
  end
51
+
53
52
  alias :completed_at :updated_at
54
- alias :canceled_at :updated_at
53
+ alias :canceled_at :updated_at
54
+
55
+ def private_trade
56
+ [
57
+ "private",
58
+ "debug_user",
59
+ "trade",
60
+ {
61
+ trade: "some-data",
62
+ },
63
+ ]
64
+ end
55
65
 
56
66
  def order_created
57
67
  [
58
- "#{market}.order_created",
68
+ "public",
69
+ market,
70
+ "order_created",
59
71
  {
60
- market: "#{market}",
61
- type: "buy",
62
- trader_uid: buyer_uid,
63
- income_unit: "btc",
64
- income_fee_type: "relative",
65
- income_fee_value: "0.0015",
66
- outcome_unit: "usd",
67
- outcome_fee_type: "relative",
68
- outcome_fee_value: "0.0",
69
- initial_income_amount: "14.0",
70
- current_income_amount: "14.0",
72
+ market: "#{market}",
73
+ type: "buy",
74
+ trader_uid: buyer_uid,
75
+ income_unit: "btc",
76
+ income_fee_type: "relative",
77
+ income_fee_value: "0.0015",
78
+ outcome_unit: "usd",
79
+ outcome_fee_type: "relative",
80
+ outcome_fee_value: "0.0",
81
+ initial_income_amount: "14.0",
82
+ current_income_amount: "14.0",
71
83
  initial_outcome_amount: "0.42",
72
84
  current_outcome_amount: "0.42",
73
- strategy: "limit",
74
- price: "0.03",
75
- state: "open",
76
- trades_count: 0,
77
- created_at: created_at.iso8601
78
- }
85
+ strategy: "limit",
86
+ price: "0.03",
87
+ state: "open",
88
+ trades_count: 0,
89
+ created_at: created_at.iso8601,
90
+ },
79
91
  ]
80
92
  end
81
93
 
82
94
  def order_canceled
83
95
  [
84
- "#{market}.order_canceled",
96
+ "public",
97
+ market,
98
+ "order_canceled",
85
99
  {
86
- market: "#{market}",
87
- type: "sell",
88
- trader_uid: seller_uid,
89
- income_unit: "usd",
90
- income_fee_type: "relative",
91
- income_fee_value: "0.0015",
92
- outcome_unit: "btc",
93
- outcome_fee_type: "relative",
94
- outcome_fee_value: "0.0",
95
- initial_income_amount: "3.0",
96
- current_income_amount: "3.0",
97
- initial_outcome_amount: "100.0",
98
- current_outcome_amount: "100.0",
99
- strategy: "limit",
100
- price: "0.03",
101
- state: "canceled",
102
- trades_count: 0,
103
- created_at: created_at.iso8601,
104
- canceled_at: canceled_at.iso8601
105
- }
100
+ market: "#{market}",
101
+ type: "sell",
102
+ trader_uid: seller_uid,
103
+ income_unit: "usd",
104
+ income_fee_type: "relative",
105
+ income_fee_value: "0.0015",
106
+ outcome_unit: "btc",
107
+ outcome_fee_type: "relative",
108
+ outcome_fee_value: "0.0",
109
+ initial_income_amount: "3.0",
110
+ current_income_amount: "3.0",
111
+ initial_outcome_amount: "100.0",
112
+ current_outcome_amount: "100.0",
113
+ strategy: "limit",
114
+ price: "0.03",
115
+ state: "canceled",
116
+ trades_count: 0,
117
+ created_at: created_at.iso8601,
118
+ canceled_at: canceled_at.iso8601,
119
+ },
106
120
  ]
107
121
  end
108
122
 
109
123
  def order_completed
110
124
  [
111
- "#{market}.order_completed", {
112
- market: "#{market}",
113
- type: "sell",
114
- trader_uid: seller_uid,
115
- income_unit: "usd",
116
- income_fee_type: "relative",
117
- income_fee_value: "0.0015",
118
- outcome_unit: "btc",
119
- outcome_fee_type: "relative",
120
- outcome_fee_value: "0.0",
121
- initial_income_amount: "3.0",
122
- current_income_amount: "0.0",
123
- previous_income_amount: "3.0",
124
- initial_outcome_amount: "100.0",
125
- current_outcome_amount: "0.0",
125
+ "public",
126
+ market,
127
+ "order_completed", {
128
+ market: "#{market}",
129
+ type: "sell",
130
+ trader_uid: seller_uid,
131
+ income_unit: "usd",
132
+ income_fee_type: "relative",
133
+ income_fee_value: "0.0015",
134
+ outcome_unit: "btc",
135
+ outcome_fee_type: "relative",
136
+ outcome_fee_value: "0.0",
137
+ initial_income_amount: "3.0",
138
+ current_income_amount: "0.0",
139
+ previous_income_amount: "3.0",
140
+ initial_outcome_amount: "100.0",
141
+ current_outcome_amount: "0.0",
126
142
  previous_outcome_amount: "100.0",
127
- strategy: "limit",
128
- price: "0.03",
129
- state: "completed",
130
- trades_count: 1,
131
- created_at: created_at.iso8601,
132
- completed_at: completed_at.iso8601
133
- }
143
+ strategy: "limit",
144
+ price: "0.03",
145
+ state: "completed",
146
+ trades_count: 1,
147
+ created_at: created_at.iso8601,
148
+ completed_at: completed_at.iso8601,
149
+ },
134
150
  ]
135
151
  end
136
152
 
137
153
  def order_updated
138
154
  [
139
- "#{market}.order_updated", {
140
- market: "#{market}",
141
- type: "sell",
142
- trader_uid: seller_uid,
143
- income_unit: "usd",
144
- income_fee_type: "relative",
145
- income_fee_value: "0.0015",
146
- outcome_unit: "btc",
147
- outcome_fee_type: "relative",
148
- outcome_fee_value: "0.0",
149
- initial_income_amount: "3.0",
150
- current_income_amount: "2.4",
151
- previous_income_amount: "3.0",
152
- initial_outcome_amount: "100.0",
153
- current_outcome_amount: "80.0",
155
+ "public",
156
+ market,
157
+ "order_updated", {
158
+ market: "#{market}",
159
+ type: "sell",
160
+ trader_uid: seller_uid,
161
+ income_unit: "usd",
162
+ income_fee_type: "relative",
163
+ income_fee_value: "0.0015",
164
+ outcome_unit: "btc",
165
+ outcome_fee_type: "relative",
166
+ outcome_fee_value: "0.0",
167
+ initial_income_amount: "3.0",
168
+ current_income_amount: "2.4",
169
+ previous_income_amount: "3.0",
170
+ initial_outcome_amount: "100.0",
171
+ current_outcome_amount: "80.0",
154
172
  previous_outcome_amount: "100.0",
155
- strategy: "limit",
156
- price: "0.03",
157
- state: "open",
158
- trades_count: 1,
159
- created_at: created_at.iso8601,
160
- updated_at: updated_at.iso8601
161
- }
173
+ strategy: "limit",
174
+ price: "0.03",
175
+ state: "open",
176
+ trades_count: 1,
177
+ created_at: created_at.iso8601,
178
+ updated_at: updated_at.iso8601,
179
+ },
162
180
  ]
163
181
  end
164
182
 
165
183
  def trade_completed
166
184
  [
167
- "#{market}.trade_completed", {
168
- market: "#{market}",
169
- price: "0.03",
170
- buyer_uid: buyer_uid,
171
- buyer_income_unit: "btc",
172
- buyer_income_amount: "14.0",
173
- buyer_income_fee: "0.021",
174
- buyer_outcome_unit: "usd",
175
- buyer_outcome_amount: "0.42",
176
- buyer_outcome_fee: "0.0",
177
- seller_uid: seller_uid,
178
- seller_income_unit: "usd",
179
- seller_income_amount: "0.42",
180
- seller_income_fee: "0.00063",
181
- seller_outcome_unit: "btc",
185
+ "public",
186
+ market,
187
+ "trade_completed", {
188
+ market: "#{market}",
189
+ price: "0.03",
190
+ buyer_uid: buyer_uid,
191
+ buyer_income_unit: "btc",
192
+ buyer_income_amount: "14.0",
193
+ buyer_income_fee: "0.021",
194
+ buyer_outcome_unit: "usd",
195
+ buyer_outcome_amount: "0.42",
196
+ buyer_outcome_fee: "0.0",
197
+ seller_uid: seller_uid,
198
+ seller_income_unit: "usd",
199
+ seller_income_amount: "0.42",
200
+ seller_income_fee: "0.00063",
201
+ seller_outcome_unit: "btc",
182
202
  seller_outcome_amount: "14.0",
183
- seller_outcome_fee: "0.0",
184
- completed_at: completed_at.iso8601
185
- }
203
+ seller_outcome_fee: "0.0",
204
+ completed_at: completed_at.iso8601,
205
+ },
186
206
  ]
187
207
  end
188
-
189
208
  end
190
209
  end
@@ -1,23 +1,29 @@
1
1
  module Peatio::MQ
2
2
  class Client
3
3
  class << self
4
- attr_reader :channel, :connection
4
+ attr_accessor :channel, :connection
5
5
 
6
6
  def new
7
- options = {
7
+ @options = {
8
8
  host: ENV["RABBITMQ_HOST"] || "0.0.0.0",
9
9
  port: ENV["RABBITMQ_PORT"] || "5672",
10
10
  username: ENV["RABBITMQ_USER"],
11
11
  password: ENV["RABBITMQ_PASSWORD"],
12
12
  }
13
- @connection = AMQP.connect(options)
14
- @channel = AMQP::Channel.new(@connection)
13
+ end
14
+
15
+ def connect!
16
+ @connection = Bunny.new(@options)
17
+ @connection.start
18
+ end
19
+
20
+ def create_channel!
21
+ @channel = @connection.create_channel
15
22
  end
16
23
 
17
24
  def disconnect
18
- connection.close do
19
- yield
20
- end
25
+ @connection.close
26
+ yield if block_given?
21
27
  end
22
28
  end
23
29
  end
@@ -1,26 +1,47 @@
1
+ require "socket"
2
+
1
3
  module Peatio::MQ::Events
2
4
  def self.subscribe!
3
5
  ranger = RangerEvents.new
4
6
  ranger.subscribe
5
7
  end
6
8
 
7
- def self.start!
9
+ def self.publish(type, id, event, payload)
10
+ @@client ||= begin
11
+ ranger = RangerEvents.new
12
+ ranger.connect!
13
+ ranger
14
+ end
15
+
16
+ @@client.publish(type, id, event, payload) do
17
+ yield if block_given?
18
+ end
8
19
  end
9
20
 
10
- class SocketHandler
11
- attr_accessor :event
21
+ class Client
22
+ attr_accessor :streams, :authorized, :user
12
23
 
13
24
  @@all = []
14
25
 
15
- class << self
16
- def all
17
- @@all
26
+ def self.all
27
+ @@all
28
+ end
29
+
30
+ def self.user(user)
31
+ @@all.each do |handler|
32
+ if handler.user == user
33
+ yield handler
34
+ end
18
35
  end
19
36
  end
20
37
 
21
- def initialize(socket, event)
38
+ def initialize(socket, streams)
22
39
  @socket = socket
23
- @event = event
40
+ @streams = streams
41
+
42
+ @user = ""
43
+ @authorized = false
44
+
24
45
  @@all << self
25
46
  end
26
47
 
@@ -36,9 +57,22 @@ module Peatio::MQ::Events
36
57
  @exchange_name = "peatio.events.market"
37
58
  end
38
59
 
39
- def subscribe
40
- require "socket"
60
+ def connect!
61
+ @exchange = Peatio::MQ::Client.channel.topic(@exchange_name)
62
+ end
63
+
64
+ def publish(type, id, event, payload)
65
+ routing_key = [type, id, event].join(".")
66
+ serialized_data = JSON.dump(payload)
67
+
68
+ @exchange.publish(serialized_data, routing_key: routing_key)
69
+
70
+ Peatio::Logger::debug { "published event to #{routing_key} " }
41
71
 
72
+ yield if block_given?
73
+ end
74
+
75
+ def subscribe
42
76
  exchange = Peatio::MQ::Client.channel.topic(@exchange_name)
43
77
 
44
78
  suffix = "#{Socket.gethostname.split(/-/).last}#{Random.rand(10_000)}"
@@ -47,19 +81,42 @@ module Peatio::MQ::Events
47
81
 
48
82
  Peatio::MQ::Client.channel
49
83
  .queue(queue_name, durable: false, auto_delete: true)
50
- .bind(exchange, routing_key: "#").subscribe do |metadata, payload|
84
+ .bind(exchange, routing_key: "#").subscribe do |delivery_info, metadata, payload|
85
+
86
+ # type@id@event
87
+ # type can be public|private
88
+ # id can be user id or market
89
+ # event can be anything like order_completed or just trade
51
90
 
52
- Peatio::Logger.debug { "event received: #{payload}" }
91
+ routing_key = delivery_info.routing_key
92
+ if routing_key.count(".") != 2
93
+ Peatio::Logger::error do
94
+ "got invalid routing key from amqp: #{routing_key}"
95
+ end
96
+
97
+ next
98
+ end
53
99
 
54
- event = metadata.routing_key
100
+ type, id, event = routing_key.split(".")
55
101
 
56
- SocketHandler.all.each do |handler|
57
- if event == handler.event
102
+ if type == "private"
103
+ Client.user(id) do |client|
104
+ if client.streams.include?(event)
105
+ client.send_payload payload
106
+ end
107
+ end
108
+
109
+ next
110
+ end
111
+
112
+ stream = [id, event].join(".")
113
+
114
+ Client.all.each do |handler|
115
+ if handler.streams.include?(stream)
58
116
  handler.send_payload payload
59
117
  end
60
118
  end
61
119
  end
62
120
  end
63
121
  end
64
-
65
122
  end
data/lib/peatio/ranger.rb CHANGED
@@ -1,31 +1,106 @@
1
1
  module Peatio::Ranger
2
- def run!
2
+ class Connection
3
+ def initialize(authenticator, socket, logger)
4
+ @authenticator = authenticator
5
+ @socket = socket
6
+ @logger = logger
7
+ @streams = []
8
+ end
9
+
10
+ def send(method, data)
11
+ payload = JSON.dump(method => data)
12
+ @logger.debug { payload }
13
+ @socket.send payload
14
+ end
15
+
16
+ def handle(msg)
17
+ authorized = false
18
+ begin
19
+ data = JSON.parse(msg)
20
+
21
+ token = data["jwt"]
22
+
23
+ payload = @authenticator.authenticate!(token)
24
+
25
+ authorized = true
26
+ rescue JSON::ParserError
27
+ rescue => error
28
+ @logger.error error.message
29
+ end
30
+
31
+ if !authorized
32
+ send :error, message: "Authentication failed."
33
+ return
34
+ end
35
+
36
+ @client.user = payload[:uid]
37
+ @client.authorized = true
38
+
39
+ @logger.info "ranger: user #{@client.user} authenticated #{@streams}"
40
+
41
+ send :success, message: "Authenticated."
42
+ end
43
+
44
+ def handshake(handshake)
45
+ query = URI::decode_www_form(handshake.query_string)
46
+
47
+ @streams = query.map do |item|
48
+ if item.first == "stream"
49
+ item.last
50
+ end
51
+ end
52
+
53
+ @logger.info "ranger: WebSocket connection openned, streams: #{@streams}"
54
+
55
+ @client = Peatio::MQ::Events::Client.new(
56
+ @socket, @streams,
57
+ )
58
+
59
+ @socket.instance_variable_set(:@connection_handler, @client)
60
+ end
61
+ end
62
+
63
+ def self.run!(jwt_public_key)
64
+ host = ENV["RANGER_HOST"] || "0.0.0.0"
65
+ port = ENV["RANGER_PORT"] || "8081"
66
+
67
+ authenticator = Peatio::Auth::JWTAuthenticator.new(jwt_public_key)
68
+
3
69
  logger = Peatio::Logger.logger
4
- port = 8081
5
70
  logger.info "Starting the server on port #{port}"
6
71
 
7
72
  EM.run do
8
73
  Peatio::MQ::Client.new
74
+ Peatio::MQ::Client.connect!
75
+ Peatio::MQ::Client.create_channel!
76
+
9
77
  Peatio::MQ::Events.subscribe!
10
78
 
11
- EM::WebSocket.start(host: "0.0.0.0", port: port) do |ws|
12
- ws.onopen do |id|
13
- logger.info "ranger: WebSocket connection openned"
79
+ EM::WebSocket.start(
80
+ host: host,
81
+ port: port,
82
+ secure: true,
83
+ ) do |socket|
84
+ connection = Connection.new(authenticator, socket, logger)
85
+
86
+ socket.onopen do |handshake|
87
+ connection.handshake(handshake)
88
+ end
14
89
 
15
- ws.instance_variable_set(
16
- :@connection_handler,
17
- Peatio::MQ::Events::SocketHandler.new(ws, "eurusd.order_created")
18
- )
90
+ socket.onmessage do |msg|
91
+ connection.handle(msg)
19
92
  end
20
93
 
21
- ws.onclose { logger.info "ranger: WebSocket connection closed" }
94
+ socket.onclose do
95
+ logger.info "ranger: WebSocket connection closed"
96
+ end
22
97
 
23
- ws.onerror { |e|
24
- puts "ranger: WebSocket Error: #{e.message}"
25
- }
98
+ socket.onerror do |e|
99
+ logger.error "ranger: WebSocket Error: #{e.message}"
100
+ end
26
101
  end
102
+
103
+ yield if block_given?
27
104
  end
28
105
  end
29
-
30
- module_function :run!
31
106
  end
@@ -1,3 +1,3 @@
1
1
  module Peatio
2
- VERSION = '0.2.0'
2
+ VERSION = "0.3.0"
3
3
  end
data/peatio.gemspec CHANGED
@@ -4,23 +4,22 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
  require "peatio/version"
5
5
 
6
6
  Gem::Specification.new do |spec|
7
- spec.name = "peatio"
8
- spec.version = Peatio::VERSION
9
- spec.authors = ["Louis B.", "Camille M."]
10
- spec.email = ["lbellet@heliostech.fr"]
11
-
12
- spec.summary = %q{Peatio is a gem for running critical core services}
13
- spec.description = %q{Peatio gem contains microservices and command line tools}
14
- spec.homepage = "https://www.peatio.tech"
7
+ spec.name = "peatio"
8
+ spec.version = Peatio::VERSION
9
+ spec.authors = ["Louis B.", "Camille M."]
10
+ spec.email = ["lbellet@heliostech.fr"]
15
11
 
12
+ spec.summary = %q{Peatio is a gem for running critical core services}
13
+ spec.description = %q{Peatio gem contains microservices and command line tools}
14
+ spec.homepage = "https://www.peatio.tech"
16
15
 
17
16
  # Specify which files should be added to the gem when it is released.
18
17
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
19
- spec.files = Dir.chdir(File.expand_path("..", __FILE__)) do
18
+ spec.files = Dir.chdir(File.expand_path("..", __FILE__)) do
20
19
  `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
21
20
  end
22
- spec.bindir = "bin"
23
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
21
+ spec.bindir = "bin"
22
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
24
23
  spec.require_paths = ["lib"]
25
24
 
26
25
  spec.add_dependency "clamp"
@@ -28,9 +27,14 @@ Gem::Specification.new do |spec|
28
27
  spec.add_dependency "eventmachine"
29
28
  spec.add_dependency "em-websocket"
30
29
  spec.add_dependency "mysql2"
30
+ spec.add_dependency "jwt"
31
+ spec.add_dependency "bunny"
31
32
  spec.add_development_dependency "bundler", "~> 1.16"
32
33
  spec.add_development_dependency "rake", "~> 10.0"
33
34
  spec.add_development_dependency "rspec", "~> 3.0"
35
+ spec.add_development_dependency "em-spec"
36
+ spec.add_development_dependency "em-websocket-client"
37
+ spec.add_development_dependency "bunny-mock"
34
38
  spec.add_development_dependency "simplecov"
35
39
  spec.add_development_dependency "simplecov-json"
36
40
  spec.add_development_dependency "rspec_junit_formatter"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: peatio
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Louis B.
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2018-08-09 00:00:00.000000000 Z
12
+ date: 2018-08-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: clamp
@@ -81,6 +81,34 @@ dependencies:
81
81
  - - ">="
82
82
  - !ruby/object:Gem::Version
83
83
  version: '0'
84
+ - !ruby/object:Gem::Dependency
85
+ name: jwt
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ type: :runtime
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ - !ruby/object:Gem::Dependency
99
+ name: bunny
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ type: :runtime
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
84
112
  - !ruby/object:Gem::Dependency
85
113
  name: bundler
86
114
  requirement: !ruby/object:Gem::Requirement
@@ -123,6 +151,48 @@ dependencies:
123
151
  - - "~>"
124
152
  - !ruby/object:Gem::Version
125
153
  version: '3.0'
154
+ - !ruby/object:Gem::Dependency
155
+ name: em-spec
156
+ requirement: !ruby/object:Gem::Requirement
157
+ requirements:
158
+ - - ">="
159
+ - !ruby/object:Gem::Version
160
+ version: '0'
161
+ type: :development
162
+ prerelease: false
163
+ version_requirements: !ruby/object:Gem::Requirement
164
+ requirements:
165
+ - - ">="
166
+ - !ruby/object:Gem::Version
167
+ version: '0'
168
+ - !ruby/object:Gem::Dependency
169
+ name: em-websocket-client
170
+ requirement: !ruby/object:Gem::Requirement
171
+ requirements:
172
+ - - ">="
173
+ - !ruby/object:Gem::Version
174
+ version: '0'
175
+ type: :development
176
+ prerelease: false
177
+ version_requirements: !ruby/object:Gem::Requirement
178
+ requirements:
179
+ - - ">="
180
+ - !ruby/object:Gem::Version
181
+ version: '0'
182
+ - !ruby/object:Gem::Dependency
183
+ name: bunny-mock
184
+ requirement: !ruby/object:Gem::Requirement
185
+ requirements:
186
+ - - ">="
187
+ - !ruby/object:Gem::Version
188
+ version: '0'
189
+ type: :development
190
+ prerelease: false
191
+ version_requirements: !ruby/object:Gem::Requirement
192
+ requirements:
193
+ - - ">="
194
+ - !ruby/object:Gem::Version
195
+ version: '0'
126
196
  - !ruby/object:Gem::Dependency
127
197
  name: simplecov
128
198
  requirement: !ruby/object:Gem::Requirement
@@ -202,12 +272,15 @@ files:
202
272
  - bin/peatio
203
273
  - bin/setup
204
274
  - lib/peatio.rb
275
+ - lib/peatio/auth/error.rb
276
+ - lib/peatio/auth/jwt_authenticator.rb
205
277
  - lib/peatio/command/amqp.rb
206
278
  - lib/peatio/command/base.rb
207
279
  - lib/peatio/command/db.rb
208
280
  - lib/peatio/command/inject.rb
209
281
  - lib/peatio/command/root.rb
210
282
  - lib/peatio/command/service.rb
283
+ - lib/peatio/error.rb
211
284
  - lib/peatio/executor.rb
212
285
  - lib/peatio/injectors/peatio_events.rb
213
286
  - lib/peatio/logger.rb