Adds token_validation_rs.go with requestState-aware variants of the
token validation path:
isUserAuthenticatedRS(rs) -> dispatches by provider
validateStandardTokensRS(rs) -> standard path (eliminates 17 RLocks)
validateAzureTokensRS(rs) -> Azure path (eliminates 10 RLocks)
validateGoogleTokensRS(rs) -> delegates to standard
validateTokenExpiryRS(rs, tok) -> shared expiry check (eliminates 4 RLocks)
middleware.ServeHTTP now calls isUserAuthenticatedRS(rs) on the hot
path. The pre-v1.0.20 non-RS variants are kept untouched for tests
and any future caller that doesn't have a captured snapshot.
Why
---
The standard validation path read SessionData via session.GetX() 17
times, with GetRefreshToken alone called 11 times (every "return
'needs refresh'" branch re-reads it). Each call acquires
sd.sessionMutex.RLock(). Under Yaegi each RLock costs ~1-5ms of
interpreter dispatch. The captured snapshot already lives on rs, so
the RS variants substitute direct struct field reads.
Per-request cost on the hot authenticated path
----------------------------------------------
ServeHTTP enters:
+ 1 RLock to populate rs (was 0)
Validation path:
Standard: was 17 RLocks, now 0
Azure: was 10 RLocks, now 0
processAuthorizedRequestRS:
was 4-6 GetX calls, now 0 (already in v1.0.19)
Net: ~22-27 fewer Yaegi-dispatched RLock acquisitions per authenticated
request on the hot path.
Caveats
-------
* Refresh / expired / callback paths still use the non-RS validators
because they can mutate session state between validation and use.
* The RS variants are by-design line-for-line equivalents of the
originals. If logic in the originals changes, the RS variants need
matching updates. This is acceptable for now; a future refactor
could collapse them once the non-RS callers are gone.
All tests pass with -race; golangci-lint clean.