cpaas-sdk 1.0.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 +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.travis.yml +7 -0
- data/CHANGELOG.md +11 -0
- data/Gemfile +4 -0
- data/LICENSE.md +1 -0
- data/README.md +25 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/cpaas-sdk.gemspec +32 -0
- data/docs/Cpaas.html +446 -0
- data/docs/Cpaas/Conversation.html +1742 -0
- data/docs/Cpaas/Notification.html +301 -0
- data/docs/Cpaas/TwoFactor.html +908 -0
- data/docs/_index.html +146 -0
- data/docs/_index.md +21 -0
- data/docs/class_list.html +51 -0
- data/docs/css/common.css +1 -0
- data/docs/css/full_list.css +58 -0
- data/docs/css/style.css +496 -0
- data/docs/file.README.html +102 -0
- data/docs/file._index.html +94 -0
- data/docs/file_list.html +56 -0
- data/docs/frames.html +17 -0
- data/docs/index.html +94 -0
- data/docs/js/app.js +303 -0
- data/docs/js/full_list.js +216 -0
- data/docs/js/jquery.js +4 -0
- data/docs/method_list.html +251 -0
- data/docs/mv_index.html +102 -0
- data/docs/top-level-namespace.html +663 -0
- data/examples/2fa/.env.example +6 -0
- data/examples/2fa/.gitignore +159 -0
- data/examples/2fa/.ruby-gemset +1 -0
- data/examples/2fa/.ruby-version +1 -0
- data/examples/2fa/Gemfile +8 -0
- data/examples/2fa/README.md +34 -0
- data/examples/2fa/app.rb +134 -0
- data/examples/2fa/config.ru +10 -0
- data/examples/2fa/helper.rb +37 -0
- data/examples/2fa/public/stylesheets/forms.css +28 -0
- data/examples/2fa/public/stylesheets/global.css +7 -0
- data/examples/2fa/public/stylesheets/layout.css +45 -0
- data/examples/2fa/public/stylesheets/main.css +3 -0
- data/examples/2fa/views/alert.erb +5 -0
- data/examples/2fa/views/dashboard.erb +4 -0
- data/examples/2fa/views/index.erb +17 -0
- data/examples/2fa/views/login.erb +13 -0
- data/examples/2fa/views/verify.erb +8 -0
- data/examples/sms/.env.example +4 -0
- data/examples/sms/.gitignore +159 -0
- data/examples/sms/.ruby-gemset +1 -0
- data/examples/sms/.ruby-version +1 -0
- data/examples/sms/Gemfile +8 -0
- data/examples/sms/README.md +80 -0
- data/examples/sms/app.rb +87 -0
- data/examples/sms/config.ru +10 -0
- data/examples/sms/helper.rb +33 -0
- data/examples/sms/public/scripts/notification.js +46 -0
- data/examples/sms/public/stylesheets/forms.css +28 -0
- data/examples/sms/public/stylesheets/global.css +7 -0
- data/examples/sms/public/stylesheets/layout.css +74 -0
- data/examples/sms/public/stylesheets/main.css +3 -0
- data/examples/sms/views/alert.erb +5 -0
- data/examples/sms/views/index.erb +48 -0
- data/lib/cpaas-sdk.rb +30 -0
- data/lib/cpaas-sdk/api.rb +139 -0
- data/lib/cpaas-sdk/config.rb +9 -0
- data/lib/cpaas-sdk/resources.rb +4 -0
- data/lib/cpaas-sdk/resources/conversation.rb +268 -0
- data/lib/cpaas-sdk/resources/notification.rb +62 -0
- data/lib/cpaas-sdk/resources/notification_channel.rb +39 -0
- data/lib/cpaas-sdk/resources/twofactor.rb +136 -0
- data/lib/cpaas-sdk/util.rb +93 -0
- data/lib/cpaas-sdk/version.rb +3 -0
- data/tutorials/2FA.md +109 -0
- data/tutorials/2fa-flow.png +0 -0
- data/tutorials/GetStarted.md +86 -0
- data/tutorials/SMSMessaging.md +132 -0
- data/tutorials/index.html +86 -0
- data/tutorials/quickstarts.yml +15 -0
- metadata +238 -0
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'cpaas-sdk/util'
|
2
|
+
|
3
|
+
module Cpaas
|
4
|
+
#
|
5
|
+
# CPaaS notification helper methods
|
6
|
+
#
|
7
|
+
class Notification
|
8
|
+
#
|
9
|
+
# Parse inbound sms notification received in webhook. It parses the notification and returns
|
10
|
+
# simplified version of the response.
|
11
|
+
#
|
12
|
+
# @param notification [Hash] JSON received in the subscription webhook.
|
13
|
+
#
|
14
|
+
def self.parse(notification)
|
15
|
+
parsed_notification = convert_hash_keys(notification)
|
16
|
+
top_level_key = parsed_notification.keys.first
|
17
|
+
notification_obj = parsed_notification[top_level_key]
|
18
|
+
|
19
|
+
case top_level_key
|
20
|
+
when :outbound_sms_message_notification, :inbound_sms_message_notification
|
21
|
+
message = notification_obj.dig(:outbound_sms_message).nil? ? notification_obj.dig(:inbound_sms_message) : notification_obj.dig(:outbound_sms_message)
|
22
|
+
|
23
|
+
{
|
24
|
+
notification_id: notification_obj.dig(:id),
|
25
|
+
notification_date_time: notification_obj.dig(:date_time),
|
26
|
+
type: types[top_level_key]
|
27
|
+
}.merge(message)
|
28
|
+
when :sms_subscription_cancellation_notification
|
29
|
+
{
|
30
|
+
subscription_id: id_from(notification_obj.dig(:link, 0, :href)),
|
31
|
+
notification_id: notification_obj.dig(:id),
|
32
|
+
notification_date_time: notification_obj.dig(:date_time),
|
33
|
+
type: types[top_level_key]
|
34
|
+
}
|
35
|
+
when :sms_event_notification
|
36
|
+
{
|
37
|
+
notification_id: notification_obj.dig(:id),
|
38
|
+
notification_date_time: notification_obj.dig(:date_time),
|
39
|
+
message_id: id_from(notification_obj.dig(:link, 0, :href)),
|
40
|
+
type: types[top_level_key],
|
41
|
+
event_details: {
|
42
|
+
description: notification_obj.dig(:event_description),
|
43
|
+
type: notification_obj.dig(:event_type)
|
44
|
+
}
|
45
|
+
}
|
46
|
+
else
|
47
|
+
notification_obj
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def self.types
|
54
|
+
{
|
55
|
+
outbound_sms_message_notification: 'outbound',
|
56
|
+
inbound_sms_message_notification: 'inbound',
|
57
|
+
sms_subscription_cancellation_notification: 'subscriptionCancel',
|
58
|
+
sms_event_notification: 'event'
|
59
|
+
}
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'cpaas-sdk/util'
|
2
|
+
|
3
|
+
module Cpaas
|
4
|
+
# @private
|
5
|
+
|
6
|
+
class NotificationChannel
|
7
|
+
def self.create_channel(params)
|
8
|
+
options = {
|
9
|
+
body: {
|
10
|
+
notificationChannel: {
|
11
|
+
channelData: {
|
12
|
+
'x-webhookURL': params[:webhook_url]
|
13
|
+
},
|
14
|
+
channelType: 'webhooks',
|
15
|
+
clientCorrelator: Cpaas.api.client_correlator
|
16
|
+
}
|
17
|
+
}
|
18
|
+
}
|
19
|
+
|
20
|
+
response = Cpaas.api.send_request("#{base_url}/channels", options, :post)
|
21
|
+
|
22
|
+
process_response(response) do |res|
|
23
|
+
channel = res.dig(:notification_channel)
|
24
|
+
|
25
|
+
{
|
26
|
+
channel_id: channel[:callback_url],
|
27
|
+
webhook_url: channel[:channel_data][:x_webhook_url],
|
28
|
+
channel_type: channel[:channel_type]
|
29
|
+
}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def self.base_url
|
36
|
+
"/cpaas/notificationchannel/v1/#{Cpaas.api.user_id}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'cpaas-sdk/util'
|
2
|
+
|
3
|
+
module Cpaas
|
4
|
+
##
|
5
|
+
# CPaaS provides Authentication API where a two-factor authentication (2FA) flow can be implemented by using that.
|
6
|
+
# Sections below describe two sample use cases, two-factor authentication via SMS and two-factor authentication via e-mail
|
7
|
+
#
|
8
|
+
|
9
|
+
class Twofactor
|
10
|
+
#
|
11
|
+
# Create a new authentication code.
|
12
|
+
#
|
13
|
+
# @param params [Hash]
|
14
|
+
# @option params [String|Array[string]] :destination_address Destination address of the authentication code being sent. For sms type authentication codes, it should contain a E164 phone number. For e-mail type authentication codes, it should contain a valid e-mail address.
|
15
|
+
# @option params [String] :message Message text sent to the destination, containing the placeholder for the code within the text. CPaaS requires to have *{code}* string within the text in order to generate a code and inject into the text. For email type code, one usage is to have the *{code}* string located within the link in order to get a unique link.
|
16
|
+
# @option params [String] :method ('sms') +optional+ Type of the authentication code delivery method, sms and email are supported types. Possible values: sms, email
|
17
|
+
# @option params [Number] :expiry (120) +optional+ Lifetime duration of the code sent in seconds. This can contain values between 30 and 3600 seconds.
|
18
|
+
# @option params [Number] :length (6) +optional+ Length of the authentication code tha CPaaS should generate for this request. It can contain values between 4 and 10.
|
19
|
+
# @option params [String] :type ('numeric') +optional+ Type of the code that is generated. If not provided, default value is numeric. Possible values: numeric, alphanumeric, alphabetic
|
20
|
+
#
|
21
|
+
def self.send_code(params = {})
|
22
|
+
address = (params[:destination_address].is_a? String) ? [ params[:destination_address] ] : params[:destination_address]
|
23
|
+
|
24
|
+
options = {
|
25
|
+
body: {
|
26
|
+
code: {
|
27
|
+
address: address,
|
28
|
+
method: params[:method] || 'sms',
|
29
|
+
format: {
|
30
|
+
length: params[:length] || 6,
|
31
|
+
type: params[:type] || 'numeric'
|
32
|
+
},
|
33
|
+
expiry: params[:expiry] || 120,
|
34
|
+
message: params[:message]
|
35
|
+
}
|
36
|
+
}
|
37
|
+
}
|
38
|
+
|
39
|
+
response = Cpaas.api.send_request("#{base_url}/codes", options, :post)
|
40
|
+
|
41
|
+
process_response(response) do |res|
|
42
|
+
{
|
43
|
+
code_id: id_from(res.dig(:code, :resource_url))
|
44
|
+
}
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
#
|
49
|
+
# Verifying authentication code
|
50
|
+
#
|
51
|
+
# @param params [Hash]
|
52
|
+
# @option params [String] :code_id ID of the authentication code.
|
53
|
+
# @option params [String] :verification_code Code that is being verified
|
54
|
+
#
|
55
|
+
def self.verify_code(params = {})
|
56
|
+
options = {
|
57
|
+
body: {
|
58
|
+
code: {
|
59
|
+
verify: params[:verification_code]
|
60
|
+
}
|
61
|
+
}
|
62
|
+
}
|
63
|
+
|
64
|
+
response = Cpaas.api.send_request("#{base_url}/codes/#{params[:code_id]}/verify", options, :put)
|
65
|
+
|
66
|
+
process_response(response) do |res|
|
67
|
+
if res[:status_code] == 204
|
68
|
+
{
|
69
|
+
verified: true,
|
70
|
+
message: 'Success'
|
71
|
+
}
|
72
|
+
else
|
73
|
+
{
|
74
|
+
verified: false,
|
75
|
+
message: 'Code invalid or expired'
|
76
|
+
}
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
##
|
82
|
+
# Resending the authentication code via same code resource, invalidating the previously sent code.
|
83
|
+
#
|
84
|
+
# @param params [Hash]
|
85
|
+
# @option params [String|Array[string]] :destination_address Destination address of the authentication code being sent. For sms type authentication codes, it should contain a E164 phone number. For e-mail type authentication codes, it should contain a valid e-mail address.
|
86
|
+
# @option params [String] :message Message text sent to the destination, containing the placeholder for the code within the text. CPaaS requires to have *{code}* string within the text in order to generate a code and inject into the text. For email type code, one usage is to have the *{code}* string located within the link in order to get a unique link.
|
87
|
+
# @option params [String] :code_id ID of the authentication code.
|
88
|
+
# @option params [String] :method ('sms') +optional+ Type of the authentication code delivery method, sms and email are supported types. Possible values: sms, email
|
89
|
+
# @option params [Number] :expiry (120) +optional+ Lifetime duration of the code sent in seconds. This can contain values between 30 and 3600 seconds.
|
90
|
+
# @option params [Number] :length (6) +optional+ Length of the authentication code tha CPaaS should generate for this request. It can contain values between 4 and 10.
|
91
|
+
# @option params [String] :type ('numeric') +optional+ Type of the code that is generated. If not provided, default value is numeric. Possible values: numeric, alphanumeric, alphabetic
|
92
|
+
#
|
93
|
+
def self.resend_code(params = {})
|
94
|
+
address = (params[:destination_address].is_a? String) ? [ params[:destination_address] ] : params[:destination_address]
|
95
|
+
|
96
|
+
options = {
|
97
|
+
body: {
|
98
|
+
code: {
|
99
|
+
address: address,
|
100
|
+
method: params[:method] || 'sms',
|
101
|
+
format: {
|
102
|
+
length: params[:length] || 6,
|
103
|
+
type: params[:type] || 'numeric'
|
104
|
+
},
|
105
|
+
expiry: params[:expiry] || 120,
|
106
|
+
message: params[:message]
|
107
|
+
}
|
108
|
+
}
|
109
|
+
}
|
110
|
+
|
111
|
+
response = Cpaas.api.send_request("#{base_url}/codes/#{params[:code_id]}", options, :put)
|
112
|
+
|
113
|
+
process_response(response) do |res|
|
114
|
+
{
|
115
|
+
code_id: id_from(res.dig(:code, :resource_url))
|
116
|
+
}
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
#
|
121
|
+
# Delete authentication code resource.
|
122
|
+
#
|
123
|
+
# @param [Hash] params
|
124
|
+
# @option params [String] :code_id ID of the authentication code.
|
125
|
+
#
|
126
|
+
def self.delete_code(params = {})
|
127
|
+
Cpaas.api.send_request("#{base_url}/codes/#{params[:code_id]}", {}, :delete)
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
def self.base_url
|
133
|
+
"/cpaas/auth/v1/#{Cpaas.api.user_id}"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
def process_response(res, remove_outer_key = true)
|
2
|
+
if res.key? :exception_id
|
3
|
+
response = res
|
4
|
+
elsif res && res.dig(:__for_test__)
|
5
|
+
custom_body = res.dig(:custom_body)
|
6
|
+
reject(res, :custom_body)
|
7
|
+
|
8
|
+
if !res.dig(:custom_body).nil? && block_given?
|
9
|
+
custom_body = yield res[:custom_body]
|
10
|
+
end
|
11
|
+
|
12
|
+
response = custom_body.nil? ? res : res.merge(custom_body)
|
13
|
+
elsif block_given?
|
14
|
+
response = yield res
|
15
|
+
elsif remove_outer_key
|
16
|
+
topLevelKey = res.keys.first
|
17
|
+
response = res[topLevelKey].as_json
|
18
|
+
else
|
19
|
+
response = res
|
20
|
+
end
|
21
|
+
|
22
|
+
response
|
23
|
+
end
|
24
|
+
|
25
|
+
def compose_error_from(err_response)
|
26
|
+
error_obj = deep_find(err_response, :message_id)
|
27
|
+
|
28
|
+
if (error_obj)
|
29
|
+
message = error_obj[:text]
|
30
|
+
|
31
|
+
error_obj[:variables].each_with_index { |variable, index| message.gsub!("%#{index + 1}", variable) }
|
32
|
+
|
33
|
+
response = {
|
34
|
+
name: error_obj[:name],
|
35
|
+
exception_id: error_obj[:message_id],
|
36
|
+
message: message
|
37
|
+
}
|
38
|
+
else
|
39
|
+
response = {
|
40
|
+
name: err_response.keys.first,
|
41
|
+
exception_id: 'Unknown',
|
42
|
+
message: err_response[:message]
|
43
|
+
}
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def id_from (url)
|
48
|
+
url.split('/').last
|
49
|
+
end
|
50
|
+
|
51
|
+
def deep_find(object, key, parentKey = '')
|
52
|
+
result = nil
|
53
|
+
|
54
|
+
if object.respond_to?(:key?) && object.key?(key)
|
55
|
+
object[:name] = parentKey
|
56
|
+
return object
|
57
|
+
elsif object.is_a? Enumerable
|
58
|
+
object.each do |k, v|
|
59
|
+
result = deep_find(v, key, k)
|
60
|
+
|
61
|
+
return result if !result.nil?
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
return result
|
66
|
+
end
|
67
|
+
|
68
|
+
def convert_hash_keys(value)
|
69
|
+
case value
|
70
|
+
when Array
|
71
|
+
value.map { |v| convert_hash_keys(v) }
|
72
|
+
when Hash
|
73
|
+
Hash[value.map { |k, v| [underscore_key(k), convert_hash_keys(v)] }]
|
74
|
+
else
|
75
|
+
value
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def underscore_key(k)
|
80
|
+
underscore(k.to_s).to_sym
|
81
|
+
end
|
82
|
+
|
83
|
+
def underscore(str)
|
84
|
+
str.gsub(/::/, '/').
|
85
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
86
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
87
|
+
tr("-", "_").
|
88
|
+
downcase
|
89
|
+
end
|
90
|
+
|
91
|
+
def reject(obj, key)
|
92
|
+
obj.reject { |k,v| k == key }
|
93
|
+
end
|
data/tutorials/2FA.md
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
# Two-Factor Authentication
|
2
|
+
$KANDY$ provides [Authentication API](/developer/references/ruby/1.0.0#twofactor-send-code) using which a two-factor authentication (2FA) flow can be implemented.
|
3
|
+
|
4
|
+
Sections below describe two sample use cases, two-factor authentication via SMS and two-factor authentication via e-mail.
|
5
|
+
|
6
|
+
## Two-Factor Authentication via SMS
|
7
|
+
The following diagram explains a sample use case and its logical flow for two-factor authentication via SMS:
|
8
|
+
|
9
|
+

|
10
|
+
|
11
|
+
1. User opens the MyApp web page, enters credentials, and clicks login
|
12
|
+
2. MyApp web server initiates two-factor authentication via SMS flow, sends request to $KANDY$ to send validation code
|
13
|
+
3. User receives the code via SMS
|
14
|
+
4. User enters the code on MyApp web page
|
15
|
+
5. User submits the code
|
16
|
+
6. MyApp web server sends validation request to $KANDY$ with the code user entered and receives the result
|
17
|
+
7. MyApp web page takes the action based on the result
|
18
|
+
|
19
|
+
Your $KANDY$ admin can purchase SMS DID and assign to the MyApp project, so that the two-factor authentication SMS sent to app users always has the same originating number seen on the phone. Otherwise, the number seen on app users' phones may differ per transaction.
|
20
|
+
|
21
|
+
First, MyApp web server sends request to send a two-factor authentication code:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
Cpaas::Twofactor.send_code({
|
25
|
+
destination_address: '+12292990344',
|
26
|
+
method: 'sms',
|
27
|
+
expiry: 360,
|
28
|
+
message: 'Your code is {code}',
|
29
|
+
length: 6,
|
30
|
+
type: 'alphanumeric'
|
31
|
+
})
|
32
|
+
```
|
33
|
+
The response contains `code_id` which is a unique ID needed for `verify_code`. The response object can look something like this:
|
34
|
+
```ruby
|
35
|
+
{
|
36
|
+
code_id: '1fc8907-d336-4707-ad3c'
|
37
|
+
}
|
38
|
+
```
|
39
|
+
|
40
|
+
Walking through the method parameters:
|
41
|
+
|
42
|
+
+ `destination_address` is required with a routable destination number in E.164 format, either with tel schema or not. For v1, only one address is supported.
|
43
|
+
+ `method` is mandatory and must have `sms` for verification via SMS flow.
|
44
|
+
+ `expiry` indicates the desired period of time in seconds that the code will be valid on $KANDY$. It is optional having default value as 120 seconds, while application can ask for values between 30 and 3600 seconds.
|
45
|
+
+ `message` is required with a `{code}` string within the text so that $KANDY$ can replace that with the real code generated, and send it as the SMS content.
|
46
|
+
+ `length` indicates the desired length of the code, default value is 6 and application can request for values between 4 and 10.
|
47
|
+
+ `type` indicates the desired type of the code from the set `numeric`, `alphanumeric`, `alphabetic`. Default is `numeric`.
|
48
|
+
|
49
|
+
The `code_id` in the `response` object should be used for verifying the user’s verification code or resending a new verification code to the user.
|
50
|
+
|
51
|
+
To verify the code, here is an example:
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
Cpaas::Twofactor.verify_code({
|
55
|
+
code_id: '1fc8907-d336-4707-ad3c',
|
56
|
+
verification_code: '123456'
|
57
|
+
})
|
58
|
+
```
|
59
|
+
In the response, `verified: true` means code is verified and removed from $KANDY$, while `verified: false` indicates the code is not valid.
|
60
|
+
|
61
|
+
A successful verification will have the following response:
|
62
|
+
```ruby
|
63
|
+
{
|
64
|
+
verified: true,
|
65
|
+
message: 'Success'
|
66
|
+
}
|
67
|
+
```
|
68
|
+
An invalid/failed verification will have the following response:
|
69
|
+
```ruby
|
70
|
+
{
|
71
|
+
verified: false,
|
72
|
+
message: 'Code expired or invalid'
|
73
|
+
}
|
74
|
+
```
|
75
|
+
|
76
|
+
## Two-Factor Authentication via E-mail
|
77
|
+
A similar flow with the SMS section above can be implemented for e-mail verification.
|
78
|
+
|
79
|
+
The following email code request example, requires insertion of the generated code within the link that the user is supposed to click and navigate to the application's web service:
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
Cpaas::Twofactor.send_code({
|
83
|
+
destination_address: 'johndev@someemail.com',
|
84
|
+
method: 'email',
|
85
|
+
length: 10,
|
86
|
+
type: 'alphanumeric',
|
87
|
+
expiry: 3600,
|
88
|
+
message: 'Here is your code: {code}'
|
89
|
+
})
|
90
|
+
```
|
91
|
+
The response contains `code_id` which is a unique ID needed for `verify_code`. The response object can look something like this:
|
92
|
+
```ruby
|
93
|
+
{
|
94
|
+
code_id: '1fc8907-d336-4707-ad3c'
|
95
|
+
}
|
96
|
+
```
|
97
|
+
|
98
|
+
As can be seen within the example, `method` parameter has `email` value, while `destination_address` field includes a destination e-mail address. For v1, only plain text is supported.
|
99
|
+
|
100
|
+
Verification procedure for two-factor authentication via e-mail is same with two-factor authentication via SMS as described in previous section.
|
101
|
+
|
102
|
+
## Additional Operations
|
103
|
+
The `code` can be:
|
104
|
+
|
105
|
+
+ Resend using the same resource, which "invalidates" the previously sent code and triggers a new SMS or email containing a new code.
|
106
|
+
+ Deleted explicitly if desired (deletion operation does not block the previously started send operation)
|
107
|
+
|
108
|
+
## References
|
109
|
+
For all two factor authentication related method details, refer to [Two Factor Authentication](/developer/references/ruby/1.0.0#twofactor-send-code).
|