terminalwire 0.1.11 → 0.1.13

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: 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