meshchat 0.8.0 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (121) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +39 -11
  3. data/lib/meshchat.rb +48 -42
  4. data/lib/meshchat/configuration.rb +14 -0
  5. data/lib/meshchat/configuration/app_config.rb +63 -0
  6. data/lib/meshchat/configuration/database.rb +41 -0
  7. data/lib/meshchat/{config → configuration}/hash_file.rb +7 -6
  8. data/lib/meshchat/configuration/identity.rb +79 -0
  9. data/lib/meshchat/{config → configuration}/settings.rb +22 -26
  10. data/lib/meshchat/debug.rb +69 -0
  11. data/lib/meshchat/encryption.rb +7 -2
  12. data/lib/meshchat/encryption/aes_rsa.rb +2 -1
  13. data/lib/meshchat/encryption/passthrough.rb +5 -3
  14. data/lib/meshchat/locale/en.yml +14 -0
  15. data/lib/meshchat/models/node.rb +140 -0
  16. data/lib/meshchat/network.rb +19 -0
  17. data/lib/meshchat/network/dispatcher.rb +83 -0
  18. data/lib/meshchat/network/errors.rb +11 -0
  19. data/lib/meshchat/network/incoming.rb +13 -0
  20. data/lib/meshchat/network/incoming/message_decryptor.rb +51 -0
  21. data/lib/meshchat/network/incoming/message_processor.rb +75 -0
  22. data/lib/meshchat/network/incoming/request_processor.rb +30 -0
  23. data/lib/meshchat/network/local.rb +12 -0
  24. data/lib/meshchat/network/local/connection.rb +58 -0
  25. data/lib/meshchat/network/local/server.rb +69 -0
  26. data/lib/meshchat/network/message.rb +34 -0
  27. data/lib/meshchat/network/message/base.rb +139 -0
  28. data/lib/meshchat/network/message/chat.rb +9 -0
  29. data/lib/meshchat/network/message/disconnect.rb +21 -0
  30. data/lib/meshchat/network/message/emote.rb +9 -0
  31. data/lib/meshchat/network/message/factory.rb +80 -0
  32. data/lib/meshchat/network/message/node_list.rb +75 -0
  33. data/lib/meshchat/network/message/node_list_diff.rb +18 -0
  34. data/lib/meshchat/network/message/node_list_hash.rb +32 -0
  35. data/lib/meshchat/network/message/ping.rb +31 -0
  36. data/lib/meshchat/network/message/ping_reply.rb +12 -0
  37. data/lib/meshchat/network/message/whisper.rb +28 -0
  38. data/lib/meshchat/network/remote.rb +13 -0
  39. data/lib/meshchat/network/remote/connection.rb +28 -0
  40. data/lib/meshchat/network/remote/relay.rb +109 -0
  41. data/lib/meshchat/network/remote/relay_pool.rb +52 -0
  42. data/lib/meshchat/ui.rb +13 -0
  43. data/lib/meshchat/ui/cli.rb +48 -0
  44. data/lib/meshchat/ui/cli/base.rb +39 -0
  45. data/lib/meshchat/ui/cli/input_factory.rb +50 -0
  46. data/lib/meshchat/ui/cli/keyboard_line_input.rb +14 -0
  47. data/lib/meshchat/ui/command.rb +51 -0
  48. data/lib/meshchat/ui/command/base.rb +77 -0
  49. data/lib/meshchat/ui/command/bind.rb +47 -0
  50. data/lib/meshchat/ui/command/chat.rb +31 -0
  51. data/lib/meshchat/ui/command/config.rb +37 -0
  52. data/lib/meshchat/ui/command/emote.rb +23 -0
  53. data/lib/meshchat/ui/command/exit.rb +16 -0
  54. data/lib/meshchat/ui/command/help.rb +20 -0
  55. data/lib/meshchat/ui/command/identity.rb +16 -0
  56. data/lib/meshchat/ui/command/import.rb +42 -0
  57. data/lib/meshchat/ui/command/irb.rb +22 -0
  58. data/lib/meshchat/ui/command/offline.rb +23 -0
  59. data/lib/meshchat/ui/command/online.rb +18 -0
  60. data/lib/meshchat/ui/command/ping.rb +65 -0
  61. data/lib/meshchat/ui/command/ping_all.rb +19 -0
  62. data/lib/meshchat/ui/command/send_disconnect.rb +20 -0
  63. data/lib/meshchat/ui/command/server.rb +22 -0
  64. data/lib/meshchat/ui/command/share.rb +16 -0
  65. data/lib/meshchat/ui/command/whisper.rb +40 -0
  66. data/lib/meshchat/ui/display.rb +78 -0
  67. data/lib/meshchat/ui/display/base.rb +58 -0
  68. data/lib/meshchat/ui/display/manager.rb +59 -0
  69. data/lib/meshchat/ui/notifier.rb +9 -0
  70. data/lib/meshchat/ui/notifier/base.rb +33 -0
  71. data/lib/meshchat/version.rb +3 -2
  72. metadata +150 -80
  73. data/lib/meshchat/cli.rb +0 -188
  74. data/lib/meshchat/cli/base.rb +0 -13
  75. data/lib/meshchat/cli/input.rb +0 -37
  76. data/lib/meshchat/command/base.rb +0 -80
  77. data/lib/meshchat/command/bind.rb +0 -44
  78. data/lib/meshchat/command/chat.rb +0 -30
  79. data/lib/meshchat/command/config.rb +0 -34
  80. data/lib/meshchat/command/emote.rb +0 -20
  81. data/lib/meshchat/command/exit.rb +0 -13
  82. data/lib/meshchat/command/help.rb +0 -17
  83. data/lib/meshchat/command/identity.rb +0 -13
  84. data/lib/meshchat/command/import.rb +0 -41
  85. data/lib/meshchat/command/init.rb +0 -34
  86. data/lib/meshchat/command/irb.rb +0 -23
  87. data/lib/meshchat/command/listen.rb +0 -13
  88. data/lib/meshchat/command/offline.rb +0 -20
  89. data/lib/meshchat/command/online.rb +0 -15
  90. data/lib/meshchat/command/ping.rb +0 -65
  91. data/lib/meshchat/command/ping_all.rb +0 -15
  92. data/lib/meshchat/command/send_disconnect.rb +0 -15
  93. data/lib/meshchat/command/server.rb +0 -20
  94. data/lib/meshchat/command/share.rb +0 -13
  95. data/lib/meshchat/command/stop_listening.rb +0 -13
  96. data/lib/meshchat/command/whisper.rb +0 -38
  97. data/lib/meshchat/database.rb +0 -30
  98. data/lib/meshchat/display.rb +0 -33
  99. data/lib/meshchat/display/base.rb +0 -60
  100. data/lib/meshchat/display/manager.rb +0 -55
  101. data/lib/meshchat/instance.rb +0 -40
  102. data/lib/meshchat/message.rb +0 -41
  103. data/lib/meshchat/message/base.rb +0 -97
  104. data/lib/meshchat/message/chat.rb +0 -19
  105. data/lib/meshchat/message/disconnect.rb +0 -13
  106. data/lib/meshchat/message/emote.rb +0 -9
  107. data/lib/meshchat/message/node_list.rb +0 -63
  108. data/lib/meshchat/message/node_list_diff.rb +0 -15
  109. data/lib/meshchat/message/node_list_hash.rb +0 -33
  110. data/lib/meshchat/message/ping.rb +0 -32
  111. data/lib/meshchat/message/ping_reply.rb +0 -9
  112. data/lib/meshchat/message/relay.rb +0 -43
  113. data/lib/meshchat/message/whisper.rb +0 -36
  114. data/lib/meshchat/models/entry.rb +0 -104
  115. data/lib/meshchat/net/client.rb +0 -83
  116. data/lib/meshchat/net/listener/errors.rb +0 -11
  117. data/lib/meshchat/net/listener/request.rb +0 -48
  118. data/lib/meshchat/net/listener/request_processor.rb +0 -50
  119. data/lib/meshchat/net/listener/server.rb +0 -114
  120. data/lib/meshchat/net/request.rb +0 -29
  121. data/lib/meshchat/notifier/base.rb +0 -31
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+ module Meshchat
3
+ # This file is stupid.
4
+ # But very helpful when debugging problems...
5
+ module Debug
6
+ module_function
7
+
8
+ # TODO: extract this idea to a gem
9
+ # - automatic logging of method calls
10
+ def log(method_list)
11
+ method_list = Array[method_list]
12
+ method_list.each do |method|
13
+ backup_name = "#{method}_bak".to_sym
14
+ alias_method :backup_name, :method
15
+ define_method(method) do |*args|
16
+ Display.debug("##{method}: ")
17
+ Display.debug(args.inspect)
18
+ end
19
+ end
20
+ end
21
+
22
+ def message_type_not_found(type)
23
+ Display.debug('Type not found: ' + type.to_s)
24
+ end
25
+
26
+ def not_on_local_network(node)
27
+ Display.debug('SENDING: ' + node.alias_name + ' is not on the local network')
28
+ end
29
+
30
+ def received_message_from_relay(message, relay_url)
31
+ Display.debug('RECEIVING on RELAY: ' + relay_url)
32
+ Display.debug('RECEIVING on RELAY: ')
33
+ Display.debug(message)
34
+ end
35
+
36
+ def sending_message_over_relay(node, message, relay_url)
37
+ Display.debug('SENDING on RELAY: ' + relay_url)
38
+ Display.debug('SENDING on RELAY: ' + node.as_json.to_json)
39
+ Display.debug('SENDING on RELAY: ' + message.class.name)
40
+ Display.debug('SENDING on RELAY: ' + message.inspect)
41
+ end
42
+
43
+ def receiving_message(message)
44
+ Display.debug('RECEIVING: ' + message.type)
45
+ Display.debug('RECEIVING: ' + message.sender.to_s)
46
+ Display.debug('RECEIVING: ' + message.message.to_s)
47
+ end
48
+
49
+ def sending_message(message)
50
+ Display.debug('SENDING: ' + message.type)
51
+ Display.debug('SENDING: ' + message.sender.to_s)
52
+ Display.debug('SENDING: ' + message.message.to_s)
53
+ end
54
+
55
+ def person_not_online(node, message, e)
56
+ Display.debug("#{message.class.name}: Issue connectiong to #{node.alias_name}@#{node.location}")
57
+ Display.debug(e.message)
58
+ end
59
+
60
+ def encryption_failed(node)
61
+ Display.info "Public key encryption for #{node.try(:alias_name) || 'unknown'} failed"
62
+ end
63
+
64
+ def creating_input_failed(e)
65
+ Display.error e.message
66
+ Display.error e.backtrace.join("\n").colorize(:red)
67
+ end
68
+ end
69
+ end
@@ -1,8 +1,14 @@
1
+ # frozen_string_literal: true
1
2
  require 'meshchat/encryption/aes_rsa'
2
3
  require 'meshchat/encryption/passthrough'
3
4
 
4
- module MeshChat
5
+ module Meshchat
5
6
  module Encryption
7
+ extend ActiveSupport::Autoload
8
+
9
+ autoload :AES_RSA
10
+ autoload :Passthrough
11
+
6
12
  module_function
7
13
 
8
14
  DEFAULT_ENCRYPTOR = AES_RSA
@@ -22,6 +28,5 @@ module MeshChat
22
28
  def encrypt(*args)
23
29
  current_encryptor.encrypt(*args)
24
30
  end
25
-
26
31
  end
27
32
  end
@@ -1,4 +1,5 @@
1
- module MeshChat
1
+ # frozen_string_literal: true
2
+ module Meshchat
2
3
  module Encryption
3
4
  # Using AES266-CBC with RSA
4
5
  #
@@ -1,15 +1,17 @@
1
- module MeshChat
1
+ # frozen_string_literal: true
2
+ module Meshchat
2
3
  module Encryption
3
4
  # This should normally be for testing
4
5
  #
5
6
  # It just retuns the message that is asked to be encrypted
6
7
  module Passthrough
7
8
  module_function
8
- def encrypt(msg, *args)
9
+
10
+ def encrypt(msg, *_args)
9
11
  msg
10
12
  end
11
13
 
12
- def decrypt(msg, *args)
14
+ def decrypt(msg, *_args)
13
15
  msg
14
16
  end
15
17
  end
@@ -0,0 +1,14 @@
1
+ confirm_options: ' (Y/N)'
2
+ confirm_yes: yes
3
+ confirm_y: y
4
+ identity:
5
+ settings_not_detected: a settings file was not detected or incomplete, would you like to generate one?
6
+ unknown_error_try_again: something went wrong with identity creation. try again?
7
+ settings_are_invalid: settings are invalid
8
+ confirm_uid_replace: uid exists, are you sure you want a new identity?
9
+ confirm_alias_replace: alias exists, would you like to overwrite it?
10
+ confirm_key_replace: keys exist, overwrite?
11
+ ask_for_alias: 'Type the name/alias you would like to go by in meshchat:'
12
+ current_alias: 'Your current name/alias is: %{name}'
13
+ node:
14
+ not_found: "Node not found, or does not have a location. Have you imported %{name}?"
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+ module Meshchat
3
+ class Node < ActiveRecord::Base
4
+ IPV4_WITH_PORT = /((?:(?:^|\.)(?:\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])){4})(:\d*)?/
5
+ # http://rubular.com/r/WYT09ptct3
6
+ DOMAIN_WITH_PORT = /(https?:\/\/)?([\da-z\.-]+)\.?([a-z\.]{2,6})([\/\w \.-]*)*[^\/](:\d*)?/
7
+
8
+ validates :alias_name,
9
+ :location,
10
+ :public_key, presence: true
11
+
12
+ validates :uid, presence: true, uniqueness: true
13
+
14
+ # ipv4 with port
15
+ validates_format_of :location_on_network, with: lambda { |e|
16
+ location_on_network = e.location_on_network || ''
17
+ is_domain = location_on_network.include?('//') ||
18
+ location_on_network.include?('localhost')
19
+
20
+ is_domain ? DOMAIN_WITH_PORT : IPV4_WITH_PORT
21
+ }
22
+
23
+ scope :on_local_network, -> { where(on_local_network: true) }
24
+ scope :on_relay, -> { where(on_relay: true) }
25
+ scope :online, -> { on_local_network.or(on_relay) }
26
+
27
+ class << self
28
+ def sha_preimage
29
+ all.map(&:public_key).sort.join(',')
30
+ end
31
+
32
+ def as_sha512
33
+ digest = Digest::SHA512.new
34
+ digest.hexdigest sha_preimage
35
+ end
36
+
37
+ def as_json
38
+ # must also include ourselves
39
+ # so that we can pass our own public key
40
+ # to those who don't have it
41
+ others = all.map(&:as_json)
42
+ me = APP_CONFIG.user.identity_as_json
43
+ others << me
44
+ end
45
+
46
+ def from_json(json)
47
+ new(
48
+ alias_name: json['alias'],
49
+ location_on_network: json['location'],
50
+ uid: json['uid'],
51
+ public_key: json['publickey']
52
+ )
53
+ end
54
+
55
+ def public_key_from_uid(uid)
56
+ find_by_uid(uid).try(:public_key)
57
+ end
58
+
59
+ # @param [Array] theirs array of hashes representing node entries
60
+ # @return [Array<-,+>] nodes only we have, and nodes only they have
61
+ def diff(theirs)
62
+ ours = as_json
63
+ we_only_have = ours - theirs
64
+ they_only_have = theirs - ours
65
+
66
+ [we_only_have, they_only_have]
67
+ end
68
+
69
+ def import_from_file(filename)
70
+ f = File.read(filename)
71
+ hash = JSON.parse(f)
72
+ n = from_json(hash)
73
+ n.save
74
+ n
75
+ rescue => e
76
+ Display.alert e.message
77
+ end
78
+
79
+ # Try to find the node, given a location, or uid
80
+ #
81
+ # TODO: do we want to also be able to find by relay address?
82
+ # - this would be non-unique
83
+ # - maybe finding should only happen via UID
84
+ # @param [String] location - the local network address
85
+ # @param [String] uid - the node's UID
86
+ # @param [Node] node - the node
87
+ # @return [Node]
88
+ def for(location: nil, uid: nil, node: nil)
89
+ unless node
90
+ node = Node.find_by_location_on_network(location) if location
91
+ node = Node.find_by_uid(uid) if uid && !node
92
+ end
93
+
94
+ unless node && node.valid?
95
+ msg = I18n.t('node.not_found', name: location || uid || '')
96
+ return Display.alert msg
97
+ end
98
+
99
+ node
100
+ end
101
+ end
102
+
103
+ def ==(other)
104
+ result = false
105
+
106
+ if other.is_a?(Hash)
107
+ result = as_json.values_at(*other.keys) == other.values
108
+ end
109
+
110
+ result || super
111
+ end
112
+
113
+ def online
114
+ on_relay? || on_local_network?
115
+ end
116
+ alias_method :online?, :online
117
+
118
+ def location
119
+ return location_of_relay if on_relay?
120
+ location_on_network
121
+ end
122
+
123
+ def location_is_web_socket?
124
+ location.match(/wss?/).present?
125
+ end
126
+
127
+ def as_json
128
+ {
129
+ 'alias' => alias_name,
130
+ 'location' => location_on_network,
131
+ 'uid' => uid,
132
+ 'publickey' => public_key
133
+ }
134
+ end
135
+
136
+ def as_info
137
+ "#{alias_name}@#{location}"
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+ module Meshchat
3
+ module Network
4
+ extend ActiveSupport::Autoload
5
+ NETWORK_LOCAL = :local
6
+ NETWORK_RELAY = :relay
7
+
8
+ eager_autoload do
9
+ autoload :Errors
10
+ autoload :Message
11
+ autoload :Dispatcher
12
+ autoload :Incoming
13
+ autoload :Local
14
+ autoload :Remote
15
+ autoload :MessageProcessor
16
+ autoload :RequestProcessor
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+ module Meshchat
3
+ module Network
4
+ class Dispatcher
5
+ # creates messages
6
+ attr_reader :_message_factory
7
+
8
+ # standard peer-to-peer message sending
9
+ attr_reader :_local_client
10
+
11
+ # the action cable client ( web socket / connection beyond the firewall)
12
+ # - responsible for the relay server if the http client can't find the recipient
13
+ attr_reader :_relay_client
14
+
15
+ def initialize
16
+ @_message_factory = Message::Factory.new(self)
17
+ @_local_client = Local::Connection.new(self, @_message_factory)
18
+ @_relay_client = Remote::Connection.new(self, @_message_factory)
19
+ end
20
+
21
+ # @note Either the location, node, or uid should be present
22
+ #
23
+ # @param [String] location (Optional) location of target
24
+ # @param [String] uid (Optional) uid of target
25
+ # @param [Node] node (Optional) target
26
+ # @param [Message] message (Required) what to send to the target
27
+ def send_message(location: nil, uid: nil, node: nil, message: nil)
28
+ # verify node is valid
29
+ node = Node.for(location: location, uid: uid, node: node)
30
+ # don't proceed if we don't have a node
31
+ return unless node
32
+ # don't send to ourselves
33
+ return if APP_CONFIG.user['uid'] == node.uid
34
+
35
+ # everything is valid so far... DISPATCH!
36
+ dispatch!(node, message)
37
+ end
38
+
39
+ private
40
+
41
+ def dispatch!(node, message)
42
+ Debug.sending_message(message)
43
+
44
+ message = encrypted_message(node, message)
45
+
46
+ # determine last known sending method
47
+ if node.on_local_network?
48
+ try_dispatching_over_local_network_first(node, message)
49
+ else
50
+ try_dispatching_over_the_relay_first(node, message)
51
+ end
52
+ end
53
+
54
+ # this attempts to send over http to the local network,
55
+ # if that fails, the passed block will be invoked
56
+ def try_dispatching_over_local_network_first(node, message)
57
+ _local_client.send_message(node, message) do
58
+ Debug.not_on_local_network(node)
59
+ node.update(on_local_network: false)
60
+ _relay_client.send_message(node, message)
61
+ end
62
+ end
63
+
64
+ # this attempts to send over the relay first
65
+ # if that fails, the passed block will be invked
66
+ def try_dispatching_over_the_relay_first(node, message)
67
+ # Due to the constant-connection nature of web-sockets,
68
+ # The sending via http client will happen if the node's-
69
+ # on_local_network property is true.
70
+ # node.update(on_local_network: true)
71
+ _relay_client.send_message(node, message)
72
+ end
73
+
74
+ def encrypted_message(node, message)
75
+ message.encrypt_for(node)
76
+ rescue => e
77
+ Display.debug e.message
78
+ Display.debug e.backtrace
79
+ Debug.encryption_failed(node)
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+ module Meshchat
3
+ module Network
4
+ module Errors
5
+ class NotAuthorized < StandardError; end
6
+ class Forbidden < StandardError; end
7
+ class BadRequest < StandardError; end
8
+ class MessageTypeNotRecognized < StandardError; end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+ module Meshchat
3
+ module Network
4
+ module Incoming
5
+ extend ActiveSupport::Autoload
6
+ eager_autoload do
7
+ autoload :MessageProcessor
8
+ autoload :RequestProcessor
9
+ autoload :MessageDecryptor
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+ module Meshchat
3
+ module Network
4
+ module Incoming
5
+ class MessageDecryptor
6
+ attr_reader :_json, :_message, :_input
7
+ attr_reader :_message_factory
8
+
9
+ def initialize(encoded_message, message_factory)
10
+ @_message_factory = message_factory
11
+ @_input = try_decrypt(encoded_message)
12
+ @_json = parse_json(@_input)
13
+ @_message = process_json
14
+ end
15
+
16
+ def message
17
+ _message
18
+ end
19
+
20
+ private
21
+
22
+ def parse_json(input)
23
+ return JSON.parse(input)
24
+ rescue => e
25
+ Display.debug e.message
26
+ Display.debug e.backtrace.join("\n")
27
+ raise Errors::BadRequest, 'could not parse json'
28
+ end
29
+
30
+ def try_decrypt(input)
31
+ begin
32
+ decoded = Base64.decode64(input)
33
+ input = Encryption.decrypt(decoded, APP_CONFIG.user[:privatekey])
34
+ rescue => e
35
+ Display.debug e.message
36
+ Display.debug e.backtrace.join("\n")
37
+ Display.debug input
38
+ raise Errors::NotAuthorized, e.message
39
+ end
40
+
41
+ input
42
+ end
43
+
44
+ def process_json
45
+ type = _json['type']
46
+ _message_factory.create(type, data: { payload: _json })
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end