@objectstack/plugin-auth 8.0.1 → 9.0.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/README.md +11 -7
- package/dist/index.d.mts +26 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +179 -13
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +181 -14
- 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,
|
|
@@ -1159,6 +1170,39 @@ var AuthManager = class {
|
|
|
1159
1170
|
}
|
|
1160
1171
|
this.config = { ...this.config, baseUrl: url };
|
|
1161
1172
|
}
|
|
1173
|
+
/**
|
|
1174
|
+
* Merge runtime configuration into the manager.
|
|
1175
|
+
*
|
|
1176
|
+
* Settings-backed auth policy can change after the manager is constructed.
|
|
1177
|
+
* better-auth itself is created lazily, so changing config before the first
|
|
1178
|
+
* request is enough. If an instance already exists, reset it so the next
|
|
1179
|
+
* request rebuilds with the new policy.
|
|
1180
|
+
*/
|
|
1181
|
+
applyConfigPatch(patch) {
|
|
1182
|
+
const next = {
|
|
1183
|
+
...this.config,
|
|
1184
|
+
...patch,
|
|
1185
|
+
...patch.emailAndPassword ? {
|
|
1186
|
+
emailAndPassword: {
|
|
1187
|
+
...this.config.emailAndPassword ?? {},
|
|
1188
|
+
...patch.emailAndPassword
|
|
1189
|
+
}
|
|
1190
|
+
} : {},
|
|
1191
|
+
...patch.plugins ? {
|
|
1192
|
+
plugins: {
|
|
1193
|
+
...this.config.plugins ?? {},
|
|
1194
|
+
...patch.plugins
|
|
1195
|
+
}
|
|
1196
|
+
} : {}
|
|
1197
|
+
};
|
|
1198
|
+
if ("socialProviders" in patch) {
|
|
1199
|
+
next.socialProviders = patch.socialProviders;
|
|
1200
|
+
}
|
|
1201
|
+
this.config = next;
|
|
1202
|
+
if (this.auth && !patch.authInstance) {
|
|
1203
|
+
this.auth = null;
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1162
1206
|
/**
|
|
1163
1207
|
* Inject (or replace) the outbound email service used by better-auth
|
|
1164
1208
|
* callbacks. Safe to call after construction but BEFORE the first
|
|
@@ -1291,8 +1335,7 @@ var AuthManager = class {
|
|
|
1291
1335
|
}
|
|
1292
1336
|
}
|
|
1293
1337
|
const emailPasswordConfig = this.config.emailAndPassword ?? {};
|
|
1294
|
-
const
|
|
1295
|
-
const disableSignUpFromEnv = disableSignUpEnv != null ? String(disableSignUpEnv).toLowerCase() === "true" : void 0;
|
|
1338
|
+
const disableSignUpFromEnv = readDisableSignUpEnv();
|
|
1296
1339
|
const emailPassword = {
|
|
1297
1340
|
enabled: emailPasswordConfig.enabled !== false,
|
|
1298
1341
|
// Default to true
|
|
@@ -1317,14 +1360,16 @@ var AuthManager = class {
|
|
|
1317
1360
|
const privacyUrl = resolveLegalUrl(rawPrivacyUrl, DEFAULT_PRIVACY_URL);
|
|
1318
1361
|
const oidcEnv = globalThis?.process?.env?.OS_OIDC_PROVIDER_ENABLED;
|
|
1319
1362
|
const oidcFromEnv = oidcEnv != null ? String(oidcEnv).toLowerCase() === "true" : void 0;
|
|
1363
|
+
const twoFactorFromEnv = readBooleanEnv("OS_AUTH_TWO_FACTOR");
|
|
1320
1364
|
const features = {
|
|
1321
|
-
twoFactor: pluginConfig.twoFactor ?? false,
|
|
1365
|
+
twoFactor: twoFactorFromEnv ?? pluginConfig.twoFactor ?? false,
|
|
1322
1366
|
passkeys: pluginConfig.passkeys ?? false,
|
|
1323
1367
|
magicLink: pluginConfig.magicLink ?? false,
|
|
1324
1368
|
organization: pluginConfig.organization ?? true,
|
|
1325
1369
|
multiOrgEnabled,
|
|
1326
1370
|
oidcProvider: oidcFromEnv ?? pluginConfig.oidcProvider ?? false,
|
|
1327
1371
|
deviceAuthorization: pluginConfig.deviceAuthorization ?? false,
|
|
1372
|
+
admin: pluginConfig.admin ?? false,
|
|
1328
1373
|
...termsUrl ? { termsUrl } : {},
|
|
1329
1374
|
...privacyUrl ? { privacyUrl } : {}
|
|
1330
1375
|
};
|
|
@@ -1443,6 +1488,30 @@ var AuthPlugin = class {
|
|
|
1443
1488
|
...options
|
|
1444
1489
|
};
|
|
1445
1490
|
}
|
|
1491
|
+
/**
|
|
1492
|
+
* Open-source provider fallback: enable Google sign-in from conventional
|
|
1493
|
+
* provider env vars when the application did not configure Google itself.
|
|
1494
|
+
* Enterprise / product packages can contribute richer provider sets through
|
|
1495
|
+
* the `auth:configure` hook below.
|
|
1496
|
+
*/
|
|
1497
|
+
applyEnvSocialProviderFallbacks(config) {
|
|
1498
|
+
const env = globalThis?.process?.env;
|
|
1499
|
+
if (String(env?.OS_AUTH_GOOGLE_ENABLED ?? "true").toLowerCase() === "false") return;
|
|
1500
|
+
const googleClientId = env?.GOOGLE_CLIENT_ID;
|
|
1501
|
+
const googleClientSecret = env?.GOOGLE_CLIENT_SECRET;
|
|
1502
|
+
if (!googleClientId || !googleClientSecret) return;
|
|
1503
|
+
const socialProviders = {
|
|
1504
|
+
...config.socialProviders ?? {}
|
|
1505
|
+
};
|
|
1506
|
+
if (!socialProviders.google) {
|
|
1507
|
+
socialProviders.google = {
|
|
1508
|
+
clientId: googleClientId,
|
|
1509
|
+
clientSecret: googleClientSecret,
|
|
1510
|
+
enabled: true
|
|
1511
|
+
};
|
|
1512
|
+
config.socialProviders = socialProviders;
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1446
1515
|
async init(ctx) {
|
|
1447
1516
|
ctx.logger.info("Initializing Auth Plugin...");
|
|
1448
1517
|
if (!this.options.secret) {
|
|
@@ -1452,10 +1521,14 @@ var AuthPlugin = class {
|
|
|
1452
1521
|
if (!dataEngine) {
|
|
1453
1522
|
ctx.logger.warn("No data engine service found - auth will use in-memory storage");
|
|
1454
1523
|
}
|
|
1455
|
-
|
|
1524
|
+
const authConfig = {
|
|
1456
1525
|
...this.options,
|
|
1457
1526
|
dataEngine
|
|
1458
|
-
}
|
|
1527
|
+
};
|
|
1528
|
+
this.applyEnvSocialProviderFallbacks(authConfig);
|
|
1529
|
+
await ctx.trigger("auth:configure", authConfig, ctx);
|
|
1530
|
+
this.configuredSocialProviders = authConfig.socialProviders ? { ...authConfig.socialProviders } : void 0;
|
|
1531
|
+
this.authManager = new AuthManager(authConfig);
|
|
1459
1532
|
ctx.registerService("auth", this.authManager);
|
|
1460
1533
|
ctx.getService("manifest").register({
|
|
1461
1534
|
...authPluginManifestHeader,
|
|
@@ -1484,7 +1557,9 @@ var AuthPlugin = class {
|
|
|
1484
1557
|
// (e.g. legacy `users.view` had phone/status/active columns that do
|
|
1485
1558
|
// not exist on sys_user). Schema-embedded listViews is the single
|
|
1486
1559
|
// source of truth.
|
|
1487
|
-
dashboards: [SystemOverviewDashboard]
|
|
1560
|
+
dashboards: [SystemOverviewDashboard],
|
|
1561
|
+
// ADR-0021 — datasets backing the System Overview dashboard's widgets.
|
|
1562
|
+
datasets: SystemOverviewDatasets
|
|
1488
1563
|
});
|
|
1489
1564
|
ctx.logger.info("Auth Plugin initialized successfully");
|
|
1490
1565
|
}
|
|
@@ -1496,6 +1571,7 @@ var AuthPlugin = class {
|
|
|
1496
1571
|
if (this.options.registerRoutes) {
|
|
1497
1572
|
ctx.hook("kernel:ready", async () => {
|
|
1498
1573
|
if (this.authManager) {
|
|
1574
|
+
await this.bindAuthSettings(ctx);
|
|
1499
1575
|
try {
|
|
1500
1576
|
const emailSvc = ctx.getService("email");
|
|
1501
1577
|
if (emailSvc) {
|
|
@@ -1582,6 +1658,97 @@ var AuthPlugin = class {
|
|
|
1582
1658
|
}
|
|
1583
1659
|
ctx.logger.info("Auth Plugin started successfully");
|
|
1584
1660
|
}
|
|
1661
|
+
/**
|
|
1662
|
+
* Bind the small open-source auth settings namespace to better-auth config.
|
|
1663
|
+
*
|
|
1664
|
+
* Only explicit settings values (stored or OS_AUTH_* env overrides) affect
|
|
1665
|
+
* runtime config. Manifest defaults are UI defaults and do not mask code or
|
|
1666
|
+
* deployment configuration.
|
|
1667
|
+
*/
|
|
1668
|
+
async bindAuthSettings(ctx) {
|
|
1669
|
+
if (!this.authManager) return;
|
|
1670
|
+
let settings;
|
|
1671
|
+
try {
|
|
1672
|
+
settings = ctx.getService("settings");
|
|
1673
|
+
} catch {
|
|
1674
|
+
return;
|
|
1675
|
+
}
|
|
1676
|
+
if (!settings || typeof settings.getNamespace !== "function") return;
|
|
1677
|
+
const applySettings = async () => {
|
|
1678
|
+
if (!this.authManager) return;
|
|
1679
|
+
try {
|
|
1680
|
+
const payload = await settings.getNamespace("auth");
|
|
1681
|
+
const values = {};
|
|
1682
|
+
const sources = {};
|
|
1683
|
+
for (const [key, entry] of Object.entries(payload.values)) {
|
|
1684
|
+
values[key] = entry?.value;
|
|
1685
|
+
sources[key] = entry?.source;
|
|
1686
|
+
}
|
|
1687
|
+
const isExplicit = (key) => (sources[key] ?? "default") !== "default";
|
|
1688
|
+
const asBoolean = (value, fallback) => {
|
|
1689
|
+
if (typeof value === "boolean") return value;
|
|
1690
|
+
if (typeof value === "string") return value.toLowerCase() !== "false";
|
|
1691
|
+
if (typeof value === "number") return value !== 0;
|
|
1692
|
+
return fallback;
|
|
1693
|
+
};
|
|
1694
|
+
const asTrimmedString = (value) => {
|
|
1695
|
+
if (typeof value !== "string") return void 0;
|
|
1696
|
+
const trimmed = value.trim();
|
|
1697
|
+
return trimmed ? trimmed : void 0;
|
|
1698
|
+
};
|
|
1699
|
+
const patch = {};
|
|
1700
|
+
const emailAndPassword = {};
|
|
1701
|
+
if (isExplicit("email_password_enabled")) {
|
|
1702
|
+
emailAndPassword.enabled = asBoolean(values.email_password_enabled, true);
|
|
1703
|
+
}
|
|
1704
|
+
if (isExplicit("signup_enabled")) {
|
|
1705
|
+
emailAndPassword.disableSignUp = !asBoolean(values.signup_enabled, true);
|
|
1706
|
+
}
|
|
1707
|
+
if (isExplicit("require_email_verification")) {
|
|
1708
|
+
emailAndPassword.requireEmailVerification = asBoolean(
|
|
1709
|
+
values.require_email_verification,
|
|
1710
|
+
false
|
|
1711
|
+
);
|
|
1712
|
+
}
|
|
1713
|
+
if (Object.keys(emailAndPassword).length > 0) {
|
|
1714
|
+
patch.emailAndPassword = emailAndPassword;
|
|
1715
|
+
}
|
|
1716
|
+
if (isExplicit("google_enabled") || isExplicit("google_client_id") || isExplicit("google_client_secret")) {
|
|
1717
|
+
const socialProviders = {
|
|
1718
|
+
...this.configuredSocialProviders ?? {}
|
|
1719
|
+
};
|
|
1720
|
+
const env = globalThis?.process?.env;
|
|
1721
|
+
const googleEnabledFromEnv = env?.OS_AUTH_GOOGLE_ENABLED != null ? asBoolean(env.OS_AUTH_GOOGLE_ENABLED, true) : void 0;
|
|
1722
|
+
const googleClientId = asTrimmedString(values.google_client_id) ?? env?.GOOGLE_CLIENT_ID;
|
|
1723
|
+
const googleClientSecret = asTrimmedString(values.google_client_secret) ?? env?.GOOGLE_CLIENT_SECRET;
|
|
1724
|
+
if (googleEnabledFromEnv ?? (isExplicit("google_enabled") ? asBoolean(values.google_enabled, true) : true)) {
|
|
1725
|
+
if (!socialProviders.google && googleClientId && googleClientSecret) {
|
|
1726
|
+
socialProviders.google = {
|
|
1727
|
+
clientId: googleClientId,
|
|
1728
|
+
clientSecret: googleClientSecret,
|
|
1729
|
+
enabled: true
|
|
1730
|
+
};
|
|
1731
|
+
}
|
|
1732
|
+
} else {
|
|
1733
|
+
delete socialProviders.google;
|
|
1734
|
+
}
|
|
1735
|
+
patch.socialProviders = Object.keys(socialProviders).length > 0 ? socialProviders : void 0;
|
|
1736
|
+
}
|
|
1737
|
+
if (Object.keys(patch).length > 0) {
|
|
1738
|
+
this.authManager.applyConfigPatch(patch);
|
|
1739
|
+
}
|
|
1740
|
+
} catch (err) {
|
|
1741
|
+
ctx.logger.warn("Auth: failed to apply auth settings: " + (err?.message ?? err));
|
|
1742
|
+
}
|
|
1743
|
+
};
|
|
1744
|
+
await applySettings();
|
|
1745
|
+
if (typeof settings.subscribe === "function") {
|
|
1746
|
+
settings.subscribe("auth", () => {
|
|
1747
|
+
void applySettings();
|
|
1748
|
+
});
|
|
1749
|
+
ctx.logger.info("Auth: bound to settings namespace=auth");
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1585
1752
|
async destroy() {
|
|
1586
1753
|
this.authManager = null;
|
|
1587
1754
|
}
|