diff --git a/NAMESPACE b/NAMESPACE index 4c2235c8..eae573bd 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -42,6 +42,7 @@ export(local_verbosity) export(new_response) export(oauth_cache_clear) export(oauth_cache_path) +export(oauth_cache_prune) export(oauth_client) export(oauth_client_req_auth) export(oauth_client_req_auth_body) diff --git a/NEWS.md b/NEWS.md index 4803a63c..a5330ead 100644 --- a/NEWS.md +++ b/NEWS.md @@ -3,6 +3,7 @@ * Fixed OAuth token cache pruning so that it actually matches the encrypted `.rds.enc` files written to disk; previously the pruning pattern only matched an unencrypted `.rds` file that was never created, so cached tokens were never automatically deleted regardless of age. * httr2 now requires rlang >= 1.3.0, which changes the hash used to name files cached by `req_cache()` and on-disk OAuth token caches (e.g. from `req_oauth_auth_code(cache_disk = TRUE)`). Existing cached files won't match the new hash, so they'll be silently ignored (triggering a normal cache miss/re-authentication) and cleaned up over time by the usual pruning rules; you can also delete them manually. * `oauth_cache_path()` now defaults to a standard R cache directory (via `tools::R_user_dir()`). Because this release also changes the hash used for cache filenames, existing OAuth tokens will generally not be reused and you may need to authenticate once after upgrading. New tokens are written to the new location, and obsolete tokens in both the old and new locations are removed by the usual pruning rules. httr2 no longer requires the rappdirs package (#800). +* New `oauth_cache_prune()` lets you manually delete cached OAuth tokens older than a given number of days, exposing the pruning that httr2 already performs automatically on load. * `req_oauth_*()` gains an `expiry_margin` argument to control how early cached OAuth tokens are treated as expired; the default margin increases from 5 to 30 seconds (@zacdav-db, #860). # httr2 1.2.3 diff --git a/R/oauth.R b/R/oauth.R index a7e24e6d..01ddb96c 100644 --- a/R/oauth.R +++ b/R/oauth.R @@ -209,9 +209,27 @@ cache_disk <- function(client, key = NULL) { ) } +#' Prune the OAuth token cache +#' +#' Deletes cached OAuth tokens (from both the current and legacy cache +#' directories, see [oauth_cache_path()]) that are older than +#' `max_age_days`. This is called automatically when httr2 is loaded, so +#' you should only need to call it yourself if you want to prune the cache +#' immediately. +#' +#' @param max_age_days Delete cached tokens that haven't been modified in +#' this many days. +#' @export +oauth_cache_prune <- function(max_age_days = 30) { + check_number_whole(max_age_days, min = 0) + + cache_disk_prune(max_age_days) + invisible() +} + # Update req_oauth_auth_code() docs if change default from 30 cache_disk_prune <- function( - days = 30, + max_age_days = 30, paths = c(oauth_cache_path(), oauth_cache_path_legacy()) ) { files <- dir( @@ -222,7 +240,7 @@ cache_disk_prune <- function( ) mtime <- file.mtime(files) - old <- mtime < (Sys.time() - days * 86400) + old <- mtime < (Sys.time() - max_age_days * 86400) unlink(files[old]) } diff --git a/man/oauth_cache_prune.Rd b/man/oauth_cache_prune.Rd new file mode 100644 index 00000000..98af0b25 --- /dev/null +++ b/man/oauth_cache_prune.Rd @@ -0,0 +1,19 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/oauth.R +\name{oauth_cache_prune} +\alias{oauth_cache_prune} +\title{Prune the OAuth token cache} +\usage{ +oauth_cache_prune(max_age_days = 30) +} +\arguments{ +\item{max_age_days}{Delete cached tokens that haven't been modified in +this many days.} +} +\description{ +Deletes cached OAuth tokens (from both the current and legacy cache +directories, see \code{\link[=oauth_cache_path]{oauth_cache_path()}}) that are older than +\code{max_age_days}. This is called automatically when httr2 is loaded, so +you should only need to call it yourself if you want to prune the cache +immediately. +} diff --git a/tests/testthat/_snaps/oauth.md b/tests/testthat/_snaps/oauth.md index 5661d90d..4eba6322 100644 --- a/tests/testthat/_snaps/oauth.md +++ b/tests/testthat/_snaps/oauth.md @@ -13,3 +13,11 @@ Message Caching httr2 token in '/httr2-test/2c0a8a99dc147d5445c3b49d035665b2-token.rds.enc'. +# oauth_cache_prune() validates its input + + Code + oauth_cache_prune("x") + Condition + Error in `oauth_cache_prune()`: + ! `max_age_days` must be a whole number, not the string "x". + diff --git a/tests/testthat/test-oauth.R b/tests/testthat/test-oauth.R index ba1b0b4e..dfec9094 100644 --- a/tests/testthat/test-oauth.R +++ b/tests/testthat/test-oauth.R @@ -235,6 +235,26 @@ test_that("prunes old files from both new and legacy locations", { expect_equal(dir(legacy_path), "a-token.rds.enc") }) +test_that("oauth_cache_prune() prunes the default cache locations", { + new_path <- withr::local_tempdir() + legacy_path <- withr::local_tempdir() + local_mocked_bindings( + oauth_cache_path = function() new_path, + oauth_cache_path_legacy = function() legacy_path + ) + + touch(file.path(new_path, "a-token.rds.enc"), Sys.time() - 86400 * 1) + touch(file.path(new_path, "b-token.rds.enc"), Sys.time() - 86400 * 2) + + oauth_cache_prune(2) + + expect_equal(dir(new_path), "a-token.rds.enc") +}) + +test_that("oauth_cache_prune() validates its input", { + expect_snapshot(oauth_cache_prune("x"), error = TRUE) +}) + # cache_path -------------------------------------------------------------- test_that("can override path with env var", {