terminalwire 0.1.12 → 0.1.14
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 +4 -4
- data/LICENSE.txt +1 -1
- data/lib/terminalwire/client.rb +9 -21
- data/lib/terminalwire/licensing.rb +187 -0
- data/lib/terminalwire/logging.rb +1 -1
- data/lib/terminalwire/server.rb +3 -77
- data/lib/terminalwire/transport.rb +0 -65
- data/lib/terminalwire/version.rb +1 -1
- data/lib/terminalwire.rb +0 -5
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 75d01d183f7babe341773fad359c35f4ea478430792a2f292f3e1bce52b6b0ec
|
4
|
+
data.tar.gz: 4634c87d3d66e1553d7ae6ddf6198b9a5da72c1a9a2eb597e9bb1c9556effe8b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c0588f7064884a7e76b111aee494dd6ee0d694766afac82cf7aee8c420fd5b9b108991ecee6e57f5800fb54752e580ed240129aa2085b2f6134ac85478845ebe
|
7
|
+
data.tar.gz: afb0912cdc926020e32a6976562dfe59ff66365a90c17b3b3a35cf45ccd408a41182ba69403b07f10fdcd7538e7bc120379c58fc10f8fc64cca5576980e8b470
|
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
|
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
|
|
data/lib/terminalwire/client.rb
CHANGED
@@ -28,13 +28,15 @@ module Terminalwire
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def connect
|
31
|
-
@adapter.write(
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
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,187 @@
|
|
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
|
+
def self.time
|
18
|
+
Time.now.utc
|
19
|
+
end
|
20
|
+
|
21
|
+
# Handles encoding data into a license key with prefixes that can be packed and unpacked.
|
22
|
+
module Key
|
23
|
+
# Mix into classes that need to generate or read keys
|
24
|
+
module Serialization
|
25
|
+
# This is called when the module is included in a class
|
26
|
+
def self.included(base)
|
27
|
+
# Extend the class with the class methods when the module is included
|
28
|
+
base.extend(ClassMethods)
|
29
|
+
end
|
30
|
+
|
31
|
+
def serialize(...)
|
32
|
+
self.class.serialize(...)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Define the class methods that will be available on the including class
|
36
|
+
module ClassMethods
|
37
|
+
def serializer
|
38
|
+
Key::Serializer.new(prefix: self::PREFIX)
|
39
|
+
end
|
40
|
+
|
41
|
+
def serialize(...)
|
42
|
+
serializer.serialize(...)
|
43
|
+
end
|
44
|
+
|
45
|
+
def deserialize(...)
|
46
|
+
serializer.deserialize(...)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class Serializer
|
52
|
+
attr_reader :prefix
|
53
|
+
|
54
|
+
def initialize(prefix:)
|
55
|
+
@prefix = prefix
|
56
|
+
end
|
57
|
+
|
58
|
+
def serialize(data)
|
59
|
+
prepend_prefix Base64.urlsafe_encode64 MessagePack.pack data
|
60
|
+
end
|
61
|
+
|
62
|
+
def deserialize(data)
|
63
|
+
MessagePack.unpack Base64.urlsafe_decode64 unshift_prefix data
|
64
|
+
end
|
65
|
+
|
66
|
+
protected
|
67
|
+
|
68
|
+
def prepend_prefix(key)
|
69
|
+
[prefix, key].join
|
70
|
+
end
|
71
|
+
|
72
|
+
def unshift_prefix(key)
|
73
|
+
head, prefix, tail = key.partition(@prefix)
|
74
|
+
# Check if partition successfully split the string with the correct prefix
|
75
|
+
raise RuntimeError, "Expected prefix #{@prefix.inspect} on #{key.inspect}" if prefix.empty?
|
76
|
+
tail
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# This code all runs on Terminalwire servers.
|
82
|
+
module Issuer
|
83
|
+
# Generates license keys that developers use to activate their software.
|
84
|
+
class ServerKeyGenerator
|
85
|
+
include Key::Serialization
|
86
|
+
|
87
|
+
PREFIX = "server_key_".freeze
|
88
|
+
|
89
|
+
VERSION = "1.0".freeze
|
90
|
+
|
91
|
+
def initialize(public_key:, license_url:, generated_at: Terminalwire::Licensing.time)
|
92
|
+
@public_key = public_key
|
93
|
+
@license_url = URI(license_url)
|
94
|
+
@generated_at = generated_at
|
95
|
+
end
|
96
|
+
|
97
|
+
def to_h
|
98
|
+
{
|
99
|
+
version: VERSION,
|
100
|
+
generated_at: @generated_at.iso8601,
|
101
|
+
public_key: @public_key.to_pem,
|
102
|
+
license_url: @license_url.to_s
|
103
|
+
}
|
104
|
+
end
|
105
|
+
|
106
|
+
def server_key
|
107
|
+
serialize to_h
|
108
|
+
end
|
109
|
+
alias :to_s :server_key
|
110
|
+
end
|
111
|
+
|
112
|
+
class ClientKeyVerifier
|
113
|
+
# Time variance the server will tolerate from the client.
|
114
|
+
DRIFT_SECONDS = 600 # 600 seconds, or 10 minutes.
|
115
|
+
|
116
|
+
# This means the server will tolerate a 10 minute drift in the generated_at time.
|
117
|
+
def self.drift
|
118
|
+
now = Terminalwire::Licensing.time
|
119
|
+
(now - DRIFT_SECONDS)...(now + DRIFT_SECONDS)
|
120
|
+
end
|
121
|
+
|
122
|
+
def initialize(client_key:, private_key:, drift: self.class.drift)
|
123
|
+
@data = Server::ClientKeyGenerator.deserialize client_key
|
124
|
+
@private_key = private_key
|
125
|
+
@drift = drift
|
126
|
+
end
|
127
|
+
|
128
|
+
def server_attestation
|
129
|
+
@server_attestation ||= decrypt @data.fetch("server_attestation")
|
130
|
+
end
|
131
|
+
|
132
|
+
def decrypt(data)
|
133
|
+
MessagePack.unpack @private_key.private_decrypt Base64.urlsafe_decode64 data
|
134
|
+
end
|
135
|
+
|
136
|
+
def generated_at
|
137
|
+
@generated_at ||= Time.parse(server_attestation.fetch("generated_at"))
|
138
|
+
end
|
139
|
+
|
140
|
+
def valid?
|
141
|
+
@drift.include? generated_at
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
# Those code runs on customer servers
|
147
|
+
module Server
|
148
|
+
class ClientKeyGenerator
|
149
|
+
VERSION = "1.0".freeze
|
150
|
+
|
151
|
+
include Key::Serialization
|
152
|
+
PREFIX = "client_key_".freeze
|
153
|
+
|
154
|
+
def initialize(server_key:, generated_at: Terminalwire::Licensing.time)
|
155
|
+
@data = Issuer::ServerKeyGenerator.deserialize server_key
|
156
|
+
@license_url = URI(@data.fetch("license_url"))
|
157
|
+
@generated_at = generated_at
|
158
|
+
end
|
159
|
+
|
160
|
+
def to_h
|
161
|
+
{
|
162
|
+
version: VERSION,
|
163
|
+
license_url: @license_url.to_s,
|
164
|
+
server_attestation: attest(
|
165
|
+
version: VERSION,
|
166
|
+
generated_at: @generated_at.iso8601,
|
167
|
+
)
|
168
|
+
}
|
169
|
+
end
|
170
|
+
|
171
|
+
def client_key
|
172
|
+
serialize to_h
|
173
|
+
end
|
174
|
+
alias :to_s :client_key
|
175
|
+
|
176
|
+
protected
|
177
|
+
|
178
|
+
def attest(data)
|
179
|
+
Base64.urlsafe_encode64 public_key.public_encrypt MessagePack.pack data
|
180
|
+
end
|
181
|
+
|
182
|
+
def public_key
|
183
|
+
@public_key ||= OpenSSL::PKey::RSA.new(@data.fetch("public_key"))
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
data/lib/terminalwire/logging.rb
CHANGED
data/lib/terminalwire/server.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
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)
|
data/lib/terminalwire/version.rb
CHANGED
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.
|
4
|
+
version: 0.1.14
|
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-
|
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.
|
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.
|
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
|