Files
backend/handlers.go
T
NanamiAdmin e3b3a3aa98 feat(logging): add detailed logging throughout application components
- Implement logging in router setup, auth handlers, and frpc operations
- Add SoftwareInfo struct for version tracking and logging
- Enhance error messages with more context and logging
- Replace direct error returns with formatted error logging
- Add debug logs for token operations and request validations
2026-02-27 23:44:41 +08:00

356 lines
11 KiB
Go

package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"strconv"
"super-frpc/postLog"
"time"
)
type RegisterRequest struct {
Username string `json:"username"`
Passwd string `json:"passwd"`
TimeStamp int64 `json:"timeStamp"`
Type string `json:"type"`
}
type LoginRequest struct {
Username string `json:"username"`
Passwd string `json:"passwd"`
TimeStamp int64 `json:"timeStamp"`
}
type Response struct {
Success bool `json:"success"`
Message string `json:"message,omitempty"`
Data interface{} `json:"data,omitempty"`
}
func RegisterHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
SendErrorResponse(w, http.StatusMethodNotAllowed, "Invalid request method")
postLog.Warning(fmt.Sprintf("[RegisterHandler] Invalid request method: %s", r.Method))
return
}
body, err := io.ReadAll(r.Body)
if err != nil {
SendErrorResponse(w, http.StatusBadRequest, "Failed to read request body")
postLog.Warning(fmt.Sprintf("[RegisterHandler] Failed to read request body: %v", err))
return
}
defer r.Body.Close()
var req RegisterRequest
if err := json.Unmarshal(body, &req); err != nil {
SendErrorResponse(w, http.StatusBadRequest, "Invalid request format")
postLog.Warning(fmt.Sprintf("[RegisterHandler] Invalid request format: %v", err))
return
}
if req.Username == "" || req.Passwd == "" {
SendErrorResponse(w, http.StatusBadRequest, "Username and password are required")
postLog.Warning("[RegisterHandler] New user registration failed: username or password is empty")
return
}
if err := ValidateTimeStamp(req.TimeStamp); err != nil {
SendErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
if !isValidInput(req.Username) || !isValidInput(req.Passwd) {
SendErrorResponse(w, http.StatusBadRequest, "Invalid input: contains illegal characters")
postLog.Debug(fmt.Sprintf("[RegisterHandler] New user registration failed: username or password contains illegal characters \"%s\":\"%s\"", req.Username, req.Passwd))
return
}
if !isValidPassword(req.Passwd) {
SendErrorResponse(w, http.StatusBadRequest, "Password does not meet complexity requirements (must contain uppercase, lowercase, digit, and special character)")
postLog.Debug(fmt.Sprintf("[RegisterHandler] New user registration failed: password \"%s\" does not meet complexity requirements", req.Passwd))
return
}
userType := req.Type
if userType == "" {
userType = "visitor"
}
validTypes := map[string]bool{
"superuser": true,
"admin": true,
"visitor": true,
}
if !validTypes[userType] {
SendErrorResponse(w, http.StatusBadRequest, "Invalid user type")
postLog.Warning(fmt.Sprintf("[RegisterHandler] New user registration failed: invalid user type \"%s\"", userType))
return
}
userID, err := AddUser(req.Username, req.Passwd, userType)
if err != nil {
SendErrorResponse(w, http.StatusInternalServerError, err.Error())
postLog.Error(fmt.Sprintf("[RegisterHandler] Failed to register user \"%s\": %v", req.Username, err))
return
}
user, err := GetUserByID(userID)
if err != nil {
SendErrorResponse(w, http.StatusInternalServerError, "Failed to retrieve user after registration")
postLog.Error(fmt.Sprintf("[RegisterHandler] Failed to retrieve user \"%s\" after registration: %v", req.Username, err))
return
}
SendSuccessResponse(w, "user registered successfully", map[string]interface{}{
"userID": user.UserID,
"username": user.Username,
"type": user.Type,
})
}
func LoginHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
SendErrorResponse(w, http.StatusMethodNotAllowed, "Invalid request method")
postLog.Warning(fmt.Sprintf("[LoginHandler] Invalid request method: %s", r.Method))
return
}
body, err := io.ReadAll(r.Body)
if err != nil {
SendErrorResponse(w, http.StatusBadRequest, "Failed to read request body")
postLog.Warning(fmt.Sprintf("[LoginHandler] Failed to read request body: %v", err))
return
}
defer r.Body.Close()
var req LoginRequest
if err := json.Unmarshal(body, &req); err != nil {
SendErrorResponse(w, http.StatusBadRequest, "Invalid request format")
postLog.Warning(fmt.Sprintf("[LoginHandler] Invalid request format: %v", err))
return
}
if req.Username == "" || req.Passwd == "" {
SendErrorResponse(w, http.StatusBadRequest, "Username and password are required")
postLog.Warning("[LoginHandler] Login failed: username or password is empty")
return
}
if err := ValidateTimeStamp(req.TimeStamp); err != nil {
SendErrorResponse(w, http.StatusBadRequest, err.Error())
postLog.Warning(fmt.Sprintf("[LoginHandler] User \"%s\" Login failed: invalid timestamp \"%d\"", req.Username, req.TimeStamp))
return
}
if !isValidInput(req.Username) || !isValidInput(req.Passwd) {
SendErrorResponse(w, http.StatusBadRequest, "Invalid input: contains illegal characters")
postLog.Debug(fmt.Sprintf("[LoginHandler] Login failed: username or password contains illegal characters \"%s\":\"%s\"", req.Username, req.Passwd))
return
}
user, err := GetUserByUsername(req.Username)
if err != nil {
SendErrorResponse(w, http.StatusUnauthorized, "Invalid username")
postLog.Warning(fmt.Sprintf("[LoginHandler] Login failed: invalid username \"%s\"", req.Username))
return
}
if !verifyPassword(req.Passwd, user.Passwd) {
SendErrorResponse(w, http.StatusUnauthorized, "Invalid password")
postLog.Warning(fmt.Sprintf("[LoginHandler] Login failed: invalid password for user \"%s\"", req.Username))
return
}
existingTokenInfo, err := GetTokenInfo(user.UserID)
if err == nil && existingTokenInfo != nil {
SendErrorResponse(w, http.StatusConflict, "User is already logged in")
postLog.Warning(fmt.Sprintf("[LoginHandler] Login failed: user \"%s\" is already logged in", req.Username))
return
}
token, err := GenerateToken(user.UserID)
if err != nil {
SendErrorResponse(w, http.StatusInternalServerError, "Failed to generate token")
postLog.Error(fmt.Sprintf("[LoginHandler] Failed to generate token for user \"%s\": %v", req.Username, err))
return
}
SendSuccessResponse(w, "Login successful", map[string]interface{}{
"token": token,
"userID": user.UserID,
"username": user.Username,
"type": user.Type,
})
postLog.Info(fmt.Sprintf("[LoginHandler] User \"%s\" Login successful", req.Username))
}
func SendErrorResponse(w http.ResponseWriter, statusCode int, message string) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(statusCode)
resp := Response{
Success: false,
Message: message,
}
jsonResp, err := json.Marshal(resp)
if err != nil {
postLog.Error(fmt.Sprintf("Failed to marshal error response: %v", err))
return
}
w.Write(jsonResp)
}
func SendSuccessResponse(w http.ResponseWriter, message string, data interface{}) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
resp := Response{
Success: true,
Message: message,
Data: data,
}
jsonResp, err := json.Marshal(resp)
if err != nil {
postLog.Error(fmt.Sprintf("Failed to marshal success response: %v", err))
return
}
w.Write(jsonResp)
}
func ValidateRequest(w http.ResponseWriter, r *http.Request, requiredFields ...string) (int, string, error) {
body, err := io.ReadAll(r.Body)
if err != nil {
return 0, "", fmt.Errorf("Failed to read request body: %w", err)
}
defer r.Body.Close()
return ValidateRequestWithBody(w, r, body, requiredFields...)
}
func ValidateRequestWithBody(w http.ResponseWriter, r *http.Request, body []byte, requiredFields ...string) (int, string, error) {
var reqMap map[string]interface{}
if err := json.Unmarshal(body, &reqMap); err != nil {
return 0, "", fmt.Errorf("Invalid request format: %w", err)
}
token, ok := reqMap["token"].(string)
if !ok || token == "" {
return 0, "", fmt.Errorf("Token is required: %s", token)
}
timeStamp := int64(0)
if ts, ok := reqMap["timeStamp"].(float64); ok {
timeStamp = int64(ts)
} else if !globalConfig.Debug {
return 0, "", fmt.Errorf("Timestamp is required: %d", timeStamp)
}
if err := ValidateTimeStamp(timeStamp); err != nil {
return 0, "", fmt.Errorf("Invalid timestamp: %w", err)
}
userID, err := extractUserIDFromToken(token)
if err != nil {
return 0, "", fmt.Errorf("Invalid token format: %w", err)
}
if err := ValidateToken(userID, token); err != nil {
return 0, "", fmt.Errorf("Token validation failed: %w", err)
}
for _, field := range requiredFields {
if _, ok := reqMap[field]; !ok {
return 0, "", fmt.Errorf("required field %s is missing: %s", field, reqMap[field])
}
}
return userID, token, nil
}
func ValidateRequestWithHeader(w http.ResponseWriter, r *http.Request, requiredFields ...string) (int, string, error) {
token := r.Header.Get("X-Token")
if token == "" {
return 0, "", fmt.Errorf("Token is required in header: %s", token)
}
timeStampStr := r.Header.Get("X-Timestamp")
timeStamp := int64(0)
if timeStampStr != "" {
var err error
timeStamp, err = strconv.ParseInt(timeStampStr, 10, 64)
if err != nil {
return 0, "", fmt.Errorf("Invalid timestamp format in header: %w", err)
}
} else if !globalConfig.Debug {
return 0, "", fmt.Errorf("Timestamp is required in header: %s", timeStampStr)
}
if err := ValidateTimeStamp(timeStamp); err != nil {
return 0, "", err
}
userID, err := extractUserIDFromToken(token)
if err != nil {
return 0, "", fmt.Errorf("Invalid token format in header: %w", err)
}
if err := ValidateToken(userID, token); err != nil {
return 0, "", fmt.Errorf("Token validation failed in header: %w", err)
}
for _, field := range requiredFields {
headerValue := r.Header.Get(fmt.Sprintf("X-%s", field))
if headerValue == "" {
return 0, "", fmt.Errorf("required field %s is missing in header: %s", field, headerValue)
}
}
return userID, token, nil
}
func GetUserType(userID int) (string, error) {
user, err := GetUserByID(userID)
if err != nil {
return "", fmt.Errorf("Failed to get user type: %w", err)
}
return user.Type, nil
}
func CheckPermission(userID int, requiredTypes ...string) error {
userType, err := GetUserType(userID)
if err != nil {
return fmt.Errorf("Failed to check permission: %w", err)
}
for _, t := range requiredTypes {
if userType == t {
return nil
}
}
return fmt.Errorf("Permission denied for user type %s", userType)
}
func GetClientIP(r *http.Request) string {
forwarded := r.Header.Get("X-Forwarded-For")
if forwarded != "" {
return forwarded
}
return r.RemoteAddr
}
func LogRequest(r *http.Request, userID int) {
postLog.Info(fmt.Sprintf("[%s] %s %s - UserID: %d - IP: %s",
time.Now().Format("2006-01-02 15:04:05"),
r.Method,
r.URL.Path,
userID,
GetClientIP(r),
))
}
func IntToString(i int) string {
return strconv.Itoa(i)
}