wefunder 0.1.0.beta2 → 0.1.0.beta3
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 +10 -6
- data/lib/wefunder/client.rb +3 -0
- data/lib/wefunder/token_manager.rb +50 -18
- data/lib/wefunder/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: a3ac10a62a698ee6cb163f3ec6e100248d461dc6c55bf97caeb1bac349b44af6
|
|
4
|
+
data.tar.gz: 26d0e08433a88d288942da627f8d509ffa196e6e2ed3cc347bae43beadaf84bf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4ee97ef8052e3e7a9476a8b0243b52a706bfd2c6a5911e1b9362c07e5d5d6290529615ed6daace3d953769d126a492440692ebef5191cd7846c418a70f6a54b5
|
|
7
|
+
data.tar.gz: 535800baceafa25b8fcb744d2a1fc16a0596e14d80db5fba6f0c83772ee371c16a361335f82819b5a5e664bf016d17b008d3ff46a3f07f527eab491d8aa36bdd
|
data/README.md
CHANGED
|
@@ -84,10 +84,11 @@ class TokenStore
|
|
|
84
84
|
end
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
-
The rotated set is saved **before** any thread can use it. If `store.save` raises, the SDK
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
and
|
|
87
|
+
The rotated set is saved **before** any thread can use it. If `store.save` or `on_token_refresh` raises, the SDK raises
|
|
88
|
+
`Wefunder::TokenPersistenceError` and keeps the rotated set *pending*: no request is made with it, the
|
|
89
|
+
consumed refresh token is never reused, and the next call retries the save (or persist
|
|
90
|
+
`error.tokens` yourself and call `wf.token_manager.mark_persisted!(error.tokens)`; a stale acknowledgment after another rotation is a no-op). Alert on this error; a process
|
|
91
|
+
that keeps failing to save cannot reconnect after a restart.
|
|
91
92
|
|
|
92
93
|
Concurrent requests that hit a 401 at the same time share one refresh (the client is thread-safe).
|
|
93
94
|
If several application instances can use the same OAuth connection, serialize refreshes for that
|
|
@@ -229,10 +230,13 @@ sends a fully-wrapped request and returns the decoded JSON.
|
|
|
229
230
|
```bash
|
|
230
231
|
bundle install
|
|
231
232
|
bundle exec rubocop
|
|
232
|
-
bundle exec rspec
|
|
233
|
-
bundle exec rspec spec/e2e
|
|
233
|
+
bundle exec rspec # hermetic — live examples are tagged :e2e and filtered out
|
|
234
|
+
WEFUNDER_E2E=1 bundle exec rspec spec/e2e # live sandbox; also needs WEFUNDER_CLIENT_ID / WEFUNDER_CLIENT_SECRET
|
|
234
235
|
```
|
|
235
236
|
|
|
237
|
+
The live group is opt-in by tag, not by path: without `WEFUNDER_E2E=1` it never runs, even when sandbox
|
|
238
|
+
credentials happen to be exported in your shell.
|
|
239
|
+
|
|
236
240
|
Generated files in `lib/wefunder_generated/` come from `openapi/openapi.yaml` via `script/generate`
|
|
237
241
|
(openapi-generator in Docker; no Java needed) and are never edited by hand.
|
|
238
242
|
|
data/lib/wefunder/client.rb
CHANGED
|
@@ -70,6 +70,9 @@ module Wefunder
|
|
|
70
70
|
# The current token set (rotated refresh token included).
|
|
71
71
|
def tokens = @token_manager.current
|
|
72
72
|
|
|
73
|
+
# The TokenManager (for +mark_persisted!+ after handling a TokenPersistenceError).
|
|
74
|
+
attr_reader :token_manager
|
|
75
|
+
|
|
73
76
|
# The generated APIs, one accessor per tag (+wf.raw.syndicate_members.list_syndicate_members(id)+).
|
|
74
77
|
# Wrap calls in +wrap+ to get typed errors.
|
|
75
78
|
attr_reader :raw
|
|
@@ -8,8 +8,10 @@ module Wefunder
|
|
|
8
8
|
# manager already holds a different one, another caller recovered in the meantime and the
|
|
9
9
|
# current set is returned without a round-trip. The rotated set is persisted (store +
|
|
10
10
|
# callback) BEFORE the retried request can use it. Thread-safe (Mutex).
|
|
11
|
-
# Raised when the token store failed to save a rotated token set. +tokens+ is the
|
|
12
|
-
# is
|
|
11
|
+
# Raised when the token store failed to save a rotated token set. +tokens+ is the rotated
|
|
12
|
+
# set that is NOT yet durable and NOT yet in use; the manager keeps it pending and retries
|
|
13
|
+
# the save on the next call (or persist it yourself and call +mark_persisted!(tokens)+). Until it is
|
|
14
|
+
# saved no request is made with it — the consumed refresh token is never reused either.
|
|
13
15
|
class TokenPersistenceError < StandardError
|
|
14
16
|
attr_reader :tokens
|
|
15
17
|
|
|
@@ -22,7 +24,9 @@ module Wefunder
|
|
|
22
24
|
class TokenManager
|
|
23
25
|
DEFAULT_EXPIRY_LEEWAY_SECONDS = 30.0
|
|
24
26
|
|
|
25
|
-
|
|
27
|
+
# The durable, in-use token set. A rotated set that could not be persisted is held in
|
|
28
|
+
# +pending_tokens+ instead until a save succeeds.
|
|
29
|
+
attr_reader :current, :pending_tokens
|
|
26
30
|
|
|
27
31
|
def initialize(tokens, client_id: nil, client_secret: nil, re_mint: nil, on_token_refresh: nil, store: nil,
|
|
28
32
|
faraday: nil, now: nil, expiry_leeway_seconds: DEFAULT_EXPIRY_LEEWAY_SECONDS,
|
|
@@ -46,16 +50,35 @@ module Wefunder
|
|
|
46
50
|
can_rotate? || !@re_mint.nil?
|
|
47
51
|
end
|
|
48
52
|
|
|
49
|
-
# A valid access token, refreshing proactively if expired / within the leeway.
|
|
53
|
+
# A valid access token, refreshing proactively if expired / within the leeway. If a rotated
|
|
54
|
+
# set is pending persistence, the save is retried first — no request uses an undurable token.
|
|
50
55
|
def access_token
|
|
56
|
+
@lock.synchronize { publish_pending! if @pending_tokens } if @pending_tokens # re-check under the lock
|
|
51
57
|
refresh(stale_token: @current.access_token) if near_expiry? && can_refresh?
|
|
52
58
|
@current.access_token
|
|
53
59
|
end
|
|
54
60
|
|
|
61
|
+
# Tell the manager you persisted +tokens+ (the set from a TokenPersistenceError) yourself.
|
|
62
|
+
# Publishes it only if it is still the pending set; a stale acknowledgment (the manager has
|
|
63
|
+
# since rotated again) is a no-op and returns false, so an older save can never publish a
|
|
64
|
+
# newer, unsaved set.
|
|
65
|
+
def mark_persisted!(tokens)
|
|
66
|
+
@lock.synchronize do
|
|
67
|
+
return false unless @pending_tokens && same_token_set?(@pending_tokens, tokens)
|
|
68
|
+
|
|
69
|
+
@current = @pending_tokens
|
|
70
|
+
@pending_tokens = nil
|
|
71
|
+
true
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
55
75
|
# Recover the token (after a 401 or proactively). If +stale_token+ is no longer the current
|
|
56
76
|
# token, someone else already recovered and the current set is returned.
|
|
57
77
|
def refresh(stale_token: nil)
|
|
58
78
|
@lock.synchronize do
|
|
79
|
+
# A rotated set awaiting persistence: retry the save rather than rotating again (the
|
|
80
|
+
# old refresh token was consumed by that rotation).
|
|
81
|
+
return publish_pending! if @pending_tokens
|
|
59
82
|
return @current if stale_token && @current.access_token != stale_token
|
|
60
83
|
|
|
61
84
|
previous_refresh = @current.refresh_token
|
|
@@ -71,26 +94,35 @@ module Wefunder
|
|
|
71
94
|
else
|
|
72
95
|
raise AuthError, "Access token expired and no refresh token / re-mint capability is configured."
|
|
73
96
|
end
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
@on_token_refresh&.call(nxt)
|
|
77
|
-
nxt
|
|
97
|
+
@pending_tokens = nxt
|
|
98
|
+
publish_pending!
|
|
78
99
|
end
|
|
79
100
|
end
|
|
80
101
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
#
|
|
84
|
-
#
|
|
85
|
-
#
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
102
|
+
private
|
|
103
|
+
|
|
104
|
+
# Persist BEFORE publishing (caller holds the lock): no thread may use the rotated token
|
|
105
|
+
# until it is durable, and a failed save must not leave the process working in memory but
|
|
106
|
+
# unable to reconnect after a restart. On failure the set stays in +pending_tokens+ and
|
|
107
|
+
# TokenPersistenceError is raised; the next call retries the save.
|
|
108
|
+
def publish_pending!
|
|
109
|
+
tokens = @pending_tokens
|
|
110
|
+
begin
|
|
111
|
+
@store&.save(tokens)
|
|
112
|
+
# on_token_refresh is a persistence path too, so it runs BEFORE publication and a failure
|
|
113
|
+
# keeps the set pending exactly like a store failure.
|
|
114
|
+
@on_token_refresh&.call(tokens)
|
|
115
|
+
rescue StandardError => e
|
|
116
|
+
raise TokenPersistenceError.new(tokens, e)
|
|
117
|
+
end
|
|
118
|
+
@pending_tokens = nil
|
|
89
119
|
@current = tokens
|
|
90
|
-
|
|
120
|
+
tokens
|
|
91
121
|
end
|
|
92
122
|
|
|
93
|
-
|
|
123
|
+
def same_token_set?(a, b)
|
|
124
|
+
a.access_token == b.access_token && a.refresh_token == b.refresh_token
|
|
125
|
+
end
|
|
94
126
|
|
|
95
127
|
def can_rotate?
|
|
96
128
|
!@current.refresh_token.nil? && !@current.refresh_token.empty? && !@client_id.nil?
|
data/lib/wefunder/version.rb
CHANGED