terminalwire 0.1.11 → 0.1.13

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: 55ab6e5d0132726c5041fce03026f14963c2de4b60a2bc92cd2560b7f14890cf
4
- data.tar.gz: b2f1d360f6135fddf2ab90ca5ad992f1e313ccd4de09cd8bca9314b6af0dbaac
3
+ metadata.gz: 1b3e02592c8154c835d32f4dc149d47212665f5dd2974d2634746207ced582b3
4
+ data.tar.gz: 0a4b15090653b5b54a10a215db6a39ee5465cac9a10c4e1cd29ba7fc0622822a
5
5
  SHA512:
6
- metadata.gz: 5f003df286a23cff8e92c92d5372f81dc7cc0a915b1d32a6ba8c0bd5005a54395286b9a72aad2d6cc9856d0e6ba2c1622757a6bfbf8aba343885e55b42980227
7
- data.tar.gz: eb480c644c568de7b6391a4e3310219fbf64869972eaeac7f0d4a7a6365b8fb644edd0193ae3920df604b3044b14fdfc09f05aa39a491be5d634ab084d8058d3
6
+ metadata.gz: d74b1186e46a73eb62de35f0624f20a4f3f90aa8bf24137b772423a87a09bea920886824d7175c5b08bd18c963b3823c4cccbff0e7abd44120bfe186edda84e6
7
+ data.tar.gz: 9997f63145801d3ca47476ead70f49ac001f77765cb777063b44b62d96e1b3dfdf266f42897fae01eb0a9a224e8fddeb5e878533e773f11aaafe2e81512a48f4
data/LICENSE.txt CHANGED
@@ -1 +1,9 @@
1
- Copyright (c) 2024 Brad Gessler. Email brad@terminalwire.com to discuss licensing.
1
+ Copyright (c) 2024 Brad Gessler.
2
+
3
+ License is propietary. Here's the deal:
4
+
5
+ * You need to pay for Terminalwire if your organization has more than $1m in assets or makes more than $100k/year in revenue.
6
+ * Terminalwire is free for personal use, hobbyists, or businesses that are just starting out, but server licenses still need to be registered at https://terminalwire.com/developers/licenses.
7
+ * You can only use the Terminalwire client with licensed Terminalwire servers.
8
+
9
+ This list is not exhaustive, please read the full license text at https://terminalwire.com/license.
@@ -7,6 +7,7 @@ class Terminalwire::InstallGenerator < Rails::Generators::Base
7
7
 
8
8
  def create_terminal_files
9
9
  template "application_terminal.rb.tt", Rails.root.join("app/terminal/application_terminal.rb")
10
+ template "main_terminal.rb", Rails.root.join("app/terminal/main_terminal.rb")
10
11
  end
11
12
 
12
13
  def create_binary_files
@@ -17,7 +18,7 @@ class Terminalwire::InstallGenerator < Rails::Generators::Base
17
18
  def add_route
18
19
  route <<~ROUTE
19
20
  match "/terminal",
20
- to: Terminalwire::Server::Thor.new(ApplicationTerminal),
21
+ to: Terminalwire::Server::Thor.new(MainTerminal),
21
22
  via: [:get, :connect]
22
23
  ROUTE
23
24
  end
@@ -1,44 +1,11 @@
1
1
  # Learn how to use Thor at http://whatisthor.com.
2
2
  class ApplicationTerminal < Thor
3
+ # Enables IO Streaming.
3
4
  include Terminalwire::Thor
4
5
 
6
+ # The name of your binary. Thor uses this for its help output.
5
7
  def self.basename = "<%= binary_name %>"
6
8
 
7
- desc "hello NAME", "say hello to NAME"
8
- def hello(name)
9
- puts "Hello #{name}"
10
- end
11
-
12
- desc "login", "Login to your account"
13
- def login
14
- print "Email: "
15
- email = gets.chomp
16
-
17
- print "Password: "
18
- password = getpass
19
-
20
- if self.current_user = User.authenticate(email, password)
21
- puts "Successfully logged in as #{current_user.email}."
22
- else
23
- puts "Could not find a user with that email and password."
24
- end
25
- end
26
-
27
- desc "whoami", "Displays current user information."
28
- def whoami
29
- if self.current_user
30
- puts "Logged in as #{current_user.email}."
31
- else
32
- puts "Not logged in. Run `#{self.class.basename} login` to login."
33
- end
34
- end
35
-
36
- desc "logout", "Logout of your account"
37
- def logout
38
- session.reset
39
- puts "Successfully logged out."
40
- end
41
-
42
9
  private
43
10
 
44
11
  def current_user=(user)
@@ -0,0 +1,40 @@
1
+ class MainTerminal < ApplicationTerminal
2
+ desc "hello NAME", "say hello to NAME"
3
+ def hello(name)
4
+ puts "Hello #{name}"
5
+ end
6
+
7
+ desc "login", "Login to your account"
8
+ def login
9
+ print "Email: "
10
+ email = gets.chomp
11
+
12
+ print "Password: "
13
+ password = getpass
14
+
15
+ # Replace this with your own authentication logic; this is an example
16
+ # of how you might do this with Devise.
17
+ user = User.find_for_authentication(email: email)
18
+ if user && user.valid_password?(password)
19
+ self.current_user = user
20
+ puts "Successfully logged in as #{current_user.email}."
21
+ else
22
+ puts "Could not find a user with that email and password."
23
+ end
24
+ end
25
+
26
+ desc "whoami", "Displays current user information."
27
+ def whoami
28
+ if self.current_user
29
+ puts "Logged in as #{current_user.email}."
30
+ else
31
+ puts "Not logged in. Run `#{self.class.basename} login` to login."
32
+ end
33
+ end
34
+
35
+ desc "logout", "Logout of your account"
36
+ def logout
37
+ session.reset
38
+ puts "Successfully logged out."
39
+ end
40
+ end
@@ -28,13 +28,15 @@ module Terminalwire
28
28
  end
29
29
 
30
30
  def connect
31
- @adapter.write(event: "initialization",
32
- protocol: { version: VERSION },
33
- entitlement: @entitlement.serialize,
34
- program: {
35
- name: @program_name,
36
- arguments: @program_arguments
37
- })
31
+ @adapter.write(
32
+ event: "initialization",
33
+ protocol: { version: VERSION },
34
+ entitlement: @entitlement.serialize,
35
+ program: {
36
+ name: @program_name,
37
+ arguments: @program_arguments
38
+ }
39
+ )
38
40
 
39
41
  loop do
40
42
  handle @adapter.read
@@ -51,20 +53,6 @@ module Terminalwire
51
53
  end
52
54
  end
53
55
 
54
- def self.tcp(...)
55
- socket = TCPSocket.new(...)
56
- transport = Terminalwire::Transport::Socket.new(socket)
57
- adapter = Terminalwire::Adapter::Socket.new(transport)
58
- Terminalwire::Client::Handler.new(adapter)
59
- end
60
-
61
- def self.socket(...)
62
- socket = UNIXSocket.new(...)
63
- transport = Terminalwire::Transport::Socket.new(socket)
64
- adapter = Terminalwire::Adapter::Socket.new(transport)
65
- Terminalwire::Client::Handler.new(adapter)
66
- end
67
-
68
56
  # Extracted from HTTP. This is so we can
69
57
  def self.authority(url)
70
58
  if url.port == url.default_port
@@ -0,0 +1,183 @@
1
+ require "msgpack"
2
+ require "openssl"
3
+ require "base64"
4
+ require "uri"
5
+
6
+ module Terminalwire::Licensing
7
+ PRIVATE_KEY_LENGTH = 2048
8
+
9
+ def self.generate_private_key
10
+ OpenSSL::PKey::RSA.new(PRIVATE_KEY_LENGTH)
11
+ end
12
+
13
+ def self.generate_private_pem
14
+ generate_private_key.to_pem
15
+ end
16
+
17
+ # Handles encoding data into a license key with prefixes that can be packed and unpacked.
18
+ module Key
19
+ # Mix into classes that need to generate or read keys
20
+ module Serialization
21
+ # This is called when the module is included in a class
22
+ def self.included(base)
23
+ # Extend the class with the class methods when the module is included
24
+ base.extend(ClassMethods)
25
+ end
26
+
27
+ def serialize(...)
28
+ self.class.serialize(...)
29
+ end
30
+
31
+ # Define the class methods that will be available on the including class
32
+ module ClassMethods
33
+ def serializer
34
+ Key::Serializer.new(prefix: self::PREFIX)
35
+ end
36
+
37
+ def serialize(...)
38
+ serializer.serialize(...)
39
+ end
40
+
41
+ def deserialize(...)
42
+ serializer.deserialize(...)
43
+ end
44
+ end
45
+ end
46
+
47
+ class Serializer
48
+ attr_reader :prefix
49
+
50
+ def initialize(prefix:)
51
+ @prefix = prefix
52
+ end
53
+
54
+ def serialize(data)
55
+ prepend_prefix Base64.urlsafe_encode64 MessagePack.pack data
56
+ end
57
+
58
+ def deserialize(data)
59
+ MessagePack.unpack Base64.urlsafe_decode64 unshift_prefix data
60
+ end
61
+
62
+ protected
63
+
64
+ def prepend_prefix(key)
65
+ [prefix, key].join
66
+ end
67
+
68
+ def unshift_prefix(key)
69
+ head, prefix, tail = key.partition(@prefix)
70
+ # Check if partition successfully split the string with the correct prefix
71
+ raise RuntimeError, "Expected prefix #{@prefix.inspect} on #{key.inspect}" if prefix.empty?
72
+ tail
73
+ end
74
+ end
75
+ end
76
+
77
+ # This code all runs on Terminalwire servers.
78
+ module Issuer
79
+ # Generates license keys that developers use to activate their software.
80
+ class ServerKeyGenerator
81
+ include Key::Serialization
82
+
83
+ PREFIX = "server_key_".freeze
84
+
85
+ VERSION = "1.0".freeze
86
+
87
+ def initialize(public_key:, license_url:)
88
+ @public_key = public_key
89
+ @license_url = URI(license_url)
90
+ @generated_at = Time.now.utc
91
+ end
92
+
93
+ def to_h
94
+ {
95
+ version: VERSION,
96
+ generated_at: @generated_at.iso8601,
97
+ public_key: @public_key.to_pem,
98
+ license_url: @license_url.to_s
99
+ }
100
+ end
101
+
102
+ def server_key
103
+ serialize to_h
104
+ end
105
+ alias :to_s :server_key
106
+ end
107
+
108
+ class ClientKeyVerifier
109
+ # Time variance the server will tolerate from the client.
110
+ DRIFT_SECONDS = 600 # 600 seconds, or 10 minutes.
111
+
112
+ # This means the server will tolerate a 10 minute drift in the generated_at time.
113
+ def self.drift
114
+ now = Time.now.utc
115
+ (now - DRIFT_SECONDS)...(now + DRIFT_SECONDS)
116
+ end
117
+
118
+ def initialize(client_key:, private_key:, drift: self.class.drift)
119
+ @data = Server::ClientKeyGenerator.deserialize client_key
120
+ @private_key = private_key
121
+ @drift = drift
122
+ end
123
+
124
+ def server_attestation
125
+ @server_attestation ||= decrypt @data.fetch("server_attestation")
126
+ end
127
+
128
+ def decrypt(data)
129
+ MessagePack.unpack @private_key.private_decrypt Base64.urlsafe_decode64 data
130
+ end
131
+
132
+ def generated_at
133
+ @generated_at ||= Time.parse(server_attestation.fetch("generated_at"))
134
+ end
135
+
136
+ def valid?
137
+ @drift.include? generated_at
138
+ end
139
+ end
140
+ end
141
+
142
+ # Those code runs on customer servers
143
+ module Server
144
+ class ClientKeyGenerator
145
+ VERSION = "1.0".freeze
146
+
147
+ include Key::Serialization
148
+ PREFIX = "client_key_".freeze
149
+
150
+ def initialize(server_key:)
151
+ @data = Issuer::ServerKeyGenerator.deserialize server_key
152
+ @generated_at = Time.now.utc
153
+ @license_url = URI(@data.fetch("license_url"))
154
+ end
155
+
156
+ def to_h
157
+ {
158
+ version: VERSION,
159
+ license_url: @license_url.to_s,
160
+ server_attestation: attest(
161
+ version: VERSION,
162
+ generated_at: @generated_at.iso8601,
163
+ )
164
+ }
165
+ end
166
+
167
+ def client_key
168
+ serialize to_h
169
+ end
170
+ alias :to_s :client_key
171
+
172
+ protected
173
+
174
+ def attest(data)
175
+ Base64.urlsafe_encode64 public_key.public_encrypt MessagePack.pack data
176
+ end
177
+
178
+ def public_key
179
+ @public_key ||= OpenSSL::PKey::RSA.new(@data.fetch("public_key"))
180
+ end
181
+ end
182
+ end
183
+ end
@@ -2,7 +2,7 @@ require 'logger'
2
2
 
3
3
  module Terminalwire
4
4
  module Logging
5
- DEVICE = Logger.new($stdout, level: ENV.fetch("LOG_LEVEL", "info"))
5
+ DEVICE = Logger.new($stdout, level: ENV.fetch("TERMINALWIRE_LOG_LEVEL", "info"))
6
6
  def logger = DEVICE
7
7
  end
8
8
  end
@@ -2,57 +2,16 @@ require "thor"
2
2
 
3
3
  module Terminalwire
4
4
  module Server
5
- class MyCLI < ::Thor
6
- include Terminalwire::Thor
7
-
8
- desc "greet NAME", "Greet a person"
9
- def greet(name)
10
- name = ask "What's your name?"
11
- say "Hello, #{name}!"
12
- end
13
- end
14
-
15
- class Socket
16
- include Logging
17
-
18
- def initialize(server_socket)
19
- @server_socket = server_socket
20
- end
21
-
22
- def listen
23
- logger.info "Socket: Listening..."
24
- loop do
25
- client_socket = @server_socket.accept
26
- logger.debug "Socket: Client #{client_socket.inspect} connected"
27
- handle_client(client_socket)
28
- end
29
- end
30
-
31
- private
32
-
33
- def handle_client(socket)
34
- transport = Transport::Socket.new(socket)
35
- adapter = Adapter.new(transport)
36
-
37
- Thread.new do
38
- handler = Handler.new(adapter)
39
- handler.run
40
- end
41
- end
42
- end
43
-
44
5
  class WebSocket
45
6
  include Logging
46
7
 
47
8
  def call(env)
48
9
  Async::WebSocket::Adapters::Rack.open(env, protocols: ['ws']) do |connection|
49
- run(Adapter::Socket.new(Terminalwire::Transport::WebSocket.new(connection)))
10
+ handle(Adapter::Socket.new(Terminalwire::Transport::WebSocket.new(connection)))
50
11
  end or [200, { "Content-Type" => "text/plain" }, ["Connect via WebSockets"]]
51
12
  end
52
13
 
53
- private
54
-
55
- def run(adapter)
14
+ def handle(adapter)
56
15
  while message = adapter.read
57
16
  puts message
58
17
  end
@@ -76,7 +35,7 @@ module Terminalwire
76
35
  "An error occurred. Please try again."
77
36
  end
78
37
 
79
- def run(adapter)
38
+ def handle(adapter)
80
39
  logger.info "ThorServer: Running #{@cli_class.inspect}"
81
40
  while message = adapter.read
82
41
  case message
@@ -100,38 +59,5 @@ module Terminalwire
100
59
  end
101
60
  end
102
61
  end
103
-
104
- class Handler
105
- include Logging
106
-
107
- def initialize(adapter)
108
- @adapter = adapter
109
- end
110
-
111
- def run
112
- logger.info "Server Handler: Running"
113
- loop do
114
- message = @adapter.read
115
- case message
116
- in { event: "initialization", protocol:, program: { arguments: }, entitlement: }
117
- Context.new(adapter: @adapter) do |context|
118
- MyCLI.start(arguments, context:)
119
- end
120
- end
121
- end
122
- rescue EOFError, Errno::ECONNRESET
123
- logger.info "Server Handler: Client disconnected"
124
- ensure
125
- @adapter.close
126
- end
127
- end
128
-
129
- def self.tcp(...)
130
- Server::Socket.new(TCPServer.new(...))
131
- end
132
-
133
- def self.socket(...)
134
- Server::Socket.new(UNIXServer.new(...))
135
- end
136
62
  end
137
63
  end
@@ -1,5 +1,4 @@
1
1
  require 'uri'
2
- require 'socket'
3
2
  require 'async/websocket/client'
4
3
 
5
4
  module Terminalwire
@@ -26,70 +25,6 @@ module Terminalwire
26
25
  end
27
26
  end
28
27
 
29
- class TCP < Base
30
- def self.connect(url)
31
- uri = URI(url)
32
- new(TCPSocket.new(uri.host, uri.port))
33
- end
34
-
35
- def self.listen(url)
36
- uri = URI(url)
37
- new(TCPServer.new(uri.host, uri.port))
38
- end
39
-
40
- def initialize(socket)
41
- @socket = socket
42
- end
43
-
44
- def read
45
- length = @socket.read(4)
46
- return nil if length.nil?
47
- length = length.unpack('L>')[0]
48
- @socket.read(length)
49
- end
50
-
51
- def write(data)
52
- length = [data.bytesize].pack('L>')
53
- @socket.write(length + data)
54
- end
55
-
56
- def close
57
- @socket.close
58
- end
59
- end
60
-
61
- class Unix < Base
62
- def self.connect(url)
63
- uri = URI(url)
64
- new(UNIXSocket.new(uri.path))
65
- end
66
-
67
- def self.listen(url)
68
- uri = URI(url)
69
- new(UNIXServer.new(uri.path))
70
- end
71
-
72
- def initialize(socket)
73
- @socket = socket
74
- end
75
-
76
- def read
77
- length = @socket.read(4)
78
- return nil if length.nil?
79
- length = length.unpack('L>')[0]
80
- @socket.read(length)
81
- end
82
-
83
- def write(data)
84
- length = [data.bytesize].pack('L>')
85
- @socket.write(length + data)
86
- end
87
-
88
- def close
89
- @socket.close
90
- end
91
- end
92
-
93
28
  class WebSocket < Base
94
29
  def self.connect(url)
95
30
  uri = URI(url)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Terminalwire
4
- VERSION = "0.1.11"
4
+ VERSION = "0.1.13"
5
5
  end
data/lib/terminalwire.rb CHANGED
@@ -2,7 +2,6 @@
2
2
 
3
3
  require_relative "terminalwire/version"
4
4
 
5
- require 'socket'
6
5
  require 'forwardable'
7
6
  require 'uri'
8
7
  require 'zeitwerk'
@@ -40,10 +39,6 @@ module Terminalwire
40
39
  respond(status: "success", response:, **data)
41
40
  end
42
41
 
43
- def self.protocol_key
44
- name.split("::").last.downcase
45
- end
46
-
47
42
  private
48
43
 
49
44
  def respond(**response)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: terminalwire
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.11
4
+ version: 0.1.13
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brad Gessler
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-10-02 00:00:00.000000000 Z
11
+ date: 2024-10-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: async-websocket
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0.25'
19
+ version: '0.30'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0.25'
26
+ version: '0.30'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: zeitwerk
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -158,12 +158,14 @@ files:
158
158
  - lib/generators/terminalwire/install/install_generator.rb
159
159
  - lib/generators/terminalwire/install/templates/application_terminal.rb.tt
160
160
  - lib/generators/terminalwire/install/templates/bin/terminalwire
161
+ - lib/generators/terminalwire/install/templates/main_terminal.rb
161
162
  - lib/terminalwire.rb
162
163
  - lib/terminalwire/adapter.rb
163
164
  - lib/terminalwire/client.rb
164
165
  - lib/terminalwire/client/entitlement.rb
165
166
  - lib/terminalwire/client/exec.rb
166
167
  - lib/terminalwire/client/resource.rb
168
+ - lib/terminalwire/licensing.rb
167
169
  - lib/terminalwire/logging.rb
168
170
  - lib/terminalwire/rails.rb
169
171
  - lib/terminalwire/server.rb
@@ -175,7 +177,7 @@ files:
175
177
  - sig/terminalwire.rbs
176
178
  homepage: https://terminalwire.com/ruby
177
179
  licenses:
178
- - Proprietary
180
+ - Proprietary (https://terminalwire.com/license)
179
181
  metadata:
180
182
  allowed_push_host: https://rubygems.org/
181
183
  homepage_uri: https://terminalwire.com/ruby