eli 0.1.13

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.
data/lib/eli/admin.rb ADDED
@@ -0,0 +1,240 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Eli
4
+ # Documentation for `Eli::Admin`.
5
+ #
6
+ # Administrative REST client used to manage admin sessions, accounts and to
7
+ # sign users in on their behalf.
8
+ module Admin
9
+ module_function
10
+
11
+ # Sign in.
12
+ #
13
+ # ## Examples
14
+ #
15
+ # Eli::Admin.sign_in("user.email@domain.com", "seCr#t.passw0rd")
16
+ # # 200
17
+ # # { "data" => { "token" => "JWT session token" } }
18
+ #
19
+ # # 400
20
+ # # { "errors" => { "detail" => "invalid credentials" } }
21
+ def sign_in(email, password)
22
+ url = Eli::Config.base_url + "/rest/admin/sessions"
23
+
24
+ options = {
25
+ params: {
26
+ email: email,
27
+ password: password
28
+ }
29
+ }
30
+
31
+ Eli::RestApi.post(url, options)
32
+ end
33
+
34
+ # Checks whether the admin user is signed in or not.
35
+ #
36
+ # ## Examples
37
+ #
38
+ # Eli::Admin.signed_in("JWT session token")
39
+ # # true
40
+ # # false
41
+ def signed_in(session_token)
42
+ url = Eli::Config.base_url + "/rest/admin/sessions/signed_in"
43
+
44
+ options = {
45
+ headers: { "authorization" => "Bearer #{session_token}" }
46
+ }
47
+
48
+ resp = Eli::RestApi.head(url, options)
49
+ resp.status == 200
50
+ end
51
+
52
+ # Returns the current admin user data.
53
+ #
54
+ # ## Examples
55
+ #
56
+ # Eli::Admin.current_user("JWT session token")
57
+ # # 200
58
+ # # {
59
+ # # "data" => {
60
+ # # "active" => true,
61
+ # # "email" => "user@mail.com",
62
+ # # "id" => "732cf1c2-6299-41fa-8784-e458765743b7",
63
+ # # "language" => "en",
64
+ # # "name" => "User Name",
65
+ # # "timezone" => "Europe/London"
66
+ # # }
67
+ # # }
68
+ #
69
+ # # 404
70
+ # # { "errors" => { "detail" => "Not Found" } }
71
+ def current_user(session_token)
72
+ url = Eli::Config.base_url + "/rest/admin/sessions"
73
+
74
+ options = {
75
+ headers: { "authorization" => "Bearer #{session_token}" }
76
+ }
77
+
78
+ Eli::RestApi.get(url, options)
79
+ end
80
+
81
+ # Refresh your token session returning a new session token.
82
+ #
83
+ # ## Examples
84
+ #
85
+ # Eli::Admin.refresh("JWT session token")
86
+ # # 200
87
+ # # { "data" => { "token" => "JWT session token" } }
88
+ #
89
+ # # 404
90
+ # # { "errors" => { "detail" => "Not Found" } }
91
+ def refresh(session_token)
92
+ url = Eli::Config.base_url + "/rest/admin/sessions"
93
+
94
+ options = {
95
+ headers: { "authorization" => "Bearer #{session_token}" }
96
+ }
97
+
98
+ Eli::RestApi.put(url, options)
99
+ end
100
+
101
+ # Signs the admin user out closing and deleting the session.
102
+ #
103
+ # ## Examples
104
+ #
105
+ # Eli::Admin.sign_out("JWT session token")
106
+ # # 200
107
+ # # { "data" => { "message" => "signed out successfully" } }
108
+ #
109
+ # # 404
110
+ # # { "errors" => { "detail" => "Not Found" } }
111
+ def sign_out(session_token)
112
+ url = Eli::Config.base_url + "/rest/admin/sessions"
113
+
114
+ options = {
115
+ headers: { "authorization" => "Bearer #{session_token}" }
116
+ }
117
+
118
+ Eli::RestApi.delete(url, options)
119
+ end
120
+
121
+ # Unlocks user making him/her able to sign in again.
122
+ #
123
+ # ## Examples
124
+ #
125
+ # Eli::Admin.unlock("Unlock token")
126
+ # # 202
127
+ # # { "data" => { "message" => "account was successfully unlocked" } }
128
+ #
129
+ # # 404
130
+ # # { "errors" => { "detail" => "Not Found" } }
131
+ def unlock(unlock_token)
132
+ url = Eli::Config.base_url + "/rest/accounts/unlock"
133
+
134
+ options = {
135
+ params: { token: unlock_token }
136
+ }
137
+
138
+ Eli::RestApi.put(url, options)
139
+ end
140
+
141
+ # Confirms user's email.
142
+ #
143
+ # ## Examples
144
+ #
145
+ # Eli::Admin.confirm("Confirmation token")
146
+ # # 202
147
+ # # { "data" => { "message" => "account was successfully confirmed" } }
148
+ #
149
+ # # 404
150
+ # # { "errors" => { "detail" => "Not Found" } }
151
+ def confirm(confirmation_token)
152
+ url = Eli::Config.base_url + "/rest/accounts/confirm"
153
+
154
+ options = {
155
+ params: { token: confirmation_token }
156
+ }
157
+
158
+ Eli::RestApi.put(url, options)
159
+ end
160
+
161
+ # Requests password recovery.
162
+ #
163
+ # ## Examples
164
+ #
165
+ # Eli::Admin.request_password_recovery("app token", "user@example.com")
166
+ # # 200
167
+ # # { "data" => { "message" => "password recovery was successfully requested" } }
168
+ #
169
+ # # 404
170
+ # # { "errors" => { "detail" => "Not Found" } }
171
+ def request_password_recovery(app_token, email)
172
+ url = Eli::Config.base_url + "/rest/accounts/password/recover"
173
+
174
+ options = {
175
+ headers: { "app-token" => app_token },
176
+ params: { email: email }
177
+ }
178
+
179
+ Eli::RestApi.post(url, options)
180
+ end
181
+
182
+ # Recover password updating it.
183
+ #
184
+ # ## Examples
185
+ #
186
+ # Eli::Admin.recover_password("token", "Secret.123", "Secret.123")
187
+ # # 200
188
+ # # { "data" => { "message" => "password was successfully recovered" } }
189
+ #
190
+ # # 400
191
+ # # { "errors" => { "detail" => "token is invalid" } }
192
+ #
193
+ # # 400
194
+ # # { "errors" => { "detail" => "password has an invalid format" } }
195
+ #
196
+ # # 400
197
+ # # { "errors" => { "detail" => "password and confirmation password are different" } }
198
+ def recover_password(token, password, password_confirmation)
199
+ url = Eli::Config.base_url + "/rest/accounts/password/recover"
200
+
201
+ options = {
202
+ params: {
203
+ token: token,
204
+ password: password,
205
+ password_confirmation: password_confirmation
206
+ }
207
+ }
208
+
209
+ Eli::RestApi.put(url, options)
210
+ end
211
+
212
+ # Admin signs in any user without password.
213
+ #
214
+ # ## Examples
215
+ #
216
+ # Eli::Admin.signs_in(
217
+ # "JWT session token",
218
+ # { email: "user.email@domain.com", name: "User Name" },
219
+ # "app_id"
220
+ # )
221
+ # # 200
222
+ # # { "data" => { "user" => { "active" => true, "email" => "..." } } }
223
+ #
224
+ # # 400
225
+ # # { "errors" => { "detail" => "invalid credentials" } }
226
+ def signs_in(session_token, user_data, app_id)
227
+ url = Eli::Config.base_url + "/rest/admin/sessions/signs_in"
228
+
229
+ options = {
230
+ headers: { "authorization" => "Bearer #{session_token}" },
231
+ params: {
232
+ user_data: user_data,
233
+ app_id: app_id
234
+ }
235
+ }
236
+
237
+ Eli::RestApi.post(url, options)
238
+ end
239
+ end
240
+ end
data/lib/eli/client.rb ADDED
@@ -0,0 +1,259 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Documentation for `Eli`.
4
+ #
5
+ # Public REST client used by applications integrating with Letmein. These are
6
+ # the end-user facing endpoints (sessions and account management). The methods
7
+ # are available directly on the `Eli` module, e.g. `Eli.sign_in(...)`.
8
+ module Eli
9
+ module_function
10
+
11
+ # Sign in.
12
+ #
13
+ # ## Examples
14
+ #
15
+ # Eli.sign_in("app token", "user.email@domain.com", "secret.password")
16
+ # # 200
17
+ # # { "data" => { "token" => "JWT session token" } }
18
+ #
19
+ # # 400
20
+ # # { "errors" => { "detail" => "invalid credentials" } }
21
+ def sign_in(app_token, email, password)
22
+ url = Eli::Config.base_url + "/rest/sessions"
23
+
24
+ options = {
25
+ headers: { "app-token" => app_token },
26
+ params: {
27
+ email: email,
28
+ password: password
29
+ }
30
+ }
31
+
32
+ Eli::RestApi.post(url, options)
33
+ end
34
+
35
+ # Checks whether the user is signed in or not.
36
+ #
37
+ # ## Examples
38
+ #
39
+ # Eli.signed_in("JWT session token")
40
+ # # true
41
+ # # false
42
+ def signed_in(session_token)
43
+ url = Eli::Config.base_url + "/rest/sessions/signed_in"
44
+
45
+ options = {
46
+ headers: { "authorization" => "Bearer #{session_token}" }
47
+ }
48
+
49
+ resp = Eli::RestApi.head(url, options)
50
+ resp.status == 200
51
+ end
52
+
53
+ # Returns the current user data.
54
+ #
55
+ # ## Examples
56
+ #
57
+ # Eli.current_user("JWT session token")
58
+ # # 200
59
+ # # {
60
+ # # "data" => {
61
+ # # "active" => true,
62
+ # # "email" => "user.name@domain.com",
63
+ # # "id" => "732cf1c2-6299-41fa-8784-e458765743b7",
64
+ # # "language" => "en",
65
+ # # "name" => "Adilson Chacon",
66
+ # # "timezone" => "Europe/London"
67
+ # # }
68
+ # # }
69
+ #
70
+ # # 404
71
+ # # { "errors" => { "detail" => "Not Found" } }
72
+ def current_user(session_token)
73
+ url = Eli::Config.base_url + "/rest/sessions"
74
+
75
+ options = {
76
+ headers: { "authorization" => "Bearer #{session_token}" }
77
+ }
78
+
79
+ Eli::RestApi.get(url, options)
80
+ end
81
+
82
+ # Refresh your token session returning a new session token.
83
+ #
84
+ # ## Examples
85
+ #
86
+ # Eli.refresh("JWT session token")
87
+ # # 200
88
+ # # { "data" => { "token" => "JWT session token" } }
89
+ #
90
+ # # 404
91
+ # # { "errors" => { "detail" => "Not Found" } }
92
+ def refresh(session_token)
93
+ url = Eli::Config.base_url + "/rest/sessions"
94
+
95
+ options = {
96
+ headers: { "authorization" => "Bearer #{session_token}" }
97
+ }
98
+
99
+ Eli::RestApi.put(url, options)
100
+ end
101
+
102
+ # Signs user out closing and deleting session.
103
+ #
104
+ # ## Examples
105
+ #
106
+ # Eli.sign_out("JWT session token")
107
+ # # 200
108
+ # # { "data" => { "message" => "signed out successfully" } }
109
+ #
110
+ # # 404
111
+ # # { "errors" => { "detail" => "Not Found" } }
112
+ def sign_out(session_token)
113
+ url = Eli::Config.base_url + "/rest/sessions"
114
+
115
+ options = {
116
+ headers: { "authorization" => "Bearer #{session_token}" }
117
+ }
118
+
119
+ Eli::RestApi.delete(url, options)
120
+ end
121
+
122
+ # Unlocks user making him/her able to sign in again.
123
+ #
124
+ # ## Examples
125
+ #
126
+ # Eli.unlock("Unlock token")
127
+ # # 202
128
+ # # { "data" => { "message" => "account was successfully unlocked" } }
129
+ #
130
+ # # 404
131
+ # # { "errors" => { "detail" => "Not Found" } }
132
+ def unlock(unlock_token)
133
+ url = Eli::Config.base_url + "/rest/accounts/unlock"
134
+
135
+ options = {
136
+ params: { token: unlock_token }
137
+ }
138
+
139
+ Eli::RestApi.put(url, options)
140
+ end
141
+
142
+ # Confirms user email.
143
+ #
144
+ # ## Examples
145
+ #
146
+ # Eli.confirm("Confirmation token")
147
+ # # 202
148
+ # # { "data" => { "message" => "account was successfully confirmed" } }
149
+ #
150
+ # # 404
151
+ # # { "errors" => { "detail" => "Not Found" } }
152
+ def confirm(confirmation_token)
153
+ url = Eli::Config.base_url + "/rest/accounts/confirm"
154
+
155
+ options = {
156
+ params: { token: confirmation_token }
157
+ }
158
+
159
+ Eli::RestApi.put(url, options)
160
+ end
161
+
162
+ # Requests password recovery.
163
+ #
164
+ # ## Examples
165
+ #
166
+ # Eli.request_password_recovery("app token", "user@example.com")
167
+ # # 200
168
+ # # { "data" => { "message" => "password recovery was successfully requested" } }
169
+ #
170
+ # # 404
171
+ # # { "errors" => { "detail" => "Not Found" } }
172
+ def request_password_recovery(app_token, email)
173
+ url = Eli::Config.base_url + "/rest/accounts/password/recover"
174
+
175
+ options = {
176
+ headers: { "app-token" => app_token },
177
+ params: { email: email }
178
+ }
179
+
180
+ Eli::RestApi.post(url, options)
181
+ end
182
+
183
+ # Recover password updating it.
184
+ #
185
+ # ## Examples
186
+ #
187
+ # Eli.recover_password("token", "Secret.123", "Secret.123")
188
+ # # 200
189
+ # # { "data" => { "message" => "password was successfully recovered" } }
190
+ #
191
+ # # 400
192
+ # # { "errors" => { "detail" => "token is invalid" } }
193
+ #
194
+ # # 400
195
+ # # { "errors" => { "detail" => "password has an invalid format" } }
196
+ #
197
+ # # 400
198
+ # # { "errors" => { "detail" => "password and confirmation password are different" } }
199
+ def recover_password(token, password, password_confirmation)
200
+ url = Eli::Config.base_url + "/rest/accounts/password/recover"
201
+
202
+ options = {
203
+ params: {
204
+ token: token,
205
+ password: password,
206
+ password_confirmation: password_confirmation
207
+ }
208
+ }
209
+
210
+ Eli::RestApi.put(url, options)
211
+ end
212
+
213
+ # Updates the password of the signed in user.
214
+ #
215
+ # ## Examples
216
+ #
217
+ # passwords = {
218
+ # current_password: "Secret.123",
219
+ # new_password: "NewSecret.1234",
220
+ # new_password_confirmation: "NewSecret.1234"
221
+ # }
222
+ # Eli.update_password("JWT session token", passwords)
223
+ # # 200
224
+ # # { "data" => { "message" => "password was successfully changed" } }
225
+ #
226
+ # # 400 or 404
227
+ # # { "errors" => { "detail" => "Error message" } }
228
+ def update_password(session_token, passwords)
229
+ url = Eli::Config.base_url + "/rest/accounts/password/update"
230
+
231
+ options = {
232
+ headers: { "authorization" => "Bearer #{session_token}" },
233
+ params: passwords
234
+ }
235
+
236
+ Eli::RestApi.put(url, options)
237
+ end
238
+
239
+ # Resend email for account confirmation.
240
+ #
241
+ # ## Examples
242
+ #
243
+ # Eli.resend_account_confirmation_email("App token", "user@example.com")
244
+ # # 200
245
+ # # { "data" => { "message" => "account confirmation email was successfully sent" } }
246
+ #
247
+ # # 404
248
+ # # { "errors" => { "detail" => "Not Found" } }
249
+ def resend_account_confirmation_email(app_token, email)
250
+ url = Eli::Config.base_url + "/rest/accounts/confirm/resend"
251
+
252
+ options = {
253
+ headers: { "app-token" => app_token },
254
+ params: { email: email }
255
+ }
256
+
257
+ Eli::RestApi.post(url, options)
258
+ end
259
+ end
data/lib/eli/config.rb ADDED
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Eli
4
+ # Documentation for `Eli::Config`.
5
+ #
6
+ # Holds the configuration used by the library, the most important being the
7
+ # base URL of the Letmein server the client talks to.
8
+ class Config
9
+ DEFAULT_BASE_URL = "http://localhost:4000"
10
+
11
+ class << self
12
+ # Set base_url value.
13
+ #
14
+ # ### Example
15
+ # Eli::Config.base_url = "http://localhost:4000"
16
+ def base_url=(value)
17
+ raise ArgumentError, "base_url must be a string" unless value.is_a?(String)
18
+
19
+ @base_url = value
20
+ end
21
+
22
+ # Get base_url value.
23
+ #
24
+ # Falls back to the `LETMEIN_BASE_URL` environment variable and finally to
25
+ # `DEFAULT_BASE_URL` when nothing was explicitly configured.
26
+ #
27
+ # ### Example
28
+ # Eli::Config.base_url
29
+ def base_url
30
+ @base_url || ENV["LETMEIN_BASE_URL"] || DEFAULT_BASE_URL
31
+ end
32
+
33
+ # Resets any value set through `base_url=` (useful for tests).
34
+ def reset!
35
+ @base_url = nil
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,171 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "uri"
5
+ require "faraday"
6
+
7
+ module Eli
8
+ # Documentation for `Eli::RestApi`.
9
+ #
10
+ # Thin wrapper around Faraday that mirrors the behaviour of the Elixir `Req`
11
+ # based client: it normalizes headers (always sending a JSON content type),
12
+ # encodes request bodies as JSON and appends params to the query string for
13
+ # the verbs that don't carry a body.
14
+ class RestApi
15
+ JSON_CONTENT_TYPE = "application/json"
16
+
17
+ # Lightweight response value object exposing `status` and the parsed `body`.
18
+ Response = Struct.new(:status, :body)
19
+
20
+ class << self
21
+ # Post.
22
+ #
23
+ # ## Examples
24
+ #
25
+ # Eli::RestApi.post("/rest/your/endpoint", params: { foo: "bar" }, headers: { "token" => "A_TOKEN" })
26
+ def post(url, options = {})
27
+ request_with_body(:post, url, options)
28
+ end
29
+
30
+ # Put.
31
+ #
32
+ # ## Examples
33
+ #
34
+ # Eli::RestApi.put("/rest/your/endpoint", params: { foo: "bar" }, headers: { "token" => "A_TOKEN" })
35
+ def put(url, options = {})
36
+ request_with_body(:put, url, options)
37
+ end
38
+
39
+ # Patch.
40
+ #
41
+ # ## Examples
42
+ #
43
+ # Eli::RestApi.patch("/rest/your/endpoint", params: { foo: "bar" }, headers: { "token" => "A_TOKEN" })
44
+ def patch(url, options = {})
45
+ request_with_body(:patch, url, options)
46
+ end
47
+
48
+ # Get.
49
+ #
50
+ # ## Examples
51
+ #
52
+ # Eli::RestApi.get("/rest/your/endpoint", params: { foo: "bar" }, headers: { "token" => "A_TOKEN" })
53
+ def get(url, options = {})
54
+ request_with_query(:get, url, options)
55
+ end
56
+
57
+ # Head.
58
+ #
59
+ # ## Examples
60
+ #
61
+ # Eli::RestApi.head("/rest/your/endpoint", params: { foo: "bar" }, headers: { "token" => "A_TOKEN" })
62
+ def head(url, options = {})
63
+ request_with_query(:head, url, options)
64
+ end
65
+
66
+ # Delete.
67
+ #
68
+ # ## Examples
69
+ #
70
+ # Eli::RestApi.delete("/rest/your/endpoint", params: { foo: "bar" }, headers: { "token" => "A_TOKEN" })
71
+ def delete(url, options = {})
72
+ request_with_query(:delete, url, options)
73
+ end
74
+
75
+ private
76
+
77
+ def request_with_body(method, url, options)
78
+ response = connection.run_request(
79
+ method,
80
+ url,
81
+ JSON.generate(options[:params] || {}),
82
+ normalize_headers(options[:headers])
83
+ )
84
+
85
+ build_response(response)
86
+ end
87
+
88
+ def request_with_query(method, url, options)
89
+ response = connection.run_request(
90
+ method,
91
+ build_url(url, options[:params]),
92
+ nil,
93
+ normalize_headers(options[:headers])
94
+ )
95
+
96
+ build_response(response)
97
+ end
98
+
99
+ def connection
100
+ Faraday.new
101
+ end
102
+
103
+ def build_response(response)
104
+ Response.new(response.status, parse_body(response.body))
105
+ end
106
+
107
+ def parse_body(body)
108
+ return body if body.nil? || body.to_s.empty?
109
+
110
+ JSON.parse(body)
111
+ rescue JSON::ParserError
112
+ body
113
+ end
114
+
115
+ def normalize_headers(headers)
116
+ headers = build_headers(headers)
117
+
118
+ if app_json_content_type?(headers)
119
+ headers
120
+ else
121
+ headers.merge(default_headers)
122
+ end
123
+ end
124
+
125
+ def build_headers(headers)
126
+ case headers
127
+ when nil
128
+ default_headers
129
+ when Hash
130
+ stringify(headers)
131
+ when Array
132
+ stringify(headers.to_h)
133
+ else
134
+ raise ArgumentError, "headers must be a Hash, an Array of pairs or nil"
135
+ end
136
+ end
137
+
138
+ def default_headers
139
+ { "content-type" => JSON_CONTENT_TYPE }
140
+ end
141
+
142
+ def stringify(headers)
143
+ headers.each_with_object({}) do |(key, value), acc|
144
+ acc[key.to_s] = value.to_s
145
+ end
146
+ end
147
+
148
+ def app_json_content_type?(headers)
149
+ headers.any? do |key, value|
150
+ key.to_s.upcase == "CONTENT-TYPE" && value.to_s.upcase == JSON_CONTENT_TYPE.upcase
151
+ end
152
+ end
153
+
154
+ def build_url(url, params)
155
+ return url if params.nil? || params.empty?
156
+
157
+ uri = URI.parse(url)
158
+ existing = uri.query ? URI.decode_www_form(uri.query).to_h : {}
159
+ merged = existing.merge(stringify_params(params))
160
+ uri.query = URI.encode_www_form(merged)
161
+ uri.to_s
162
+ end
163
+
164
+ def stringify_params(params)
165
+ params.each_with_object({}) do |(key, value), acc|
166
+ acc[key.to_s] = value.to_s
167
+ end
168
+ end
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Eli
4
+ VERSION = "0.1.13"
5
+ end