Skip to content

Commit a5657ed

Browse files
hotlongCopilot
andcommitted
fix(cli): widen better-auth trustedOrigins for preview-mode subdomains
Better-auth's CSRF check uses a glob that does not cross dot-separators, so the default `http://localhost:*` pattern rejected preview hosts of the form `<commit>--<pidShort>.localhost:<port>`. The browser sign-up POST to /api/v1/auth/sign-up/email returned 403 with '[Better Auth]: Invalid origin: http://...localhost:3100'. The CLI's auto-registered AuthPlugin now builds the trustedOrigins allow-list explicitly: - explicit OS_TRUSTED_ORIGINS (comma-separated) wins - the configured baseUrl is always added - in OS_PREVIEW_MODE=1, add `http(s)://*.<base>:<*?>` for every domain in OS_PREVIEW_BASE_DOMAINS (default 'preview.objectstack.ai,localhost'); loopback bases use http+:* - dev keeps the legacy `http://localhost:*` for non-preview portals Verified end-to-end: a preview host now serves /_account/setup, the sign-up POST succeeds, the user is redirected to /_console/home, and clicking the Enterprise CRM card renders the lead view with the three sandbox lead records pulled from the per-(project,commit) memory kernel. No source change is needed in the AuthPlugin, console, or preview stack. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 0404d30 commit a5657ed

1 file changed

Lines changed: 43 additions & 0 deletions

File tree

packages/cli/src/commands/serve.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -595,10 +595,53 @@ export default class Serve extends Command {
595595
if (process.env.GITHUB_CLIENT_ID && process.env.GITHUB_CLIENT_SECRET)
596596
socialProviders.github = { clientId: process.env.GITHUB_CLIENT_ID, clientSecret: process.env.GITHUB_CLIENT_SECRET };
597597

598+
// Trusted origins (CSRF). better-auth uses a `*` glob that
599+
// does NOT cross dot-separators, so `http://localhost:*` does
600+
// not cover `http://<sub>.localhost:*`. Build the allow-list
601+
// explicitly:
602+
// - explicit `OS_TRUSTED_ORIGINS` (comma-separated) wins
603+
// - else dev / preview defaults below
604+
const trustedOrigins: string[] = [];
605+
const explicitTrusted = process.env.OS_TRUSTED_ORIGINS?.trim();
606+
if (explicitTrusted) {
607+
explicitTrusted.split(',').map(s => s.trim()).filter(Boolean).forEach(o => {
608+
if (!trustedOrigins.includes(o)) trustedOrigins.push(o);
609+
});
610+
}
611+
// Always add the configured baseUrl so first-party redirects work.
612+
try {
613+
const u = new URL(baseUrl);
614+
const baseOrigin = `${u.protocol}//${u.host}`;
615+
if (!trustedOrigins.includes(baseOrigin)) trustedOrigins.push(baseOrigin);
616+
} catch { /* ignore malformed baseUrl */ }
617+
// Preview-mode subdomain wildcards (`<commit>--<pid>.<base>`).
618+
// Honour `OS_PREVIEW_BASE_DOMAINS` (used by service-cloud's
619+
// preview routing) and add `http://*.<base>:*` patterns.
620+
const previewMode = (process.env.OS_PREVIEW_MODE ?? '').trim().toLowerCase();
621+
const isPreviewMode = previewMode === '1' || previewMode === 'true' || previewMode === 'yes';
622+
if (isPreviewMode) {
623+
const baseDomains = (process.env.OS_PREVIEW_BASE_DOMAINS
624+
?? 'preview.objectstack.ai,localhost')
625+
.split(',').map(s => s.trim()).filter(Boolean);
626+
for (const dom of baseDomains) {
627+
const isLoopback = dom === 'localhost' || dom.endsWith('.localhost');
628+
const scheme = isLoopback ? 'http' : 'https';
629+
const portSuffix = isLoopback ? ':*' : '';
630+
const wildcard = `${scheme}://*.${dom}${portSuffix}`;
631+
if (!trustedOrigins.includes(wildcard)) trustedOrigins.push(wildcard);
632+
}
633+
}
634+
// Dev convenience: keep `http://localhost:*` so plain
635+
// `localhost:<port>` still works for non-preview Studio/Console.
636+
if (isDev && !trustedOrigins.includes('http://localhost:*')) {
637+
trustedOrigins.push('http://localhost:*');
638+
}
639+
598640
await kernel.use(new AuthPlugin({
599641
secret,
600642
baseUrl,
601643
socialProviders: Object.keys(socialProviders).length > 0 ? socialProviders : undefined,
644+
trustedOrigins: trustedOrigins.length ? trustedOrigins : undefined,
602645
}));
603646
trackPlugin('Auth');
604647

0 commit comments

Comments
 (0)