From 7f37ccfe83382f6f3f12733dbddacff736a48535 Mon Sep 17 00:00:00 2001 From: zoffline Date: Sun, 8 Nov 2020 13:43:49 -0500 Subject: [PATCH] Nits, niceties, randomized secret --- cdn/static/web/launcher/signup.html | 6 +++ cdn/static/web/launcher/user_home.html | 2 - standalone.py | 7 +++- zwift_offline.py | 56 +++++++++++++------------- 4 files changed, 38 insertions(+), 33 deletions(-) diff --git a/cdn/static/web/launcher/signup.html b/cdn/static/web/launcher/signup.html index 672e565..4be58ae 100644 --- a/cdn/static/web/launcher/signup.html +++ b/cdn/static/web/launcher/signup.html @@ -24,6 +24,12 @@ +
+
+ + +
+
diff --git a/cdn/static/web/launcher/user_home.html b/cdn/static/web/launcher/user_home.html index 59ebd20..ed8315a 100644 --- a/cdn/static/web/launcher/user_home.html +++ b/cdn/static/web/launcher/user_home.html @@ -19,8 +19,6 @@ Logout
- {% else %} - Upload {% endif %}
diff --git a/standalone.py b/standalone.py index 044c629..a0d7589 100755 --- a/standalone.py +++ b/standalone.py @@ -298,11 +298,14 @@ class UDPHandler(socketserver.BaseRequestHandler): data = self.request[0] socket = self.request[1] recv = udp_node_msgs_pb2.ClientToServer() - + try: recv.ParseFromString(data[:-4]) except: - recv.ParseFromString(data[3:-4]) + try: + recv.ParseFromString(data[3:-4]) + except: + return client_address = self.client_address player_id = recv.player_id diff --git a/zwift_offline.py b/zwift_offline.py index 1a5ecbb..575b371 100644 --- a/zwift_offline.py +++ b/zwift_offline.py @@ -76,6 +76,7 @@ DATABASE_CUR_VER = 2 # For auth server AUTOLAUNCH_FILE = "%s/auto_launch.txt" % STORAGE_DIR SERVER_IP_FILE = "%s/server-ip.txt" % STORAGE_DIR +SECRET_KEY_FILE = "%s/secret-key.txt" % STORAGE_DIR MULTIPLAYER = False if os.path.exists("%s/multiplayer.txt" % STORAGE_DIR): MULTIPLAYER = True @@ -87,7 +88,11 @@ AUTH_PATH = "%s/auth.db" % STORAGE_DIR app = Flask(__name__, static_folder='%s/cdn/gameassets' % SCRIPT_DIR, static_url_path='/gameassets', template_folder='%s/cdn/static/web/launcher' % SCRIPT_DIR) app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///{db}'.format(db=AUTH_PATH) app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False -app.config['SECRET_KEY'] = 'zoffline' +if not os.path.exists(SECRET_KEY_FILE): + with open(SECRET_KEY_FILE, 'wb') as f: + f.write(os.urandom(16)) +with open(SECRET_KEY_FILE, 'rb') as f: + app.config['SECRET_KEY'] = f.read() app.config['MAX_CONTENT_LENGTH'] = 1024 * 1024 db = SQLAlchemy(app) @@ -240,12 +245,16 @@ def signup(): if request.method == "POST": username = request.form['username'] password = request.form['password'] + confirm_password = request.form['confirm_password'] first_name = request.form['first_name'] last_name = request.form['last_name'] - if not (username and password and first_name and last_name): + if not (username and password and confirm_password and first_name and last_name): flash("All fields are required.") return redirect(url_for('signup')) + if password != confirm_password: + flash("Passwords did not match.") + return redirect(url_for('signup')) hashed_pwd = generate_password_hash(password, 'sha256') @@ -301,7 +310,7 @@ def upload(username): os.makedirs(profile_dir) except IOError as e: logger.error("failed to create profile dir (%s): %s", profile_dir, str(e)) - sys.exit(1) + return '', 500 if request.method == 'POST': uploaded_file = request.files['file'] @@ -327,7 +336,7 @@ def upload(username): stat = os.stat(token_file) token = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(stat.st_mtime)) - return render_template("upload.html", username=username, profile=profile, name=name, token=token) + return render_template("upload.html", username=current_user.username, profile=profile, name=name, token=token) @app.route("/logout/") @@ -514,7 +523,7 @@ def api_profiles_me(): os.makedirs(profile_dir) except IOError as e: logger.error("failed to create profile dir (%s): %s", profile_dir, str(e)) - sys.exit(1) + return '', 500 profile = profile_pb2.Profile() profile_file = '%s/profile.bin' % profile_dir if not os.path.isfile(profile_file): @@ -527,6 +536,17 @@ def api_profiles_me(): with open(profile_file, 'rb') as fd: profile.ParseFromString(fd.read()) if MULTIPLAYER: + # For newly added existing profiles, User's player id likely differs from profile's player id. + # If there's existing data in db for this profile, update it for the newly assigned player id. + # XXX: Users can maliciously abuse this by intentionally uploading a profile with another user's current player id. + # However, without it, anyone "upgrading" to multiplayer mode will lose their existing data. + # TODO: need a warning in README that switching to multiplayer mode and back to single player will lose your existing data. + if profile.id != profile_id: + cur = g.db.cursor() + cur.execute('UPDATE activity SET player_id = ? WHERE player_id = ?', (str(profile_id), str(profile.id))) + cur.execute('UPDATE goal SET player_id = ? WHERE player_id = ?', (str(profile_id), str(profile.id))) + cur.execute('UPDATE segment_result SET player_id = ? WHERE player_id = ?', (str(profile_id), str(profile.id))) + g.db.commit() profile.id = profile_id elif current_user.player_id != profile.id: # Update AnonUser's player_id to match @@ -1328,28 +1348,6 @@ def static_web_launcher(filename): return render_template(filename) -def check_columns(): - time.sleep(3) - result = db.engine.execute(sqlalchemy.text("PRAGMA table_info(user)")) - should_have_columns = User.metadata.tables['user'].columns - current_columns = list() - for row in result: - current_columns.append(row[1]) - for column in should_have_columns: - if not column.name in current_columns: - nulltext = None - if column.nullable: - nulltext = "NULL" - else: - nulltext = "NOT NULL" - defaulttext = None - if column.default == None: - defaulttext = "" - else: - defaulttext = " DEFAULT %s" % column.default.arg - db.engine.execute(sqlalchemy.text("ALTER TABLE user ADD %s %s %s%s;" % (column.name, str(column.type), nulltext, defaulttext))) - - def run_standalone(passedOnline, passedGhostsEnabled, passedSaveGhost, passedPlayerUpdateQueue): global online global ghostsEnabled @@ -1363,11 +1361,11 @@ def run_standalone(passedOnline, passedGhostsEnabled, passedSaveGhost, passedPla login_manager = LoginManager() login_manager.login_view = 'login' login_manager.session_protection = None - db.create_all(app=app) - db.session.commit() if not MULTIPLAYER: login_manager.anonymous_user = AnonUser login_manager.init_app(app) + db.create_all(app=app) + db.session.commit() @login_manager.user_loader def load_user(uid):