terminalwire 0.1.12 → 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: 0ffa1e8b55e7c09d41b9a57fc645e950e65f6658767022cf67cac994823fb760
4
- data.tar.gz: 32fb995746b753eeed71543b954aa2376c2af4b1e61721b14eac8704c9f555f6
3
+ metadata.gz: 1b3e02592c8154c835d32f4dc149d47212665f5dd2974d2634746207ced582b3
4
+ data.tar.gz: 0a4b15090653b5b54a10a215db6a39ee5465cac9a10c4e1cd29ba7fc0622822a
5
5
  SHA512:
6
- metadata.gz: cb3ca0210a84eb64c924522d4eeb04f75a59bf8d785581a232216c8c417ecc837a66001947449e4da91127ebe942c12bce6ea07f8de1be2fe1fc69e5dcfdc332
7
- data.tar.gz: e9e0ea45f77040fc503acf2c09f22671d2a09f26315cad69ce1de8e62d4d0578b96663db13414446b7e99ce4abd3dd792ccb6b9191462ae6f67ff8789750856a
6
+ metadata.gz: d74b1186e46a73eb62de35f0624f20a4f3f90aa8bf24137b772423a87a09bea920886824d7175c5b08bd18c963b3823c4cccbff0e7abd44120bfe186edda84e6
7
+ data.tar.gz: 9997f63145801d3ca47476ead70f49ac001f77765cb777063b44b62d96e1b3dfdf266f42897fae01eb0a9a224e8fddeb5e878533e773f11aaafe2e81512a48f4
data/LICENSE.txt CHANGED
@@ -2,7 +2,7 @@ Copyright (c) 2024 Brad Gessler.
2
2
 
3
3
  License is propietary. Here's the deal:
4
4
 
5
- * You need to pay for Terminalwire if your business has more than $1m in assets or makes more than $100k/year in revenue.
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
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
7
  * You can only use the Terminalwire client with licensed Terminalwire servers.
8
8
 
@@ -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.12"
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.12
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-14 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
@@ -165,6 +165,7 @@ files:
165
165
  - lib/terminalwire/client/entitlement.rb
166
166
  - lib/terminalwire/client/exec.rb
167
167
  - lib/terminalwire/client/resource.rb
168
+ - lib/terminalwire/licensing.rb
168
169
  - lib/terminalwire/logging.rb
169
170
  - lib/terminalwire/rails.rb
170
171
  - lib/terminalwire/server.rb