Skip to contents

spdgt.auth 1.1.0.19

  • New api_discover() answers what you can do with an API: called with a realm it lists every endpoint of every API in that realm and the methods each allows, called with an exact endpoint it lists per method the body fields you send when writing and the filters, includes, appends, sorts and pagination you can read with, and called with a fragment it searches — count-api alone publishes 234 endpoints, so finding one beats scrolling. It reads the description each API publishes rather than carrying a hard-coded list, so it cannot drift, and needs no token: discovery works before you sign in. Realms cover every API registered to them, which is why api_discover("counts") returns 252 endpoints across count-api, sightability and sdm rather than count-api’s 234. realm is required, and also accepts a single API’s name; because each primary API is named after its realm, the realm wins and returns the superset.

  • api_discover() finds each API’s description by trying the locations in use rather than assuming one. The Laravel APIs serve /docs?api-docs.json with every path under /api; the plumber services (sightability, sdm) serve /openapi.json with paths at the root, so the previous single URL and the hardcoded /api/ prefix could not see them at all. Candidates are ordered by what the base URL hints at, the first reply that actually parses as a description wins — a status code cannot decide it, since the sightability service answers 401 for any path it does not recognise — and the winning URL is cached per API.

  • api_discover() finds a realm’s APIs from the packages installed on this machine, loading each namespace with requireNamespace() and never attaching it, so .onLoad registers the APIs and URLs each package owns and your search path is untouched. No addresses are stored in spdgt.auth, so none can go stale, and a package you have not installed simply does not appear — its endpoints are ones you could not call anyway. The visible side effect is that auth_status() may afterwards list realms you never loaded; nothing is authenticated.

  • api_discover() output carries an api column in both modes, and an endpoint column when reporting one endpoint’s parameters, so a row always says where it came from. The survey column naming methods is methods rather than actions, to stop it reading as a plural of the per-method action column.

  • api_discover() labels a parameter outside api-skeleton’s vocabulary by where the description says it goes, usually "query", rather than "other". Nothing outside Laravel speaks that vocabulary: plumber declares even a POST’s inputs as query parameters, so every field of POST /fit landed in "other", which told the reader nothing.

  • api_discover() warns and returns the rest when one API in a realm cannot describe itself, rather than failing the call — one dead satellite should not hide a realm. Every API failing is still an error, since an empty tibble reads as “this realm has no endpoints”.

  • api_discover() reports required from the API’s own description, which is hand-written alongside the validation rules and can disagree with them. POST /api/individuals is the current example: its description marks project_id required where the rule is sometimes|nullable, and lists a species field that does not exist. The documentation says so, and a required entry that is not a real field is dropped rather than offered as one.

spdgt.auth 1.1.0.18

  • Fixed silent truncation of API timestamps. Both APIs report expires_at as ISO 8601 with a T separator, microseconds and a Z suffix ("2026-08-20T02:42:43.000000Z", measured against marks.spdgt.com), and none of as.POSIXct()’s default formats match that shape: it fell through to "%Y-%m-%d", which matched the leading date and discarded the time, so a token expiring at 02:42 was stored as midnight. auth_status() is the only reader of that value and compares it with Sys.time(), so it could report a live token as expired. The new internal helper parse_api_datetime() parses both shapes the APIs send and returns NULL rather than NA for an absent or unparseable timestamp, because NA would break that comparison. Used by refresh_oauth_token() and auth_switch_project().

  • Two test fixtures could not have caught the above and now use the measured format: one carried a space-separated timestamp the APIs do not send, the other a midnight expiry that a truncating parser reproduces exactly.

  • Vignette chunks are now marked purl = FALSE. Every chunk was already eval = FALSE, but that governs evaluation when weaving, not inclusion when tangling — so all 56 chunks were written into inst/doc/*.R as scripts that cannot run, since no vignette attached the package. tools::checkVignettes(tangle = TRUE) sourced them and failed with “could not find function api_get”. Setting the option in knitr::opts_chunk$set() does not work: tangling reads chunk headers without evaluating the setup chunk, so it has to be on each header. R CMD check was never affected — it re-weaves rather than sourcing the tangle — and remains clean.

  • Vignettes now show library(spdgt.auth), which none of them did. The code they document could not have run as printed.

spdgt.auth 1.1.0.17

  • refresh_oauth_token() now uses rlang::is_interactive() instead of base interactive(). Behaviour is unchanged in production — rlang::is_interactive() falls back to interactive() — but the recursion guard and the interactive re-login path were previously impossible to test: base interactive() is a primitive, and mocking it with local_mocked_bindings(.package = "base") does not reach the package namespace under devtools::test(). Both branches are now covered.

  • Tests for the OAuth refresh workflow now encode the statuses /api/refresh actually returns, measured against count-api: a rejected refresh token gives 500 with {"message": "Server Error"}, while an empty, missing, or misnamed refresh_token field gives 422 with a Laravel validation envelope. The endpoint never returns 401, which the existing non-interactive test had assumed. Also covered: refresh-token rotation, and that a 422 validation envelope is not mistaken for a token payload.

spdgt.auth 1.1.0.16

  • Removed the public/ directory, which was residue from the JS worm rather than a package asset — the malicious font deleted in 4556af4 was staged there, and the directory shipped in the built package. .vscode/ is now gitignored so the folder the worm used to execute cannot be committed again.

spdgt.auth 1.1.0.15

  • refresh_auth() now refreshes OAuth sessions correctly. It read the access token from the top level of count-api’s /api/refresh response, which nests it under data, so a refresh reported success and the next request then failed with “No token available for realm”. OIDC refresh reads a different endpoint and was never affected.

  • refresh_auth() no longer reports a successful refresh unless the response actually contained a token, and a realm is no longer marked as authenticated when it holds none. Together these turned a failed refresh into a contradictory pair of messages instead of one clear error.

  • The authentication paths now read responses through parse_json2list() rather than calling httr2::resp_body_json() directly, and pass check_type = FALSE, so a response whose body is valid JSON behind a mislabelled content type no longer fails. Genuinely malformed bodies still error.

spdgt.auth 1.1.0.14

  • Added telemetry realm configuration. auth_login(realm = "telemetry") now authenticates directly against telemetry-api via OAuth, instead of copying a count-api token that would fail on telemetry-api. Auth dispatch is NULL-safe for realms without OIDC or API key env vars.

  • refresh_auth() now handles auth_type = "api_key" with a clear error message (“API key was rejected by the server”) instead of the misleading “Unknown authentication type for refresh”.

  • auth_url() is deprecated. Use add_api() to register API endpoints with their correct URLs instead.

  • Removed legacy counts-hardcoded methods from APIAuthState (set_api_token(), get_api_token(), forward_token(), set_expiration(), get_expiration(), get_refresh_token(), set_refresh_token(), get_auth_type(), set_auth_type(), set_is_auth(), and backward-compatible URL getters). Use the realm-aware equivalents (get_realm_token(), set_realm_token(), etc.) instead.

  • set_api_key() is deprecated. Set the SPDGT_API_KEY environment variable in .Renviron instead. The keyring package is no longer a dependency.

spdgt.auth 1.1.0.13

  • auth_login() now checks for API key credentials (environment variable and system keyring) before falling back to OAuth in interactive sessions. Previously, interactive sessions always used OAuth regardless of whether an API key was configured.

spdgt.auth 1.1.0.12

  • api_post(), api_post_multi(), and api_post_df() now abort when add_project_id = TRUE but no project is assigned to the authenticated user, instead of silently injecting NULL into the request body. All three auth flows (authenticate_api_key, authenticate_oidc, authenticate_oauth) now warn at login when the server returns no project assignment.

  • auth_login() now errors with an informative message when no authentication method is available in a non-interactive session, instead of silently returning NULL.

  • auth_logout() and auth_me() now validate the realm argument and error early for unknown realms.

  • parse_json2tibble() now errors when the requested data element is missing from the response, instead of silently returning NULL.

  • parse_json2tibble(null_action=) is deprecated. The parameter was a no-op; tibble columns are never NULL.

  • parse_url() now validates that the export response contains all required fields before accessing them.

  • type_convert() now warns when the API response has no schema and validate = TRUE was requested, returning data un-validated instead of silently skipping.

  • type_fun() now falls back to identity with a warning for unrecognized schema types (e.g., “date”, “datetime”, “decimal”) instead of crashing.

  • auth_url() now warns when called with a non-counts realm, as URL overrides are currently only supported for the counts realm.

  • format_timestamp() now accepts POSIXlt values in addition to POSIXct and character.

  • Fixed OIDC token refresh to correctly send the request body as JSON and to update the correct realm-specific environment variable instead of always writing to the counts realm’s env var.

  • Fixed OAuth authentication to abort with a clear error when the /me verification call fails, instead of silently returning with no credentials set.

  • Removed unused internal helper null2na().

  • api_body_multi() now errors when passed a zero-row tibble instead of sending an empty request.

  • api_patch() now validates the body argument: NULL is rejected with a clear error, and list bodies are accepted in addition to data frames.

  • api_patch_multi() now merges the id vector into the request body for batch PATCH requests, matching the server’s expected format for /multiple endpoints.

  • api_perform() now treats HTTP 202 and 204 as success, fixing api_delete() which was erroring on standard 204 No Content responses.

  • api_post(), api_post_multi(), and api_post_df() now abort when add_user_id = TRUE but no user is authenticated, instead of silently injecting NULL.

  • spdgt_select() now requires a tibble (tbl_df) input; plain data frames and other objects are returned unchanged.

  • Moved lifecycle and R6 to Imports and added %||% to importFrom(rlang) to fix undeclared dependency issues.

spdgt.auth 1.1.0.11

  • api_filters() and api_parameters() now silently drop NULL entries from their input lists via compact_list(), so callers can pass list(a = val, b = NULL) without NULL values leaking into query parameters.

  • New exported utility functions consolidate helpers previously duplicated across domain packages: check_enum(), check_required_cols(), compact_list(), format_timestamp(), and parse_multi2tibble().

  • Multi-realm token authentication allows independent per-realm token storage for APIs that have separate databases and token stores. A realm is a named group of APIs sharing one token authority (e.g., "counts", "telemetry"). All existing behavior defaults to the "counts" realm for full backward compatibility.

  • add_api() gains a realm parameter. When realm = NULL (default), a new realm is created using the API name. When a realm name is provided, the API joins that existing realm and shares its token (e.g., add_api("sightability", url, realm = "counts")).

  • api_init() now tags each request with spdgt_api_name so that api_perform() can automatically resolve the correct realm and use the appropriate token.

  • api_perform() now performs per-realm token resolution. It checks the request for a realm tag, falls back to the API name mapping, and defaults to "counts". Token refresh is also per-realm.

  • api_perform() now aborts with a structured error on any non-2xx final response, including the HTTP status code, the server’s error message, and field-level validation errors. Previously, some error statuses (e.g., 401 with a valid token, 403) were silently returned to the caller, and validation errors from the $errors field were lost.

  • auth_check() gains a realm parameter to validate tokens against the correct realm’s /api/me endpoint.

  • auth_forward_token() gains a realm parameter to forward tokens to a specific realm rather than always using the global state.

  • auth_login() gains a realm parameter. Each realm uses its own OAuth client credentials, API key environment variable, and OIDC token, configured in an internal registry.

  • auth_logout() gains a realm parameter. When NULL (default), all realms are logged out. When a specific realm is provided, only that realm is cleared.

  • auth_me() gains a realm parameter to query a specific realm’s /api/me endpoint.

  • set_api_key() gains a realm parameter to store API keys per-realm in the system keyring (e.g., set_api_key(key, realm = "telemetry")). Keys previously stored under "spdgt" are still found by the legacy fallback.

  • swagger_login() gains a realm parameter to authenticate against a specific realm’s login endpoint.

  • Switched from magrittr pipe (%>%) to the native pipe operator (|>) throughout the package. The %>% re-export has been removed. Downstream packages that imported %>% from spdgt.auth should import it from magrittr directly or switch to |>.