- Add detailed file-level documentation with @fileoverview tags - Document all classes, methods, interfaces, and properties with JSDoc - Include author attribution (Matthew Raymer) and version information - Add rich logging with tagged console messages throughout codebase - Update all @since dates to reflect actual project timeline (2023-09-06) - Add current documentation update date (2025-07-23) to README - Document singleton patterns, async methods, and security considerations - Add parameter and return value documentation for all functions - Include TypeORM entity documentation with property descriptions - Enhance README with project intent and middleware architecture details Files updated: - src/main.ts: Server entry point and API route documentation - src/notificationService.ts: Push delivery and encryption documentation - src/subscriptionService.ts: Subscription management documentation - src/vapidService.ts: VAPID key generation and authentication docs - src/worker.ts: Background worker thread documentation - src/db.ts: Database service and TypeORM integration docs - src/Subscription.ts: Database entity documentation - src/VapidKeys.ts: VAPID keys entity documentation - README.md: Enhanced project documentation and timeline This commit significantly improves code maintainability and developer onboarding by providing comprehensive documentation for the entire push notification middleware codebase.
174 lines
5.5 KiB
TypeScript
174 lines
5.5 KiB
TypeScript
/**
|
|
* @fileoverview VAPID key management service for Time Safari
|
|
*
|
|
* This service handles the generation, storage, and management of VAPID (Voluntary
|
|
* Application Server Identification) keys used for authenticating push notification
|
|
* requests. It provides secure key generation and JWT-based authentication headers.
|
|
*
|
|
* @author Matthew Raymer
|
|
* @version 1.0.0
|
|
* @since 2023-09-06
|
|
*/
|
|
|
|
import { VapidKeys } from './VapidKeys.js';
|
|
import jwt from 'jsonwebtoken';
|
|
import crypto from 'crypto';
|
|
import DBService from './db.js';
|
|
import { fileURLToPath } from 'url';
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
|
|
/**
|
|
* Interface representing VAPID key pair data
|
|
*
|
|
* @interface VapidKeyData
|
|
* @description Defines the structure of a VAPID key pair with public and private keys
|
|
*/
|
|
export interface VapidKeyData {
|
|
/** Base64-encoded public key for client-side subscription */
|
|
publicKey: string;
|
|
/** Base64-encoded private key for server-side authentication */
|
|
privateKey: string;
|
|
}
|
|
|
|
/**
|
|
* Service class for managing VAPID keys and authentication
|
|
*
|
|
* @class VapidService
|
|
* @description Provides a singleton service for generating, storing, and using VAPID
|
|
* keys for push notification authentication. Implements JWT-based authentication
|
|
* headers for secure push delivery.
|
|
*/
|
|
class VapidService {
|
|
/** Singleton instance of the VAPID service */
|
|
private static instance: VapidService;
|
|
/** Database service for VAPID key persistence */
|
|
private dbService: DBService = DBService.getInstance();
|
|
|
|
/**
|
|
* Private constructor to enforce singleton pattern
|
|
*
|
|
* @private
|
|
* @description Prevents direct instantiation of the service class
|
|
*/
|
|
private constructor() {
|
|
}
|
|
|
|
/**
|
|
* Gets the singleton instance of the VAPID service
|
|
*
|
|
* @returns {VapidService} The singleton instance
|
|
* @static
|
|
* @description Implements the singleton pattern to ensure only one instance
|
|
* of the VAPID service exists throughout the application
|
|
*/
|
|
public static getInstance(): VapidService {
|
|
if (!VapidService.instance) {
|
|
VapidService.instance = new VapidService();
|
|
}
|
|
return VapidService.instance;
|
|
}
|
|
|
|
/**
|
|
* Creates and stores a new VAPID key pair
|
|
*
|
|
* @returns {Promise<VapidKeys>} The created VAPID keys entity
|
|
* @async
|
|
* @description Generates a new ECDH key pair and stores it in the database
|
|
*/
|
|
public async createVAPIDKeys(): Promise<VapidKeys> {
|
|
console.log('[VAPID] Creating new VAPID keys');
|
|
let result = new VapidKeys();
|
|
|
|
if (this.dbService.isReady) {
|
|
const keys = this.generateVAPIDKeys();
|
|
await this.dbService.saveVapidKeys(keys['publicKey'], keys['privateKey']);
|
|
console.log('[VAPID] VAPID keys created and saved successfully');
|
|
} else {
|
|
console.log('[VAPID] Database is not ready, cannot save VAPID keys');
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Generates a new VAPID key pair using ECDH
|
|
*
|
|
* @returns {VapidKeyData} The generated public and private keys
|
|
* @private
|
|
* @description Creates a new ECDH key pair using the P-256 curve for VAPID authentication
|
|
*/
|
|
private generateVAPIDKeys(): VapidKeyData {
|
|
console.log('[VAPID] Generating new ECDH key pair');
|
|
const ecdh = crypto.createECDH('prime256v1');
|
|
ecdh.generateKeys();
|
|
|
|
const result = {
|
|
publicKey: ecdh.getPublicKey().toString('base64'),
|
|
privateKey: ecdh.getPrivateKey().toString('base64')
|
|
};
|
|
|
|
console.log('[VAPID] Key pair generated successfully');
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Retrieves all stored VAPID keys from the database
|
|
*
|
|
* @returns {Promise<VapidKeys[]>} Array of stored VAPID keys
|
|
* @async
|
|
* @description Fetches all VAPID keys from the database for authentication use
|
|
*/
|
|
async getVapidKeys(): Promise<VapidKeys[]> {
|
|
console.log('[VAPID] Retrieving VAPID keys from database');
|
|
let result = await this.dbService.getVapidKeys();
|
|
console.log('[VAPID] Retrieved', result.length, 'VAPID key(s)');
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Creates VAPID authentication headers for push notification requests
|
|
*
|
|
* @param {string} endpoint - The push service endpoint URL
|
|
* @param {number} expiration - JWT expiration time in seconds
|
|
* @param {string} subject - The subject identifier (usually email)
|
|
* @returns {Promise<{ 'Authorization': string, 'Crypto-Key': string }>} VAPID headers
|
|
* @async
|
|
* @description Generates JWT-based authentication headers required for push delivery
|
|
*/
|
|
async createVapidAuthHeader(
|
|
endpoint: string,
|
|
expiration: number,
|
|
subject: string
|
|
): Promise<{ 'Authorization': string, 'Crypto-Key': string }> {
|
|
console.log('[VAPID] Creating authentication headers for endpoint:', endpoint);
|
|
|
|
const vapidKeys = await this.getVapidKeys();
|
|
const { publicKey, privateKey } = vapidKeys[0];
|
|
|
|
// Create JWT payload for VAPID authentication
|
|
const jwtInfo = {
|
|
aud: new URL(endpoint).origin, // Audience (push service origin)
|
|
exp: Math.floor((Date.now() / 1000) + expiration), // Expiration time
|
|
sub: subject // Subject (contact email)
|
|
};
|
|
|
|
// Sign the JWT with the private key using ES256 algorithm
|
|
const jwtToken = jwt.sign(jwtInfo, privateKey, { algorithm: 'ES256' });
|
|
|
|
const headers = {
|
|
'Authorization': `vapid t=${jwtToken}, k=${publicKey}`,
|
|
'Crypto-Key': publicKey
|
|
};
|
|
|
|
console.log('[VAPID] Authentication headers created successfully');
|
|
return headers;
|
|
}
|
|
}
|
|
|
|
// Self-executing function for initialization (currently empty)
|
|
(async ()=> {
|
|
// Future initialization logic can be added here
|
|
})();
|
|
|
|
export default VapidService; |