diff --git a/ModuleConfig.cfc b/ModuleConfig.cfc index 61a6b78..2fe55bb 100644 --- a/ModuleConfig.cfc +++ b/ModuleConfig.cfc @@ -36,6 +36,8 @@ component { // By default, all csrf tokens have a life-span of 30 minutes. After 30 minutes, they expire and we aut-generate new ones. // If you do not want expiring tokens, then set this value to 0 rotationTimeout : 30, + // The interval in seconds within which, if a token's expiration is impending, we force generate new token for the user. + timeoutSkew : 60, // Enable the /cbcsrf/generate endpoint to generate cbcsrf tokens for secured users. enableEndpoint : false, // The WireBox mapping to use for the CacheStorage diff --git a/box.json b/box.json index a53f6fd..82100c5 100644 --- a/box.json +++ b/box.json @@ -1,6 +1,6 @@ { "name":"ColdBox Cross Site Request Forgery (CSRF)", - "version":"3.2.0", + "version":"3.3.0", "location":"https://downloads.ortussolutions.com/ortussolutions/coldbox-modules/cbcsrf/@build.version@/cbcsrf-@build.version@.zip", "author":"Ortus Solutions.com ", "slug":"cbcsrf", @@ -27,7 +27,7 @@ "cbstorages":"^3.0.0" }, "devDependencies":{ - "commandbox-boxlang":"*", + "commandbox-boxlang":"*", "commandbox-cfformat":"*", "commandbox-docbox":"*" }, @@ -47,11 +47,11 @@ "format:watch":"cfformat watch handlers/,interceptors/,models/,test-harness/tests/specs,ModuleConfig.cfc ./.cfformat.json", "format:check":"cfformat check handlers/,interceptors/,models/,test-harness/tests/specs,ModuleConfig.cfc", "install:dependencies":"install && cd test-harness && install", - "start:lucee" : "server start serverConfigFile=server-lucee@5.json", - "start:2021" : "server start serverConfigFile=server-adobe@2021.json", - "stop:lucee" : "server stop serverConfigFile=server-lucee@5.json", - "stop:2021" : "server stop serverConfigFile=server-adobe@2021.json", - "logs:lucee" : "server log serverConfigFile=server-lucee@5.json --follow", - "logs:2021" : "server log serverConfigFile=server-adobe@2021.json --follow" + "start:lucee":"server start serverConfigFile=server-lucee@5.json", + "start:2021":"server start serverConfigFile=server-adobe@2021.json", + "stop:lucee":"server stop serverConfigFile=server-lucee@5.json", + "stop:2021":"server stop serverConfigFile=server-adobe@2021.json", + "logs:lucee":"server log serverConfigFile=server-lucee@5.json --follow", + "logs:2021":"server log serverConfigFile=server-adobe@2021.json --follow" } } diff --git a/changelog.md b/changelog.md index 22a448d..a1abfef 100644 --- a/changelog.md +++ b/changelog.md @@ -9,6 +9,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- Marked `CBCsrf` as `threadSafe` so WireBox publishes the singleton only after + autowiring it. Concurrent requests hitting a cold singleton cache (right after a + framework reinit, for instance) could otherwise get a half-wired instance and fail + with `variable [CACHESTORAGE] doesn't exist`. + ## [3.2.0] - 2025-02-19 ### Added diff --git a/models/cbcsrf.cfc b/models/cbcsrf.cfc index e08bffc..1f7e35f 100644 --- a/models/cbcsrf.cfc +++ b/models/cbcsrf.cfc @@ -4,7 +4,7 @@ * --- * Service that encapsulates token security against cross site request forgery (csrf) */ -component accessors="true" singleton { +component accessors="true" singleton threadSafe { /* ********************************************************************* ** DI @@ -60,12 +60,7 @@ component accessors="true" singleton { // Is it a new key? !csrfData.keyExists( arguments.key ) || // Has the token expired? - ( - csrfData[ arguments.key ].expires != "never" && dateCompare( - now(), - csrfData[ arguments.key ].expires - ) == 1 - ) + tokenInExpiryRange( csrfData[ arguments.key ] ) ) { // Generate a new token csrfData[ arguments.key ] = { @@ -87,6 +82,23 @@ component accessors="true" singleton { return csrfData[ arguments.key ].token; } + + /** + * Determines if the token is within the expiry range + * + * @tokenData The token data struct + * + * @return True if the token is within the expiry range + */ + private boolean function tokenInExpiryRange( required struct tokenData ){ + if ( tokenData.expires == "never" ) { + return false; + } + + var secondsToExpiry = dateDiff( "s", now(), tokenData.expires ); + return secondsToExpiry <= variables.settings.timeoutSkew; + } + /** * Validates the given token against the same stored in the session for a specific key. * diff --git a/readme.md b/readme.md index 9f22483..6084706 100644 --- a/readme.md +++ b/readme.md @@ -74,6 +74,8 @@ moduleSettings = { // By default, all csrf tokens have a life-span of 30 minutes. After 30 minutes, they expire and we aut-generate new ones. // If you do not want expiring tokens, then set this value to 0 rotationTimeout : 30, + // The interval in seconds within which, if a token's expiration is impending, we force generate new token for the user. + timeoutSkew : 120, // Enable the /cbcsrf/generate endpoint to generate cbcsrf tokens for secured users. enableEndpoint : false, // The WireBox mapping to use for the CacheStorage