Skip to content

Commit ef155aa

Browse files
authored
Merge pull request #112 from reqcore-inc/feat/better-demo
feat(auth): improve BETTER_AUTH_URL handling for Railway environments…
2 parents 8c530cf + e368cc8 commit ef155aa

3 files changed

Lines changed: 26 additions & 37 deletions

File tree

.env.example

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ BETTER_AUTH_SECRET=replace-with-openssl-rand-base64-32-output
2222

2323
# Public URL of the app (used for session cookies and OAuth callbacks)
2424
# Local: http://localhost:3000 | Production: https://yourdomain.com
25-
# Railway PR/preview: auto-resolved from RAILWAY_PUBLIC_DOMAIN
25+
# Railway: auto-resolved from RAILWAY_PUBLIC_DOMAIN — only set for custom domains
2626
BETTER_AUTH_URL=http://localhost:3000
2727

2828
# Comma-separated allowed origins (optional, for multi-domain setups)

server/utils/auth.ts

Lines changed: 10 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -27,40 +27,24 @@ function resolveTrustedOrigins(baseUrl: string): string[] {
2727
function resolveBetterAuthUrl(): string {
2828
const explicitUrl = env.BETTER_AUTH_URL?.trim()
2929
const railwayDomain = env.RAILWAY_PUBLIC_DOMAIN?.trim()
30-
const hasPreviewDomain = railwayDomain ? railwayDomain.toLowerCase().includes('-pr-') : false
31-
const hasPrNumber = !!env.RAILWAY_GIT_PR_NUMBER?.trim()
32-
const isPreview = isRailwayPreviewEnvironment(env.RAILWAY_ENVIRONMENT_NAME) || hasPreviewDomain || hasPrNumber
33-
34-
if (!isPreview) {
35-
if (!explicitUrl) {
36-
throw new Error('BETTER_AUTH_URL is required outside Railway PR/preview environments')
37-
}
3830

31+
// Explicit URL always wins (custom domain, local dev, etc.)
32+
if (explicitUrl) {
3933
return explicitUrl
4034
}
4135

36+
// Derive from Railway's auto-injected public domain (works for all environments)
4237
if (railwayDomain) {
43-
const previewUrl = `https://${railwayDomain}`
44-
console.info(`[Reqcore] Using Railway public-domain BETTER_AUTH_URL: ${previewUrl}`)
45-
return previewUrl
46-
}
47-
48-
const prNumber = env.RAILWAY_GIT_PR_NUMBER?.trim()
49-
if (prNumber) {
50-
console.warn(
51-
`[Reqcore] Railway PR number detected (${prNumber}) but RAILWAY_PUBLIC_DOMAIN is missing. ` +
52-
'Set BETTER_AUTH_URL explicitly or ensure Railway generated domains are enabled.',
53-
)
54-
}
55-
56-
if (explicitUrl) {
57-
console.info('[Reqcore] Using explicit BETTER_AUTH_URL in Railway PR/preview environment')
58-
return explicitUrl
38+
// Railway sets this as bare domain (e.g. "app.up.railway.app"), never with protocol
39+
const domain = railwayDomain.replace(/^https?:\/\//, '')
40+
const url = `https://${domain}`
41+
console.info(`[Reqcore] Using Railway public-domain BETTER_AUTH_URL: ${url}`)
42+
return url
5943
}
6044

6145
throw new Error(
62-
'Unable to resolve BETTER_AUTH_URL in Railway PR/preview environment. ' +
63-
'Set RAILWAY_GIT_PR_NUMBER, RAILWAY_PUBLIC_DOMAIN, or BETTER_AUTH_URL.',
46+
'BETTER_AUTH_URL is required. Either set it explicitly or generate a public domain in Railway.\n' +
47+
'Railway users: go to Settings → Networking → Generate Domain, then redeploy.',
6448
)
6549
}
6650

server/utils/env.ts

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,16 @@ const envSchema = z
3838
.object({
3939
DATABASE_URL: z.url(),
4040
BETTER_AUTH_SECRET: emptyToUndefined.pipe(z.string().min(32, 'BETTER_AUTH_SECRET must be at least 32 characters')),
41-
BETTER_AUTH_URL: emptyToUndefined.pipe(z.url()).optional(),
41+
BETTER_AUTH_URL: z.preprocess(
42+
(val) => {
43+
if (typeof val !== 'string') return val
44+
const trimmed = val.trim()
45+
// Treat empty strings and broken Railway template refs ("https://") as unset
46+
if (trimmed === '' || trimmed === 'https://' || trimmed === 'http://') return undefined
47+
return trimmed
48+
},
49+
z.string().url(),
50+
).optional(),
4251
/** Comma-separated list of additional trusted origins for Better Auth CSRF checks. */
4352
BETTER_AUTH_TRUSTED_ORIGINS: emptyToUndefined
4453
.pipe(z.string())
@@ -81,17 +90,13 @@ const envSchema = z
8190
CRON_SECRET: emptyToUndefined.pipe(z.string().min(16)).optional(),
8291
})
8392
.superRefine((data, ctx) => {
84-
const hasPreviewDomain = data.RAILWAY_PUBLIC_DOMAIN
85-
? data.RAILWAY_PUBLIC_DOMAIN.toLowerCase().includes('-pr-')
86-
: false
87-
const hasPrNumber = !!data.RAILWAY_GIT_PR_NUMBER
88-
const isPreview = isRailwayPreviewEnvironment(data.RAILWAY_ENVIRONMENT_NAME) || hasPreviewDomain || hasPrNumber
89-
90-
if (!isPreview && !data.BETTER_AUTH_URL) {
93+
// BETTER_AUTH_URL can be derived at runtime from RAILWAY_PUBLIC_DOMAIN,
94+
// so it's only required when not running on Railway.
95+
if (!data.BETTER_AUTH_URL && !data.RAILWAY_PUBLIC_DOMAIN) {
9196
ctx.addIssue({
9297
code: z.ZodIssueCode.custom,
9398
path: ['BETTER_AUTH_URL'],
94-
message: 'BETTER_AUTH_URL is required outside Railway PR/preview environments',
99+
message: 'BETTER_AUTH_URL is required when RAILWAY_PUBLIC_DOMAIN is not available',
95100
})
96101
}
97102
})
@@ -124,7 +129,7 @@ export const env = new Proxy({} as z.infer<typeof envSchema>, {
124129
`\n[Reqcore] ❌ Missing or invalid environment variables:\n${missing}\n\n` +
125130
`Ensure these variables are set in your Railway service (Settings → Variables).\n` +
126131
`Required: DATABASE_URL, BETTER_AUTH_SECRET, S3_ENDPOINT, S3_ACCESS_KEY, S3_SECRET_KEY, S3_BUCKET\n` +
127-
`Required outside Railway PR/preview environments: BETTER_AUTH_URL\n` +
132+
`Required when not on Railway: BETTER_AUTH_URL (or generate a Railway domain)\n` +
128133
`Optional: BETTER_AUTH_TRUSTED_ORIGINS, S3_REGION (default: us-east-1), S3_FORCE_PATH_STYLE (default: true), TRUSTED_PROXY_IP, DEMO_ORG_SLUG, RESEND_API_KEY, RESEND_FROM_EMAIL\n`,
129134
)
130135
throw result.error

0 commit comments

Comments
 (0)