mirror of
https://gitlab.com/soapbox-pub/ditto.git
synced 2025-12-06 03:19: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",
|
||||
"trends": "deno run -A scripts/trends.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": [
|
||||
"cron",
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { generateVapidKeys } from '@negrel/webpush';
|
||||
import { encodeBase64 } from '@std/encoding/base64';
|
||||
import { exists } from '@std/fs/exists';
|
||||
import { generateSecretKey, nip19 } from 'nostr-tools';
|
||||
import question from 'question-deno';
|
||||
|
|
@ -95,6 +97,15 @@ if (vars.DITTO_UPLOADER === 'local') {
|
|||
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...');
|
||||
|
||||
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';
|
||||
|
||||
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) {
|
||||
this._server = (async () => {
|
||||
const store = await Storages.db();
|
||||
const meta = await getInstanceMetadata(store);
|
||||
const keys = await Conf.vapidKeys;
|
||||
|
||||
if (keys) {
|
||||
return await ApplicationServer.new({
|
||||
contactInformation: `mailto:${meta.email}`,
|
||||
vapidKeys: await Conf.vapidKeys,
|
||||
vapidKeys: keys,
|
||||
});
|
||||
} else {
|
||||
console.warn('VAPID keys are not set. Push notifications will be disabled.');
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
|
|
@ -29,6 +34,11 @@ export class DittoPush {
|
|||
opts: PushMessageOptions = {},
|
||||
): Promise<void> {
|
||||
const server = await this.server;
|
||||
|
||||
if (!server) {
|
||||
return;
|
||||
}
|
||||
|
||||
const subscriber = new PushSubscriber(server, subscription);
|
||||
const text = JSON.stringify(json);
|
||||
return subscriber.pushTextMessage(text, opts);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
import os from 'node:os';
|
||||
import ISO6391, { LanguageCode } from 'iso-639-1';
|
||||
import { generateVapidKeys } from '@negrel/webpush';
|
||||
import * as dotenv from '@std/dotenv';
|
||||
import { getPublicKey, nip19 } from 'nostr-tools';
|
||||
import { z } from 'zod';
|
||||
import { decodeBase64, encodeBase64 } from '@std/encoding/base64';
|
||||
|
||||
import { getEcdsaPublicKey } from '@/utils/crypto.ts';
|
||||
|
||||
/** Load environment config from `.env` */
|
||||
await dotenv.load({
|
||||
|
|
@ -83,8 +85,42 @@ class Conf {
|
|||
static get pgliteDebug(): 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> {
|
||||
return generateVapidKeys(); // FIXME: get the key from environment.
|
||||
private static _vapidPublicKey: Promise<string | undefined> | undefined;
|
||||
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 = {
|
||||
/** Database query timeout configurations. */
|
||||
|
|
@ -107,21 +143,6 @@ class Conf {
|
|||
static get captchaTTL(): number {
|
||||
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. */
|
||||
static get postCharLimit(): number {
|
||||
return Number(Deno.env.get('POST_CHAR_LIMIT') || 5000);
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ const instanceV2Controller: AppController = async (c) => {
|
|||
streaming: `${wsProtocol}//${host}`,
|
||||
},
|
||||
vapid: {
|
||||
public_key: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=',
|
||||
public_key: await Conf.vapidPublicKey,
|
||||
},
|
||||
accounts: {
|
||||
max_featured_tags: 10,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { nip19 } from 'nostr-tools';
|
|||
import { z } from 'zod';
|
||||
|
||||
import { AppController } from '@/app.ts';
|
||||
import { Conf } from '@/config.ts';
|
||||
import { Storages } from '@/storages.ts';
|
||||
import { parseBody } from '@/utils/api.ts';
|
||||
import { getTokenHash } from '@/utils/auth.ts';
|
||||
|
|
@ -76,6 +77,6 @@ export const pushSubscribeController: AppController = async (c) => {
|
|||
endpoint: subscription.endpoint,
|
||||
alerts: data?.alerts ?? {},
|
||||
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> {
|
||||
await db.schema
|
||||
.createTable('push_subscriptions')
|
||||
.addColumn('id', 'bigint', (c) => c.primaryKey().autoIncrement())
|
||||
.addColumn('id', 'bigserial', (c) => c.primaryKey())
|
||||
.addColumn('pubkey', 'char(64)', (c) => c.notNull())
|
||||
.addColumn('token_hash', 'bytea', (c) => c.references('auth_tokens.token_hash').onDelete('cascade').notNull())
|
||||
.addColumn('endpoint', 'text', (c) => c.notNull())
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue