peatio 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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