Browse Source

Debugged encrypt before sending notification

unsubscribe-mute
Matthew Raymer 1 year ago
parent
commit
9075ea8c91
  1. 2
      Dockerfile
  2. 18
      src/main.ts
  3. 74
      src/notificationService.ts
  4. 1
      src/vapidService.ts

2
Dockerfile

@ -3,6 +3,8 @@ FROM node:18.17.1-alpine3.17
RUN mkdir -p /usr/src/app/data RUN mkdir -p /usr/src/app/data
WORKDIR /usr/src/app WORKDIR /usr/src/app
RUN apk add bash
COPY package*.json ./ COPY package*.json ./
COPY tsconfig.json ./ COPY tsconfig.json ./
COPY .eslintrc.json ./ COPY .eslintrc.json ./

18
src/main.ts

@ -14,7 +14,7 @@ import { dirname, join } from 'path';
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename); const __dirname = dirname(__filename);
export interface Subscription { export interface BrowserSubscription {
endpoint: string; endpoint: string;
keys: { keys: {
p256dh: string; p256dh: string;
@ -27,15 +27,13 @@ class Server {
private port: number; private port: number;
private worker?: Worker; private worker?: Worker;
private subscriptionService: SubscriptionService = SubscriptionService.getInstance(); private subscriptionService: SubscriptionService = SubscriptionService.getInstance();
private notificationService: NotificationService; private notificationService: NotificationService = NotificationService.getInstance();
dbService: DBService = DBService.getInstance(); dbService: DBService = DBService.getInstance();
vapidService: VapidService = VapidService.getInstance(); vapidService: VapidService = VapidService.getInstance();
private message: Message;
constructor(port: number) { constructor(port: number) {
this.app = express(); this.app = express();
this.port = port; this.port = port;
this.notificationService = new NotificationService();
this.setupRoutes(); this.setupRoutes();
this.startWorker(); this.startWorker();
@ -46,21 +44,23 @@ class Server {
private setupRoutes(): void { private setupRoutes(): void {
this.app.use(express.json()) this.app.use(express.json())
this.app.post('/web-push/subscribe', async (req: Request, res: Response) => { this.app.post('/web-push/subscribe', async (req: Request, res: Response) => {
const subscription = req.body as Subscription; const subscription = req.body as BrowserSubscription;
const message = { "title": "You are subscribed." } as Message;
console.log("/web-push/subscribe", req.body); console.log("/web-push/subscribe", req.body);
await this.subscriptionService.addSubscription(subscription); await this.subscriptionService.addSubscription(subscription);
await this.notificationService.sendNotification(subscription, message);
res.status(201).send(); res.status(201).send();
}); });
this.app.post('/web-push/unsubscribe', async (req: Request, res: Response) => { this.app.post('/web-push/unsubscribe', async (req: Request, res: Response) => {
const subscription = req.body as Subscription; const subscription = req.body as BrowserSubscription;
console.log(subscription); console.log(subscription);
res.status(501).send(); res.status(501).send();
}); });
this.app.post('/web-push/mute', async (req: Request, res: Response) => { this.app.post('/web-push/mute', async (req: Request, res: Response) => {
const subscription = req.body as Subscription; const subscription = req.body as BrowserSubscription;
console.log(subscription); console.log(subscription);
res.status(501).send(); res.status(501).send();
@ -88,8 +88,7 @@ class Server {
this.worker.on('message', (message) => { this.worker.on('message', (message) => {
console.log(message); console.log(message);
this.message = { "title": "Check TimeSafari"} as Message;
this.notificationService.broadcast(this.message);
}); });
this.worker.on('error', (error) => { this.worker.on('error', (error) => {
@ -137,6 +136,7 @@ const executeAsyncFunction = async () => {
} }
}; };
setTimeout(() => { setTimeout(() => {
executeAsyncFunction().catch(error => { executeAsyncFunction().catch(error => {
// Handle any errors here // Handle any errors here

74
src/notificationService.ts

@ -1,10 +1,10 @@
import SubscriptionService from './subscriptionService.js'; //import SubscriptionService from './subscriptionService.js';
import VapidService from './vapidService.js'; import VapidService from './vapidService.js';
import { VapidKeys } from './VapidKeys.js'; import { VapidKeys } from './VapidKeys.js';
import * as https from 'https'; import * as https from 'https';
import * as http_ece from 'http_ece'; import * as http_ece from 'http_ece';
import crypto from 'crypto'; import crypto from 'crypto';
import { Subscription } from "./Subscription.js" //import { Subscription } from "./Subscription.js"
export interface Message { export interface Message {
title: string; title: string;
@ -12,34 +12,44 @@ export interface Message {
[key: string]: any; [key: string]: any;
} }
export class NotificationService {
private subscriptionService: SubscriptionService = SubscriptionService.getInstance(); export interface BrowserSubscription {
endpoint: string;
keys: {
p256dh: string;
auth: string;
};
}
export class NotificationService {
private static instance: NotificationService;
// private subscriptionService: SubscriptionService = SubscriptionService.getInstance();
private vapidService: VapidService = VapidService.getInstance(); private vapidService: VapidService = VapidService.getInstance();
constructor() { constructor() {
} }
private generateSalt(length = 16): Buffer { public static getInstance(): NotificationService {
return crypto.randomBytes(length); if (!NotificationService.instance) {
NotificationService.instance = new NotificationService();
}
return NotificationService.instance;
} }
async broadcast(message: Message): Promise<void> { private generateSalt(length = 16): Buffer {
const subscriptions = await this.subscriptionService.fetchSubscriptions(); return crypto.randomBytes(length);
for (const subscription of subscriptions) {
await this.pushToEndpoint(subscription, message);
}
} }
async sendNotification(subscription: Subscription, message: Message) { async sendNotification(subscription: BrowserSubscription, message: Message) {
await this.pushToEndpoint(subscription, message); await this.pushToEndpoint(subscription, message);
} }
private async pushToEndpoint(subscription: Subscription, message: Message): Promise<void> { private async pushToEndpoint(subscription: BrowserSubscription, message: Message): Promise<void> {
const payload = JSON.stringify(message); const payloadString = JSON.stringify(message);
const payloadBuffer = Buffer.from(payloadString, 'utf-8');
const encrypted = this.encrypt(subscription.keys_p256dh, subscription.keys_auth, payload); const encrypted = await this.encrypt(subscription.keys.p256dh, subscription.keys.auth, payloadBuffer);
const endpoint = subscription.endpoint; const endpoint = subscription.endpoint;
const vapidHeaders = await this.vapidService.createVapidAuthHeader(endpoint, 12 * 60 * 60, 'mailto:example@example.com'); const vapidHeaders = await this.vapidService.createVapidAuthHeader(endpoint, 12 * 60 * 60, 'mailto:example@example.com');
@ -77,18 +87,32 @@ export class NotificationService {
}); });
} }
private encrypt(publicKey: string, auth: string, payload: string): Buffer {
http_ece.keys = {
'p256dh': publicKey,
'auth': auth
};
const vapidKeys: VapidKeys = this.vapidService.getVapidKeys()[0]; private async encrypt(publicKey: string, auth: string, payload: Buffer): Promise<Buffer> {
try {
console.log('Public Key:', publicKey);
console.log('Auth:', auth);
const vapidKeys: VapidKeys[] = await this.vapidService.getVapidKeys();
const vapidkey: VapidKeys = vapidKeys[0];
const vapidPrivateKeyBase64: string = vapidkey['privateKey'];
console.log(vapidPrivateKeyBase64);
const vapidPrivateKeyBuffer: Buffer = Buffer.from(vapidPrivateKeyBase64, 'base64');
const ecdh = crypto.createECDH('prime256v1');
ecdh.setPrivateKey(vapidPrivateKeyBuffer);
const publicKeyBuffer: Buffer = Buffer.from(publicKey, 'base64');
// const authBuffer: Buffer = Buffer.from(auth, 'base64');
return http_ece.encrypt(payload, { return http_ece.encrypt(payload, {
'salt': this.generateSalt(), 'salt': this.generateSalt(),
'dh': vapidKeys.publicKey, 'privateKey': ecdh, // Your VAPID private key
'keyid': 'p256dh', 'publicKey': publicKeyBuffer, // Client's public key from the subscription object subscription.keys.p256dh
'contentEncoding': 'aes128gcm' 'authSecret': auth // Client's auth secret from the subscription object subscription.keys.auth
}); });
} catch (error) {
console.error('Error encrypting payload:', error);
// Handle the error as appropriate for your application
throw error;
}
} }
} }

1
src/vapidService.ts

@ -66,6 +66,7 @@ class VapidService {
*/ */
async getVapidKeys(): Promise<VapidKeys[]> { async getVapidKeys(): Promise<VapidKeys[]> {
console.log("getVapidKeys");
let result = await this.dbService.getVapidKeys(); let result = await this.dbService.getVapidKeys();
return result; return result;
} }

Loading…
Cancel
Save