selfsdk 0.0.124

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.
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'time'
4
+ require 'net/ntp'
5
+
6
+ module SelfSDK
7
+ class Time
8
+ @@last_check = nil
9
+ @diff = nil
10
+ def self.now
11
+ timeout = 1
12
+ ntp_time = nil
13
+ 5.times do
14
+ begin
15
+ ntp_time = get_ntp_current_time
16
+ break
17
+ rescue Timeout::Error
18
+ puts "time.google.com timed out, retrying in #{timeout} seconds..."
19
+ sleep timeout
20
+ timeout = timeout+1
21
+ end
22
+ end
23
+ raise Timeout::Error.new("ntp sync timed out") if ntp_time.nil?
24
+ ntp_time
25
+ end
26
+
27
+ private
28
+
29
+ def self.get_ntp_current_time
30
+ seconds_to_expire = 60
31
+
32
+ return ::Time.now.utc if ENV["RAKE_ENV"] == "test"
33
+
34
+ if @diff.nil?
35
+ self.sync
36
+ return @now
37
+ end
38
+ @now = (::Time.now + @diff).utc
39
+ if @@last_check + seconds_to_expire < @now
40
+ self.sync
41
+ end
42
+ @now
43
+ end
44
+
45
+ def self.sync
46
+ @@last_check = ::Time.parse(Net::NTP.get("time.google.com", "ntp", 2).time.to_s).utc
47
+ @diff = (@@last_check - ::Time.now.utc).abs
48
+ @now = (::Time.now + @diff).utc
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,19 @@
1
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
2
+ # source: acl.proto
3
+
4
+ require 'google/protobuf'
5
+
6
+ require_relative 'msgtype_pb'
7
+ require_relative 'aclcommand_pb'
8
+ Google::Protobuf::DescriptorPool.generated_pool.build do
9
+ add_message "msgproto.AccessControlList" do
10
+ optional :type, :enum, 1, "msgproto.MsgType"
11
+ optional :id, :string, 2
12
+ optional :command, :enum, 3, "msgproto.ACLCommand"
13
+ optional :payload, :bytes, 4
14
+ end
15
+ end
16
+
17
+ module Msgproto
18
+ AccessControlList = Google::Protobuf::DescriptorPool.generated_pool.lookup("msgproto.AccessControlList").msgclass
19
+ end
@@ -0,0 +1,16 @@
1
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
2
+ # source: aclcommand.proto
3
+
4
+ require 'google/protobuf'
5
+
6
+ Google::Protobuf::DescriptorPool.generated_pool.build do
7
+ add_enum "msgproto.ACLCommand" do
8
+ value :LIST, 0
9
+ value :PERMIT, 1
10
+ value :REVOKE, 2
11
+ end
12
+ end
13
+
14
+ module Msgproto
15
+ ACLCommand = Google::Protobuf::DescriptorPool.generated_pool.lookup("msgproto.ACLCommand").enummodule
16
+ end
@@ -0,0 +1,19 @@
1
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
2
+ # source: auth.proto
3
+
4
+ require 'google/protobuf'
5
+
6
+ require_relative 'msgtype_pb'
7
+ Google::Protobuf::DescriptorPool.generated_pool.build do
8
+ add_message "msgproto.Auth" do
9
+ optional :type, :enum, 1, "msgproto.MsgType"
10
+ optional :id, :string, 2
11
+ optional :token, :string, 3
12
+ optional :device, :string, 4
13
+ optional :offset, :int64, 5
14
+ end
15
+ end
16
+
17
+ module Msgproto
18
+ Auth = Google::Protobuf::DescriptorPool.generated_pool.lookup("msgproto.Auth").msgclass
19
+ end
@@ -0,0 +1,19 @@
1
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
2
+ # source: errtype.proto
3
+
4
+ require 'google/protobuf'
5
+
6
+ Google::Protobuf::DescriptorPool.generated_pool.build do
7
+ add_enum "msgproto.ErrType" do
8
+ value :ErrConnection, 0
9
+ value :ErrBadRequest, 1
10
+ value :ErrInternal, 2
11
+ value :ErrMessage, 3
12
+ value :ErrAuth, 4
13
+ value :ErrACL, 5
14
+ end
15
+ end
16
+
17
+ module Msgproto
18
+ ErrType = Google::Protobuf::DescriptorPool.generated_pool.lookup("msgproto.ErrType").enummodule
19
+ end
@@ -0,0 +1,16 @@
1
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
2
+ # source: header.proto
3
+
4
+ require 'google/protobuf'
5
+
6
+ require_relative 'msgtype_pb'
7
+ Google::Protobuf::DescriptorPool.generated_pool.build do
8
+ add_message "msgproto.Header" do
9
+ optional :type, :enum, 1, "msgproto.MsgType"
10
+ optional :id, :string, 2
11
+ end
12
+ end
13
+
14
+ module Msgproto
15
+ Header = Google::Protobuf::DescriptorPool.generated_pool.lookup("msgproto.Header").msgclass
16
+ end
@@ -0,0 +1,22 @@
1
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
2
+ # source: message.proto
3
+
4
+ require 'google/protobuf'
5
+
6
+ require_relative 'msgtype_pb'
7
+ require 'google/protobuf/timestamp_pb'
8
+ Google::Protobuf::DescriptorPool.generated_pool.build do
9
+ add_message "msgproto.Message" do
10
+ optional :type, :enum, 1, "msgproto.MsgType"
11
+ optional :id, :string, 2
12
+ optional :sender, :string, 3
13
+ optional :recipient, :string, 4
14
+ optional :ciphertext, :bytes, 5
15
+ optional :timestamp, :message, 6, "google.protobuf.Timestamp"
16
+ optional :offset, :int64, 7
17
+ end
18
+ end
19
+
20
+ module Msgproto
21
+ Message = Google::Protobuf::DescriptorPool.generated_pool.lookup("msgproto.Message").msgclass
22
+ end
@@ -0,0 +1,18 @@
1
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
2
+ # source: msgtype.proto
3
+
4
+ require 'google/protobuf'
5
+
6
+ Google::Protobuf::DescriptorPool.generated_pool.build do
7
+ add_enum "msgproto.MsgType" do
8
+ value :MSG, 0
9
+ value :ACK, 1
10
+ value :ERR, 2
11
+ value :AUTH, 3
12
+ value :ACL, 4
13
+ end
14
+ end
15
+
16
+ module Msgproto
17
+ MsgType = Google::Protobuf::DescriptorPool.generated_pool.lookup("msgproto.MsgType").enummodule
18
+ end
@@ -0,0 +1,19 @@
1
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
2
+ # source: notification.proto
3
+
4
+ require 'google/protobuf'
5
+
6
+ require_relative 'msgtype_pb'
7
+ require_relative 'errtype_pb'
8
+ Google::Protobuf::DescriptorPool.generated_pool.build do
9
+ add_message "msgproto.Notification" do
10
+ optional :type, :enum, 1, "msgproto.MsgType"
11
+ optional :id, :string, 2
12
+ optional :error, :string, 3
13
+ optional :errtype, :enum, 4, "msgproto.ErrType"
14
+ end
15
+ end
16
+
17
+ module Msgproto
18
+ Notification = Google::Protobuf::DescriptorPool.generated_pool.lookup("msgproto.Notification").msgclass
19
+ end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+ require "ed25519"
5
+ require 'json'
6
+ require 'net/http'
7
+ require 'rqrcode'
8
+ require_relative 'log'
9
+ require_relative 'jwt_service'
10
+ require_relative 'client'
11
+ require_relative 'messaging'
12
+ require_relative 'ntptime'
13
+ require_relative 'authenticated'
14
+ require_relative 'acl'
15
+ require_relative 'sources'
16
+ require_relative 'services/auth'
17
+ require_relative 'services/facts'
18
+ require_relative 'services/identity'
19
+ require_relative 'services/messaging'
20
+
21
+ # Namespace for classes and modules that handle Self interactions.
22
+ module SelfSDK
23
+ # Abstract base class for CLI utilities. Provides some helper methods for
24
+ # the option parser
25
+ #
26
+ # @attr_reader [Types] app_id the identifier of the current app.
27
+ # @attr_reader [Types] app_key the api key for the current app.
28
+ class App
29
+ BASE_URL = "https://api.selfid.net".freeze
30
+ MESSAGING_URL = "wss://messaging.selfid.net/v1/messaging".freeze
31
+
32
+ attr_reader :client
33
+ attr_accessor :messaging_client
34
+
35
+ # Initializes a SelfSDK App
36
+ #
37
+ # @param app_id [string] the app id.
38
+ # @param app_key [string] the app api key provided by developer portal.
39
+ # @param storage_key [string] the key to be used to encrypt persisted data.
40
+ # @param [Hash] opts the options to authenticate.
41
+ # @option opts [String] :base_url The self provider url.
42
+ # @option opts [String] :messaging_url The messaging self provider url.
43
+ # @option opts [Bool] :auto_reconnect Automatically reconnects to websocket if connection is lost (defaults to true).
44
+ # @option opts [Symbol] :env The environment to be used, defaults to ":production".
45
+ # @option opts [String] :storage_dir The folder where encryption sessions and settings will be stored
46
+ def initialize(app_id, app_key, storage_key, opts = {})
47
+ SelfSDK.logger.debug "syncing ntp times #{SelfSDK::Time.now}"
48
+ env = opts.fetch(:env, "")
49
+
50
+ @client = RestClient.new(base_url(opts), app_id, app_key, env)
51
+ messaging_url = messaging_url(opts)
52
+ unless messaging_url.nil?
53
+ @messaging_client = MessagingClient.new(messaging_url,
54
+ @client,
55
+ storage_dir: opts.fetch(:storage_dir, MessagingClient::DEFAULT_STORAGE_DIR),
56
+ auto_reconnect: opts.fetch(:auto_reconnect, MessagingClient::DEFAULT_AUTO_RECONNECT),
57
+ device_id: opts.fetch(:device_id, MessagingClient::DEFAULT_DEVICE))
58
+ end
59
+ end
60
+
61
+ # Provides access to SelfSDK::Services::Facts service
62
+ def facts
63
+ @facts ||= SelfSDK::Services::Facts.new(@messaging_client, @client)
64
+ end
65
+
66
+ # Provides access to SelfSDK::Services::Authentication service
67
+ def authentication
68
+ @authentication ||= SelfSDK::Services::Authentication.new(@messaging_client, @client)
69
+ end
70
+
71
+ # Provides access to SelfSDK::Services::Identity service
72
+ def identity
73
+ @identity ||= SelfSDK::Services::Identity.new(@client)
74
+ end
75
+
76
+ # Provides access to SelfSDK::Services::Messaging service
77
+ def messaging
78
+ @messaging ||= SelfSDK::Services::Messaging.new(@messaging_client)
79
+ end
80
+
81
+ def app_id
82
+ client.jwt.id
83
+ end
84
+
85
+ def app_key
86
+ client.jwt.key
87
+ end
88
+
89
+ # Closes the websocket connection
90
+ def close
91
+ @messaging_client.close
92
+ end
93
+
94
+ protected
95
+
96
+ def base_url(opts)
97
+ return opts[:base_url] if opts.key? :base_url
98
+ return "https://api.#{opts[:env].to_s}.selfid.net" if opts.key? :env
99
+ BASE_URL
100
+ end
101
+
102
+ def messaging_url(opts)
103
+ return opts[:messaging_url] if opts.key? :messaging_url
104
+ return "wss://messaging.#{opts[:env].to_s}.selfid.net/v1/messaging" if opts.key? :env
105
+ MESSAGING_URL
106
+ end
107
+
108
+ end
109
+ end
@@ -0,0 +1,163 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Namespace for classes and modules that handle SelfSDK gem
4
+ module SelfSDK
5
+ # Namespace for classes and modules that handle selfsdk-gem public ui
6
+ module Services
7
+ # Input class to handle authentication requests on self network.
8
+ class Authentication
9
+ # Creates a new authentication service.
10
+ # Authentication service mainly manages authentication requests against self
11
+ # users wanting to authenticate on your app.
12
+ #
13
+ # @param messaging [SelfSDK::Messaging] messaging object.
14
+ # @param client [SelfSDK::Client] http client object.
15
+ #
16
+ # @return [SelfSDK::Services::Authentication] authentication service.
17
+ def initialize(messaging, client)
18
+ @messaging = messaging
19
+ @client = client
20
+ end
21
+
22
+ # Sends an authentication request to the specified selfid.
23
+ # An authentication requests allows your users to authenticate on your app using
24
+ # a secure self app.
25
+ #
26
+ # @overload request(selfid, opts = {}, &block)
27
+ # @param [String] selfid the receiver of the authentication request.
28
+ # @param [Hash] opts the options to authenticate.
29
+ # @option opts [String] :cid The unique identifier of the authentication request.
30
+ # @yield [request] Invokes the block with an authentication response for each result.
31
+ # @return [String, String] conversation id or encoded body.
32
+ #
33
+ # @overload request(selfid, opts = {})
34
+ # @param [String] selfid the receiver of the authentication request.
35
+ # @param [Hash] opts the options to authenticate.
36
+ # @option [Boolean] :async if the request is asynchronous.
37
+ # @option opts [String] :cid The unique identifier of the authentication request.
38
+ # @return [String, String] conversation id or encoded body.
39
+ def request(selfid, opts = {}, &block)
40
+ SelfSDK.logger.info "authenticating #{selfid}"
41
+
42
+ req = SelfSDK::Messages::AuthenticationReq.new(@messaging)
43
+ req.populate(selfid, opts)
44
+
45
+ body = @client.jwt.prepare(req.body)
46
+ return body unless opts.fetch(:request, true)
47
+ return req.send_message if opts.fetch(:async, false)
48
+
49
+ # when a block is given the request will always be asynchronous.
50
+ if block_given?
51
+ @messaging.set_observer(req, timeout: req.exp_timeout, &block)
52
+ return req.send_message
53
+ end
54
+
55
+ # Otherwise the request is synchronous
56
+ req.request
57
+ end
58
+
59
+ # Generates a QR code so users can authenticate to your app.
60
+ #
61
+ # @option opts [String] :selfid the user selfid you want to authenticate.
62
+ # @option opts [String] :cid The unique identifier of the authentication request.
63
+ #
64
+ # @return [String, String] conversation id or encoded body.
65
+ def generate_qr(opts = {})
66
+ opts[:request] = false
67
+ selfid = opts.fetch(:selfid, "-")
68
+ req = request(selfid, opts)
69
+ ::RQRCode::QRCode.new(req, level: 'l')
70
+ end
71
+
72
+ # Generates a deep link to authenticate with self app.
73
+ #
74
+ # @param callback [String] the url you'll be redirected if the app is not installed.
75
+ # @option opts [String] :selfid the user selfid you want to authenticate.
76
+ # @option opts [String] :cid The unique identifier of the authentication request.
77
+ #
78
+ # @return [String, String] conversation id or encoded body.
79
+ def generate_deep_link(callback, opts = {})
80
+ opts[:request] = false
81
+ selfid = opts.fetch(:selfid, "-")
82
+ body = @client.jwt.encode(request(selfid, opts))
83
+
84
+ if @client.env.empty?
85
+ return "https://selfid.page.link/?link=#{callback}%3Fqr=#{body}&apn=net.selfid.app"
86
+ elsif @client.env == 'development'
87
+ return "https://selfid.page.link/?link=#{callback}%3Fqr=#{body}&apn=net.selfid.app.dev"
88
+ end
89
+ "https://selfid.page.link/?link=#{callback}%3Fqr=#{body}&apn=net.selfid.app.#{@client.env}"
90
+ end
91
+
92
+ # Adds an observer for an authentication response
93
+ def subscribe(&block)
94
+ @messaging.subscribe :authentication_response do |res|
95
+ valid_payload(res.input)
96
+ yield(res)
97
+ end
98
+ end
99
+
100
+ private
101
+
102
+ # Checks if the given input is an accepted authentication request.
103
+ #
104
+ # @param response [string] the response to an authentication request from self-api.
105
+ # @return [Hash] Details response.
106
+ # * :accepted [Boolean] a bool describing if authentication is accepted or not.
107
+ # * :uuid [String] the request identifier.
108
+ def authenticated?(response)
109
+ Authenticated.new(valid_payload(response))
110
+ end
111
+
112
+ # checks if a payload is valid or not.
113
+ #
114
+ # @param response [string] the response to an authentication request from self-api.
115
+ def valid_payload(response)
116
+ parse_payload(response)
117
+ rescue StandardError => e
118
+ uuid = ""
119
+ uuid = response[:cid] unless response.nil?
120
+ SelfSDK.logger.error "error checking authentication for #{uuid} : #{e.message}"
121
+ p e.backtrace
122
+ nil
123
+ end
124
+
125
+ # Prepares an authentication payload to be sent to a user.
126
+ #
127
+ # @param selfid [string] the selfid of the user you want to send the auth request to.
128
+ # @param cid [string] the conversation id to be used.
129
+ def prepare_payload(selfid, cid)
130
+ # TODO should this be moved to its own message/auth_req.rb?
131
+ body = {
132
+ typ: 'identities.authenticate.req',
133
+ aud: @client.self_url,
134
+ iss: @client.jwt.id,
135
+ sub: selfid,
136
+ iat: SelfSDK::Time.now.strftime('%FT%TZ'),
137
+ exp: (SelfSDK::Time.now + 3600).strftime('%FT%TZ'),
138
+ cid: cid,
139
+ jti: SecureRandom.uuid,
140
+ device_id: @messaging.device_id,
141
+ }
142
+
143
+ @client.jwt.prepare(body)
144
+ end
145
+
146
+ def parse_payload(response)
147
+ jws = @client.jwt.parse(response)
148
+ return unless jws.include? :payload
149
+
150
+ payload = JSON.parse(@client.jwt.decode(jws[:payload]), symbolize_names: true)
151
+ return if payload.nil?
152
+
153
+ identity = @client.entity(payload[:sub])
154
+ return if identity.nil?
155
+
156
+ identity[:public_keys].each do |key|
157
+ return payload if @client.jwt.verify(jws, key[:key])
158
+ end
159
+ nil
160
+ end
161
+ end
162
+ end
163
+ end