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
}