Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

- Backend CORS now accepts configured origins plus `*.lucassantana.tech` and `*.luk-homeserver.com.br` hosts for dashboard/API split-domain setups
- Frontend API client now auto-resolves hosted API base to `api.lucassantana.tech` or `api.luk-homeserver.com.br` when `VITE_API_BASE_URL` is not set
- Vercel routing no longer rewrites `/api/*` back to the same Lucky host, preventing `508 INFINITE_LOOP` on OAuth login
- Frontend API base URL now supports `VITE_API_BASE_URL` for hosted deployments that use a separate backend origin
- Deploy webhook trigger now uses strict curl connect/request timeouts to avoid long hangs in CI deploy jobs
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ Triggers the GitHub `Deploy to Homelab` workflow, waits for completion, and show
Vercel note: `vercel.json` runs `npm run db:generate` before `build:shared` and `build:frontend` to ensure Prisma generated client files are present during cloud builds.
For hosted frontend deployments, set `VITE_API_BASE_URL` to your backend API origin
(example: `https://api.yourdomain.com/api`) to avoid auth/API loop misrouting.
Without `VITE_API_BASE_URL`, frontend now auto-targets `api.lucassantana.tech` for
`*.lucassantana.tech` hosts and `api.luk-homeserver.com.br` for `*.luk-homeserver.com.br`.

## Environment Variables

Expand Down
2 changes: 2 additions & 0 deletions docs/WEBAPP_SETUP.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ For hosted frontend builds (Vercel/Netlify), set frontend env:
VITE_API_BASE_URL=https://api.yourdomain.com/api
```

`WEBAPP_FRONTEND_URL` also supports comma-separated origins for CORS
(for example `https://lucky.lucassantana.tech,https://app.lucassantana.tech`).
### Environment Variable Descriptions

- **CLIENT_ID**: Your Discord application's Client ID from the [Discord Developer Portal](https://discord.com/developers/applications)
Expand Down
37 changes: 36 additions & 1 deletion packages/backend/src/middleware/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,45 @@
export function setupMiddleware(app: Express): void {
const frontendUrl =
process.env.WEBAPP_FRONTEND_URL ?? 'http://localhost:5173'
const configuredOrigins = frontendUrl

Check warning on line 15 in packages/backend/src/middleware/index.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

`configuredOrigins` should be a `Set`, and use `configuredOrigins.has()` to check existence or non-existence.

See more on https://sonarcloud.io/project/issues?id=LucasSantana-Dev_NexusBot&issues=AZzVVP0XXN7jUgP80iAK&open=AZzVVP0XXN7jUgP80iAK&pullRequest=126
.split(',')
.map((origin) => origin.trim())
.filter((origin) => origin.length > 0)

const isAllowedOrigin = (origin: string): boolean => {
if (configuredOrigins.includes(origin)) {
return true
}

try {
const parsed = new URL(origin)
const host = parsed.hostname.toLowerCase()

if (host === 'localhost' || host === '127.0.0.1') {
return true
}

return (
host === 'lucassantana.tech' ||
host.endsWith('.lucassantana.tech') ||
host === 'luk-homeserver.com.br' ||
host.endsWith('.luk-homeserver.com.br')
)
} catch {
return false
}
}

app.use(
cors({
origin: frontendUrl,
origin: (origin, callback) => {
if (!origin || isAllowedOrigin(origin)) {
callback(null, true)
return
}

callback(new Error('Not allowed by CORS'))
},
credentials: true,
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization'],
Expand Down
33 changes: 29 additions & 4 deletions packages/frontend/src/services/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,35 @@
import { createLogsApi } from './logsApi'

const configuredApiBase = import.meta.env.VITE_API_BASE_URL?.trim()
const API_BASE = (configuredApiBase && configuredApiBase.length > 0
? configuredApiBase
: '/api'
).replace(/\/+$/, '')

const inferApiBase = (): string => {
if (configuredApiBase && configuredApiBase.length > 0) {
return configuredApiBase
}

if (typeof window !== 'undefined') {

Check warning on line 25 in packages/frontend/src/services/api.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `globalThis.window` over `window`.

See more on https://sonarcloud.io/project/issues?id=LucasSantana-Dev_NexusBot&issues=AZzVVP2cXN7jUgP80iAL&open=AZzVVP2cXN7jUgP80iAL&pullRequest=126
const protocol = window.location.protocol || 'https:'

Check warning on line 26 in packages/frontend/src/services/api.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `globalThis` over `window`.

See more on https://sonarcloud.io/project/issues?id=LucasSantana-Dev_NexusBot&issues=AZzVVP2cXN7jUgP80iAM&open=AZzVVP2cXN7jUgP80iAM&pullRequest=126
const hostname = window.location.hostname

Check warning on line 27 in packages/frontend/src/services/api.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `globalThis` over `window`.

See more on https://sonarcloud.io/project/issues?id=LucasSantana-Dev_NexusBot&issues=AZzVVP2cXN7jUgP80iAN&open=AZzVVP2cXN7jUgP80iAN&pullRequest=126

if (
hostname === 'lucassantana.tech' ||
hostname.endsWith('.lucassantana.tech')
) {
return `${protocol}//api.lucassantana.tech/api`
}

if (
hostname === 'luk-homeserver.com.br' ||
hostname.endsWith('.luk-homeserver.com.br')
) {
return `${protocol}//api.luk-homeserver.com.br/api`
}
}

return '/api'
}

const API_BASE = inferApiBase().replace(/\/+$/, '')
Comment on lines +18 to +47

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for hardcoded /api/ paths in frontend services that bypass API_BASE
rg -n --type=ts '/api/' packages/frontend/src/services/ | grep -v 'API_BASE' | grep -v 'import'

Repository: LucasSantana-Dev/Lucky

Length of output: 281


Use API_BASE for EventSource and other API endpoints instead of hardcoding /api/ paths.

The inferApiBase function correctly resolves the API base URL from environment variables with host-based fallbacks. However, hardcoded paths in packages/frontend/src/services/musicApi.ts (line 74) and packages/frontend/src/services/api.ts (line 345) bypass this configuration:

  • EventSource at line 74 hardcodes /api/guilds/${guildId}/music/stream
  • getConnectUrl at line 345 hardcodes /api/lastfm/connect

These will fail when the frontend and backend are on different origins. Replace with ${API_BASE}/guilds/${guildId}/music/stream and similar patterns.

🧰 Tools
🪛 GitHub Check: SonarCloud Code Analysis

[warning] 26-26: Prefer globalThis over window.

See more on https://sonarcloud.io/project/issues?id=LucasSantana-Dev_Nexus&issues=AZzVSEqguXUW7gR7dkbB&open=AZzVSEqguXUW7gR7dkbB&pullRequest=126


[warning] 27-27: Prefer globalThis over window.

See more on https://sonarcloud.io/project/issues?id=LucasSantana-Dev_Nexus&issues=AZzVSEqguXUW7gR7dkbC&open=AZzVSEqguXUW7gR7dkbC&pullRequest=126


[warning] 25-25: Prefer globalThis.window over window.

See more on https://sonarcloud.io/project/issues?id=LucasSantana-Dev_Nexus&issues=AZzVSEqguXUW7gR7dkbA&open=AZzVSEqguXUW7gR7dkbA&pullRequest=126

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/frontend/src/services/api.ts` around lines 18 - 47, The frontend is
still using hardcoded "/api/..." paths which bypass the resolved base URL from
inferApiBase/API_BASE; update all usages (e.g., the EventSource creation in
musicApi.ts and the getConnectUrl function) to use the configured API_BASE
instead of hardcoded strings. Replace instances like
'/api/guilds/${guildId}/music/stream' with
`${API_BASE}/guilds/${guildId}/music/stream` and '/api/lastfm/connect' with
`${API_BASE}/lastfm/connect`, and ensure the modules import API_BASE (from the
file that defines inferApiBase/API_BASE) so requests work across different
origins; note API_BASE is already trimmed of trailing slashes so string
concatenation as shown is safe.


interface BackendGuild {
id: string
Expand Down