Files
akiyama-manga/BACKEND_CODE_REFERENCE.md

10 KiB

Backend Authentication Handlers - Code Reference

This file shows the exact backend code that was added to support the authentication dropdown menu.

File: internal/handlers/handlers.go

GetMe Handler (Lines 77-110)

// GetMe returns the current authenticated user
func GetMe(db *gorm.DB) gin.HandlerFunc {
	return func(c *gin.Context) {
		// Get user ID from JWT claims (set by middleware)
		userIDInterface, exists := c.Get("user_id")
		if !exists {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "user not found in context"})
			return
		}

		userID, ok := userIDInterface.(string)
		if !ok {
			c.JSON(http.StatusInternalServerError, gin.H{"error": "invalid user ID format"})
			return
		}

		// Fetch user from database
		var user models.User
		if err := db.First(&user, "id = ?", userID).Error; err != nil {
			if err == gorm.ErrRecordNotFound {
				c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
			} else {
				c.JSON(http.StatusInternalServerError, gin.H{"error": "database error"})
			}
			return
		}

		// Return user info (excluding password)
		c.JSON(http.StatusOK, gin.H{
			"id":    user.ID,
			"email": user.Email,
			"name":  user.Username, // Return username as name for frontend
		})
	}
}

Logout Handler (Lines 112-119)

// Logout handles user logout
func Logout(c *gin.Context) {
	// Clear the JWT cookie
	c.SetCookie("auth_token", "", -1, "/", "", false, true)
	
	c.JSON(http.StatusOK, gin.H{
		"message": "successfully logged out",
	})
}

File: internal/middleware/middleware.go

Updated AuthMiddleware (Lines 28-58)

func AuthMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		var tokenString string

		// Try to get token from Authorization header first
		authHeader := c.GetHeader("Authorization")
		if authHeader != "" {
			tokenString = strings.TrimPrefix(authHeader, "Bearer ")
		} else {
			// Try to get token from cookie
			cookie, err := c.Cookie("auth_token")
			if err != nil {
				c.JSON(401, gin.H{"error": "Authorization required"})
				c.Abort()
				return
			}
			tokenString = cookie
		}

		if tokenString == "" {
			c.JSON(401, gin.H{"error": "No token provided"})
			c.Abort()
			return
		}

		claims, err := auth.VerifyToken(tokenString)
		if err != nil {
			c.JSON(401, gin.H{"error": "Invalid token"})
			c.Abort()
			return
		}

		c.Set("user_id", claims.UserID)
		c.Set("email", claims.Email)
		c.Set("is_admin", claims.IsAdmin)
		c.Next()
	}
}

File: cmd/server/main.go

Updated Route Registration (Lines 117-122)

	// Auth routes
	auth := router.Group("/api/auth")
	{
		auth.POST("/signup", handlers.SignUp(db))
		auth.POST("/signin", handlers.SignIn(db))
		auth.GET("/me", middleware.AuthMiddleware(), handlers.GetMe(db))      // NEW
		auth.POST("/logout", middleware.AuthMiddleware(), handlers.Logout)    // NEW
	}

Frontend Authentication JavaScript

This JavaScript was added to all 4 templates (index.html, browse.html, library.html, reader.html).

Authentication Check Function

async function checkAuth() {
    try {
        const response = await fetch('/api/auth/me', {
            method: 'GET',
            credentials: 'include'  // Send cookies with request
        });

        if (response.ok) {
            const user = await response.json();
            showUserMenu(user);  // User is authenticated
        } else {
            showLoginButtons();  // User not authenticated
        }
    } catch (error) {
        console.error('Auth check failed:', error);
        showLoginButtons();  // Default to login buttons on error
    }
}

Show User Menu Function

function showUserMenu(user) {
    authButtons.style.display = 'none';
    userProfileBtn.style.display = 'flex';
    
    // Set user initial (first letter of name or email)
    const initial = (user.name || user.email)[0].toUpperCase();
    document.getElementById('userInitial').textContent = initial;
    
    // Set user info in dropdown header
    document.getElementById('dropdownUserName').textContent = user.name || 'User';
    document.getElementById('dropdownUserEmail').textContent = user.email;
}

Show Login Buttons Function

function showLoginButtons() {
    authButtons.style.display = 'flex';
    userProfileBtn.style.display = 'none';
}

Dropdown Toggle Logic

const userProfileBtn = document.getElementById('userProfileBtn');
const dropdownMenu = document.getElementById('dropdownMenu');

// Toggle dropdown on button click
userProfileBtn.addEventListener('click', (e) => {
    e.stopPropagation();
    dropdownMenu.classList.toggle('active');
});

// Close dropdown when clicking outside
document.addEventListener('click', (e) => {
    if (!e.target.closest('.auth-container')) {
        dropdownMenu.classList.remove('active');
    }
});

Logout Handler

const logoutBtn = document.getElementById('logoutBtn');

logoutBtn.addEventListener('click', async (e) => {
    e.preventDefault();
    
    try {
        const response = await fetch('/api/auth/logout', {
            method: 'POST',
            credentials: 'include'  // Send cookies with request
        });
        
        if (response.ok) {
            showLoginButtons();  // Clear user menu
            // Optional: redirect to home
            // window.location.href = '/';
        } else {
            console.error('Logout failed:', response.statusText);
        }
    } catch (error) {
        console.error('Logout error:', error);
    }
});

Initialization on Page Load

// Run auth check when page loads
document.addEventListener('DOMContentLoaded', checkAuth);

Frontend Dropdown Menu CSS

Added to all 4 templates (approximately 95 lines).

Styles

/* Auth container for relative positioning */
.auth-container {
    position: relative;
    display: flex;
    align-items: center;
    gap: 1rem;
}

/* User profile button (circular avatar) */
.user-profile-btn {
    display: none;  /* Hidden by default, shown when logged in */
    width: 2.5rem;
    height: 2.5rem;
    border-radius: 50%;
    background: var(--primary, #3b82f6);
    color: white;
    border: none;
    cursor: pointer;
    font-weight: bold;
    font-size: 1.1rem;
    align-items: center;
    justify-content: center;
    transition: background 0.2s;
}

.user-profile-btn:hover {
    background: var(--primary-dark, #2563eb);
}

/* Dropdown menu container */
.dropdown-menu {
    position: absolute;
    top: 100%;
    right: 0;
    min-width: 200px;
    margin-top: 0.5rem;
    background: var(--card, #1a1a1a);
    border-radius: 0.5rem;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
    display: none;
    flex-direction: column;
    z-index: 1000;
}

.dropdown-menu.active {
    display: flex;
}

/* User info section in dropdown header */
.dropdown-header {
    padding: 1rem;
    border-bottom: 1px solid rgba(255, 255, 255, 0.1);
    font-size: 0.9rem;
}

.dropdown-user-name {
    font-weight: 600;
    color: white;
    margin-bottom: 0.25rem;
}

.dropdown-user-email {
    font-size: 0.85rem;
    color: rgba(255, 255, 255, 0.7);
}

/* Menu items container */
.dropdown-items {
    display: flex;
    flex-direction: column;
    padding: 0.5rem;
}

/* Individual menu item */
.dropdown-item {
    padding: 0.75rem 1rem;
    color: white;
    text-decoration: none;
    cursor: pointer;
    background: none;
    border: none;
    text-align: left;
    transition: background 0.2s;
    font-size: 0.95rem;
    width: 100%;
}

.dropdown-item:hover {
    background: rgba(255, 255, 255, 0.1);
    border-radius: 0.25rem;
}

/* Divider between menu sections */
.dropdown-divider {
    height: 1px;
    background: rgba(255, 255, 255, 0.1);
    margin: 0.5rem 0;
}

/* Danger style for logout button */
.dropdown-item.danger {
    color: #ef4444;
}

.dropdown-item.danger:hover {
    background: rgba(239, 68, 68, 0.2);
}

How to Use This Code

1. GetMe Handler

Use this to allow frontend templates to check if a user is logged in and retrieve their information.

Request:

GET /api/auth/me
Cookie: auth_token=<jwt_token>

Success Response (200):

{
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "email": "user@example.com",
    "name": "johndoe"
}

Error Response (401):

{"error": "Invalid token"}

2. Logout Handler

Use this to clear the user's session and log them out.

Request:

POST /api/auth/logout
Cookie: auth_token=<jwt_token>

Success Response (200):

{"message": "successfully logged out"}

3. AuthMiddleware

Use this middleware on any protected routes to automatically validate JWT tokens from cookies or headers.

protected := router.Group("/api")
protected.Use(middleware.AuthMiddleware())  // Validates JWT automatically
{
    protected.GET("/library", handlers.GetUserLibrary(db))
    protected.POST("/library/:id", handlers.AddToLibrary(db))
}

4. Frontend JavaScript

Add the JavaScript functions to your HTML templates and call checkAuth() on page load.

The functions handle:

  • Checking if user is authenticated
  • Displaying appropriate UI (login buttons or user menu)
  • Toggling dropdown menu
  • Handling logout

Integration Points

1. Database User Model

Must have these fields:

  • ID uuid.UUID - User ID
  • Email string - User email address
  • Username string - Username (displayed as name in dropdown)
  • Password string - Hashed password (not returned by API)

2. JWT Token

Must contain these claims:

  • user_id - User ID
  • email - User email
  • is_admin - Admin flag

JWT is stored in:

  • Name: auth_token
  • HttpOnly: true (security)
  • Path: /
  • Expiration: 24 hours

Testing Commands

# Check if user is authenticated
curl -b "auth_token=<your_jwt_token>" http://localhost:8080/api/auth/me

# Logout user
curl -X POST -b "auth_token=<your_jwt_token>" http://localhost:8080/api/auth/logout

# Check without authentication (should fail)
curl http://localhost:8080/api/auth/me

Notes:

  • All 4 templates have identical dropdown menu CSS and JavaScript
  • The middleware supports both Authorization headers and cookies
  • The frontend uses credentials: 'include' to send cookies automatically
  • Logout endpoint clears the auth_token cookie by setting expiration to -1