Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
259 commits
Select commit Hold shift + click to select a range
58920aa
Sanitize display text per code point
glasstiger Jun 18, 2026
1db99c1
Reject tokens from error or non-2xx responses
glasstiger Jun 18, 2026
c0ff593
Reject a null or empty provider token
glasstiger Jun 18, 2026
9824d69
improved tests
glasstiger Jun 18, 2026
faa6e47
Speed up JSON unescape and validate URL hosts
glasstiger Jun 18, 2026
19a9966
Add OIDC issuer pin and .well-known discovery
glasstiger Jun 18, 2026
dc02c16
Validate OIDC URLs and enforce the discoveryUrl pin
glasstiger Jun 19, 2026
e523db2
Reject display-unsafe characters in OIDC URLs
glasstiger Jun 19, 2026
caa5087
Strip unpaired surrogates from OIDC display text
glasstiger Jun 19, 2026
7266d47
Clamp slow_down interval and reset parser fields
glasstiger Jun 19, 2026
6f02ccf
Simplify JSON unescape and tidy method ordering
glasstiger Jun 19, 2026
9da26c1
Test the OIDC response body size cap
glasstiger Jun 19, 2026
697f49a
Harden OIDC device-flow status and timeout checks
glasstiger Jun 19, 2026
6e97d14
Pin OIDC discovery to the discoveryUrl origin
glasstiger Jun 21, 2026
4dbce9e
Reject a non-numeric OIDC HTTP status code
glasstiger Jun 21, 2026
ddd3e62
Escape control chars in ILP error messages
glasstiger Jun 21, 2026
c0ed8b4
Test the plaintext-channel OIDC pin firing path
glasstiger Jun 21, 2026
8421148
Fix OIDC Windows test, use try-with-resources
glasstiger Jun 22, 2026
49becd9
Reject OIDC tokens with control or non-ASCII chars
glasstiger Jun 22, 2026
bd37dc8
Bound chunked response reads to the call timeout
glasstiger Jun 22, 2026
64933dc
Escape bidi and format chars in error messages
glasstiger Jun 22, 2026
619a3fc
Tighten OIDC device-flow comments and javadoc
glasstiger Jun 22, 2026
7b9d20f
Add OIDC browser-open prompt and DiscoveryOptions
glasstiger Jun 23, 2026
6ac442b
Make OIDC browser-open the default
glasstiger Jun 23, 2026
2126e22
Send OIDC audience on device and refresh requests
glasstiger Jun 23, 2026
b8f073e
Tighten OIDC IdP transport and issuer-path trust
glasstiger Jun 23, 2026
0a31d49
Clamp OIDC device-code lifetime to 600s/1800s
glasstiger Jun 23, 2026
28dc110
reduce max poll interval to 60s
glasstiger Jun 23, 2026
8e721d4
Treat OIDC token-poll 429 as a transient backoff
glasstiger Jun 23, 2026
62403f3
Remove OIDC poll-error budget; match Python model
glasstiger Jun 23, 2026
f0cd84f
Accept token provider over WebSocket transport
glasstiger Jun 23, 2026
63487c1
Make OIDC clock skew fixed and lifetime-capped
glasstiger Jun 23, 2026
aab512b
Harden client response reads and display escaping
glasstiger Jun 23, 2026
4430a50
Fix HTTP client leak on lexer alloc failure
glasstiger Jun 23, 2026
a654fbb
Sort static helpers and pre-encode grant types
glasstiger Jun 23, 2026
0865d0e
Drop the OIDC connection on a bounded-read abort
glasstiger Jun 24, 2026
7d52ad5
Harden OIDC URL parsing and address review nits
glasstiger Jun 24, 2026
94da999
Reject control/non-ASCII chars in provider tokens
glasstiger Jun 24, 2026
15067f7
Fix build-time pull claim in token provider docs
glasstiger Jun 24, 2026
67c78d9
Harden issuer-path scope and fix review nits
glasstiger Jun 24, 2026
d5bcf93
Escape control/bidi chars in flush error messages
glasstiger Jun 24, 2026
23b6656
Unify display-safety classifier and add tests
glasstiger Jun 24, 2026
cea40a5
Trust discovered OIDC endpoints; drop discoveryUrl
glasstiger Jun 25, 2026
5dd82b4
Merge remote-tracking branch 'origin/main' into ia_oidc_device_flow
glasstiger Jun 25, 2026
c35d931
Fix Java 8 build breaks in OIDC device flow
glasstiger Jun 25, 2026
0493b4c
Sanitize HTTP status and probe text in errors
glasstiger Jun 25, 2026
a4875f2
Tighten OIDC token-kind and status validation
glasstiger Jun 25, 2026
e1f3d53
Fix Java 8 build: replace String.repeat in test
glasstiger Jun 25, 2026
3d10a4e
QWP egress token provider and OIDC API rename
glasstiger Jun 25, 2026
8d38d4f
OIDC device-flow review follow-ups
glasstiger Jun 25, 2026
272d704
Fix Java 8 build: use URLEncoder String charset
glasstiger Jun 25, 2026
1b7fecd
Fail fast on QWP token-provider failures
glasstiger Jun 25, 2026
a4e928f
opt-in token persistence
glasstiger Jun 29, 2026
65f590b
Merge remote-tracking branch 'origin/main' into ia_oidc_device_flow
glasstiger Jun 29, 2026
e0010d6
Cap token-store lock budget; add persistence tests
glasstiger Jun 29, 2026
148d895
Merge remote-tracking branch 'origin/main' into ia_oidc_device_flow
glasstiger Jun 29, 2026
71e01f2
Harden OIDC token store lock and expiry clamp
glasstiger Jun 29, 2026
9be7c28
Harden token-store load and key validation
glasstiger Jun 29, 2026
2128e54
Tighten token-store hardening and hygiene
glasstiger Jun 29, 2026
aba8399
Refine token-store cross-process and load semantics
glasstiger Jun 29, 2026
8469be9
Enforce OIDC token store lock staleness floor
glasstiger Jun 30, 2026
b016dd0
Harden OIDC token-store lock steal and release
glasstiger Jun 30, 2026
ea7c1a8
Harden token-store cross-process lock edge cases
glasstiger Jun 30, 2026
a454ac0
Fix OIDC refresh NPE and harden display safety
glasstiger Jul 1, 2026
2ce1ca0
Reject OIDC endpoint fragment, harden token lock
glasstiger Jul 1, 2026
9e0a756
Fix WS per-endpoint token pull and getToken doc
glasstiger Jul 1, 2026
1d67163
Reject non-ASCII OIDC hosts and fix review nits
glasstiger Jul 1, 2026
6ced352
Fix flaky token-store steal-contention test
glasstiger Jul 1, 2026
f3e62ed
Address code-review findings on OIDC device flow
glasstiger Jul 1, 2026
acfa6e6
Serialize token-store long fields as digits
glasstiger Jul 1, 2026
7ba71a7
Add OIDC token-provider tests and cleanups
glasstiger Jul 1, 2026
8b91ecb
token persistence example
glasstiger Jul 1, 2026
6334bba
Fix OIDC clock-skew collapse for stored tokens
glasstiger Jul 1, 2026
4b385ee
Harden OIDC device-flow input validation
glasstiger Jul 1, 2026
9daaba6
Merge remote-tracking branch 'origin/main' into ia_oidc_device_flow
glasstiger Jul 14, 2026
c726c13
Fix three OIDC device-flow token-provider defects
glasstiger Jul 14, 2026
1418554
Cover untested OIDC load-bearing guards
glasstiger Jul 14, 2026
304663f
Address moderate OIDC review findings
glasstiger Jul 14, 2026
07f809b
Build ILP token-provider request once per flush
glasstiger Jul 14, 2026
82ae4af
Address remaining moderate OIDC review findings
glasstiger Jul 14, 2026
82b8bab
Fix OIDC cancelRow crash and SF drainer terminal
glasstiger Jul 15, 2026
3250b3e
Harden OIDC token validation, lock and issuer-path pin
glasstiger Jul 15, 2026
b7bb36d
Harden OIDC token flow, store lock, name escaping
glasstiger Jul 15, 2026
2acba17
Fix OIDC blank-refresh gate and correct docs
glasstiger Jul 15, 2026
7ed1aa1
Correct misleading OIDC docs and mislabeled test
glasstiger Jul 15, 2026
bea586d
Merge remote-tracking branch 'origin/main' into ia_oidc_device_flow
glasstiger Aug 5, 2026
f2f9cf9
Validate the row before at() writes any bytes
glasstiger Aug 5, 2026
e0b2037
Report a credential outage to the error handler
glasstiger Aug 5, 2026
5ec1c21
Let close() break a drainer stuck in a token pull
glasstiger Aug 5, 2026
e06be8c
Correct the frozen token-store interop contract
glasstiger Aug 5, 2026
a606bce
Stop a hostile chunk size spinning the read loop
glasstiger Aug 6, 2026
17a811f
Keep the token store working under an interrupt
glasstiger Aug 6, 2026
14df48b
Let an orphan drainer ride out a rotating 401
glasstiger Aug 6, 2026
0aa7ca0
Keep a persisted refresh token with no served kind
glasstiger Aug 6, 2026
fde8db5
Harden the token store lock and on-disk format
glasstiger Aug 6, 2026
4b52bf6
Ride out a rotating 401 that lands mid-drain
glasstiger Aug 6, 2026
fa13d4d
Surface mock handler errors; leak-check QWP tests
glasstiger Aug 6, 2026
1a0bad9
Harden rotating auth retry and provider failures
glasstiger Aug 6, 2026
7a95bb3
Merge branch 'main' into ia_oidc_device_flow
glasstiger Aug 6, 2026
d34f05b
Merge remote-tracking branch 'origin/main' into ia_oidc_device_flow
glasstiger Aug 9, 2026
d4f2887
Merge branch 'main' into ia_oidc_device_flow
bluestreak01 Aug 18, 2026
2d08b59
feat: support token providers for pooled clients
glasstiger Aug 18, 2026
9a5eb88
update review-pr skill
glasstiger Aug 18, 2026
29ae3a0
fix Java 8 build
glasstiger Aug 18, 2026
9947e68
Report a credential outage from the orphan drainer
glasstiger Aug 18, 2026
3223cc6
Cover token providers on SF pooled senders
glasstiger Aug 18, 2026
efa5d90
Cover the fixed-vs-rotating credential tag
glasstiger Aug 18, 2026
54a364e
Restore the exported API signatures this branch broke
glasstiger Aug 18, 2026
7681b96
Stop a device grant inheriting the previous user's refresh token
glasstiger Aug 18, 2026
6246c3f
Make the token store's lock waits interruptible
glasstiger Aug 18, 2026
97fa287
Reject every overflowing chunk size, not just negative ones
glasstiger Aug 18, 2026
6674c6e
Tighten parseHexLong itself instead of adding a checked sibling
glasstiger Aug 18, 2026
67061c2
Require a 2xx before trusting a discovery body
glasstiger Aug 18, 2026
823498c
Stop a response-body read abort from re-sending a flush
glasstiger Aug 18, 2026
47f0210
Degrade around a throwing TokenStore instead of failing the sign-in
glasstiger Aug 18, 2026
c287f89
Roll back HttpClient construction when it fails partway
glasstiger Aug 18, 2026
fcd8859
Reject a store entry carrying only a refresh token
glasstiger Aug 19, 2026
793db46
Assert the store directory instead of assuming it
glasstiger Aug 19, 2026
d3d3650
Keep a carried interrupt from swallowing a sign-out
glasstiger Aug 19, 2026
8cf04f9
Handle a malformed response head from the provider
glasstiger Aug 19, 2026
71c04f9
Bound the rotating-credential 401 ride-out
glasstiger Aug 19, 2026
6b7ccc4
Check the store directory on the read path too
glasstiger Aug 19, 2026
c0b341d
Retry an unparseable response head on the ILP flush
glasstiger Aug 19, 2026
4b7e4ed
Restart the rotating-401 dwell after a transient
glasstiger Aug 19, 2026
45f87aa
Back off after a failed silent refresh
glasstiger Aug 19, 2026
e900d94
Say SLF4J where the docs promised System.err
glasstiger Aug 19, 2026
d7416d5
Decline a silent refresh on a cancelled caller
glasstiger Aug 19, 2026
3f94ec2
Clamp the rotating-401 dwell instead of capping attempts
glasstiger Aug 19, 2026
1a9f765
Correct what the rotating-401 comments promise
glasstiger Aug 19, 2026
b71f8b2
Restart the 401 dwell on a capability gap too
glasstiger Aug 19, 2026
36935a7
Hold the ride-out budgets per drain, not per call
glasstiger Aug 19, 2026
a974adb
Roll back ResponseHeaders' own buffer too
glasstiger Aug 19, 2026
cda4a81
Report an empty buffer as address zero, not -1
glasstiger Aug 19, 2026
75ad4b8
Close three security residuals in the token store
glasstiger Aug 19, 2026
6bc8528
Throttle a failing token store read instead of retrying per flush
glasstiger Aug 19, 2026
131a78c
Give the weak tests teeth and stop repeating the failure block
glasstiger Aug 19, 2026
67542a2
Bound the two lock waits on nanoTime, not the wall clock
glasstiger Aug 19, 2026
002228a
Make java.desktop optional so the module resolves without it
glasstiger Aug 19, 2026
a4da03b
Make the desktop-free child structurally unable to open a browser
glasstiger Aug 19, 2026
65f0733
Stripe the in-process token store locks, unblock the lock stamp
glasstiger Aug 19, 2026
5c11757
Pin what five regression tests only appeared to test
glasstiger Aug 19, 2026
94adf95
State the token store's real single-login and integrity limits
glasstiger Aug 20, 2026
90e49e1
Drop the credentials on close instead of keeping them
glasstiger Aug 20, 2026
ce9c142
Validate a row once per row, not twice
glasstiger Aug 20, 2026
a230dfc
Tidy four things the auth tests were getting away with
glasstiger Aug 20, 2026
0e4c762
Arm the refresh back-off only on a real attempt
glasstiger Aug 20, 2026
c19e037
End the settle budget when the wire delivers
glasstiger Aug 20, 2026
5533480
Create the store fixture dir owner-only, not by umask
glasstiger Aug 20, 2026
9ceb005
Give each identity its own in-process lock again
glasstiger Aug 20, 2026
42fd547
Refuse a served token that reads as a bare JSON null
glasstiger Aug 20, 2026
ca7074a
Decline a sign-in on a cancelled thread, store or not
glasstiger Aug 20, 2026
be0b821
Restore what the review-pr skill sync overwrote
glasstiger Aug 20, 2026
72c5828
Say elapsed time where the comments claimed the wall clock
glasstiger Aug 20, 2026
dc6b1a6
Assert the leak test actually injected a failure
glasstiger Aug 20, 2026
be6ce1c
Pin the rotating-401 dwell clamp at its call site
glasstiger Aug 20, 2026
d23e952
Discard the whole store directory, not one entry, when it was exposed
glasstiger Aug 20, 2026
3052914
Pin the exported signatures this branch had to put back
glasstiger Aug 20, 2026
6204a0e
Snapshot a pulled token before validating it
glasstiger Aug 20, 2026
49e0c9b
Collapse two mutually masking SIGSEGV guards into one that is pinned
glasstiger Aug 20, 2026
90df372
Zero the buffer StringSink abandons when it grows
glasstiger Aug 20, 2026
975cdaf
Reunite eight doc comments with the members they document
glasstiger Aug 20, 2026
675b786
Say why the error response body could not be read
glasstiger Aug 20, 2026
5ebdaf0
Keep the two other things the ILP sender was handed and dropped
glasstiger Aug 20, 2026
807f984
Tidy three hygiene slips in the branch's own files
glasstiger Aug 20, 2026
110775b
Keep the rotated refresh token a refresh returns
glasstiger Aug 20, 2026
d228c96
Discard only files the token store itself wrote
glasstiger Aug 20, 2026
967a729
Namespace the in-process store lock by directory
glasstiger Aug 20, 2026
56eecc7
Stop signIn() ignoring and erasing a cancellation
glasstiger Aug 20, 2026
79e31ca
Assert the retryable half of isRetryable(), not just the other one
glasstiger Aug 20, 2026
7a66cb0
Bound the response head read on elapsed time
glasstiger Aug 20, 2026
2720bbf
Derive the OIDC transport budgets from its timeout
glasstiger Aug 20, 2026
930e568
Keep the interrupt a cancelled lock wait consumed
glasstiger Aug 20, 2026
71eadd7
Break a recovery delegate stuck in a credential pull
glasstiger Aug 20, 2026
26744e7
Retry a store whose directory was transiently unusable
glasstiger Aug 20, 2026
43db382
Cover lazy_connect crossed with a token provider
glasstiger Aug 20, 2026
d780810
Bound the chunk size where it is read, not in Numbers
glasstiger Aug 21, 2026
1fc842a
Document the bound the no-arg recv() now carries
glasstiger Aug 21, 2026
939c7ee
Write the JsonParser contract the lexer now relies on
glasstiger Aug 21, 2026
da85158
Restore the Windows FDSet leak injection
glasstiger Aug 21, 2026
7b3e207
Stop a carried interrupt skipping client teardown
glasstiger Aug 21, 2026
16180b9
Pin the capability-gap counter across recycles
glasstiger Aug 21, 2026
e03a512
Bound the rotating-401 ride-out under alternation
glasstiger Aug 21, 2026
7554b84
Wipe the JSON lexer's decode buffers too
glasstiger Aug 21, 2026
1cd272c
Qualify when a WebSocket build() pulls a token
glasstiger Aug 21, 2026
76b1b61
Make TokenStoreKey a value type
glasstiger Aug 21, 2026
9d5d053
Pin the OIDC body read's elapsed deadline
glasstiger Aug 21, 2026
7dd9358
Copy printable text in one go, not per character
glasstiger Aug 21, 2026
7faad66
Document the credential exception as the API it is
glasstiger Aug 21, 2026
89f5622
Pin the two uncovered escaping callsites
glasstiger Aug 21, 2026
5ecb431
Pin snapshot-before-validate on all three callsites
glasstiger Aug 21, 2026
b8ce60c
Stop a carried interrupt failing a delegate close
glasstiger Aug 21, 2026
929b626
Stop retrying a response head that cannot change
glasstiger Aug 21, 2026
5a6c431
Stop a recovered store read undoing a sign-in
glasstiger Aug 21, 2026
0a662f8
Stop writing a store entry adopt() refuses to read
glasstiger Aug 21, 2026
016ad0a
Cover the token store's lock-restore arm
glasstiger Aug 23, 2026
09eb8b5
Annotate recv(int) as an override
glasstiger Aug 23, 2026
bfc66a9
Pin the drain-abort disconnect
glasstiger Aug 24, 2026
1edc09c
Pin isRetryable across the unreadable-body wrapper
glasstiger Aug 24, 2026
3b313d1
Reattach recvOrDie's javadoc to recvOrDie
glasstiger Aug 24, 2026
b9be5df
Swallow AWTError from the browser launch
glasstiger Aug 24, 2026
5948257
Stop the drainer's dispatcher close busy-spinning
glasstiger Aug 24, 2026
0e7bc4b
Count connect and TLS in the lock-hold multiple
glasstiger Aug 24, 2026
accd41c
Say that tlsConfig also governs IdP validation
glasstiger Aug 24, 2026
d0ee772
Pin the ack-progress guard and the interrupt hand-back
glasstiger Aug 24, 2026
6e3a043
Restore alphabetical member order
glasstiger Aug 24, 2026
1640890
Preserve an interrupt across the store rename retry
glasstiger Aug 24, 2026
919cfa8
Drop stale and over-claiming comments
glasstiger Aug 24, 2026
903e3fd
Merge remote-tracking branch 'origin/main' into ia_oidc_device_flow
glasstiger Aug 24, 2026
25a8064
Stop a carried interrupt skipping the escalation
glasstiger Aug 24, 2026
990f690
Pin both halves of the ack-progress reset
glasstiger Aug 24, 2026
67ffa18
Pin the lock steal's capture, verify and restore
glasstiger Aug 24, 2026
1f8c3ac
Say what re-enters the drain loop, and name a real method
glasstiger Aug 24, 2026
fdad4c8
Fix token-store concurrent trust-verdict race
glasstiger Aug 24, 2026
d66e1f3
Close mutation-tested client test coverage gaps
glasstiger Aug 24, 2026
fb9c858
Merge remote-tracking branch 'origin/main' into ia_oidc_device_flow
glasstiger Aug 25, 2026
0a935f0
Fix flaky mid-drain 401 dwell-anchor test
glasstiger Aug 25, 2026
4652f05
Distrust a sentinel name a symlink squats
glasstiger Aug 25, 2026
9921a56
Say when an untrusted mark can no longer be lifted
glasstiger Aug 25, 2026
b8e9805
Say what an interrupted drainer close actually does
glasstiger Aug 25, 2026
16c1078
Keep the rotation a rejected refresh arrived with
glasstiger Aug 25, 2026
83c5311
Stop a clock vetoing the sweep that ignores it
glasstiger Aug 25, 2026
df57890
Name the third flush-path read the bound reaches
glasstiger Aug 25, 2026
966de32
Name the fault that wedged the untrusted mark
glasstiger Aug 25, 2026
16bca45
Sanitize OidcAuthException oauth error accessor
glasstiger Aug 25, 2026
4a25610
Correct the /settings probe retry-bound comment
glasstiger Aug 25, 2026
2ebfc4f
Fix FileTokenStore javadoc to match the code
glasstiger Aug 25, 2026
66cd691
fix(auth): serialize token store recovery and writes
glasstiger Aug 25, 2026
13753dc
Validate OIDC discovery issuer
glasstiger Aug 25, 2026
66b8515
Remove terminal escape from source
glasstiger Aug 25, 2026
d1941b2
Preserve trailing slash in OIDC issuer validation
glasstiger Aug 25, 2026
63a0f97
fix(auth): recover directory lock after process crash
glasstiger Aug 25, 2026
9b2f046
test(pool): restore SF teardown timeout
glasstiger Aug 25, 2026
03122f6
Observe late acknowledgements before recycling
glasstiger Aug 26, 2026
4c7de6d
Preserve shutdown escalation across interrupts
glasstiger Aug 26, 2026
29e49b7
fix(client): interrupt OIDC refresh wait on close
glasstiger Aug 26, 2026
989962e
fix(client): handle interrupts in auth and close drain
glasstiger Aug 26, 2026
48a475f
fix(client): separate OIDC token store warnings
glasstiger Aug 26, 2026
2eafcb9
fix(client): strip orphan FSNs from forwarded errors
glasstiger Aug 26, 2026
62ee56b
test(client): pin post-terminal drainer ack
glasstiger Aug 26, 2026
f99e9c1
fix(client): narrow OIDC close interruption
glasstiger Aug 27, 2026
8535b95
fix(client): harden OIDC refresh and cleanup
glasstiger Aug 27, 2026
0649c22
fix(client): tighten token store permissions safely
glasstiger Aug 27, 2026
9309cd6
fix(client): pin OIDC and recovery guards
glasstiger Aug 27, 2026
6b743be
fix(test): keep FileTokenStoreTest Java 8 compatible
glasstiger Aug 27, 2026
01fe9c9
fix(test): stabilize rotating auth retry windows
glasstiger Aug 27, 2026
3e534eb
Close two token store recovery gaps
glasstiger Aug 27, 2026
7fe76d3
fix(client): interrupt OIDC cache clear on close
glasstiger Aug 27, 2026
d93474a
fix(client): recover squatted token-store locks on Windows
glasstiger Aug 27, 2026
7eda522
fix(client): harden auth cleanup and sender shutdown
glasstiger Aug 27, 2026
3423784
fix(test): pin dynamic-token 401 outage window
glasstiger Aug 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 100 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,106 @@ try (QuestDB db = QuestDB.connect("wss::addr=localhost:9000;tls_verify=unsafe_of
}
```

### OIDC Sign-In (Device Flow)

For QuestDB Enterprise instances secured with OIDC, `OidcDeviceAuth` signs a user in interactively using the [OAuth 2.0 Device Authorization Grant](https://www.rfc-editor.org/rfc/rfc8628). It works from environments that have no local browser — a remote notebook kernel, a container, a headless job — because the user authorizes on any device (laptop or phone) while the process only makes outbound calls to the identity provider.

On first use it prints a verification URL and a short code, and opens the URL in your default browser when one is available; authorize there (or open the URL on any device, such as your phone), enter the code, and the token is cached in memory and refreshed silently on later calls.

```java
import io.questdb.client.QuestDB;
import io.questdb.client.Sender;
import io.questdb.client.cutlass.auth.OidcDeviceAuth;

// Discover the client id, scope and endpoints from the QuestDB server's /settings:
try (OidcDeviceAuth auth = OidcDeviceAuth.fromQuestDB("https://questdb.example.com:9000")) {
auth.signIn(); // sign in once: prompts on first use, then caches and refreshes

// The provider is shared by the ingest and query pools. It is queried for
// every initial WebSocket upgrade and reconnect, so both pools follow token
// rotation without putting a credential in the configuration string.
try (QuestDB db = QuestDB.connect(
"wss::addr=questdb.example.com:9000;",
auth::getToken)) {
try (Sender sender = db.borrowSender()) {
sender.table("trades")
.symbol("symbol", "ETH-USD")
.doubleColumn("price", 2615.54)
.atNow();
}
// db.borrowQuery() uses the same provider for query connections.
}
}
```

For a standalone sender, use `httpTokenProvider(auth::getToken)` for the same rotating-token behavior. A fixed `httpToken(token)` or `token=` connect-string value captures the token once, so a client that reconnects after that token expires starts failing authentication. Hand rotating credentials to the provider API, not a `Sender.fromConfig(...)` string or the `QDB_CLIENT_CONF` environment variable, which are easily logged, persisted, or left in shell history.

`getToken()` sits on a hot path — it is called once per ILP flush and once per WebSocket upgrade or reconnect — so a credential failure is rate-limited rather than retried on every call. When a silent refresh fails, `getToken()` does not attempt another one for 5 seconds: calls inside that window fail immediately, asking for an interactive `signIn()`, instead of hitting the identity provider again. Without that guard a producer retrying its rows would drive one token-endpoint round trip per flush, blocking the producer thread for each one and hitting the provider hard enough to trip its rate limits and lengthen the very outage being retried. Only a real refresh attempt arms the guard, and an explicit `signIn()` or `clearCache()` clears it outright. It is deliberately short — a stampede guard, not a circuit breaker — so a credential that comes back within seconds is picked up on the first call after the window rather than on the first call after it recovers.

By default the prompt prints the verification URL and code to `System.out` **and** tries to open the URL in your default browser. The browser open is best-effort: it only opens an `http(s)` URL, is skipped on a headless host or a JVM without the `java.desktop` module, and never blocks sign-in (the client declares `requires static java.desktop`, so the module is optional at run time and its absence can never break module resolution; a modular application therefore gets the browser launch only when `java.desktop` is in its own module graph) — the URL and code are always printed too, so a remote or browserless process still works. To disable the browser launch for a whole process (a server, automation, CI), set the system property `-Dquestdb.client.oidc.open.browser=false`. To print only (no browser) for a single client, pass `DeviceCodePrompt.SYSTEM_OUT`; to render the challenge yourself (a clickable link or QR code in a notebook), pass any `DeviceCodePrompt`:

```java
// print only, do not open a browser:
try (OidcDeviceAuth auth = OidcDeviceAuth.fromQuestDB(
"https://questdb.example.com:9000",
new OidcDeviceAuth.DiscoveryOptions().prompt(DeviceCodePrompt.SYSTEM_OUT))) {
auth.signIn();
}
```

The same token can be presented to QuestDB over any auth path the server already validates:

- **REST API:** send it as an `Authorization: Bearer <token>` header (`auth.getAuthorizationHeaderValue()` returns the full value).
- **PG-wire:** connect as user `_sso` with the token as the password (requires `acl.oidc.pg.token.as.password.enabled=true` on the server).

To configure the identity provider explicitly instead of discovering it from the server:

```java
OidcDeviceAuth auth = OidcDeviceAuth.builder()
.clientId("questdb")
.deviceAuthorizationEndpoint("https://idp.example.com/as/device_authz.oauth2")
.tokenEndpoint("https://idp.example.com/as/token.oauth2")
.scope("openid groups")
.groupsInToken(true) // matches acl.oidc.groups.encoded.in.token on the server
.build();
```

Discovery via `fromQuestDB(...)` reads the OIDC client id, scope, audience and endpoints from the server's `/settings`, and the identity provider's client must have the device authorization grant enabled. When the server does not advertise its device authorization endpoint (today's servers), pin the identity provider by its issuer so the client can discover the endpoint from the issuer's `.well-known/openid-configuration` document:

```java
try (OidcDeviceAuth auth = OidcDeviceAuth.fromQuestDB(
"https://questdb.example.com:9000",
new OidcDeviceAuth.DiscoveryOptions().issuer("https://idp.example.com"))) {
auth.signIn();
}
```

The identity provider's device authorization and token endpoints must use `https` — a loopback endpoint (`localhost` or `127.0.0.0/8`) may use `http`, since the request never leaves the host — so the device code and refresh token are never sent in cleartext. That is a rule about the *scheme*: the trust anchor is `tlsConfig`, and one `OidcDeviceAuth` carries a single one, so `ClientTlsConfiguration.INSECURE_NO_VALIDATION` — reached for to talk to a QuestDB server with a self-signed certificate — also stops the client validating the *identity provider's* certificate on the legs that carry the device code and the refresh token. Point `tlsConfig` at a trust store rather than disabling validation whenever an identity provider is involved. `allowInsecureTransport(true)` relaxes only the QuestDB `/settings` link (for local development against an `http` QuestDB server), e.g. `OidcDeviceAuth.fromQuestDB(url, new OidcDeviceAuth.DiscoveryOptions().allowInsecureTransport(true))`; it never relaxes the identity provider endpoints, matching the Python client.

`fromQuestDB(...)` takes the identity provider endpoints from the server's unauthenticated `/settings`, so it trusts that server to designate where you sign in: a spoofed, compromised, or man-in-the-middled server could redirect the sign-in to an attacker-controlled identity provider. Only use it against a server you trust, reached over `https`. Passing an issuer hardens this: the token and device authorization endpoints are then pinned to the issuer's origin (and, when the issuer has a path, an endpoint advertised by `/settings` must also be under that path — so a tampered `/settings` cannot redirect to a different tenant on a path-based provider such as Keycloak `…/realms/{realm}`), and an endpoint outside it is rejected; the issuer itself comes from you out of band, so a tampered `/settings` cannot move it. When `.well-known` discovery is needed, the document must also return the exact issuer prefix used to retrieve it before any discovered endpoint is accepted. When the server is not trusted, configure the identity provider explicitly with `OidcDeviceAuth.builder()` (optionally with `.issuer(...)`) instead of discovering it.

#### Persisting the Token Across Restarts

By default the token lives in memory only, so a process that restarts has to run the device flow again. Pass a `TokenStore` to persist it; the restarted process then resumes from the saved refresh token — a silent call to the token endpoint — instead of prompting the user again:

```java
import io.questdb.client.cutlass.auth.FileTokenStore;

try (OidcDeviceAuth auth = OidcDeviceAuth.fromQuestDB(
"https://questdb.example.com:9000",
new OidcDeviceAuth.DiscoveryOptions().tokenStore(FileTokenStore.atDefaultLocation()))) {
auth.signIn(); // prompts the first time; after a restart it refreshes silently from the saved token
}
```

`FileTokenStore.atDefaultLocation()` writes one file per OIDC configuration under `${user.home}/.questdb/oidc-tokens/` (override the directory with the `questdb.client.oidc.token.store.dir` system property). The file name is a hash of the endpoints, client id, scope, audience and groups-in-token mode — the *configuration*, not the person who signed in through it, since none of those fields names a subject. Entries for different servers, providers or client configurations therefore stay separate, but **two people signing in through the same configuration share one entry**: whoever signs in last overwrites the previous token, so a store represents a single active login. If more than one application user has to be signed in at the same time, give each their own store — `FileTokenStore.at(dir)` on a per-user directory, or a per-user `questdb.client.oidc.token.store.dir` — rather than relying on the file name to separate them. The default location is already per OS user, so this only arises when one OS user (a shared service account, a multi-tenant process) signs in as several people. After a restart, `getToken()` also works as the first call — no explicit `signIn()` needed — which suits a long-lived `Sender` built with `httpTokenProvider(auth::getToken)`. `clearCache()` removes the persisted entry and forces a fresh sign-in next time.

A store read that *throws* — an unreadable file after a `chmod` or a uid change in a container, `EIO`/`ESTALE` on an NFS home — is not fatal and does not disable persistence for the life of the process, but it is not retried on every call either, since `getToken()` would otherwise pay a blocking file open and a `WARN` line per ILP flush, forever. The first failure is retried immediately, so a one-shot fault (notably a carried interrupt flag, which makes the channel underneath `FileTokenStore` throw on a thread that merely carries it) recovers on the next call; each consecutive failure after that backs off 5 seconds, doubling to a 60 second cap. A store that simply has nothing to return is unaffected — `load` reports that by returning `null` rather than by throwing, and the client stops asking.

The token is stored as **plaintext JSON protected by file permissions** — `0600` file, `0700` directory on POSIX systems (Linux, macOS), the same approach `gcloud`, `aws` and `gh` take. On Windows these POSIX permissions cannot be enforced, so the file currently relies on the user-profile directory's default ACL (owner-only ACL hardening is a follow-up); the client logs a one-line warning through SLF4J at `WARN` the first time it cannot enforce them (the library ships `slf4j-api` only, so this - and every other client warning - is discarded unless your application supplies an SLF4J binding). Enabling persistence therefore writes a long-lived refresh token to disk: anyone who can read the file holds a credential until it expires or is revoked. To encrypt it at rest, supply your own `TokenStore` (backed by an OS keychain or a secrets manager) instead of `FileTokenStore`. A persisted file is treated as untrusted input on load, but it is **not cryptographically authenticated** — there is no MAC or signature over its contents. Anyone who can write the file can therefore substitute a well-formed entry of their own, and the client will adopt it and present those tokens: the file permissions, not the file format, are what protect it. What the load path rejects is corruption and mix-ups rather than forgery — an oversized, malformed or unparseable file; an entry whose recorded client id, endpoints, scope, audience or groups-in-token mode does not match the identity being loaded; an entry carrying no usable token; and a token with control or non-ASCII characters, which is never placed on the wire — with the recorded expiry and lifetime clamped rather than trusted. In each case the client falls back to a refresh or an interactive sign-in. On POSIX the container is checked too: if the directory was writable by other local users, it is tightened back to `0700` and **every** entry in it is discarded — not just the one being read, since the tightening is what destroys the evidence, so anything left behind would look protected to the next load. Each identity then signs in again. Inside those permissions, though, the client cannot tell a planted credential from its own.

`FileTokenStore` is safe to share between processes that sign in as the same identity: each update is written atomically (so a concurrent reader never sees a half-written credential), and when the identity provider rotates the refresh token on each refresh, the read-refresh-write is serialized across processes with a lock file so they do not race each other into an unnecessary re-prompt. The lock file's staleness is judged by its modification time, so this coordination assumes the processes share a clock — a single machine, or machines with synchronized clocks; under significant clock skew (for example a store directory on NFS shared across hosts) a live lock can be mis-judged stale or a dead one never expire. `clearCache()` removes the persisted entry under the same lock, but across processes it is best-effort: a peer that still holds a live in-memory token may legitimately re-persist afterwards (it always forces a fresh sign-in for the calling process).

### Explicit Timestamps

```java
Expand Down
Loading
Loading