Skip to content

Commit 22c1ed2

Browse files
Fix generic OAuth login error 'NoneType object has no attribute token'
- When using generic OAuth (e.g. Authentik), the `register_user_from_generic_oauth` function creates a `GenericOIDCSession` with an explicit token but no blueprint to avoid race conditions. - Flask-Dance's `OAuth2Session` assumes a blueprint is always present and tries to access `self.blueprint.token` in its `token` property, causing a crash when blueprint is None. - This fix overrides the `token` property in `GenericOIDCSession` to return the explicit token if available, and adds a deleter to handle Flask-Dance's initialization logic. - This ensures the session works correctly even when used independently of a blueprint for user registration. Fixes #715 Fixes #800
1 parent 3f24e0e commit 22c1ed2

2 files changed

Lines changed: 43 additions & 1 deletion

File tree

CONTRIBUTORS

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ Copyright (C) 2024-2025 Calibre-Web Automated contributors
1212
# Upstream Contributors (janeczku/calibre-web)
1313

1414
- OzzieIsaacs (anon) (2758 commits)
15-
- Ozzie Isaacs (anon) (263 commits)
15+
- Ozzie Isaacs (anon) (264 commits)
1616
- cbartondock (96 commits)
1717
- idalin (69 commits)
1818
- cervinko (68 commits)

cps/oauth_bb.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,15 +69,49 @@ class GenericOIDCSession(BaseOAuth2Session):
6969
1. SSL verification based on OAUTH_SSL_STRICT setting
7070
2. Lenient scope validation (order-independent comparison)
7171
3. Normalizes scope in token response to prevent mismatch warnings
72+
4. Explicit token usage without blueprint (Issue #715)
7273
"""
7374
def __init__(self, *args, **kwargs):
75+
self._explicit_token = kwargs.get('token')
7476
super().__init__(*args, **kwargs)
7577
# Configure SSL verification for all requests
7678
self.verify = constants.OAUTH_SSL_STRICT
7779

7880
# Register compliance hook to normalize scope in token response (Issue #715)
7981
# This prevents "scope_changed" warnings when Authentik returns scopes in different order
8082
self.register_compliance_hook('access_token_response', self._normalize_token_scope)
83+
84+
@property
85+
def token(self):
86+
"""
87+
Override token property to support explicit token usage without blueprint.
88+
Flask-Dance's token property crashes if blueprint is None.
89+
"""
90+
if self._explicit_token:
91+
return self._explicit_token
92+
return self.blueprint.token
93+
94+
@token.setter
95+
def token(self, value):
96+
"""
97+
Allow setting token (required for token refresh/update).
98+
This mimics cached_property behavior by shadowing the blueprint token.
99+
"""
100+
self._explicit_token = value
101+
102+
@token.deleter
103+
def token(self):
104+
"""
105+
Handle deletion of token.
106+
Flask-Dance deletes the token in __init__ to ensure it uses the blueprint's token.
107+
We want to preserve our explicit token if it was passed, so we do nothing here
108+
if we are in explicit mode.
109+
"""
110+
# If we are not in explicit mode, we might want to clear something?
111+
# But since we store everything in _explicit_token or delegate to blueprint,
112+
# and _explicit_token is what we want to keep, we can just ignore the delete
113+
# if it's coming from Flask-Dance's init.
114+
pass
81115

82116
def _normalize_token_scope(self, response):
83117
"""
@@ -116,6 +150,14 @@ def request(self, method, url, *args, **kwargs):
116150
"""Override request to ensure SSL verification is applied"""
117151
if 'verify' not in kwargs:
118152
kwargs['verify'] = self.verify
153+
154+
# If we have an explicit token and no blueprint, we need to handle request manually
155+
# to avoid Flask-Dance's dependency on blueprint
156+
if self._explicit_token and not self.blueprint:
157+
# Bypass Flask-Dance's request method which requires blueprint
158+
# Call requests_oauthlib.OAuth2Session.request directly
159+
return super(BaseOAuth2Session, self).request(method, url, *args, **kwargs)
160+
119161
return super().request(method, url, *args, **kwargs)
120162

121163

0 commit comments

Comments
 (0)