339 lines
12 KiB
Go
339 lines
12 KiB
Go
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,
|
|
authMiddleware *middleware.AuthMiddleware,
|
|
) {
|
|
// 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")
|
|
|
|
// 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)
|
|
|
|
// 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)
|
|
|
|
// 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)
|
|
|
|
// ============================================================================
|
|
// 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 <token> (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)
|
|
}
|