1
0
mirror of https://github.com/janeczku/calibre-web synced 2025-01-11 18:00:30 +00:00

Update login routine (remember me working)

This commit is contained in:
Ozzie Isaacs 2024-07-16 20:44:12 +02:00
parent c09e1ed203
commit 1d3a768dfe
5 changed files with 937 additions and 17947 deletions

View File

@ -19,12 +19,12 @@
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import ast
import hashlib
from .cw_login import LoginManager
from flask import session
from .cw_login import LoginManager, confirm_login
from flask import session, current_app
from .cw_login.utils import decode_cookie
from .cw_login.signals import user_loaded_from_cookie
class MyLoginManager(LoginManager): class MyLoginManager(LoginManager):
@ -36,19 +36,5 @@ class MyLoginManager(LoginManager):
return super(). _session_protection_failed() return super(). _session_protection_failed()
return False return False
def _load_user_from_remember_cookie(self, cookie):
user_id = decode_cookie(cookie)
if user_id is not None:
session["_user_id"] = user_id
session["_fresh"] = False
user = None
if self._user_callback:
user = self._user_callback(user_id, None, None)
if user is not None:
app = current_app._get_current_object()
user_loaded_from_cookie.send(app, user=user)
# if session was restored from remember me cookie make login valid
confirm_login()
return user
return None

View File

@ -1,5 +1,6 @@
from datetime import datetime from datetime import datetime
from datetime import timedelta from datetime import timedelta
import hashlib
from flask import abort from flask import abort
from flask import current_app from flask import current_app
@ -9,6 +10,8 @@ from flask import has_app_context
from flask import redirect from flask import redirect
from flask import request from flask import request
from flask import session from flask import session
from itsdangerous import URLSafeSerializer
from flask.json.tag import TaggedJSONSerializer
from .config import AUTH_HEADER_NAME from .config import AUTH_HEADER_NAME
from .config import COOKIE_DURATION from .config import COOKIE_DURATION
@ -32,8 +35,7 @@ from .signals import user_needs_refresh
from .signals import user_unauthorized from .signals import user_unauthorized
from .utils import _create_identifier from .utils import _create_identifier
from .utils import _user_context_processor from .utils import _user_context_processor
from .utils import decode_cookie from .utils import confirm_login
from .utils import encode_cookie
from .utils import expand_login_view from .utils import expand_login_view
from .utils import login_url as make_login_url from .utils import login_url as make_login_url
from .utils import make_next_param from .utils import make_next_param
@ -323,7 +325,7 @@ class LoginManager:
if self._user_callback is None and self._request_callback is None: if self._user_callback is None and self._request_callback is None:
raise Exception( raise Exception(
"Missing user_loader or request_loader. Refer to " "Missing user_loader or request_loader. Refer to "
"http://flask-login.readthedocs.io/#how-it-works " "https://flask-login.readthedocs.io/#how-it-works "
"for more info." "for more info."
) )
@ -361,7 +363,8 @@ class LoginManager:
elif header_name in request.headers: elif header_name in request.headers:
header = request.headers[header_name] header = request.headers[header_name]
user = self._load_user_from_header(header) user = self._load_user_from_header(header)
if not user:
self._update_request_context_with_user()
return self._update_request_context_with_user(user) return self._update_request_context_with_user(user)
def _session_protection_failed(self): def _session_protection_failed(self):
@ -393,16 +396,32 @@ class LoginManager:
return False return False
def _load_user_from_remember_cookie(self, cookie): def _load_user_from_remember_cookie(self, cookie):
user_id = decode_cookie(cookie) signer_kwargs = dict(
if user_id is not None: key_derivation="hmac", digest_method=staticmethod(hashlib.sha1)
session["_user_id"] = user_id )
try:
remember_dict = URLSafeSerializer(
current_app.secret_key,
salt="remember",
serializer=TaggedJSONSerializer(),
signer_kwargs=signer_kwargs,
).loads(cookie)
except Exception:
return None
if remember_dict['user'] is not None:
session["_user_id"] = remember_dict['user']
if "_random" not in session:
session["_random"] = remember_dict['random']
session["_fresh"] = False session["_fresh"] = False
user = None user = None
if self._user_callback: if self._user_callback:
user = self._user_callback(user_id) user = self._user_callback(remember_dict['user'], session["_random"], None)
if user is not None: if user is not None:
app = current_app._get_current_object() app = current_app._get_current_object()
user_loaded_from_cookie.send(app, user=user) user_loaded_from_cookie.send(app, user=user)
# if session was restored from remember me cookie make login valid
confirm_login()
return user return user
return None return None
@ -461,7 +480,17 @@ class LoginManager:
duration = config.get("REMEMBER_COOKIE_DURATION", COOKIE_DURATION) duration = config.get("REMEMBER_COOKIE_DURATION", COOKIE_DURATION)
# prepare data # prepare data
data = encode_cookie(str(session["_user_id"])) max_age = int(current_app.permanent_session_lifetime.total_seconds())
signer_kwargs = dict(
key_derivation="hmac", digest_method=staticmethod(hashlib.sha1)
)
# save
data = URLSafeSerializer(
current_app.secret_key,
salt="remember",
serializer=TaggedJSONSerializer(),
signer_kwargs=signer_kwargs,
).dumps({"user":session["_user_id"], "random":session["_random"]})
if isinstance(duration, int): if isinstance(duration, int):
duration = timedelta(seconds=duration) duration = timedelta(seconds=duration)

View File

@ -74,10 +74,9 @@ def store_user_session():
_user = flask_session.get('_user_id', "") _user = flask_session.get('_user_id', "")
_id = flask_session.get('_id', "") _id = flask_session.get('_id', "")
_random = flask_session.get('_random', "") _random = flask_session.get('_random', "")
if flask_session.get('_user_id', ""): if flask_session.get('_user_id', ""):
try: try:
if not check_user_session(_user, _id): if not check_user_session(_user, _id, _random):
expiry = int((datetime.datetime.now() + datetime.timedelta(days=31)).timestamp()) expiry = int((datetime.datetime.now() + datetime.timedelta(days=31)).timestamp())
user_session = User_Sessions(_user, _id, _random, expiry) user_session = User_Sessions(_user, _id, _random, expiry)
session.add(user_session) session.add(user_session)
@ -103,10 +102,12 @@ def delete_user_session(user_id, session_key):
log.exception(ex) log.exception(ex)
def check_user_session(user_id, session_key): def check_user_session(user_id, session_key, random):
try: try:
found = session.query(User_Sessions).filter(User_Sessions.user_id==user_id, found = session.query(User_Sessions).filter(User_Sessions.user_id==user_id,
User_Sessions.session_key==session_key).one_or_none() User_Sessions.session_key==session_key,
User_Sessions.random == random,
).one_or_none()
if found is not None: if found is not None:
new_expiry = int((datetime.datetime.now() + datetime.timedelta(days=31)).timestamp()) new_expiry = int((datetime.datetime.now() + datetime.timedelta(days=31)).timestamp())
if new_expiry - found.expiry > 86400: if new_expiry - found.expiry > 86400:
@ -614,10 +615,13 @@ def migrate_Database(_session):
def clean_database(_session): def clean_database(_session):
# Remove expired remote login tokens # Remove expired remote login tokens
now = datetime.datetime.now() now = datetime.datetime.now()
try:
_session.query(RemoteAuthToken).filter(now > RemoteAuthToken.expiration).\ _session.query(RemoteAuthToken).filter(now > RemoteAuthToken.expiration).\
filter(RemoteAuthToken.token_type != 1).delete() filter(RemoteAuthToken.token_type != 1).delete()
_session.commit() _session.commit()
except exc.OperationalError: # Database is not writeable
print('Settings database is not writeable. Exiting...')
sys.exit(2)
# Save downloaded books per user in calibre-web's own database # Save downloaded books per user in calibre-web's own database

View File

@ -40,7 +40,6 @@ def verify_password(username, password):
if user.name.lower() == "guest": if user.name.lower() == "guest":
if config.config_anonbrowse == 1: if config.config_anonbrowse == 1:
return user return user
limiter.check()
if config.config_login_type == constants.LOGIN_LDAP and services.ldap: if config.config_login_type == constants.LOGIN_LDAP and services.ldap:
login_result, error = services.ldap.bind_user(user.name, password) login_result, error = services.ldap.bind_user(user.name, password)
if login_result: if login_result:
@ -48,7 +47,9 @@ def verify_password(username, password):
return user return user
if error is not None: if error is not None:
log.error(error) log.error(error)
elif check_password_hash(str(user.password), password): else:
limiter.check()
if check_password_hash(str(user.password), password):
[limiter.limiter.storage.clear(k.key) for k in limiter.current_limits] [limiter.limiter.storage.clear(k.key) for k in limiter.current_limits]
return user return user
ip_address = request.headers.get('X-Forwarded-For', request.remote_addr) ip_address = request.headers.get('X-Forwarded-For', request.remote_addr)
@ -127,10 +128,14 @@ def load_user_from_reverse_proxy_header(req):
@lm.user_loader @lm.user_loader
def load_user(user_id, random, session_key): def load_user(user_id, random, session_key):
user = ub.session.query(ub.User).filter(ub.User.id == int(user_id)).first() user = ub.session.query(ub.User).filter(ub.User.id == int(user_id)).first()
if random and session_key: if session_key:
entry = ub.session.query(ub.User_Sessions).filter(ub.User_Sessions.random == random, entry = ub.session.query(ub.User_Sessions).filter(ub.User_Sessions.random == random,
ub.User_Sessions.session_key == session_key).first() ub.User_Sessions.session_key == session_key).first()
if not entry or entry.user_id != user.id: if not entry or entry.user_id != user.id:
return None return None
elif random:
entry = ub.session.query(ub.User_Sessions).filter(ub.User_Sessions.random == random).first()
if not entry or entry.user_id != user.id:
return None
return user return user

File diff suppressed because one or more lines are too long