minor changes due problems deleting users and generating new passwords
This commit is contained in:
parent
602ddc7143
commit
e8ea813896
106
setup.sh
106
setup.sh
|
@ -116,6 +116,8 @@ gunicorn
|
||||||
apprise
|
apprise
|
||||||
debugpy
|
debugpy
|
||||||
pytz
|
pytz
|
||||||
|
Flask-Session
|
||||||
|
redis
|
||||||
EOL
|
EOL
|
||||||
|
|
||||||
# 3. .env Datei in Parent-Folder
|
# 3. .env Datei in Parent-Folder
|
||||||
|
@ -155,6 +157,9 @@ APPRISE_URLS=""
|
||||||
#gotify://gotify.example.com/TOKEN
|
#gotify://gotify.example.com/TOKEN
|
||||||
#matrixs://TOKEN@matrix.org/!ROOM_ID"
|
#matrixs://TOKEN@matrix.org/!ROOM_ID"
|
||||||
|
|
||||||
|
# Redis URL
|
||||||
|
REDIS_URL=redis://redis:6379/0
|
||||||
|
|
||||||
# Enable Debug (e.g. for VS Code)
|
# Enable Debug (e.g. for VS Code)
|
||||||
DEBUGPY=0
|
DEBUGPY=0
|
||||||
EOL
|
EOL
|
||||||
|
@ -186,12 +191,14 @@ from flask import (
|
||||||
abort
|
abort
|
||||||
)
|
)
|
||||||
from flask_sqlalchemy import SQLAlchemy
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
from flask_session import Session
|
||||||
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
|
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
|
||||||
from werkzeug.security import generate_password_hash, check_password_hash
|
from werkzeug.security import generate_password_hash, check_password_hash
|
||||||
from flask_wtf import CSRFProtect
|
from flask_wtf import CSRFProtect
|
||||||
from flask import abort
|
from flask import abort
|
||||||
from flask import request, redirect
|
from flask import request, redirect
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
|
from flask_wtf.csrf import CSRFProtect
|
||||||
from wtforms import StringField, SelectField, TextAreaField, validators
|
from wtforms import StringField, SelectField, TextAreaField, validators
|
||||||
import io
|
import io
|
||||||
import warnings
|
import warnings
|
||||||
|
@ -232,6 +239,8 @@ from sqlalchemy.engine import Engine
|
||||||
import sqlite3
|
import sqlite3
|
||||||
from sqlalchemy.orm import joinedload
|
from sqlalchemy.orm import joinedload
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
from flask_session import Session
|
||||||
|
from redis import Redis
|
||||||
|
|
||||||
@event.listens_for(Engine, "connect")
|
@event.listens_for(Engine, "connect")
|
||||||
def enable_foreign_keys(dbapi_connection, connection_record):
|
def enable_foreign_keys(dbapi_connection, connection_record):
|
||||||
|
@ -287,7 +296,9 @@ if app.debug:
|
||||||
def admin_required(f):
|
def admin_required(f):
|
||||||
@wraps(f)
|
@wraps(f)
|
||||||
def decorated_function(*args, **kwargs):
|
def decorated_function(*args, **kwargs):
|
||||||
if not current_user.is_authenticated or not getattr(current_user, 'is_admin', False):
|
if not current_user.is_authenticated:
|
||||||
|
abort(403)
|
||||||
|
if not current_user.is_admin:
|
||||||
abort(403)
|
abort(403)
|
||||||
return f(*args, **kwargs)
|
return f(*args, **kwargs)
|
||||||
return decorated_function
|
return decorated_function
|
||||||
|
@ -311,20 +322,38 @@ load_dotenv(override=True)
|
||||||
|
|
||||||
# App-Configuration
|
# App-Configuration
|
||||||
app.config.update(
|
app.config.update(
|
||||||
|
# WICHTIGSTE EINSTELLUNGEN
|
||||||
SECRET_KEY=os.getenv('SECRET_KEY'),
|
SECRET_KEY=os.getenv('SECRET_KEY'),
|
||||||
SQLALCHEMY_DATABASE_URI = 'sqlite:////app/data/games.db',
|
SQLALCHEMY_DATABASE_URI = 'sqlite:////app/data/games.db',
|
||||||
SQLALCHEMY_TRACK_MODIFICATIONS = False,
|
SQLALCHEMY_TRACK_MODIFICATIONS = False,
|
||||||
|
|
||||||
|
# SESSION-HANDLING (Produktion: Redis verwenden!)
|
||||||
|
SESSION_TYPE='redis',
|
||||||
|
SESSION_PERMANENT = False,
|
||||||
|
SESSION_USE_SIGNER = True,
|
||||||
|
SESSION_REDIS=Redis.from_url(os.getenv("REDIS_URL", "redis://redis:6379/0")),
|
||||||
|
SESSION_FILE_DIR = '/app/data/flask-sessions',
|
||||||
SESSION_COOKIE_NAME = 'gamekeys_session',
|
SESSION_COOKIE_NAME = 'gamekeys_session',
|
||||||
SESSION_COOKIE_SECURE=os.getenv('SESSION_COOKIE_SECURE', 'False') == 'True',
|
SESSION_COOKIE_SECURE = os.getenv('SESSION_COOKIE_SECURE', 'False').lower() == 'true',
|
||||||
|
SESSION_COOKIE_HTTPONLY = True,
|
||||||
SESSION_COOKIE_SAMESITE = 'Lax',
|
SESSION_COOKIE_SAMESITE = 'Lax',
|
||||||
PERMANENT_SESSION_LIFETIME = timedelta(days=30),
|
PERMANENT_SESSION_LIFETIME = timedelta(days=30),
|
||||||
SESSION_REFRESH_EACH_REQUEST=False,
|
|
||||||
WTF_CSRF_ENABLED=os.getenv('CSRF_ENABLED', 'True') == 'True',
|
# CSRF-PROTECTION
|
||||||
|
WTF_CSRF_ENABLED = True,
|
||||||
|
WTF_CSRF_SECRET_KEY = os.getenv('CSRF_SECRET_KEY', os.urandom(32).hex()),
|
||||||
|
WTF_CSRF_TIME_LIMIT = 3600,
|
||||||
|
|
||||||
|
# SECURITYsa & PERFORMANCE
|
||||||
REGISTRATION_ENABLED = os.getenv('REGISTRATION_ENABLED', 'True').lower() == 'true',
|
REGISTRATION_ENABLED = os.getenv('REGISTRATION_ENABLED', 'True').lower() == 'true',
|
||||||
SEND_FILE_MAX_AGE_DEFAULT = int(os.getenv('SEND_FILE_MAX_AGE_DEFAULT', 0)),
|
SEND_FILE_MAX_AGE_DEFAULT = int(os.getenv('SEND_FILE_MAX_AGE_DEFAULT', 0)),
|
||||||
TEMPLATES_AUTO_RELOAD=os.getenv('TEMPLATES_AUTO_RELOAD', 'True') == 'True'
|
TEMPLATES_AUTO_RELOAD = os.getenv('TEMPLATES_AUTO_RELOAD', 'True').lower() == 'true',
|
||||||
|
PREFERRED_URL_SCHEME = 'https' if os.getenv('FORCE_HTTPS') else 'http'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
Session(app)
|
||||||
|
|
||||||
interval_hours = int(os.getenv('CHECK_EXPIRING_KEYS_INTERVAL_HOURS', 12))
|
interval_hours = int(os.getenv('CHECK_EXPIRING_KEYS_INTERVAL_HOURS', 12))
|
||||||
|
|
||||||
# Initialisation
|
# Initialisation
|
||||||
|
@ -993,8 +1022,13 @@ def admin_reset_password(user_id):
|
||||||
new_password = secrets.token_urlsafe(8)
|
new_password = secrets.token_urlsafe(8)
|
||||||
user.password = generate_password_hash(new_password)
|
user.password = generate_password_hash(new_password)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
flash(translate('New password for %(username)s: %(password)s',
|
|
||||||
username=user.username, password=new_password), 'info')
|
flash(
|
||||||
|
translate('New password for {username}: {password}',
|
||||||
|
username=user.username,
|
||||||
|
password=new_password),
|
||||||
|
'info'
|
||||||
|
)
|
||||||
return redirect(url_for('admin_users'))
|
return redirect(url_for('admin_users'))
|
||||||
|
|
||||||
|
|
||||||
|
@ -1922,9 +1956,9 @@ cat <<HTML_END > templates/admin_users.html
|
||||||
<button type="submit" class="btn btn-danger btn-sm">{{ _('Delete') }}</button>
|
<button type="submit" class="btn btn-danger btn-sm">{{ _('Delete') }}</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<form method="POST" action="{{ url_for('admin_reset_password', user_id=user.id) }}" class="d-inline">
|
<form method="POST" action="{{ url_for('admin_reset_password', user_id=user.id) }}">
|
||||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||||
<button type="submit" class="btn btn-warning btn-sm">{{ _('Reset Password') }}</button>
|
<button type="submit" class="btn btn-warning">{{ _('Reset Password') }}</button>
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
|
@ -2103,6 +2137,7 @@ chmod +x entrypoint.sh
|
||||||
|
|
||||||
# create translate.sh and run it
|
# create translate.sh and run it
|
||||||
cat <<'SCRIPT_END' > ../translate.sh
|
cat <<'SCRIPT_END' > ../translate.sh
|
||||||
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
APP_DIR="steam-gift-manager"
|
APP_DIR="steam-gift-manager"
|
||||||
|
@ -2114,35 +2149,32 @@ if ! command -v jq &>/dev/null; then
|
||||||
echo "❌ jq is required. Install with: sudo apt-get install jq"
|
echo "❌ jq is required. Install with: sudo apt-get install jq"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
echo -e "\n\033[1;32m✅ we are extracting translations from the templates.\033[0m"
|
|
||||||
# 1. Lege JSON-Dateien an, falls sie fehlen
|
echo -e "\n\033[1;32m✅ Extracting translations...\033[0m"
|
||||||
|
|
||||||
|
# 1. create json files
|
||||||
|
mkdir -p "$TRANSLATION_DIR"
|
||||||
for lang in "${LANGS[@]}"; do
|
for lang in "${LANGS[@]}"; do
|
||||||
file="$TRANSLATION_DIR/$lang.json"
|
file="$TRANSLATION_DIR/$lang.json"
|
||||||
if [ ! -f "$file" ]; then
|
[ -f "$file" ] || echo "{}" > "$file"
|
||||||
echo "{}" > "$file"
|
|
||||||
echo "Created $file"
|
|
||||||
fi
|
|
||||||
done
|
done
|
||||||
|
|
||||||
# 2. Extrahiere alle zu übersetzenden Strings
|
# 2. extract all strings
|
||||||
STRINGS=$(grep -rhoP "_\(\s*['\"](.+?)['\"]\s*\)" \
|
STRINGS=$(grep -rhoP "_\(\s*['\"]((?:[^']|'[^'])*?)['\"]\s*[,)]" \
|
||||||
"$APP_DIR/templates" "$APP_DIR/app.py" | \
|
"$APP_DIR/templates" "$APP_DIR/app.py" | \
|
||||||
sed -E "s/_\(\s*['\"](.+?)['\"]\s*\)/\1/" | sort | uniq)
|
sed -E "s/_\(\s*['\"](.+?)['\"]\s*[,)]/\1/" | sort | uniq)
|
||||||
|
|
||||||
# 3. Ergänze neue Keys in die JSON-Dateien
|
# 3. put da keys in da json
|
||||||
for lang in "${LANGS[@]}"; do
|
for lang in "${LANGS[@]}"; do
|
||||||
file="$TRANSLATION_DIR/$lang.json"
|
file="$TRANSLATION_DIR/$lang.json"
|
||||||
tmp="$file.tmp"
|
tmp="$file.tmp"
|
||||||
cp "$file" "$tmp"
|
jq --argjson keys "$(echo "$STRINGS" | jq -R . | jq -s .)" \
|
||||||
while IFS= read -r key; do
|
'reduce $keys[] as $k (.; .[$k] = (.[$k] // ""))' "$file" > "$tmp"
|
||||||
if ! jq -e --arg k "$key" 'has($k)' "$tmp" >/dev/null; then
|
|
||||||
jq --arg k "$key" '. + {($k): ""}' "$tmp" > "$tmp.new" && mv "$tmp.new" "$tmp"
|
|
||||||
fi
|
|
||||||
done <<< "$STRINGS"
|
|
||||||
mv "$tmp" "$file"
|
mv "$tmp" "$file"
|
||||||
echo "Updated $file"
|
|
||||||
done
|
done
|
||||||
echo -e "\n\033[1;32m✅ JSON translation files updated. Please enter your translations!\033[0m"
|
|
||||||
|
echo -e "\n\033[1;32m✅ Done! Translation keys added.\033[0m"
|
||||||
|
|
||||||
SCRIPT_END
|
SCRIPT_END
|
||||||
|
|
||||||
chmod +x ../translate.sh
|
chmod +x ../translate.sh
|
||||||
|
@ -2201,6 +2233,15 @@ DOCKER_END
|
||||||
# 6. docker-compose.yml
|
# 6. docker-compose.yml
|
||||||
cat <<COMPOSE_END > docker-compose.yml
|
cat <<COMPOSE_END > docker-compose.yml
|
||||||
services:
|
services:
|
||||||
|
redis:
|
||||||
|
image: redis:alpine
|
||||||
|
ports:
|
||||||
|
- "6379:6379"
|
||||||
|
volumes:
|
||||||
|
- redis_data:/data
|
||||||
|
networks:
|
||||||
|
- app-network
|
||||||
|
|
||||||
steam-manager:
|
steam-manager:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
|
@ -2214,6 +2255,7 @@ services:
|
||||||
- FLASK_DEBUG=0
|
- FLASK_DEBUG=0
|
||||||
- REGISTRATION_ENABLED=${REGISTRATION_ENABLED:-True}
|
- REGISTRATION_ENABLED=${REGISTRATION_ENABLED:-True}
|
||||||
- TZ=${TZ}
|
- TZ=${TZ}
|
||||||
|
- REDIS_URL=redis://redis:6379/0
|
||||||
volumes:
|
volumes:
|
||||||
- ../data:/app/data
|
- ../data:/app/data
|
||||||
- ./translations:/app/translations:rw
|
- ./translations:/app/translations:rw
|
||||||
|
@ -2221,6 +2263,16 @@ services:
|
||||||
user: "${UID:-1000}:${GID:-1000}"
|
user: "${UID:-1000}:${GID:-1000}"
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command: ["/app/entrypoint.sh"]
|
command: ["/app/entrypoint.sh"]
|
||||||
|
networks:
|
||||||
|
- app-network
|
||||||
|
depends_on:
|
||||||
|
- redis
|
||||||
|
volumes:
|
||||||
|
redis_data:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
app-network:
|
||||||
|
driver: bridge
|
||||||
|
|
||||||
COMPOSE_END
|
COMPOSE_END
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue