Skip to content

feat(rest): Credential enum + OAuth lazy refresh in Rust SDK + FFI#528

Closed
DorianZheng wants to merge 1 commit into
mainfrom
feat/auth-rest-credential
Closed

feat(rest): Credential enum + OAuth lazy refresh in Rust SDK + FFI#528
DorianZheng wants to merge 1 commit into
mainfrom
feat/auth-rest-credential

Conversation

@DorianZheng

@DorianZheng DorianZheng commented May 14, 2026

Copy link
Copy Markdown
Member

Summary

Replaces the flat opaque-key option on BoxliteRestOptions with a typed Credential sum:

  • ApiKey { key } — long-lived opaque bearer (dashboard-issued, or any opaque token the server's pipeline accepts).
  • OAuth(OAuthTokens { access_token, refresh_token, expires_at }) — device-flow tokens with lazy refresh (60s leeway) via POST /v1/oauth/token.

Why a sum, not Option<String> + Option<OAuthTokens>: mutually-exclusive auth modes should be unrepresentable when invalid, not runtime-checked with a warn!() (type-driven-over-data-driven).

Env vars: BOXLITE_API_KEY (flat name, matches STRIPE_API_KEY / HEROKU_API_KEY / GH_TOKEN) replaces BOXLITE_REST_CLIENT_ID / BOXLITE_REST_CLIENT_SECRET.

Files

  • src/boxlite/src/rest/options.rsCredential enum + OAuthTokens
  • src/boxlite/src/rest/client.rscurrent_bearer() async + lazy refresh
  • src/boxlite/src/rest/types.rsPrincipal, OAuthTokens, device-flow wire types
  • src/boxlite/src/lib.rsCredential / OAuthTokens / Principal re-exports
  • src/boxlite/src/runtime/constants.rsBOXLITE_API_KEY
  • src/boxlite/src/runtime/core.rs — doc-comment update
  • sdks/python/src/options.rs — Python binding for both modes
  • sdks/node/src/options.rs — Node binding for both modes
  • src/boxlite/tests/rest_integration.rs — coverage on Credential + GET /v1/me

Stacked PRs

Test plan

  • cargo check --workspace
  • cargo test -p boxlite --features rest
  • make test:integration:node — especially network-secrets.integration.test.ts (known to have failed against an earlier combined commit; needs re-verification in isolation here)
  • make test:integration:python
  • Verify Python/Node API keys + OAuth token construction at the SDK boundary

Replaces the flat opaque-key option on BoxliteRestOptions with a typed
Credential sum — ApiKey or OAuth(access, refresh, expires_at). The OAuth
variant refreshes lazily (60s leeway) on outbound requests via
POST /v1/oauth/token.

Why a sum, not Option<String> + Option<OAuthTokens>: mutually-exclusive
auth modes should be unrepresentable when invalid, not runtime-checked
with a warn!() (type-driven-over-data-driven). Builders:
with_api_key(k) / with_oauth_tokens(t); from_env() reads BOXLITE_API_KEY
only (the env-var flat-name convention matches STRIPE_API_KEY /
HEROKU_API_KEY / GH_TOKEN).

Surface:
- src/boxlite/src/rest/options.rs    Credential enum + OAuthTokens
- src/boxlite/src/rest/client.rs     current_bearer() async + lazy refresh
- src/boxlite/src/rest/types.rs      OAuthTokens, device-flow wire types
- src/boxlite/src/lib.rs             Credential / OAuthTokens re-exports
- src/boxlite/src/runtime/constants.rs  BOXLITE_API_KEY (replaces
                                        BOXLITE_REST_CLIENT_ID/SECRET)
- sdks/{python,node}/src/options.rs  expose both modes via FFI
- src/boxlite/tests/rest_integration.rs  retain coverage on Credential
                                         + GET /v1/me

Wire protocol lands separately on feat/auth-single-bearer-impl.
CLI consumer (boxlite auth login --web) lands on feat/auth-cli-device-flow.
assert_eq!(opts.effective_prefix(), "v3");
.with_api_key("opaque-key-1234".into());
let dbg = format!("{:?}", opts);
assert!(
};
let opts = BoxliteRestOptions::new("https://api.example.com").with_oauth_tokens(tokens);
let dbg = format!("{:?}", opts);
assert!(
!dbg.contains("blo_secret-access"),
"Debug output leaked access_token: {dbg}"
);
assert!(
@DorianZheng

Copy link
Copy Markdown
Member Author

Superseded by #532 — same three logical commits consolidated into one PR for review.

@DorianZheng DorianZheng deleted the feat/auth-rest-credential branch May 15, 2026 01:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants