pkg/server/auth/handlers.go (168 lines of code) (raw):

package auth import ( "context" "encoding/json" "fmt" "io" "net/http" "net/url" "time" "github.com/VictoriaMetrics/fastcache" ) type UserInfo struct { ID string `json:"id"` Email string `json:"email"` VerifiedEmail bool `json:"verified_email"` Name string `json:"name"` GivenName string `json:"given_name"` FamilyName string `json:"family_name"` Picture string `json:"picture"` Locale string `json:"locale"` HD string `json:"hd"` } func CreateGetUserInfoHandler() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { accessToken := r.Header.Get("X-Auth-Request-Access-Token") if accessToken == "" { http.Error(w, "Unauthorized", http.StatusUnauthorized) return } userInfo, err := FetchUserInfo(r.Context(), accessToken) if err != nil { http.Error(w, "Failed to fetch user info", http.StatusInternalServerError) return } err = json.NewEncoder(w).Encode(userInfo) if err != nil { http.Error(w, "Failed to parse user info", http.StatusInternalServerError) return } } } func FetchUserInfo(ctx context.Context, googleToken string) (*UserInfo, error) { client := &http.Client{} req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://www.googleapis.com/oauth2/v2/userinfo", http.NoBody) if err != nil { return nil, err } req.Header.Add("Authorization", "Bearer "+googleToken) resp, err := client.Do(req) if err != nil { return nil, err } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return nil, err } var userInfo UserInfo err = json.Unmarshal(body, &userInfo) if err != nil { return nil, err } return &userInfo, nil } type YTAuth struct { cache *fastcache.Cache youtrackToken string youtrackUrl string client http.Client } type YTUser struct { ID string `json:"id"` } func NewYTAuth(youtrackUrl string, youtrackToken string) *YTAuth { return &YTAuth{ cache: fastcache.New(10 * 1000 * 1000), youtrackToken: youtrackToken, youtrackUrl: youtrackUrl, client: http.Client{ Timeout: 10 * time.Second, }, } } func (ytAuth *YTAuth) GetUser(ctx context.Context, email string) (*YTUser, error) { cachedId, exists := ytAuth.cache.HasGet(nil, []byte(email)) if exists { var v YTUser err := json.Unmarshal(cachedId, &v) return &v, err } hubId, err := ytAuth.getHubUserId(ctx, email) if err != nil { return nil, fmt.Errorf("cannot get Hub user id for %s: %w", email, err) } if hubId == "" { return nil, fmt.Errorf("user %s not found in JetBrains Hub", email) } ytId, err := ytAuth.getYTUserId(ctx, hubId) if err != nil { return nil, fmt.Errorf("cannot get YouTrack user id for %s: %w", email, err) } v, err := json.Marshal(ytId) if err != nil { return nil, fmt.Errorf("cannot marshal YTUser: %w", err) } ytAuth.cache.Set([]byte(email), v) return ytId, nil } func (ytAuth *YTAuth) getYTUserId(ctx context.Context, hubToken string) (*YTUser, error) { req, err := http.NewRequestWithContext(ctx, http.MethodGet, ytAuth.youtrackUrl+"/api/users/"+hubToken+"?fields=id", http.NoBody) if err != nil { return nil, fmt.Errorf("creating request failed: %w", err) } req.Header.Add("Authorization", "Bearer "+ytAuth.youtrackToken) resp, err := ytAuth.client.Do(req) if err != nil { return nil, fmt.Errorf("failed to make request to YouTrack: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("service YouTrack API returned non-200 status: %d", resp.StatusCode) } body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("failed to read response body: %w", err) } userInfo := &YTUser{} err = json.Unmarshal(body, userInfo) if err != nil { return nil, fmt.Errorf("failed to unmarshal YTUser: %w", err) } return userInfo, nil } type usersPage struct { Users []hubUser `json:"users"` } type hubUser struct { ID string `json:"id"` } func (ytAuth *YTAuth) getHubUserId(ctx context.Context, email string) (string, error) { emailEscaped := url.QueryEscape(email) req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://hub.jetbrains.com/api/rest/users?fileds=id&query=email:"+emailEscaped, http.NoBody) if err != nil { return "", fmt.Errorf("creating request failed: %w", err) } req.Header.Add("Authorization", "Bearer "+ytAuth.youtrackToken) resp, err := ytAuth.client.Do(req) if err != nil { return "", fmt.Errorf("failed to make request to JetBrains Hub: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return "", fmt.Errorf("service Hub API returned non-200 status: %d", resp.StatusCode) } body, err := io.ReadAll(resp.Body) if err != nil { return "", fmt.Errorf("failed to read response body: %w", err) } var userInfo usersPage err = json.Unmarshal(body, &userInfo) if err != nil { return "", fmt.Errorf("failed to unmarshal user info: %w", err) } if len(userInfo.Users) == 0 { return "", nil } return userInfo.Users[0].ID, nil }