magic-admin 0.0.0 → 0.1.0
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/.github/ISSUE_TEMPLATE/bug_report.md +42 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +23 -0
- data/.github/ISSUE_TEMPLATE/question.md +23 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +23 -0
- data/.gitignore +7 -0
- data/.rspec +6 -0
- data/.rubocop.yml +45 -0
- data/CHANGELOG.md +17 -0
- data/CONTRIBUTING.md +142 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +73 -0
- data/bin/magic-console +14 -0
- data/lib/magic-admin.rb +135 -0
- data/lib/magic-admin/config.rb +71 -0
- data/lib/magic-admin/errors.rb +98 -0
- data/lib/magic-admin/http/client.rb +136 -0
- data/lib/magic-admin/http/request.rb +89 -0
- data/lib/magic-admin/http/response.rb +95 -0
- data/lib/magic-admin/resource/token.rb +146 -0
- data/lib/magic-admin/resource/user.rb +130 -0
- data/lib/magic-admin/util.rb +61 -0
- data/lib/magic-admin/version.rb +5 -0
- data/magic-admin.gemspec +36 -0
- data/test/http/client_test.rb +79 -0
- data/test/http/request_test.rb +52 -0
- data/test/http/response_test.rb +192 -0
- data/test/magic_test.rb +124 -0
- data/test/resource/token_test.rb +187 -0
- data/test/resource/user_test.rb +98 -0
- data/test/spec_helper.rb +48 -0
- data/test/util_test.rb +42 -0
- metadata +140 -15
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MagicAdmin
|
4
|
+
|
5
|
+
module Http
|
6
|
+
|
7
|
+
# Http Request and its methods are accessible
|
8
|
+
# on the Magic instance by the http_client.http_request attribute.
|
9
|
+
# It provides methods to interact with the http_request.
|
10
|
+
class Request
|
11
|
+
|
12
|
+
class << self
|
13
|
+
# Description:
|
14
|
+
# Method configure request object and provides request object
|
15
|
+
# based on method argument.
|
16
|
+
#
|
17
|
+
# Arguments:
|
18
|
+
# method: http method
|
19
|
+
# url: get request url
|
20
|
+
# options: a hash contains params and headers for request
|
21
|
+
#
|
22
|
+
# Returns:
|
23
|
+
# A request object.
|
24
|
+
def request(method, url, options)
|
25
|
+
case method
|
26
|
+
when :get, "get" then new.get(url, options)
|
27
|
+
when :post, "post" then new.post(url, options)
|
28
|
+
else
|
29
|
+
raise APIError.new("Request method not supported.", { http_method: method })
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Description:
|
35
|
+
# Method configure request object and provides you get request object.
|
36
|
+
#
|
37
|
+
# Arguments:
|
38
|
+
# url: get request url
|
39
|
+
# options: a hash contains params and headers for request
|
40
|
+
#
|
41
|
+
# Returns:
|
42
|
+
# A get request object.
|
43
|
+
def get(url, options)
|
44
|
+
headers = options[:headers] || {}
|
45
|
+
params = options[:params] || {}
|
46
|
+
url = url_with_params(url, params)
|
47
|
+
req = Net::HTTP::Get.new(url)
|
48
|
+
request_with_headers(req, headers)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Description:
|
52
|
+
# Method configure request object and provides you post request object.
|
53
|
+
#
|
54
|
+
# Arguments:
|
55
|
+
# url: post request url
|
56
|
+
# options: a hash contains params and headers for request
|
57
|
+
#
|
58
|
+
# Returns:
|
59
|
+
# A post request object.
|
60
|
+
def post(url, options)
|
61
|
+
headers = options[:headers] || {}
|
62
|
+
params = options[:params] || {}
|
63
|
+
req = Net::HTTP::Post.new(url)
|
64
|
+
req = request_with_headers(req, headers)
|
65
|
+
request_with_params(req, params)
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def request_with_headers(req, headers)
|
71
|
+
headers.each do |key, val|
|
72
|
+
req[key.to_s] = val
|
73
|
+
end
|
74
|
+
req
|
75
|
+
end
|
76
|
+
|
77
|
+
def url_with_params(url, params)
|
78
|
+
url.query = URI.encode_www_form(params)
|
79
|
+
url
|
80
|
+
end
|
81
|
+
|
82
|
+
def request_with_params(req, params)
|
83
|
+
req.body = params.to_json
|
84
|
+
req
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MagicAdmin
|
4
|
+
|
5
|
+
module Http
|
6
|
+
|
7
|
+
# Http Request and its methods are accessible
|
8
|
+
# on the Magic instance by the http_client.http_request attribute.
|
9
|
+
# It provides methods to interact with the http_request.
|
10
|
+
class Response
|
11
|
+
|
12
|
+
# attribute reader for response data
|
13
|
+
attr_reader :data
|
14
|
+
|
15
|
+
# attribute reader for response body
|
16
|
+
attr_reader :content
|
17
|
+
|
18
|
+
# attribute reader for response status_code
|
19
|
+
attr_reader :status_code
|
20
|
+
|
21
|
+
# Description:
|
22
|
+
# Method parse Magic API response
|
23
|
+
#
|
24
|
+
# Arguments:
|
25
|
+
# http_resp: Magic API response.
|
26
|
+
# request: request object.
|
27
|
+
#
|
28
|
+
# Returns:
|
29
|
+
# A HTTP Response object or raise an error
|
30
|
+
def self.from_net_http(http_resp, request)
|
31
|
+
resp = Response.new(http_resp)
|
32
|
+
error = case http_resp
|
33
|
+
when Net::HTTPUnauthorized then AuthenticationError
|
34
|
+
when Net::HTTPBadRequest then BadRequestError
|
35
|
+
when Net::HTTPForbidden then ForbiddenError
|
36
|
+
when Net::HTTPTooManyRequests then RateLimitingError
|
37
|
+
when Net::HTTPServerError then APIError
|
38
|
+
when Net::HTTPGatewayTimeout then APIError
|
39
|
+
when Net::HTTPServiceUnavailable then APIError
|
40
|
+
when Net::HTTPBadGateway then APIError
|
41
|
+
end
|
42
|
+
return resp unless error
|
43
|
+
|
44
|
+
raise error.new(resp.data[:message], resp.error_opt(request))
|
45
|
+
end
|
46
|
+
|
47
|
+
# The constructor allows you to create HTTP Response Object
|
48
|
+
# when your application interacting with the Magic API
|
49
|
+
#
|
50
|
+
# Arguments:
|
51
|
+
# http_resp: Magic API response.
|
52
|
+
#
|
53
|
+
# Returns:
|
54
|
+
# A HTTP Response object that provides access to
|
55
|
+
# all the supported resources.
|
56
|
+
#
|
57
|
+
# Examples:
|
58
|
+
# Response.new(<http_resp>)
|
59
|
+
def initialize(http_resp)
|
60
|
+
@content = http_resp.body
|
61
|
+
@data = JSON.parse(http_resp.body, symbolize_names: true)
|
62
|
+
@status_code = http_resp.code.to_i
|
63
|
+
end
|
64
|
+
|
65
|
+
# Description:
|
66
|
+
# Method provides you error info hash
|
67
|
+
#
|
68
|
+
# Arguments:
|
69
|
+
# request: request object.
|
70
|
+
#
|
71
|
+
# Returns:
|
72
|
+
# hash with following keys.
|
73
|
+
# http_status:
|
74
|
+
# status_code:
|
75
|
+
# http_response:
|
76
|
+
# http_message:
|
77
|
+
# http_error_code:
|
78
|
+
# http_request_params:
|
79
|
+
# http_request_header:
|
80
|
+
# http_method:
|
81
|
+
def error_opt(request)
|
82
|
+
{
|
83
|
+
http_status: data[:status],
|
84
|
+
http_code: status_code,
|
85
|
+
http_response: content,
|
86
|
+
http_message: data[:message],
|
87
|
+
http_error_code: data[:error_code],
|
88
|
+
http_request_params: request.body,
|
89
|
+
http_method: request.method
|
90
|
+
}
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MagicAdmin
|
4
|
+
|
5
|
+
module Resource
|
6
|
+
|
7
|
+
# The token resource and its methods are accessible
|
8
|
+
# on the Magic instance by the Token attribute.
|
9
|
+
# It provides methods to interact with the DID Token.
|
10
|
+
class Token
|
11
|
+
|
12
|
+
# Description:
|
13
|
+
# Method validate did_token
|
14
|
+
#
|
15
|
+
# Arguments:
|
16
|
+
# did_token: A DID Token generated by a Magic user on the client-side.
|
17
|
+
#
|
18
|
+
# Returns:
|
19
|
+
# true or raise an error
|
20
|
+
def validate(did_token)
|
21
|
+
time = Time.now.to_i
|
22
|
+
proof, claim = decode(did_token)
|
23
|
+
rec_address = rec_pub_address(claim, proof)
|
24
|
+
|
25
|
+
validate_public_address!(rec_address, did_token)
|
26
|
+
validate_claim_fields!(claim)
|
27
|
+
validate_claim_ext!(time, claim["ext"])
|
28
|
+
validate_claim_nbf!(time, claim["nbf"])
|
29
|
+
end
|
30
|
+
|
31
|
+
# Description:
|
32
|
+
# Method Decodes a DID Token from a Base64 string into
|
33
|
+
# a tuple of its individual components: proof and claim.
|
34
|
+
# This method allows you decode the DID Token
|
35
|
+
# and inspect the token
|
36
|
+
#
|
37
|
+
# Arguments:
|
38
|
+
# did_token: A DID Token generated by a Magic user on the client-side.
|
39
|
+
#
|
40
|
+
# Returns:
|
41
|
+
# An array containing proof and claim or raise an error
|
42
|
+
def decode(did_token)
|
43
|
+
proof = nil
|
44
|
+
claim = nil
|
45
|
+
begin
|
46
|
+
token_array = JSON.parse(base64_decode(did_token))
|
47
|
+
proof = token_array[0]
|
48
|
+
claim = JSON.parse(token_array[1])
|
49
|
+
validate_claim_fields!(claim)
|
50
|
+
rescue JSON::ParserError, ArgumentError
|
51
|
+
raise DIDTokenError, "DID Token is malformed"
|
52
|
+
end
|
53
|
+
[proof, claim]
|
54
|
+
end
|
55
|
+
|
56
|
+
# Description:
|
57
|
+
# Method parse public_address and extract issuer
|
58
|
+
#
|
59
|
+
# Arguments:
|
60
|
+
# public_address: Cryptographic public address of the Magic User.
|
61
|
+
#
|
62
|
+
# Returns:
|
63
|
+
# issuer info
|
64
|
+
def construct_issuer_with_public_address(public_address)
|
65
|
+
"did:ethr:#{public_address}"
|
66
|
+
end
|
67
|
+
|
68
|
+
# Description:
|
69
|
+
# Method parse did_token and extract issuer
|
70
|
+
#
|
71
|
+
# Arguments:
|
72
|
+
# did_token: A DID Token generated by a Magic user on the client-side.
|
73
|
+
#
|
74
|
+
# Returns:
|
75
|
+
# issuer info
|
76
|
+
def get_issuer(did_token)
|
77
|
+
decode(did_token).last["iss"]
|
78
|
+
end
|
79
|
+
|
80
|
+
# Description:
|
81
|
+
# Method parse did_token and extract cryptographic public_address
|
82
|
+
#
|
83
|
+
# Arguments:
|
84
|
+
# did_token: A DID Token generated by a Magic user on the client-side.
|
85
|
+
#
|
86
|
+
# Returns:
|
87
|
+
# cryptographic public address of the Magic User
|
88
|
+
# who generated the supplied DID Token.
|
89
|
+
def get_public_address(did_token)
|
90
|
+
get_issuer(did_token).split(":").last
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
def base64_decode(did_token)
|
96
|
+
Base64.urlsafe_decode64(did_token)
|
97
|
+
end
|
98
|
+
|
99
|
+
def personal_recover(claim, proof)
|
100
|
+
Eth::Key.personal_recover(JSON.dump(claim), proof)
|
101
|
+
end
|
102
|
+
|
103
|
+
def rec_pub_address(claim, proof)
|
104
|
+
Eth::Utils.public_key_to_address personal_recover(claim, proof)
|
105
|
+
end
|
106
|
+
|
107
|
+
def claim_fields
|
108
|
+
%w[iat ext iss sub aud nbf tid]
|
109
|
+
end
|
110
|
+
|
111
|
+
def validate_claim_fields!(claim)
|
112
|
+
missing_fields = claim_fields - claim.keys
|
113
|
+
return true unless missing_fields.any?
|
114
|
+
|
115
|
+
message = "DID Token missing required fields: %s"
|
116
|
+
raise DIDTokenError, message % missing_fields.join(", ")
|
117
|
+
end
|
118
|
+
|
119
|
+
def validate_public_address!(rec_address, did_token)
|
120
|
+
return true if rec_address.eql? get_public_address(did_token)
|
121
|
+
|
122
|
+
message = "Signature mismatch between 'proof' and 'claim'."
|
123
|
+
raise DIDTokenError, message
|
124
|
+
end
|
125
|
+
|
126
|
+
def validate_claim_ext!(time, claim_ext)
|
127
|
+
return true unless time > claim_ext
|
128
|
+
|
129
|
+
message = "Given DID token has expired. Please generate a new one."
|
130
|
+
raise DIDTokenError, message
|
131
|
+
end
|
132
|
+
|
133
|
+
def apply_nbf_grace_period(claim_nbf)
|
134
|
+
claim_nbf - MagicAdmin::Config.nbf_grace_period
|
135
|
+
end
|
136
|
+
|
137
|
+
def validate_claim_nbf!(time, claim_nbf)
|
138
|
+
return true unless time < apply_nbf_grace_period(claim_nbf)
|
139
|
+
|
140
|
+
message = "Given DID token cannot be used at this time."
|
141
|
+
raise DIDTokenError, message
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MagicAdmin
|
4
|
+
|
5
|
+
module Resource
|
6
|
+
|
7
|
+
# The user resource and its methods are accessible
|
8
|
+
# on the Magic instance by the User attribute.
|
9
|
+
# It provides methods to interact with the User.
|
10
|
+
class User
|
11
|
+
|
12
|
+
# attribute reader for magic client object
|
13
|
+
attr_reader :magic
|
14
|
+
|
15
|
+
# The constructor allows you to create user object
|
16
|
+
# when your application interacting with the Magic API
|
17
|
+
#
|
18
|
+
# Arguments:
|
19
|
+
# magic: A Magic object.
|
20
|
+
#
|
21
|
+
# Returns:
|
22
|
+
# A user object that provides access to all the supported resources.
|
23
|
+
#
|
24
|
+
# Examples:
|
25
|
+
# User.new(<magic>)
|
26
|
+
def initialize(magic)
|
27
|
+
@magic = magic
|
28
|
+
end
|
29
|
+
|
30
|
+
# Description:
|
31
|
+
# Method Retrieves information about the user by
|
32
|
+
# the supplied issuer
|
33
|
+
#
|
34
|
+
# Arguments:
|
35
|
+
# issuer: Extracted iss component of DID Token generated by a Magic user
|
36
|
+
# on the client-side.
|
37
|
+
#
|
38
|
+
# Returns:
|
39
|
+
# Metadata information about the user
|
40
|
+
def get_metadata_by_issuer(issuer)
|
41
|
+
headers = MagicAdmin::Util.headers(magic.secret_key)
|
42
|
+
options = { params: { issuer: issuer }, headers: headers }
|
43
|
+
magic.http_client
|
44
|
+
.call(:get, "/v1/admin/auth/user/get", options)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Description:
|
48
|
+
# Method Retrieves information about the user by
|
49
|
+
# the supplied public_address
|
50
|
+
#
|
51
|
+
# Arguments:
|
52
|
+
# public_address: Extracted The user's Ethereum public address component
|
53
|
+
# of DID Token generated by a Magic user on the client-side.
|
54
|
+
#
|
55
|
+
# Returns:
|
56
|
+
# Metadata information about the user
|
57
|
+
def get_metadata_by_public_address(public_address)
|
58
|
+
issuer = token.construct_issuer_with_public_address(public_address)
|
59
|
+
get_metadata_by_issuer(issuer)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Description:
|
63
|
+
# Method Retrieves information about the user by
|
64
|
+
# the supplied DID Token
|
65
|
+
#
|
66
|
+
# Arguments:
|
67
|
+
# did_token: A DID Token generated by a Magic user on the client-side.
|
68
|
+
#
|
69
|
+
# Returns:
|
70
|
+
# Metadata information about the user
|
71
|
+
def get_metadata_by_token(did_token)
|
72
|
+
issuer = token.get_issuer(did_token)
|
73
|
+
get_metadata_by_issuer(issuer)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Description:
|
77
|
+
# Method logs a user out of all Magic SDK sessions by
|
78
|
+
# the supplied issuer
|
79
|
+
#
|
80
|
+
# Arguments:
|
81
|
+
# issuer: Extracted iss component of DID Token generated by a Magic user
|
82
|
+
# on the client-side.
|
83
|
+
#
|
84
|
+
# Returns:
|
85
|
+
# Magic Response
|
86
|
+
def logout_by_issuer(issuer)
|
87
|
+
headers = MagicAdmin::Util.headers(magic.secret_key)
|
88
|
+
options = { params: { issuer: issuer }, headers: headers }
|
89
|
+
magic.http_client
|
90
|
+
.call(:post, "/v2/admin/auth/user/logout", options)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Description:
|
94
|
+
# Method logs a user out of all Magic SDK sessions by
|
95
|
+
# the supplied public_address
|
96
|
+
#
|
97
|
+
# Arguments:
|
98
|
+
# public_address: Extracted the user's Ethereum public address component
|
99
|
+
# of DID Token generated by a Magic user on the client-side.
|
100
|
+
#
|
101
|
+
# Returns:
|
102
|
+
# Magic Response
|
103
|
+
def logout_by_public_address(public_address)
|
104
|
+
issuer = token.construct_issuer_with_public_address(public_address)
|
105
|
+
logout_by_issuer(issuer)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Description:
|
109
|
+
# Method logs a user out of all Magic SDK sessions by
|
110
|
+
# the supplied DID Token
|
111
|
+
#
|
112
|
+
# Arguments:
|
113
|
+
# did_token: A DID Token generated by a Magic user on the client-side.
|
114
|
+
#
|
115
|
+
# Returns:
|
116
|
+
# Magic Response
|
117
|
+
def logout_by_token(did_token)
|
118
|
+
issuer = token.get_issuer(did_token)
|
119
|
+
logout_by_issuer(issuer)
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
|
124
|
+
def token
|
125
|
+
magic.token
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|