db.test.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. import assert from 'node:assert/strict';
  2. import { mkdtempSync } from 'node:fs';
  3. import { tmpdir } from 'node:os';
  4. import path from 'node:path';
  5. import { test } from 'node:test';
  6. import { DatabaseSync } from 'node:sqlite';
  7. import {
  8. authenticateUser,
  9. claimLegacyData,
  10. createApiToken,
  11. createDomain,
  12. createUser,
  13. getSmtpCredential,
  14. initDatabase,
  15. listDomains,
  16. listSendEvents,
  17. logSendEvent,
  18. saveSmtpCredential,
  19. seedAdminUser,
  20. verifyApiToken,
  21. verifySmtpCredential
  22. } from '../src/db.js';
  23. test('migrates legacy data to the seeded admin user', () => {
  24. const dataDir = tempDataDir();
  25. const dbPath = path.join(dataDir, 'mailhub.sqlite');
  26. const legacy = new DatabaseSync(dbPath);
  27. legacy.exec(`
  28. CREATE TABLE domains (
  29. id INTEGER PRIMARY KEY AUTOINCREMENT,
  30. domain TEXT NOT NULL UNIQUE,
  31. selector TEXT NOT NULL,
  32. verification_token TEXT NOT NULL,
  33. dkim_public TEXT NOT NULL,
  34. dkim_private TEXT NOT NULL,
  35. sender_host TEXT NOT NULL,
  36. sending_ip TEXT NOT NULL,
  37. spf_extra TEXT NOT NULL DEFAULT '',
  38. dmarc_policy TEXT NOT NULL DEFAULT 'none',
  39. dmarc_rua TEXT NOT NULL DEFAULT '',
  40. status_json TEXT NOT NULL DEFAULT '{}',
  41. created_at TEXT NOT NULL,
  42. updated_at TEXT NOT NULL
  43. );
  44. CREATE TABLE send_events (
  45. id INTEGER PRIMARY KEY AUTOINCREMENT,
  46. domain_id INTEGER,
  47. sender TEXT NOT NULL,
  48. recipients TEXT NOT NULL,
  49. subject TEXT NOT NULL,
  50. status TEXT NOT NULL,
  51. detail TEXT NOT NULL DEFAULT '',
  52. created_at TEXT NOT NULL
  53. );
  54. CREATE TABLE smtp_credentials (
  55. id INTEGER PRIMARY KEY CHECK (id = 1),
  56. username TEXT NOT NULL,
  57. password_hash TEXT NOT NULL,
  58. password_secret TEXT NOT NULL DEFAULT '',
  59. created_at TEXT NOT NULL,
  60. updated_at TEXT NOT NULL
  61. );
  62. `);
  63. legacy
  64. .prepare(`
  65. INSERT INTO domains (
  66. domain, selector, verification_token, dkim_public, dkim_private,
  67. sender_host, sending_ip, spf_extra, dmarc_policy, dmarc_rua, created_at, updated_at
  68. ) VALUES ('legacy.example', 'mh', 'tok', 'pub', 'priv', 'mail.legacy.example', '127.0.0.1', '', 'none', '', 'now', 'now')
  69. `)
  70. .run();
  71. legacy
  72. .prepare(`
  73. INSERT INTO send_events (domain_id, sender, recipients, subject, status, detail, created_at)
  74. VALUES (1, 'noreply@legacy.example', '["user@example.com"]', 'hi', 'queued', '', 'now')
  75. `)
  76. .run();
  77. legacy
  78. .prepare(`
  79. INSERT INTO smtp_credentials (id, username, password_hash, password_secret, created_at, updated_at)
  80. VALUES (1, 'legacy-smtp', 'scrypt$salt$hash', '', 'now', 'now')
  81. `)
  82. .run();
  83. legacy.close();
  84. initDatabase(dataDir, 'test-secret');
  85. const admin = seedAdminUser({ username: 'admin', email: 'admin@example.com', password: 'password123' });
  86. claimLegacyData(admin.id);
  87. assert.equal(listDomains(admin.id).length, 1);
  88. assert.equal(listSendEvents(admin.id).length, 1);
  89. assert.equal(getSmtpCredential(admin.id).username, 'legacy-smtp');
  90. });
  91. test('isolates domains, smtp credentials, and api tokens by user', () => {
  92. initDatabase(tempDataDir(), 'test-secret');
  93. const admin = seedAdminUser({ username: 'admin', email: 'admin@example.com', password: 'password123' });
  94. claimLegacyData(admin.id);
  95. const alice = createUser({ username: 'alice', email: 'alice@example.com', password: 'password123' });
  96. const bob = createUser({ username: 'bob', email: 'bob@example.com', password: 'password123' });
  97. assert.equal(authenticateUser('alice', 'password123').id, alice.id);
  98. assert.equal(authenticateUser('alice', 'wrong'), null);
  99. createDomain(alice.id, domainFixture('alice.example'));
  100. assert.equal(listDomains(alice.id).length, 1);
  101. assert.equal(listDomains(bob.id).length, 0);
  102. logSendEvent({
  103. userId: alice.id,
  104. domainId: listDomains(alice.id)[0].id,
  105. sender: 'noreply@alice.example',
  106. recipients: ['user@example.com'],
  107. subject: 'Hello',
  108. status: 'queued'
  109. });
  110. assert.equal(listSendEvents(alice.id).length, 1);
  111. assert.equal(listSendEvents(bob.id).length, 0);
  112. saveSmtpCredential(alice.id, { username: 'smtp-alice', password: 'copy-me-123' });
  113. assert.equal(getSmtpCredential(alice.id, { includePassword: true }).password, 'copy-me-123');
  114. assert.equal(verifySmtpCredential('smtp-alice', 'copy-me-123').user.id, alice.id);
  115. assert.equal(verifySmtpCredential('smtp-alice', 'wrong'), null);
  116. const token = createApiToken(alice.id, 'send');
  117. assert.equal(verifyApiToken(token.token).id, alice.id);
  118. });
  119. function domainFixture(domain) {
  120. return {
  121. domain,
  122. selector: 'mh202607',
  123. verificationToken: 'token',
  124. dkimPublic: 'public',
  125. dkimPrivate: 'private',
  126. senderHost: `mail.${domain}`,
  127. sendingIp: '127.0.0.1',
  128. spfExtra: '',
  129. dmarcPolicy: 'none',
  130. dmarcRua: ''
  131. };
  132. }
  133. function tempDataDir() {
  134. return mkdtempSync(path.join(tmpdir(), 'mailhub-test-'));
  135. }