From c6846cb71a7c292e0cd6fa591575c1a5cc50ccbd Mon Sep 17 00:00:00 2001 From: Emin Martinian Date: Mon, 3 Nov 2025 09:57:17 -0500 Subject: [PATCH 1/3] Fix bug in issue #1566 based on ideas in #1567. This fixes a bug where changing the password still allowed login by a previous cookie even if the server was restarted. --- jupyter_server/auth/identity.py | 21 +++++++++++++++++++++ jupyter_server/serverapp.py | 1 + 2 files changed, 22 insertions(+) diff --git a/jupyter_server/auth/identity.py b/jupyter_server/auth/identity.py index fc4b02992..668f97518 100644 --- a/jupyter_server/auth/identity.py +++ b/jupyter_server/auth/identity.py @@ -10,6 +10,7 @@ import binascii import datetime +import hashlib import json import os import re @@ -610,6 +611,18 @@ def logout_available(self): """Whether a LogoutHandler is needed.""" return True + def cookie_secret_hook(self, h: hashlib._Hash) -> hashlib._Hash: + """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 +753,14 @@ def validate_security( self.log.critical(_i18n("\t$ python -m jupyter_server.auth password")) sys.exit(1) + def cookie_secret_hook(self, h: hashlib._Hash) -> hashlib._Hash: + """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: From 68aef60942f698ee0aefa068e4542a29a80b2e1b Mon Sep 17 00:00:00 2001 From: Emin Martinian Date: Mon, 3 Nov 2025 10:38:09 -0500 Subject: [PATCH 2/3] fix type hint --- jupyter_server/auth/identity.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jupyter_server/auth/identity.py b/jupyter_server/auth/identity.py index 668f97518..b46ae2d39 100644 --- a/jupyter_server/auth/identity.py +++ b/jupyter_server/auth/identity.py @@ -10,7 +10,7 @@ import binascii import datetime -import hashlib +import hmac import json import os import re @@ -611,7 +611,7 @@ def logout_available(self): """Whether a LogoutHandler is needed.""" return True - def cookie_secret_hook(self, h: hashlib._Hash) -> hashlib._Hash: + def cookie_secret_hook(self, h: hmac.HMAC) -> hmac.HMAC: """Update cookie secret input Subclasses may call `h.update()` with any credentials that, @@ -753,7 +753,7 @@ def validate_security( self.log.critical(_i18n("\t$ python -m jupyter_server.auth password")) sys.exit(1) - def cookie_secret_hook(self, h: hashlib._Hash) -> hashlib._Hash: + def cookie_secret_hook(self, h: hmac.HMAC) -> hmac.HMAC: """Include password in cookie secret. This makes it so changing the password invalidates cookies. From d8aba18d15d2f29e918bee01d594648cd30ce629 Mon Sep 17 00:00:00 2001 From: Emin Martinian Date: Mon, 3 Nov 2025 10:42:00 -0500 Subject: [PATCH 3/3] import hmac only for type checking to fix lint --- jupyter_server/auth/identity.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/jupyter_server/auth/identity.py b/jupyter_server/auth/identity.py index b46ae2d39..19801ac57 100644 --- a/jupyter_server/auth/identity.py +++ b/jupyter_server/auth/identity.py @@ -29,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]")