@objectstack/plugin-auth 6.7.1 → 6.8.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/LICENSE +93 -202
- package/README.md +1 -1
- package/dist/index.d.mts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +100 -42
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +101 -42
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// src/auth-plugin.ts
|
|
2
2
|
import {
|
|
3
3
|
SETUP_APP,
|
|
4
|
+
STUDIO_APP,
|
|
4
5
|
SystemOverviewDashboard,
|
|
5
6
|
SetupAppTranslations
|
|
6
7
|
} from "@objectstack/platform-objects/apps";
|
|
@@ -527,6 +528,7 @@ var AuthManager = class {
|
|
|
527
528
|
*/
|
|
528
529
|
async createAuthInstance() {
|
|
529
530
|
const { betterAuth } = await import("better-auth");
|
|
531
|
+
const { createAuthMiddleware } = await import("better-auth/api");
|
|
530
532
|
const plugins = await this.buildPluginList();
|
|
531
533
|
const passwordHasher = await this.resolvePasswordHasher();
|
|
532
534
|
const betterAuthConfig = {
|
|
@@ -585,46 +587,55 @@ var AuthManager = class {
|
|
|
585
587
|
},
|
|
586
588
|
// Social / OAuth providers
|
|
587
589
|
...this.config.socialProviders ? { socialProviders: this.config.socialProviders } : {},
|
|
588
|
-
// Email and password configuration
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
590
|
+
// Email and password configuration.
|
|
591
|
+
// `disableSignUp`: the env var `OS_DISABLE_SIGNUP=true` overrides
|
|
592
|
+
// the config-file value so deployments can flip the toggle without
|
|
593
|
+
// a code change (`getPublicConfig()` applies the same precedence so
|
|
594
|
+
// `/auth/config` stays consistent with the server enforcement).
|
|
595
|
+
emailAndPassword: (() => {
|
|
596
|
+
const disableSignUpEnv = globalThis?.process?.env?.OS_DISABLE_SIGNUP;
|
|
597
|
+
const disableSignUpFromEnv = disableSignUpEnv != null ? String(disableSignUpEnv).toLowerCase() === "true" : void 0;
|
|
598
|
+
const effectiveDisableSignUp = disableSignUpFromEnv ?? this.config.emailAndPassword?.disableSignUp;
|
|
599
|
+
return {
|
|
600
|
+
enabled: this.config.emailAndPassword?.enabled ?? true,
|
|
601
|
+
...passwordHasher ? { password: passwordHasher } : {},
|
|
602
|
+
...effectiveDisableSignUp != null ? { disableSignUp: effectiveDisableSignUp } : {},
|
|
603
|
+
...this.config.emailAndPassword?.requireEmailVerification != null ? { requireEmailVerification: this.config.emailAndPassword.requireEmailVerification } : {},
|
|
604
|
+
...this.config.emailAndPassword?.minPasswordLength != null ? { minPasswordLength: this.config.emailAndPassword.minPasswordLength } : {},
|
|
605
|
+
...this.config.emailAndPassword?.maxPasswordLength != null ? { maxPasswordLength: this.config.emailAndPassword.maxPasswordLength } : {},
|
|
606
|
+
...this.config.emailAndPassword?.resetPasswordTokenExpiresIn != null ? { resetPasswordTokenExpiresIn: this.config.emailAndPassword.resetPasswordTokenExpiresIn } : {},
|
|
607
|
+
...this.config.emailAndPassword?.autoSignIn != null ? { autoSignIn: this.config.emailAndPassword.autoSignIn } : {},
|
|
608
|
+
...this.config.emailAndPassword?.revokeSessionsOnPasswordReset != null ? { revokeSessionsOnPasswordReset: this.config.emailAndPassword.revokeSessionsOnPasswordReset } : {},
|
|
609
|
+
sendResetPassword: async ({ user, url, token }) => {
|
|
610
|
+
const email = this.getEmailService();
|
|
611
|
+
if (!email) {
|
|
612
|
+
console.warn(
|
|
613
|
+
`[AuthManager] Password-reset requested for ${user.email} but no email service is wired. URL: ${url}`
|
|
614
|
+
);
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
const ttlSec = this.config.emailAndPassword?.resetPasswordTokenExpiresIn ?? 60 * 60;
|
|
618
|
+
try {
|
|
619
|
+
await email.sendTemplate({
|
|
620
|
+
template: "auth.password_reset",
|
|
621
|
+
to: { address: user.email, ...user.name ? { name: user.name } : {} },
|
|
622
|
+
data: {
|
|
623
|
+
user: { name: user.name || user.email, email: user.email, id: user.id },
|
|
624
|
+
resetUrl: url,
|
|
625
|
+
token,
|
|
626
|
+
expiresInMinutes: Math.round(ttlSec / 60),
|
|
627
|
+
appName: this.getAppName()
|
|
628
|
+
},
|
|
629
|
+
relatedObject: "sys_user",
|
|
630
|
+
relatedId: user.id
|
|
631
|
+
});
|
|
632
|
+
} catch (err) {
|
|
633
|
+
console.error(`[AuthManager] sendResetPassword failed: ${err?.message ?? err}`);
|
|
634
|
+
throw err;
|
|
635
|
+
}
|
|
625
636
|
}
|
|
626
|
-
}
|
|
627
|
-
},
|
|
637
|
+
};
|
|
638
|
+
})(),
|
|
628
639
|
// Email verification
|
|
629
640
|
...this.config.emailVerification || this.config.emailService ? {
|
|
630
641
|
emailVerification: {
|
|
@@ -676,6 +687,38 @@ var AuthManager = class {
|
|
|
676
687
|
// for SSO JIT-provisioning too, unlike kernel-level ObjectQL
|
|
677
688
|
// middleware which better-auth's adapter bypasses).
|
|
678
689
|
...this.config.databaseHooks ? { databaseHooks: this.config.databaseHooks } : {},
|
|
690
|
+
// Bootstrap bypass for `disableSignUp`. The first-run owner wizard
|
|
691
|
+
// (`/_account/setup`) calls `POST /auth/sign-up/email` to create
|
|
692
|
+
// the very first user — if `OS_DISABLE_SIGNUP=true` is set on a
|
|
693
|
+
// fresh install we'd lock the operator out of their own instance.
|
|
694
|
+
// Solution: when the request hits `/sign-up/email` AND no users
|
|
695
|
+
// exist yet, temporarily flip `disableSignUp` off for *this*
|
|
696
|
+
// request's context. Once the owner is created the next request
|
|
697
|
+
// sees `userCount > 0` and the toggle is enforced again.
|
|
698
|
+
hooks: {
|
|
699
|
+
before: createAuthMiddleware(async (ctx) => {
|
|
700
|
+
if (ctx?.path !== "/sign-up/email") return;
|
|
701
|
+
const ep = ctx?.context?.options?.emailAndPassword;
|
|
702
|
+
if (!ep?.disableSignUp) return;
|
|
703
|
+
try {
|
|
704
|
+
const adapter = ctx.context.adapter;
|
|
705
|
+
const existing = await adapter.findOne({ model: "user", where: [] });
|
|
706
|
+
if (!existing) {
|
|
707
|
+
ctx.context.__osDisableSignUpOrig = ep.disableSignUp;
|
|
708
|
+
ep.disableSignUp = false;
|
|
709
|
+
}
|
|
710
|
+
} catch {
|
|
711
|
+
}
|
|
712
|
+
}),
|
|
713
|
+
after: createAuthMiddleware(async (ctx) => {
|
|
714
|
+
if (ctx?.path !== "/sign-up/email") return;
|
|
715
|
+
const ep = ctx?.context?.options?.emailAndPassword;
|
|
716
|
+
if (ep && ctx.context.__osDisableSignUpOrig !== void 0) {
|
|
717
|
+
ep.disableSignUp = ctx.context.__osDisableSignUpOrig;
|
|
718
|
+
delete ctx.context.__osDisableSignUpOrig;
|
|
719
|
+
}
|
|
720
|
+
})
|
|
721
|
+
},
|
|
679
722
|
// Trusted origins for CSRF protection (supports wildcards like "https://*.example.com")
|
|
680
723
|
// Auto-includes origins from CORS_ORIGIN env var so CORS and CSRF stay in sync.
|
|
681
724
|
...(() => {
|
|
@@ -1219,16 +1262,30 @@ var AuthManager = class {
|
|
|
1219
1262
|
}
|
|
1220
1263
|
}
|
|
1221
1264
|
const emailPasswordConfig = this.config.emailAndPassword ?? {};
|
|
1265
|
+
const disableSignUpEnv = globalThis?.process?.env?.OS_DISABLE_SIGNUP;
|
|
1266
|
+
const disableSignUpFromEnv = disableSignUpEnv != null ? String(disableSignUpEnv).toLowerCase() === "true" : void 0;
|
|
1222
1267
|
const emailPassword = {
|
|
1223
1268
|
enabled: emailPasswordConfig.enabled !== false,
|
|
1224
1269
|
// Default to true
|
|
1225
|
-
disableSignUp: emailPasswordConfig.disableSignUp ?? false,
|
|
1270
|
+
disableSignUp: disableSignUpFromEnv ?? emailPasswordConfig.disableSignUp ?? false,
|
|
1226
1271
|
requireEmailVerification: emailPasswordConfig.requireEmailVerification ?? false
|
|
1227
1272
|
};
|
|
1228
1273
|
const pluginConfig = this.config.plugins ?? {};
|
|
1229
1274
|
const multiOrgEnabled = String(
|
|
1230
1275
|
globalThis?.process?.env?.OS_MULTI_ORG_ENABLED ?? "true"
|
|
1231
1276
|
).toLowerCase() !== "false";
|
|
1277
|
+
const DEFAULT_TERMS_URL = "https://objectstack.ai/terms";
|
|
1278
|
+
const DEFAULT_PRIVACY_URL = "https://objectstack.ai/privacy";
|
|
1279
|
+
const rawTermsUrl = globalThis?.process?.env?.OS_TERMS_URL;
|
|
1280
|
+
const rawPrivacyUrl = globalThis?.process?.env?.OS_PRIVACY_URL;
|
|
1281
|
+
const resolveLegalUrl = (raw, fallback) => {
|
|
1282
|
+
if (typeof raw !== "string") return fallback;
|
|
1283
|
+
const trimmed = raw.trim();
|
|
1284
|
+
if (trimmed === "") return void 0;
|
|
1285
|
+
return trimmed;
|
|
1286
|
+
};
|
|
1287
|
+
const termsUrl = resolveLegalUrl(rawTermsUrl, DEFAULT_TERMS_URL);
|
|
1288
|
+
const privacyUrl = resolveLegalUrl(rawPrivacyUrl, DEFAULT_PRIVACY_URL);
|
|
1232
1289
|
const features = {
|
|
1233
1290
|
twoFactor: pluginConfig.twoFactor ?? false,
|
|
1234
1291
|
passkeys: pluginConfig.passkeys ?? false,
|
|
@@ -1236,7 +1293,9 @@ var AuthManager = class {
|
|
|
1236
1293
|
organization: pluginConfig.organization ?? true,
|
|
1237
1294
|
multiOrgEnabled,
|
|
1238
1295
|
oidcProvider: pluginConfig.oidcProvider ?? false,
|
|
1239
|
-
deviceAuthorization: pluginConfig.deviceAuthorization ?? false
|
|
1296
|
+
deviceAuthorization: pluginConfig.deviceAuthorization ?? false,
|
|
1297
|
+
...termsUrl ? { termsUrl } : {},
|
|
1298
|
+
...privacyUrl ? { privacyUrl } : {}
|
|
1240
1299
|
};
|
|
1241
1300
|
return {
|
|
1242
1301
|
emailPassword,
|
|
@@ -1344,7 +1403,7 @@ var AuthPlugin = class {
|
|
|
1344
1403
|
// @objectstack/platform-objects/apps). plugin-auth is the natural
|
|
1345
1404
|
// owner of its registration since it loads first among the trio
|
|
1346
1405
|
// (auth + security + audit) that supplies the underlying objects.
|
|
1347
|
-
apps: [SETUP_APP],
|
|
1406
|
+
apps: [SETUP_APP, STUDIO_APP],
|
|
1348
1407
|
// List views for each Setup-nav object are defined on the schema
|
|
1349
1408
|
// itself via the canonical `listViews` map (e.g.
|
|
1350
1409
|
// sys_user.listViews.{all_users,unverified,two_factor}). Registering
|