removed secrets from the code (reom from chatgpt)

This commit is contained in:
2026-05-14 21:16:51 +02:00
parent ff9056fece
commit 7e64c5b4cf
7 changed files with 125 additions and 63 deletions
+45
View File
@@ -7,3 +7,48 @@ install
2. create subfolder "instance" to store database file
3. for an initial instance: run python3 initialze_data.py, with the python from the virtual-env
4. add the below config to your apache2 enabled site
## Secrets and configuration
LAPP does not store runtime secrets in the source code. Configure them with
environment variables in production:
```sh
export LAPP_SECRET_KEY="replace-with-a-long-random-secret"
export LAPP_APPLICATION_KEY="replace-with-another-long-random-secret"
export LAPP_DATABASE_URI="sqlite:///app.db"
```
`LAPP_SECRET_KEY` signs Flask sessions and auto-login cookies. Keep it stable:
changing it logs users out and invalidates existing auto-login cookies.
`LAPP_APPLICATION_KEY` is used by `/api/validate_key`.
`LAPP_DATABASE_URI` is optional. If it is not set, LAPP uses `sqlite:///app.db`.
For local development, if `LAPP_SECRET_KEY` or `LAPP_APPLICATION_KEY` is not set,
the app creates stable random secrets in:
```text
instance/secret_key
instance/application_key
```
The `instance/` directory is ignored by git, so these generated secrets should
not be committed.
## Initial data secrets
`initialize_data.py` creates initial users and groups only when the database has
no users yet. You can provide the initial passwords and group secrets with:
```sh
export LAPP_INITIAL_ADMIN_PASSWORD="replace-me"
export LAPP_INITIAL_ADMIN_GROUP="replace-me"
export LAPP_INITIAL_USER_PASSWORD="replace-me"
export LAPP_INITIAL_USER_GROUP="replace-me"
./venv/bin/python initialize_data.py
```
If these variables are not set, `initialize_data.py` generates random values and
prints them once when it creates the initial data.
+2 -2
View File
@@ -1,4 +1,4 @@
from flask import Blueprint, render_template, redirect, url_for, request
from flask import Blueprint, render_template, redirect, url_for, request, current_app
from flask_login import login_required, current_user, login_user
from models import db
from models import User, Item
@@ -14,7 +14,7 @@ def validate_key():
result['data'] = ''
if "key" in request.form:
key = request.form["key"]
if key == 'NogNietNodigHier':
if key == current_app.config["APPLICATION_KEY"]:
result['status'] = 1
else:
result['message'] = 'Unknown Application-Key'
+15 -44
View File
@@ -1,64 +1,35 @@
from flask import Blueprint, render_template, request, redirect, url_for, flash, make_response
from flask import Blueprint, render_template, request, redirect, url_for, flash, make_response, current_app
from flask_login import login_user, logout_user, login_required, current_user
from models import db
from models import User
from log import Log
import pickle
from cryptography.fernet import Fernet
import datetime
from itsdangerous import BadSignature, SignatureExpired, URLSafeTimedSerializer
auth_bp = Blueprint("auth", __name__)
# =====================================================
# SECRET KEY (only for this program)
# =====================================================
# Generate once using: Fernet.generate_key()
_SECRET_KEY = b'YFK7QCyTzhyLO4vqrnRxvDAI5uu8mXEYrInEjbRoQgs='
fernet = Fernet(_SECRET_KEY)
AUTOLOGIN_MAX_AGE_SECONDS = 7 * 24 * 60 * 60
AUTOLOGIN_SALT = "lapp-autologin"
# =====================================================
# ENCRYPT
# =====================================================
def encrypt_object(obj) -> bytes:
"""
Encrypt any Python object and return encrypted bytes.
"""
serialized = pickle.dumps(obj)
encrypted = fernet.encrypt(serialized)
return encrypted
# =====================================================
# DECRYPT
# =====================================================
def decrypt_object(encrypted_data: bytes):
"""
Decrypt bytes back into the original Python object.
"""
decrypted = fernet.decrypt(encrypted_data)
obj = pickle.loads(decrypted)
return obj
def autologin_serializer():
return URLSafeTimedSerializer(current_app.config["SECRET_KEY"], salt=AUTOLOGIN_SALT)
def create_cookie(userid):
cookie_data = {
"user": userid,
"datetime": datetime.datetime.now()
"user": userid
}
return encrypt_object(cookie_data).decode('utf-8')
return autologin_serializer().dumps(cookie_data)
def is_cookie_ok_to_autologin(cookie_content):
user = 0
autologin = False
if not cookie_content:
return 0
try:
cookie_data = decrypt_object(cookie_content)
user = cookie_data["user"]
dt = cookie_data["datetime"]
lastweek = datetime.datetime.now() - datetime.timedelta(days=7)
autologin = dt > lastweek
except:
cookie_data = autologin_serializer().loads(cookie_content,
max_age=AUTOLOGIN_MAX_AGE_SECONDS)
return int(cookie_data["user"])
except (BadSignature, SignatureExpired, KeyError, TypeError, ValueError):
Log.info("Cookie error")
if autologin:
return user
return 0
#. @auth_bp.route("/", methods=["GET", "POST"])
+21 -5
View File
@@ -1,25 +1,41 @@
from lapp import create_app
import os
import secrets
import sqlalchemy as sa
from models import db, User, ListOfItems, Shared, Item, Group
def initial_secret(env_name):
return os.environ.get(env_name) or secrets.token_urlsafe(18)
if __name__ == "__main__":
app = create_app()
app.app_context().push()
query = sa.select(User)
users = db.session.scalars(query).all()
if len(users) == 0:
g = Group(secret="This is the group just for the admin")
admin_group_secret = initial_secret("LAPP_INITIAL_ADMIN_GROUP")
user_group_secret = initial_secret("LAPP_INITIAL_USER_GROUP")
admin_password = initial_secret("LAPP_INITIAL_ADMIN_PASSWORD")
user_password = initial_secret("LAPP_INITIAL_USER_PASSWORD")
g = Group(secret=admin_group_secret)
db.session.add(g)
g = Group(secret="GroupForIgnaceAndLoversThatDoGroceries")
g = Group(secret=user_group_secret)
db.session.add(g)
admin = User(name="admin", group_id=1, is_admin=True, is_approved=True, is_private=True)
admin.set_password("suy2025")
admin.set_password(admin_password)
db.session.add(admin)
user1 = User(name="ignace", group_id=2, is_admin=False, is_approved=True)
user1.set_password("xanderj2")
user1.set_password(user_password)
db.session.add(user1)
db.session.commit()
print("Created initial credentials:")
print(f"admin password: {admin_password}")
print(f"admin group secret: {admin_group_secret}")
print(f"ignace password: {user_password}")
print(f"ignace group secret: {user_group_secret}")
lol1 = ListOfItems(owner_user_id = user1.id, name="Supermarkt", is_active=True)
db.session.add(lol1)
lol2 = ListOfItems(owner_user_id = user1.id, name="Klusjes", is_active=True)
@@ -35,4 +51,4 @@ if __name__ == "__main__":
db.session.add(item3)
db.session.add(item4)
db.session.commit()
db.session.commit()
+24 -3
View File
@@ -1,3 +1,6 @@
import os
import secrets
from pathlib import Path
from flask import Flask, send_from_directory, url_for
from flask_login import LoginManager
from models import db
@@ -11,10 +14,28 @@ from groups import groups_bp
login_manager = LoginManager()
def load_secret(app, env_name, filename):
secret = os.environ.get(env_name)
if secret:
return secret
instance_path = Path(app.instance_path)
instance_path.mkdir(parents=True, exist_ok=True)
secret_path = instance_path / filename
if secret_path.exists():
return secret_path.read_text(encoding="utf-8").strip()
secret = secrets.token_urlsafe(48)
secret_path.write_text(secret, encoding="utf-8")
secret_path.chmod(0o600)
return secret
def create_app():
app = Flask(__name__)
app.config["SECRET_KEY"] = "9823740934riurehoiuwerf873487qe78feri"
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///app.db"
app = Flask(__name__, instance_relative_config=True)
app.config["SECRET_KEY"] = load_secret(app, "LAPP_SECRET_KEY", "secret_key")
app.config["APPLICATION_KEY"] = load_secret(app, "LAPP_APPLICATION_KEY", "application_key")
app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get("LAPP_DATABASE_URI", "sqlite:///app.db")
db.init_app(app)
login_manager.init_app(app)
+2 -8
View File
@@ -37,12 +37,7 @@ def edit():
abort(403)
newname = request.form["name"].strip()
double = False
for d in ListOfItems.query.filter_by(owner_user_id=current_user.id, name=newname).all():
if not d.id == ilist_id:
# there is a duplicate name
double = True
if double:
if not newname or ListOfItems.name_exists_for_owner(current_user.id, newname, exclude_id=ilist_id):
return render_template("lists_edit.html", user=current_user,
ilists=current_user.my_owned_lists(with_inactive=True),
users=not_current_user(current_user.id),
@@ -76,8 +71,7 @@ def edit():
# must be a new list
newlist = ListOfItems(name=request.form["name"].strip(), owner_user_id=current_user.id)
# check if name exists
duplicate = ListOfItems.query.filter_by(owner_user_id=current_user.id, name=newlist.name).first()
if duplicate:
if not newlist.name or ListOfItems.name_exists_for_owner(current_user.id, newlist.name):
# there is a duplicate name
return render_template("lists_edit.html", user=current_user,
ilists=current_user.my_owned_lists(with_inactive=True),
+16 -1
View File
@@ -2,6 +2,7 @@ from flask_sqlalchemy import SQLAlchemy
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
from datetime import datetime
from sqlalchemy import func
db = SQLAlchemy() # Create the extension object
@@ -72,12 +73,26 @@ class User(UserMixin, db.Model):
return 0
class ListOfItems(db.Model):
__table_args__ = (
db.UniqueConstraint("owner_user_id", "name", name="uq_list_owner_name"),
)
id = db.Column(db.Integer, primary_key=True)
updated_at = db.Column(db.TIMESTAMP, default=datetime.now(), onupdate=datetime.now(), nullable=False)
owner_user_id = db.Column(db.Integer, nullable=False)
name = db.Column(db.String(40), unique=True, nullable=False)
name = db.Column(db.String(40), nullable=False)
is_active = db.Column(db.Boolean, default=False)
@classmethod
def name_exists_for_owner(cls, owner_user_id, name, exclude_id=None):
query = cls.query.filter(
cls.owner_user_id == owner_user_id,
func.lower(cls.name) == name.strip().lower(),
)
if exclude_id is not None:
query = query.filter(cls.id != exclude_id)
return query.first() is not None
def as_dict(self):
result = {}
for p in ['id','name','owner_user_id','is_active']: