@objectstack/plugin-auth 8.0.1 → 9.0.1
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/README.md +11 -7
- package/dist/index.d.mts +26 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +186 -17
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +188 -18
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -9
package/dist/index.mjs
CHANGED
|
@@ -5,7 +5,8 @@ import {
|
|
|
5
5
|
SETUP_NAV_CONTRIBUTIONS,
|
|
6
6
|
STUDIO_APP,
|
|
7
7
|
ACCOUNT_APP,
|
|
8
|
-
SystemOverviewDashboard
|
|
8
|
+
SystemOverviewDashboard,
|
|
9
|
+
SystemOverviewDatasets
|
|
9
10
|
} from "@objectstack/platform-objects/apps";
|
|
10
11
|
import { SysOrganizationDetailPage, SysUserDetailPage } from "@objectstack/platform-objects/pages";
|
|
11
12
|
|
|
@@ -511,6 +512,18 @@ function installWebContainerRequestStatePolyfill() {
|
|
|
511
512
|
);
|
|
512
513
|
}
|
|
513
514
|
}
|
|
515
|
+
function readBooleanEnv(name, legacyName) {
|
|
516
|
+
const env = globalThis?.process?.env;
|
|
517
|
+
const raw = env?.[name] ?? (legacyName ? env?.[legacyName] : void 0);
|
|
518
|
+
if (raw == null) return void 0;
|
|
519
|
+
const normalized = String(raw).trim().toLowerCase();
|
|
520
|
+
return !["0", "false", "off", "no"].includes(normalized);
|
|
521
|
+
}
|
|
522
|
+
function readDisableSignUpEnv() {
|
|
523
|
+
const signupEnabled = readBooleanEnv("OS_AUTH_SIGNUP_ENABLED");
|
|
524
|
+
if (signupEnabled != null) return !signupEnabled;
|
|
525
|
+
return readBooleanEnv("OS_DISABLE_SIGNUP");
|
|
526
|
+
}
|
|
514
527
|
var AuthManager = class {
|
|
515
528
|
constructor(config) {
|
|
516
529
|
this.auth = null;
|
|
@@ -594,13 +607,10 @@ var AuthManager = class {
|
|
|
594
607
|
// Social / OAuth providers
|
|
595
608
|
...this.config.socialProviders ? { socialProviders: this.config.socialProviders } : {},
|
|
596
609
|
// Email and password configuration.
|
|
597
|
-
// `disableSignUp`:
|
|
598
|
-
// the
|
|
599
|
-
// a code change (`getPublicConfig()` applies the same precedence so
|
|
600
|
-
// `/auth/config` stays consistent with the server enforcement).
|
|
610
|
+
// `disableSignUp`: env overrides config/settings so deployments can
|
|
611
|
+
// lock the registration policy without relying on UI state.
|
|
601
612
|
emailAndPassword: (() => {
|
|
602
|
-
const
|
|
603
|
-
const disableSignUpFromEnv = disableSignUpEnv != null ? String(disableSignUpEnv).toLowerCase() === "true" : void 0;
|
|
613
|
+
const disableSignUpFromEnv = readDisableSignUpEnv();
|
|
604
614
|
const effectiveDisableSignUp = disableSignUpFromEnv ?? this.config.emailAndPassword?.disableSignUp;
|
|
605
615
|
return {
|
|
606
616
|
enabled: this.config.emailAndPassword?.enabled ?? true,
|
|
@@ -815,9 +825,10 @@ var AuthManager = class {
|
|
|
815
825
|
const plugins = [];
|
|
816
826
|
const oidcEnv = globalThis?.process?.env?.OS_OIDC_PROVIDER_ENABLED;
|
|
817
827
|
const oidcFromEnv = oidcEnv != null ? String(oidcEnv).toLowerCase() === "true" : void 0;
|
|
828
|
+
const twoFactorFromEnv = readBooleanEnv("OS_AUTH_TWO_FACTOR");
|
|
818
829
|
const enabled = {
|
|
819
830
|
organization: pluginConfig.organization ?? true,
|
|
820
|
-
twoFactor: pluginConfig.twoFactor ?? false,
|
|
831
|
+
twoFactor: twoFactorFromEnv ?? pluginConfig.twoFactor ?? false,
|
|
821
832
|
passkeys: pluginConfig.passkeys ?? false,
|
|
822
833
|
magicLink: pluginConfig.magicLink ?? false,
|
|
823
834
|
oidcProvider: oidcFromEnv ?? pluginConfig.oidcProvider ?? false,
|
|
@@ -1088,16 +1099,19 @@ var AuthManager = class {
|
|
|
1088
1099
|
});
|
|
1089
1100
|
return (Array.isArray(members) ? members : []).some((m) => {
|
|
1090
1101
|
const raw = typeof m?.role === "string" ? m.role : "";
|
|
1091
|
-
const
|
|
1092
|
-
return
|
|
1102
|
+
const roles2 = raw.split(",").map((s) => s.trim().toLowerCase());
|
|
1103
|
+
return roles2.includes("owner") || roles2.includes("admin");
|
|
1093
1104
|
});
|
|
1094
1105
|
} catch {
|
|
1095
1106
|
return false;
|
|
1096
1107
|
}
|
|
1097
1108
|
};
|
|
1098
1109
|
const promote = await isPlatformAdmin() || await isActiveOrgAdmin();
|
|
1099
|
-
|
|
1100
|
-
|
|
1110
|
+
const storedRole = typeof user.role === "string" ? user.role : "";
|
|
1111
|
+
const roles = storedRole.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1112
|
+
if (promote && !roles.includes("admin")) roles.push("admin");
|
|
1113
|
+
if (!promote) return { user: { ...user, roles }, session };
|
|
1114
|
+
return { user: { ...user, role: "admin", roles }, session };
|
|
1101
1115
|
}));
|
|
1102
1116
|
}
|
|
1103
1117
|
return plugins;
|
|
@@ -1159,6 +1173,39 @@ var AuthManager = class {
|
|
|
1159
1173
|
}
|
|
1160
1174
|
this.config = { ...this.config, baseUrl: url };
|
|
1161
1175
|
}
|
|
1176
|
+
/**
|
|
1177
|
+
* Merge runtime configuration into the manager.
|
|
1178
|
+
*
|
|
1179
|
+
* Settings-backed auth policy can change after the manager is constructed.
|
|
1180
|
+
* better-auth itself is created lazily, so changing config before the first
|
|
1181
|
+
* request is enough. If an instance already exists, reset it so the next
|
|
1182
|
+
* request rebuilds with the new policy.
|
|
1183
|
+
*/
|
|
1184
|
+
applyConfigPatch(patch) {
|
|
1185
|
+
const next = {
|
|
1186
|
+
...this.config,
|
|
1187
|
+
...patch,
|
|
1188
|
+
...patch.emailAndPassword ? {
|
|
1189
|
+
emailAndPassword: {
|
|
1190
|
+
...this.config.emailAndPassword ?? {},
|
|
1191
|
+
...patch.emailAndPassword
|
|
1192
|
+
}
|
|
1193
|
+
} : {},
|
|
1194
|
+
...patch.plugins ? {
|
|
1195
|
+
plugins: {
|
|
1196
|
+
...this.config.plugins ?? {},
|
|
1197
|
+
...patch.plugins
|
|
1198
|
+
}
|
|
1199
|
+
} : {}
|
|
1200
|
+
};
|
|
1201
|
+
if ("socialProviders" in patch) {
|
|
1202
|
+
next.socialProviders = patch.socialProviders;
|
|
1203
|
+
}
|
|
1204
|
+
this.config = next;
|
|
1205
|
+
if (this.auth && !patch.authInstance) {
|
|
1206
|
+
this.auth = null;
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1162
1209
|
/**
|
|
1163
1210
|
* Inject (or replace) the outbound email service used by better-auth
|
|
1164
1211
|
* callbacks. Safe to call after construction but BEFORE the first
|
|
@@ -1291,8 +1338,7 @@ var AuthManager = class {
|
|
|
1291
1338
|
}
|
|
1292
1339
|
}
|
|
1293
1340
|
const emailPasswordConfig = this.config.emailAndPassword ?? {};
|
|
1294
|
-
const
|
|
1295
|
-
const disableSignUpFromEnv = disableSignUpEnv != null ? String(disableSignUpEnv).toLowerCase() === "true" : void 0;
|
|
1341
|
+
const disableSignUpFromEnv = readDisableSignUpEnv();
|
|
1296
1342
|
const emailPassword = {
|
|
1297
1343
|
enabled: emailPasswordConfig.enabled !== false,
|
|
1298
1344
|
// Default to true
|
|
@@ -1317,14 +1363,16 @@ var AuthManager = class {
|
|
|
1317
1363
|
const privacyUrl = resolveLegalUrl(rawPrivacyUrl, DEFAULT_PRIVACY_URL);
|
|
1318
1364
|
const oidcEnv = globalThis?.process?.env?.OS_OIDC_PROVIDER_ENABLED;
|
|
1319
1365
|
const oidcFromEnv = oidcEnv != null ? String(oidcEnv).toLowerCase() === "true" : void 0;
|
|
1366
|
+
const twoFactorFromEnv = readBooleanEnv("OS_AUTH_TWO_FACTOR");
|
|
1320
1367
|
const features = {
|
|
1321
|
-
twoFactor: pluginConfig.twoFactor ?? false,
|
|
1368
|
+
twoFactor: twoFactorFromEnv ?? pluginConfig.twoFactor ?? false,
|
|
1322
1369
|
passkeys: pluginConfig.passkeys ?? false,
|
|
1323
1370
|
magicLink: pluginConfig.magicLink ?? false,
|
|
1324
1371
|
organization: pluginConfig.organization ?? true,
|
|
1325
1372
|
multiOrgEnabled,
|
|
1326
1373
|
oidcProvider: oidcFromEnv ?? pluginConfig.oidcProvider ?? false,
|
|
1327
1374
|
deviceAuthorization: pluginConfig.deviceAuthorization ?? false,
|
|
1375
|
+
admin: pluginConfig.admin ?? false,
|
|
1328
1376
|
...termsUrl ? { termsUrl } : {},
|
|
1329
1377
|
...privacyUrl ? { privacyUrl } : {}
|
|
1330
1378
|
};
|
|
@@ -1443,6 +1491,30 @@ var AuthPlugin = class {
|
|
|
1443
1491
|
...options
|
|
1444
1492
|
};
|
|
1445
1493
|
}
|
|
1494
|
+
/**
|
|
1495
|
+
* Open-source provider fallback: enable Google sign-in from conventional
|
|
1496
|
+
* provider env vars when the application did not configure Google itself.
|
|
1497
|
+
* Enterprise / product packages can contribute richer provider sets through
|
|
1498
|
+
* the `auth:configure` hook below.
|
|
1499
|
+
*/
|
|
1500
|
+
applyEnvSocialProviderFallbacks(config) {
|
|
1501
|
+
const env = globalThis?.process?.env;
|
|
1502
|
+
if (String(env?.OS_AUTH_GOOGLE_ENABLED ?? "true").toLowerCase() === "false") return;
|
|
1503
|
+
const googleClientId = env?.GOOGLE_CLIENT_ID;
|
|
1504
|
+
const googleClientSecret = env?.GOOGLE_CLIENT_SECRET;
|
|
1505
|
+
if (!googleClientId || !googleClientSecret) return;
|
|
1506
|
+
const socialProviders = {
|
|
1507
|
+
...config.socialProviders ?? {}
|
|
1508
|
+
};
|
|
1509
|
+
if (!socialProviders.google) {
|
|
1510
|
+
socialProviders.google = {
|
|
1511
|
+
clientId: googleClientId,
|
|
1512
|
+
clientSecret: googleClientSecret,
|
|
1513
|
+
enabled: true
|
|
1514
|
+
};
|
|
1515
|
+
config.socialProviders = socialProviders;
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1446
1518
|
async init(ctx) {
|
|
1447
1519
|
ctx.logger.info("Initializing Auth Plugin...");
|
|
1448
1520
|
if (!this.options.secret) {
|
|
@@ -1452,10 +1524,14 @@ var AuthPlugin = class {
|
|
|
1452
1524
|
if (!dataEngine) {
|
|
1453
1525
|
ctx.logger.warn("No data engine service found - auth will use in-memory storage");
|
|
1454
1526
|
}
|
|
1455
|
-
|
|
1527
|
+
const authConfig = {
|
|
1456
1528
|
...this.options,
|
|
1457
1529
|
dataEngine
|
|
1458
|
-
}
|
|
1530
|
+
};
|
|
1531
|
+
this.applyEnvSocialProviderFallbacks(authConfig);
|
|
1532
|
+
await ctx.trigger("auth:configure", authConfig, ctx);
|
|
1533
|
+
this.configuredSocialProviders = authConfig.socialProviders ? { ...authConfig.socialProviders } : void 0;
|
|
1534
|
+
this.authManager = new AuthManager(authConfig);
|
|
1459
1535
|
ctx.registerService("auth", this.authManager);
|
|
1460
1536
|
ctx.getService("manifest").register({
|
|
1461
1537
|
...authPluginManifestHeader,
|
|
@@ -1484,7 +1560,9 @@ var AuthPlugin = class {
|
|
|
1484
1560
|
// (e.g. legacy `users.view` had phone/status/active columns that do
|
|
1485
1561
|
// not exist on sys_user). Schema-embedded listViews is the single
|
|
1486
1562
|
// source of truth.
|
|
1487
|
-
dashboards: [SystemOverviewDashboard]
|
|
1563
|
+
dashboards: [SystemOverviewDashboard],
|
|
1564
|
+
// ADR-0021 — datasets backing the System Overview dashboard's widgets.
|
|
1565
|
+
datasets: SystemOverviewDatasets
|
|
1488
1566
|
});
|
|
1489
1567
|
ctx.logger.info("Auth Plugin initialized successfully");
|
|
1490
1568
|
}
|
|
@@ -1496,6 +1574,7 @@ var AuthPlugin = class {
|
|
|
1496
1574
|
if (this.options.registerRoutes) {
|
|
1497
1575
|
ctx.hook("kernel:ready", async () => {
|
|
1498
1576
|
if (this.authManager) {
|
|
1577
|
+
await this.bindAuthSettings(ctx);
|
|
1499
1578
|
try {
|
|
1500
1579
|
const emailSvc = ctx.getService("email");
|
|
1501
1580
|
if (emailSvc) {
|
|
@@ -1582,6 +1661,97 @@ var AuthPlugin = class {
|
|
|
1582
1661
|
}
|
|
1583
1662
|
ctx.logger.info("Auth Plugin started successfully");
|
|
1584
1663
|
}
|
|
1664
|
+
/**
|
|
1665
|
+
* Bind the small open-source auth settings namespace to better-auth config.
|
|
1666
|
+
*
|
|
1667
|
+
* Only explicit settings values (stored or OS_AUTH_* env overrides) affect
|
|
1668
|
+
* runtime config. Manifest defaults are UI defaults and do not mask code or
|
|
1669
|
+
* deployment configuration.
|
|
1670
|
+
*/
|
|
1671
|
+
async bindAuthSettings(ctx) {
|
|
1672
|
+
if (!this.authManager) return;
|
|
1673
|
+
let settings;
|
|
1674
|
+
try {
|
|
1675
|
+
settings = ctx.getService("settings");
|
|
1676
|
+
} catch {
|
|
1677
|
+
return;
|
|
1678
|
+
}
|
|
1679
|
+
if (!settings || typeof settings.getNamespace !== "function") return;
|
|
1680
|
+
const applySettings = async () => {
|
|
1681
|
+
if (!this.authManager) return;
|
|
1682
|
+
try {
|
|
1683
|
+
const payload = await settings.getNamespace("auth");
|
|
1684
|
+
const values = {};
|
|
1685
|
+
const sources = {};
|
|
1686
|
+
for (const [key, entry] of Object.entries(payload.values)) {
|
|
1687
|
+
values[key] = entry?.value;
|
|
1688
|
+
sources[key] = entry?.source;
|
|
1689
|
+
}
|
|
1690
|
+
const isExplicit = (key) => (sources[key] ?? "default") !== "default";
|
|
1691
|
+
const asBoolean = (value, fallback) => {
|
|
1692
|
+
if (typeof value === "boolean") return value;
|
|
1693
|
+
if (typeof value === "string") return value.toLowerCase() !== "false";
|
|
1694
|
+
if (typeof value === "number") return value !== 0;
|
|
1695
|
+
return fallback;
|
|
1696
|
+
};
|
|
1697
|
+
const asTrimmedString = (value) => {
|
|
1698
|
+
if (typeof value !== "string") return void 0;
|
|
1699
|
+
const trimmed = value.trim();
|
|
1700
|
+
return trimmed ? trimmed : void 0;
|
|
1701
|
+
};
|
|
1702
|
+
const patch = {};
|
|
1703
|
+
const emailAndPassword = {};
|
|
1704
|
+
if (isExplicit("email_password_enabled")) {
|
|
1705
|
+
emailAndPassword.enabled = asBoolean(values.email_password_enabled, true);
|
|
1706
|
+
}
|
|
1707
|
+
if (isExplicit("signup_enabled")) {
|
|
1708
|
+
emailAndPassword.disableSignUp = !asBoolean(values.signup_enabled, true);
|
|
1709
|
+
}
|
|
1710
|
+
if (isExplicit("require_email_verification")) {
|
|
1711
|
+
emailAndPassword.requireEmailVerification = asBoolean(
|
|
1712
|
+
values.require_email_verification,
|
|
1713
|
+
false
|
|
1714
|
+
);
|
|
1715
|
+
}
|
|
1716
|
+
if (Object.keys(emailAndPassword).length > 0) {
|
|
1717
|
+
patch.emailAndPassword = emailAndPassword;
|
|
1718
|
+
}
|
|
1719
|
+
if (isExplicit("google_enabled") || isExplicit("google_client_id") || isExplicit("google_client_secret")) {
|
|
1720
|
+
const socialProviders = {
|
|
1721
|
+
...this.configuredSocialProviders ?? {}
|
|
1722
|
+
};
|
|
1723
|
+
const env = globalThis?.process?.env;
|
|
1724
|
+
const googleEnabledFromEnv = env?.OS_AUTH_GOOGLE_ENABLED != null ? asBoolean(env.OS_AUTH_GOOGLE_ENABLED, true) : void 0;
|
|
1725
|
+
const googleClientId = asTrimmedString(values.google_client_id) ?? env?.GOOGLE_CLIENT_ID;
|
|
1726
|
+
const googleClientSecret = asTrimmedString(values.google_client_secret) ?? env?.GOOGLE_CLIENT_SECRET;
|
|
1727
|
+
if (googleEnabledFromEnv ?? (isExplicit("google_enabled") ? asBoolean(values.google_enabled, true) : true)) {
|
|
1728
|
+
if (!socialProviders.google && googleClientId && googleClientSecret) {
|
|
1729
|
+
socialProviders.google = {
|
|
1730
|
+
clientId: googleClientId,
|
|
1731
|
+
clientSecret: googleClientSecret,
|
|
1732
|
+
enabled: true
|
|
1733
|
+
};
|
|
1734
|
+
}
|
|
1735
|
+
} else {
|
|
1736
|
+
delete socialProviders.google;
|
|
1737
|
+
}
|
|
1738
|
+
patch.socialProviders = Object.keys(socialProviders).length > 0 ? socialProviders : void 0;
|
|
1739
|
+
}
|
|
1740
|
+
if (Object.keys(patch).length > 0) {
|
|
1741
|
+
this.authManager.applyConfigPatch(patch);
|
|
1742
|
+
}
|
|
1743
|
+
} catch (err) {
|
|
1744
|
+
ctx.logger.warn("Auth: failed to apply auth settings: " + (err?.message ?? err));
|
|
1745
|
+
}
|
|
1746
|
+
};
|
|
1747
|
+
await applySettings();
|
|
1748
|
+
if (typeof settings.subscribe === "function") {
|
|
1749
|
+
settings.subscribe("auth", () => {
|
|
1750
|
+
void applySettings();
|
|
1751
|
+
});
|
|
1752
|
+
ctx.logger.info("Auth: bound to settings namespace=auth");
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1585
1755
|
async destroy() {
|
|
1586
1756
|
this.authManager = null;
|
|
1587
1757
|
}
|