konfidant 0.7.0 → 1.3.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/README.md +119 -255
- data/lib/konfidant/client.rb +139 -95
- data/lib/konfidant/errors.rb +8 -1
- data/lib/konfidant/knf.rb +469 -0
- data/lib/konfidant/types.rb +14 -18
- data/lib/konfidant/version.rb +1 -1
- data/lib/konfidant.rb +1 -0
- metadata +6 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b9defdc63a2d22d69dabe29e0c3639d225348ba9aef1b975a8153e9b7e762179
|
|
4
|
+
data.tar.gz: 59d2d610234de9ccd59fd02bd3a06f3343375ae6ccc25baab71ad0aab4f98fc9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 81a88c2361ec384f99e7282516a40dd8286fdceed5bce6834a2e219cd4fc5aa8fa5c8cc24b5f70cf794dd25b672a0e35f6fc84e6bdeffcfd59cc20d02cad116a
|
|
7
|
+
data.tar.gz: 3c4177715e1bcc654e8d90772b4d97abb501449b6f741077c2fc854ef21a927142f1044bbdee8826558f7e61a976a0e9b7c73594f3130b91ecb9fad04615711c
|
data/README.md
CHANGED
|
@@ -6,29 +6,34 @@
|
|
|
6
6
|
|
|
7
7
|
Official Ruby SDK for the [Konfidant](https://www.konfidant.app) API.
|
|
8
8
|
|
|
9
|
-
Konfidant lets you share secrets —
|
|
9
|
+
Konfidant lets you share secrets — text and files — that self-destruct after being read.
|
|
10
|
+
|
|
11
|
+
## Zero-knowledge model
|
|
12
|
+
|
|
13
|
+
All content is **encrypted on your machine** before anything is sent:
|
|
14
|
+
|
|
15
|
+
- Every share gets a fresh random 256-bit key. Text, file bytes, file name and MIME type are encrypted together
|
|
16
|
+
with AES-256-GCM in the chunked KNF1
|
|
17
|
+
format (stdlib `OpenSSL` only).
|
|
18
|
+
- Konfidant receives only ciphertext and its size. It never sees the key, the content or the file name.
|
|
19
|
+
- The key lives **only in the share link's URL fragment**: `https://download.konfidant.app/#t=<token>&k=<key>`.
|
|
20
|
+
Browsers never send the fragment to a server. The `t` token is single-use, so the ciphertext can be fetched once.
|
|
21
|
+
- Anyone holding the full `share_url` can read the secret once — treat it like the secret itself. If you lose it,
|
|
22
|
+
the content cannot be recovered by anyone, including Konfidant.
|
|
10
23
|
|
|
11
24
|
---
|
|
12
25
|
|
|
13
26
|
## Installation
|
|
14
27
|
|
|
15
|
-
Add to your `Gemfile`:
|
|
16
|
-
|
|
17
28
|
```ruby
|
|
18
29
|
gem 'konfidant'
|
|
19
30
|
```
|
|
20
31
|
|
|
21
|
-
Then run:
|
|
22
|
-
|
|
23
32
|
```bash
|
|
24
|
-
bundle install
|
|
33
|
+
bundle install # or: gem install konfidant
|
|
25
34
|
```
|
|
26
35
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
```bash
|
|
30
|
-
gem install konfidant
|
|
31
|
-
```
|
|
36
|
+
Requires Ruby >= 3.2. No runtime dependencies (stdlib `net/http`, `openssl`, `json`).
|
|
32
37
|
|
|
33
38
|
---
|
|
34
39
|
|
|
@@ -37,307 +42,166 @@ gem install konfidant
|
|
|
37
42
|
```ruby
|
|
38
43
|
require 'konfidant'
|
|
39
44
|
|
|
40
|
-
client = Konfidant::Client.new(api_key: '
|
|
41
|
-
|
|
42
|
-
result = client.share_text(text: 'super-secret-password', ttl_hours: 24)
|
|
43
|
-
|
|
44
|
-
puts "Share this link: #{result.share_url}"
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
---
|
|
48
|
-
|
|
49
|
-
## Authentication
|
|
45
|
+
client = Konfidant::Client.new(api_key: ENV['KONFIDANT_API_KEY'])
|
|
50
46
|
|
|
51
|
-
|
|
47
|
+
text = client.share_text(text: 'db-password: hunter2', ttl_hours: 24)
|
|
48
|
+
puts text.share_url
|
|
52
49
|
|
|
53
|
-
|
|
54
|
-
client
|
|
50
|
+
file = File.open('contract.pdf', 'rb') do |f|
|
|
51
|
+
client.share_file(content: f, filename: 'contract.pdf', content_type: 'application/pdf', ttl_hours: 48)
|
|
52
|
+
end
|
|
53
|
+
puts file.share_url
|
|
55
54
|
```
|
|
56
55
|
|
|
57
56
|
---
|
|
58
57
|
|
|
59
|
-
## API
|
|
58
|
+
## API reference
|
|
60
59
|
|
|
61
|
-
### `Konfidant::Client.new`
|
|
60
|
+
### `Konfidant::Client.new(api_key:, base_url: nil, http_timeout: 120)`
|
|
62
61
|
|
|
63
62
|
| Option | Type | Required | Description |
|
|
64
63
|
|----------------|-----------|----------|----------------------------------------------------------------------|
|
|
65
|
-
| `api_key` | `String` | Yes | Your Konfidant API key
|
|
66
|
-
| `base_url` | `String` | No | Override the base URL (default: `https://www.konfidant.app`)
|
|
64
|
+
| `api_key` | `String` | Yes | Your Konfidant API key (sent as `Authorization: Bearer …`) |
|
|
65
|
+
| `base_url` | `String` | No | Override the API base URL (default: `https://www.konfidant.app`) |
|
|
67
66
|
| `http_timeout` | `Integer` | No | Per-request HTTP timeout in seconds (default: `120`; `nil` disables) |
|
|
68
67
|
|
|
69
68
|
Raises `ArgumentError` if `api_key` is nil or empty.
|
|
70
69
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
### `client.share_text(text:, ttl_hours:)`
|
|
74
|
-
|
|
75
|
-
Encrypt and share a text message.
|
|
76
|
-
|
|
77
|
-
| Argument | Type | Description |
|
|
78
|
-
|-------------|-----------|--------------------------|
|
|
79
|
-
| `text` | `String` | The secret text to share |
|
|
80
|
-
| `ttl_hours` | `Integer` | Time-to-live in hours |
|
|
81
|
-
|
|
82
|
-
Returns a `Konfidant::ShareTextResponse`:
|
|
83
|
-
|
|
84
|
-
| Field | Type | Description |
|
|
85
|
-
|----------------|-----------|---------------------------------------------|
|
|
86
|
-
| `text_id` | `String` | Unique ID of the shared text |
|
|
87
|
-
| `share_url` | `String` | One-time download link to send to recipient |
|
|
88
|
-
| `expires_at` | `String` | Expiry datetime |
|
|
89
|
-
| `verified_burn`| `Boolean` | Whether burn-on-read is verified |
|
|
90
|
-
|
|
91
|
-
#### Example: share text
|
|
92
|
-
|
|
93
|
-
```ruby
|
|
94
|
-
result = client.share_text(text: 'db-password: hunter2', ttl_hours: 48)
|
|
95
|
-
|
|
96
|
-
puts result.share_url # send to recipient
|
|
97
|
-
puts result.expires_at
|
|
98
|
-
puts result.verified_burn
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
---
|
|
102
|
-
|
|
103
|
-
### `client.share_file(filename:, file_size:, ttl_hours:)`
|
|
104
|
-
|
|
105
|
-
Request a presigned upload URL for a file. Use the returned response with `upload_file` to complete
|
|
106
|
-
the upload, then poll `get_file_status` for the share link.
|
|
70
|
+
### `client.share_text(text:, ttl_hours: nil)` → `Konfidant::TextShare`
|
|
107
71
|
|
|
108
|
-
|
|
72
|
+
Encrypts `text` (UTF-8, no SDK-side size limit; your plan's limit applies server-side) and uploads the ciphertext.
|
|
73
|
+
`ttl_hours` is omitted from the request when `nil` (server default applies).
|
|
109
74
|
|
|
110
|
-
|
|
|
111
|
-
|
|
112
|
-
| `
|
|
113
|
-
| `
|
|
114
|
-
| `
|
|
75
|
+
| Field | Type | Description |
|
|
76
|
+
|--------------|---------------|------------------------------------------------|
|
|
77
|
+
| `share_url` | `String` | One-time link **including the key** (`&k=…`) |
|
|
78
|
+
| `text_id` | `String, nil` | Text ID (`nil` when verified burn is disabled) |
|
|
79
|
+
| `expires_at` | `String` | ISO 8601 expiry |
|
|
115
80
|
|
|
116
|
-
|
|
81
|
+
### `client.share_file(content:, filename:, content_type: '', ttl_hours: nil)` → `Konfidant::FileShare`
|
|
117
82
|
|
|
118
|
-
|
|
119
|
-
|--------------------|-------------------------------|-------------------------------------------------|
|
|
120
|
-
| `upload_url` | `String` | Short-lived presigned S3 PUT URL |
|
|
121
|
-
| `file_key` | `String` | Use with `get_file_status` and `upload_file` |
|
|
122
|
-
| `poll_url` | `String` | Convenience URL for status polling |
|
|
123
|
-
| `metadata_headers` | `Konfidant::FileMetadataHeaders` | Required S3 headers — passed by `upload_file` |
|
|
83
|
+
Encrypts and shares a file in one call: encrypt → `create_file_upload` → `upload_ciphertext` → `complete_file_upload`.
|
|
124
84
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
85
|
+
| Argument | Type | Description |
|
|
86
|
+
|----------------|--------------|-------------------------------------------------------------------------------------|
|
|
87
|
+
| `content` | `String, IO` | File bytes, or a readable IO. `File`/`StringIO` are streamed chunk by chunk; other IOs are read into memory |
|
|
88
|
+
| `filename` | `String` | Original file name, at most 1 024 UTF-8 bytes (encrypted) |
|
|
89
|
+
| `content_type` | `String` | MIME type, at most 255 bytes, may be empty (encrypted) |
|
|
90
|
+
| `ttl_hours` | `Integer` | Time-to-live in hours (optional) |
|
|
131
91
|
|
|
132
|
-
|
|
|
133
|
-
|
|
134
|
-
| `
|
|
135
|
-
| `
|
|
136
|
-
| `
|
|
137
|
-
| `
|
|
92
|
+
| Field | Type | Description |
|
|
93
|
+
|-----------------|---------------|----------------------------------------------|
|
|
94
|
+
| `share_url` | `String` | One-time link **including the key** (`&k=…`) |
|
|
95
|
+
| `file_id` | `String, nil` | File ID (`nil` when verified burn is off) |
|
|
96
|
+
| `expires_at` | `String` | ISO 8601 expiry |
|
|
97
|
+
| `verified_burn` | `Boolean` | Whether verified burn is enabled |
|
|
138
98
|
|
|
139
|
-
|
|
99
|
+
### Low-level file flow
|
|
140
100
|
|
|
141
|
-
|
|
101
|
+
Use these when you need control over each step (e.g. your own retry logic).
|
|
142
102
|
|
|
143
103
|
```ruby
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
# Step 1 – get presigned URL
|
|
148
|
-
presigned = client.share_file(
|
|
149
|
-
filename: 'report.pdf',
|
|
150
|
-
file_size: size,
|
|
151
|
-
ttl_hours: 72
|
|
152
|
-
)
|
|
153
|
-
|
|
154
|
-
# Step 2 – upload to S3
|
|
155
|
-
client.upload_file(
|
|
156
|
-
io: file,
|
|
157
|
-
size: size,
|
|
158
|
-
content_type: 'application/pdf',
|
|
159
|
-
presigned: presigned
|
|
160
|
-
)
|
|
161
|
-
|
|
162
|
-
# Step 3 – poll for share link
|
|
163
|
-
loop do
|
|
164
|
-
status = client.get_file_status(presigned.file_key)
|
|
165
|
-
if status.status == 'complete'
|
|
166
|
-
puts "Share URL: #{status.share_url}"
|
|
167
|
-
break
|
|
168
|
-
end
|
|
169
|
-
sleep 2
|
|
170
|
-
end
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
---
|
|
174
|
-
|
|
175
|
-
### `client.get_file_status(file_key)`
|
|
176
|
-
|
|
177
|
-
Poll the encryption status of an uploaded file.
|
|
178
|
-
|
|
179
|
-
| Argument | Type | Description |
|
|
180
|
-
|------------|----------|-----------------------------------------------|
|
|
181
|
-
| `file_key` | `String` | The `file_key` from the `share_file` response |
|
|
182
|
-
|
|
183
|
-
Returns a `Konfidant::FileStatusResponse`:
|
|
184
|
-
|
|
185
|
-
| Field | Type | When set |
|
|
186
|
-
|----------------|-----------|-----------------------------------------|
|
|
187
|
-
| `status` | `String` | Always (`"processing"` or `"complete"`) |
|
|
188
|
-
| `message` | `String` | When `processing` |
|
|
189
|
-
| `file_id` | `String` | When `complete` |
|
|
190
|
-
| `file_name` | `String` | When `complete` |
|
|
191
|
-
| `share_url` | `String` | When `complete` |
|
|
192
|
-
| `expires_at` | `String` | When `complete` |
|
|
193
|
-
| `verified_burn`| `Boolean` | When `complete` |
|
|
194
|
-
|
|
195
|
-
---
|
|
196
|
-
|
|
197
|
-
### `client.list_shares(type: nil, status: nil, limit: nil, offset: nil)`
|
|
198
|
-
|
|
199
|
-
List all shares for the authenticated organization. All parameters are optional.
|
|
200
|
-
|
|
201
|
-
| Argument | Type | Description |
|
|
202
|
-
|----------|----------|--------------------------------|
|
|
203
|
-
| `type` | `String` | `"file"` or `"text"` |
|
|
204
|
-
| `status` | `String` | `"active"` or `"accessed"` |
|
|
205
|
-
| `limit` | `Integer`| Page size (default 50) |
|
|
206
|
-
| `offset` | `Integer`| Pagination offset |
|
|
104
|
+
key = Konfidant::Knf.generate_key
|
|
105
|
+
encryptor = Konfidant::Knf.encryptor(key: key, content: File.open('a.zip', 'rb'), kind: 'file',
|
|
106
|
+
name: 'a.zip', mime: 'application/zip')
|
|
207
107
|
|
|
208
|
-
|
|
108
|
+
upload = client.create_file_upload(ciphertext_size: encryptor.size, ttl_hours: 24)
|
|
109
|
+
client.upload_ciphertext(upload: upload, ciphertext: encryptor) # String or IO-like
|
|
110
|
+
done = client.complete_file_upload(file_key: upload.file_key)
|
|
209
111
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
| `shares` | `Array<Konfidant::Share>` | Share entries |
|
|
213
|
-
| `pagination` | `Konfidant::Pagination` | Page metadata |
|
|
112
|
+
share_url = Konfidant::Knf.build_share_url(done.download_url, key)
|
|
113
|
+
```
|
|
214
114
|
|
|
215
|
-
|
|
115
|
+
| Method | Request | Returns |
|
|
116
|
+
|-----------------------------------------------------|--------------------------------------------------|-----------------------------------------------------------------------------------------|
|
|
117
|
+
| `create_file_upload(ciphertext_size:, ttl_hours:)` | `POST /api/v1/files` | `FileUpload(upload_url, file_key, upload_headers, upload_expires_in, ciphertext_size)` |
|
|
118
|
+
| `upload_ciphertext(upload:, ciphertext:)` | `PUT upload_url` with exactly `upload_headers` | `nil`; never sends the API key |
|
|
119
|
+
| `complete_file_upload(file_key:)` | `POST /api/v1/files/{file_key}/complete` | `CompletedUpload(download_url, file_id, expires_at, verified_burn)` |
|
|
216
120
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
| `type` | `String` | `"file"` or `"text"` |
|
|
220
|
-
| `file_name` | `String` | Filename or text label |
|
|
221
|
-
| `file_size_bytes`| `Integer` | Size in bytes |
|
|
222
|
-
| `created_at` | `String` | Creation datetime |
|
|
223
|
-
| `expires_at` | `String` | Expiry datetime |
|
|
224
|
-
| `accessed_at` | `String, nil` | Access datetime, or nil |
|
|
225
|
-
| `created_by` | `String` | Email of creator |
|
|
121
|
+
`complete_file_upload` raises `ApiError` with status `409` and message `upload_incomplete` if the PUT did not land.
|
|
122
|
+
`download_url` contains only the token — append the key with `Knf.build_share_url` before sending it to anyone.
|
|
226
123
|
|
|
227
|
-
`Konfidant::
|
|
124
|
+
### `client.open_share(share_url)` → `Konfidant::OpenedShare`
|
|
228
125
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
| `total` | `Integer` | Total number of shares |
|
|
232
|
-
| `limit` | `Integer` | Page size used |
|
|
233
|
-
| `offset` | `Integer` | Offset used |
|
|
234
|
-
| `has_more` | `Boolean` | Whether more pages exist |
|
|
126
|
+
Recipient side. Sends only the token (`POST https://<link host>/api/download`, no API key, no decryption key),
|
|
127
|
+
receives the ciphertext and decrypts it locally. Opening a share **consumes it**.
|
|
235
128
|
|
|
236
|
-
|
|
129
|
+
| Field | Type | Description |
|
|
130
|
+
|--------|---------------|---------------------------------------------|
|
|
131
|
+
| `kind` | `String` | `"text"` or `"file"` |
|
|
132
|
+
| `name` | `String` | File name (`""` for text) |
|
|
133
|
+
| `mime` | `String` | MIME type (`""` for text) |
|
|
134
|
+
| `data` | `String` | Decrypted bytes (binary / ASCII-8BIT) |
|
|
135
|
+
| `text` | `String, nil` | UTF-8 text for text shares, otherwise `nil` |
|
|
237
136
|
|
|
238
|
-
|
|
239
|
-
|
|
137
|
+
Raises `ApiError` (`410`) if the share was already opened or has expired, `KnfError` if the ciphertext does not
|
|
138
|
+
authenticate (wrong key, modified or truncated data).
|
|
240
139
|
|
|
241
|
-
|
|
242
|
-
puts "#{share.file_name} — expires #{share.expires_at}"
|
|
243
|
-
end
|
|
140
|
+
### `client.list_shares(type: nil, status: nil, limit: nil, offset: nil)` → `Konfidant::ListSharesResponse`
|
|
244
141
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
142
|
+
| Argument | Type | Description |
|
|
143
|
+
|----------|-----------|----------------------------|
|
|
144
|
+
| `type` | `String` | `"file"` or `"text"` |
|
|
145
|
+
| `status` | `String` | `"active"` or `"accessed"` |
|
|
146
|
+
| `limit` | `Integer` | Page size (default 50) |
|
|
147
|
+
| `offset` | `Integer` | Pagination offset |
|
|
248
148
|
|
|
249
|
-
|
|
149
|
+
Returns `shares` (`Array<Konfidant::Share>` with `type`, `file_size_bytes`, `created_at`, `expires_at`,
|
|
150
|
+
`accessed_at`, `created_by`) and `pagination` (`total`, `limit`, `offset`, `has_more`). File names are not
|
|
151
|
+
available: the server never sees them.
|
|
250
152
|
|
|
251
|
-
### `
|
|
252
|
-
|
|
253
|
-
Convenience wrapper that calls `share_file` → `upload_file` → polls `get_file_status` until complete.
|
|
254
|
-
|
|
255
|
-
```ruby
|
|
256
|
-
client.share_and_upload_file(
|
|
257
|
-
io: io,
|
|
258
|
-
size: size,
|
|
259
|
-
filename: filename,
|
|
260
|
-
content_type: content_type,
|
|
261
|
-
ttl_hours: ttl_hours,
|
|
262
|
-
poll_interval: 2, # seconds between status checks (default: 2)
|
|
263
|
-
timeout: 60 # max wait for encryption in seconds (default: 60)
|
|
264
|
-
)
|
|
265
|
-
```
|
|
153
|
+
### `Konfidant::Knf`
|
|
266
154
|
|
|
267
|
-
|
|
268
|
-
|-----------------|-----------|---------|-------------------------------------|
|
|
269
|
-
| `io` | `IO` | — | Readable IO object (File, StringIO) |
|
|
270
|
-
| `size` | `Integer` | — | File size in bytes |
|
|
271
|
-
| `filename` | `String` | — | Filename with extension |
|
|
272
|
-
| `content_type` | `String` | — | MIME type |
|
|
273
|
-
| `ttl_hours` | `Integer` | — | Time-to-live in hours |
|
|
274
|
-
| `poll_interval` | `Numeric` | `2` | Seconds between status checks |
|
|
275
|
-
| `timeout` | `Numeric` | `60` | Max seconds to wait for encryption |
|
|
155
|
+
The KNF1 implementation is public for advanced use and interoperability:
|
|
276
156
|
|
|
277
|
-
|
|
157
|
+
| Method | Description |
|
|
158
|
+
|------------------------------------------------------------------------|-----------------------------------------------------|
|
|
159
|
+
| `generate_key` / `encode_key(key)` / `decode_key(str)` | 32-byte key; unpadded base64url (43 chars) |
|
|
160
|
+
| `encrypt(key:, content:, kind:, name: '', mime: '')` | Full ciphertext as a binary String |
|
|
161
|
+
| `encryptor(...)` | Streaming IO-like encryptor (`#read`, `#size`) |
|
|
162
|
+
| `encrypt_text(key:, text:)` | Text share ciphertext |
|
|
163
|
+
| `decrypt(key:, ciphertext:)` / `Decryptor.new(key:)#push`/`#finish` | Verify and decrypt (one-shot or streaming) |
|
|
164
|
+
| `ciphertext_size(metadata_length:, content_length:)` | Exact ciphertext length |
|
|
165
|
+
| `build_share_url(download_url, key)` / `parse_share_url(url)` | Share link helpers |
|
|
278
166
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
| `share_url` | `String` | One-time download link |
|
|
282
|
-
| `file_id` | `String` | Unique file ID |
|
|
283
|
-
| `expires_at` | `String` | Expiry datetime |
|
|
284
|
-
| `verified_burn`| `Boolean` | Whether burn-on-read is active |
|
|
285
|
-
|
|
286
|
-
Raises `RuntimeError` with `"konfidant: encryption timed out after Ns"` if encryption does not complete
|
|
287
|
-
within `timeout` seconds.
|
|
288
|
-
|
|
289
|
-
#### Example: share and upload file
|
|
290
|
-
|
|
291
|
-
```ruby
|
|
292
|
-
file = File.open('confidential.zip', 'rb')
|
|
293
|
-
size = File.size('confidential.zip')
|
|
294
|
-
|
|
295
|
-
result = client.share_and_upload_file(
|
|
296
|
-
io: file,
|
|
297
|
-
size: size,
|
|
298
|
-
filename: 'confidential.zip',
|
|
299
|
-
content_type: 'application/zip',
|
|
300
|
-
ttl_hours: 48
|
|
301
|
-
)
|
|
302
|
-
|
|
303
|
-
puts "Ready to share: #{result.share_url}"
|
|
304
|
-
```
|
|
167
|
+
`encrypt` also accepts `chunk_size:` and a `nonce_prefix:` option that exists **only** for reproducing test
|
|
168
|
+
vectors — never pass `nonce_prefix` in production.
|
|
305
169
|
|
|
306
170
|
---
|
|
307
171
|
|
|
308
172
|
## Error handling
|
|
309
173
|
|
|
310
|
-
All
|
|
174
|
+
All SDK errors inherit from `Konfidant::Error`.
|
|
311
175
|
|
|
312
176
|
```ruby
|
|
313
177
|
begin
|
|
314
178
|
client.share_text(text: 'secret', ttl_hours: 1)
|
|
315
179
|
rescue Konfidant::ApiError => e
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
180
|
+
e.message # the "error" field of the JSON body, e.g. "upload_incomplete", or "HTTP 500"
|
|
181
|
+
e.status_code # e.g. 401
|
|
182
|
+
e.body # parsed body (Hash) or raw String
|
|
183
|
+
rescue Konfidant::KnfError => e
|
|
184
|
+
e.message # encryption/decryption failure
|
|
319
185
|
end
|
|
320
186
|
```
|
|
321
187
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
|
325
|
-
|
|
326
|
-
| `
|
|
327
|
-
| `
|
|
328
|
-
| `
|
|
329
|
-
| `
|
|
188
|
+
| Status | Meaning |
|
|
189
|
+
|--------|-------------------------------------------|
|
|
190
|
+
| `400` | Bad request / invalid body |
|
|
191
|
+
| `401` | Missing or invalid API key |
|
|
192
|
+
| `403` | Insufficient API key scope |
|
|
193
|
+
| `404` | Resource not found |
|
|
194
|
+
| `409` | `upload_incomplete` on complete |
|
|
195
|
+
| `410` | Share already opened or expired |
|
|
330
196
|
|
|
331
197
|
---
|
|
332
198
|
|
|
333
199
|
## Development
|
|
334
200
|
|
|
335
201
|
```bash
|
|
336
|
-
bundle install
|
|
337
|
-
bundle exec rspec
|
|
202
|
+
bundle install
|
|
203
|
+
bundle exec rspec
|
|
338
204
|
```
|
|
339
205
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
- Ruby >= 3.2.0
|
|
343
|
-
- No runtime dependencies (uses stdlib `net/http`, `uri`, `json`)
|
|
206
|
+
`spec/fixtures/knf1-test-vectors.json` holds the cross-SDK KNF1 test vectors; the specs reproduce every ciphertext
|
|
207
|
+
byte for byte.
|