diff --git a/jupyter_server/auth/identity.py b/jupyter_server/auth/identity.py index fc4b02992..19801ac57 100644 --- a/jupyter_server/auth/identity.py +++ b/jupyter_server/auth/identity.py @@ -10,6 +10,7 @@ import binascii import datetime +import hmac import json import os import re @@ -28,6 +29,9 @@ from .security import passwd_check, set_password from .utils import get_anonymous_username +if t.TYPE_CHECKING: + import hmac + _non_alphanum = re.compile(r"[^A-Za-z0-9]") @@ -610,6 +614,18 @@ def logout_available(self): """Whether a LogoutHandler is needed.""" return True + def cookie_secret_hook(self, h: hmac.HMAC) -> hmac.HMAC: + """Update cookie secret input + + Subclasses may call `h.update()` with any credentials that, + when changed, should invalidate existing cookies, such as a + password. + + The updated hashlib object should be returned. + + """ + return h + class PasswordIdentityProvider(IdentityProvider): """A password identity provider.""" @@ -740,6 +756,14 @@ def validate_security( self.log.critical(_i18n("\t$ python -m jupyter_server.auth password")) sys.exit(1) + def cookie_secret_hook(self, h: hmac.HMAC) -> hmac.HMAC: + """Include password in cookie secret. + + This makes it so changing the password invalidates cookies. + """ + h.update(self.hashed_password.encode()) + return h + class LegacyIdentityProvider(PasswordIdentityProvider): """Legacy IdentityProvider for use with custom LoginHandlers diff --git a/jupyter_server/serverapp.py b/jupyter_server/serverapp.py index 1afbef4d0..045c3c324 100644 --- a/jupyter_server/serverapp.py +++ b/jupyter_server/serverapp.py @@ -1169,6 +1169,7 @@ def _default_cookie_secret(self) -> bytes: self._write_cookie_secret_file(key) h = hmac.new(key, digestmod=hashlib.sha256) h.update(self.password.encode()) + h = self.identity_provider.cookie_secret_hook(h) return h.digest() def _write_cookie_secret_file(self, secret: bytes) -> None: