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