package routes import ( "github.com/creativenoz/aurganize-v62/backend/internal/handlers" "github.com/creativenoz/aurganize-v62/backend/internal/middleware" "github.com/labstack/echo/v4" ) // SetUpRoutes configures all HTTP routes for the application. // This is the central routing configuration where all API endpoints are defined. // // What are routes? // Routes map HTTP methods and URL paths to handler functions. // Example: POST /api/v1/auth/login → authHandler.Login // // Route organization: // This application uses route groups to organize endpoints by: // 1. API versioning (/api/v1) // 2. Feature area (/auth, /users, /projects, etc.) // 3. Authentication requirement (public vs protected) // // Why use route groups? // - Organization: Related routes grouped together // - Shared middleware: Apply middleware to entire group // - URL prefixing: Avoid repeating base paths // - Versioning: Easy to add /api/v2 later // // Architecture pattern: Dependency Injection // - Handlers and middleware are passed in (not created here) // - Makes testing easier (can inject mocks) // - Makes dependencies explicit // - Follows SOLID principles // // Current route structure: // /api/v1 // /auth (public - no authentication required) // POST /login - User authentication // POST /refresh - Token rotation // POST /logout - User logout // /{protected routes} (require authentication) // GET /health - Health check with user info // // Future expansion: // Add more route groups for different features: // - /users (user management) // - /projects (project operations) // - /tasks (task management) // - /admin (admin-only endpoints) // // Parameters: // - e: Echo instance (the web framework) // - authHandler: Handler for authentication endpoints // - authMiddleware: Middleware for protecting routes // // Usage: // e := echo.New() // authHandler := handlers.NewAuthHandler(...) // authMiddleware := middleware.NewAuthMiddleware(...) // routes.SetUpRoutes(e, authHandler, authMiddleware) // e.Start(":8080") func SetUpRoutes( e *echo.Echo, authHandler *handlers.AuthHandler, userHandler *handlers.UserRegisterHander, tenantHandler *handlers.TenantHandler, authMiddleware *middleware.AuthMiddleware, globalRateLimiterMiddleware *middleware.RateLimiter, ) { // Create API version 1 group // All routes will be prefixed with /api/v1 // This enables API versioning for backward compatibility // // Why version your API? // - Breaking changes: Can release v2 while maintaining v1 // - Client compatibility: Old clients continue working // - Gradual migration: Clients upgrade at their own pace // - Clear communication: Version tells clients what to expect // // Example URLs: // - /api/v1/auth/login // - /api/v1/health // Future: /api/v2/auth/login (with different behavior) api := e.Group("/api/v1") // ============================================================================ // AUTHENTICATION ROUTES (Public - No Authentication Required) // ============================================================================ // // These routes handle user authentication and token management. // They are PUBLIC because users need to authenticate BEFORE having tokens. // // Security note: While these routes don't require authentication, // they should still be protected by: // - Rate limiting (prevent brute force attacks) // - HTTPS only (protect credentials in transit) // - CORS restrictions (only allowed origins) // // Base path: /api/v1/auth auth := api.Group("/auth") auth.POST("/register", userHandler.Register, globalRateLimiterMiddleware.Limit) // POST /api/v1/auth/login // Authenticates user credentials and issues tokens // // What it does: // 1. Validates email and password // 2. Checks if user is active // 3. Generates access token (15 min lifetime) // 4. Generates refresh token (7 day lifetime) // 5. Creates session in database // 6. Sets HttpOnly cookies // 7. Returns user data and tokens // // Request: // POST /api/v1/auth/login // Content-Type: application/json // Body: { // "email": "user@example.com", // "password": "SecurePassword123!" // } // // Response (Success - 200): // Set-Cookie: access_token=...; HttpOnly; Secure; SameSite=Lax // Set-Cookie: refresh_token=...; HttpOnly; Secure; SameSite=Lax // Body: { // "user": { "id": "...", "email": "...", "role": "..." }, // "access_token": "eyJhbGci...", // "refresh_token": "eyJhbGci...", // "expires_in": 900 // } // // Response (Error - 401): // Body: { "message": "invalid credentials" } // // Security features: // - Password never returned in response // - Generic error messages (prevents email enumeration) // - HttpOnly cookies (XSS protection) // - Session tracking (device, IP, user agent) auth.POST("/login", authHandler.Login, globalRateLimiterMiddleware.Limit) // POST /api/v1/auth/refresh // Rotates refresh token and issues new access token // // What it does: // 1. Validates old refresh token (from cookie or body) // 2. Generates new access token // 3. Generates new refresh token (rotation) // 4. Revokes old refresh token (invalidates it) // 5. Creates new session // 6. Sets new cookies // 7. Returns new tokens // // Why token rotation? // - Limits stolen token exposure window // - Enables theft detection (reused old token = possible attack) // - Each token is single-use after rotation // - Industry security best practice // // Request (Option 1 - Cookie): // POST /api/v1/auth/refresh // Cookie: refresh_token=eyJhbGci... // // Request (Option 2 - Body): // POST /api/v1/auth/refresh // Content-Type: application/json // Body: { "refresh_token": "eyJhbGci..." } // // Response (Success - 200): // Set-Cookie: access_token=...; (new token) // Set-Cookie: refresh_token=...; (NEW token, different from request!) // Body: { // "access_token": "eyJhbGci...", // "refresh_token": "eyJhbGci...", // Client MUST store this new token // "expires_in": 900 // } // // Response (Error - 401): // - "missing refresh token" (no token provided) // - "refresh token expired" (needs re-login) // - "refresh token revoked" (session invalidated) // - "invalid refresh token" (signature invalid or malformed) // // IMPORTANT: Client must store the new refresh token and discard the old one! // The old refresh token is immediately invalidated after successful rotation. auth.POST("/refresh", authHandler.RefreshTokenWithRotation, globalRateLimiterMiddleware.Limit) // POST /api/v1/auth/logout // Revokes refresh token and clears authentication cookies // // What it does: // 1. Extracts refresh token from cookie // 2. Revokes session in database (marks as revoked) // 3. Clears access_token cookie // 4. Clears refresh_token cookie // 5. Returns success // // Why logout is important: // - Security: Prevents refresh token from being used again // - Privacy: Removes tokens from browser // - Session management: Marks session as ended // - User control: User can explicitly end session // // Request: // POST /api/v1/auth/logout // Cookie: refresh_token=eyJhbGci... // // Response (Success - 200): // Set-Cookie: access_token=; MaxAge=-1 (deleted) // Set-Cookie: refresh_token=; MaxAge=-1 (deleted) // Body: (no content) // // Note: Always returns success even if token is invalid or missing // This prevents information leakage about token validity // // What happens to current access token? // - Access token still works until it expires (~15 min) // - This is acceptable because: // 1. Access tokens are short-lived // 2. Checking database on every request would be slow // 3. User is effectively logged out (can't get new access tokens) auth.POST("/logout", authHandler.Logout, globalRateLimiterMiddleware.Limit) // ============================================================================ // PROTECTED ROUTES (Authentication Required) // ============================================================================ // // These routes require valid authentication via access token. // Requests without valid tokens receive 401 Unauthorized response. // // Authentication methods supported: // 1. Cookie: access_token=... (automatic for browsers) // 2. Header: Authorization: Bearer (for mobile/API clients) // // How authentication works: // 1. authMiddleware.Authenticate extracts token // 2. Validates token signature and expiration // 3. Stores user claims in context (user_id, email, role, etc.) // 4. Proceeds to handler if valid // 5. Returns 401 if invalid/expired // // What handlers can access: // userID := c.Get("user_id").(uuid.UUID) // email := c.Get("email").(string) // role := c.Get("role").(string) // tenantID := c.Get("tenant_id").(uuid.UUID) // claims := c.Get("claims").(*auth.AccessTokenClaims) // // Base path: /api/v1 // All routes in this group require authentication protected := api.Group("") // Apply authentication middleware to all routes in this group // This means every route added to 'protected' will: // 1. Check for access token // 2. Validate token // 3. Block request if invalid // 4. Store user info in context if valid protected.Use(authMiddleware.Authenticate) // GET /api/v1/health // Health check endpoint that returns server status and user info // // What it does: // 1. Checks that server is running // 2. Verifies authentication middleware works // 3. Returns authenticated user's email // // Why a protected health check? // - Verifies entire auth pipeline works // - Tests token validation // - Confirms middleware is properly applied // - Useful for monitoring authenticated endpoints // // Request: // GET /api/v1/health // Cookie: access_token=eyJhbGci... // OR // Authorization: Bearer eyJhbGci... // // Response (Success - 200): // Body: { // "status": "ok", // "user": "user@example.com" // Email of authenticated user // } // // Response (Error - 401): // Body: { "message": "missing authentication token" } // OR: { "message": "token has expired" } // OR: { "message": "invalid token" } // // Note: This is a simple inline handler for demonstration // Production endpoints should use dedicated handler functions // // Type assertion: c.Get("email").(string) // - c.Get() returns interface{} (any type) // - .(string) asserts that value is a string // - Safe because middleware guarantees email is set as string // - Will panic if middleware didn't set email (which shouldn't happen) protected.GET("/health", func(c echo.Context) error { return c.JSON(200, map[string]interface{}{ "status": "ok", "user": c.Get("email").(string), }) }) // ============================================================================ // FUTURE ROUTE GROUPS // ============================================================================ // // As the application grows, add more route groups here: // // // User management routes (protected) // users := protected.Group("/users") // users.GET("", userHandler.List) // GET /api/v1/users // users.GET("/:id", userHandler.GetByID) // GET /api/v1/users/:id // users.PUT("/:id", userHandler.Update) // PUT /api/v1/users/:id // users.DELETE("/:id", userHandler.Delete) // DELETE /api/v1/users/:id // // // Project routes (protected) // projects := protected.Group("/projects") // projects.GET("", projectHandler.List) // GET /api/v1/projects // projects.POST("", projectHandler.Create) // POST /api/v1/projects // projects.GET("/:id", projectHandler.GetByID) // GET /api/v1/projects/:id // // // Admin routes (protected + role check) // admin := protected.Group("/admin") // admin.Use(middleware.RequireRole("admin")) // Extra middleware for role check // admin.GET("/users", adminHandler.ListAllUsers) // admin.POST("/users/:id/suspend", adminHandler.SuspendUser) // // // Public routes (optional auth - shows different content if logged in) // public := api.Group("") // public.Use(authMiddleware.OptionalAuth) // public.GET("/posts", postHandler.List) // Shows public + user's private posts if authenticated // // // Webhook routes (API key auth instead of JWT) // webhooks := api.Group("/webhooks") // webhooks.Use(webhookMiddleware.ValidateSignature) // webhooks.POST("/github", webhookHandler.HandleGitHub) tenants := protected.Group("/tenants") tenants.GET("/mine", tenantHandler.GetMyTenant) tenants.GET("/:id", tenantHandler.GetTenant) }