onetime-up 0.6.1 → 0.6.2
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 +11 -0
- data/README.md +36 -1
- data/bin/onetime +16 -17
- data/lib/onetime/api.rb +68 -8
- data/lib/onetime/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 829b100c0882cdb8e5f797d27d0f9e52e576cd20f20235f7836532f3872b74ea
|
|
4
|
+
data.tar.gz: 9df7bd8e315d3fe6339619016b1b03c0450bbfb9c6a83644f1ce9bcf77e77fd6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 905c34dd750efb26e2d3af6314d22b30d8a101885a9eef965d730a2c71849b80b54e6ddf6bad8f952c1de8a4a822b1df0f26667dc7b4cc883fec0efe25221d6d
|
|
7
|
+
data.tar.gz: 53e8f305c78a4a79a539adb359508078835ae83f96156f8c9abd503ff7adce81d758411d86efffb30833eae46588ff5e8d6937dd316892020bc5291712d60f44
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [Unreleased]
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- Update library callers to API v2 response shapes (`record.receipt`, `record.secret`, reveal `record.secret_value`)
|
|
8
|
+
- Restore `apiversion` path handling for callers that pass an API version explicitly
|
|
9
|
+
- Add safer CLI response handling for changed or partial API responses
|
|
10
|
+
- Accept secret URLs from root and regional onetimesecret.com hosts
|
|
11
|
+
|
|
3
12
|
## [0.6.0] - 2026-01-18
|
|
4
13
|
|
|
5
14
|
### Added
|
|
@@ -8,9 +17,11 @@ Major/breaking changes:
|
|
|
8
17
|
|
|
9
18
|
- Upgrade to API v2
|
|
10
19
|
- Rename command "metadata" to "receipt"
|
|
20
|
+
- Change the default API host from the root service to the EU region (`https://eu.onetimesecret.com/api`)
|
|
11
21
|
|
|
12
22
|
Other changes:
|
|
13
23
|
|
|
24
|
+
- Add opt-in official API contract validation for v2 endpoint shapes
|
|
14
25
|
- Remove Rakefile, signing data and Jeweler references
|
|
15
26
|
- Simplified and modernized gemspec (set minimum Ruby to 3.2)
|
|
16
27
|
- Add Gemfile
|
data/README.md
CHANGED
|
@@ -2,8 +2,43 @@
|
|
|
2
2
|
|
|
3
3
|
Fork of the Ruby program [`One-Time Secret`](https://github.com/onetimesecret/onetime-ruby), using the API v2.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Basic usage
|
|
6
|
+
|
|
7
|
+
Share a secret:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
echo "secret text" | onetime share
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Share a secret protected by a passphrase:
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
echo "secret text" | onetime share -p "shared-passphrase"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Retrieve a secret:
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
onetime secret SECRET_KEY
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Generate a random secret:
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
onetime generate
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Use JSON or YAML output:
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
onetime -j generate
|
|
35
|
+
onetime -y receipt RECEIPT_KEY
|
|
36
|
+
```
|
|
6
37
|
|
|
7
38
|
## Breaking changes
|
|
8
39
|
|
|
9
40
|
The command `metadata` has been renamed `receipt` for consistency with the API.
|
|
41
|
+
|
|
42
|
+
The default API host is now `https://eu.onetimesecret.com/api`.
|
|
43
|
+
|
|
44
|
+
The library now uses API v2 response shapes. Programmatic callers should read receipt data from `record.receipt` and secret data from `record.secret` or reveal responses from `record.secret_value`.
|
data/bin/onetime
CHANGED
|
@@ -51,7 +51,7 @@ class Onetime::CLI
|
|
|
51
51
|
if @res.nil?
|
|
52
52
|
raise RuntimeError, 'Could not complete request'
|
|
53
53
|
elsif @api.response.code != 200
|
|
54
|
-
raise RuntimeError, @res
|
|
54
|
+
raise RuntimeError, OT::API.response_error_message(@res)
|
|
55
55
|
end
|
|
56
56
|
case obj.global.format
|
|
57
57
|
when 'json'
|
|
@@ -79,7 +79,7 @@ class Onetime::CLI
|
|
|
79
79
|
if @res.nil?
|
|
80
80
|
raise RuntimeError, 'Could not complete request'
|
|
81
81
|
elsif @api.response.code != 200
|
|
82
|
-
raise RuntimeError, @res
|
|
82
|
+
raise RuntimeError, OT::API.response_error_message(@res)
|
|
83
83
|
end
|
|
84
84
|
case obj.global.format
|
|
85
85
|
when 'json'
|
|
@@ -104,15 +104,12 @@ class Onetime::CLI
|
|
|
104
104
|
|
|
105
105
|
opts = { continue: true }
|
|
106
106
|
opts[:passphrase] = obj.option.passphrase if obj.option.passphrase
|
|
107
|
-
|
|
108
|
-
if secret_key =~ /#{base_uri.hostname}\/secret\/([a-zA-Z0-9]+)/
|
|
109
|
-
secret_key = $1
|
|
110
|
-
end
|
|
107
|
+
secret_key = OT::API.extract_secret_key(secret_key)
|
|
111
108
|
@res = @api.post '/secret/%s/reveal' % [secret_key], opts
|
|
112
109
|
if @res.nil?
|
|
113
110
|
raise RuntimeError, 'Could not complete request'
|
|
114
111
|
elsif @api.response.code != 200
|
|
115
|
-
raise RuntimeError, @res
|
|
112
|
+
raise RuntimeError, OT::API.response_error_message(@res)
|
|
116
113
|
end
|
|
117
114
|
case obj.global.format
|
|
118
115
|
when 'json'
|
|
@@ -154,9 +151,10 @@ class Onetime::CLI
|
|
|
154
151
|
if @res.nil?
|
|
155
152
|
raise RuntimeError, 'Could not complete request'
|
|
156
153
|
elsif @api.response.code != 200
|
|
157
|
-
raise RuntimeError, @res
|
|
154
|
+
raise RuntimeError, OT::API.response_error_message(@res)
|
|
158
155
|
end
|
|
159
|
-
secret_key = @res
|
|
156
|
+
secret_key = OT::API.secret_key_from_response(@res)
|
|
157
|
+
raise RuntimeError, 'Unexpected response: missing record.secret.key' unless secret_key
|
|
160
158
|
uri = OT::API.web_uri('secret', secret_key)
|
|
161
159
|
case obj.global.format
|
|
162
160
|
when 'json'
|
|
@@ -164,9 +162,9 @@ class Onetime::CLI
|
|
|
164
162
|
when 'yaml'
|
|
165
163
|
puts @res.to_yaml
|
|
166
164
|
else
|
|
167
|
-
|
|
168
|
-
if
|
|
169
|
-
STDERR.puts '# Secret link sent to: %s' %
|
|
165
|
+
recipients = OT::API.recipients_from_response(@res)
|
|
166
|
+
if !recipients.empty?
|
|
167
|
+
STDERR.puts '# Secret link sent to: %s' % recipients.join(',')
|
|
170
168
|
else
|
|
171
169
|
puts uri
|
|
172
170
|
end
|
|
@@ -186,9 +184,10 @@ class Onetime::CLI
|
|
|
186
184
|
if @res.nil?
|
|
187
185
|
raise RuntimeError, 'Could not complete request'
|
|
188
186
|
elsif @api.response.code != 200
|
|
189
|
-
raise RuntimeError, @res
|
|
187
|
+
raise RuntimeError, OT::API.response_error_message(@res)
|
|
190
188
|
end
|
|
191
|
-
secret_key = @res
|
|
189
|
+
secret_key = OT::API.secret_key_from_response(@res)
|
|
190
|
+
raise RuntimeError, 'Unexpected response: missing record.secret.key' unless secret_key
|
|
192
191
|
uri = OT::API.web_uri('secret', secret_key)
|
|
193
192
|
case obj.global.format
|
|
194
193
|
when 'json'
|
|
@@ -198,9 +197,9 @@ class Onetime::CLI
|
|
|
198
197
|
when 'csv'
|
|
199
198
|
puts uri
|
|
200
199
|
else
|
|
201
|
-
|
|
202
|
-
if
|
|
203
|
-
STDERR.puts '# Secret link sent to: %s' %
|
|
200
|
+
recipients = OT::API.recipients_from_response(@res)
|
|
201
|
+
if !recipients.empty?
|
|
202
|
+
STDERR.puts '# Secret link sent to: %s' % recipients.join(',')
|
|
204
203
|
else
|
|
205
204
|
puts uri
|
|
206
205
|
end
|
data/lib/onetime/api.rb
CHANGED
|
@@ -41,11 +41,12 @@ module Onetime
|
|
|
41
41
|
base_uri 'https://eu.onetimesecret.com/api'
|
|
42
42
|
format :json
|
|
43
43
|
headers 'X-Onetime-Client' => 'ruby: %s/%s' % [RUBY_VERSION, Onetime::VERSION]
|
|
44
|
-
attr_reader :opts, :response, :custid, :key, :default_params, :anonymous
|
|
44
|
+
attr_reader :opts, :response, :custid, :key, :default_params, :anonymous, :apiversion
|
|
45
45
|
def initialize custid=nil, key=nil, opts={}
|
|
46
46
|
unless ENV['ONETIME_HOST'].to_s.empty?
|
|
47
47
|
self.class.base_uri ENV['ONETIME_HOST']
|
|
48
48
|
end
|
|
49
|
+
@apiversion = opts.delete(:apiversion) || opts.delete('apiversion') || 2
|
|
49
50
|
@opts = opts
|
|
50
51
|
@default_params = {}
|
|
51
52
|
@custid = custid || ENV['ONETIME_CUSTID']
|
|
@@ -65,8 +66,15 @@ module Onetime
|
|
|
65
66
|
opts[:query] = (params || {}).merge default_params
|
|
66
67
|
execute_request :get, path, opts
|
|
67
68
|
end
|
|
68
|
-
def post path, params=nil
|
|
69
|
+
def post path, params=nil, request_opts={}
|
|
69
70
|
opts = self.opts.clone
|
|
71
|
+
wrap = if request_opts.key?(:wrap)
|
|
72
|
+
request_opts[:wrap]
|
|
73
|
+
elsif request_opts.key?('wrap')
|
|
74
|
+
request_opts['wrap']
|
|
75
|
+
else
|
|
76
|
+
:auto
|
|
77
|
+
end
|
|
70
78
|
body_params = (params || {}).merge default_params
|
|
71
79
|
|
|
72
80
|
# V2 API uses JSON format
|
|
@@ -75,9 +83,7 @@ module Onetime
|
|
|
75
83
|
'Accept' => 'application/json'
|
|
76
84
|
})
|
|
77
85
|
|
|
78
|
-
|
|
79
|
-
# Other endpoints (reveal, burn, etc.) do NOT wrap
|
|
80
|
-
if path =~ /\/secret\/(conceal|generate)$/
|
|
86
|
+
if wrap_secret_body?(path, wrap)
|
|
81
87
|
body_params = { secret: body_params }
|
|
82
88
|
end
|
|
83
89
|
|
|
@@ -86,16 +92,21 @@ module Onetime
|
|
|
86
92
|
execute_request :post, path, opts
|
|
87
93
|
end
|
|
88
94
|
def api_path *args
|
|
89
|
-
args.unshift ['',
|
|
95
|
+
args.unshift ['', "v#{apiversion}"] # force leading slash and version
|
|
90
96
|
path = args.flatten.join('/')
|
|
91
97
|
path.gsub(/\/+/, '/')
|
|
92
98
|
end
|
|
93
99
|
private
|
|
100
|
+
def wrap_secret_body?(path, wrap)
|
|
101
|
+
return false if wrap == false || wrap.nil?
|
|
102
|
+
return true if wrap == :secret
|
|
103
|
+
api_path(path).match?(%r{\A/v\d+/secret/(conceal|generate)/?\z})
|
|
104
|
+
end
|
|
105
|
+
|
|
94
106
|
def execute_request meth, path, opts
|
|
95
107
|
path = api_path [path]
|
|
96
108
|
@response = self.class.send meth, path, opts
|
|
97
|
-
|
|
98
|
-
result
|
|
109
|
+
self.class.indifferent_params @response.parsed_response
|
|
99
110
|
end
|
|
100
111
|
class << self
|
|
101
112
|
def web_uri *args
|
|
@@ -128,6 +139,55 @@ module Onetime
|
|
|
128
139
|
def indifferent_hash
|
|
129
140
|
Hash.new {|hash,key| hash[key.to_s] if Symbol === key }
|
|
130
141
|
end
|
|
142
|
+
def extract_secret_key(value, api_base_uri=base_uri)
|
|
143
|
+
return value unless value
|
|
144
|
+
|
|
145
|
+
uri = URI.parse(value.to_s)
|
|
146
|
+
return value unless uri.host && uri.path
|
|
147
|
+
return value unless accepted_secret_host?(uri.host, api_base_uri)
|
|
148
|
+
|
|
149
|
+
match = uri.path.match(%r{\A/secret/([a-zA-Z0-9]+)\z})
|
|
150
|
+
match ? match[1] : value
|
|
151
|
+
rescue URI::InvalidURIError
|
|
152
|
+
value
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def response_error_message(response)
|
|
156
|
+
return 'Could not complete request' if response.nil?
|
|
157
|
+
|
|
158
|
+
# Symbol lookups preserve behavior for plain Ruby hashes even though
|
|
159
|
+
# parsed API responses usually support indifferent access.
|
|
160
|
+
['message', :message, 'error', :error, 'field', :field].each do |key|
|
|
161
|
+
value = response[key] if response.respond_to?(:[])
|
|
162
|
+
return value.to_s unless value.to_s.empty?
|
|
163
|
+
end
|
|
164
|
+
response.to_s
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def secret_key_from_response(response)
|
|
168
|
+
response&.dig('record', 'secret', 'key')
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def receipt_key_from_response(response)
|
|
172
|
+
response&.dig('record', 'receipt', 'key') || response&.dig('record', 'metadata', 'key')
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def recipients_from_response(response)
|
|
176
|
+
recipients = response&.dig('record', 'receipt', 'recipients')
|
|
177
|
+
if recipients.nil? || recipients == '' || (recipients.is_a?(Array) && recipients.empty?)
|
|
178
|
+
recipients = response&.dig('details', 'recipient')
|
|
179
|
+
end
|
|
180
|
+
Array(recipients).flatten.compact.reject { |recipient| recipient.to_s.empty? }
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
private
|
|
184
|
+
|
|
185
|
+
def accepted_secret_host?(host, api_base_uri)
|
|
186
|
+
configured_host = URI.parse(api_base_uri.to_s).host
|
|
187
|
+
host == configured_host || host == 'onetimesecret.com' || host.end_with?('.onetimesecret.com')
|
|
188
|
+
rescue URI::InvalidURIError
|
|
189
|
+
host == 'onetimesecret.com' || host.end_with?('.onetimesecret.com')
|
|
190
|
+
end
|
|
131
191
|
end
|
|
132
192
|
end
|
|
133
193
|
end
|
data/lib/onetime/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: onetime-up
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.6.
|
|
4
|
+
version: 0.6.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Delano Mandelbaum
|
|
8
8
|
- Saverio Miroddi
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-05-13 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: drydock
|
|
@@ -102,7 +102,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
102
102
|
- !ruby/object:Gem::Version
|
|
103
103
|
version: 2.0.0
|
|
104
104
|
requirements: []
|
|
105
|
-
rubygems_version:
|
|
105
|
+
rubygems_version: 4.0.7
|
|
106
106
|
specification_version: 4
|
|
107
107
|
summary: Command-line tool and library for onetimesecret.com API (API v2)
|
|
108
108
|
test_files: []
|