Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a49ed98ed7 | |||
| 4d9bb62907 | |||
| 8251cd9bfd | |||
| 3ed3148b8a |
+49
-58
@@ -10375,11 +10375,24 @@ def my_borrowed_items():
|
||||
|
||||
@app.route('/api/push/vapid-key', methods=['GET'])
|
||||
def get_vapid_key():
|
||||
"""Returns the VAPID public key for web push subscriptions."""
|
||||
"""
|
||||
Returns the VAPID public key for web push subscriptions
|
||||
"""
|
||||
from Web.push_notifications import _get_vapid_public
|
||||
if not _get_vapid_public():
|
||||
return jsonify({'error': 'VAPID public key not configured'}), 500
|
||||
return jsonify({'publicKey': _get_vapid_public()})
|
||||
if 'username' not in session:
|
||||
return jsonify({'success': False, 'error': 'Not authenticated'}), 401
|
||||
|
||||
try:
|
||||
if not _get_vapid_public():
|
||||
return jsonify({'success': False, 'error': 'VAPID public key not configured'}), 500
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'publicKey': _get_vapid_public()
|
||||
})
|
||||
except Exception as e:
|
||||
app.logger.error(f'Error getting VAPID key: {e}')
|
||||
return jsonify({'success': False}), 500
|
||||
|
||||
@app.route('/notifications')
|
||||
def notifications_view():
|
||||
@@ -10466,45 +10479,49 @@ def mark_notification_read(notification_id):
|
||||
return redirect(url_for('notifications_view'))
|
||||
|
||||
|
||||
@app.route('/notifications/mark_all_read', methods=['POST'])
|
||||
@app.route('/notifications/mark-all-read', methods=['POST'])
|
||||
def mark_all_notifications_read():
|
||||
"""Mark all visible notifications as read for the current user."""
|
||||
if 'username' not in session:
|
||||
flash('Bitte melden Sie sich an.', 'error')
|
||||
return redirect(url_for('login'))
|
||||
|
||||
username = session['username']
|
||||
is_admin_user = False
|
||||
|
||||
current_permissions = us.get_effective_permissions(session['username'])
|
||||
|
||||
if not current_permissions['actions'].get('can_manage_settings', False):
|
||||
is_admin_user = True
|
||||
else:
|
||||
is_admin_user = False
|
||||
|
||||
query = {
|
||||
'$or': [
|
||||
{'Audience': 'user', 'TargetUser': username},
|
||||
]
|
||||
}
|
||||
if is_admin_user:
|
||||
query['$or'].append({'Audience': 'admin'})
|
||||
current_permissions = us.get_effective_permissions(username)
|
||||
is_admin_user = current_permissions['actions'].get('can_manage_settings', False)
|
||||
|
||||
client = None
|
||||
try:
|
||||
client = MongoClient(MONGODB_HOST, MONGODB_PORT)
|
||||
db = client[MONGODB_DB]
|
||||
result = db['notifications'].update_many(
|
||||
|
||||
# Filter-Logik: Welche Benachrichtigungen sollen als gelesen markiert werden?
|
||||
# Wenn Sie 'Audience' nutzen (wie in Ihrer notifications_view Route):
|
||||
query = {}
|
||||
if is_admin_user:
|
||||
# Ein Admin sieht sowohl an ihn gerichtete als auch allgemeine Admin-Meldungen
|
||||
query['$or'] = [
|
||||
{'TargetUser': username},
|
||||
{'Audience': 'admin'}
|
||||
]
|
||||
else:
|
||||
# Normale User sehen nur Meldungen, die an sie oder alle User gerichtet sind
|
||||
query['$or'] = [
|
||||
{'TargetUser': username},
|
||||
{'Audience': 'user'}
|
||||
]
|
||||
|
||||
# WICHTIG: Fügt den Benutzernamen per $addToSet in das ReadBy-Array ein.
|
||||
# $addToSet verhindert, dass der Name doppelt eingetragen wird, falls er schon drinsteht.
|
||||
db['notifications'].update_many(
|
||||
query,
|
||||
{
|
||||
'$addToSet': {'ReadBy': username},
|
||||
'$set': {'UpdatedAt': datetime.datetime.now()}
|
||||
}
|
||||
{'$addToSet': {'ReadBy': username}}
|
||||
)
|
||||
if result.modified_count > 0:
|
||||
_bump_notification_version(f'user:{username}')
|
||||
|
||||
flash('Alle Benachrichtigungen wurden als gelesen markiert.', 'success')
|
||||
except Exception as exc:
|
||||
app.logger.warning(f"Could not mark all notifications as read for {encrypt_text(username)}: {exc}")
|
||||
app.logger.error(f"Error marking all notifications as read: {exc}")
|
||||
flash('Fehler beim Aktualisieren der Benachrichtigungen.', 'error')
|
||||
finally:
|
||||
if client:
|
||||
client.close()
|
||||
@@ -12132,7 +12149,7 @@ def subscribe_to_push():
|
||||
return jsonify({'success': False, 'error': 'Not authenticated'}), 401
|
||||
|
||||
try:
|
||||
data = request.get_json() or {}
|
||||
data = request.get_json(silent=True) or {}
|
||||
subscription = data.get('subscription')
|
||||
|
||||
if not subscription or not subscription.get('endpoint'):
|
||||
@@ -12169,7 +12186,7 @@ def unsubscribe_from_push():
|
||||
return jsonify({'success': False, 'error': 'Not authenticated'}), 401
|
||||
|
||||
try:
|
||||
data = request.get_json() or {}
|
||||
data = request.get_json(silent=True) or {}
|
||||
endpoint = data.get('endpoint')
|
||||
|
||||
if not endpoint:
|
||||
@@ -12225,32 +12242,6 @@ def get_push_subscriptions():
|
||||
app.logger.error(f'Error getting push subscriptions: {e}')
|
||||
return jsonify({'success': False}), 500
|
||||
|
||||
|
||||
@app.route('/api/push/vapid-key', methods=['GET'])
|
||||
def get_vapid_key():
|
||||
"""
|
||||
Get the VAPID public key for push notifications
|
||||
Used by the service worker to communicate with push service
|
||||
"""
|
||||
try:
|
||||
vapid_key = pn.VAPID_PUBLIC_KEY
|
||||
if not vapid_key:
|
||||
app.logger.warning('VAPID_PUBLIC_KEY not configured')
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': 'Push notifications not configured on server'
|
||||
}), 501
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'vapid_key': vapid_key
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
app.logger.error(f'Error getting VAPID key: {e}')
|
||||
return jsonify({'success': False}), 500
|
||||
|
||||
|
||||
@app.route('/api/push/test', methods=['POST'])
|
||||
def test_push_notification():
|
||||
"""
|
||||
@@ -12263,7 +12254,7 @@ def test_push_notification():
|
||||
return jsonify({'success': False, 'error': 'Admin access required'}), 403
|
||||
|
||||
try:
|
||||
data = request.get_json() or {}
|
||||
data = request.get_json(silent=True) or {}
|
||||
target_user = data.get('target_user', session['username'])
|
||||
|
||||
sent = pn.send_push_notification(
|
||||
|
||||
+18
-18
@@ -40,14 +40,18 @@ try:
|
||||
else:
|
||||
vapid = Vapid.from_file(VAPID_PRIVATE_PEM)
|
||||
|
||||
# Extract public key in standard uncompressed point format encoded in URL-safe base64
|
||||
raw_pub = vapid.public_key.public_bytes(
|
||||
serialization.Encoding.X962,
|
||||
serialization.PublicFormat.UncompressedPoint
|
||||
)
|
||||
|
||||
VAPID_PUBLIC_KEY = b64urlencode(raw_pub).decode('utf-8')
|
||||
VAPID_PRIVATE_KEY = VAPID_PRIVATE_PEM # pywebpush accepts the file path to the private PEM
|
||||
encoded_pub = b64urlencode(raw_pub)
|
||||
if isinstance(encoded_pub, bytes):
|
||||
VAPID_PUBLIC_KEY = encoded_pub.decode('utf-8')
|
||||
else:
|
||||
VAPID_PUBLIC_KEY = encoded_pub
|
||||
|
||||
VAPID_PRIVATE_KEY = VAPID_PRIVATE_PEM
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f'Could not load or generate VAPID keys: {e}')
|
||||
@@ -118,15 +122,15 @@ def get_user_subscriptions(username):
|
||||
|
||||
|
||||
def save_push_subscription(username, subscription_obj):
|
||||
"""
|
||||
Save a new push subscription for a user with field-level encryption.
|
||||
"""
|
||||
"""Save a new push subscription for a user with field-level encryption."""
|
||||
try:
|
||||
endpoint = subscription_obj.get('endpoint')
|
||||
if not endpoint:
|
||||
logger.warning('Invalid subscription object: missing endpoint')
|
||||
keys = subscription_obj.get('keys', {})
|
||||
|
||||
if not endpoint or not keys.get('p256dh') or not keys.get('auth'):
|
||||
logger.warning(f'Invalid subscription object for {username}: missing endpoint or keys')
|
||||
return False
|
||||
|
||||
|
||||
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
|
||||
db = client[cfg.MONGODB_DB]
|
||||
subs_col = get_push_subscriptions_collection(db)
|
||||
@@ -180,27 +184,23 @@ def save_push_subscription(username, subscription_obj):
|
||||
|
||||
|
||||
def remove_push_subscription(username, endpoint):
|
||||
"""
|
||||
Remove a push subscription by making it inactive.
|
||||
"""
|
||||
try:
|
||||
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
|
||||
db = client[cfg.MONGODB_DB]
|
||||
subs_col = get_push_subscriptions_collection(db)
|
||||
|
||||
# Recreate the deterministic hash to find the specific subscription
|
||||
|
||||
sub_hash = hashlib.shake_256(
|
||||
f"{username}:{endpoint}".encode('utf-8')
|
||||
).hexdigest()
|
||||
|
||||
|
||||
result = subs_col.update_one(
|
||||
{'SubscriptionHash': sub_hash},
|
||||
{'$set': {'IsActive': False}}
|
||||
)
|
||||
|
||||
|
||||
client.close()
|
||||
return result.modified_count > 0
|
||||
|
||||
return result.matched_count > 0
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f'Error removing push subscription: {e}')
|
||||
return False
|
||||
|
||||
@@ -25,7 +25,7 @@ class PushNotificationManager {
|
||||
* Initialize push notification system
|
||||
* Must be called after page load
|
||||
*/
|
||||
async init() {
|
||||
async init(providedKey = null) {
|
||||
if (!this.isSupported) {
|
||||
console.log('Push notifications not supported in this browser');
|
||||
return false;
|
||||
@@ -49,11 +49,18 @@ class PushNotificationManager {
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch VAPID public key from server
|
||||
// Wenn der Key direkt aus dem Template übergeben wurde, nutze diesen (spart einen Request)
|
||||
if (providedKey) {
|
||||
this.vapidKey = providedKey;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ansonsten: Fetch VAPID public key from server
|
||||
const keyResponse = await fetch('/api/push/vapid-key');
|
||||
if (keyResponse.ok) {
|
||||
const keyData = await keyResponse.json();
|
||||
this.vapidKey = keyData.vapid_key;
|
||||
// WICHTIG: Muss exakt mit dem Python-JSON-Key übereinstimmen!
|
||||
this.vapidKey = keyData.publicKey;
|
||||
} else {
|
||||
console.warn('Failed to fetch VAPID key');
|
||||
return false;
|
||||
@@ -155,19 +162,17 @@ class PushNotificationManager {
|
||||
const subscription = await this.serviceWorkerRegistration.pushManager.getSubscription();
|
||||
if (!subscription) {
|
||||
console.warn('No active push subscription');
|
||||
return false;
|
||||
return true; // Bereits deaktiviert
|
||||
}
|
||||
|
||||
// Remove subscription on server
|
||||
const success = await this.removeSubscriptionFromServer(subscription);
|
||||
// Best-Effort: Dem Server Bescheid geben (wir ignorieren absichtlich,
|
||||
// falls der Server das Abo nicht mehr kennt)
|
||||
await this.removeSubscriptionFromServer(subscription);
|
||||
|
||||
// Unsubscribe from push service
|
||||
if (success) {
|
||||
await subscription.unsubscribe();
|
||||
return true;
|
||||
}
|
||||
// WICHTIG: Das Abo im Browser immer zwingend löschen!
|
||||
await subscription.unsubscribe();
|
||||
return true;
|
||||
|
||||
return false;
|
||||
} catch (error) {
|
||||
console.error('Failed to unsubscribe from push notifications:', error);
|
||||
return false;
|
||||
|
||||
Reference in New Issue
Block a user