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.
Files changed (84) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +7 -0
  5. data/CHANGELOG.md +11 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE.md +1 -0
  8. data/README.md +25 -0
  9. data/Rakefile +6 -0
  10. data/bin/console +14 -0
  11. data/bin/setup +8 -0
  12. data/cpaas-sdk.gemspec +32 -0
  13. data/docs/Cpaas.html +446 -0
  14. data/docs/Cpaas/Conversation.html +1742 -0
  15. data/docs/Cpaas/Notification.html +301 -0
  16. data/docs/Cpaas/TwoFactor.html +908 -0
  17. data/docs/_index.html +146 -0
  18. data/docs/_index.md +21 -0
  19. data/docs/class_list.html +51 -0
  20. data/docs/css/common.css +1 -0
  21. data/docs/css/full_list.css +58 -0
  22. data/docs/css/style.css +496 -0
  23. data/docs/file.README.html +102 -0
  24. data/docs/file._index.html +94 -0
  25. data/docs/file_list.html +56 -0
  26. data/docs/frames.html +17 -0
  27. data/docs/index.html +94 -0
  28. data/docs/js/app.js +303 -0
  29. data/docs/js/full_list.js +216 -0
  30. data/docs/js/jquery.js +4 -0
  31. data/docs/method_list.html +251 -0
  32. data/docs/mv_index.html +102 -0
  33. data/docs/top-level-namespace.html +663 -0
  34. data/examples/2fa/.env.example +6 -0
  35. data/examples/2fa/.gitignore +159 -0
  36. data/examples/2fa/.ruby-gemset +1 -0
  37. data/examples/2fa/.ruby-version +1 -0
  38. data/examples/2fa/Gemfile +8 -0
  39. data/examples/2fa/README.md +34 -0
  40. data/examples/2fa/app.rb +134 -0
  41. data/examples/2fa/config.ru +10 -0
  42. data/examples/2fa/helper.rb +37 -0
  43. data/examples/2fa/public/stylesheets/forms.css +28 -0
  44. data/examples/2fa/public/stylesheets/global.css +7 -0
  45. data/examples/2fa/public/stylesheets/layout.css +45 -0
  46. data/examples/2fa/public/stylesheets/main.css +3 -0
  47. data/examples/2fa/views/alert.erb +5 -0
  48. data/examples/2fa/views/dashboard.erb +4 -0
  49. data/examples/2fa/views/index.erb +17 -0
  50. data/examples/2fa/views/login.erb +13 -0
  51. data/examples/2fa/views/verify.erb +8 -0
  52. data/examples/sms/.env.example +4 -0
  53. data/examples/sms/.gitignore +159 -0
  54. data/examples/sms/.ruby-gemset +1 -0
  55. data/examples/sms/.ruby-version +1 -0
  56. data/examples/sms/Gemfile +8 -0
  57. data/examples/sms/README.md +80 -0
  58. data/examples/sms/app.rb +87 -0
  59. data/examples/sms/config.ru +10 -0
  60. data/examples/sms/helper.rb +33 -0
  61. data/examples/sms/public/scripts/notification.js +46 -0
  62. data/examples/sms/public/stylesheets/forms.css +28 -0
  63. data/examples/sms/public/stylesheets/global.css +7 -0
  64. data/examples/sms/public/stylesheets/layout.css +74 -0
  65. data/examples/sms/public/stylesheets/main.css +3 -0
  66. data/examples/sms/views/alert.erb +5 -0
  67. data/examples/sms/views/index.erb +48 -0
  68. data/lib/cpaas-sdk.rb +30 -0
  69. data/lib/cpaas-sdk/api.rb +139 -0
  70. data/lib/cpaas-sdk/config.rb +9 -0
  71. data/lib/cpaas-sdk/resources.rb +4 -0
  72. data/lib/cpaas-sdk/resources/conversation.rb +268 -0
  73. data/lib/cpaas-sdk/resources/notification.rb +62 -0
  74. data/lib/cpaas-sdk/resources/notification_channel.rb +39 -0
  75. data/lib/cpaas-sdk/resources/twofactor.rb +136 -0
  76. data/lib/cpaas-sdk/util.rb +93 -0
  77. data/lib/cpaas-sdk/version.rb +3 -0
  78. data/tutorials/2FA.md +109 -0
  79. data/tutorials/2fa-flow.png +0 -0
  80. data/tutorials/GetStarted.md +86 -0
  81. data/tutorials/SMSMessaging.md +132 -0
  82. data/tutorials/index.html +86 -0
  83. data/tutorials/quickstarts.yml +15 -0
  84. 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
@@ -0,0 +1,3 @@
1
+ module Cpaas
2
+ VERSION = '1.0.0'
3
+ 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
+ ![2FA via SMS flow](2fa-flow.png)
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).