Compare commits
No commits in common. "3a872bceb297f16f5a6b34735d8e2d98c55da09c" and "b8e704c3004839311dba2a85887259bd020f69d2" have entirely different histories.
3a872bceb2
...
b8e704c300
@ -1,9 +1,7 @@
|
|||||||
FROM alpine:latest
|
FROM alpine:latest
|
||||||
|
|
||||||
RUN apk add --update --no-cache \
|
RUN apk add --update --no-cache \
|
||||||
bash python3 py3-pip nginx uwsgi uwsgi-python3 certbot certbot-nginx npm jq
|
bash python3 py3-pip nginx uwsgi uwsgi-python3 certbot certbot-nginx
|
||||||
|
|
||||||
RUN npm install -g web-push
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -30,5 +30,3 @@ json_indent = 4
|
|||||||
# sender info: usually mailto link to responsible party to contact about issues
|
# sender info: usually mailto link to responsible party to contact about issues
|
||||||
push_private_key = abcdefghijklm_NOPQRSTUVWXYZ-0123456789
|
push_private_key = abcdefghijklm_NOPQRSTUVWXYZ-0123456789
|
||||||
push_sender_info = mailto:it@fasttube.de
|
push_sender_info = mailto:it@fasttube.de
|
||||||
# when to notify users, in hours after arrival
|
|
||||||
notify_after_hrs = 10
|
|
||||||
|
@ -19,7 +19,7 @@ class Config:
|
|||||||
configfile = findConfigFile()
|
configfile = findConfigFile()
|
||||||
|
|
||||||
if configfile:
|
if configfile:
|
||||||
self.config = ConfigParser(strict=False)
|
self.config = ConfigParser()
|
||||||
self.config.read(configfile)
|
self.config.read(configfile)
|
||||||
else:
|
else:
|
||||||
raise Exception("No config file found")
|
raise Exception("No config file found")
|
||||||
@ -30,6 +30,3 @@ class Config:
|
|||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
return self.config['global'].get(key)
|
return self.config['global'].get(key)
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return repr(self.config.items('global'))
|
|
||||||
|
@ -22,19 +22,14 @@ from flask import Flask, request, redirect
|
|||||||
app = Flask(__name__, static_folder='../web')
|
app = Flask(__name__, static_folder='../web')
|
||||||
|
|
||||||
|
|
||||||
|
from pywebpush import webpush, WebPushException
|
||||||
|
pushsubs = db.table('pushsubs')
|
||||||
|
|
||||||
|
|
||||||
if config['delete_after_days']:
|
if config['delete_after_days']:
|
||||||
from .deleter import Deleter
|
from .deleter import Deleter
|
||||||
deleter = Deleter(db, int(config['delete_after_days']))
|
deleter = Deleter(db, int(config['delete_after_days']))
|
||||||
|
|
||||||
|
|
||||||
if config['notify_after_hrs']:
|
|
||||||
from .notifier import Notifier
|
|
||||||
notifier = Notifier(db, int(config['notify_after_hrs']), {
|
|
||||||
'private_key': config['push_private_key'],
|
|
||||||
'claims': {'sub': config['push_sender_info']}
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
def shutdown():
|
def shutdown():
|
||||||
print('\rReceived stop signal, stopping threads...')
|
print('\rReceived stop signal, stopping threads...')
|
||||||
deleter.stop()
|
deleter.stop()
|
||||||
@ -42,7 +37,6 @@ def shutdown():
|
|||||||
|
|
||||||
atexit.register(shutdown)
|
atexit.register(shutdown)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/guidelines')
|
@app.route('/guidelines')
|
||||||
def get_guidelines():
|
def get_guidelines():
|
||||||
dest = config['guideline_url'] or None
|
dest = config['guideline_url'] or None
|
||||||
@ -180,32 +174,44 @@ def post_pushsub():
|
|||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
return 'Error: JSON decode error:\n' + str(e), 400
|
return 'Error: JSON decode error:\n' + str(e), 400
|
||||||
|
|
||||||
notifier.subscribe_user(data)
|
name = slugify(data['name'])
|
||||||
|
data['name'] = name
|
||||||
|
print(json.dumps(data, indent=SPACES))
|
||||||
|
|
||||||
|
Entry = Query()
|
||||||
|
pushsubs.upsert(data, Entry.name == name)
|
||||||
|
|
||||||
return 'OK', 201
|
return 'OK', 201
|
||||||
|
|
||||||
@app.route('/testpush/<name>')
|
@app.route('/testpush/<name>')
|
||||||
def testpush(name):
|
def testpush(name):
|
||||||
|
|
||||||
if not 'Authorization' in request.headers:
|
|
||||||
return 'Error: No Authorization', 401, {'WWW-Authenticate': 'Basic'}
|
|
||||||
|
|
||||||
if request.authorization.username != config['admin_user']:
|
|
||||||
return "Wrong username", 403
|
|
||||||
|
|
||||||
if request.authorization.password != config['admin_pass']:
|
|
||||||
return "Wrong password", 403
|
|
||||||
|
|
||||||
Entry = Query()
|
Entry = Query()
|
||||||
arrivals = db.search((Entry.name == name) & (Entry.departure == None))
|
ps = pushsubs.search(Entry.name == name)[0]
|
||||||
|
|
||||||
if len(arrivals) == 0:
|
arr = db.search((Entry.name == name) & (Entry.departure == None))
|
||||||
print("User is not logged in :(")
|
arr = arr if len(arr) else None
|
||||||
return "Error: User is not logged in :(", 409
|
|
||||||
|
|
||||||
error = notifier.notify_user(arrivals[0])
|
print(ps)
|
||||||
|
|
||||||
if error:
|
subscription = ps['sub']
|
||||||
return 'Error: ' + error, 404
|
notification = {
|
||||||
|
'title': "Forgot to sign out?",
|
||||||
|
'body': "You didn't sign out of ftracker yet",
|
||||||
|
'arr': arr
|
||||||
|
}
|
||||||
|
|
||||||
return 'OK', 201
|
try:
|
||||||
|
webpush(
|
||||||
|
subscription,
|
||||||
|
json.dumps(notification, indent=SPACES),
|
||||||
|
vapid_private_key = config['push_private_key'],
|
||||||
|
vapid_claims = {
|
||||||
|
'sub': config['push_sender_info']
|
||||||
|
},
|
||||||
|
verbose=True
|
||||||
|
)
|
||||||
|
return 'OK', 201
|
||||||
|
except WebPushException as exc:
|
||||||
|
print(exc)
|
||||||
|
return 'Error', 500
|
||||||
|
@ -1,97 +0,0 @@
|
|||||||
import json
|
|
||||||
from slugify import slugify
|
|
||||||
from threading import Thread, Event
|
|
||||||
from datetime import datetime, timedelta
|
|
||||||
|
|
||||||
from tinydb import Query
|
|
||||||
|
|
||||||
from pywebpush import webpush, WebPushException
|
|
||||||
|
|
||||||
ONE_HOUR_IN_S = 60 * 60
|
|
||||||
|
|
||||||
class Notifier(Thread):
|
|
||||||
|
|
||||||
def subscribe_user(self, data):
|
|
||||||
|
|
||||||
pushsubs = self.db.table('pushsubs')
|
|
||||||
|
|
||||||
name = slugify(data['name'])
|
|
||||||
data['name'] = name
|
|
||||||
|
|
||||||
Entry = Query()
|
|
||||||
pushsubs.upsert(data, Entry.name == name)
|
|
||||||
|
|
||||||
def notify_user(self, arrival):
|
|
||||||
|
|
||||||
pushsubs = self.db.table('pushsubs')
|
|
||||||
|
|
||||||
Entry = Query()
|
|
||||||
ps = pushsubs.search(Entry.name == arrival['name'])
|
|
||||||
if len(ps) == 0:
|
|
||||||
print("User is not subscribed to notifications :(")
|
|
||||||
return "User is not subscribed to notifications :("
|
|
||||||
|
|
||||||
ps = ps[0]
|
|
||||||
|
|
||||||
print("Sending notification", arrival, ps)
|
|
||||||
|
|
||||||
subscription = ps['sub']
|
|
||||||
notification = {
|
|
||||||
'title': "Forgot to sign out?",
|
|
||||||
'body': "You didn't sign out of ftracker yet",
|
|
||||||
'arr': arrival
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
|
||||||
webpush(
|
|
||||||
subscription,
|
|
||||||
json.dumps(notification),
|
|
||||||
vapid_private_key = self.vapid_creds['private_key'],
|
|
||||||
vapid_claims = self.vapid_creds['claims']
|
|
||||||
)
|
|
||||||
print("Notification sent")
|
|
||||||
return None
|
|
||||||
except WebPushException as exc:
|
|
||||||
print("Notification failed", exc)
|
|
||||||
return repr(exc)
|
|
||||||
|
|
||||||
|
|
||||||
def notify_logged_in_users(self):
|
|
||||||
|
|
||||||
print("Notifying users that aren't signed out yet...")
|
|
||||||
|
|
||||||
td = timedelta(hours=self.hours)
|
|
||||||
|
|
||||||
threshold = datetime.utcnow() - td
|
|
||||||
|
|
||||||
iso = threshold.isoformat() + 'Z'
|
|
||||||
|
|
||||||
Entry = Query()
|
|
||||||
arrivals = self.db.search(
|
|
||||||
(Entry.arrival < iso) & (Entry.departure == None)
|
|
||||||
)
|
|
||||||
|
|
||||||
for arrival in arrivals:
|
|
||||||
self.notify_user(arrival)
|
|
||||||
|
|
||||||
print("Notified everything until UTC", iso)
|
|
||||||
|
|
||||||
def __init__(self, db, hours, vapid_creds):
|
|
||||||
|
|
||||||
self.db = db
|
|
||||||
self.hours = hours
|
|
||||||
self.vapid_creds = vapid_creds
|
|
||||||
|
|
||||||
self.notify_logged_in_users()
|
|
||||||
|
|
||||||
Thread.__init__(self, daemon=True)
|
|
||||||
self.stopped = Event()
|
|
||||||
self.start()
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
|
|
||||||
while not self.stopped.wait(ONE_HOUR_IN_S):
|
|
||||||
self.notify_logged_in_users()
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
self.stopped.set()
|
|
@ -4,9 +4,6 @@
|
|||||||
# Remove or leave empty for temporary (/tmp/ftracker-db.json) storage
|
# Remove or leave empty for temporary (/tmp/ftracker-db.json) storage
|
||||||
db_file = /var/ftracker/db.json
|
db_file = /var/ftracker/db.json
|
||||||
|
|
||||||
# Delete all information after X days (e.g. for GDPR compliance)
|
|
||||||
delete_after_days = 28
|
|
||||||
|
|
||||||
# List of people to be allowed, in .csv format (comma, no delimiters)
|
# List of people to be allowed, in .csv format (comma, no delimiters)
|
||||||
# Col1: First Name(s), Col2: Last Name(s), Col3 (optional): EMail
|
# Col1: First Name(s), Col2: Last Name(s), Col3 (optional): EMail
|
||||||
# Remove or leave empty for no check
|
# Remove or leave empty for no check
|
||||||
@ -24,11 +21,3 @@ guideline_url = https://youtu.be/oHg5SJYRHA0
|
|||||||
|
|
||||||
# JSON indentation for debugging
|
# JSON indentation for debugging
|
||||||
json_indent = 4
|
json_indent = 4
|
||||||
|
|
||||||
# VAPID credentials for push notifications
|
|
||||||
# private key: base64url encoded private part of an EC-Prime256v1 keypair. See INSTALL.md
|
|
||||||
# sender info: usually mailto link to responsible party to contact about issues
|
|
||||||
push_private_key = abcdefghijklm_NOPQRSTUVWXYZ-0123456789
|
|
||||||
push_sender_info = mailto:admin@example.com
|
|
||||||
# when to notify users, in hours after arrival
|
|
||||||
notify_after_hrs = 10
|
|
||||||
|
@ -1,25 +1,5 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
echo " >>> Checking / Creating & patching VAPID creds <<< "
|
|
||||||
|
|
||||||
VAPID_CREDS_FILE=/etc/ftracker/vapid-creds.json
|
|
||||||
if [[ ! -f $VAPID_CREDS_FILE ]]
|
|
||||||
then
|
|
||||||
|
|
||||||
echo "Generating keypair ..."
|
|
||||||
|
|
||||||
web-push generate-vapid-keys --json > $VAPID_CREDS_FILE
|
|
||||||
|
|
||||||
echo "Patching public key into frontend ..."
|
|
||||||
PUB_KEY=`cat $VAPID_CREDS_FILE | jq -r .publicKey`
|
|
||||||
sed -i "s/pushServerPublicKey = '[a-zA-Z0-9_\-]*'/pushServerPublicKey = '${PUB_KEY}'/" /var/www/html/ftracker/main.js
|
|
||||||
|
|
||||||
echo "Patching private key into backend config ..."
|
|
||||||
PRIV_KEY=`cat $VAPID_CREDS_FILE | jq -r .privateKey`
|
|
||||||
echo "push_private_key = ${PRIV_KEY}" >> /etc/ftracker/config.ini
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo " >>> Starting nginx <<< "
|
echo " >>> Starting nginx <<< "
|
||||||
|
|
||||||
mkdir /run/nginx # needed because of bug in package
|
mkdir /run/nginx # needed because of bug in package
|
||||||
|
@ -250,6 +250,7 @@ function initPush(name) {
|
|||||||
applicationServerKey: pushServerPublicKey
|
applicationServerKey: pushServerPublicKey
|
||||||
}).then(function(subscription) {
|
}).then(function(subscription) {
|
||||||
console.log("User is subscribed:", subscription);
|
console.log("User is subscribed:", subscription);
|
||||||
|
localStorage.setItem('pushsub', subscription);
|
||||||
|
|
||||||
fetch('/pushsubscribe', {
|
fetch('/pushsubscribe', {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@ -258,9 +259,6 @@ function initPush(name) {
|
|||||||
name: name,
|
name: name,
|
||||||
sub: subscription
|
sub: subscription
|
||||||
})
|
})
|
||||||
}).then(function(res) {
|
|
||||||
if (res.ok)
|
|
||||||
localStorage.setItem('pushsub', subscription);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user