terminalwire 0.1.12 → 0.1.14
Sign up to get free protection for your applications and to get access to all the features.
- 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
|