package usecase import ( "context" "encoding/json" "fmt" "io" "net/http" "net/url" "strings" "time" ) type googleUserInfoResp struct { ID string `json:"id"` Email string `json:"email"` Name string `json:"name"` } func googleUserInfo(ctx context.Context, accessOrIDToken string) (*googleUserInfoResp, error) { // Prefer userinfo with Bearer access token (stand-alone style) req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://www.googleapis.com/oauth2/v2/userinfo", nil) if err != nil { return nil, err } req.Header.Set("Authorization", "Bearer "+accessOrIDToken) client := &http.Client{Timeout: 10 * time.Second} resp, err := client.Do(req) if err != nil { return nil, err } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("google userinfo status %d: %s", resp.StatusCode, string(body)) } var info googleUserInfoResp if err := json.Unmarshal(body, &info); err != nil { return nil, err } if info.ID == "" { return nil, fmt.Errorf("google userinfo missing id") } return &info, nil } type lineTokenResp struct { AccessToken string `json:"access_token"` } type lineProfileResp struct { UserID string `json:"userId"` DisplayName string `json:"displayName"` } func lineExchangeCode(ctx context.Context, code, redirectURI, clientID, clientSecret string) (string, error) { data := url.Values{} data.Set("grant_type", "authorization_code") data.Set("code", code) data.Set("redirect_uri", redirectURI) data.Set("client_id", clientID) data.Set("client_secret", clientSecret) req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://api.line.me/oauth2/v2.1/token", strings.NewReader(data.Encode())) if err != nil { return "", err } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") client := &http.Client{Timeout: 10 * time.Second} resp, err := client.Do(req) if err != nil { return "", err } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) if resp.StatusCode != http.StatusOK { return "", fmt.Errorf("line token status %d: %s", resp.StatusCode, string(body)) } var tok lineTokenResp if err := json.Unmarshal(body, &tok); err != nil { return "", err } if tok.AccessToken == "" { return "", fmt.Errorf("line empty access_token") } return tok.AccessToken, nil } func lineProfile(ctx context.Context, accessToken string) (*lineProfileResp, error) { req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.line.me/v2/profile", nil) if err != nil { return nil, err } req.Header.Set("Authorization", "Bearer "+accessToken) client := &http.Client{Timeout: 10 * time.Second} resp, err := client.Do(req) if err != nil { return nil, err } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("line profile status %d: %s", resp.StatusCode, string(body)) } var p lineProfileResp if err := json.Unmarshal(body, &p); err != nil { return nil, err } if p.UserID == "" { return nil, fmt.Errorf("line profile missing userId") } return &p, nil }