datadome_fraud_sdk_ruby 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 562e73467299bca33309183c1e8f84e26571346082000ca19767295d4bcdb558
4
+ data.tar.gz: 9f60b87c715a36bc402d76018b9b96f7d30a901d8f1e07b6488f7537c2251f87
5
+ SHA512:
6
+ metadata.gz: 7163115049e8696367c168e69a36a1fa36f80f6bb64e72baeaab93c9614136feefe89954ec9e415913d51db72eb152670f91e2dac7f5223c5ddc225286d43979
7
+ data.tar.gz: 6600c8180c52163a5039c47d4ae5895142e57b63a602ae13bef327e4a8137e393a64540bdc00e2bcf8a549b38640c9915e77beb3cc75495ab7ab7a514300eafc
data/README.md ADDED
@@ -0,0 +1,2 @@
1
+ # fraud-sdk-ruby
2
+ Fraud Protection for Ruby
data/lib/constants.rb ADDED
@@ -0,0 +1,2 @@
1
+ SDK_VERSION = '0.1.0'
2
+ SDK_NAME = 'datadome_fraud_sdk_ruby'
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "faraday"
4
+ require "json"
5
+ require_relative "model/events"
6
+ require_relative "model/user"
7
+ require_relative "model/session"
8
+ require_relative "model/operation"
9
+ require_relative "model/api/request"
10
+ require_relative "model/api/response"
11
+
12
+ class DataDome
13
+ attr_accessor :timeout, :endpoint, :logger
14
+
15
+ def initialize(timeout = 1500, endpoint = "account-api.datadome.co", logger = Logger.new(STDOUT))
16
+ @timeout = timeout
17
+ @endpoint = endpoint
18
+ @logger = logger
19
+ end
20
+
21
+ def validate(request, event)
22
+ if event.status == StatusType::UNDEFINED
23
+ event.status = StatusType::SUCCEEDED
24
+ end
25
+
26
+ payload = build_payload(request, event)
27
+
28
+ begin
29
+ api_response = sendRequest(OperationType::VALIDATE, event.action, payload)
30
+
31
+ begin
32
+ body = JSON.parse(api_response.body) unless api_response.body.nil?
33
+ rescue JSON::ParserError => e
34
+ @logger.error("DataDome: error parsing JSON from Fraud API #{e.message}")
35
+ return DataDomeResponseError.new(DataDomeResponseAction::ALLOW, DataDomeResponseStatus::FAILURE, "DataDome: error parsing JSON from Fraud API", e.message)
36
+ end
37
+
38
+ if api_response.success?
39
+ datadome_response = DataDomeResponseSuccess.new(DataDomeResponseAction::ALLOW, DataDomeResponseStatus::OK, body["reasons"], body["ip"], body["location"])
40
+ if body["action"] === DataDomeResponseAction::DENY
41
+ datadome_response.action = DataDomeResponseAction::DENY
42
+ end
43
+ else
44
+ @logger.error("DataDome: error on Fraud API response #{body["errors"]}")
45
+ return DataDomeResponseError.new(DataDomeResponseAction::ALLOW, DataDomeResponseStatus::FAILURE, body["message"], body["errors"])
46
+ end
47
+ rescue Faraday::TimeoutError => e
48
+ return DataDomeResponseError.new(DataDomeResponseAction::ALLOW, DataDomeResponseStatus::TIMEOUT, "DataDome Fraud API request timeout", e.message)
49
+ rescue Faraday::ConnectionFailed => e
50
+ return DataDomeResponseError.new(DataDomeResponseAction::ALLOW, DataDomeResponseStatus::FAILURE, "DataDome Fraud API request failed", e.message)
51
+ end
52
+
53
+ datadome_response
54
+ end
55
+
56
+ def collect(request, event)
57
+ if event.status == StatusType::UNDEFINED
58
+ event.status = StatusType::FAILED
59
+ end
60
+
61
+ payload = build_payload(request, event)
62
+
63
+ begin
64
+ api_response = sendRequest(OperationType::COLLECT, event.action, payload)
65
+
66
+ if api_response.success?
67
+ datadome_response = DataDomeResponseSuccess.new(nil, DataDomeResponseStatus::OK, nil, nil, nil)
68
+ else
69
+ begin
70
+ body = JSON.parse(api_response.body)
71
+ @logger.error("DataDome: error on Fraud API response #{body["errors"]}")
72
+ datadome_response = DataDomeResponseError.new(nil, DataDomeResponseStatus::FAILURE, body["message"], body["errors"])
73
+ rescue JSON::ParserError => e
74
+ @logger.error("DataDome: error parsing JSON from Fraud API #{e.message}")
75
+ return DataDomeResponseError.new(nil, DataDomeResponseStatus::FAILURE, "DataDome: error parsing JSON from Fraud API", e.message)
76
+ end
77
+ end
78
+ rescue Faraday::TimeoutError => e
79
+ datadome_response = DataDomeResponseError.new(nil, DataDomeResponseStatus::TIMEOUT, "DataDome Fraud API request timeout", e.message)
80
+ rescue Faraday::ConnectionFailed => e
81
+ datadome_response = DataDomeResponseError.new(nil, DataDomeResponseStatus::FAILURE, "DataDome Fraud API request failed", e.message)
82
+ end
83
+
84
+ datadome_response
85
+ end
86
+
87
+ private
88
+
89
+ def sendRequest(operation, event, payload)
90
+ api_response = client.post("/v1/" + operation + "/" + event) do |req|
91
+ req.body = payload.to_json
92
+ end
93
+ api_response
94
+ end
95
+
96
+ def build_payload(request, event)
97
+ metadata = event.merge_with(DataDomeRequest.new(request))
98
+ payload = {}
99
+ metadata.instance_variables.each do |var|
100
+ key = var.to_s.delete("@") # Remove '@' from variable name to get key
101
+ payload[key] = metadata.instance_variable_get(var)
102
+ end
103
+ payload
104
+ end
105
+
106
+ def client
107
+ Faraday.new(api_uri) do |req|
108
+ req.adapter Faraday.default_adapter
109
+ req.options[:timeout] = @timeout / 1000.0 #timeout in seconds
110
+ req.headers["accept"] = "application/json"
111
+ req.headers["content-type"] = "application/json"
112
+ req.headers["x-api-key"] = ENV["DATADOME_FRAUD_API_KEY"]
113
+ req.ssl.verify = true if api_uri.scheme == "https"
114
+ end
115
+ end
116
+
117
+ def api_uri
118
+ url = @endpoint
119
+ url = "https://#{url}" unless (url.downcase.start_with?("https://") || url.include?("://"))
120
+
121
+ URI(url)
122
+ end
123
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Address
4
+ attr_accessor :name, :line1, :line2, :city, :countryCode, :country, :regionCode, :zipCode
5
+
6
+ def initialize(name = nil, line1 = nil, line2 = nil, city = nil, countryCode = nil, country = nil, regionCode = nil, zipCode = nil)
7
+ @name = name
8
+ @line1 = line1
9
+ @line2 = line2
10
+ @city = city
11
+ @countryCode = countryCode
12
+ @country = country
13
+ @regionCode = regionCode
14
+ @zipCode = zipCode
15
+ end
16
+
17
+ def to_s
18
+ "Address: name=#{name}, line1=#{line1}, line2=#{line2}, city=#{city}, countryCode=#{countryCode}, country=#{country}, regionCode=#{regionCode}, zipCode=#{zipCode}"
19
+ end
20
+
21
+ def to_json(options = {})
22
+ {
23
+ name: name,
24
+ line1: line1,
25
+ line2: line2,
26
+ city: city,
27
+ countryCode: countryCode,
28
+ country: country,
29
+ regionCode: regionCode,
30
+ zipCode: zipCode,
31
+ }.to_json
32
+ end
33
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../constants"
4
+
5
+ class DatadomeModule
6
+ attr_accessor :requestTimeMicros, :name, :version
7
+
8
+ def initialize
9
+ @requestTimeMicros = Time.now.strftime("%s%6N").to_i
10
+ @name = "Fraud SDK Ruby"
11
+ @version = SDK_VERSION
12
+ end
13
+
14
+ def to_json(options = {})
15
+ {
16
+ requestTimeMicros: @requestTimeMicros,
17
+ name: @name,
18
+ version: @version,
19
+ }.to_json
20
+ end
21
+ end
22
+
23
+ class DataDomeHeaders
24
+ attr_accessor :addr, :clientIp, :contentype, :host, :port, :xRealIP, :xForwardedForIp,
25
+ :acceptEncoding, :acceptLanguage, :accept, :method, :protocol, :serverHostname,
26
+ :referer, :userAgent, :from, :request, :origin, :acceptCharset, :connection,
27
+ :clientId, :secCHUA, :secCHUAMobile, :secCHUAPlatform, :secCHUAArch,
28
+ :secCHUAFullVersionList, :secCHUAModel, :secCHDeviceMemory
29
+
30
+ def initialize(request)
31
+ @addr = request.remote_ip
32
+ @clientIp = request.headers["HTTP_CLIENT_IP"]
33
+ @contentype = truncate(request.headers["Content-Type"], 64)
34
+ @host = truncate(request.headers["HTTP_HOST"], 512)
35
+ @port = request.port || 0
36
+ @xRealIP = truncate(request.headers["HTTP_X_REAL_IP"], 128)
37
+ @xForwardedForIp = truncate(request.headers["HTTP_X_FORWARDED_FOR"], -512)
38
+ @acceptEncoding = truncate(request.headers["HTTP_ACCEPT_ENCODING"], 128)
39
+ @acceptLanguage = truncate(request.headers["HTTP_ACCEPT_LANGUAGE"], 256)
40
+ @accept = truncate(request.headers["HTTP_ACCEPT"], 512)
41
+ @method = request.method
42
+ @protocol = request.protocol.gsub("://", "")
43
+ @serverHostname = truncate(request.headers["HTTP_HOST"], 256)
44
+ @referer = truncate(request.headers["HTTP_REFERER"], 1024)
45
+ @userAgent = truncate(request.headers["HTTP_USER_AGENT"], 768)
46
+ @from = truncate(request.headers["HTTP_FROM"], 128)
47
+ @request = [request.path, request.query_string].reject(&:blank?).join("?")[0, 2048]
48
+ @origin = truncate(request.headers["HTTP_ORIGIN"], 512)
49
+ @acceptCharset = truncate(request.headers["HTTP_ACCEPT_CHARSET"], 128)
50
+ @connection = truncate(request.headers["HTTP_CONNECTION"], 128)
51
+ @clientId = request.cookies["datadome"]
52
+ @secCHUA = truncate(request.headers["HTTP_SEC_CH_UA"], 128)
53
+ @secCHUAMobile = truncate(request.headers["HTTP_SEC_CH_UA_MOBILE"], 8)
54
+ @secCHUAPlatform = truncate(request.headers["HTTP_SEC_CH_UA_PLATFORM"], 32)
55
+ @secCHUAArch = truncate(request.headers["HTTP_SEC_CH_UA_ARCH"], 16)
56
+ @secCHUAFullVersionList = truncate(request.headers["HTTP_SEC_CH_UA_FULL_VERSION_LIST"], 256)
57
+ @secCHUAModel = truncate(request.headers["HTTP_SEC_CH_UA_MODEL"], 128)
58
+ @secCHDeviceMemory = truncate(request.headers["HTTP_SEC_CH_DEVICE_MEMORY"], 8)
59
+ end
60
+
61
+ def truncate(value, max_length = nil)
62
+ return value if value.nil? || max_length.nil?
63
+
64
+ max_length < 0 ? value[max_length] : value[0, max_length]
65
+ end
66
+ end
67
+
68
+ class DataDomeRequest
69
+ attr_accessor :module, :header
70
+
71
+ def initialize(request)
72
+ @module = DatadomeModule.new
73
+ @header = DataDomeHeaders.new(request)
74
+ end
75
+
76
+ def to_json(options = {})
77
+ {
78
+ module: @module.to_json,
79
+ header: @header.to_json,
80
+ }.to_json
81
+ end
82
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DataDomeResponseAction
4
+ ALLOW = "allow"
5
+ DENY = "deny"
6
+ end
7
+
8
+ module DataDomeResponseStatus
9
+ OK = "ok"
10
+ FAILURE = "failure"
11
+ TIMEOUT = "timeout"
12
+ end
13
+
14
+ class Error
15
+ attr_accessor :field, :error
16
+ end
17
+
18
+ class DataDomeResponse
19
+ attr_accessor :action, :status
20
+
21
+ def initialize(action, status)
22
+ @action = action
23
+ @status = status
24
+ end
25
+
26
+ def to_s
27
+ "DataDomeResponse: action=#{action}, status=#{status}"
28
+ end
29
+
30
+ def to_json(options = {})
31
+ {
32
+ action: @action,
33
+ status: @status,
34
+ }.to_json
35
+ end
36
+ end
37
+
38
+ class DataDomeResponseSuccess < DataDomeResponse
39
+ attr_accessor :ip, :reasons, :location
40
+
41
+ def initialize(action, status, reasons, ip, location)
42
+ super(action, status)
43
+ @reasons = reasons
44
+ @ip = ip
45
+ @location = location
46
+ end
47
+
48
+ def to_s
49
+ "DataDomeResponseSuccess: action=#{action}, status=#{status}, reasons=#{reasons}, ip=#{ip}, location=#{location}"
50
+ end
51
+
52
+ def to_json(options = {})
53
+ {
54
+ action: @action,
55
+ status: @status,
56
+ reasons: @reasons,
57
+ ip: @ip,
58
+ location: @location,
59
+ }.to_json
60
+ end
61
+ end
62
+
63
+ class DataDomeResponseError < DataDomeResponse
64
+ attr_accessor :message, :errors
65
+
66
+ def initialize(action, status, message, errors)
67
+ super(action, status)
68
+ @message = message
69
+ @errors = errors
70
+ end
71
+
72
+ def to_s
73
+ "DataDomeResponseError: action=#{action}, status=#{status}, message=#{message}, errors=#{errors}"
74
+ end
75
+
76
+ def to_json(options = {})
77
+ {
78
+ action: @action,
79
+ status: @status,
80
+ message: @message,
81
+ errors: @errors,
82
+ }.to_json
83
+ end
84
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionType
4
+ LOGIN = "login"
5
+ REGISTER = "registration"
6
+ PAYMENT = "payment"
7
+ end
8
+
9
+ module StatusType
10
+ SUCCEEDED = "succeeded"
11
+ FAILED = "failed"
12
+ UNDEFINED = "undefined"
13
+ end
14
+
15
+ class DataDomeEvent
16
+ attr_accessor :action, :status, :account
17
+
18
+ def initialize(action, account, status = StatusType::UNDEFINED)
19
+ @action = action
20
+ @account = account
21
+ @status = status
22
+ end
23
+
24
+ def merge_with(request_data)
25
+ request_data.instance_variable_set(:@account, @account)
26
+ request_data.instance_variable_set(:@status, @status)
27
+ request_data
28
+ end
29
+ end
30
+
31
+ class LoginEvent < DataDomeEvent
32
+ def initialize(account, status = StatusType::UNDEFINED)
33
+ super(ActionType::LOGIN, account, status)
34
+ end
35
+ end
36
+
37
+ class RegistrationEvent < DataDomeEvent
38
+ attr_accessor :user, :session
39
+
40
+ def initialize(account, user, session = nil, status = StatusType::UNDEFINED)
41
+ super(ActionType::REGISTER, account, status)
42
+ @session = session
43
+ @user = user
44
+ end
45
+
46
+ def merge_with(request_data)
47
+ super(request_data)
48
+ request_data.instance_variable_set(:@session, @session)
49
+ request_data.instance_variable_set(:@user, @user)
50
+ request_data
51
+ end
52
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OperationType
4
+ VALIDATE = "validate"
5
+ COLLECT = "collect"
6
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ class DataDomeSession
4
+ def initialize(id, createdAt = Time.now.strftime("%s%6N").to_i)
5
+ @id = id
6
+ @createdAt = createdAt
7
+ end
8
+
9
+ def to_s
10
+ "DataDomeSession: id=#{id}, createdAt =#{createdAt}"
11
+ end
12
+
13
+ def to_json(options = {})
14
+ {
15
+ id: id,
16
+ createdAt: createdAt,
17
+ }.to_json
18
+ end
19
+ end
data/lib/model/user.rb ADDED
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "time"
4
+ require_relative "./address"
5
+
6
+ module Title
7
+ EMPTY = nil
8
+ MR = "mr"
9
+ MRS = "mrs"
10
+ MX = "mx"
11
+ end
12
+
13
+ class DataDomeUser
14
+ def initialize(id, title = nil, firstName = nil, lastName = nil, createdAt = Time.now.iso8601, phone = nil, email = nil, address = nil)
15
+ @id = id
16
+ @title = title
17
+ @firstName = firstName
18
+ @lastName = lastName
19
+ @createdAt = createdAt
20
+ @phone = phone
21
+ @email = email
22
+ @address = address
23
+ end
24
+
25
+ def to_s
26
+ "DataDomeUser: id=#{id}, title =#{title}, firstname =#{firstName}, lastname =#{lastName} createdAt =#{createdAt}, phone =#{phone}, address= #{address}"
27
+ end
28
+
29
+ def to_json(options = {})
30
+ {
31
+ id: @id,
32
+ firstName: @firstName,
33
+ lastName: @lastName,
34
+ createdAt: @createdAt,
35
+ phone: @phone,
36
+ email: @email,
37
+ address: @address,
38
+ }.to_json
39
+ end
40
+ end
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: datadome_fraud_sdk_ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - DataDome
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-04-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: faraday
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.8'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.8'
27
+ description: DataDome Fraud Protection - Ruby SDK
28
+ email: support@datadome.co
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - README.md
34
+ - lib/constants.rb
35
+ - lib/datadome_fraud_sdk_ruby.rb
36
+ - lib/model/address.rb
37
+ - lib/model/api/request.rb
38
+ - lib/model/api/response.rb
39
+ - lib/model/events.rb
40
+ - lib/model/operation.rb
41
+ - lib/model/session.rb
42
+ - lib/model/user.rb
43
+ homepage: https://datadome.co/
44
+ licenses:
45
+ - Apache-2.0
46
+ metadata: {}
47
+ post_install_message:
48
+ rdoc_options: []
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ requirements: []
62
+ rubygems_version: 3.4.19
63
+ signing_key:
64
+ specification_version: 4
65
+ summary: DataDome Account Protect detects account takeover threats and protects you
66
+ against them.
67
+ test_files: []