konfidant 1.3.0 → 1.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b9defdc63a2d22d69dabe29e0c3639d225348ba9aef1b975a8153e9b7e762179
4
- data.tar.gz: 59d2d610234de9ccd59fd02bd3a06f3343375ae6ccc25baab71ad0aab4f98fc9
3
+ metadata.gz: 2dd0f63c7378b463ca026143137aff868ad08b6e7d8b8a8672120fa283138326
4
+ data.tar.gz: 9b77783dc745eb9919f0bb864cd506899d2651b957af20da128639329797c5a2
5
5
  SHA512:
6
- metadata.gz: 81a88c2361ec384f99e7282516a40dd8286fdceed5bce6834a2e219cd4fc5aa8fa5c8cc24b5f70cf794dd25b672a0e35f6fc84e6bdeffcfd59cc20d02cad116a
7
- data.tar.gz: 3c4177715e1bcc654e8d90772b4d97abb501449b6f741077c2fc854ef21a927142f1044bbdee8826558f7e61a976a0e9b7c73594f3130b91ecb9fad04615711c
6
+ metadata.gz: f704dc0b64fdb6820a0a9220f4bbb8abc876d402ee96bffdbac2b4187e53953528ae057661a57da014879ed2dfda51dda32dbc1684a25601b370ec8693d87037
7
+ data.tar.gz: 5f266d4494ae32900e035b0b75d23150441c9630f006fb6d22263ad7e4edf61736d59e793ea333b82953ffa217b74954dba2dc0ab4545c73aff52be249415571
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Konfidant
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md CHANGED
@@ -3,8 +3,9 @@
3
3
  [![Test](https://github.com/konfidant/sdk-ruby/actions/workflows/test.yml/badge.svg)](https://github.com/konfidant/sdk-ruby/actions/workflows/test.yml)
4
4
  [![Codacy Badge](https://app.codacy.com/project/badge/Grade/eb3798f7eb59412abc2bd3ce307760b5)](https://app.codacy.com/gh/konfidant/sdk-ruby/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
5
5
  [![Codacy Badge](https://app.codacy.com/project/badge/Coverage/eb3798f7eb59412abc2bd3ce307760b5)](https://app.codacy.com/gh/konfidant/sdk-ruby/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_coverage)
6
+ [![Quality gate status](https://sonarcloud.io/api/project_badges/measure?project=konfidant_sdk-ruby&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=konfidant_sdk-ruby)
6
7
 
7
- Official Ruby SDK for the [Konfidant](https://www.konfidant.app) API.
8
+ Official Ruby SDK for the [Konfidant](https://www.konfidant.app?utm_source=github&utm_medium=rubysdk&utm_campaign=github) API.
8
9
 
9
10
  Konfidant lets you share secrets — text and files — that self-destruct after being read.
10
11
 
@@ -150,6 +151,12 @@ Returns `shares` (`Array<Konfidant::Share>` with `type`, `file_size_bytes`, `cre
150
151
  `accessed_at`, `created_by`) and `pagination` (`total`, `limit`, `offset`, `has_more`). File names are not
151
152
  available: the server never sees them.
152
153
 
154
+ ### `client.me` → `Konfidant::KeyInfo`
155
+
156
+ Describes the API key without creating a share: `organization` (`id`, `name`), `api_key` (`name`, `environment`,
157
+ `scopes`, `rate_limit_per_minute`, `expires_at`) and the plan's `limits` (`max_ttl_hours`, `max_file_size_mb`). It
158
+ needs no scope and does not count as an API share. Use it to check a key before storing it.
159
+
153
160
  ### `Konfidant::Knf`
154
161
 
155
162
  The KNF1 implementation is public for advanced use and interoperability:
@@ -9,6 +9,11 @@ module Konfidant
9
9
  # Konfidant API client. All content is encrypted locally (KNF1) before it leaves this process; the key is only
10
10
  # ever placed in the returned share link's URL fragment and is never sent to Konfidant.
11
11
  class Client
12
+ CONTENT_TYPE = 'Content-Type'
13
+ JSON_MIME = 'application/json'
14
+ BINARY_MIME = 'application/octet-stream'
15
+ private_constant :CONTENT_TYPE, :JSON_MIME, :BINARY_MIME
16
+
12
17
  def initialize(api_key:, base_url: nil, http_timeout: DEFAULT_TIMEOUT)
13
18
  raise ArgumentError, 'api_key is required' if api_key.nil? || api_key.empty?
14
19
 
@@ -78,7 +83,7 @@ module Konfidant
78
83
  req = Net::HTTP::Put.new(uri)
79
84
  upload.upload_headers.each { |name, value| req[name] = value.to_s }
80
85
  # Only when the server did not specify one: Net::HTTP would otherwise send application/x-www-form-urlencoded.
81
- req['Content-Type'] ||= 'application/octet-stream'
86
+ req[CONTENT_TYPE] ||= BINARY_MIME
82
87
  req['Content-Length'] = size.to_s
83
88
  if ciphertext.is_a?(String)
84
89
  req.body = ciphertext.b
@@ -117,6 +122,27 @@ module Konfidant
117
122
  )
118
123
  end
119
124
 
125
+ # Describes the API key: its organization, scopes and rate limit, and the plan's share limits. Use it to validate
126
+ # a key without creating a share. Needs no scope and does not count as an API share.
127
+ def me
128
+ body = api_request(:get, '/api/v1/me')
129
+ key = body['api_key']
130
+ KeyInfo.new(
131
+ organization: Organization.new(id: body['organization']['id'], name: body['organization']['name']),
132
+ api_key: ApiKeyInfo.new(
133
+ name: key['name'],
134
+ environment: key['environment'],
135
+ scopes: key['scopes'],
136
+ rate_limit_per_minute: key['rate_limit_per_minute'],
137
+ expires_at: key['expires_at']
138
+ ),
139
+ limits: PlanLimits.new(
140
+ max_ttl_hours: body['limits']['max_ttl_hours'],
141
+ max_file_size_mb: body['limits']['max_file_size_mb']
142
+ )
143
+ )
144
+ end
145
+
120
146
  # Recipient side: fetches the ciphertext behind +share_url+ (consuming its single-use token) and decrypts it
121
147
  # locally. Only the token is sent; the key and the API key are not. Raises ApiError (410) if the share was
122
148
  # already opened or has expired, KnfError if decryption fails.
@@ -124,8 +150,8 @@ module Konfidant
124
150
  origin, token, key = Knf.parse_share_url(share_url)
125
151
  uri = URI.parse("#{origin}/api/download")
126
152
  req = Net::HTTP::Post.new(uri)
127
- req['Content-Type'] = 'application/json'
128
- req['Accept'] = 'application/octet-stream'
153
+ req[CONTENT_TYPE] = JSON_MIME
154
+ req['Accept'] = BINARY_MIME
129
155
  req.body = JSON.generate({ t: token })
130
156
 
131
157
  ciphertext = handle_response(perform(uri, req))
@@ -177,7 +203,7 @@ module Konfidant
177
203
  req = method == :get ? Net::HTTP::Get.new(uri) : Net::HTTP::Post.new(uri)
178
204
  req['Authorization'] = "Bearer #{@api_key}"
179
205
  if payload
180
- req['Content-Type'] = 'application/json'
206
+ req[CONTENT_TYPE] = JSON_MIME
181
207
  req.body = JSON.generate(payload)
182
208
  elsif method == :post
183
209
  req.body = ''
@@ -188,7 +214,7 @@ module Konfidant
188
214
 
189
215
  def handle_response(resp)
190
216
  content_type = resp['content-type'] || ''
191
- body = content_type.include?('application/json') ? parse_json(resp.body) : resp.body
217
+ body = content_type.include?(JSON_MIME) ? parse_json(resp.body) : resp.body
192
218
 
193
219
  return body if success?(resp)
194
220
 
data/lib/konfidant/knf.rb CHANGED
@@ -36,6 +36,7 @@ module Konfidant
36
36
  META_LENGTH_PREFIX = 4
37
37
  CIPHER = 'aes-256-gcm'.freeze
38
38
  KEY_PATTERN = /\A[A-Za-z0-9_-]{43}\z/
39
+ METADATA_TRUNCATED = 'Metadata truncated'.freeze
39
40
 
40
41
  # Result of a successful decryption. +data+ is a binary String; +kind+ is "text" or "file".
41
42
  Decrypted = Data.define(:kind, :name, :mime, :data) do
@@ -47,7 +48,7 @@ module Konfidant
47
48
  end
48
49
  end
49
50
 
50
- module_function
51
+ extend self
51
52
 
52
53
  # 32 random bytes from the OS CSPRNG. Use a fresh key for every share.
53
54
  def generate_key
@@ -165,6 +166,8 @@ module Konfidant
165
166
  raise ArgumentError, 'Invalid share URL'
166
167
  end
167
168
 
169
+ private
170
+
168
171
  # --- internal helpers --------------------------------------------------------------------------------------
169
172
 
170
173
  def check_key!(key)
@@ -218,10 +221,10 @@ module Konfidant
218
221
  end
219
222
 
220
223
  def decode_stream(stream)
221
- raise KnfError, 'Metadata truncated' if stream.bytesize < META_LENGTH_PREFIX
224
+ raise KnfError, METADATA_TRUNCATED if stream.bytesize < META_LENGTH_PREFIX
222
225
 
223
226
  meta_length = stream.byteslice(0, META_LENGTH_PREFIX).unpack1('N')
224
- raise KnfError, 'Metadata truncated' if META_LENGTH_PREFIX + meta_length > stream.bytesize
227
+ raise KnfError, METADATA_TRUNCATED if META_LENGTH_PREFIX + meta_length > stream.bytesize
225
228
 
226
229
  meta = stream.byteslice(META_LENGTH_PREFIX, meta_length)
227
230
  kind, name, mime = decode_metadata(meta)
@@ -230,13 +233,13 @@ module Konfidant
230
233
  end
231
234
 
232
235
  def decode_metadata(meta)
233
- raise KnfError, 'Metadata truncated' if meta.bytesize < META_FIXED_SIZE
236
+ raise KnfError, METADATA_TRUNCATED if meta.bytesize < META_FIXED_SIZE
234
237
 
235
238
  kind = KINDS.key(meta.getbyte(0))
236
239
  raise KnfError, 'Unknown content kind' unless kind
237
240
 
238
241
  name_length = meta.byteslice(1, 2).unpack1('n')
239
- raise KnfError, 'Metadata truncated' if 3 + name_length + 2 > meta.bytesize
242
+ raise KnfError, METADATA_TRUNCATED if 3 + name_length + 2 > meta.bytesize
240
243
 
241
244
  mime_length = meta.byteslice(3 + name_length, 2).unpack1('n')
242
245
  raise KnfError, 'Metadata length mismatch' unless META_FIXED_SIZE + name_length + mime_length == meta.bytesize
@@ -268,9 +271,6 @@ module Konfidant
268
271
  value.nil? || value.empty?
269
272
  end
270
273
 
271
- private_class_method :check_key!, :check_chunk_size!, :build_header, :parse_header, :chunk_nonce, :seal,
272
- :open_sealed, :decode_stream, :decode_metadata, :utf8_bytes, :utf8_string, :blank?
273
-
274
274
  # Streaming KNF1 encryptor with an IO-like read interface. #size is the exact ciphertext length, known up front
275
275
  # so it can be declared to the server (ciphertext_size) and sent as Content-Length.
276
276
  class Encryptor
@@ -24,4 +24,15 @@ module Konfidant
24
24
  Pagination = Data.define(:total, :limit, :offset, :has_more)
25
25
 
26
26
  ListSharesResponse = Data.define(:shares, :pagination)
27
+
28
+ # Result of Client#me: the key's organization, the key itself and the plan's share limits.
29
+ Organization = Data.define(:id, :name)
30
+
31
+ # +expires_at+ is nil if the key never expires.
32
+ ApiKeyInfo = Data.define(:name, :environment, :scopes, :rate_limit_per_minute, :expires_at)
33
+
34
+ # +max_file_size_mb+ is the largest file before encryption.
35
+ PlanLimits = Data.define(:max_ttl_hours, :max_file_size_mb)
36
+
37
+ KeyInfo = Data.define(:organization, :api_key, :limits)
27
38
  end
@@ -1,3 +1,3 @@
1
1
  module Konfidant
2
- VERSION = '1.3.0'
2
+ VERSION = '1.4.0'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: konfidant
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Konfidant
@@ -85,6 +85,7 @@ executables: []
85
85
  extensions: []
86
86
  extra_rdoc_files: []
87
87
  files:
88
+ - LICENSE
88
89
  - README.md
89
90
  - lib/konfidant.rb
90
91
  - lib/konfidant/client.rb