|
|
@@ -0,0 +1,146 @@
|
|
|
+import assert from 'node:assert/strict';
|
|
|
+import { mkdtempSync } from 'node:fs';
|
|
|
+import { tmpdir } from 'node:os';
|
|
|
+import path from 'node:path';
|
|
|
+import { test } from 'node:test';
|
|
|
+import { DatabaseSync } from 'node:sqlite';
|
|
|
+import {
|
|
|
+ authenticateUser,
|
|
|
+ claimLegacyData,
|
|
|
+ createApiToken,
|
|
|
+ createDomain,
|
|
|
+ createUser,
|
|
|
+ getSmtpCredential,
|
|
|
+ initDatabase,
|
|
|
+ listDomains,
|
|
|
+ listSendEvents,
|
|
|
+ logSendEvent,
|
|
|
+ saveSmtpCredential,
|
|
|
+ seedAdminUser,
|
|
|
+ verifyApiToken,
|
|
|
+ verifySmtpCredential
|
|
|
+} from '../src/db.js';
|
|
|
+
|
|
|
+test('migrates legacy data to the seeded admin user', () => {
|
|
|
+ const dataDir = tempDataDir();
|
|
|
+ const dbPath = path.join(dataDir, 'mailhub.sqlite');
|
|
|
+ const legacy = new DatabaseSync(dbPath);
|
|
|
+ legacy.exec(`
|
|
|
+ CREATE TABLE domains (
|
|
|
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
+ domain TEXT NOT NULL UNIQUE,
|
|
|
+ selector TEXT NOT NULL,
|
|
|
+ verification_token TEXT NOT NULL,
|
|
|
+ dkim_public TEXT NOT NULL,
|
|
|
+ dkim_private TEXT NOT NULL,
|
|
|
+ sender_host TEXT NOT NULL,
|
|
|
+ sending_ip TEXT NOT NULL,
|
|
|
+ spf_extra TEXT NOT NULL DEFAULT '',
|
|
|
+ dmarc_policy TEXT NOT NULL DEFAULT 'none',
|
|
|
+ dmarc_rua TEXT NOT NULL DEFAULT '',
|
|
|
+ status_json TEXT NOT NULL DEFAULT '{}',
|
|
|
+ created_at TEXT NOT NULL,
|
|
|
+ updated_at TEXT NOT NULL
|
|
|
+ );
|
|
|
+ CREATE TABLE send_events (
|
|
|
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
+ domain_id INTEGER,
|
|
|
+ sender TEXT NOT NULL,
|
|
|
+ recipients TEXT NOT NULL,
|
|
|
+ subject TEXT NOT NULL,
|
|
|
+ status TEXT NOT NULL,
|
|
|
+ detail TEXT NOT NULL DEFAULT '',
|
|
|
+ created_at TEXT NOT NULL
|
|
|
+ );
|
|
|
+ CREATE TABLE smtp_credentials (
|
|
|
+ id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
|
+ username TEXT NOT NULL,
|
|
|
+ password_hash TEXT NOT NULL,
|
|
|
+ password_secret TEXT NOT NULL DEFAULT '',
|
|
|
+ created_at TEXT NOT NULL,
|
|
|
+ updated_at TEXT NOT NULL
|
|
|
+ );
|
|
|
+ `);
|
|
|
+ legacy
|
|
|
+ .prepare(`
|
|
|
+ INSERT INTO domains (
|
|
|
+ domain, selector, verification_token, dkim_public, dkim_private,
|
|
|
+ sender_host, sending_ip, spf_extra, dmarc_policy, dmarc_rua, created_at, updated_at
|
|
|
+ ) VALUES ('legacy.example', 'mh', 'tok', 'pub', 'priv', 'mail.legacy.example', '127.0.0.1', '', 'none', '', 'now', 'now')
|
|
|
+ `)
|
|
|
+ .run();
|
|
|
+ legacy
|
|
|
+ .prepare(`
|
|
|
+ INSERT INTO send_events (domain_id, sender, recipients, subject, status, detail, created_at)
|
|
|
+ VALUES (1, 'noreply@legacy.example', '["user@example.com"]', 'hi', 'queued', '', 'now')
|
|
|
+ `)
|
|
|
+ .run();
|
|
|
+ legacy
|
|
|
+ .prepare(`
|
|
|
+ INSERT INTO smtp_credentials (id, username, password_hash, password_secret, created_at, updated_at)
|
|
|
+ VALUES (1, 'legacy-smtp', 'scrypt$salt$hash', '', 'now', 'now')
|
|
|
+ `)
|
|
|
+ .run();
|
|
|
+ legacy.close();
|
|
|
+
|
|
|
+ initDatabase(dataDir, 'test-secret');
|
|
|
+ const admin = seedAdminUser({ username: 'admin', email: 'admin@example.com', password: 'password123' });
|
|
|
+ claimLegacyData(admin.id);
|
|
|
+
|
|
|
+ assert.equal(listDomains(admin.id).length, 1);
|
|
|
+ assert.equal(listSendEvents(admin.id).length, 1);
|
|
|
+ assert.equal(getSmtpCredential(admin.id).username, 'legacy-smtp');
|
|
|
+});
|
|
|
+
|
|
|
+test('isolates domains, smtp credentials, and api tokens by user', () => {
|
|
|
+ initDatabase(tempDataDir(), 'test-secret');
|
|
|
+ const admin = seedAdminUser({ username: 'admin', email: 'admin@example.com', password: 'password123' });
|
|
|
+ claimLegacyData(admin.id);
|
|
|
+ const alice = createUser({ username: 'alice', email: 'alice@example.com', password: 'password123' });
|
|
|
+ const bob = createUser({ username: 'bob', email: 'bob@example.com', password: 'password123' });
|
|
|
+
|
|
|
+ assert.equal(authenticateUser('alice', 'password123').id, alice.id);
|
|
|
+ assert.equal(authenticateUser('alice', 'wrong'), null);
|
|
|
+
|
|
|
+ createDomain(alice.id, domainFixture('alice.example'));
|
|
|
+ assert.equal(listDomains(alice.id).length, 1);
|
|
|
+ assert.equal(listDomains(bob.id).length, 0);
|
|
|
+
|
|
|
+ logSendEvent({
|
|
|
+ userId: alice.id,
|
|
|
+ domainId: listDomains(alice.id)[0].id,
|
|
|
+ sender: 'noreply@alice.example',
|
|
|
+ recipients: ['user@example.com'],
|
|
|
+ subject: 'Hello',
|
|
|
+ status: 'queued'
|
|
|
+ });
|
|
|
+ assert.equal(listSendEvents(alice.id).length, 1);
|
|
|
+ assert.equal(listSendEvents(bob.id).length, 0);
|
|
|
+
|
|
|
+ saveSmtpCredential(alice.id, { username: 'smtp-alice', password: 'copy-me-123' });
|
|
|
+ assert.equal(getSmtpCredential(alice.id, { includePassword: true }).password, 'copy-me-123');
|
|
|
+ assert.equal(verifySmtpCredential('smtp-alice', 'copy-me-123').user.id, alice.id);
|
|
|
+ assert.equal(verifySmtpCredential('smtp-alice', 'wrong'), null);
|
|
|
+
|
|
|
+ const token = createApiToken(alice.id, 'send');
|
|
|
+ assert.equal(verifyApiToken(token.token).id, alice.id);
|
|
|
+});
|
|
|
+
|
|
|
+function domainFixture(domain) {
|
|
|
+ return {
|
|
|
+ domain,
|
|
|
+ selector: 'mh202607',
|
|
|
+ verificationToken: 'token',
|
|
|
+ dkimPublic: 'public',
|
|
|
+ dkimPrivate: 'private',
|
|
|
+ senderHost: `mail.${domain}`,
|
|
|
+ sendingIp: '127.0.0.1',
|
|
|
+ spfExtra: '',
|
|
|
+ dmarcPolicy: 'none',
|
|
|
+ dmarcRua: ''
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+function tempDataDir() {
|
|
|
+ return mkdtempSync(path.join(tmpdir(), 'mailhub-test-'));
|
|
|
+}
|