forward 0.0.14 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -39,6 +39,14 @@ module Forward
39
39
  ENV['FORWARD_SSH_PORT'] || DEFAULT_SSH_PORT
40
40
  end
41
41
 
42
+ def self.config=(config)
43
+ @config = config
44
+ end
45
+
46
+ def self.config
47
+ @config
48
+ end
49
+
42
50
  def self.client=(client)
43
51
  @client = client
44
52
  end
@@ -1,6 +1,6 @@
1
1
  require 'forward/api/resource'
2
2
  require 'forward/api/client_log'
3
- require 'forward/api/public_key'
3
+ require 'forward/api/tunnel_key'
4
4
  require 'forward/api/tunnel'
5
5
  require 'forward/api/user'
6
6
 
@@ -7,22 +7,14 @@ module Forward
7
7
  resource.uri = '/api/client_logs'
8
8
  params = {
9
9
  :client => Forward.client_string,
10
- :log => log,
10
+ :log => log
11
11
  }
12
12
 
13
- params[:user_id] = user_id unless user_id.nil?
13
+ params[:api_token] = Forward.config.api_token unless Forward.config.nil?
14
14
 
15
15
  resource.post(params)
16
16
  end
17
17
 
18
- private
19
-
20
- def self.user_id
21
- if !Forward.client.nil? && !Forward.client.config.nil?
22
- Forward.client.config.id
23
- end
24
- end
25
-
26
18
  end
27
19
  end
28
20
  end
@@ -62,8 +62,8 @@ module Forward
62
62
  def self.destroy_and_create(id)
63
63
  Forward.log(:debug, "Destroying tunnel: #{id}")
64
64
  destroy(id)
65
- puts "tunnel removed, creating your new forward"
66
- create
65
+ puts "tunnel removed, now we're creating a new one"
66
+ create(Forward.client.options)
67
67
  end
68
68
 
69
69
  def self.create_error(errors)
@@ -1,10 +1,10 @@
1
1
  module Forward
2
2
  module Api
3
- class PublicKey < Resource
3
+ class TunnelKey < Resource
4
4
 
5
5
  def self.create
6
- resource = PublicKey.new(:create)
7
- resource.uri = '/api/public_keys'
6
+ resource = TunnelKey.new(:create)
7
+ resource.uri = '/api/tunnel_keys'
8
8
 
9
9
  response = resource.post
10
10
 
@@ -193,16 +193,12 @@ module Forward
193
193
  #
194
194
  # Returns a Hash with the email and password.
195
195
  def self.authenticate
196
- if ask('Already have an account with Forward? ').chomp =~ /\An/i
197
- Client.cleanup_and_exit!("You'll need a Forward account first. You can create one at \033[04mhttps://forwardhq.com\033[04m")
198
- else
199
- puts 'Enter your email and password'
200
- email = ask('email: ').chomp
201
- password = ask('password: ') { |q| q.echo = false }.chomp
202
- Forward.log(:debug, "Authenticating User: `#{email}:#{password.gsub(/./, 'x')}'")
203
-
204
- { :email => email, :password => password }
205
- end
196
+ puts 'Enter your email and password'
197
+ email = ask('email: ').chomp
198
+ password = ask('password: ') { |q| q.echo = false }.chomp
199
+ Forward.log(:debug, "Authenticating User: `#{email}:#{password.gsub(/./, 'x')}'")
200
+
201
+ { :email => email, :password => password }
206
202
  end
207
203
 
208
204
  # Parses various options and arguments, validates everything to ensure
@@ -53,17 +53,18 @@ module Forward
53
53
  trap(:INT) { cleanup_and_exit!('closing tunnel and exiting...') }
54
54
 
55
55
  Forward.client = @client = Client.new(options)
56
- @tunnel = @client.setup_tunnel
56
+ @tunnel = @client.setup_tunnel
57
+ @session = Net::SSH.start(@tunnel.tunneler, Forward.ssh_user, @client.ssh_options)
57
58
 
58
59
  Forward.log(:debug, "Starting remote forward at #{@tunnel.subdomain}.fwd.wf on port #{@tunnel.port}")
59
60
  puts "Forwarding port #{@tunnel.hostport} at \033[04mhttps://#{@tunnel.subdomain}.fwd.wf\033[m\nCtrl-C to stop forwarding"
60
- @session = Net::SSH.start(@tunnel.tunneler, Forward.ssh_user, @client.ssh_options)
61
+
61
62
  @session.forward.remote(@tunnel.hostport, @tunnel.host, @tunnel.port)
62
63
  @session.loop { watch_session(@session) }
63
64
 
64
65
  rescue Net::SSH::AuthenticationFailed => e
65
66
  Forward.log(:fatal, "SSH Auth failed `#{e}'")
66
- cleanup_and_exit!("Authentication failed, try deleting `#{Forward::Config::CONFIG_PATH}' and giving it another go. If the problem continues, contact support@forwardhq.com")
67
+ cleanup_and_exit!("Authentication failed, try deleting `#{Forward::Config.config_path}' and giving it another go. If the problem continues, contact support@forwardhq.com")
67
68
  rescue => e
68
69
  Forward.log(:fatal, "#{e.message}\n#{e.backtrace.join("\n")}")
69
70
  cleanup_and_exit!("You've been disconnected...")
@@ -1,9 +1,10 @@
1
+ require 'fileutils'
2
+ require 'rbconfig'
3
+ require 'yaml'
4
+
1
5
  module Forward
2
6
  class Config
3
- CONFIG_PATH = File.join(ENV['HOME'], '.forward')
4
- CIPHER_LENGTH = 128
5
- CIPHER_MODE = :CBC
6
- DELIMETER = '-------M-------'
7
+ CONFIG_FILE_VALUES = [ :api_token ]
7
8
 
8
9
  attr_accessor :id
9
10
  attr_accessor :api_token
@@ -39,12 +40,11 @@ module Forward
39
40
  Hash[instance_variables.map { |var| [var[1..-1].to_sym, instance_variable_get(var)] }]
40
41
  end
41
42
 
42
- #
43
- #
44
- #
43
+ # Validate that the required values are in the Config.
44
+ # Raises a config error if values are missing.
45
45
  def validate
46
46
  Forward.log(:debug, 'Validating Config')
47
- attributes = [:id, :api_token, :private_key]
47
+ attributes = [:api_token, :private_key]
48
48
  errors = []
49
49
 
50
50
  attributes.each do |attribute|
@@ -58,62 +58,76 @@ module Forward
58
58
  elsif errors.length >= 2
59
59
  raise ConfigError, "#{errors.join(', ')} are required fields"
60
60
  end
61
-
62
61
  end
63
62
 
63
+ # Write the current config data to `config_path', and the current
64
+ # private_key to `key_path'.
64
65
  #
65
- #
66
- #
66
+ # Returns the Config object.
67
67
  def write
68
68
  Forward.log(:debug, 'Writing Config')
69
- self.validate
70
-
71
- data = JSON.dump(self.to_hash)
72
- cipher = OpenSSL::Cipher::AES.new(CIPHER_LENGTH, CIPHER_MODE)
73
- cipher.encrypt
69
+ key_folder = File.dirname(Config.key_path)
70
+ config_data = to_hash.delete_if { |k,v| !CONFIG_FILE_VALUES.include?(k) }
74
71
 
75
- key = cipher.random_key
76
- iv = cipher.random_iv
77
-
78
- encrypted_data = cipher.update(data) + cipher.final
79
- config_data = Base64.encode64(encrypted_data)
80
- config_data << DELIMETER
81
- config_data << Base64.encode64("#{key}::#{iv}")
72
+ self.validate
82
73
 
83
- File.open(CONFIG_PATH, 'w') { |f| f.write(config_data) }
74
+ FileUtils.mkdir(key_folder) unless File.exist?(key_folder)
75
+ File.open(Config.config_path, 'w') { |f| f.write(YAML.dump(config_data)) }
76
+ File.open(Config.key_path, 'w') { |f| f.write(private_key) }
84
77
 
85
78
  self
79
+ rescue
80
+ raise ConfigError, 'Unable to write config file'
86
81
  end
87
82
 
83
+ # Shortcut for checking if host os is windows.
88
84
  #
89
- #
90
- #
91
- def self.read
92
- Forward.log(:debug, 'Reading Config')
93
- config_data = File.read(CONFIG_PATH)
94
- encrypted_data, keys = config_data.split(DELIMETER).map { |d| Base64.decode64(d) }
95
- key, iv = keys.split('::')
96
- decipher = OpenSSL::Cipher::AES.new(CIPHER_LENGTH, CIPHER_MODE)
97
-
98
- decipher.decrypt
99
-
100
- decipher.key = key
101
- decipher.iv = iv
85
+ # Returns true or false if windows or not.
86
+ def self.windows?
87
+ RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
88
+ end
102
89
 
103
- unencrypted = decipher.update(encrypted_data) + decipher.final
90
+ # Returns the location of the forward ssh private key
91
+ # based on the host os.
92
+ #
93
+ # Returns the String path.
94
+ def self.key_path
95
+ if windows?
96
+ File.join(ENV['HOME'], 'forward', 'key')
97
+ else
98
+ File.join(ENV['HOME'], '.ssh', 'forwardhq.com')
99
+ end
100
+ end
104
101
 
105
- JSON.parse(unencrypted).symbolize_keys
106
- rescue
107
- raise ConfigError, 'Unable to read config file'
102
+ # Returns the location of the forward config file
103
+ # based on the host os.
104
+ #
105
+ # Returns the String path.
106
+ def self.config_path
107
+ if windows?
108
+ File.join(ENV['HOME'], 'forward', 'config')
109
+ else
110
+ File.join(ENV['HOME'], '.forward')
111
+ end
108
112
  end
109
113
 
110
114
  # Checks to see if a .forward config file exists.
111
115
  #
112
116
  # Returns true or false based on the existence of the config file.
113
117
  def self.present?
114
- File.exist? CONFIG_PATH
118
+ File.exist? config_path
119
+ end
120
+
121
+ # Checks to see if a `private_key' exist.
122
+ #
123
+ # Returns true or false based on the existence of the key file.
124
+ def self.key_file_present?
125
+ File.exist? key_path
115
126
  end
116
127
 
128
+ # Create a config file if it doesn't exist, load one if it does.
129
+ #
130
+ # Returns the resulting Config object.
117
131
  def self.create_or_load
118
132
  if Config.present?
119
133
  Config.load
@@ -122,42 +136,56 @@ module Forward
122
136
  end
123
137
  end
124
138
 
139
+ # Create a config by authenticating the user via the api,
140
+ # and saving the users api_token/id/private_key.
141
+ #
142
+ # Returns the new Config object.
125
143
  def self.create
126
144
  Forward.log(:debug, 'Creating Config')
127
- config = Config.new
128
- email, password = CLI.authenticate.values_at(:email, :password)
129
- config.update(Forward::Api::User.api_token(email, password))
130
- Forward::Api.token = config.api_token
131
- config.private_key = Forward::Api::PublicKey.create
132
-
133
- config.write
145
+ if @updating_config || ask('Already have an account with Forward? ').chomp =~ /\Ay/i
146
+ config = Config.new
147
+ email, password = CLI.authenticate.values_at(:email, :password)
148
+ config.update(Forward::Api::User.api_token(email, password))
149
+ Forward::Api.token = config.api_token
150
+ config.private_key = Forward::Api::TunnelKey.create
151
+
152
+ config.write
153
+ else
154
+ Client.cleanup_and_exit!("You'll need a Forward account first. You can create one at \033[04mhttps://forwardhq.com\033[04m")
155
+ end
134
156
  end
135
157
 
136
158
  # It initializes a new Config instance, updates it with the config values
137
159
  # from the config file, and raises an error if there's not a config or if
138
160
  # the config options aren't valid.
139
161
  #
140
- # Returns the Config instance.
162
+ # Returns the Config object.
141
163
  def self.load
142
164
  Forward.log(:debug, 'Loading Config')
143
165
  config = Config.new
144
166
 
145
- if Config.present?
146
- config.update(Config.read)
167
+ raise ConfigError, "Unable to find a forward config file at `#{config_path}'" unless Config.present?
168
+
169
+ if File.read(config_path).include? '-----M-----'
170
+ puts "Forward needs to update your config file, please re-authenticate"
171
+ File.delete(config_path)
172
+ @updating_config = true
173
+ create_or_load
147
174
  end
148
175
 
176
+ raise ConfigError, "Unable to find a forward key file at `#{key_path}'" unless Config.key_file_present?
177
+
178
+
179
+ config.update(YAML.load_file(config_path).symbolize_keys)
180
+ config.private_key = File.read(key_path)
181
+ Forward::Api.token = config.api_token
182
+
149
183
  config.validate
150
184
 
151
- Forward::Api.token = config.api_token;
185
+ Forward.config = config
152
186
 
153
187
  config
154
188
  end
155
189
 
156
- # Deletes the .forward config file if it exists.
157
- def self.clear
158
- Forward.log(:debug, 'Clearning Config')
159
- File.delete(CONFIG_PATH) if Config.present?
160
- end
161
-
162
190
  end
163
191
  end
@@ -1,3 +1,3 @@
1
1
  module Forward
2
- VERSION = '0.0.14'
2
+ VERSION = '0.1.0'
3
3
  end
@@ -1,6 +1,6 @@
1
1
  require 'test_helper'
2
2
 
3
- describe Forward::Api::PublicKey do
3
+ describe Forward::Api::TunnelKey do
4
4
 
5
5
  before :each do
6
6
  FakeWeb.allow_net_connect = false
@@ -9,19 +9,19 @@ describe Forward::Api::PublicKey do
9
9
  it 'retrieves the public_key and returns it' do
10
10
  fake_body = { :private_key => 'ssh-key 1234567890' }
11
11
 
12
- stub_api_request(:post, '/api/public_keys', :body => fake_body.to_json)
12
+ stub_api_request(:post, '/api/tunnel_keys', :body => fake_body.to_json)
13
13
 
14
- response = Api::PublicKey.create
14
+ response = Api::TunnelKey.create
15
15
  response.must_equal fake_body[:private_key]
16
16
  end
17
17
 
18
18
  it 'exits with message if response has errors' do
19
19
  fake_body = { :errors => { :base => 'Unable to retrieve a private key' } }
20
20
 
21
- stub_api_request(:post, '/api/public_keys', :body => fake_body.to_json)
21
+ stub_api_request(:post, '/api/tunnel_keys', :body => fake_body.to_json)
22
22
 
23
23
  lambda {
24
- dev_null { Api::PublicKey.create }
24
+ dev_null { Api::TunnelKey.create }
25
25
  }.must_raise SystemExit
26
26
  end
27
27
 
@@ -11,10 +11,9 @@ describe Forward::Api::Tunnel do
11
11
  Forward.client = mock
12
12
  fake_body = { :_id => '1', :subdomain => 'foo', :port => 56789 }
13
13
 
14
- Forward.client.expects(:options).returns(:port => 3000)
15
14
  stub_api_request(:post, '/api/tunnels', :body => fake_body.to_json)
16
15
 
17
- response = Forward::Api::Tunnel.create
16
+ response = Forward::Api::Tunnel.create(:port => 3000)
18
17
 
19
18
  fake_body.each do |key, value|
20
19
  response[key].must_equal fake_body[key]
@@ -25,11 +24,10 @@ describe Forward::Api::Tunnel do
25
24
  Forward.client = mock
26
25
  fake_body = { :errors => { :base => [ 'unable to create tunnel' ] } }
27
26
 
28
- Forward.client.expects(:options).returns(:port => 3000)
29
27
  stub_api_request(:post, '/api/tunnels', :body => fake_body.to_json)
30
28
 
31
29
  lambda {
32
- dev_null { Forward::Api::Tunnel.create }
30
+ dev_null { Forward::Api::Tunnel.create(:port => 3000) }
33
31
  }.must_raise SystemExit
34
32
  end
35
33
 
@@ -41,13 +39,12 @@ describe Forward::Api::Tunnel do
41
39
  ]
42
40
  index_body = [ { :_id => 'abc123' }, { :_id => 'def456' } ]
43
41
 
44
- Forward.client.expects(:options).returns(:port => 3000).twice
45
42
  stub_api_request(:post, '/api/tunnels', post_options)
46
43
  stub_api_request(:get, '/api/tunnels', :body => index_body.to_json)
47
44
  STDIN.expects(:gets).returns('1')
48
45
  Forward::Api::Tunnel.expects(:destroy).with(index_body.first[:_id])
49
46
 
50
- dev_null { Forward::Api::Tunnel.create }
47
+ dev_null { Forward::Api::Tunnel.create(:port => 3000) }
51
48
  end
52
49
 
53
50
  it 'destroys a tunnel and returns the attributes' do
@@ -3,8 +3,7 @@ require 'test_helper'
3
3
  describe Forward::Config do
4
4
 
5
5
  before :each do
6
- @user_attributes = {
7
- :id => '12345',
6
+ @config_attributes = {
8
7
  :api_token => 'abcdefg',
9
8
  :private_key => 'secret'
10
9
  }
@@ -12,56 +11,58 @@ describe Forward::Config do
12
11
  end
13
12
 
14
13
  after :each do
15
- if Forward::Config.present?
16
- FileUtils.rm(Forward::Config::CONFIG_PATH)
17
- end
14
+ FileUtils.rm(Forward::Config.config_path) if Forward::Config.present?
15
+ FileUtils.rm(Forward::Config.key_path) if Forward::Config.key_file_present?
18
16
  end
19
17
 
20
18
  it "initializes a config with a hash" do
21
- config = Forward::Config.new(@user_attributes)
19
+ config = Forward::Config.new(@config_attributes)
22
20
 
23
- @user_attributes.each do |key, value|
21
+ @config_attributes.each do |key, value|
24
22
  config.send(key).must_equal value
25
23
  end
26
24
  end
27
25
 
28
26
  it "updates a config with a hash" do
29
27
  config = Forward::Config.new
30
- config.update(@user_attributes)
28
+ config.update(@config_attributes)
31
29
 
32
- @user_attributes.each do |key, value|
30
+ @config_attributes.each do |key, value|
33
31
  config.send(key).must_equal value
34
32
  end
35
33
  end
36
34
 
37
35
  it "writes and reads a config from disk" do
38
- config = Forward::Config.new(@user_attributes)
36
+ config = Forward::Config.new(@config_attributes)
39
37
  config.write
40
38
 
41
39
  saved_config = Forward::Config.load
42
40
 
43
- @user_attributes.each do |key, value|
41
+ File.exist?(Forward::Config.config_path).must_equal true
42
+ File.exist?(Forward::Config.key_path).must_equal true
43
+
44
+ @config_attributes.each do |key, value|
44
45
  saved_config.send(key).must_equal value
45
46
  end
46
47
  end
47
48
 
49
+ it "only writes config values to the config file" do
50
+ config = Forward::Config.new(@config_attributes)
51
+ config.write
52
+
53
+ config_file = File.read(Forward::Config.config_path)
54
+ config_file.include?('private_key').must_equal false
55
+ end
56
+
48
57
  it "converts a config to a hash" do
49
- config = Forward::Config.new(@user_attributes)
58
+ config = Forward::Config.new(@config_attributes)
50
59
  config_hash = config.to_hash
51
60
 
52
- @user_attributes.each do |key, value|
61
+ @config_attributes.each do |key, value|
53
62
  config_hash[key].must_equal value
54
63
  end
55
64
  end
56
65
 
57
- it "deletes config file" do
58
- config = Forward::Config.new(@user_attributes)
59
- config.write
60
- Forward::Config.clear
61
-
62
- Forward::Config.wont_be :present?
63
- end
64
-
65
66
  it "raises exception with an empty config" do
66
67
  config = Forward::Config.new
67
68
 
@@ -69,34 +70,26 @@ describe Forward::Config do
69
70
  end
70
71
 
71
72
  it "raises exception with an invalid config" do
72
- @user_attributes.delete(:api_token)
73
- config = Forward::Config.new(@user_attributes)
73
+ @config_attributes.delete(:api_token)
74
+ config = Forward::Config.new(@config_attributes)
74
75
 
75
76
  lambda { config.validate }.must_raise Forward::ConfigError
76
77
  end
77
78
 
78
- it "raises exception with bad config on write" do
79
- @user_attributes.delete(:private_key)
80
- config = Forward::Config.new(@user_attributes)
81
-
82
- lambda { config.write }.must_raise Forward::ConfigError
83
- end
79
+ # it "raises exception with bad config on write" do
80
+ # @config_attributes.delete(:private_key)
81
+ # config = Forward::Config.new(@config_attributes)
82
+ #
83
+ # lambda { config.write }.must_raise Forward::ConfigError
84
+ # end
84
85
 
85
86
  it "raises exception when a config file is not found" do
86
87
  lambda { Forward::Config.load }.must_raise Forward::ConfigError
87
88
  end
88
89
 
89
- it "raises exception when reading bad config file" do
90
- config_path = Forward::Config::CONFIG_PATH
91
- config_data = 'the trash alone'
92
-
93
- File.open(config_path, 'w') { |f| f.write(config_data) }
94
-
95
- lambda { Forward::Config.read }.must_raise Forward::ConfigError
96
- end
97
-
98
- it "raises exception when an invalid config file is loaded" do
99
- skip
90
+ it "raises exception when a key file is not found" do
91
+ File.open(Forward::Config.config_path, 'w') { |f| f.write YAML.dump(@config_attributes) }
92
+ lambda { Forward::Config.load }.must_raise Forward::ConfigError
100
93
  end
101
94
 
102
95
  end
@@ -1,6 +1,7 @@
1
1
  $LOAD_PATH.unshift(File.expand_path('../../spec', __FILE__))
2
2
 
3
3
  require 'rubygems'
4
+ require 'yaml'
4
5
  require 'bundler'
5
6
  Bundler.setup
6
7
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: forward
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.14
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-10-04 00:00:00.000000000 Z
12
+ date: 2012-10-05 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: json
@@ -142,9 +142,9 @@ files:
142
142
  - lib/forward.rb
143
143
  - lib/forward/api.rb
144
144
  - lib/forward/api/client_log.rb
145
- - lib/forward/api/public_key.rb
146
145
  - lib/forward/api/resource.rb
147
146
  - lib/forward/api/tunnel.rb
147
+ - lib/forward/api/tunnel_key.rb
148
148
  - lib/forward/api/user.rb
149
149
  - lib/forward/cli.rb
150
150
  - lib/forward/client.rb
@@ -153,8 +153,8 @@ files:
153
153
  - lib/forward/error.rb
154
154
  - lib/forward/tunnel.rb
155
155
  - lib/forward/version.rb
156
- - test/api/public_key_test.rb
157
156
  - test/api/resource_test.rb
157
+ - test/api/tunnel_key_test.rb
158
158
  - test/api/tunnel_test.rb
159
159
  - test/api/user_test.rb
160
160
  - test/api_test.rb
@@ -188,8 +188,8 @@ signing_key:
188
188
  specification_version: 3
189
189
  summary: Forward Lets You Share localhost over the Web. Demo a Website Without Hosting.
190
190
  test_files:
191
- - test/api/public_key_test.rb
192
191
  - test/api/resource_test.rb
192
+ - test/api/tunnel_key_test.rb
193
193
  - test/api/tunnel_test.rb
194
194
  - test/api/user_test.rb
195
195
  - test/api_test.rb