Skip to content

Commit e20a6ee

Browse files
committed
feat(config): add homepage URL and CORS allowed origins support
- Introduced `HOMEPAGE_URL` and `CORS_ALLOWED_ORIGINS` environment variables to enhance CORS configuration. - Updated `docker-compose.prod.yml` to include new environment variables. - Modified CORS middleware in `main.go` to utilize the new homepage URL and allowed origins. - Adjusted configuration loading in `config.go` to parse the new environment variables. - Enhanced unit tests to validate the new configuration options.
1 parent 2f2f912 commit e20a6ee

5 files changed

Lines changed: 49 additions & 3 deletions

File tree

SECURITY.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ The registry supports three authentication methods:
4747

4848
### CORS
4949

50-
CORS is restricted to an explicit origin allowlist derived from `BASE_URL` and `FRONTEND_URL` configuration. Credentials are only sent for matching origins.
50+
CORS is restricted to an explicit origin allowlist derived from `BASE_URL`, `FRONTEND_URL`, `HOMEPAGE_URL`, and optional `CORS_ALLOWED_ORIGINS` configuration. Credentials are only sent for matching origins.
5151

5252
### Rate Limiting
5353

api/cmd/registry/main.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,14 @@ func main() {
7777
r.Use(chimiddleware.RealIP)
7878
r.Use(middleware.Logger(log.Logger))
7979
r.Use(middleware.Recover)
80-
r.Use(middleware.CORSWithAllowlist(cfg.BaseURL, cfg.FrontendURL))
80+
corsOrigins := append([]string{
81+
cfg.BaseURL,
82+
cfg.FrontendURL,
83+
cfg.HomepageURL,
84+
"https://www.devsper.com",
85+
"https://devsper.com"
86+
}, cfg.CORSAllowedOrigins...)
87+
r.Use(middleware.CORSWithAllowlist(corsOrigins...))
8188

8289
// Public
8390
r.Get("/health", health.Liveness)

api/internal/config/config.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"net/url"
66
"os"
77
"strconv"
8+
"strings"
89
"time"
910
)
1011

@@ -51,6 +52,10 @@ type Config struct {
5152
// App
5253
BaseURL string // API public URL (for OAuth redirect_uri)
5354
FrontendURL string // Where to redirect after OAuth login (defaults to BaseURL if empty)
55+
HomepageURL string // Homepage URL for CORS and other links
56+
// Additional CORS origins (comma-separated env var CORS_ALLOWED_ORIGINS).
57+
// Useful for preview/staging homepage deployments.
58+
CORSAllowedOrigins []string
5459
Port string
5560

5661
// ECR (Docker images)
@@ -101,6 +106,8 @@ func Load() (*Config, error) {
101106
SMTPPassword: getEnv("SMTP_PASSWORD", ""),
102107
BaseURL: getEnv("BASE_URL", "https://registry.devsper.com"),
103108
FrontendURL: getEnv("FRONTEND_URL", ""), // if empty, redirects use BaseURL
109+
HomepageURL: getEnv("HOMEPAGE_URL", "https://devsper.com"), // default homepage
110+
CORSAllowedOrigins: parseCSV(getEnv("CORS_ALLOWED_ORIGINS", "")),
104111
Port: getEnv("PORT", "8080"),
105112
ECRRegistry: getEnv("ECR_REGISTRY", ""),
106113
AdminSecret: getEnv("ADMIN_SECRET", ""),
@@ -182,3 +189,19 @@ func parseDuration(s string, def time.Duration) time.Duration {
182189
}
183190
return d
184191
}
192+
193+
func parseCSV(s string) []string {
194+
if s == "" {
195+
return nil
196+
}
197+
parts := strings.Split(s, ",")
198+
out := make([]string, 0, len(parts))
199+
for _, p := range parts {
200+
v := strings.TrimSpace(p)
201+
if v == "" {
202+
continue
203+
}
204+
out = append(out, v)
205+
}
206+
return out
207+
}

api/internal/config/config_test.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ func clearConfigEnv(t *testing.T) {
128128
"GOOGLE_CLIENT_ID", "GOOGLE_CLIENT_SECRET",
129129
"SES_REGION", "SES_FROM_ADDRESS", "SES_REPLY_TO",
130130
"SMTP_HOST", "SMTP_PORT", "SMTP_FROM", "SMTP_USER", "SMTP_PASSWORD",
131-
"BASE_URL", "FRONTEND_URL", "PORT",
131+
"BASE_URL", "FRONTEND_URL", "HOMEPAGE_URL", "CORS_ALLOWED_ORIGINS", "PORT",
132132
"ECR_REGISTRY", "ADMIN_SECRET", "INTERNAL_SECRET",
133133
"RATE_LIMIT_RPS", "MAX_UPLOAD_SIZE_MB", "VERIFICATION_WORKERS",
134134
"ENV",
@@ -188,6 +188,8 @@ func TestLoad_CustomEnv(t *testing.T) {
188188
t.Setenv("JWT_SECRET", "custom-secret")
189189
t.Setenv("PORT", "9090")
190190
t.Setenv("BASE_URL", "https://custom.example.com")
191+
t.Setenv("HOMEPAGE_URL", "https://home.example.com")
192+
t.Setenv("CORS_ALLOWED_ORIGINS", "https://preview-1.example.com, https://preview-2.example.com")
191193
t.Setenv("MAX_UPLOAD_SIZE_MB", "500")
192194
t.Setenv("RATE_LIMIT_RPS", "50")
193195
t.Setenv("ENV", "production")
@@ -214,6 +216,18 @@ func TestLoad_CustomEnv(t *testing.T) {
214216
if cfg.BaseURL != "https://custom.example.com" {
215217
t.Errorf("BaseURL = %q, want custom value", cfg.BaseURL)
216218
}
219+
if cfg.HomepageURL != "https://home.example.com" {
220+
t.Errorf("HomepageURL = %q, want custom value", cfg.HomepageURL)
221+
}
222+
if len(cfg.CORSAllowedOrigins) != 2 {
223+
t.Fatalf("CORSAllowedOrigins len = %d, want 2", len(cfg.CORSAllowedOrigins))
224+
}
225+
if cfg.CORSAllowedOrigins[0] != "https://preview-1.example.com" {
226+
t.Errorf("CORSAllowedOrigins[0] = %q, want first preview origin", cfg.CORSAllowedOrigins[0])
227+
}
228+
if cfg.CORSAllowedOrigins[1] != "https://preview-2.example.com" {
229+
t.Errorf("CORSAllowedOrigins[1] = %q, want second preview origin", cfg.CORSAllowedOrigins[1])
230+
}
217231
if cfg.MaxUploadSizeMB != 500 {
218232
t.Errorf("MaxUploadSizeMB = %d, want 500", cfg.MaxUploadSizeMB)
219233
}

docker-compose.prod.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ services:
2222
environment:
2323
- DATABASE_URL=postgres://${POSTGRES_USER:-registry}:${POSTGRES_PASSWORD:-password}@postgres:5432/${POSTGRES_DB:-registry}?sslmode=disable
2424
- BASE_URL=${BASE_URL:-https://registry.devsper.com}
25+
- HOMEPAGE_URL=${HOMEPAGE_URL:-https://devsper.com}
26+
- CORS_ALLOWED_ORIGINS=${CORS_ALLOWED_ORIGINS:-}
2527
- JWT_SECRET=${JWT_SECRET}
2628
- INTERNAL_SECRET=${INTERNAL_SECRET}
2729
- PORT=8080

0 commit comments

Comments
 (0)