diff options
| author | haturatu <taro@eyes4you.org> | 2025-10-13 02:25:46 +0900 |
|---|---|---|
| committer | haturatu <taro@eyes4you.org> | 2025-10-13 02:25:46 +0900 |
| commit | cb4a103e0b59519933b1ccbd0d8897d3c7238273 (patch) | |
| tree | 47cf53a76265a0203eb59e59e06ce0d7b14df038 /handlers/auth_token.go | |
| parent | 6f19c4dcb7702f002d2a1b579f2418e1bc10b18f (diff) | |
ref: refactor
Diffstat (limited to 'handlers/auth_token.go')
| -rw-r--r-- | handlers/auth_token.go | 138 |
1 files changed, 138 insertions, 0 deletions
diff --git a/handlers/auth_token.go b/handlers/auth_token.go new file mode 100644 index 0000000..7ea7ef6 --- /dev/null +++ b/handlers/auth_token.go @@ -0,0 +1,138 @@ +package handlers + +import ( + "auth-proxy/config" + "auth-proxy/logging" + "auth-proxy/models" + "auth-proxy/utils" + "encoding/json" + "net/http" + "time" +) + +// RefreshTokenHandler handles exchanging a valid refresh token for a new access token. +func RefreshTokenHandler(w http.ResponseWriter, r *http.Request) { + ip := logging.GetClientIP(r) + + // 1. Parse request body + var reqBody struct { + RefreshToken string `json:"refresh_token"` + } + if err := json.NewDecoder(r.Body).Decode(&reqBody); err != nil { + http.Error(w, "Invalid request body", http.StatusBadRequest) + return + } + if reqBody.RefreshToken == "" { + http.Error(w, "Missing refresh_token", http.StatusBadRequest) + return + } + + // 2. Validate the refresh token in DB + refreshToken, err := models.GetRefreshTokenByToken(reqBody.RefreshToken) + if err != nil { + logging.AppLog.Error("Error getting refresh token from DB", "error", err, "ip", ip) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } + if refreshToken == nil || refreshToken.IsRevoked || time.Now().After(refreshToken.ExpiresAt) { + logging.SecurityLog.Warn("REFRESH TOKEN FAIL", "reason", "token not found, revoked, or expired", "ip", ip) + http.Error(w, "Unauthorized: Invalid refresh token", http.StatusUnauthorized) + return + } + + // 3. Get user info + user, err := models.GetUserByID(refreshToken.UserID) + if err != nil || user == nil { + logging.AppLog.Error("Failed to get user from refresh token", "error", err, "user_id", refreshToken.UserID, "ip", ip) + http.Error(w, "Unauthorized: User not found", http.StatusUnauthorized) + return + } + + // 4. Issue a new access token + newAccessToken, err := utils.GenerateJWT(user, time.Duration(config.Paths.AccessTokenDurationMinutes)*time.Minute) + if err != nil { + logging.AppLog.Error("Failed to generate new access token", "error", err, "username", user.Username, "ip", ip) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } + + // --- Optional: Token Rotation --- + // 5. Delete the old refresh token + if err := models.DeleteRefreshTokenByToken(refreshToken.Token); err != nil { + logging.AppLog.Error("Failed to delete old refresh token", "error", err, "ip", ip) + // Continue, but log the error + } + + // 6. Issue a new refresh token + newRefreshToken, err := models.CreateRefreshToken(user.ID, time.Duration(config.Paths.RefreshTokenDurationDays)*24*time.Hour) + if err != nil { + logging.AppLog.Error("Failed to create new refresh token", "error", err, "username", user.Username, "ip", ip) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } + // --- End Token Rotation --- + + // 7. Respond with new tokens + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + if err := json.NewEncoder(w).Encode(map[string]string{ + "access_token": newAccessToken, + "refresh_token": newRefreshToken.Token, + }); err != nil { + logging.AppLog.Error("Failed to encode token response", "error", err, "ip", ip) + } +} + +// TokenHandler handles issuing JWT tokens for API clients. +func TokenHandler(w http.ResponseWriter, r *http.Request) { + ip := logging.GetClientIP(r) + + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + var creds struct { + Username string `json:"username"` + Password string `json:"password"` + } + if err := json.NewDecoder(r.Body).Decode(&creds); err != nil { + http.Error(w, "Invalid request body", http.StatusBadRequest) + return + } + + // Use the shared authentication logic + user, err := authenticateUser(creds.Username, creds.Password, ip) + if err != nil { + // Return a generic error to the API client + http.Error(w, "Unauthorized: "+err.Error(), http.StatusUnauthorized) + return + } + + // --- Issue Tokens --- + // Access Token + accessToken, err := utils.GenerateJWT(user, time.Duration(config.Paths.AccessTokenDurationMinutes)*time.Minute) + if err != nil { + logging.AppLog.Error("Failed to generate access token", "error", err, "username", user.Username, "ip", ip) + http.Error(w, "Failed to generate access token", http.StatusInternalServerError) + return + } + + // Refresh Token + refreshToken, err := models.CreateRefreshToken(user.ID, time.Duration(config.Paths.RefreshTokenDurationDays)*24*time.Hour) + if err != nil { + logging.AppLog.Error("Failed to create refresh token", "error", err, "username", user.Username, "ip", ip) + http.Error(w, "Failed to create refresh token", http.StatusInternalServerError) + return + } + // --- End Issue Tokens --- + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + if err := json.NewEncoder(w).Encode(map[string]string{ + "access_token": accessToken, + "refresh_token": refreshToken.Token, + }); err != nil { + logging.AppLog.Error("Failed to encode token response", "error", err, "ip", ip) + } +} |
