fix(#414): extract trustedOrigins to getTrustedOrigins() with env vars
All checks were successful
ci/woodpecker/push/api Pipeline was successful

Replace hardcoded production URLs with environment-driven config.
Reads NEXT_PUBLIC_APP_URL, NEXT_PUBLIC_API_URL, TRUSTED_ORIGINS.
Localhost fallbacks only in development mode.

Refs #414

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jason Woltje
2026-02-16 11:25:58 -06:00
parent b316e98b64
commit 7ebbcbf958

View File

@@ -18,12 +18,7 @@ vi.mock("better-auth/adapters/prisma", () => ({
prismaAdapter: (...args: unknown[]) => mockPrismaAdapter(...args),
}));
import {
isOidcEnabled,
validateOidcConfig,
createAuth,
getTrustedOrigins,
} from "./auth.config";
import { isOidcEnabled, validateOidcConfig, createAuth, getTrustedOrigins } from "./auth.config";
describe("auth.config", () => {
// Store original env vars to restore after each test
@@ -290,6 +285,127 @@ describe("auth.config", () => {
});
});
describe("getTrustedOrigins", () => {
it("should return localhost URLs when NODE_ENV is not production", () => {
process.env.NODE_ENV = "development";
const origins = getTrustedOrigins();
expect(origins).toContain("http://localhost:3000");
expect(origins).toContain("http://localhost:3001");
});
it("should return localhost URLs when NODE_ENV is not set", () => {
// NODE_ENV is deleted in beforeEach, so it's undefined here
const origins = getTrustedOrigins();
expect(origins).toContain("http://localhost:3000");
expect(origins).toContain("http://localhost:3001");
});
it("should exclude localhost URLs in production", () => {
process.env.NODE_ENV = "production";
const origins = getTrustedOrigins();
expect(origins).not.toContain("http://localhost:3000");
expect(origins).not.toContain("http://localhost:3001");
});
it("should parse TRUSTED_ORIGINS comma-separated values", () => {
process.env.TRUSTED_ORIGINS =
"https://app.mosaicstack.dev,https://api.mosaicstack.dev";
const origins = getTrustedOrigins();
expect(origins).toContain("https://app.mosaicstack.dev");
expect(origins).toContain("https://api.mosaicstack.dev");
});
it("should trim whitespace from TRUSTED_ORIGINS entries", () => {
process.env.TRUSTED_ORIGINS =
" https://app.mosaicstack.dev , https://api.mosaicstack.dev ";
const origins = getTrustedOrigins();
expect(origins).toContain("https://app.mosaicstack.dev");
expect(origins).toContain("https://api.mosaicstack.dev");
});
it("should filter out empty strings from TRUSTED_ORIGINS", () => {
process.env.TRUSTED_ORIGINS = "https://app.mosaicstack.dev,,, ,";
const origins = getTrustedOrigins();
expect(origins).toContain("https://app.mosaicstack.dev");
// No empty strings in the result
origins.forEach((o) => expect(o).not.toBe(""));
});
it("should include NEXT_PUBLIC_APP_URL", () => {
process.env.NEXT_PUBLIC_APP_URL = "https://my-app.example.com";
const origins = getTrustedOrigins();
expect(origins).toContain("https://my-app.example.com");
});
it("should include NEXT_PUBLIC_API_URL", () => {
process.env.NEXT_PUBLIC_API_URL = "https://my-api.example.com";
const origins = getTrustedOrigins();
expect(origins).toContain("https://my-api.example.com");
});
it("should deduplicate origins", () => {
process.env.NEXT_PUBLIC_APP_URL = "http://localhost:3000";
process.env.TRUSTED_ORIGINS = "http://localhost:3000,http://localhost:3001";
// NODE_ENV not set, so localhost fallbacks are also added
const origins = getTrustedOrigins();
const countLocalhost3000 = origins.filter((o) => o === "http://localhost:3000").length;
const countLocalhost3001 = origins.filter((o) => o === "http://localhost:3001").length;
expect(countLocalhost3000).toBe(1);
expect(countLocalhost3001).toBe(1);
});
it("should handle all env vars missing gracefully", () => {
// All env vars deleted in beforeEach; NODE_ENV is also deleted (not production)
const origins = getTrustedOrigins();
// Should still return localhost fallbacks since not in production
expect(origins).toContain("http://localhost:3000");
expect(origins).toContain("http://localhost:3001");
expect(origins).toHaveLength(2);
});
it("should return empty array when all env vars missing in production", () => {
process.env.NODE_ENV = "production";
const origins = getTrustedOrigins();
expect(origins).toHaveLength(0);
});
it("should combine all sources correctly", () => {
process.env.NEXT_PUBLIC_APP_URL = "https://app.mosaicstack.dev";
process.env.NEXT_PUBLIC_API_URL = "https://api.mosaicstack.dev";
process.env.TRUSTED_ORIGINS = "https://extra.example.com";
process.env.NODE_ENV = "development";
const origins = getTrustedOrigins();
expect(origins).toContain("https://app.mosaicstack.dev");
expect(origins).toContain("https://api.mosaicstack.dev");
expect(origins).toContain("https://extra.example.com");
expect(origins).toContain("http://localhost:3000");
expect(origins).toContain("http://localhost:3001");
expect(origins).toHaveLength(5);
});
});
describe("createAuth - session and cookie configuration", () => {
beforeEach(() => {
mockGenericOAuth.mockClear();