1
0
mirror of https://github.com/janeczku/calibre-web synced 2024-11-09 11:30:00 +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
# 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):
@ -36,19 +36,5 @@ class MyLoginManager(LoginManager):
return super(). _session_protection_failed()
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 timedelta
import hashlib
from flask import abort
from flask import current_app
@ -9,6 +10,8 @@ from flask import has_app_context
from flask import redirect
from flask import request
from flask import session
from itsdangerous import URLSafeSerializer
from flask.json.tag import TaggedJSONSerializer
from .config import AUTH_HEADER_NAME
from .config import COOKIE_DURATION
@ -32,8 +35,7 @@ from .signals import user_needs_refresh
from .signals import user_unauthorized
from .utils import _create_identifier
from .utils import _user_context_processor
from .utils import decode_cookie
from .utils import encode_cookie
from .utils import confirm_login
from .utils import expand_login_view
from .utils import login_url as make_login_url
from .utils import make_next_param
@ -323,7 +325,7 @@ class LoginManager:
if self._user_callback is None and self._request_callback is None:
raise Exception(
"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."
)
@ -361,7 +363,8 @@ class LoginManager:
elif header_name in request.headers:
header = request.headers[header_name]
user = self._load_user_from_header(header)
if not user:
self._update_request_context_with_user()
return self._update_request_context_with_user(user)
def _session_protection_failed(self):
@ -393,16 +396,32 @@ class LoginManager:
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
signer_kwargs = dict(
key_derivation="hmac", digest_method=staticmethod(hashlib.sha1)
)
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
user = None
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:
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
@ -461,7 +480,17 @@ class LoginManager:
duration = config.get("REMEMBER_COOKIE_DURATION", COOKIE_DURATION)
# 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):
duration = timedelta(seconds=duration)

View File

@ -74,10 +74,9 @@ def store_user_session():
_user = flask_session.get('_user_id', "")
_id = flask_session.get('_id', "")
_random = flask_session.get('_random', "")
if flask_session.get('_user_id', ""):
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())
user_session = User_Sessions(_user, _id, _random, expiry)
session.add(user_session)
@ -103,10 +102,12 @@ def delete_user_session(user_id, session_key):
log.exception(ex)
def check_user_session(user_id, session_key):
def check_user_session(user_id, session_key, random):
try:
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:
new_expiry = int((datetime.datetime.now() + datetime.timedelta(days=31)).timestamp())
if new_expiry - found.expiry > 86400:
@ -614,10 +615,13 @@ def migrate_Database(_session):
def clean_database(_session):
# Remove expired remote login tokens
now = datetime.datetime.now()
_session.query(RemoteAuthToken).filter(now > RemoteAuthToken.expiration).\
filter(RemoteAuthToken.token_type != 1).delete()
_session.commit()
try:
_session.query(RemoteAuthToken).filter(now > RemoteAuthToken.expiration).\
filter(RemoteAuthToken.token_type != 1).delete()
_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

View File

@ -40,7 +40,6 @@ def verify_password(username, password):
if user.name.lower() == "guest":
if config.config_anonbrowse == 1:
return user
limiter.check()
if config.config_login_type == constants.LOGIN_LDAP and services.ldap:
login_result, error = services.ldap.bind_user(user.name, password)
if login_result:
@ -48,9 +47,11 @@ def verify_password(username, password):
return user
if error is not None:
log.error(error)
elif check_password_hash(str(user.password), password):
[limiter.limiter.storage.clear(k.key) for k in limiter.current_limits]
return user
else:
limiter.check()
if check_password_hash(str(user.password), password):
[limiter.limiter.storage.clear(k.key) for k in limiter.current_limits]
return user
ip_address = request.headers.get('X-Forwarded-For', request.remote_addr)
log.warning('OPDS Login failed for user "%s" IP-address: %s', username, ip_address)
return None
@ -127,9 +128,13 @@ def load_user_from_reverse_proxy_header(req):
@lm.user_loader
def load_user(user_id, random, session_key):
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,
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:
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

File diff suppressed because one or more lines are too long