didww-otp_verification 1.0.0 → 1.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 +4 -4
- data/CHANGELOG.md +23 -0
- data/README.md +20 -5
- data/lib/didww/otp_verification/client.rb +14 -5
- data/lib/didww/otp_verification/errors.rb +14 -0
- data/lib/didww/otp_verification/verification.rb +12 -0
- data/lib/didww/otp_verification/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b8f7f380aca63b49a1e661d0834f16f7d94af7fdacf63e1e54d7ddacac1e8a79
|
|
4
|
+
data.tar.gz: e1920bc8b4994f0abe90fc7a93b43339a1e5dc2d492983e93625a24b70f23285
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c8867b191e13078948e4427c2b5321b365dd52f79481f5ff4e81f56eae59466f8f0a006c6979030aebc8e6ac1432c1bd6d11f9ac35d9637b7c1a7a7dd104d918
|
|
7
|
+
data.tar.gz: 330d1aea846c2104f48db034df7eed4af4e15bc47c045c086068db419bcbfffa4271d156f1da818303353d0b6f9ae53ead2c56f4df2159502824023990c3b781
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,29 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Version
|
|
|
6
6
|
[Semantic Versioning](https://semver.org/spec/v2.0.0.html): from 1.0.0 onwards a breaking change
|
|
7
7
|
to the public surface requires a major version.
|
|
8
8
|
|
|
9
|
+
## [1.1.0] — 2026-10
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **A User-Agent header.** Every request now sends
|
|
14
|
+
`User-Agent: didww-verification-ruby/<version>`.
|
|
15
|
+
- **`code_length` on the `sms` and `callout` blocks**, readable via
|
|
16
|
+
`Verification#sms_code_length`/`#callout_code_length` — the length of the
|
|
17
|
+
generated code, 4–8 digits, set per application.
|
|
18
|
+
- **`RateLimitedError` (429, `destination_in_cooldown`)**, raised when
|
|
19
|
+
`start_verification` repeats for the same app and destination inside the
|
|
20
|
+
API's cooldown window. `#retry_after` reads the `Retry-After` header in
|
|
21
|
+
seconds (`nil` if missing or unparseable). The SDK never auto-retries it.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- Corrected README claims that the verification lifetime is a fixed 120 s /
|
|
26
|
+
2-minute window: it is the per-app `code_expiry` setting (60–600 s, default
|
|
27
|
+
300), which `sms_interception_timeout` also equals.
|
|
28
|
+
- A 429 now raises `RateLimitedError`, an `APIError` subclass, instead of the
|
|
29
|
+
`APIError` base class. `rescue APIError` still catches it, but a strict
|
|
30
|
+
`e.instance_of?(APIError)` check no longer matches a 429.
|
|
31
|
+
|
|
9
32
|
## [1.0.0] — 2026-09
|
|
10
33
|
|
|
11
34
|
First public release.
|
data/README.md
CHANGED
|
@@ -33,7 +33,8 @@ verification.status # => "pending"
|
|
|
33
33
|
verification.pending? # => true
|
|
34
34
|
verification.to_h # => raw response data Hash with string keys
|
|
35
35
|
|
|
36
|
-
# Report the code the user entered (counts as an attempt; max 3, expires
|
|
36
|
+
# Report the code the user entered (counts as an attempt; max 3, expires per
|
|
37
|
+
# the app's code lifetime)
|
|
37
38
|
result = client.report_verification(
|
|
38
39
|
verification.id, delivery_method: "sms", code: "1234"
|
|
39
40
|
)
|
|
@@ -103,11 +104,13 @@ field or as a whole:
|
|
|
103
104
|
```ruby
|
|
104
105
|
v.sms_template # => "Your code is {{CODE}}"
|
|
105
106
|
v.sms_language # => "en-US"
|
|
106
|
-
v.sms_interception_timeout # =>
|
|
107
|
+
v.sms_interception_timeout # => 300
|
|
108
|
+
v.sms_code_length # => 6
|
|
107
109
|
v.sms_app_hash # => "A1b2C3d4E5f", or nil if none was stored
|
|
108
110
|
v.sms # => the raw block, or nil on a callout verification
|
|
109
111
|
|
|
110
112
|
v.callout_language # => "de-DE"
|
|
113
|
+
v.callout_code_length # => 6
|
|
111
114
|
v.callout # => the raw block, or nil on an sms verification
|
|
112
115
|
```
|
|
113
116
|
|
|
@@ -119,8 +122,11 @@ because the two catalogues are separate, a tag honoured on `sms` can still fall
|
|
|
119
122
|
back on `callout`.
|
|
120
123
|
|
|
121
124
|
`sms_interception_timeout` is how many seconds a client should keep an on-device
|
|
122
|
-
SMS listener armed. It
|
|
123
|
-
for the verification — manual
|
|
125
|
+
SMS listener armed. It equals the app's configured code lifetime (60–600 s,
|
|
126
|
+
default 300) and is **not** a deadline for the verification itself — manual
|
|
127
|
+
entry keeps working until `expires_at`.
|
|
128
|
+
`sms_code_length`/`callout_code_length` is the length of this verification's
|
|
129
|
+
code, 4–8 digits, set per application (default 6).
|
|
124
130
|
`sms_app_hash` is echoed back only when one was stored, so it reflects what was
|
|
125
131
|
persisted rather than what was requested.
|
|
126
132
|
|
|
@@ -197,6 +203,8 @@ DIDWW::OTPVerification::Client.new(
|
|
|
197
203
|
) { |conn| conn.proxy = "http://proxy:3128" }
|
|
198
204
|
```
|
|
199
205
|
|
|
206
|
+
Every request carries `User-Agent: didww-verification-ruby/<version>`.
|
|
207
|
+
|
|
200
208
|
## Auth modes
|
|
201
209
|
|
|
202
210
|
| Mode | Header | Secret | Notes |
|
|
@@ -328,7 +336,7 @@ carries `error_code` (switch on it) and `error_detail` (display it). These are
|
|
|
328
336
|
| `pending` | no | `nil` — on its way, or awaiting a report |
|
|
329
337
|
| `verified` | yes | `nil` |
|
|
330
338
|
| `failed` | yes | `too_many_attempts`, `dispatch_failed`, `stale_dispatch`, `superseded`, `application_deleted` |
|
|
331
|
-
| `expired` | yes | `expired` — past the
|
|
339
|
+
| `expired` | yes | `expired` — past the application's code lifetime (60–600 s, default 300) |
|
|
332
340
|
| `denied` | yes | `denied_by_callback`, `denied_missing_callback_url`, `denied_invalid_callback_response` |
|
|
333
341
|
|
|
334
342
|
`superseded` means a newer `start_verification` for the same number retired this
|
|
@@ -362,6 +370,7 @@ Non-2xx responses raise a typed error under `DIDWW::OTPVerification::Error`:
|
|
|
362
370
|
| `BalanceInsufficientError` | 402 | `balance_insufficient` |
|
|
363
371
|
| `NotFoundError` | 404 | `not_found` |
|
|
364
372
|
| `ValidationError` | 400 / 422 | `parameter_missing` / per-field codes |
|
|
373
|
+
| `RateLimitedError` | 429 | `destination_in_cooldown` |
|
|
365
374
|
| `ServerError` | 5xx | `internal_error` |
|
|
366
375
|
|
|
367
376
|
Every error carries the API's coded envelope: `#errors` is an array of
|
|
@@ -390,6 +399,12 @@ end
|
|
|
390
399
|
The SDK does **not** auto-retry. If you add `faraday-retry`, exclude `POST`
|
|
391
400
|
(double-charges) and `PATCH` (each report counts against the 3-attempt limit).
|
|
392
401
|
|
|
402
|
+
A `start_verification` repeated too soon for the same app and destination
|
|
403
|
+
raises `RateLimitedError` (429, `destination_in_cooldown`) — a short per-number
|
|
404
|
+
cooldown (currently 30 s). `#retry_after` gives the wait in seconds (`nil` if
|
|
405
|
+
the `Retry-After` header is missing or unparseable) — wait and call again
|
|
406
|
+
yourself; never auto-retry it.
|
|
407
|
+
|
|
393
408
|
## Development
|
|
394
409
|
|
|
395
410
|
Development is pinned to the Ruby in `.ruby-version`; the gem itself supports
|
|
@@ -21,6 +21,7 @@ module DIDWW
|
|
|
21
21
|
# Unspecified arguments fall back to DIDWW::OTPVerification.configuration.
|
|
22
22
|
class Client
|
|
23
23
|
API_PREFIX = "/api/v1".freeze
|
|
24
|
+
USER_AGENT = "didww-verification-ruby/#{VERSION}".freeze
|
|
24
25
|
|
|
25
26
|
attr_reader :key, :auth_mode, :base_url
|
|
26
27
|
|
|
@@ -110,11 +111,9 @@ module DIDWW
|
|
|
110
111
|
end
|
|
111
112
|
|
|
112
113
|
errors = parse_errors(body)
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
response: response
|
|
117
|
-
)
|
|
114
|
+
klass = error_class(response.status)
|
|
115
|
+
extra = (klass == RateLimitedError) ? {retry_after: parse_retry_after(response)} : {}
|
|
116
|
+
raise klass.new(status: response.status, errors: errors, response: response, **extra)
|
|
118
117
|
end
|
|
119
118
|
|
|
120
119
|
# Map the {"errors": [{"code", "detail"}]} envelope to ErrorItem objects.
|
|
@@ -131,12 +130,20 @@ module DIDWW
|
|
|
131
130
|
end
|
|
132
131
|
end
|
|
133
132
|
|
|
133
|
+
# @return [Integer, nil] the Retry-After header in seconds, nil if
|
|
134
|
+
# missing or not a plain non-negative integer (e.g. an HTTP-date).
|
|
135
|
+
def parse_retry_after(response)
|
|
136
|
+
value = response.headers["Retry-After"].to_s
|
|
137
|
+
Integer(value, 10) if value.match?(/\A\d+\z/)
|
|
138
|
+
end
|
|
139
|
+
|
|
134
140
|
def error_class(status)
|
|
135
141
|
case status
|
|
136
142
|
when 401 then UnauthorizedError
|
|
137
143
|
when 402 then BalanceInsufficientError
|
|
138
144
|
when 404 then NotFoundError
|
|
139
145
|
when 400, 422 then ValidationError
|
|
146
|
+
when 429 then RateLimitedError
|
|
140
147
|
when 500..599 then ServerError
|
|
141
148
|
else APIError
|
|
142
149
|
end
|
|
@@ -147,6 +154,8 @@ module DIDWW
|
|
|
147
154
|
conn.request :json
|
|
148
155
|
conn.response :json, content_type: /\bjson$/
|
|
149
156
|
@faraday_block&.call(conn)
|
|
157
|
+
# After the caller's block, so the block cannot replace it.
|
|
158
|
+
conn.headers["User-Agent"] = USER_AGENT
|
|
150
159
|
# Auth (signing) must be installed last so it sees the final request
|
|
151
160
|
# bytes/headers/path, after any user middleware from @faraday_block.
|
|
152
161
|
apply_auth(conn)
|
|
@@ -48,6 +48,20 @@ module DIDWW
|
|
|
48
48
|
# 400 Bad Request / 422 Unprocessable Content (validation errors in +errors+).
|
|
49
49
|
class ValidationError < APIError; end
|
|
50
50
|
|
|
51
|
+
# 429 Too Many Requests (+destination_in_cooldown+ code): a start_verification
|
|
52
|
+
# for the same app and destination landed within the API's cooldown window.
|
|
53
|
+
# Never auto-retry this.
|
|
54
|
+
class RateLimitedError < APIError
|
|
55
|
+
# @return [Integer, nil] seconds to wait, from the Retry-After header.
|
|
56
|
+
# Nil if the header is missing or not a plain integer.
|
|
57
|
+
attr_reader :retry_after
|
|
58
|
+
|
|
59
|
+
def initialize(message = nil, status:, errors: [], response: nil, retry_after: nil)
|
|
60
|
+
@retry_after = retry_after
|
|
61
|
+
super(message, status: status, errors: errors, response: response)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
51
65
|
# 5xx Server Error (+internal_error+ code).
|
|
52
66
|
class ServerError < APIError; end
|
|
53
67
|
end
|
|
@@ -55,6 +55,12 @@ module DIDWW
|
|
|
55
55
|
@sms && @sms["interception_timeout"]
|
|
56
56
|
end
|
|
57
57
|
|
|
58
|
+
# @return [Integer, nil] the length of this verification's code, 4..8
|
|
59
|
+
# (set per application). Only present for the +sms+ method.
|
|
60
|
+
def sms_code_length
|
|
61
|
+
@sms && @sms["code_length"]
|
|
62
|
+
end
|
|
63
|
+
|
|
58
64
|
# @return [String, nil] SMS Retriever hash, echoed back only when one was
|
|
59
65
|
# stored on this verification.
|
|
60
66
|
def sms_app_hash
|
|
@@ -71,6 +77,12 @@ module DIDWW
|
|
|
71
77
|
@callout && @callout["language"]
|
|
72
78
|
end
|
|
73
79
|
|
|
80
|
+
# @return [Integer, nil] the length of this verification's code, 4..8
|
|
81
|
+
# (set per application). Only present for the +callout+ method.
|
|
82
|
+
def callout_code_length
|
|
83
|
+
@callout && @callout["code_length"]
|
|
84
|
+
end
|
|
85
|
+
|
|
74
86
|
def pending? = status == "pending"
|
|
75
87
|
def verified? = status == "verified"
|
|
76
88
|
def failed? = status == "failed"
|