Switching to key-based APNS cert.

This commit is contained in:
Samuel Clay 2025-04-25 18:18:22 -07:00
parent b32201a570
commit 9cd40a71e8
4 changed files with 70 additions and 32 deletions

View file

@ -78,6 +78,10 @@ lint:
docker exec -it newsblur_web black --line-length 110 . docker exec -it newsblur_web black --line-length 110 .
docker exec -it newsblur_web flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics --exclude=venv docker exec -it newsblur_web flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics --exclude=venv
deps:
docker exec -t newsblur_web pip install -U uv
docker exec -t newsblur_web uv pip install -r requirements.txt
jekyll_build: jekyll_build:
cd blog && JEKYLL_ENV=production bundle exec jekyll build cd blog && JEKYLL_ENV=production bundle exec jekyll build

View file

@ -7,9 +7,8 @@ import urllib.parse
import mongoengine as mongo import mongoengine as mongo
import redis import redis
from apns2.client import APNsClient from pyapns_client import APNSClient, IOSPayloadAlert, IOSPayload, IOSNotification
from apns2.errors import BadDeviceToken, DeviceTokenNotForTopic, Unregistered from pyapns_client import APNSDeviceException, APNSServerException, APNSProgrammingException, UnregisteredException
from apns2.payload import Payload
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import User from django.contrib.auth.models import User
@ -322,21 +321,23 @@ class MUserFeedNotification(mongo.Document):
# 8. Verify: openssl s_client -connect gateway.push.apple.com:2195 -cert aps.p12.pem # 8. Verify: openssl s_client -connect gateway.push.apple.com:2195 -cert aps.p12.pem
# 9. Deploy: aps -l work -t apns,repo,celery # 9. Deploy: aps -l work -t apns,repo,celery
# Using token-based authentication (modern method) # Using token-based authentication (modern method with pyapns-client)
key_file_path = "/srv/newsblur/config/certificates/apns_key.p8" key_file_path = "/srv/newsblur/config/certificates/apns_key.p8"
apns = APNsClient(
team_id=settings.APNS_TEAM_ID,
auth_key_id=settings.APNS_KEY_ID,
auth_key_path=key_file_path,
use_sandbox=tokens.use_sandbox
)
notification_title_only = is_true(user.profile.preference_value("notification_title_only")) notification_title_only = is_true(user.profile.preference_value("notification_title_only"))
title, subtitle, body = self.title_and_body(story, usersub, notification_title_only) title, subtitle, body = self.title_and_body(story, usersub, notification_title_only)
image_url = None image_url = None
if len(story["image_urls"]): if len(story["image_urls"]):
image_url = story["image_urls"][0] image_url = story["image_urls"][0]
# print image_url
# Create APNS client
apns = APNSClient(
mode=APNSClient.MODE_DEV if tokens.use_sandbox else APNSClient.MODE_PROD,
root_cert_path=None,
auth_key_path=key_file_path,
auth_key_id=settings.APNS_KEY_ID,
team_id=settings.APNS_TEAM_ID
)
confirmed_ios_tokens = [] confirmed_ios_tokens = []
for token in tokens.ios_tokens: for token in tokens.ios_tokens:
@ -345,27 +346,44 @@ class MUserFeedNotification(mongo.Document):
"~BMStory notification by iOS: ~FY~SB%s~SN~BM~FY/~SB%s" "~BMStory notification by iOS: ~FY~SB%s~SN~BM~FY/~SB%s"
% (story["story_title"][:50], usersub.feed.feed_title[:50]), % (story["story_title"][:50], usersub.feed.feed_title[:50]),
) )
payload = Payload(
alert={"title": title, "subtitle": subtitle, "body": body}, # Create payload using helper classes
category="STORY_CATEGORY", alert = IOSPayloadAlert(title=title, subtitle=subtitle, body=body)
mutable_content=True, custom_data = {
custom={
"story_hash": story["story_hash"], "story_hash": story["story_hash"],
"story_feed_id": story["story_feed_id"], "story_feed_id": story["story_feed_id"],
"image_url": image_url, }
}, if image_url:
custom_data["image_url"] = image_url
payload = IOSPayload(
alert=alert,
custom=custom_data,
category="STORY_CATEGORY",
mutable_content=image_url is not None
) )
notification = IOSNotification(payload=payload, topic="com.newsblur.NewsBlur")
try: try:
apns.send_notification(token, payload, topic="com.newsblur.NewsBlur") apns.push(notification=notification, device_token=token)
except (BadDeviceToken, Unregistered, DeviceTokenNotForTopic):
logging.user(user, "~BMiOS token expired: ~FR~SB%s" % (token[:50]))
else:
confirmed_ios_tokens.append(token) confirmed_ios_tokens.append(token)
if settings.DEBUG: if settings.DEBUG:
logging.user( logging.user(
user, user,
"~BMiOS token good: ~FB~SB%s / %s" % (token[:50], len(confirmed_ios_tokens)), "~BMiOS token good: ~FB~SB%s / %s" % (token[:50], len(confirmed_ios_tokens)),
) )
except UnregisteredException as e:
logging.user(user, "~BMiOS token unregistered: ~FR~SB%s (since %s)" % (token[:50], e.timestamp_datetime))
except APNSDeviceException as e:
logging.user(user, "~BMiOS token invalid: ~FR~SB%s" % (token[:50]))
except APNSServerException as e:
logging.user(user, "~BMiOS notification server error: ~FR~SB%s - %s" % (token[:50], str(e)))
except APNSProgrammingException as e:
logging.user(user, "~BMiOS notification programming error: ~FR~SB%s - %s" % (token[:50], str(e)))
except Exception as e:
logging.user(user, "~BMiOS notification error: ~FR~SB%s - %s" % (token[:50], str(e)))
finally:
apns.close()
if len(confirmed_ios_tokens) < len(tokens.ios_tokens): if len(confirmed_ios_tokens) < len(tokens.ios_tokens):
tokens.ios_tokens = confirmed_ios_tokens tokens.ios_tokens = confirmed_ios_tokens

View file

@ -1,5 +1,4 @@
amqp==2.6.1 amqp==2.6.1
apns2==0.7.2
appdirs==1.4.4 appdirs==1.4.4
asgiref==3.3.4 asgiref==3.3.4
attrs==21.1.0 attrs==21.1.0
@ -46,13 +45,8 @@ Flask-BasicAuth==0.2.0
future==0.18.2 future==0.18.2
gunicorn==21.2.0 gunicorn==21.2.0
gevent==22.10.2 gevent==22.10.2
h2==2.6.2
hiredis==1.1.0 hiredis==1.1.0
hpack==3.0.0
httplib2==0.18.1 httplib2==0.18.1
httpx==0.27.2
hyper==0.7.0
hyperframe==3.2.0
idna==2.10 idna==2.10
image==1.5.33 image==1.5.33
iniconfig==1.1.1 iniconfig==1.1.1
@ -81,9 +75,9 @@ pluggy==0.13.1
psutil==5.7.3 psutil==5.7.3
psycopg2==2.9.2 psycopg2==2.9.2
py==1.10.0 py==1.10.0
pyapns-client==2.0.6
pyasn1==0.4.8 pyasn1==0.4.8
pycparser==2.20 pycparser==2.20
PyJWT==1.7.1
pymongo>=3,<4 pymongo>=3,<4
PyMySQL==0.10.1 PyMySQL==0.10.1
pynliner==0.8.0 pynliner==0.8.0

View file

@ -62,6 +62,17 @@ class AppDirectoriesFinder(PipelineAppDirectoriesFinder):
"*README*", "*README*",
] ]
def find_files(self, storage, path=None, all=False):
"""
Override to properly handle wildcard patterns like 'underscore-*.js'
"""
path = path or ''
for pattern in self.find_pattern_matches(path):
for path in storage.listdir(pattern[0])[1]:
if self.is_ignored(path, pattern[0]):
continue
yield path, storage
class FileSystemFinder(PipelineFileSystemFinder): class FileSystemFinder(PipelineFileSystemFinder):
""" """
@ -115,3 +126,14 @@ class FileSystemFinder(PipelineFileSystemFinder):
# 'Gemfile*', # 'Gemfile*',
"node_modules", "node_modules",
] ]
def find_files(self, storage, path=None, all=False):
"""
Override to properly handle wildcard patterns like 'underscore-*.js'
"""
path = path or ''
for pattern in self.find_pattern_matches(path):
for path in storage.listdir(pattern[0])[1]:
if self.is_ignored(path, pattern[0]):
continue
yield path, storage