mirror of
https://gitlab.com/soapbox-pub/ditto.git
synced 2025-12-06 11:29:46 +00:00
Load VAPID keys from configuration
This commit is contained in:
parent
8f437839d0
commit
198ec973b6
8 changed files with 79 additions and 28 deletions
|
|
@ -20,7 +20,8 @@
|
||||||
"soapbox": "curl -O https://dl.soapbox.pub/main/soapbox.zip && mkdir -p public && mv soapbox.zip public/ && cd public/ && unzip -o soapbox.zip && rm soapbox.zip",
|
"soapbox": "curl -O https://dl.soapbox.pub/main/soapbox.zip && mkdir -p public && mv soapbox.zip public/ && cd public/ && unzip -o soapbox.zip && rm soapbox.zip",
|
||||||
"trends": "deno run -A scripts/trends.ts",
|
"trends": "deno run -A scripts/trends.ts",
|
||||||
"clean:deps": "deno cache --reload src/app.ts",
|
"clean:deps": "deno cache --reload src/app.ts",
|
||||||
"db:populate-search": "deno run -A scripts/db-populate-search.ts"
|
"db:populate-search": "deno run -A scripts/db-populate-search.ts",
|
||||||
|
"vapid": "deno run -A scripts/vapid.ts"
|
||||||
},
|
},
|
||||||
"unstable": [
|
"unstable": [
|
||||||
"cron",
|
"cron",
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { generateVapidKeys } from '@negrel/webpush';
|
||||||
|
import { encodeBase64 } from '@std/encoding/base64';
|
||||||
import { exists } from '@std/fs/exists';
|
import { exists } from '@std/fs/exists';
|
||||||
import { generateSecretKey, nip19 } from 'nostr-tools';
|
import { generateSecretKey, nip19 } from 'nostr-tools';
|
||||||
import question from 'question-deno';
|
import question from 'question-deno';
|
||||||
|
|
@ -95,6 +97,15 @@ if (vars.DITTO_UPLOADER === 'local') {
|
||||||
vars.MEDIA_DOMAIN = `https://${mediaDomain}`;
|
vars.MEDIA_DOMAIN = `https://${mediaDomain}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const VAPID_PRIVATE_KEY = Deno.env.get('VAPID_PRIVATE_KEY');
|
||||||
|
if (VAPID_PRIVATE_KEY) {
|
||||||
|
vars.VAPID_PRIVATE_KEY = VAPID_PRIVATE_KEY;
|
||||||
|
} else {
|
||||||
|
const { privateKey } = await generateVapidKeys({ extractable: true });
|
||||||
|
const bytes = await crypto.subtle.exportKey('pkcs8', privateKey);
|
||||||
|
vars.VAPID_PRIVATE_KEY = encodeBase64(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
console.log('Writing to .env file...');
|
console.log('Writing to .env file...');
|
||||||
|
|
||||||
const result = Object.entries(vars).reduce((acc, [key, value]) => {
|
const result = Object.entries(vars).reduce((acc, [key, value]) => {
|
||||||
|
|
|
||||||
7
scripts/vapid.ts
Normal file
7
scripts/vapid.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { generateVapidKeys } from '@negrel/webpush';
|
||||||
|
import { encodeBase64 } from '@std/encoding/base64';
|
||||||
|
|
||||||
|
const { privateKey } = await generateVapidKeys({ extractable: true });
|
||||||
|
const bytes = await crypto.subtle.exportKey('pkcs8', privateKey);
|
||||||
|
|
||||||
|
console.log(encodeBase64(bytes));
|
||||||
|
|
@ -5,18 +5,23 @@ import { Storages } from '@/storages.ts';
|
||||||
import { getInstanceMetadata } from '@/utils/instance.ts';
|
import { getInstanceMetadata } from '@/utils/instance.ts';
|
||||||
|
|
||||||
export class DittoPush {
|
export class DittoPush {
|
||||||
static _server: Promise<ApplicationServer> | undefined;
|
static _server: Promise<ApplicationServer | undefined> | undefined;
|
||||||
|
|
||||||
static get server(): Promise<ApplicationServer> {
|
static get server(): Promise<ApplicationServer | undefined> {
|
||||||
if (!this._server) {
|
if (!this._server) {
|
||||||
this._server = (async () => {
|
this._server = (async () => {
|
||||||
const store = await Storages.db();
|
const store = await Storages.db();
|
||||||
const meta = await getInstanceMetadata(store);
|
const meta = await getInstanceMetadata(store);
|
||||||
|
const keys = await Conf.vapidKeys;
|
||||||
|
|
||||||
return await ApplicationServer.new({
|
if (keys) {
|
||||||
contactInformation: `mailto:${meta.email}`,
|
return await ApplicationServer.new({
|
||||||
vapidKeys: await Conf.vapidKeys,
|
contactInformation: `mailto:${meta.email}`,
|
||||||
});
|
vapidKeys: keys,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.warn('VAPID keys are not set. Push notifications will be disabled.');
|
||||||
|
}
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -29,6 +34,11 @@ export class DittoPush {
|
||||||
opts: PushMessageOptions = {},
|
opts: PushMessageOptions = {},
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const server = await this.server;
|
const server = await this.server;
|
||||||
|
|
||||||
|
if (!server) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const subscriber = new PushSubscriber(server, subscription);
|
const subscriber = new PushSubscriber(server, subscription);
|
||||||
const text = JSON.stringify(json);
|
const text = JSON.stringify(json);
|
||||||
return subscriber.pushTextMessage(text, opts);
|
return subscriber.pushTextMessage(text, opts);
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
import os from 'node:os';
|
import os from 'node:os';
|
||||||
import ISO6391, { LanguageCode } from 'iso-639-1';
|
import ISO6391, { LanguageCode } from 'iso-639-1';
|
||||||
import { generateVapidKeys } from '@negrel/webpush';
|
|
||||||
import * as dotenv from '@std/dotenv';
|
import * as dotenv from '@std/dotenv';
|
||||||
import { getPublicKey, nip19 } from 'nostr-tools';
|
import { getPublicKey, nip19 } from 'nostr-tools';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
import { decodeBase64, encodeBase64 } from '@std/encoding/base64';
|
||||||
|
|
||||||
|
import { getEcdsaPublicKey } from '@/utils/crypto.ts';
|
||||||
|
|
||||||
/** Load environment config from `.env` */
|
/** Load environment config from `.env` */
|
||||||
await dotenv.load({
|
await dotenv.load({
|
||||||
|
|
@ -83,8 +85,42 @@ class Conf {
|
||||||
static get pgliteDebug(): 0 | 1 | 2 | 3 | 4 | 5 {
|
static get pgliteDebug(): 0 | 1 | 2 | 3 | 4 | 5 {
|
||||||
return Number(Deno.env.get('PGLITE_DEBUG') || 0) as 0 | 1 | 2 | 3 | 4 | 5;
|
return Number(Deno.env.get('PGLITE_DEBUG') || 0) as 0 | 1 | 2 | 3 | 4 | 5;
|
||||||
}
|
}
|
||||||
static get vapidKeys(): Promise<CryptoKeyPair> {
|
private static _vapidPublicKey: Promise<string | undefined> | undefined;
|
||||||
return generateVapidKeys(); // FIXME: get the key from environment.
|
static get vapidPublicKey(): Promise<string | undefined> {
|
||||||
|
if (!this._vapidPublicKey) {
|
||||||
|
this._vapidPublicKey = (async () => {
|
||||||
|
const keys = await Conf.vapidKeys;
|
||||||
|
if (keys) {
|
||||||
|
const { publicKey } = keys;
|
||||||
|
const bytes = await crypto.subtle.exportKey('raw', publicKey);
|
||||||
|
return encodeBase64(bytes);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._vapidPublicKey;
|
||||||
|
}
|
||||||
|
static get vapidKeys(): Promise<CryptoKeyPair | undefined> {
|
||||||
|
return (async () => {
|
||||||
|
const encoded = Deno.env.get('VAPID_PRIVATE_KEY');
|
||||||
|
|
||||||
|
if (!encoded) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyData = decodeBase64(encoded);
|
||||||
|
|
||||||
|
const privateKey = await crypto.subtle.importKey(
|
||||||
|
'pkcs8',
|
||||||
|
keyData,
|
||||||
|
{ name: 'ECDSA', namedCurve: 'P-256' },
|
||||||
|
true,
|
||||||
|
['sign'],
|
||||||
|
);
|
||||||
|
const publicKey = await getEcdsaPublicKey(privateKey, true);
|
||||||
|
|
||||||
|
return { privateKey, publicKey };
|
||||||
|
})();
|
||||||
}
|
}
|
||||||
static db = {
|
static db = {
|
||||||
/** Database query timeout configurations. */
|
/** Database query timeout configurations. */
|
||||||
|
|
@ -107,21 +143,6 @@ class Conf {
|
||||||
static get captchaTTL(): number {
|
static get captchaTTL(): number {
|
||||||
return Number(Deno.env.get('CAPTCHA_TTL') || 5 * 60 * 1000);
|
return Number(Deno.env.get('CAPTCHA_TTL') || 5 * 60 * 1000);
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* BIP-32 derivation paths for different crypto use-cases.
|
|
||||||
* The `DITTO_NSEC` is used as the seed.
|
|
||||||
* Keys can be rotated by changing the derviation path.
|
|
||||||
*/
|
|
||||||
static wallet = {
|
|
||||||
/** Private key for AES-GCM encryption in the Postgres database. */
|
|
||||||
get dbKeyPath(): string {
|
|
||||||
return Deno.env.get('WALLET_DB_KEY_PATH') || "m/0'/1'";
|
|
||||||
},
|
|
||||||
/** VAPID private key path. */
|
|
||||||
get vapidKeyPath(): string {
|
|
||||||
return Deno.env.get('WALLET_VAPID_KEY_PATH') || "m/0'/3'";
|
|
||||||
},
|
|
||||||
};
|
|
||||||
/** Character limit to enforce for posts made through Mastodon API. */
|
/** Character limit to enforce for posts made through Mastodon API. */
|
||||||
static get postCharLimit(): number {
|
static get postCharLimit(): number {
|
||||||
return Number(Deno.env.get('POST_CHAR_LIMIT') || 5000);
|
return Number(Deno.env.get('POST_CHAR_LIMIT') || 5000);
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,7 @@ const instanceV2Controller: AppController = async (c) => {
|
||||||
streaming: `${wsProtocol}//${host}`,
|
streaming: `${wsProtocol}//${host}`,
|
||||||
},
|
},
|
||||||
vapid: {
|
vapid: {
|
||||||
public_key: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=',
|
public_key: await Conf.vapidPublicKey,
|
||||||
},
|
},
|
||||||
accounts: {
|
accounts: {
|
||||||
max_featured_tags: 10,
|
max_featured_tags: 10,
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { nip19 } from 'nostr-tools';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { AppController } from '@/app.ts';
|
import { AppController } from '@/app.ts';
|
||||||
|
import { Conf } from '@/config.ts';
|
||||||
import { Storages } from '@/storages.ts';
|
import { Storages } from '@/storages.ts';
|
||||||
import { parseBody } from '@/utils/api.ts';
|
import { parseBody } from '@/utils/api.ts';
|
||||||
import { getTokenHash } from '@/utils/auth.ts';
|
import { getTokenHash } from '@/utils/auth.ts';
|
||||||
|
|
@ -76,6 +77,6 @@ export const pushSubscribeController: AppController = async (c) => {
|
||||||
endpoint: subscription.endpoint,
|
endpoint: subscription.endpoint,
|
||||||
alerts: data?.alerts ?? {},
|
alerts: data?.alerts ?? {},
|
||||||
policy: data?.policy ?? 'all',
|
policy: data?.policy ?? 'all',
|
||||||
// TODO: server_key
|
server_key: await Conf.vapidPublicKey,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { Kysely, sql } from 'kysely';
|
||||||
export async function up(db: Kysely<any>): Promise<void> {
|
export async function up(db: Kysely<any>): Promise<void> {
|
||||||
await db.schema
|
await db.schema
|
||||||
.createTable('push_subscriptions')
|
.createTable('push_subscriptions')
|
||||||
.addColumn('id', 'bigint', (c) => c.primaryKey().autoIncrement())
|
.addColumn('id', 'bigserial', (c) => c.primaryKey())
|
||||||
.addColumn('pubkey', 'char(64)', (c) => c.notNull())
|
.addColumn('pubkey', 'char(64)', (c) => c.notNull())
|
||||||
.addColumn('token_hash', 'bytea', (c) => c.references('auth_tokens.token_hash').onDelete('cascade').notNull())
|
.addColumn('token_hash', 'bytea', (c) => c.references('auth_tokens.token_hash').onDelete('cascade').notNull())
|
||||||
.addColumn('endpoint', 'text', (c) => c.notNull())
|
.addColumn('endpoint', 'text', (c) => c.notNull())
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue