feat: Allow using OICD auth cache instead of session

This commit is contained in:
Hristo Kapanakov 2025-07-18 21:02:21 +03:00
commit f9294f2634
4 changed files with 63 additions and 1 deletions

View file

@ -113,6 +113,7 @@ For usage, see [Usage - OpenID Connect](../authentication/oidc-v2.md)
| OIDC_GROUPS_CLAIM | groups | Optional if not using `OIDC_USER_GROUP` or `OIDC_ADMIN_GROUP`. This is the claim Mealie will request from your IdP and will use to compare to `OIDC_USER_GROUP` or `OIDC_ADMIN_GROUP` to allow the user to log in to Mealie or is set as an admin. **Your IdP must be configured to grant this claim** | | OIDC_GROUPS_CLAIM | groups | Optional if not using `OIDC_USER_GROUP` or `OIDC_ADMIN_GROUP`. This is the claim Mealie will request from your IdP and will use to compare to `OIDC_USER_GROUP` or `OIDC_ADMIN_GROUP` to allow the user to log in to Mealie or is set as an admin. **Your IdP must be configured to grant this claim** |
| OIDC_SCOPES_OVERRIDE | None | Advanced configuration used to override the scopes requested from the IdP. **Most users won't need to change this**. At a minimum, 'openid profile email' are required. | | OIDC_SCOPES_OVERRIDE | None | Advanced configuration used to override the scopes requested from the IdP. **Most users won't need to change this**. At a minimum, 'openid profile email' are required. |
| OIDC_TLS_CACERTFILE | None | File path to Certificate Authority used to verify server certificate (e.g. `/path/to/ca.crt`) | | OIDC_TLS_CACERTFILE | None | File path to Certificate Authority used to verify server certificate (e.g. `/path/to/ca.crt`) |
| OIDC_USE_AUTH_CACHE | False | If `True`, OIDC authentication will use server cache instead of session to store its temporary data. |
### OpenAI ### OpenAI

View file

@ -338,6 +338,7 @@ class AppSettings(AppLoggingSettings):
OIDC_GROUPS_CLAIM: str | None = "groups" OIDC_GROUPS_CLAIM: str | None = "groups"
OIDC_SCOPES_OVERRIDE: str | None = None OIDC_SCOPES_OVERRIDE: str | None = None
OIDC_TLS_CACERTFILE: str | None = None OIDC_TLS_CACERTFILE: str | None = None
OIDC_USE_AUTH_CACHE: bool = False
@property @property
def OIDC_REQUIRES_GROUP_CLAIM(self) -> bool: def OIDC_REQUIRES_GROUP_CLAIM(self) -> bool:

View file

@ -19,6 +19,8 @@ from mealie.routes._base.routers import UserAPIRouter
from mealie.schema.user import PrivateUser from mealie.schema.user import PrivateUser
from mealie.schema.user.auth import CredentialsRequestForm from mealie.schema.user.auth import CredentialsRequestForm
from .auth_cache import AuthCache
public_router = APIRouter(tags=["Users: Authentication"]) public_router = APIRouter(tags=["Users: Authentication"])
user_router = UserAPIRouter(tags=["Users: Authentication"]) user_router = UserAPIRouter(tags=["Users: Authentication"])
logger = root_logger.get_logger("auth") logger = root_logger.get_logger("auth")
@ -27,7 +29,10 @@ remember_me_duration = timedelta(days=14)
settings = get_app_settings() settings = get_app_settings()
if settings.OIDC_READY: if settings.OIDC_READY:
oauth = OAuth() cache = None
if settings.OIDC_USE_AUTH_CACHE:
cache = AuthCache()
oauth = OAuth(cache=cache)
scope = None scope = None
if settings.OIDC_SCOPES_OVERRIDE: if settings.OIDC_SCOPES_OVERRIDE:
scope = settings.OIDC_SCOPES_OVERRIDE scope = settings.OIDC_SCOPES_OVERRIDE

View file

@ -0,0 +1,55 @@
import time
try:
import cPickle as pickle
except ImportError:
import pickle
class AuthCache:
def __init__(self, threshold=500, default_timeout=300):
self.default_timeout = default_timeout
self._cache = {}
self.clear = self._cache.clear
self._threshold = threshold
def _prune(self):
if len(self._cache) > self._threshold:
now = time.time()
toremove = []
for idx, (key, (expires, _)) in enumerate(self._cache.items()):
if (expires != 0 and expires <= now) or idx % 3 == 0:
toremove.append(key)
for key in toremove:
self._cache.pop(key, None)
def _normalize_timeout(self, timeout):
if timeout is None:
timeout = self.default_timeout
if timeout > 0:
timeout = time.time() + timeout
return timeout
async def get(self, key):
try:
expires, value = self._cache[key]
if expires == 0 or expires > time.time():
return pickle.loads(value)
except (KeyError, pickle.PickleError):
return None
async def set(self, key, value, timeout=None):
expires = self._normalize_timeout(timeout)
self._prune()
self._cache[key] = (expires, pickle.dumps(value, pickle.HIGHEST_PROTOCOL))
return True
async def delete(self, key):
return self._cache.pop(key, None) is not None
async def has(self, key):
try:
expires, value = self._cache[key]
return expires == 0 or expires > time.time()
except KeyError:
return False