onetime-up 0.6.0 → 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 +28 -21
- 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'
|
|
@@ -71,11 +71,15 @@ class Onetime::CLI
|
|
|
71
71
|
command :receipt do |obj|
|
|
72
72
|
raise RuntimeError, "csv not supported" if obj.global.format == 'csv'
|
|
73
73
|
raise RuntimeError, "Usage: #{$0} receipt <KEY>" unless obj.argv.key
|
|
74
|
-
|
|
74
|
+
|
|
75
|
+
# Handle case where argv.key might be an array due to drydock parsing
|
|
76
|
+
receipt_key = Array(obj.argv.key).first.to_s
|
|
77
|
+
|
|
78
|
+
@res = @api.get '/receipt/%s' % receipt_key
|
|
75
79
|
if @res.nil?
|
|
76
80
|
raise RuntimeError, 'Could not complete request'
|
|
77
81
|
elsif @api.response.code != 200
|
|
78
|
-
raise RuntimeError, @res
|
|
82
|
+
raise RuntimeError, OT::API.response_error_message(@res)
|
|
79
83
|
end
|
|
80
84
|
case obj.global.format
|
|
81
85
|
when 'json'
|
|
@@ -94,17 +98,18 @@ class Onetime::CLI
|
|
|
94
98
|
command :secret do |obj|
|
|
95
99
|
raise RuntimeError, "csv not supported" if obj.global.format == 'csv'
|
|
96
100
|
raise RuntimeError, "Usage: #{$0} secret <KEY>" unless obj.argv.key
|
|
97
|
-
|
|
101
|
+
|
|
102
|
+
# Handle case where argv.key might be an array due to drydock parsing
|
|
103
|
+
secret_key = Array(obj.argv.key).first.to_s
|
|
104
|
+
|
|
105
|
+
opts = { continue: true }
|
|
98
106
|
opts[:passphrase] = obj.option.passphrase if obj.option.passphrase
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
obj.argv.key = $1
|
|
102
|
-
end
|
|
103
|
-
@res = @api.post '/secret/%s/reveal' % [obj.argv.key], opts
|
|
107
|
+
secret_key = OT::API.extract_secret_key(secret_key)
|
|
108
|
+
@res = @api.post '/secret/%s/reveal' % [secret_key], opts
|
|
104
109
|
if @res.nil?
|
|
105
110
|
raise RuntimeError, 'Could not complete request'
|
|
106
111
|
elsif @api.response.code != 200
|
|
107
|
-
raise RuntimeError, @res
|
|
112
|
+
raise RuntimeError, OT::API.response_error_message(@res)
|
|
108
113
|
end
|
|
109
114
|
case obj.global.format
|
|
110
115
|
when 'json'
|
|
@@ -112,7 +117,7 @@ class Onetime::CLI
|
|
|
112
117
|
when 'yaml'
|
|
113
118
|
puts @res.to_yaml
|
|
114
119
|
else
|
|
115
|
-
value = @res
|
|
120
|
+
value = @res.dig('record', 'secret_value')
|
|
116
121
|
puts value if value
|
|
117
122
|
end
|
|
118
123
|
end
|
|
@@ -146,9 +151,10 @@ class Onetime::CLI
|
|
|
146
151
|
if @res.nil?
|
|
147
152
|
raise RuntimeError, 'Could not complete request'
|
|
148
153
|
elsif @api.response.code != 200
|
|
149
|
-
raise RuntimeError, @res
|
|
154
|
+
raise RuntimeError, OT::API.response_error_message(@res)
|
|
150
155
|
end
|
|
151
|
-
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
|
|
152
158
|
uri = OT::API.web_uri('secret', secret_key)
|
|
153
159
|
case obj.global.format
|
|
154
160
|
when 'json'
|
|
@@ -156,9 +162,9 @@ class Onetime::CLI
|
|
|
156
162
|
when 'yaml'
|
|
157
163
|
puts @res.to_yaml
|
|
158
164
|
else
|
|
159
|
-
|
|
160
|
-
if
|
|
161
|
-
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(',')
|
|
162
168
|
else
|
|
163
169
|
puts uri
|
|
164
170
|
end
|
|
@@ -178,9 +184,10 @@ class Onetime::CLI
|
|
|
178
184
|
if @res.nil?
|
|
179
185
|
raise RuntimeError, 'Could not complete request'
|
|
180
186
|
elsif @api.response.code != 200
|
|
181
|
-
raise RuntimeError, @res
|
|
187
|
+
raise RuntimeError, OT::API.response_error_message(@res)
|
|
182
188
|
end
|
|
183
|
-
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
|
|
184
191
|
uri = OT::API.web_uri('secret', secret_key)
|
|
185
192
|
case obj.global.format
|
|
186
193
|
when 'json'
|
|
@@ -190,9 +197,9 @@ class Onetime::CLI
|
|
|
190
197
|
when 'csv'
|
|
191
198
|
puts uri
|
|
192
199
|
else
|
|
193
|
-
|
|
194
|
-
if
|
|
195
|
-
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(',')
|
|
196
203
|
else
|
|
197
204
|
puts uri
|
|
198
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:
|
|
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: []
|