noresponse 2.0.17

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 (231) hide show
  1. checksums.yaml +7 -0
  2. data/lib/crystalline/enum.rbi +17 -0
  3. data/lib/crystalline/metadata_fields.rb +263 -0
  4. data/lib/crystalline/module.rb +186 -0
  5. data/lib/crystalline/types.rb +168 -0
  6. data/lib/crystalline/utils.rb +67 -0
  7. data/lib/crystalline.rb +11 -0
  8. data/lib/no_response/assessments.rb +1252 -0
  9. data/lib/no_response/auth.rb +263 -0
  10. data/lib/no_response/issued_credential.rb +21 -0
  11. data/lib/no_response/keys.rb +318 -0
  12. data/lib/no_response/late_results.rb +301 -0
  13. data/lib/no_response/models/callbacks.rb +10 -0
  14. data/lib/no_response/models/errors/apistatuserror.rb +41 -0
  15. data/lib/no_response/models/errors/apistatuserror.rbi +30 -0
  16. data/lib/no_response/models/errors/authenticationerror.rb +108 -0
  17. data/lib/no_response/models/errors/authenticationerror.rbi +56 -0
  18. data/lib/no_response/models/errors/badrequesterror.rb +108 -0
  19. data/lib/no_response/models/errors/badrequesterror.rbi +56 -0
  20. data/lib/no_response/models/errors/conflicterror.rb +108 -0
  21. data/lib/no_response/models/errors/conflicterror.rbi +56 -0
  22. data/lib/no_response/models/errors/notfounderror.rb +108 -0
  23. data/lib/no_response/models/errors/notfounderror.rbi +56 -0
  24. data/lib/no_response/models/errors/permissiondeniederror.rb +108 -0
  25. data/lib/no_response/models/errors/permissiondeniederror.rbi +56 -0
  26. data/lib/no_response/models/errors/ratelimiterror.rb +108 -0
  27. data/lib/no_response/models/errors/ratelimiterror.rbi +56 -0
  28. data/lib/no_response/models/errors.rb +16 -0
  29. data/lib/no_response/models/operations/createassessment_request.rb +36 -0
  30. data/lib/no_response/models/operations/createassessment_request.rbi +20 -0
  31. data/lib/no_response/models/operations/createassessment_response.rb +32 -0
  32. data/lib/no_response/models/operations/createassessment_response.rbi +20 -0
  33. data/lib/no_response/models/operations/getassessment_request.rb +41 -0
  34. data/lib/no_response/models/operations/getassessment_request.rbi +20 -0
  35. data/lib/no_response/models/operations/getassessment_response.rb +32 -0
  36. data/lib/no_response/models/operations/getassessment_response.rbi +20 -0
  37. data/lib/no_response/models/operations/queryaccountassessments_response.rb +44 -0
  38. data/lib/no_response/models/operations/queryaccountassessments_response.rbi +20 -0
  39. data/lib/no_response/models/operations/recordassessmentoutcome_request.rb +52 -0
  40. data/lib/no_response/models/operations/recordassessmentoutcome_request.rbi +26 -0
  41. data/lib/no_response/models/operations/recordassessmentoutcome_response.rb +32 -0
  42. data/lib/no_response/models/operations/recordassessmentoutcome_response.rbi +20 -0
  43. data/lib/no_response/models/operations/rotateapikey_response.rb +32 -0
  44. data/lib/no_response/models/operations/rotateapikey_response.rbi +20 -0
  45. data/lib/no_response/models/operations.rb +17 -0
  46. data/lib/no_response/models/shared/alternate_name.rb +23 -0
  47. data/lib/no_response/models/shared/alternate_name.rbi +9 -0
  48. data/lib/no_response/models/shared/alternate_name_union.rb +23 -0
  49. data/lib/no_response/models/shared/alternate_name_union.rbi +9 -0
  50. data/lib/no_response/models/shared/assessment.rb +235 -0
  51. data/lib/no_response/models/shared/assessment.rbi +116 -0
  52. data/lib/no_response/models/shared/assessmentlist.rb +78 -0
  53. data/lib/no_response/models/shared/assessmentlist.rbi +38 -0
  54. data/lib/no_response/models/shared/assessmentoutcome.rb +61 -0
  55. data/lib/no_response/models/shared/assessmentoutcome.rbi +32 -0
  56. data/lib/no_response/models/shared/assessmentoutcomerequest.rb +71 -0
  57. data/lib/no_response/models/shared/assessmentoutcomerequest.rbi +32 -0
  58. data/lib/no_response/models/shared/assessmentquery.rb +70 -0
  59. data/lib/no_response/models/shared/assessmentquery.rbi +32 -0
  60. data/lib/no_response/models/shared/assessmentrequest.rb +125 -0
  61. data/lib/no_response/models/shared/assessmentrequest.rbi +62 -0
  62. data/lib/no_response/models/shared/assessmentrequestclientcontext.rb +125 -0
  63. data/lib/no_response/models/shared/assessmentrequestclientcontext.rbi +62 -0
  64. data/lib/no_response/models/shared/assessmentrequestidentifiers.rb +74 -0
  65. data/lib/no_response/models/shared/assessmentrequestidentifiers.rbi +38 -0
  66. data/lib/no_response/models/shared/assessmentrequestissuedcredential.rb +33 -0
  67. data/lib/no_response/models/shared/assessmentrequestissuedcredential.rbi +14 -0
  68. data/lib/no_response/models/shared/assessmentrequestpolicycontext.rb +46 -0
  69. data/lib/no_response/models/shared/assessmentrequestpolicycontext.rbi +20 -0
  70. data/lib/no_response/models/shared/assessmenttiming.rb +41 -0
  71. data/lib/no_response/models/shared/assessmenttiming.rbi +20 -0
  72. data/lib/no_response/models/shared/assurancelevel.rb +18 -0
  73. data/lib/no_response/models/shared/assurancelevel.rbi +9 -0
  74. data/lib/no_response/models/shared/authcontext.rb +285 -0
  75. data/lib/no_response/models/shared/authcontext.rbi +152 -0
  76. data/lib/no_response/models/shared/authcontextmfa.rb +55 -0
  77. data/lib/no_response/models/shared/authcontextmfa.rbi +26 -0
  78. data/lib/no_response/models/shared/authenticationerrorcode.rb +18 -0
  79. data/lib/no_response/models/shared/authenticationerrorcode.rbi +9 -0
  80. data/lib/no_response/models/shared/badrequesterrorcode.rb +20 -0
  81. data/lib/no_response/models/shared/badrequesterrorcode.rbi +9 -0
  82. data/lib/no_response/models/shared/band.rb +21 -0
  83. data/lib/no_response/models/shared/band.rbi +9 -0
  84. data/lib/no_response/models/shared/breach.rb +108 -0
  85. data/lib/no_response/models/shared/breach.rbi +56 -0
  86. data/lib/no_response/models/shared/breachexposed.rb +28 -0
  87. data/lib/no_response/models/shared/breachexposed.rbi +9 -0
  88. data/lib/no_response/models/shared/breachkind.rb +24 -0
  89. data/lib/no_response/models/shared/breachkind.rbi +9 -0
  90. data/lib/no_response/models/shared/breachselector.rb +20 -0
  91. data/lib/no_response/models/shared/breachselector.rbi +9 -0
  92. data/lib/no_response/models/shared/breachsummary.rb +109 -0
  93. data/lib/no_response/models/shared/breachsummary.rbi +56 -0
  94. data/lib/no_response/models/shared/confidence.rb +21 -0
  95. data/lib/no_response/models/shared/confidence.rbi +9 -0
  96. data/lib/no_response/models/shared/conflicterrorcode.rb +19 -0
  97. data/lib/no_response/models/shared/conflicterrorcode.rbi +9 -0
  98. data/lib/no_response/models/shared/consistency.rb +85 -0
  99. data/lib/no_response/models/shared/consistency.rbi +32 -0
  100. data/lib/no_response/models/shared/consistencyvalue.rb +21 -0
  101. data/lib/no_response/models/shared/consistencyvalue.rbi +9 -0
  102. data/lib/no_response/models/shared/country.rb +20 -0
  103. data/lib/no_response/models/shared/country.rbi +9 -0
  104. data/lib/no_response/models/shared/email.rb +23 -0
  105. data/lib/no_response/models/shared/email.rbi +9 -0
  106. data/lib/no_response/models/shared/email_union.rb +23 -0
  107. data/lib/no_response/models/shared/email_union.rbi +9 -0
  108. data/lib/no_response/models/shared/failurereason.rb +24 -0
  109. data/lib/no_response/models/shared/failurereason.rbi +9 -0
  110. data/lib/no_response/models/shared/flow.rb +18 -0
  111. data/lib/no_response/models/shared/flow.rbi +9 -0
  112. data/lib/no_response/models/shared/freshness.rb +20 -0
  113. data/lib/no_response/models/shared/freshness.rbi +9 -0
  114. data/lib/no_response/models/shared/hookpoint.rb +19 -0
  115. data/lib/no_response/models/shared/hookpoint.rbi +9 -0
  116. data/lib/no_response/models/shared/identifierkind.rb +21 -0
  117. data/lib/no_response/models/shared/identifierkind.rbi +9 -0
  118. data/lib/no_response/models/shared/incompletereason.rb +23 -0
  119. data/lib/no_response/models/shared/incompletereason.rbi +9 -0
  120. data/lib/no_response/models/shared/incompletesource.rb +53 -0
  121. data/lib/no_response/models/shared/incompletesource.rbi +20 -0
  122. data/lib/no_response/models/shared/ipsummary.rb +61 -0
  123. data/lib/no_response/models/shared/ipsummary.rbi +26 -0
  124. data/lib/no_response/models/shared/ipsummarytype.rb +24 -0
  125. data/lib/no_response/models/shared/ipsummarytype.rbi +9 -0
  126. data/lib/no_response/models/shared/keyrotation.rb +79 -0
  127. data/lib/no_response/models/shared/keyrotation.rbi +44 -0
  128. data/lib/no_response/models/shared/keyrotationrequest.rb +33 -0
  129. data/lib/no_response/models/shared/keyrotationrequest.rbi +14 -0
  130. data/lib/no_response/models/shared/lane.rb +21 -0
  131. data/lib/no_response/models/shared/lane.rbi +9 -0
  132. data/lib/no_response/models/shared/linetype.rb +22 -0
  133. data/lib/no_response/models/shared/linetype.rbi +9 -0
  134. data/lib/no_response/models/shared/location.rb +23 -0
  135. data/lib/no_response/models/shared/location.rbi +9 -0
  136. data/lib/no_response/models/shared/location_union.rb +23 -0
  137. data/lib/no_response/models/shared/location_union.rbi +9 -0
  138. data/lib/no_response/models/shared/name_candidate.rb +23 -0
  139. data/lib/no_response/models/shared/name_candidate.rbi +9 -0
  140. data/lib/no_response/models/shared/name_candidate_union.rb +23 -0
  141. data/lib/no_response/models/shared/name_candidate_union.rbi +9 -0
  142. data/lib/no_response/models/shared/notfounderrorcode.rb +18 -0
  143. data/lib/no_response/models/shared/notfounderrorcode.rbi +9 -0
  144. data/lib/no_response/models/shared/outcome.rb +23 -0
  145. data/lib/no_response/models/shared/outcome.rbi +9 -0
  146. data/lib/no_response/models/shared/permissiondeniederrorcode.rb +18 -0
  147. data/lib/no_response/models/shared/permissiondeniederrorcode.rbi +9 -0
  148. data/lib/no_response/models/shared/phoneenrichment.rb +32 -0
  149. data/lib/no_response/models/shared/phoneenrichment.rbi +14 -0
  150. data/lib/no_response/models/shared/phoneenrichmentfields.rb +451 -0
  151. data/lib/no_response/models/shared/phoneenrichmentfields.rbi +212 -0
  152. data/lib/no_response/models/shared/platform.rb +18 -0
  153. data/lib/no_response/models/shared/platform.rbi +9 -0
  154. data/lib/no_response/models/shared/probleminvalidparam.rb +48 -0
  155. data/lib/no_response/models/shared/probleminvalidparam.rbi +26 -0
  156. data/lib/no_response/models/shared/ratelimiterrorcode.rb +18 -0
  157. data/lib/no_response/models/shared/ratelimiterrorcode.rbi +9 -0
  158. data/lib/no_response/models/shared/reasoncode.rb +52 -0
  159. data/lib/no_response/models/shared/reasoncode.rbi +9 -0
  160. data/lib/no_response/models/shared/reasonidentifier.rb +23 -0
  161. data/lib/no_response/models/shared/reasonidentifier.rbi +9 -0
  162. data/lib/no_response/models/shared/recommendation.rb +20 -0
  163. data/lib/no_response/models/shared/recommendation.rbi +9 -0
  164. data/lib/no_response/models/shared/responsemode.rb +16 -0
  165. data/lib/no_response/models/shared/responsemode.rbi +9 -0
  166. data/lib/no_response/models/shared/risk.rb +171 -0
  167. data/lib/no_response/models/shared/risk.rbi +80 -0
  168. data/lib/no_response/models/shared/riskconfidence.rb +20 -0
  169. data/lib/no_response/models/shared/riskconfidence.rbi +9 -0
  170. data/lib/no_response/models/shared/riskcoverage.rb +49 -0
  171. data/lib/no_response/models/shared/riskcoverage.rbi +26 -0
  172. data/lib/no_response/models/shared/riskdetails.rb +55 -0
  173. data/lib/no_response/models/shared/riskdetails.rbi +26 -0
  174. data/lib/no_response/models/shared/riskdetailsbreachname.rb +40 -0
  175. data/lib/no_response/models/shared/riskdetailsbreachname.rbi +20 -0
  176. data/lib/no_response/models/shared/riskdetailsphonename.rb +40 -0
  177. data/lib/no_response/models/shared/riskdetailsphonename.rbi +20 -0
  178. data/lib/no_response/models/shared/riskreason.rb +67 -0
  179. data/lib/no_response/models/shared/riskreason.rbi +26 -0
  180. data/lib/no_response/models/shared/risksummary.rb +73 -0
  181. data/lib/no_response/models/shared/risksummary.rbi +38 -0
  182. data/lib/no_response/models/shared/risksummaryemail.rb +55 -0
  183. data/lib/no_response/models/shared/risksummaryemail.rbi +26 -0
  184. data/lib/no_response/models/shared/risksummaryphone.rb +69 -0
  185. data/lib/no_response/models/shared/risksummaryphone.rbi +32 -0
  186. data/lib/no_response/models/shared/risksummaryusername.rb +41 -0
  187. data/lib/no_response/models/shared/risksummaryusername.rbi +20 -0
  188. data/lib/no_response/models/shared/scope.rb +21 -0
  189. data/lib/no_response/models/shared/scope.rbi +9 -0
  190. data/lib/no_response/models/shared/security.rb +32 -0
  191. data/lib/no_response/models/shared/security.rbi +14 -0
  192. data/lib/no_response/models/shared/signal.rb +149 -0
  193. data/lib/no_response/models/shared/signal.rbi +68 -0
  194. data/lib/no_response/models/shared/signaloutcome.rb +22 -0
  195. data/lib/no_response/models/shared/signaloutcome.rbi +9 -0
  196. data/lib/no_response/models/shared/social_profile.rb +23 -0
  197. data/lib/no_response/models/shared/social_profile.rbi +9 -0
  198. data/lib/no_response/models/shared/social_profile_union.rb +23 -0
  199. data/lib/no_response/models/shared/social_profile_union.rbi +9 -0
  200. data/lib/no_response/models/shared/status.rb +20 -0
  201. data/lib/no_response/models/shared/status.rbi +9 -0
  202. data/lib/no_response/models/shared/timezone.rb +23 -0
  203. data/lib/no_response/models/shared/timezone.rbi +9 -0
  204. data/lib/no_response/models/shared/timezone_union.rb +23 -0
  205. data/lib/no_response/models/shared/timezone_union.rbi +9 -0
  206. data/lib/no_response/models/shared/transport.rb +17 -0
  207. data/lib/no_response/models/shared/transport.rbi +9 -0
  208. data/lib/no_response/models/shared/website.rb +23 -0
  209. data/lib/no_response/models/shared/website.rbi +9 -0
  210. data/lib/no_response/models/shared/website_union.rb +23 -0
  211. data/lib/no_response/models/shared/website_union.rbi +9 -0
  212. data/lib/no_response/models/shared.rb +92 -0
  213. data/lib/no_response/noresponseclient.rb +110 -0
  214. data/lib/no_response/sdk_hooks/hooks.rb +97 -0
  215. data/lib/no_response/sdk_hooks/idempotency_key_hook.rb +25 -0
  216. data/lib/no_response/sdk_hooks/registration.rb +43 -0
  217. data/lib/no_response/sdk_hooks/types.rb +178 -0
  218. data/lib/no_response/sdkconfiguration.rb +107 -0
  219. data/lib/no_response/sdkconfiguration.rbi +83 -0
  220. data/lib/no_response/utils/forms.rb +230 -0
  221. data/lib/no_response/utils/headers.rb +80 -0
  222. data/lib/no_response/utils/query_params.rb +165 -0
  223. data/lib/no_response/utils/request_bodies.rb +77 -0
  224. data/lib/no_response/utils/retries.rb +137 -0
  225. data/lib/no_response/utils/security.rb +156 -0
  226. data/lib/no_response/utils/url.rb +122 -0
  227. data/lib/no_response/utils/utils.rb +243 -0
  228. data/lib/no_response/wait.rb +66 -0
  229. data/lib/no_response/webhooks.rb +177 -0
  230. data/lib/noresponse.rb +25 -0
  231. metadata +501 -0
@@ -0,0 +1,301 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ # Late-result handlers. Hand-written beside the generated client.
5
+ #
6
+ # An assessment can be revised after the signup was allowed: a slow source
7
+ # settles and the recommendation rises. NoResponse sends the new version as a
8
+ # signed assessment.updated webhook. LateResults.handle verifies that delivery
9
+ # with Webhooks.construct_event, picks an action from your policy, and acts
10
+ # through the auth provider's admin API: revoke the user's sessions or ban the
11
+ # user.
12
+ #
13
+ # The handler verifies the delivery itself, so it never acts on an unverified
14
+ # event. The provider calls are thin: you pass the credentials, and each
15
+ # action is the provider's documented admin endpoint.
16
+
17
+ require 'erb'
18
+ require 'json'
19
+ require 'net/http'
20
+ require 'uri'
21
+ require_relative 'webhooks'
22
+
23
+ module NoResponse
24
+ module LateResults
25
+ # What to do to the user. BAN also ends every session.
26
+ NONE = 'none'
27
+ REVOKE_SESSIONS = 'revoke_sessions'
28
+ BAN = 'ban'
29
+ ACTIONS = [NONE, REVOKE_SESSIONS, BAN].freeze
30
+
31
+ # Revoke sessions on review; do nothing on allow or step_up. Banning is opt-in.
32
+ DEFAULT_POLICY = { 'allow' => NONE, 'step_up' => NONE, 'review' => REVOKE_SESSIONS }.freeze
33
+
34
+ RECOMMENDATIONS = %w[allow step_up review].freeze
35
+
36
+ # Raised when the provider's admin API refuses a call. Answer the webhook
37
+ # with a 5xx so NoResponse retries it. The message holds no response body
38
+ # and no credentials.
39
+ class ProviderActionError < StandardError
40
+ attr_reader :provider, :status
41
+
42
+ def initialize(provider, status, message)
43
+ super(message)
44
+ @provider = provider
45
+ @status = status
46
+ end
47
+ end
48
+
49
+ # event is the verified Webhooks::Event. action is the action taken: NONE
50
+ # when the policy, the event or the user id called for nothing. user_id is
51
+ # the provider user id acted on, set only when an action was taken. Answer
52
+ # an endpoint.verification with Webhooks.verification_response(event).
53
+ Result = Struct.new(:event, :action, :user_id, keyword_init: true)
54
+
55
+ class << self
56
+ # The action your policy names for a verified event. Only a served
57
+ # assessment with a recommendation can act. policy maps allow, step_up
58
+ # and review (as strings or symbols) to NONE, REVOKE_SESSIONS or BAN; a
59
+ # recommendation you leave out does nothing. An unknown action raises
60
+ # ArgumentError.
61
+ def action_for(event, policy = DEFAULT_POLICY)
62
+ return NONE if event.type != 'assessment.updated' || event.expired?
63
+
64
+ data = event.data
65
+ recommendation = data.recommendation || data.risk&.recommendation
66
+ recommendation = recommendation.to_s
67
+ return NONE unless RECOMMENDATIONS.include?(recommendation)
68
+
69
+ table = policy.to_h.transform_keys(&:to_s)
70
+ return NONE unless table.key?(recommendation)
71
+
72
+ action = table[recommendation]
73
+ return NONE if action.nil?
74
+
75
+ action = action.to_s
76
+ raise ArgumentError, "unknown late result action #{action}" unless ACTIONS.include?(action)
77
+
78
+ action
79
+ end
80
+
81
+ # Verify a webhook delivery and apply your policy to it.
82
+ #
83
+ # payload, headers and secrets, plus tolerance_seconds and now, are
84
+ # exactly what Webhooks.construct_event takes; a delivery that fails
85
+ # verification raises Webhooks::VerificationError before any provider
86
+ # call. Deduplicate on result.event.id and ignore versions older than one
87
+ # you have acted on.
88
+ #
89
+ # policy defaults to DEFAULT_POLICY. user_id is an optional callable
90
+ # (account_id, event) returning the provider user id for the assessment's
91
+ # account_id; it defaults to the account_id itself, as when you assess
92
+ # with the provider's user id. Return nil or "" to take no action.
93
+ def handle(payload, headers, secrets, provider, policy: DEFAULT_POLICY, user_id: nil, **verify)
94
+ event = Webhooks.construct_event(payload, headers, secrets, **verify)
95
+ action = action_for(event, policy)
96
+ return Result.new(event: event, action: NONE) if action == NONE || event.type != 'assessment.updated'
97
+
98
+ account_id = event.data.account_id
99
+ resolved = user_id ? user_id.call(account_id, event) : account_id
100
+ return Result.new(event: event, action: NONE) if resolved.nil? || resolved.to_s.empty?
101
+
102
+ resolved = resolved.to_s
103
+ if action == BAN
104
+ provider.ban(resolved)
105
+ else
106
+ provider.revoke_sessions(resolved)
107
+ end
108
+ Result.new(event: event, action: action, user_id: resolved)
109
+ end
110
+
111
+ # Clerk Backend API. revoke_sessions lists the user's active sessions
112
+ # (GET /sessions?user_id&status=active) and revokes each
113
+ # (POST /sessions/{id}/revoke). ban is POST /users/{id}/ban, which
114
+ # revokes every session and blocks sign-in.
115
+ #
116
+ # base_url defaults to https://api.clerk.com/v1. http_client is an
117
+ # optional callable (method, uri, headers, body) returning
118
+ # [status, body]; the default uses Net::HTTP.
119
+ def clerk(secret_key:, base_url: 'https://api.clerk.com/v1', http_client: nil)
120
+ raise ArgumentError, 'clerk needs secret_key' if secret_key.to_s.empty?
121
+
122
+ Clerk.new(Caller.new('clerk', base_url, "Bearer #{secret_key}", http_client))
123
+ end
124
+
125
+ # WorkOS User Management. revoke_sessions pages through
126
+ # GET /user_management/users/{id}/sessions and revokes each active one
127
+ # with POST /user_management/sessions/revoke. WorkOS has no block or ban,
128
+ # so ban revokes the sessions and then deletes the user
129
+ # (DELETE /user_management/users/{id}); deletion is permanent.
130
+ #
131
+ # base_url defaults to https://api.workos.com.
132
+ def workos(api_key:, base_url: 'https://api.workos.com', http_client: nil)
133
+ raise ArgumentError, 'workos needs api_key' if api_key.to_s.empty?
134
+
135
+ WorkOS.new(Caller.new('workos', base_url, "Bearer #{api_key}", http_client))
136
+ end
137
+
138
+ # Auth0 Management API. revoke_sessions deletes every session
139
+ # (DELETE /users/{id}/sessions) and refresh token
140
+ # (DELETE /users/{id}/refresh-tokens). ban first sets blocked: true
141
+ # (PATCH /users/{id}), then does the same. Pass domain (such as
142
+ # example.eu.auth0.com; the API is https://{domain}/api/v2) or base_url.
143
+ # token is a Management API access token with update:users,
144
+ # delete:sessions and delete:refresh_tokens.
145
+ def auth0(token:, domain: nil, base_url: nil, http_client: nil)
146
+ raise ArgumentError, 'auth0 needs token' if token.to_s.empty?
147
+
148
+ base = base_url || (domain.to_s.empty? ? nil : "https://#{domain}/api/v2")
149
+ raise ArgumentError, 'auth0 needs domain or base_url' if base.nil?
150
+
151
+ Auth0.new(Caller.new('auth0', base, "Bearer #{token}", http_client))
152
+ end
153
+ end
154
+
155
+ # One provider HTTP client: base URL, Authorization header, error mapping.
156
+ class Caller
157
+ def initialize(name, base_url, authorization, http_client)
158
+ @name = name
159
+ @base = base_url.to_s.sub(%r{/+\z}, '')
160
+ @authorization = authorization
161
+ @http_client = http_client
162
+ end
163
+
164
+ attr_reader :name
165
+
166
+ def call(method, path, query: nil, body: nil, allow_404: false)
167
+ uri = URI.parse(@base + path)
168
+ uri.query = URI.encode_www_form(query) if query && !query.empty?
169
+ headers = { 'Authorization' => @authorization, 'Accept' => 'application/json' }
170
+ payload = nil
171
+ unless body.nil?
172
+ headers['Content-Type'] = 'application/json'
173
+ payload = JSON.generate(body)
174
+ end
175
+ status, text = perform(method, uri, headers, payload)
176
+ return nil if allow_404 && status == 404
177
+ raise ProviderActionError.new(@name, status, "#{@name} #{method} #{path} answered #{status}") unless status.between?(200, 299)
178
+ return nil if text.nil? || text.empty?
179
+
180
+ begin
181
+ JSON.parse(text)
182
+ rescue JSON::ParserError
183
+ nil
184
+ end
185
+ end
186
+
187
+ private
188
+
189
+ def perform(method, uri, headers, payload)
190
+ return @http_client.call(method, uri, headers, payload) if @http_client
191
+
192
+ http = Net::HTTP.new(uri.host, uri.port)
193
+ http.use_ssl = uri.scheme == 'https'
194
+ http.open_timeout = 10
195
+ http.read_timeout = 30
196
+ request = Net::HTTPGenericRequest.new(method, !payload.nil?, true, uri.request_uri, headers)
197
+ request.body = payload unless payload.nil?
198
+ response = http.start { |connection| connection.request(request) }
199
+ [response.code.to_i, response.body.to_s]
200
+ end
201
+ end
202
+
203
+ module Segment
204
+ def self.encode(id)
205
+ raise ArgumentError, 'user id must be a non-empty string' unless id.is_a?(String) && !id.empty?
206
+
207
+ ERB::Util.url_encode(id)
208
+ end
209
+
210
+ def self.sessions(body)
211
+ list = body.is_a?(Hash) ? body['data'] : body
212
+ list.is_a?(Array) ? list.grep(Hash) : []
213
+ end
214
+
215
+ def self.active_ids(list)
216
+ list.select { |s| s['status'] == 'active' && s['id'].is_a?(String) && !s['id'].empty? }.map { |s| s['id'] }
217
+ end
218
+ end
219
+
220
+ class Clerk
221
+ PAGE = 100
222
+
223
+ def initialize(caller_)
224
+ @call = caller_
225
+ end
226
+
227
+ def name = 'clerk'
228
+
229
+ def revoke_sessions(user_id)
230
+ Segment.encode(user_id)
231
+ active = []
232
+ (0...1000).each do |round|
233
+ page = Segment.sessions(@call.call('GET', '/sessions', query: [['user_id', user_id], %w[status active],
234
+ ['limit', PAGE.to_s],
235
+ ['offset', (round * PAGE).to_s]]))
236
+ active.concat(Segment.active_ids(page))
237
+ break if page.length < PAGE
238
+ end
239
+ active.each { |id| @call.call('POST', "/sessions/#{ERB::Util.url_encode(id)}/revoke") }
240
+ nil
241
+ end
242
+
243
+ def ban(user_id)
244
+ @call.call('POST', "/users/#{Segment.encode(user_id)}/ban")
245
+ nil
246
+ end
247
+ end
248
+
249
+ class WorkOS
250
+ def initialize(caller_)
251
+ @call = caller_
252
+ end
253
+
254
+ def name = 'workos'
255
+
256
+ def revoke_sessions(user_id)
257
+ user = Segment.encode(user_id)
258
+ active = []
259
+ after = T.let(nil, T.nilable(String))
260
+ 1000.times do
261
+ query = T.let([%w[limit 100]], T::Array[T::Array[String]])
262
+ query << ['after', after] if after
263
+ body = @call.call('GET', "/user_management/users/#{user}/sessions", query: query)
264
+ active.concat(Segment.active_ids(Segment.sessions(body)))
265
+ nxt = body.is_a?(Hash) && body['list_metadata'].is_a?(Hash) ? body['list_metadata']['after'] : nil
266
+ break unless nxt.is_a?(String) && !nxt.empty?
267
+
268
+ after = nxt
269
+ end
270
+ active.each { |id| @call.call('POST', '/user_management/sessions/revoke', body: { 'session_id' => id }) }
271
+ nil
272
+ end
273
+
274
+ def ban(user_id)
275
+ revoke_sessions(user_id)
276
+ @call.call('DELETE', "/user_management/users/#{Segment.encode(user_id)}", allow_404: true)
277
+ nil
278
+ end
279
+ end
280
+
281
+ class Auth0
282
+ def initialize(caller_)
283
+ @call = caller_
284
+ end
285
+
286
+ def name = 'auth0'
287
+
288
+ def revoke_sessions(user_id)
289
+ user = Segment.encode(user_id)
290
+ @call.call('DELETE', "/users/#{user}/sessions")
291
+ @call.call('DELETE', "/users/#{user}/refresh-tokens")
292
+ nil
293
+ end
294
+
295
+ def ban(user_id)
296
+ @call.call('PATCH', "/users/#{Segment.encode(user_id)}", body: { 'blocked' => true })
297
+ revoke_sessions(user_id)
298
+ end
299
+ end
300
+ end
301
+ end
@@ -0,0 +1,10 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module NoResponse
5
+ module Models
6
+ module Callbacks
7
+
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,41 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module NoResponse
5
+ module Models
6
+ module Errors
7
+
8
+ # Every error the SDK raises for an API response is a NoResponseError.
9
+ class NoResponseError < StandardError
10
+ end
11
+
12
+ class APIStatusError < NoResponseError
13
+ include Crystalline::MetadataFields
14
+ extend T::Sig
15
+
16
+ field :body, T.nilable(::String), {'format_json': {'letter_case': ::NoResponse::Utils.field_name("body")}}
17
+ field :raw_response, T.nilable(Faraday::Response), {}
18
+ field(
19
+ :status_code,
20
+ T.nilable(::Integer),
21
+ {'format_json': {'letter_case': ::NoResponse::Utils.field_name("status_code")}}
22
+ )
23
+
24
+ sig { params(status_code: ::Integer, body: ::String, raw_response: Faraday::Response).void }
25
+ def initialize(status_code:, body:, raw_response:)
26
+ @status_code = status_code
27
+ @body = body
28
+ @raw_response = raw_response
29
+ end
30
+
31
+ def ==(other)
32
+ return false unless other.is_a?(self.class)
33
+ return false unless @status_code == other.status_code
34
+ return false unless @body == other.body
35
+ return false unless @raw_response == other.raw_response
36
+ true
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,30 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ class NoResponse::Models::Errors::NoResponseError < StandardError
5
+ end
6
+
7
+ class NoResponse::Models::Errors::APIStatusError < NoResponse::Models::Errors::NoResponseError
8
+ extend ::Crystalline::MetadataFields::ClassMethods
9
+ end
10
+
11
+ class NoResponse::Models::Errors::APIStatusError
12
+
13
+ def body
14
+ end
15
+
16
+ def body=(body)
17
+ end
18
+
19
+ def raw_response
20
+ end
21
+
22
+ def raw_response=(raw_response)
23
+ end
24
+
25
+ def status_code
26
+ end
27
+
28
+ def status_code=(status_code)
29
+ end
30
+ end
@@ -0,0 +1,108 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "apistatuserror"
5
+
6
+ module NoResponse
7
+ module Models
8
+ module Errors
9
+ # HTTP 401 problem document.
10
+ class AuthenticationError < NoResponse::Models::Errors::NoResponseError
11
+ extend T::Sig
12
+ include Crystalline::MetadataFields
13
+
14
+ field :type, ::String, {'format_json': {'letter_case': ::NoResponse::Utils.field_name("type"), required: true}}
15
+
16
+ field(
17
+ :title,
18
+ ::String,
19
+ {'format_json': {'letter_case': ::NoResponse::Utils.field_name("title"), required: true}}
20
+ )
21
+
22
+ field(
23
+ :detail,
24
+ ::String,
25
+ {'format_json': {'letter_case': ::NoResponse::Utils.field_name("detail"), required: true}}
26
+ )
27
+ # Machine codes an HTTP 401 answer can carry.
28
+ field(
29
+ :code,
30
+ Models::Shared::AuthenticationErrorCode,
31
+ {
32
+ 'format_json': {
33
+ 'letter_case': ::NoResponse::Utils.field_name("code"),
34
+ required: true,
35
+ 'decoder': ::NoResponse::Utils.open_enum_from_string(Models::Shared::AuthenticationErrorCode, false)
36
+ }
37
+ }
38
+ )
39
+
40
+ field(
41
+ :request_id,
42
+ ::String,
43
+ {'format_json': {'letter_case': ::NoResponse::Utils.field_name("request_id"), required: true}}
44
+ )
45
+
46
+ field(
47
+ :status,
48
+ ::Integer,
49
+ {'format_json': {'letter_case': ::NoResponse::Utils.field_name("status"), required: true}}
50
+ )
51
+
52
+ field(
53
+ :instance,
54
+ Crystalline::Nilable.new(::String),
55
+ {'format_json': {'letter_case': ::NoResponse::Utils.field_name("instance")}}
56
+ )
57
+
58
+ field(
59
+ :invalid_params,
60
+ Crystalline::Nilable.new(Crystalline::Array.new(Models::Shared::ProblemInvalidParam)),
61
+ {'format_json': {'letter_case': ::NoResponse::Utils.field_name("invalid_params")}}
62
+ )
63
+
64
+ sig {
65
+ params(
66
+ type: ::String,
67
+ title: ::String,
68
+ detail: ::String,
69
+ code: Models::Shared::AuthenticationErrorCode,
70
+ request_id: ::String,
71
+ status: ::Integer,
72
+ instance: T.nilable(::String),
73
+ invalid_params: T.nilable(T::Array[Models::Shared::ProblemInvalidParam])
74
+ )
75
+ .void
76
+ }
77
+ def initialize(type:, title:, detail:, code:, request_id:, status: 401, instance: nil, invalid_params: nil)
78
+ @type = type
79
+ @title = title
80
+ @detail = detail
81
+ @code = code
82
+ @request_id = request_id
83
+ unless status == 401
84
+ raise ArgumentError, "Invalid value for status"
85
+ end
86
+
87
+ @status = 401
88
+ @instance = instance
89
+ @invalid_params = invalid_params
90
+ end
91
+
92
+ sig { params(other: T.untyped).returns(T::Boolean) }
93
+ def ==(other)
94
+ return false unless other.is_a?(self.class)
95
+ return false unless @type == other.type
96
+ return false unless @title == other.title
97
+ return false unless @detail == other.detail
98
+ return false unless @code == other.code
99
+ return false unless @request_id == other.request_id
100
+ return false unless @status == other.status
101
+ return false unless @instance == other.instance
102
+ return false unless @invalid_params == other.invalid_params
103
+ true
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,56 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ class NoResponse::Models::Errors::AuthenticationError
5
+ extend ::Crystalline::MetadataFields::ClassMethods
6
+ end
7
+
8
+ class NoResponse::Models::Errors::AuthenticationError
9
+ def type
10
+ end
11
+
12
+ def type=(str_)
13
+ end
14
+
15
+ def title
16
+ end
17
+
18
+ def title=(str_)
19
+ end
20
+
21
+ def detail
22
+ end
23
+
24
+ def detail=(str_)
25
+ end
26
+
27
+ def code
28
+ end
29
+
30
+ def code=(str_)
31
+ end
32
+
33
+ def request_id
34
+ end
35
+
36
+ def request_id=(str_)
37
+ end
38
+
39
+ def status
40
+ end
41
+
42
+ def status=(str_)
43
+ end
44
+
45
+ def instance
46
+ end
47
+
48
+ def instance=(str_)
49
+ end
50
+
51
+ def invalid_params
52
+ end
53
+
54
+ def invalid_params=(str_)
55
+ end
56
+ end
@@ -0,0 +1,108 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "apistatuserror"
5
+
6
+ module NoResponse
7
+ module Models
8
+ module Errors
9
+ # HTTP 400 problem document.
10
+ class BadRequestError < NoResponse::Models::Errors::NoResponseError
11
+ extend T::Sig
12
+ include Crystalline::MetadataFields
13
+
14
+ field :type, ::String, {'format_json': {'letter_case': ::NoResponse::Utils.field_name("type"), required: true}}
15
+
16
+ field(
17
+ :title,
18
+ ::String,
19
+ {'format_json': {'letter_case': ::NoResponse::Utils.field_name("title"), required: true}}
20
+ )
21
+
22
+ field(
23
+ :detail,
24
+ ::String,
25
+ {'format_json': {'letter_case': ::NoResponse::Utils.field_name("detail"), required: true}}
26
+ )
27
+ # Machine codes an HTTP 400 answer can carry.
28
+ field(
29
+ :code,
30
+ Models::Shared::BadRequestErrorCode,
31
+ {
32
+ 'format_json': {
33
+ 'letter_case': ::NoResponse::Utils.field_name("code"),
34
+ required: true,
35
+ 'decoder': ::NoResponse::Utils.open_enum_from_string(Models::Shared::BadRequestErrorCode, false)
36
+ }
37
+ }
38
+ )
39
+
40
+ field(
41
+ :request_id,
42
+ ::String,
43
+ {'format_json': {'letter_case': ::NoResponse::Utils.field_name("request_id"), required: true}}
44
+ )
45
+
46
+ field(
47
+ :status,
48
+ ::Integer,
49
+ {'format_json': {'letter_case': ::NoResponse::Utils.field_name("status"), required: true}}
50
+ )
51
+
52
+ field(
53
+ :instance,
54
+ Crystalline::Nilable.new(::String),
55
+ {'format_json': {'letter_case': ::NoResponse::Utils.field_name("instance")}}
56
+ )
57
+
58
+ field(
59
+ :invalid_params,
60
+ Crystalline::Nilable.new(Crystalline::Array.new(Models::Shared::ProblemInvalidParam)),
61
+ {'format_json': {'letter_case': ::NoResponse::Utils.field_name("invalid_params")}}
62
+ )
63
+
64
+ sig {
65
+ params(
66
+ type: ::String,
67
+ title: ::String,
68
+ detail: ::String,
69
+ code: Models::Shared::BadRequestErrorCode,
70
+ request_id: ::String,
71
+ status: ::Integer,
72
+ instance: T.nilable(::String),
73
+ invalid_params: T.nilable(T::Array[Models::Shared::ProblemInvalidParam])
74
+ )
75
+ .void
76
+ }
77
+ def initialize(type:, title:, detail:, code:, request_id:, status: 400, instance: nil, invalid_params: nil)
78
+ @type = type
79
+ @title = title
80
+ @detail = detail
81
+ @code = code
82
+ @request_id = request_id
83
+ unless status == 400
84
+ raise ArgumentError, "Invalid value for status"
85
+ end
86
+
87
+ @status = 400
88
+ @instance = instance
89
+ @invalid_params = invalid_params
90
+ end
91
+
92
+ sig { params(other: T.untyped).returns(T::Boolean) }
93
+ def ==(other)
94
+ return false unless other.is_a?(self.class)
95
+ return false unless @type == other.type
96
+ return false unless @title == other.title
97
+ return false unless @detail == other.detail
98
+ return false unless @code == other.code
99
+ return false unless @request_id == other.request_id
100
+ return false unless @status == other.status
101
+ return false unless @instance == other.instance
102
+ return false unless @invalid_params == other.invalid_params
103
+ true
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end