Browse Source

Checkpoint: Problem with worker metdata for VapidKeys

pull/1/head
Matthew Raymer 1 year ago
parent
commit
5b689125a6
  1. 24
      package-lock.json
  2. 14
      src/VapidKeys.ts
  3. 24
      src/db.ts
  4. 10
      src/main.ts
  5. 15
      src/notificationService.ts
  6. 5
      src/subscriptionService.ts
  7. 44
      src/vapidService.ts
  8. 3
      src/worker.ts

24
package-lock.json

@ -2991,9 +2991,9 @@
} }
}, },
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001520", "version": "1.0.30001521",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001520.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001521.tgz",
"integrity": "sha512-tahF5O9EiiTzwTUqAeFjIZbn4Dnqxzz7ktrgGlMYNLH43Ul26IgTMH/zvL3DG0lZxBYnlT04axvInszUsZULdA==", "integrity": "sha512-fnx1grfpEOvDGH+V17eccmNjucGUnCbP6KL+l5KqBIerp26WK/+RQ7CIDE37KGJjaPyqWXXlFUyKiWmvdNNKmQ==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@ -3596,9 +3596,9 @@
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
}, },
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.4.491", "version": "1.4.492",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.491.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.492.tgz",
"integrity": "sha512-ZzPqGKghdVzlQJ+qpfE+r6EB321zed7e5JsvHIlMM4zPFF8okXUkF5Of7h7F3l3cltPL0rG7YVmlp5Qro7RQLA==", "integrity": "sha512-36K9b/6skMVwAIEsC7GiQ8I8N3soCALVSHqWHzNDtGemAcI9Xu8hP02cywWM0A794rTHm0b0zHPeLJHtgFVamQ==",
"dev": true "dev": true
}, },
"node_modules/elliptic": { "node_modules/elliptic": {
@ -5279,9 +5279,9 @@
} }
}, },
"node_modules/jackspeak": { "node_modules/jackspeak": {
"version": "2.2.3", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.2.3.tgz", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.0.tgz",
"integrity": "sha512-pF0kfjmg8DJLxDrizHoCZGUFz4P4czQ3HyfW4BU0ffebYkzAVlBywp5zaxW/TM+r0sGbmrQdi8EQQVTJFxnGsQ==", "integrity": "sha512-uKmsITSsF4rUWQHzqaRUuyAir3fZfW3f202Ee34lz/gZCi970CPZwyQXLGNgWJvvZbvFyzeyGq0+4fcG/mBKZg==",
"dependencies": { "dependencies": {
"@isaacs/cliui": "^8.0.2" "@isaacs/cliui": "^8.0.2"
}, },
@ -7297,9 +7297,9 @@
} }
}, },
"node_modules/prettier": { "node_modules/prettier": {
"version": "3.0.1", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.1.tgz", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.2.tgz",
"integrity": "sha512-fcOWSnnpCrovBsmFZIGIy9UqK2FaI7Hqax+DIO0A9UxeVoY4iweyaFjS5TavZN97Hfehph0nhsZnjlVKzEQSrQ==", "integrity": "sha512-o2YR9qtniXvwEZlOKbveKfDQVyqxbEIWn48Z8m3ZJjBjcCmUy3xZGIv+7AkaeuaTr6yPXJjwv07ZWlsWbEy1rQ==",
"dev": true, "dev": true,
"bin": { "bin": {
"prettier": "bin/prettier.cjs" "prettier": "bin/prettier.cjs"

14
src/VapidKeys.ts

@ -0,0 +1,14 @@
// Subscription.ts
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
@Entity()
export class VapidKeys {
@PrimaryGeneratedColumn()
id: number;
@Column()
publicKey: string;
@Column()
privateKey: string;
}

24
src/db.ts

@ -1,10 +1,13 @@
import { DataSource, Repository } from "typeorm"; import { DataSource, Repository } from "typeorm";
import {Subscription} from './Subscription.js' import {Subscription} from './Subscription.js'
import { VapidKeys } from './VapidKeys.js';
export class DBService { export class DBService {
private dataSource: DataSource; private dataSource: DataSource;
private vapidSource: DataSource;
private subscriptionRepository: Repository<Subscription>; private subscriptionRepository: Repository<Subscription>;
private vapidRepository: Repository<VapidKeys>;
constructor() { constructor() {
this.dataSource = new DataSource({ this.dataSource = new DataSource({
@ -12,7 +15,15 @@ export class DBService {
database: "subscriptions", database: "subscriptions",
entities: [Subscription], entities: [Subscription],
}); });
this.dataSource.getRepository(Subscription) this.subscriptionRepository = this.dataSource.getRepository(Subscription)
this.vapidSource = new DataSource({
type: "sqlite",
database: "vapidKeys",
entities: [VapidKeys],
});
this.vapidRepository = this.vapidSource.getRepository(VapidKeys);
} }
async saveSubscription(endpoint: string, keys_p256dh: string, keys_auth: string) { async saveSubscription(endpoint: string, keys_p256dh: string, keys_auth: string) {
@ -26,4 +37,15 @@ export class DBService {
async getSubscriptions(): Promise<Subscription[]> { async getSubscriptions(): Promise<Subscription[]> {
return this.subscriptionRepository.find(); return this.subscriptionRepository.find();
} }
async getVapidKeys(): Promise<VapidKeys[]> {
return this.vapidRepository.find();
}
async saveVapidKeys(publicKey: string, privateKey: string) {
const vapidkeys = new VapidKeys();
vapidkeys.privateKey = privateKey;
vapidkeys.publicKey = publicKey;
return this.vapidRepository.save(vapidkeys);
}
} }

10
src/main.ts

@ -1,4 +1,4 @@
import { Subscription, SubscriptionService } from './subscriptionService.js'; import { SubscriptionService } from './subscriptionService.js';
import express, { Express, Request, Response } from 'express'; import express, { Express, Request, Response } from 'express';
import { Worker } from 'worker_threads'; import { Worker } from 'worker_threads';
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
@ -7,6 +7,14 @@ 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 {
endpoint: string;
keys: {
p256dh: string;
auth: string;
};
}
class Server { class Server {
private app: Express; private app: Express;
private port: number; private port: number;

15
src/notificationService.ts

@ -1,8 +1,10 @@
import { SubscriptionService, Subscription } from './subscriptionService.js'; import { SubscriptionService } from './subscriptionService.js';
import { VapidService, VapidKeys } from './vapidService.js'; import { VapidService } from './vapidService.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"
export interface Message { export interface Message {
title: string; title: string;
@ -14,7 +16,6 @@ export class NotificationService {
private subscriptionService: SubscriptionService; private subscriptionService: SubscriptionService;
private vapidService: VapidService; private vapidService: VapidService;
private vapidKeys: VapidKeys;
constructor() { constructor() {
this.subscriptionService = new SubscriptionService(); this.subscriptionService = new SubscriptionService();
@ -27,7 +28,6 @@ export class NotificationService {
async broadcast(message: Message): Promise<void> { async broadcast(message: Message): Promise<void> {
const subscriptions = await this.subscriptionService.fetchSubscriptions(); const subscriptions = await this.subscriptionService.fetchSubscriptions();
this.vapidKeys = await this.vapidService.getVapidKeys();
for (const subscription of subscriptions) { for (const subscription of subscriptions) {
await this.pushToEndpoint(subscription, message); await this.pushToEndpoint(subscription, message);
@ -35,14 +35,13 @@ export class NotificationService {
} }
async sendNotification(subscription: Subscription, message: Message) { async sendNotification(subscription: Subscription, message: Message) {
this.vapidKeys = await this.vapidService.getVapidKeys();
await this.pushToEndpoint(subscription, message); await this.pushToEndpoint(subscription, message);
} }
private async pushToEndpoint(subscription: Subscription, message: Message): Promise<void> { private async pushToEndpoint(subscription: Subscription, message: Message): Promise<void> {
const payload = JSON.stringify(message); const payload = JSON.stringify(message);
const encrypted = this.encrypt(subscription.keys.p256dh, subscription.keys.auth, payload); const encrypted = this.encrypt(subscription.keys_p256dh, subscription.keys_auth, payload);
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');
@ -86,12 +85,12 @@ export class NotificationService {
'auth': auth 'auth': auth
}; };
const vapidKeys: VapidKeys = this.vapidService.getVapidKeys()[0];
return http_ece.encrypt(payload, { return http_ece.encrypt(payload, {
'salt': this.generateSalt(), 'salt': this.generateSalt(),
'dh': this.vapidKeys.publicKey, 'dh': vapidKeys.publicKey,
'keyid': 'p256dh', 'keyid': 'p256dh',
'contentEncoding': 'aes128gcm' 'contentEncoding': 'aes128gcm'
}); });
} }
} }

5
src/subscriptionService.ts

@ -1,6 +1,7 @@
import { DBService } from './db.js'; import { DBService } from './db.js';
import { Subscription } from './Subscription.js';
export interface Subscription { export interface SubscriptionData {
endpoint: string; endpoint: string;
keys: { keys: {
p256dh: string; p256dh: string;
@ -15,7 +16,7 @@ export class SubscriptionService {
this.dbService = new DBService(); this.dbService = new DBService();
} }
async addSubscription(subscription: Subscription): Promise<void> { async addSubscription(subscription: SubscriptionData): Promise<void> {
await this.dbService.saveSubscription( await this.dbService.saveSubscription(
subscription.endpoint, subscription.endpoint,
subscription.keys.p256dh, subscription.keys.p256dh,

44
src/vapidService.ts

@ -1,25 +1,24 @@
import { Database } from 'sqlite3';
import jwt from 'jsonwebtoken'; import jwt from 'jsonwebtoken';
import crypto from 'crypto'; import crypto from 'crypto';
import { DBService } from './db.js';
import { VapidKeys } from './VapidKeys.js';
export interface VapidKeys { export interface VapidKeyData {
publicKey: string; publicKey: string;
privateKey: string; privateKey: string;
} }
export class VapidService { export class VapidService {
private db: Database; private dbService: DBService;
constructor() { constructor() {
this.db = new Database('vapidKeys.db'); this.dbService = new DBService();
this.initializeDatabase(); const keys = this.generateVAPIDKeys();
} this.addVapidKeys(keys);
private initializeDatabase(): void {
this.db.run(`CREATE TABLE IF NOT EXISTS vapid (id INTEGER PRIMARY KEY, publicKey TEXT, privateKey TEXT)`);
} }
private generateVAPIDKeys(): VapidKeys { private generateVAPIDKeys(): VapidKeyData {
const ecdh = crypto.createECDH('prime256v1'); const ecdh = crypto.createECDH('prime256v1');
ecdh.generateKeys(); ecdh.generateKeys();
@ -29,26 +28,19 @@ export class VapidService {
}; };
} }
async getVapidKeys(): Promise<VapidKeys> { async addVapidKeys(vapidkeys: VapidKeyData) {
return new Promise((resolve, reject) => { const keys = await this.getVapidKeys();
this.db.get('SELECT publicKey, privateKey FROM vapid WHERE id = ?', [1], (err, row: VapidKeys) => { if (keys.length == 0) {
if (err) reject(err); this.dbService.saveVapidKeys(vapidkeys.publicKey, vapidkeys.privateKey);
if (row) {
resolve({ publicKey: row.publicKey, privateKey: row.privateKey });
} else {
const keys = this.generateVAPIDKeys();
this.db.run('INSERT INTO vapid (publicKey, privateKey) VALUES (?, ?)', [keys.publicKey, keys.privateKey], (err) => {
if (err) reject(err);
resolve(keys);
});
} }
}); }
});
async getVapidKeys(): Promise<VapidKeys[]> {
return this.dbService.getVapidKeys();
} }
async createVapidAuthHeader(endpoint: string, expiration: number, subject: string): Promise<{ 'Authorization': string, 'Crypto-Key': string }> { async createVapidAuthHeader(endpoint: string, expiration: number, subject: string): Promise<{ 'Authorization': string, 'Crypto-Key': string }> {
const { publicKey, privateKey } = await this.getVapidKeys(); const { publicKey, privateKey } = await this.getVapidKeys()[0];
const jwtInfo = { const jwtInfo = {
aud: new URL(endpoint).origin, aud: new URL(endpoint).origin,

3
src/worker.ts

@ -8,8 +8,9 @@ class WorkerThread {
constructor(interval: number) { constructor(interval: number) {
this.interval = interval; this.interval = interval;
this.notificationService = new NotificationService(); this.notificationService = new NotificationService();
this.message.title = "Check the app."; this.message = { "title": "Check the app." };
this.startPeriodicTask(); this.startPeriodicTask();
} }

Loading…
Cancel
Save