@objectstack/plugin-security 6.8.1 → 6.9.0

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.
package/dist/index.mjs CHANGED
@@ -339,6 +339,7 @@ function genId(prefix) {
339
339
  }
340
340
  async function bootstrapPlatformAdmin(ql, bootstrapPermissionSets, options = {}) {
341
341
  const logger = options.logger;
342
+ const multiTenant = options.multiTenant === true;
342
343
  if (!ql || typeof ql.find !== "function" || typeof ql.insert !== "function") {
343
344
  return { seeded: 0, adminPromoted: false, reason: "objectql_unavailable" };
344
345
  }
@@ -400,7 +401,48 @@ async function bootstrapPlatformAdmin(ql, bootstrapPermissionSets, options = {})
400
401
  return { seeded: seededCount, adminPromoted: false, reason: "insert_failed" };
401
402
  }
402
403
  logger?.info?.(`[security] first user promoted to platform admin: ${target.email ?? target.id}`);
403
- return { seeded: seededCount, adminPromoted: true };
404
+ let defaultOrgCreated = false;
405
+ let defaultOrgId;
406
+ if (multiTenant) {
407
+ const memberships = await tryFind(ql, "sys_member", { user_id: target.id }, 1);
408
+ if (memberships.length === 0) {
409
+ const existingDefault = await tryFind(ql, "sys_organization", { slug: "default" }, 1);
410
+ if (existingDefault.length > 0 && existingDefault[0].id) {
411
+ defaultOrgId = String(existingDefault[0].id);
412
+ } else {
413
+ const newOrgId = genId("org");
414
+ const orgRow = await tryInsert(ql, "sys_organization", {
415
+ id: newOrgId,
416
+ name: "Default Organization",
417
+ slug: "default",
418
+ logo: null,
419
+ metadata: null
420
+ });
421
+ if (orgRow) {
422
+ defaultOrgId = orgRow?.id ?? newOrgId;
423
+ defaultOrgCreated = true;
424
+ } else {
425
+ logger?.warn?.("[security] failed to create default organization for platform admin");
426
+ }
427
+ }
428
+ if (defaultOrgId) {
429
+ const memRow = await tryInsert(ql, "sys_member", {
430
+ id: genId("mem"),
431
+ organization_id: defaultOrgId,
432
+ user_id: target.id,
433
+ role: "owner"
434
+ });
435
+ if (memRow) {
436
+ logger?.info?.(
437
+ `[security] bound platform admin to default organization (${defaultOrgId}): ${target.email ?? target.id}`
438
+ );
439
+ } else {
440
+ logger?.warn?.("[security] failed to bind platform admin to default organization");
441
+ }
442
+ }
443
+ }
444
+ }
445
+ return { seeded: seededCount, adminPromoted: true, defaultOrgCreated, defaultOrgId };
404
446
  }
405
447
 
406
448
  // src/claim-orphan-tenant-rows.ts
@@ -649,126 +691,6 @@ async function cloneTenantSeedData(ql, targetOrgId, options = {}) {
649
691
  return summary;
650
692
  }
651
693
 
652
- // src/ensure-user-has-organization.ts
653
- var SYSTEM_CTX4 = { isSystem: true };
654
- function genId2(prefix) {
655
- const rand = Math.random().toString(36).slice(2, 10);
656
- const ts = Date.now().toString(36);
657
- return `${prefix}_${ts}${rand}`;
658
- }
659
- function slugify(input, fallback = "workspace") {
660
- const cleaned = input.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
661
- return cleaned || fallback;
662
- }
663
- function deriveSlugFallback(user) {
664
- if (user.email) {
665
- const local = user.email.split("@")[0] ?? "";
666
- const localSlug = local.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
667
- if (localSlug) return localSlug;
668
- }
669
- const idTail = user.id.replace(/[^a-z0-9]/gi, "").slice(-8).toLowerCase();
670
- return idTail ? `user-${idTail}` : "user";
671
- }
672
- function deriveBaseName(user) {
673
- if (user.name && user.name.trim()) return user.name.trim();
674
- if (user.email) {
675
- const local = user.email.split("@")[0];
676
- if (local) return local;
677
- }
678
- return user.id;
679
- }
680
- async function tryFind2(ql, object, where, limit = 1) {
681
- try {
682
- const rows = await ql.find(object, { where, limit }, { context: SYSTEM_CTX4 });
683
- return Array.isArray(rows) ? rows : [];
684
- } catch {
685
- return [];
686
- }
687
- }
688
- async function ensureUserHasOrganization(ql, user, options = {}) {
689
- const logger = options.logger;
690
- const cloneSeedData = options.cloneSeedData;
691
- if (!ql || typeof ql.find !== "function" || typeof ql.insert !== "function") {
692
- return { created: false, reason: "objectql_unavailable" };
693
- }
694
- if (!user?.id) return { created: false, reason: "invalid_user" };
695
- const existing = await tryFind2(ql, "sys_member", { user_id: user.id }, 1);
696
- if (existing.length > 0) {
697
- return { created: false, reason: "already_member" };
698
- }
699
- const base = deriveBaseName(user);
700
- const orgName = `${base}'s Workspace`;
701
- const slugFallback = deriveSlugFallback(user);
702
- const baseSlug = slugify(base, slugFallback);
703
- let slug = `${baseSlug}-workspace`;
704
- for (let attempt = 1; attempt <= 5; attempt += 1) {
705
- const collision = await tryFind2(ql, "sys_organization", { slug }, 1);
706
- if (collision.length === 0) break;
707
- slug = `${baseSlug}-workspace-${attempt + 1}`;
708
- if (attempt === 5) {
709
- logger?.warn?.(
710
- `[security] could not find a free slug for personal org of ${user.email ?? user.id}`
711
- );
712
- return { created: false, reason: "slug_exhausted" };
713
- }
714
- }
715
- const orgId = genId2("org");
716
- let orgRow = null;
717
- try {
718
- orgRow = await ql.insert(
719
- "sys_organization",
720
- { id: orgId, name: orgName, slug, logo: null, metadata: null },
721
- { context: SYSTEM_CTX4 }
722
- );
723
- } catch (e) {
724
- logger?.warn?.(`[security] failed to create personal org for ${user.email ?? user.id}`, {
725
- error: e.message
726
- });
727
- return { created: false, reason: "org_insert_failed" };
728
- }
729
- const finalOrgId = orgRow?.id ?? orgId;
730
- try {
731
- await ql.insert(
732
- "sys_member",
733
- {
734
- id: genId2("mem"),
735
- organization_id: finalOrgId,
736
- user_id: user.id,
737
- role: "owner"
738
- },
739
- { context: SYSTEM_CTX4 }
740
- );
741
- } catch (e) {
742
- logger?.warn?.(`[security] failed to create owner-member row for ${user.email ?? user.id}`, {
743
- error: e.message
744
- });
745
- return { created: false, reason: "member_insert_failed", organizationId: finalOrgId };
746
- }
747
- logger?.info?.(
748
- `[security] created personal organization "${orgName}" (${finalOrgId}) for ${user.email ?? user.id}`
749
- );
750
- if (cloneSeedData) {
751
- void cloneSeedData(ql, finalOrgId, { logger }).then(
752
- (summary) => {
753
- if (summary.length > 0) {
754
- const total = summary.reduce((s, c) => s + c.count, 0);
755
- logger?.info?.(
756
- `[security] cloned ${total} seed row(s) into personal organization ${finalOrgId}`,
757
- { breakdown: summary }
758
- );
759
- }
760
- },
761
- (e) => {
762
- logger?.warn?.("[security] cloneTenantSeedData failed", {
763
- organizationId: finalOrgId,
764
- error: e.message
765
- });
766
- }
767
- );
768
- }
769
- return { created: true, organizationId: finalOrgId };
770
- }
771
-
772
694
  // src/manifest.ts
773
695
  import {
774
696
  SysPermissionSet,
@@ -1009,7 +931,8 @@ var SecurityPlugin = class {
1009
931
  const runBootstrap = async () => {
1010
932
  try {
1011
933
  const report = await bootstrapPlatformAdmin(ql, this.bootstrapPermissionSets, {
1012
- logger: ctx.logger
934
+ logger: ctx.logger,
935
+ multiTenant: this.multiTenant
1013
936
  });
1014
937
  bootstrapRanOnce = true;
1015
938
  ctx.logger.info("[security] platform bootstrap complete", report);
@@ -1030,21 +953,6 @@ var SecurityPlugin = class {
1030
953
  if (bootstrapRanOnce) {
1031
954
  await runBootstrap();
1032
955
  }
1033
- if (this.multiTenant) {
1034
- const newUser = opCtx?.result ?? opCtx?.data;
1035
- if (newUser?.id) {
1036
- try {
1037
- await ensureUserHasOrganization(ql, newUser, {
1038
- logger: ctx.logger,
1039
- cloneSeedData: cloneTenantSeedData
1040
- });
1041
- } catch (e) {
1042
- ctx.logger.warn("[security] ensure-user-has-organization failed", {
1043
- error: e.message
1044
- });
1045
- }
1046
- }
1047
- }
1048
956
  }
1049
957
  });
1050
958
  if (this.multiTenant) {
@@ -1221,7 +1129,6 @@ export {
1221
1129
  SECURITY_PLUGIN_VERSION,
1222
1130
  SecurityPlugin,
1223
1131
  cloneTenantSeedData,
1224
- ensureUserHasOrganization,
1225
1132
  isPermissionDeniedError,
1226
1133
  securityDefaultPermissionSets,
1227
1134
  securityObjects,