notifkit 0.1.7 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dashboard/README.md +29 -0
  2. package/dashboard/dist/assets/index-7mvXb4bS.js +310 -0
  3. package/dashboard/dist/assets/index-o7L-f3-c.css +1 -0
  4. package/dashboard/dist/favicon.svg +4 -0
  5. package/dashboard/dist/index.html +14 -0
  6. package/dist/index.d.mts +447 -9
  7. package/dist/index.d.mts.map +1 -1
  8. package/dist/index.mjs +2 -2
  9. package/dist/{main-CRPxM-O3.mjs → main-4Wyge9ms.mjs} +2 -2
  10. package/dist/{main-CRPxM-O3.mjs.map → main-4Wyge9ms.mjs.map} +1 -1
  11. package/dist/{main-Bgi2wWck.mjs → main-AO0JaY5z.mjs} +2 -2
  12. package/dist/{main-Bgi2wWck.mjs.map → main-AO0JaY5z.mjs.map} +1 -1
  13. package/dist/{main-DFMHcN_d.mjs → main-BMYSYcv2.mjs} +2 -2
  14. package/dist/{main-DFMHcN_d.mjs.map → main-BMYSYcv2.mjs.map} +1 -1
  15. package/dist/{main-Yorxzw0Z.mjs → main-BPbPnBIG.mjs} +2 -2
  16. package/dist/{main-Yorxzw0Z.mjs.map → main-BPbPnBIG.mjs.map} +1 -1
  17. package/dist/{main-pFAfCkVX.mjs → main-CyeQ_yDt.mjs} +2 -2
  18. package/dist/{main-pFAfCkVX.mjs.map → main-CyeQ_yDt.mjs.map} +1 -1
  19. package/dist/{main-CjbVdUqa.mjs → main-D19Wxs0x.mjs} +2 -2
  20. package/dist/{main-CjbVdUqa.mjs.map → main-D19Wxs0x.mjs.map} +1 -1
  21. package/dist/{main-D6SG3Isk.mjs → main-DSmh1e-V.mjs} +2 -2
  22. package/dist/{main-D6SG3Isk.mjs.map → main-DSmh1e-V.mjs.map} +1 -1
  23. package/dist/{main-CFSukWm8.mjs → main-JdOvJ70b.mjs} +495 -41
  24. package/dist/main-JdOvJ70b.mjs.map +1 -0
  25. package/dist/{src-CPMwsUCJ.mjs → src-CGmXRfsM.mjs} +86 -20
  26. package/dist/src-CGmXRfsM.mjs.map +1 -0
  27. package/drizzle/0005_admin_users.sql +11 -0
  28. package/drizzle/meta/_journal.json +7 -0
  29. package/package.json +8 -3
  30. package/scripts/create-admin.mjs +67 -0
  31. package/src/config/index.ts +12 -0
  32. package/src/db/schema.ts +13 -0
  33. package/src/repositories/index.ts +82 -1
  34. package/src/services/api/admin-static.ts +225 -0
  35. package/src/services/api/handlers.ts +224 -33
  36. package/src/services/api/main.ts +183 -29
  37. package/src/services/api/router.ts +7 -1
  38. package/src/services/auth/index.ts +111 -0
  39. package/dist/main-CFSukWm8.mjs.map +0 -1
  40. package/dist/src-CPMwsUCJ.mjs.map +0 -1
@@ -7,7 +7,7 @@ import { createHmac, randomUUID, timingSafeEqual } from "node:crypto";
7
7
  import postgres from "postgres";
8
8
  import { drizzle } from "drizzle-orm/postgres-js";
9
9
  import { boolean, check, index, integer, jsonb, pgEnum, pgTable, primaryKey, text, time, timestamp, unique, uuid, varchar } from "drizzle-orm/pg-core";
10
- import { and, desc, eq, inArray, sql } from "drizzle-orm";
10
+ import { and, desc, eq, inArray, or, sql } from "drizzle-orm";
11
11
  import { createInsertSchema, createSelectSchema } from "drizzle-zod";
12
12
  import { migrate } from "drizzle-orm/postgres-js/migrator";
13
13
  import { fileURLToPath } from "url";
@@ -69,6 +69,9 @@ const baseConfigSchema = z.object({
69
69
  REDIS_URL: z.string().url().default("redis://localhost:6379"),
70
70
  DATABASE_URL: z.string().url().default("postgres://platform:platform@localhost:5432/notifkit"),
71
71
  ADMIN_API_KEY: z.string().trim().optional(),
72
+ ADMIN_EMAIL: z.string().trim().email().optional(),
73
+ ADMIN_PASSWORD: z.string().trim().min(6).optional(),
74
+ ADMIN_USERNAME: z.string().trim().optional(),
72
75
  WORKER_CONCURRENCY: z.coerce.number().int().min(1).default(10),
73
76
  QUEUE_MAX_LEN: z.coerce.number().int().min(1).default(1e7),
74
77
  DB_MAX_CONNECTIONS: z.coerce.number().int().min(1).default(2),
@@ -87,7 +90,16 @@ const baseConfigSchema = z.object({
87
90
  * permanent: a dead link means the recipient reaches for the spam button
88
91
  * instead, which costs far more than the key ever protected.
89
92
  */
90
- UNSUBSCRIBE_SECRET: z.string().min(16).optional()
93
+ UNSUBSCRIBE_SECRET: z.string().min(16).optional(),
94
+ /**
95
+ * When true, trusts X-Forwarded-For headers from reverse proxies for client IP resolution.
96
+ * Defaults to false to prevent client-spoofed headers from bypassing rate limits.
97
+ */
98
+ TRUST_PROXY: z.coerce.boolean().default(false),
99
+ /**
100
+ * Comma-separated list of allowed origins for CORS on session-authenticated admin routes.
101
+ */
102
+ CORS_ORIGIN: z.string().optional()
91
103
  });
92
104
  let globalConfig = null;
93
105
  function setGlobalConfig(config) {
@@ -673,10 +685,12 @@ registry.define("notification.ai_pending", NotificationAiPendingPayloadSchema);
673
685
  //#endregion
674
686
  //#region src/db/schema.ts
675
687
  var schema_exports = /* @__PURE__ */ __exportAll({
688
+ adminUsers: () => adminUsers,
676
689
  apiKeyRoleEnum: () => apiKeyRoleEnum,
677
690
  channelEnum: () => channelEnum,
678
691
  contactTopicPreferences: () => contactTopicPreferences,
679
692
  deliveryOutbox: () => deliveryOutbox,
693
+ insertAdminUserSchema: () => insertAdminUserSchema,
680
694
  insertDeliveryOutboxSchema: () => insertDeliveryOutboxSchema,
681
695
  insertMessageLogSchema: () => insertMessageLogSchema,
682
696
  insertProjectApiKeySchema: () => insertProjectApiKeySchema,
@@ -696,6 +710,7 @@ var schema_exports = /* @__PURE__ */ __exportAll({
696
710
  projects: () => projects,
697
711
  quietHours: () => quietHours,
698
712
  scheduledPayloads: () => scheduledPayloads,
713
+ selectAdminUserSchema: () => selectAdminUserSchema,
699
714
  selectDeliveryOutboxSchema: () => selectDeliveryOutboxSchema,
700
715
  selectMessageLogSchema: () => selectMessageLogSchema,
701
716
  selectProjectApiKeySchema: () => selectProjectApiKeySchema,
@@ -941,6 +956,17 @@ const scheduledPayloads = pgTable("scheduled_payloads", {
941
956
  payload: jsonb("payload").notNull(),
942
957
  createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull()
943
958
  });
959
+ const adminUsers = pgTable("admin_users", {
960
+ id: uuid("id").primaryKey().defaultRandom(),
961
+ email: varchar("email").notNull().unique(),
962
+ username: varchar("username").unique(),
963
+ passwordHash: varchar("password_hash").notNull(),
964
+ role: varchar("role").notNull().default("admin"),
965
+ createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
966
+ updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull()
967
+ });
968
+ const insertAdminUserSchema = createInsertSchema(adminUsers);
969
+ const selectAdminUserSchema = createSelectSchema(adminUsers);
944
970
  const insertProjectSchema = createInsertSchema(projects);
945
971
  const selectProjectSchema = createSelectSchema(projects);
946
972
  const insertProjectApiKeySchema = createInsertSchema(projectApiKeys);
@@ -2567,6 +2593,46 @@ var SegmentRepository = class {
2567
2593
  `)).map((r) => r.segment);
2568
2594
  }
2569
2595
  };
2596
+ var AdminUserRepository = class {
2597
+ db;
2598
+ constructor(db) {
2599
+ this.db = db;
2600
+ }
2601
+ async findByEmailOrUsername(identifier) {
2602
+ const normalized = identifier.trim().toLowerCase();
2603
+ return (await this.db.select().from(adminUsers).where(or(eq(adminUsers.email, normalized), eq(adminUsers.username, identifier.trim()))).limit(1))[0] ?? null;
2604
+ }
2605
+ async findById(id) {
2606
+ return (await this.db.select().from(adminUsers).where(eq(adminUsers.id, id)).limit(1))[0] ?? null;
2607
+ }
2608
+ async create(data) {
2609
+ return (await this.db.insert(adminUsers).values({
2610
+ email: data.email.trim().toLowerCase(),
2611
+ username: data.username ? data.username.trim() : null,
2612
+ passwordHash: data.passwordHash,
2613
+ role: data.role || "admin"
2614
+ }).returning())[0];
2615
+ }
2616
+ async updatePassword(id, passwordHash) {
2617
+ return (await this.db.update(adminUsers).set({
2618
+ passwordHash,
2619
+ updatedAt: /* @__PURE__ */ new Date()
2620
+ }).where(eq(adminUsers.id, id)).returning()).length > 0;
2621
+ }
2622
+ async list() {
2623
+ return await this.db.select({
2624
+ id: adminUsers.id,
2625
+ email: adminUsers.email,
2626
+ username: adminUsers.username,
2627
+ role: adminUsers.role,
2628
+ createdAt: adminUsers.createdAt,
2629
+ updatedAt: adminUsers.updatedAt
2630
+ }).from(adminUsers).orderBy(adminUsers.createdAt);
2631
+ }
2632
+ async count() {
2633
+ return (await this.db.select({ count: sql`count(*)::int` }).from(adminUsers))[0]?.count ?? 0;
2634
+ }
2635
+ };
2570
2636
  //#endregion
2571
2637
  //#region src/templates/render.ts
2572
2638
  function escapeHtml(unsafe) {
@@ -3402,35 +3468,35 @@ var NotifkitServer = class extends EventEmitter {
3402
3468
  }
3403
3469
  const startupPromises = [];
3404
3470
  if (services.includes("api")) {
3405
- const { startApiServer } = await import("./main-CFSukWm8.mjs");
3471
+ const { startApiServer } = await import("./main-JdOvJ70b.mjs");
3406
3472
  startupPromises.push(startApiServer());
3407
3473
  }
3408
3474
  if (services.includes("delivery")) {
3409
- const { startDeliveryWorker } = await import("./main-pFAfCkVX.mjs");
3475
+ const { startDeliveryWorker } = await import("./main-CyeQ_yDt.mjs");
3410
3476
  startupPromises.push(startDeliveryWorker());
3411
3477
  }
3412
3478
  if (services.includes("engine")) {
3413
- const { startEngineWorker } = await import("./main-Yorxzw0Z.mjs");
3479
+ const { startEngineWorker } = await import("./main-BPbPnBIG.mjs");
3414
3480
  startupPromises.push(startEngineWorker());
3415
3481
  }
3416
3482
  if (services.includes("enricher")) {
3417
- const { startEnricherWorker } = await import("./main-Bgi2wWck.mjs");
3483
+ const { startEnricherWorker } = await import("./main-AO0JaY5z.mjs");
3418
3484
  startupPromises.push(startEnricherWorker());
3419
3485
  }
3420
3486
  if (services.includes("scheduler")) {
3421
- const { startSchedulerWorker } = await import("./main-DFMHcN_d.mjs");
3487
+ const { startSchedulerWorker } = await import("./main-BMYSYcv2.mjs");
3422
3488
  startupPromises.push(startSchedulerWorker());
3423
3489
  }
3424
3490
  if (services.includes("ai")) {
3425
- const { startAiWorker } = await import("./main-D6SG3Isk.mjs");
3491
+ const { startAiWorker } = await import("./main-DSmh1e-V.mjs");
3426
3492
  startupPromises.push(startAiWorker());
3427
3493
  }
3428
3494
  if (services.includes("workflow")) {
3429
- const { startWorkflowWorker } = await import("./main-CjbVdUqa.mjs");
3495
+ const { startWorkflowWorker } = await import("./main-D19Wxs0x.mjs");
3430
3496
  startupPromises.push(startWorkflowWorker());
3431
3497
  }
3432
3498
  if (services.includes("events")) {
3433
- const { startEventWorker } = await import("./main-CRPxM-O3.mjs");
3499
+ const { startEventWorker } = await import("./main-4Wyge9ms.mjs");
3434
3500
  startupPromises.push(startEventWorker());
3435
3501
  }
3436
3502
  const handleSignal = async (signal) => {
@@ -3470,35 +3536,35 @@ var NotifkitServer = class extends EventEmitter {
3470
3536
  "events"
3471
3537
  ] : this.options.services;
3472
3538
  if (services.includes("api")) {
3473
- const { stopApiServer } = await import("./main-CFSukWm8.mjs");
3539
+ const { stopApiServer } = await import("./main-JdOvJ70b.mjs");
3474
3540
  await stopApiServer();
3475
3541
  }
3476
3542
  if (services.includes("delivery")) {
3477
- const { stopDeliveryWorker } = await import("./main-pFAfCkVX.mjs");
3543
+ const { stopDeliveryWorker } = await import("./main-CyeQ_yDt.mjs");
3478
3544
  await stopDeliveryWorker();
3479
3545
  }
3480
3546
  if (services.includes("engine")) {
3481
- const { stopEngineWorker } = await import("./main-Yorxzw0Z.mjs");
3547
+ const { stopEngineWorker } = await import("./main-BPbPnBIG.mjs");
3482
3548
  await stopEngineWorker();
3483
3549
  }
3484
3550
  if (services.includes("enricher")) {
3485
- const { stopEnricherWorker } = await import("./main-Bgi2wWck.mjs");
3551
+ const { stopEnricherWorker } = await import("./main-AO0JaY5z.mjs");
3486
3552
  await stopEnricherWorker();
3487
3553
  }
3488
3554
  if (services.includes("scheduler")) {
3489
- const { stopSchedulerWorker } = await import("./main-DFMHcN_d.mjs");
3555
+ const { stopSchedulerWorker } = await import("./main-BMYSYcv2.mjs");
3490
3556
  await stopSchedulerWorker();
3491
3557
  }
3492
3558
  if (services.includes("ai")) {
3493
- const { stopAiWorker } = await import("./main-D6SG3Isk.mjs");
3559
+ const { stopAiWorker } = await import("./main-DSmh1e-V.mjs");
3494
3560
  await stopAiWorker();
3495
3561
  }
3496
3562
  if (services.includes("workflow")) {
3497
- const { stopWorkflowWorker } = await import("./main-CjbVdUqa.mjs");
3563
+ const { stopWorkflowWorker } = await import("./main-D19Wxs0x.mjs");
3498
3564
  await stopWorkflowWorker();
3499
3565
  }
3500
3566
  if (services.includes("events")) {
3501
- const { stopEventWorker } = await import("./main-CRPxM-O3.mjs");
3567
+ const { stopEventWorker } = await import("./main-4Wyge9ms.mjs");
3502
3568
  await stopEventWorker();
3503
3569
  }
3504
3570
  if (this.pgContainer) {
@@ -3512,6 +3578,6 @@ var NotifkitServer = class extends EventEmitter {
3512
3578
  }
3513
3579
  };
3514
3580
  //#endregion
3515
- export { childLogger as $, INBOUND_STREAMS as $t, ProjectSettingsCache as A, NotificationRequestedPayloadSchema as At, LUA_RELEASE_LOCK as B, PreferencesSchema as Bt, ProjectRepository as C, DeliveryOptionsSchema as Ct, WorkflowRepository as D, NotificationEnrichedPayloadSchema as Dt, UserRepository as E, NotificationScheduledPayloadSchema as Et, sleep as F, ContactChannelSchema as Ft, normaliseTarget as G, UpdateProjectSchema as Gt, LUA_SCHEDULER_CLAIM as H, SyncTemplatesSchema as Ht, DataLoader as I, CreateWorkflowSchema as It, PendingMessageScanner as J, WorkflowNotifyPayloadSchema as Jt, LRUCache as K, UpdateUserSchema as Kt, CircuitBreaker as L, IngestEventSchema as Lt, AppError as M, AddContactSchema as Mt, ValidationError as N, AddUserSchema as Nt, Redis as O, RecipientProfileSchema as Ot, generateId as P, BatchAddContactsSchema as Pt, metrics as Q, ENRICHED_STREAMS as Qt, BatchProcessor as R, InlineUserSchema as Rt, PreferenceRepository as S, NotificationDeliveredPayloadSchema as St, TemplateRepository as T, RenderedContentSchema as Tt, LUA_SCHEDULER_POLL as U, TemplateSchema as Ut, LUA_RENEW_LOCK as V, QuietHoursSchema as Vt, getPriorityBucket as W, TriggerWorkflowSchema as Wt, StreamProducer as X, buildStreamEvent as Xt, StreamConsumer as Y, WorkflowStepSchema as Yt, getMetricsRegistry as Z, CONSUMER_GROUPS as Zt, escapeHeader as _, readBaseConfig as _n, workflowWaiters as _t, SuspendExecutionError as a, StreamEventSchema as an, runMigrations as at, renderWithTemplate as b, NotificationSkippedPayloadSchema as bt, BaseWorker as c, EventMetadataSchema as cn, projectApiKeys as ct, buildUnsubscribeHeaders as d, NotificationStatusSchema as dn, suppressions as dt, OUTBOUND_STREAMS as en, createLogger as et, signUnsubscribeToken as f, AI_DEFAULTS as fn, userTopicPreferences as ft, TemplateCache as g, parseConfig as gn, workflowSteps as gt, templateRegistry as h, loadEnv as hn, workflowInstances as ht, workflowRegistry as i, StreamEventMetadataSchema as in, createDatabase as it, UserThrottle as j, NotificationTargetSchema as jt, RedisClient as k, NotificationCreatedPayloadSchema as kt, NonRetryableError as l, NotificationChannelSchema as ln, projects as lt, renderTemplate as m, getAiConfig as mn, workflowDefinitions as mt, NotifkitClient as n, STREAMS as nn, withRequestId as nt, buildStepNotifyPayload as o, EventRegistry as on, deliveryOutbox as ot, verifyUnsubscribeToken as p, baseConfigSchema as pn, users as pt, globalEmitter as q, UserContactInputSchema as qt, workflow as r, EventEnvelopeSchema as rn, IdempotencyGuard as rt, resolveStepTarget as s, registry as sn, messageLogs as st, NotifkitServer as t, PUBSUB_CHANNELS as tn, withContext as tt, startHealthReporter as u, NotificationPrioritySchema as un, scheduledPayloads as ut, escapeHtml as v, setAiConfig as vn, NotificationAiPendingPayloadSchema as vt, SegmentRepository as w, NotificationDispatchedPayloadSchema as wt, ContactRepository as x, NotificationFailedPayloadSchema as xt, interpolate as y, setGlobalConfig as yn, NotificationCanceledPayloadSchema as yt, AsyncSemaphore as z, NotifyRequestSchema as zt };
3581
+ export { metrics as $, ENRICHED_STREAMS as $t, RedisClient as A, NotificationCreatedPayloadSchema as At, AsyncSemaphore as B, NotifyRequestSchema as Bt, PreferenceRepository as C, NotificationDeliveredPayloadSchema as Ct, UserRepository as D, NotificationScheduledPayloadSchema as Dt, TemplateRepository as E, RenderedContentSchema as Et, generateId as F, BatchAddContactsSchema as Ft, getPriorityBucket as G, TriggerWorkflowSchema as Gt, LUA_RENEW_LOCK as H, QuietHoursSchema as Ht, sleep as I, ContactChannelSchema as It, globalEmitter as J, UserContactInputSchema as Jt, normaliseTarget as K, UpdateProjectSchema as Kt, DataLoader as L, CreateWorkflowSchema as Lt, UserThrottle as M, NotificationTargetSchema as Mt, AppError as N, AddContactSchema as Nt, WorkflowRepository as O, NotificationEnrichedPayloadSchema as Ot, ValidationError as P, AddUserSchema as Pt, getMetricsRegistry as Q, CONSUMER_GROUPS as Qt, CircuitBreaker as R, IngestEventSchema as Rt, ContactRepository as S, NotificationFailedPayloadSchema as St, SegmentRepository as T, NotificationDispatchedPayloadSchema as Tt, LUA_SCHEDULER_CLAIM as U, SyncTemplatesSchema as Ut, LUA_RELEASE_LOCK as V, PreferencesSchema as Vt, LUA_SCHEDULER_POLL as W, TemplateSchema as Wt, StreamConsumer as X, WorkflowStepSchema as Xt, PendingMessageScanner as Y, WorkflowNotifyPayloadSchema as Yt, StreamProducer as Z, buildStreamEvent as Zt, escapeHeader as _, parseConfig as _n, workflowSteps as _t, SuspendExecutionError as a, StreamEventMetadataSchema as an, createDatabase as at, renderWithTemplate as b, setGlobalConfig as bn, NotificationCanceledPayloadSchema as bt, BaseWorker as c, registry as cn, messageLogs as ct, buildUnsubscribeHeaders as d, NotificationPrioritySchema as dn, scheduledPayloads as dt, INBOUND_STREAMS as en, childLogger as et, signUnsubscribeToken as f, NotificationStatusSchema as fn, suppressions as ft, TemplateCache as g, loadEnv as gn, workflowInstances as gt, templateRegistry as h, getAiConfig as hn, workflowDefinitions as ht, workflowRegistry as i, EventEnvelopeSchema as in, IdempotencyGuard as it, ProjectSettingsCache as j, NotificationRequestedPayloadSchema as jt, Redis as k, RecipientProfileSchema as kt, NonRetryableError as l, EventMetadataSchema as ln, projectApiKeys as lt, renderTemplate as m, baseConfigSchema as mn, users as mt, NotifkitClient as n, PUBSUB_CHANNELS as nn, withContext as nt, buildStepNotifyPayload as o, StreamEventSchema as on, runMigrations as ot, verifyUnsubscribeToken as p, AI_DEFAULTS as pn, userTopicPreferences as pt, LRUCache as q, UpdateUserSchema as qt, workflow as r, STREAMS as rn, withRequestId as rt, resolveStepTarget as s, EventRegistry as sn, deliveryOutbox as st, NotifkitServer as t, OUTBOUND_STREAMS as tn, createLogger as tt, startHealthReporter as u, NotificationChannelSchema as un, projects as ut, escapeHtml as v, readBaseConfig as vn, workflowWaiters as vt, ProjectRepository as w, DeliveryOptionsSchema as wt, AdminUserRepository as x, NotificationSkippedPayloadSchema as xt, interpolate as y, setAiConfig as yn, NotificationAiPendingPayloadSchema as yt, BatchProcessor as z, InlineUserSchema as zt };
3516
3582
 
3517
- //# sourceMappingURL=src-CPMwsUCJ.mjs.map
3583
+ //# sourceMappingURL=src-CGmXRfsM.mjs.map