You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
131 lines
4.2 KiB
131 lines
4.2 KiB
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
|
|
|
|
|