summaryrefslogtreecommitdiff
path: root/handlers/auth_token.go
diff options
context:
space:
mode:
authorhaturatu <taro@eyes4you.org>2025-10-13 02:25:46 +0900
committerhaturatu <taro@eyes4you.org>2025-10-13 02:25:46 +0900
commitcb4a103e0b59519933b1ccbd0d8897d3c7238273 (patch)
tree47cf53a76265a0203eb59e59e06ce0d7b14df038 /handlers/auth_token.go
parent6f19c4dcb7702f002d2a1b579f2418e1bc10b18f (diff)
ref: refactor
Diffstat (limited to 'handlers/auth_token.go')
-rw-r--r--handlers/auth_token.go138
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)
+ }
+}