131 lines
3.7 KiB
Go
131 lines
3.7 KiB
Go
package usecase
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type googleIDTokenInfoResp struct {
|
|
Audience string `json:"aud"`
|
|
Issuer string `json:"iss"`
|
|
Subject string `json:"sub"`
|
|
Email string `json:"email"`
|
|
Name string `json:"name"`
|
|
Expires string `json:"exp"`
|
|
}
|
|
|
|
func googleIDTokenInfo(ctx context.Context, idToken, clientID string) (*googleIDTokenInfoResp, error) {
|
|
endpoint := "https://oauth2.googleapis.com/tokeninfo?id_token=" + url.QueryEscape(strings.TrimSpace(idToken))
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
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 tokeninfo status %d", resp.StatusCode)
|
|
}
|
|
var info googleIDTokenInfoResp
|
|
if err := json.Unmarshal(body, &info); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := validateGoogleIDTokenInfo(&info, clientID, time.Now()); err != nil {
|
|
return nil, err
|
|
}
|
|
return &info, nil
|
|
}
|
|
|
|
func validateGoogleIDTokenInfo(info *googleIDTokenInfoResp, clientID string, now time.Time) error {
|
|
if info == nil || info.Subject == "" || info.Audience != strings.TrimSpace(clientID) {
|
|
return fmt.Errorf("google ID token audience or subject is invalid")
|
|
}
|
|
if info.Issuer != "accounts.google.com" && info.Issuer != "https://accounts.google.com" {
|
|
return fmt.Errorf("google ID token issuer is invalid")
|
|
}
|
|
expires, err := strconv.ParseInt(info.Expires, 10, 64)
|
|
if err != nil || expires <= now.Unix() {
|
|
return fmt.Errorf("google ID token is expired")
|
|
}
|
|
return 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
|
|
}
|