cpaas-sdk 1.0.0

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