registar / main.go
wilooper's picture
V3
7c4758a
Raw History Blame Contribute Delete
4.11 kB
package main
import (
"log"
"net/http"
"os"
"strings"
"time"
"collabos-auth/internal/db"
"collabos-auth/internal/handlers"
"github.com/gin-gonic/gin"
"github.com/joho/godotenv"
)
func main() {
_ = godotenv.Load()
if err := db.InitMongo(); err != nil {
log.Fatalf("[FATAL] MongoDB: %v", err)
}
log.Println("[OK] MongoDB connected")
if err := db.InitRedis(); err != nil {
log.Printf("[WARN] Redis unavailable (continuing without cache): %v", err)
} else if db.Redis != nil {
log.Println("[OK] Upstash Redis connected")
}
gin.SetMode(gin.ReleaseMode)
r := gin.New()
r.Use(gin.Recovery())
r.Use(corsMiddleware())
// ── Liveness ──────────────────────────────────────────────────────────────
r.GET("/health", func(c *gin.Context) {
c.JSON(200, gin.H{"status": "ok", "service": "collabos-auth", "version": "3.0.0"})
})
// ── Auth pipe (WS) ────────────────────────────────────────────────────────
// Single endpoint for ALL login and signup across all providers.
// Requires: api_key + nonce in payload. No credentials in URL or headers.
r.GET("/ws/auth", handlers.HandleAuthPipe)
// ── Data pipe (WS) ────────────────────────────────────────────────────────
// Requires: Authorization: Bearer <jwt> AND X-API-Token header.
// Both verified BEFORE the WS upgrade β€” no upgrade on bad creds.
r.GET("/ws/data", handlers.HandleDataPipe)
// ── OTP helper (thin REST proxy to OTP server) ────────────────────────────
// No auth required β€” just proxies email to OTP server. Rate limiting is
// on the OTP server side. No credentials stored here.
r.POST("/otp/send", handlers.SendOTPProxy)
// ── Profile update ────────────────────────────────────────────────────────
// Requires: Authorization: Bearer <jwt> AND X-Update-Key: <UPDATE_API_KEY>
// AND body.uid must match jwt.uid. Three independent checks.
r.POST("/update", handlers.HandleUpdate)
port := os.Getenv("PORT")
if port == "" {
port = "7860"
}
log.Printf("[START] CollabOS Auth v3.0.0 on :%s", port)
if err := r.Run(":" + port); err != nil {
log.Fatalf("[FATAL] %v", err)
}
}
// corsMiddleware builds the CORS handler from ALLOWED_ORIGINS env.
// Default is * for development. Set to comma-separated URLs for production.
func corsMiddleware() gin.HandlerFunc {
originsRaw := os.Getenv("ALLOWED_ORIGINS")
return func(c *gin.Context) {
origin := c.Request.Header.Get("Origin")
allowed := false
if originsRaw == "" || originsRaw == "*" {
allowed = true
c.Header("Access-Control-Allow-Origin", "*")
} else {
for _, o := range strings.Split(originsRaw, ",") {
if strings.TrimSpace(o) == origin {
allowed = true
c.Header("Access-Control-Allow-Origin", origin)
c.Header("Vary", "Origin")
break
}
}
}
if !allowed {
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "origin not allowed"})
return
}
c.Header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
c.Header("Access-Control-Allow-Headers",
"Content-Type, Authorization, X-API-Token, X-Update-Key")
c.Header("Access-Control-Max-Age", "86400")
if c.Request.Method == http.MethodOptions {
c.AbortWithStatus(http.StatusNoContent)
return
}
// Add security headers on every response
c.Header("X-Content-Type-Options", "nosniff")
c.Header("X-Frame-Options", "DENY")
c.Header("Referrer-Policy", "strict-origin-when-cross-origin")
c.Header("Cache-Control", "no-store")
// Timestamp every response for debugging
c.Header("X-Response-Time", time.Now().UTC().Format(time.RFC3339))
c.Next()
}
}