@automagik/omni 2.260923.5 → 2.260923.6

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.
@@ -0,0 +1,58 @@
1
+ -- Slack OAuth identity uniqueness (#1235, follow-up to #1233).
2
+ --
3
+ -- The OAuth callback upserts one instance per Slack identity, but nothing in
4
+ -- the database enforced it: two callbacks for the same identity landing
5
+ -- together could both insert. These partial unique indexes make the identity
6
+ -- a constraint and give the upsert an ON CONFLICT target:
7
+ --
8
+ -- instances_slack_oauth_user_uq (slack_team_id, slack_user_id) for
9
+ -- user-mode OAuth rows
10
+ -- instances_slack_oauth_bot_uq (slack_team_id) for bot-mode OAuth rows
11
+ -- (NULL slack_user_id) — two bot installs
12
+ -- for one workspace are one identity
13
+ --
14
+ -- Only slack_connection_method = 'oauth' rows are covered: the
15
+ -- instance.connected listener also fills the identity columns of manual
16
+ -- pasted-token instances, which may legitimately repeat.
17
+ --
18
+ -- Duplicate-row policy: detect and refuse. A database that already holds two
19
+ -- OAuth rows for one identity cannot build the index, so the DO block below
20
+ -- fails the migration with the duplicate instance ids instead. Resolve by
21
+ -- deleting the extra instance(s), then restart; nothing is deduped
22
+ -- automatically because each row carries its own live tokens.
23
+ --
24
+ -- Hand-written following the 0043/0044/0052 precedent (additive, idempotent).
25
+ -- No explicit BEGIN/COMMIT — the boot migrator executes this file on a pooled
26
+ -- postgres-js connection, which rejects raw transaction control.
27
+
28
+ DO $$
29
+ DECLARE
30
+ duplicates text;
31
+ BEGIN
32
+ SELECT string_agg(
33
+ format('team %s %s: instances %s',
34
+ slack_team_id,
35
+ COALESCE('user ' || slack_user_id, '(bot)'),
36
+ ids),
37
+ '; ')
38
+ INTO duplicates
39
+ FROM (
40
+ SELECT slack_team_id, slack_user_id, string_agg(id::text, ', ' ORDER BY created_at) AS ids
41
+ FROM "instances"
42
+ WHERE slack_connection_method = 'oauth' AND slack_team_id IS NOT NULL
43
+ GROUP BY slack_team_id, slack_user_id
44
+ HAVING count(*) > 1
45
+ ) d;
46
+
47
+ IF duplicates IS NOT NULL THEN
48
+ RAISE EXCEPTION 'Duplicate Slack OAuth instances for one identity (#1235): %. Delete the extra instance(s) and restart.', duplicates;
49
+ END IF;
50
+ END $$;
51
+
52
+ CREATE UNIQUE INDEX IF NOT EXISTS "instances_slack_oauth_user_uq"
53
+ ON "instances" ("slack_team_id", "slack_user_id")
54
+ WHERE "slack_connection_method" = 'oauth' AND "slack_user_id" IS NOT NULL;
55
+
56
+ CREATE UNIQUE INDEX IF NOT EXISTS "instances_slack_oauth_bot_uq"
57
+ ON "instances" ("slack_team_id")
58
+ WHERE "slack_connection_method" = 'oauth' AND "slack_user_id" IS NULL;
@@ -554,6 +554,13 @@
554
554
  "when": 1787800000000,
555
555
  "tag": "0078_instances_slack_oauth_identity",
556
556
  "breakpoints": true
557
+ },
558
+ {
559
+ "idx": 79,
560
+ "version": "7",
561
+ "when": 1787900000000,
562
+ "tag": "0079_instances_slack_identity_unique",
563
+ "breakpoints": true
557
564
  }
558
565
  ]
559
566
  }
package/dist/index.js CHANGED
@@ -82460,6 +82460,8 @@ var init_schema2 = __esm(() => {
82460
82460
  agentIdIdx: index("instances_agent_id_idx").on(table3.agentId),
82461
82461
  metaPhoneNumberIdx: index("instances_meta_phone_number_idx").on(table3.metaPhoneNumberId),
82462
82462
  slackIdentityIdx: index("instances_slack_identity_idx").on(table3.slackTeamId, table3.slackUserId),
82463
+ slackOAuthUserUq: uniqueIndex("instances_slack_oauth_user_uq").on(table3.slackTeamId, table3.slackUserId).where(sql`${table3.slackConnectionMethod} = 'oauth' AND ${table3.slackUserId} IS NOT NULL`),
82464
+ slackOAuthBotUq: uniqueIndex("instances_slack_oauth_bot_uq").on(table3.slackTeamId).where(sql`${table3.slackConnectionMethod} = 'oauth' AND ${table3.slackUserId} IS NULL`),
82463
82465
  chainModeCheck: check("instances_chain_mode_check", sql`${table3.chainMode} IN ('off', 'forward', 'bidirectional')`)
82464
82466
  }));
82465
82467
  whatsappTemplates = pgTable("whatsapp_templates", {
@@ -115984,7 +115986,7 @@ var require_client2 = __commonJS((exports) => {
115984
115986
 
115985
115987
  // ../../node_modules/.bun/@sentry+node-core@10.52.0+2c50d7e904f6cfd7/node_modules/@sentry/node-core/build/cjs/sdk/esmLoader.js
115986
115988
  var require_esmLoader = __commonJS((exports) => {
115987
- var __filename = "/home/runner/work/omni/omni/.stable-source/node_modules/.bun/@sentry+node-core@10.52.0+2c50d7e904f6cfd7/node_modules/@sentry/node-core/build/cjs/sdk/esmLoader.js";
115989
+ var __filename = "/home/runner/work/omni/omni/node_modules/.bun/@sentry+node-core@10.52.0+2c50d7e904f6cfd7/node_modules/@sentry/node-core/build/cjs/sdk/esmLoader.js";
115988
115990
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
115989
115991
  var core = require_cjs();
115990
115992
  var importInTheMiddle = require_import_in_the_middle();
@@ -137460,7 +137462,7 @@ import { fileURLToPath } from "url";
137460
137462
  // package.json
137461
137463
  var package_default = {
137462
137464
  name: "@automagik/omni",
137463
- version: "2.260923.5",
137465
+ version: "2.260923.6",
137464
137466
  repository: {
137465
137467
  type: "git",
137466
137468
  url: "git+https://github.com/automagik-dev/omni.git",
@@ -77157,7 +77157,7 @@ var require_ClientVoiceManager = __commonJS((exports, module) => {
77157
77157
 
77158
77158
  // ../../node_modules/.bun/@discordjs+ws@1.2.3/node_modules/@discordjs/ws/dist/index.js
77159
77159
  var require_dist9 = __commonJS((exports, module) => {
77160
- var __dirname = "/home/runner/work/omni/omni/.stable-source/node_modules/.bun/@discordjs+ws@1.2.3/node_modules/@discordjs/ws/dist";
77160
+ var __dirname = "/home/runner/work/omni/omni/node_modules/.bun/@discordjs+ws@1.2.3/node_modules/@discordjs/ws/dist";
77161
77161
  var __create2 = Object.create;
77162
77162
  var __defProp2 = Object.defineProperty;
77163
77163
  var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
@@ -176984,7 +176984,7 @@ var require_indexes = __commonJS((exports, module) => {
176984
176984
 
176985
176985
  // ../../node_modules/.bun/thread-stream@3.1.0/node_modules/thread-stream/index.js
176986
176986
  var require_thread_stream = __commonJS((exports, module) => {
176987
- var __dirname = "/home/runner/work/omni/omni/.stable-source/node_modules/.bun/thread-stream@3.1.0/node_modules/thread-stream";
176987
+ var __dirname = "/home/runner/work/omni/omni/node_modules/.bun/thread-stream@3.1.0/node_modules/thread-stream";
176988
176988
  var { version } = require_package5();
176989
176989
  var { EventEmitter: EventEmitter3 } = __require("events");
176990
176990
  var { Worker } = __require("worker_threads");
@@ -177405,7 +177405,7 @@ var require_thread_stream = __commonJS((exports, module) => {
177405
177405
 
177406
177406
  // ../../node_modules/.bun/pino@9.14.0/node_modules/pino/lib/transport.js
177407
177407
  var require_transport2 = __commonJS((exports, module) => {
177408
- var __dirname = "/home/runner/work/omni/omni/.stable-source/node_modules/.bun/pino@9.14.0/node_modules/pino/lib";
177408
+ var __dirname = "/home/runner/work/omni/omni/node_modules/.bun/pino@9.14.0/node_modules/pino/lib";
177409
177409
  var { createRequire: createRequire2 } = __require("module");
177410
177410
  var getCallers = require_caller();
177411
177411
  var { join: join8, isAbsolute, sep: sep2 } = __require("path");
@@ -188261,6 +188261,8 @@ var init_schema2 = __esm(() => {
188261
188261
  agentIdIdx: index("instances_agent_id_idx").on(table3.agentId),
188262
188262
  metaPhoneNumberIdx: index("instances_meta_phone_number_idx").on(table3.metaPhoneNumberId),
188263
188263
  slackIdentityIdx: index("instances_slack_identity_idx").on(table3.slackTeamId, table3.slackUserId),
188264
+ slackOAuthUserUq: uniqueIndex("instances_slack_oauth_user_uq").on(table3.slackTeamId, table3.slackUserId).where(sql`${table3.slackConnectionMethod} = 'oauth' AND ${table3.slackUserId} IS NOT NULL`),
188265
+ slackOAuthBotUq: uniqueIndex("instances_slack_oauth_bot_uq").on(table3.slackTeamId).where(sql`${table3.slackConnectionMethod} = 'oauth' AND ${table3.slackUserId} IS NULL`),
188264
188266
  chainModeCheck: check("instances_chain_mode_check", sql`${table3.chainMode} IN ('off', 'forward', 'bidirectional')`)
188265
188267
  }));
188266
188268
  whatsappTemplates = pgTable("whatsapp_templates", {
@@ -259216,7 +259218,7 @@ var require_client3 = __commonJS((exports) => {
259216
259218
 
259217
259219
  // ../../node_modules/.bun/@sentry+node-core@10.52.0+2c50d7e904f6cfd7/node_modules/@sentry/node-core/build/cjs/sdk/esmLoader.js
259218
259220
  var require_esmLoader = __commonJS((exports) => {
259219
- var __filename = "/home/runner/work/omni/omni/.stable-source/node_modules/.bun/@sentry+node-core@10.52.0+2c50d7e904f6cfd7/node_modules/@sentry/node-core/build/cjs/sdk/esmLoader.js";
259221
+ var __filename = "/home/runner/work/omni/omni/node_modules/.bun/@sentry+node-core@10.52.0+2c50d7e904f6cfd7/node_modules/@sentry/node-core/build/cjs/sdk/esmLoader.js";
259220
259222
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
259221
259223
  var core3 = require_cjs6();
259222
259224
  var importInTheMiddle = require_import_in_the_middle();
@@ -266442,7 +266444,7 @@ var init_sentry_scrub = __esm(() => {
266442
266444
  var require_package7 = __commonJS((exports, module) => {
266443
266445
  module.exports = {
266444
266446
  name: "@omni/api",
266445
- version: "2.260923.5",
266447
+ version: "2.260923.6",
266446
266448
  private: true,
266447
266449
  type: "module",
266448
266450
  exports: {
@@ -293545,7 +293547,7 @@ var require_src45 = __commonJS((exports) => {
293545
293547
 
293546
293548
  // ../../node_modules/.bun/@grpc+grpc-js@1.14.3/node_modules/@grpc/grpc-js/build/src/channelz.js
293547
293549
  var require_channelz = __commonJS((exports) => {
293548
- var __dirname = "/home/runner/work/omni/omni/.stable-source/node_modules/.bun/@grpc+grpc-js@1.14.3/node_modules/@grpc/grpc-js/build/src";
293550
+ var __dirname = "/home/runner/work/omni/omni/node_modules/.bun/@grpc+grpc-js@1.14.3/node_modules/@grpc/grpc-js/build/src";
293549
293551
  Object.defineProperty(exports, "__esModule", { value: true });
293550
293552
  exports.registerChannelzSocket = exports.registerChannelzServer = exports.registerChannelzSubchannel = exports.registerChannelzChannel = exports.ChannelzCallTrackerStub = exports.ChannelzCallTracker = exports.ChannelzChildrenTrackerStub = exports.ChannelzChildrenTracker = exports.ChannelzTrace = exports.ChannelzTraceStub = undefined;
293551
293553
  exports.unregisterChannelzRef = unregisterChannelzRef;
@@ -298948,7 +298950,7 @@ var require_duration = __commonJS((exports) => {
298948
298950
 
298949
298951
  // ../../node_modules/.bun/@grpc+grpc-js@1.14.3/node_modules/@grpc/grpc-js/build/src/orca.js
298950
298952
  var require_orca = __commonJS((exports) => {
298951
- var __dirname = "/home/runner/work/omni/omni/.stable-source/node_modules/.bun/@grpc+grpc-js@1.14.3/node_modules/@grpc/grpc-js/build/src";
298953
+ var __dirname = "/home/runner/work/omni/omni/node_modules/.bun/@grpc+grpc-js@1.14.3/node_modules/@grpc/grpc-js/build/src";
298952
298954
  Object.defineProperty(exports, "__esModule", { value: true });
298953
298955
  exports.OrcaOobMetricsSubchannelWrapper = exports.GRPC_METRICS_HEADER = exports.ServerMetricRecorder = exports.PerRequestMetricRecorder = undefined;
298954
298956
  exports.createOrcaClient = createOrcaClient;
@@ -387615,12 +387617,39 @@ class InstanceService {
387615
387617
  const [row] = await this.db.select({ tenantId: instances.tenantId }).from(instances).where(eq(instances.id, id)).limit(1);
387616
387618
  return this.sealTenantFor(row?.tenantId ?? null);
387617
387619
  }
387620
+ async findBySlackIdentity(teamId, userId) {
387621
+ const [row] = await this.db.select().from(instances).where(and2(eq(instances.slackConnectionMethod, "oauth"), eq(instances.slackTeamId, teamId), userId === null ? isNull2(instances.slackUserId) : eq(instances.slackUserId, userId))).limit(1);
387622
+ if (!row)
387623
+ return;
387624
+ rememberInstanceOwners([row]);
387625
+ return openInstanceCredentials(row);
387626
+ }
387618
387627
  async create(data) {
387619
- const rowTenant = data.tenantId ?? null;
387620
- const [created] = await this.db.insert(instances).values(sealInstanceCredentials(this.sealTenantFor(rowTenant), data)).returning();
387628
+ const created = await this.insert(data, false);
387621
387629
  if (!created) {
387622
387630
  throw new Error("Failed to create instance");
387623
387631
  }
387632
+ return created;
387633
+ }
387634
+ async createSlackOAuth(data) {
387635
+ return this.insert({ ...data, slackConnectionMethod: "oauth" }, true);
387636
+ }
387637
+ async insert(data, slackIdentityConflict) {
387638
+ const rowTenant = data.tenantId ?? null;
387639
+ const query = this.db.insert(instances).values(sealInstanceCredentials(this.sealTenantFor(rowTenant), data));
387640
+ if (slackIdentityConflict) {
387641
+ query.onConflictDoNothing(data.slackUserId == null ? {
387642
+ target: instances.slackTeamId,
387643
+ where: sql`${instances.slackConnectionMethod} = 'oauth' AND ${instances.slackUserId} IS NULL`
387644
+ } : {
387645
+ target: [instances.slackTeamId, instances.slackUserId],
387646
+ where: sql`${instances.slackConnectionMethod} = 'oauth' AND ${instances.slackUserId} IS NOT NULL`
387647
+ });
387648
+ }
387649
+ const [created] = await query.returning();
387650
+ if (!created) {
387651
+ return null;
387652
+ }
387624
387653
  rememberInstanceOwners([created]);
387625
387654
  if (this.eventBus) {
387626
387655
  await this.eventBus.publish("instance.connected", {
@@ -412858,19 +412887,8 @@ function isAllowedReturnTo(returnTo, publicOrigin) {
412858
412887
  }
412859
412888
  return url.origin === publicOrigin && url.search === "" && url.hash === "";
412860
412889
  }
412861
- function matchesIdentity(row, input) {
412862
- if (row.channel !== "slack" || row.slackTeamId !== input.teamId)
412863
- return false;
412864
- if (input.mode === "user")
412865
- return row.slackUserId === input.userId && row.slackAuthMode === "user";
412866
- return row.slackUserId == null && (row.slackAuthMode ?? "bot") === "bot";
412867
- }
412868
- async function findExisting(instances2, input) {
412869
- const page = await instances2.list({ channel: ["slack"], limit: SLACK_LOOKUP_LIMIT });
412870
- if (page.hasMore) {
412871
- log127.warn("Slack OAuth identity lookup truncated; add a service finder", { limit: SLACK_LOOKUP_LIMIT });
412872
- }
412873
- return page.items.find((row) => matchesIdentity(row, input));
412890
+ function findExisting(instances2, input) {
412891
+ return instances2.findBySlackIdentity(input.teamId, input.mode === "user" ? input.userId : null);
412874
412892
  }
412875
412893
  function baseName(input) {
412876
412894
  const team = (input.teamName ?? input.teamId).trim() || input.teamId;
@@ -412903,7 +412921,7 @@ async function createWithUniqueName(instances2, input) {
412903
412921
  for (let attempt = 1;attempt <= NAME_SUFFIX_ATTEMPTS; attempt++) {
412904
412922
  const name = attempt === 1 ? base2 : `${base2} ${attempt}`;
412905
412923
  try {
412906
- return await instances2.create({
412924
+ return await instances2.createSlackOAuth({
412907
412925
  name,
412908
412926
  channel: "slack",
412909
412927
  profileName: input.mode === "user" ? input.displayName ?? null : input.teamName ?? null,
@@ -412956,8 +412974,14 @@ function resolvePlugin(registry5) {
412956
412974
  async function upsertSlackOAuthInstance(deps, input) {
412957
412975
  const plugin13 = resolvePlugin(deps.channelRegistry);
412958
412976
  const existing = await findExisting(deps.instances, input);
412959
- const row = existing ? await deps.instances.update(existing.id, credentialColumns(input)) : await createWithUniqueName(deps.instances, input);
412960
- const created = !existing;
412977
+ let row = existing ? null : await createWithUniqueName(deps.instances, input);
412978
+ const created = row !== null;
412979
+ if (!row) {
412980
+ const current = existing ?? await findExisting(deps.instances, input);
412981
+ if (!current)
412982
+ throw new Error("slack-oauth: identity conflict but no row found");
412983
+ row = await deps.instances.update(current.id, credentialColumns(input));
412984
+ }
412961
412985
  log127.info(created ? "Slack OAuth instance created" : "Slack OAuth instance re-authorized", {
412962
412986
  instanceId: row.id,
412963
412987
  teamId: input.teamId,
@@ -412972,7 +412996,7 @@ async function upsertSlackOAuthInstance(deps, input) {
412972
412996
  }
412973
412997
  return { instanceId: row.id, created };
412974
412998
  }
412975
- var log127, SLACK_LOOKUP_LIMIT = 1000, NAME_MAX = 200, NAME_SUFFIX_ATTEMPTS = 20;
412999
+ var log127, NAME_MAX = 200, NAME_SUFFIX_ATTEMPTS = 20;
412976
413000
  var init_slack_oauth2 = __esm(() => {
412977
413001
  init_src4();
412978
413002
  init_src();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automagik/omni",
3
- "version": "2.260923.5",
3
+ "version": "2.260923.6",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/automagik-dev/omni.git",