forward 0.0.14 → 0.1.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.
@@ -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