Files
pwa-push-server/src/vapidService.ts
Matthew Raymer ecc2f809bc docs: Add comprehensive JSDoc documentation to all source files
- 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.
2025-07-23 07:36:37 +00:00

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;