Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ecde954b7 |
33
SECURITY_CHECKLIST.md
Normal file
33
SECURITY_CHECKLIST.md
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# Security Audit Checklist for Web Push Service
|
||||||
|
|
||||||
|
## Authentication & Authorization
|
||||||
|
- [x] Basic auth implemented for admin endpoints
|
||||||
|
- [x] VAPID authentication for push notifications
|
||||||
|
- [x] Environment variable for admin password
|
||||||
|
- [ ] Consider rate limiting for subscription endpoints
|
||||||
|
- [ ] Consider adding API key authentication for public endpoints
|
||||||
|
|
||||||
|
## Data Validation
|
||||||
|
- [x] Input validation for subscription data
|
||||||
|
- [x] Message size limits (100 chars)
|
||||||
|
- [x] Notification type validation
|
||||||
|
- [ ] Consider adding input sanitization for messages
|
||||||
|
|
||||||
|
## Database Security
|
||||||
|
- [x] SQLite database with configurable path
|
||||||
|
- [x] No raw SQL queries (uses SQLAlchemy ORM)
|
||||||
|
- [ ] Consider adding database connection pooling
|
||||||
|
- [ ] Consider encryption at rest for sensitive data
|
||||||
|
|
||||||
|
## Push Notification Security
|
||||||
|
- [x] VAPID key rotation capability
|
||||||
|
- [x] Secure key generation using cryptography library
|
||||||
|
- [x] Proper error handling for expired subscriptions
|
||||||
|
- [ ] Consider adding payload encryption
|
||||||
|
|
||||||
|
## General Security
|
||||||
|
- [x] Type hints for better code safety
|
||||||
|
- [x] Error logging implemented
|
||||||
|
- [ ] Consider adding request logging
|
||||||
|
- [ ] Consider adding CORS protection
|
||||||
|
- [ ] Consider adding CSP headers
|
||||||
67
models.py
67
models.py
@@ -1,19 +1,86 @@
|
|||||||
|
"""
|
||||||
|
Database Models for Web Push Notification Service
|
||||||
|
|
||||||
|
This module defines the SQLAlchemy models for managing web push notifications,
|
||||||
|
including VAPID keys, user subscriptions, and application settings.
|
||||||
|
|
||||||
|
Author: Matthew Raymer
|
||||||
|
Created: 2025
|
||||||
|
"""
|
||||||
|
|
||||||
from flask_sqlalchemy import SQLAlchemy
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
db = SQLAlchemy()
|
db = SQLAlchemy()
|
||||||
|
|
||||||
class VAPIDKey(db.Model):
|
class VAPIDKey(db.Model):
|
||||||
|
"""
|
||||||
|
Stores VAPID (Voluntary Application Server Identification) keys for
|
||||||
|
web push authentication.
|
||||||
|
|
||||||
|
VAPID keys are used to identify the application server to push services
|
||||||
|
and establish a trust relationship for sending notifications.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
id (int): Primary key identifier
|
||||||
|
public_key (str): Base64-encoded public VAPID key (max 255 chars)
|
||||||
|
private_key (str): Base64-encoded private VAPID key (max 255 chars)
|
||||||
|
subscriptions (List[Subscription]): Related push notification subscriptions
|
||||||
|
"""
|
||||||
|
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
public_key = db.Column(db.String(255), nullable=False)
|
public_key = db.Column(db.String(255), nullable=False)
|
||||||
private_key = db.Column(db.String(255), nullable=False)
|
private_key = db.Column(db.String(255), nullable=False)
|
||||||
subscriptions = db.relationship('Subscription', backref='vapid_key', lazy=True)
|
subscriptions = db.relationship('Subscription', backref='vapid_key', lazy=True)
|
||||||
|
|
||||||
class Settings(db.Model):
|
class Settings(db.Model):
|
||||||
|
"""
|
||||||
|
Application settings and state management for notification processing.
|
||||||
|
|
||||||
|
Tracks the execution state of notification jobs to prevent duplicate
|
||||||
|
processing and maintain notification history.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
id (int): Primary key identifier
|
||||||
|
prev_notify_end_time (str): ISO 8601 timestamp of last completed notification run
|
||||||
|
running_notify_end_time (Optional[str]): ISO 8601 timestamp of currently running
|
||||||
|
notification job, or None if no job is running
|
||||||
|
"""
|
||||||
|
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
prev_notify_end_time = db.Column(db.String(29), nullable=False)
|
prev_notify_end_time = db.Column(db.String(29), nullable=False)
|
||||||
running_notify_end_time = db.Column(db.String(29), nullable=True)
|
running_notify_end_time = db.Column(db.String(29), nullable=True)
|
||||||
|
|
||||||
class Subscription(db.Model):
|
class Subscription(db.Model):
|
||||||
|
"""
|
||||||
|
User subscription details for web push notifications.
|
||||||
|
|
||||||
|
Stores the necessary information to send push notifications to a specific
|
||||||
|
browser/device, including encryption keys and notification preferences.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
id (int): Primary key identifier
|
||||||
|
auth (str): Authentication secret for push encryption (max 255 chars)
|
||||||
|
created_date (Optional[str]): ISO 8601 timestamp of subscription creation
|
||||||
|
endpoint (str): Push service URL for this subscription (max 500 chars)
|
||||||
|
message (Optional[str]): Custom message for direct notifications (max 100 chars)
|
||||||
|
notify_time (str): Daily notification time in "HH:MM" format (UTC)
|
||||||
|
notify_type (Optional[str]): Type of notification subscription
|
||||||
|
Valid values:
|
||||||
|
- 'DAILY_CHECK': Regular daily notifications
|
||||||
|
- 'DIRECT_NOTIFICATION': One-time direct notifications
|
||||||
|
p256dh (str): Client's public key for push encryption (max 255 chars)
|
||||||
|
vapid_key_id (int): Foreign key reference to associated VAPID key
|
||||||
|
|
||||||
|
Relationships:
|
||||||
|
vapid_key: References the VAPID key used for this subscription
|
||||||
|
|
||||||
|
Note:
|
||||||
|
The endpoint URL is unique per browser/device/user combination and
|
||||||
|
serves as the primary identifier for the subscription from the
|
||||||
|
push service's perspective.
|
||||||
|
"""
|
||||||
|
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
auth = db.Column(db.String(255), nullable=False)
|
auth = db.Column(db.String(255), nullable=False)
|
||||||
created_date = db.Column(db.String(29), nullable=True)
|
created_date = db.Column(db.String(29), nullable=True)
|
||||||
|
|||||||
@@ -3,3 +3,4 @@ flask>=2.0.0
|
|||||||
flask_sqlalchemy
|
flask_sqlalchemy
|
||||||
pywebpush
|
pywebpush
|
||||||
gunicorn
|
gunicorn
|
||||||
|
structlog>=24.1.0
|
||||||
|
|||||||
Reference in New Issue
Block a user