from cryptography.hazmat.primitives import serialization from flask import Flask, request, jsonify from models import db, VAPIDKey, Subscription from py_vapid import Vapid from pywebpush import webpush, WebPushException import base64 import json import os import re def create_app(config_name): app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///data/webpush.db' db.init_app(app) def generate_and_save_vapid_keys(): vapid = Vapid() try: vapid.generate_keys() private_key = vapid.private_pem().decode('utf-8').strip() public_key = vapid.public_pem().decode('utf-8').strip() except Exception as e: print(f"Error generating VAPID keys: {e}") key = VAPIDKey(public_key=public_key, private_key=private_key) db.session.add(key) db.session.commit() def initialize(): if not VAPIDKey.query.first(): generate_and_save_vapid_keys() def send_push_notification(subscription_info, message, vapid_key): try: webpush( subscription_info=subscription_info, data=json.dumps(message), vapid_private_key=vapid_key.private_key, vapid_claims={ "sub": "mailto:your-email@example.com" } ) except WebPushException as e: print(f"Failed to send notification: {e}") def is_valid_base64_url(s): return re.match(r'^[A-Za-z0-9_-]*$', s) is not None def is_valid_server_key(server_key): if not is_valid_base64_url(server_key): return False return len(server_key) == 88 @app.route('/web-push/vapid', methods=['GET']) def get_vapid(): key = VAPIDKey.query.first() pem_bytes = key.private_key.encode("utf-8") private_key = serialization.load_pem_private_key(pem_bytes, password=None) public_key = private_key.public_key() public_key_der = public_key.public_bytes( encoding=serialization.Encoding.DER, format=serialization.PublicFormat.SubjectPublicKeyInfo ) base64_data = base64.urlsafe_b64encode(public_key_der) base64_str = base64_data.decode('utf-8') base64_str = base64_str.rstrip('=') if is_valid_server_key(base64_str): return jsonify(vapidKey=base64_str) else: return jsonify(error=f'VAPID server key is not valid. {len(base64_str)} {is_valid_base64_url(base64_str)}'), 422 return jsonify(error='No VAPID keys found'), 404 @app.route('/web-push/subscribe', methods=['POST']) def subscribe(): content = request.json vapid_key = VAPIDKey.query.first() if not vapid_key: return jsonify(success=False, error="No VAPID keys available"), 500 subscription = Subscription(endpoint=content['endpoint'], p256dh=content['keys']['p256dh'], auth=content['keys']['auth'], vapid_key_id=vapid_key.id) db.session.add(subscription) db.session.commit() subscription_info = { "endpoint": subscription.endpoint, "keys": { "p256dh": subscription.p256dh, "auth": subscription.auth } } message = {"title": "Subscription Successful", "body": "Thank you for subscribing!"} send_push_notification(subscription_info, message, vapid_key) return jsonify(success=True) @app.route('/web-push/unsubscribe', methods=['DELETE']) def unsubscribe(): content = request.json endpoint = content['endpoint'] subscription = Subscription.query.filter_by(endpoint=endpoint).first() if subscription: db.session.delete(subscription) db.session.commit() return jsonify(success=True, message="Subscription deleted successfully") else: return jsonify(success=False, error="Subscription not found"), 404 with app.app_context(): initialize() return app