package worker import ( "bytes" "context" "encoding/json" "fmt" "io" "net/http" "service/internal/auth" "service/pkg/logger" "strings" "time" ) // apiLoginResponse is a helper struct to unmarshal the nested API response. type apiLoginResponse struct { Data auth.LoginResponse `json:"data"` } // login performs the initial login to get the first set of tokens. func (m *Manager) login(ctx context.Context) error { loginURL := fmt.Sprintf("%s/auth/login", strings.TrimRight(m.cfg.SatuSehat.InternalFHIRServerURL, "/")) reqBody := map[string]string{ "email": "admin@example.com", "password": "password123", } payloadBytes, _ := json.Marshal(reqBody) req, err := http.NewRequestWithContext(ctx, "POST", loginURL, bytes.NewBuffer(payloadBytes)) if err != nil { return fmt.Errorf("gagal membuat login request: %w", err) } req.Header.Set("Content-Type", "application/json") logger.Default().Info("[WORKER AUTH] Melakukan login untuk mendapatkan token awal...", logger.String("url", loginURL)) client := &http.Client{Timeout: 15 * time.Second} resp, err := client.Do(req) if err != nil { return fmt.Errorf("gagal melakukan login request: %w", err) } defer resp.Body.Close() // Baca body untuk logging jika terjadi error bodyBytes, readErr := io.ReadAll(resp.Body) if readErr != nil { return fmt.Errorf("gagal membaca response body login: %w", readErr) } if resp.StatusCode != http.StatusOK { return fmt.Errorf("login gagal dengan status: %d, response: %s", resp.StatusCode, string(bodyBytes)) } var apiResp apiLoginResponse if err := json.Unmarshal(bodyBytes, &apiResp); err != nil { return fmt.Errorf("gagal decode response login: %w, body: %s", err, string(bodyBytes)) } loginResp := apiResp.Data if loginResp.AccessToken == "" || loginResp.RefreshToken == "" { logger.Default().Error("[WORKER AUTH] Token kosong diterima dari server", logger.Any("response", loginResp)) return fmt.Errorf("access token atau refresh token kosong diterima dari server") } m.accessToken = loginResp.AccessToken m.refreshToken = loginResp.RefreshToken logger.Default().Info("[WORKER AUTH] Berhasil mendapatkan token awal.") return nil } // refresh uses the refresh_token to get a new access_token. func (m *Manager) refresh(ctx context.Context) error { refreshURL := fmt.Sprintf("%s/auth/refresh", strings.TrimRight(m.cfg.SatuSehat.InternalFHIRServerURL, "/")) if m.refreshToken == "" { return fmt.Errorf("tidak ada refresh token untuk digunakan, akan mencoba login ulang") } reqBody := auth.RefreshTokenRequest{ RefreshToken: m.refreshToken, Provider: "jwt", } payloadBytes, _ := json.Marshal(reqBody) req, err := http.NewRequestWithContext(ctx, "POST", refreshURL, bytes.NewBuffer(payloadBytes)) if err != nil { return fmt.Errorf("gagal membuat refresh request: %w", err) } req.Header.Set("Content-Type", "application/json") logger.Default().Info("[WORKER AUTH] Melakukan refresh token...", logger.String("url", refreshURL)) client := &http.Client{Timeout: 15 * time.Second} resp, err := client.Do(req) if err != nil { return fmt.Errorf("gagal melakukan refresh request: %w", err) } defer resp.Body.Close() bodyBytes, readErr := io.ReadAll(resp.Body) if readErr != nil { return fmt.Errorf("gagal membaca response body refresh: %w", readErr) } if resp.StatusCode != http.StatusOK { return fmt.Errorf("refresh token gagal dengan status: %d, response: %s", resp.StatusCode, string(bodyBytes)) } var apiResp apiLoginResponse if err := json.Unmarshal(bodyBytes, &apiResp); err != nil { return fmt.Errorf("gagal decode response refresh: %w, body: %s", err, string(bodyBytes)) } refreshResp := apiResp.Data if refreshResp.AccessToken == "" { logger.Default().Error("[WORKER AUTH] Access token kosong diterima saat refresh", logger.Any("response", refreshResp)) return fmt.Errorf("access token kosong diterima dari server saat refresh") } m.accessToken = refreshResp.AccessToken // Update refresh token if a new one is provided if refreshResp.RefreshToken != "" { m.refreshToken = refreshResp.RefreshToken } logger.Default().Info("[WORKER AUTH] Berhasil me-refresh token.") return nil } // GetAccessToken safely retrieves the current access token. func (m *Manager) GetAccessToken() string { m.tokenMutex.RLock() defer m.tokenMutex.RUnlock() return m.accessToken } // ForceRefreshAndGetToken tries to refresh the token, falls back to login, and returns the new token. func (m *Manager) ForceRefreshAndGetToken(ctx context.Context) (string, error) { m.tokenMutex.Lock() defer m.tokenMutex.Unlock() if err := m.refresh(ctx); err != nil { logger.Default().Warn("[WORKER AUTH] Refresh token gagal, mencoba login ulang...", logger.ErrorField(err)) if loginErr := m.login(ctx); loginErr != nil { return "", fmt.Errorf("fallback login juga gagal: %w", loginErr) } } return m.accessToken, nil }