@objectstack/plugin-auth 4.1.0 → 4.2.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
@@ -2,7 +2,8 @@
2
2
  import {
3
3
  SETUP_APP,
4
4
  SystemOverviewDashboard,
5
- SecurityOverviewDashboard
5
+ SecurityOverviewDashboard,
6
+ SetupAppTranslations
6
7
  } from "@objectstack/platform-objects/apps";
7
8
 
8
9
  // src/objectql-adapter.ts
@@ -714,6 +715,54 @@ var AuthManager = class {
714
715
  // who wire a real mailer can re-enable downstream.
715
716
  requireEmailVerificationOnInvitation: false,
716
717
  ...customOrgRoles ? { roles: customOrgRoles } : {},
718
+ // ── Slug-change guard ─────────────────────────────────────
719
+ // An org's slug is baked into every env hostname at creation
720
+ // time (see service-tenant `project-provisioning.ts`). Renaming
721
+ // it while live envs exist would silently desync the URL from
722
+ // the org identity. Block the change here; the cloud Console
723
+ // surfaces this as an actionable error and points users to
724
+ // `change_hostname` or archiving the env. Org `name` (display
725
+ // label) is unaffected — only `slug` is guarded.
726
+ //
727
+ // We resolve the data engine lazily so non-cloud apps (which
728
+ // never seed `sys_environment`) keep working: any lookup error
729
+ // is treated as "no envs to protect".
730
+ organizationHooks: {
731
+ beforeUpdateOrganization: async ({ organization: organization2, member }) => {
732
+ const newSlug = organization2?.slug;
733
+ const orgId = member?.organizationId;
734
+ if (!newSlug || !orgId) return;
735
+ const dataEngine2 = this.config.dataEngine;
736
+ if (!dataEngine2) return;
737
+ let currentSlug;
738
+ try {
739
+ const current = await dataEngine2.findOne("sys_organization", {
740
+ where: { id: orgId }
741
+ });
742
+ currentSlug = current?.slug;
743
+ } catch {
744
+ return;
745
+ }
746
+ if (!currentSlug || currentSlug === newSlug) return;
747
+ let activeEnvs = 0;
748
+ try {
749
+ const envs = await dataEngine2.find("sys_environment", {
750
+ where: { organization_id: orgId }
751
+ });
752
+ activeEnvs = (envs ?? []).filter(
753
+ (e) => e?.status !== "archived" && e?.status !== "failed"
754
+ ).length;
755
+ } catch {
756
+ return;
757
+ }
758
+ if (activeEnvs > 0) {
759
+ const { APIError } = await import("better-auth/api");
760
+ throw new APIError("FORBIDDEN", {
761
+ message: `Cannot change organization slug while ${activeEnvs} active environment(s) still reference it. Archive those environments or rename their hostnames first.`
762
+ });
763
+ }
764
+ }
765
+ },
717
766
  // No mailer is wired in framework yet — log the accept URL so
718
767
  // operators / UI can fall back to copy-paste flows. Replace this
719
768
  // with a real mail integration when available.
@@ -1178,6 +1227,30 @@ var AuthPlugin = class {
1178
1227
  if (!this.authManager) {
1179
1228
  throw new Error("Auth manager not initialized");
1180
1229
  }
1230
+ ctx.hook("kernel:ready", async () => {
1231
+ try {
1232
+ const i18n = ctx.getService("i18n");
1233
+ let loaded = 0;
1234
+ for (const [locale, data] of Object.entries(SetupAppTranslations)) {
1235
+ if (data && typeof data === "object") {
1236
+ try {
1237
+ i18n.loadTranslations(locale, data);
1238
+ loaded++;
1239
+ } catch (err) {
1240
+ ctx.logger.warn(
1241
+ `Auth: failed to load Setup App translations for '${locale}': ${err?.message ?? err}`
1242
+ );
1243
+ }
1244
+ }
1245
+ }
1246
+ if (loaded > 0) {
1247
+ ctx.logger.info(
1248
+ `Auth: contributed Setup App translations (${loaded} locale${loaded > 1 ? "s" : ""})`
1249
+ );
1250
+ }
1251
+ } catch {
1252
+ }
1253
+ });
1181
1254
  if (this.options.registerRoutes) {
1182
1255
  ctx.hook("kernel:ready", async () => {
1183
1256
  if (this.authManager) {