Files
maingate/core/platformapple.go

400 lines
11 KiB
Go

package core
import (
"context"
"crypto/x509"
"encoding/json"
"encoding/pem"
"errors"
"io"
"net/http"
"net/url"
"strings"
"time"
"repositories.action2quare.com/ayo/gocommon/logger"
"github.com/golang-jwt/jwt"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo/options"
)
type Apple_WebValidationTokenRequest struct {
ClientID string
ClientSecret string
Code string
RedirectURI string
}
type Apple_WebRefreshTokenRequest struct {
ClientID string
ClientSecret string
RefreshToken string
}
type Apple_ValidationResponse struct {
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"`
ExpiresIn int `json:"expires_in"`
RefreshToken string `json:"refresh_token"`
IDToken string `json:"id_token"`
Error string `json:"error"`
ErrorDescription string `json:"error_description"`
}
func (mg *Maingate) platform_apple_get_login_url(w http.ResponseWriter, r *http.Request) {
browserinfo, err := mg.GetUserBrowserInfo(r)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
logger.Error(err)
return
}
existid := r.URL.Query().Get("existid")
//fmt.Println("existid =>", existid)
if existid != "" {
// 기존 계정이 있는 경우에는 그 계정 부터 조회한다.
info, err := mg.getUserTokenWithCheck(AuthPlatformApple, existid, browserinfo)
if err == nil {
if info.token != "" {
params := url.Values{}
params.Add("id", existid)
params.Add("authtype", AuthPlatformApple)
http.Redirect(w, r, "actionsquare://login?"+params.Encode(), http.StatusSeeOther)
return
}
}
}
sessionkey := mg.GeneratePlatformLoginNonceKey()
nonce := mg.GeneratePlatformLoginNonceKey()
mg.mongoClient.Delete(CollectionPlatformLoginToken, bson.M{
"platform": AuthPlatformApple,
"key": sessionkey,
})
_, _, err = mg.mongoClient.Update(CollectionPlatformLoginToken, bson.M{
"_id": primitive.NewObjectID(),
}, bson.M{
"$setOnInsert": bson.M{
"platform": AuthPlatformApple,
"key": sessionkey,
"nonce": nonce,
"brinfo": browserinfo,
},
}, options.Update().SetUpsert(true))
if err != nil {
w.WriteHeader(http.StatusBadRequest)
logger.Error(err)
return
}
params := url.Values{}
params.Add("client_id", mg.AppleCientId)
params.Add("redirect_uri", mg.RedirectBaseUrl+"/authorize/"+AuthPlatformApple)
params.Add("response_type", "code id_token")
params.Add("scope", "name email")
params.Add("nonce", nonce)
params.Add("response_mode", "form_post")
// set cookie for storing token
cookie := http.Cookie{
Name: "LoginFlowContext_SessionKey",
Value: sessionkey,
Expires: time.Now().Add(1 * time.Hour),
//SameSite: http.SameSiteStrictMode,
SameSite: http.SameSiteLaxMode,
// HttpOnly: false,
Secure: true,
Path: "/",
}
http.SetCookie(w, &cookie)
//Set-Cookie
http.Redirect(w, r, "https://appleid.apple.com/auth/authorize?"+params.Encode(), http.StatusSeeOther)
}
func (mg *Maingate) platform_apple_authorize(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
body, _ := io.ReadAll(r.Body)
bodyString := string(body)
code := ""
for _, params := range strings.Split(bodyString, "&") {
args := strings.Split(params, "=")
if len(args) == 2 {
if args[0] == "code" {
code = args[1]
}
}
}
// set cookie for storing token
cookie := http.Cookie{
Name: "LoginFlowContext_code",
Value: code,
Expires: time.Now().Add(1 * time.Minute),
SameSite: http.SameSiteLaxMode,
Secure: true,
Path: "/",
}
http.SetCookie(w, &cookie)
http.Redirect(w, r, mg.RedirectBaseUrl+"/authorize_result/"+AuthPlatformApple, http.StatusSeeOther) //-- 바로 받으니까 쿠키 안와서 한번 더 Redirect 시킨다.
}
func (mg *Maingate) platform_apple_authorize_result(w http.ResponseWriter, r *http.Request) {
brinfo, err := mg.GetUserBrowserInfo(r)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
logger.Error(err)
return
}
cookie, err := r.Cookie("LoginFlowContext_SessionKey")
if err != nil {
logger.Println("Session not found", err)
w.WriteHeader(http.StatusBadRequest)
return
}
cookiecode, err := r.Cookie("LoginFlowContext_code")
if err != nil {
logger.Println("code not found", err)
w.WriteHeader(http.StatusBadRequest)
return
}
code := cookiecode.Value
found, err := mg.mongoClient.FindOne(CollectionPlatformLoginToken, bson.M{
"key": cookie.Value,
"platform": AuthPlatformApple,
})
if err != nil {
logger.Println("LoginFlowContext_SessionKey find key :", err)
w.WriteHeader(http.StatusBadRequest)
return
}
if found == nil {
logger.Println("LoginFlowContext_SessionKey not found")
w.WriteHeader(http.StatusBadRequest)
return
}
if cookie.Value != found["key"] {
logger.Println("LoginFlowContext_SessionKey key not match")
logger.Println(cookie.Value)
logger.Println(found["key"])
w.WriteHeader(http.StatusBadRequest)
return
}
if brinfo != found["brinfo"] { //-- 로그인 시작점과 인증점의 브라우저 혹은 접속지 정보가 다르다?
logger.Println("LoginFlowContext_SessionKey brinfo not match ")
logger.Println(brinfo)
logger.Println(found["brinfo"])
w.WriteHeader(http.StatusBadRequest)
return
}
// Generate the client secret used to authenticate with Apple's validation servers
secret, err := generateClientSecret(mg.ApplePrivateKey, mg.AppleTeamId, mg.AppleServiceId, mg.AppleKeyId)
if err != nil {
logger.Error("error generating secret: ", err)
return
}
vReq := Apple_WebValidationTokenRequest{
ClientID: mg.AppleServiceId,
ClientSecret: secret,
Code: code,
RedirectURI: mg.RedirectBaseUrl + "/authorize/" + AuthPlatformApple, // This URL must be validated with apple in your service
}
var resp Apple_ValidationResponse
err = verifyWebToken(context.Background(), vReq, &resp)
if err != nil {
logger.Error("error verifying: ", err)
return
}
if resp.Error != "" {
logger.Errorf("apple returned an error: %s - %s\n", resp.Error, resp.ErrorDescription)
return
}
// fmt.Println("==============================")
// fmt.Println("IDToken:", resp.IDToken)
// fmt.Println("AccessToken:", resp.AccessToken)
// fmt.Println("ExpiresIn:", resp.ExpiresIn)
// fmt.Println("RefreshToken:", resp.RefreshToken)
// fmt.Println("TokenType:", resp.TokenType)
// fmt.Println("==============================")
userid, email, nonce := JWTparseCode("https://appleid.apple.com/auth/keys", resp.IDToken)
if nonce == "" || nonce != found["nonce"] {
logger.Errorf("nonce not match")
return
}
if userid != "" && email != "" {
var info usertokeninfo
info.platform = AuthPlatformApple
info.userid = userid
info.token = resp.RefreshToken
info.brinfo = brinfo
mg.setUserToken(info)
params := url.Values{}
params.Add("id", userid)
params.Add("authtype", AuthPlatformApple)
http.Redirect(w, r, "actionsquare://login?"+params.Encode(), http.StatusSeeOther)
} else {
http.Redirect(w, r, "actionsquare://error", http.StatusSeeOther)
}
}
func (mg *Maingate) platform_apple_getuserinfo(refreshToken string) (bool, string, string) {
//=================================RefreshToken을 사용해서 정보 가져 온다. 이미 인증된 사용자의 업데이트 목적
secret, err := generateClientSecret(mg.ApplePrivateKey, mg.AppleTeamId, mg.AppleServiceId, mg.AppleKeyId)
if err != nil {
logger.Error("error generating secret: ", err)
return false, "", ""
}
vReqRefreshToken := Apple_WebRefreshTokenRequest{
ClientID: mg.AppleServiceId,
ClientSecret: secret,
RefreshToken: refreshToken,
}
var respReferesh Apple_ValidationResponse
err = verifyRefreshToken(context.Background(), vReqRefreshToken, &respReferesh)
if err != nil {
logger.Error("error verifying: " + err.Error())
return false, "", ""
}
if respReferesh.Error != "" {
logger.Error("apple returned an error: %s - %s\n", respReferesh.Error, respReferesh.ErrorDescription)
return false, "", ""
}
userid, email, _ := JWTparseCode("https://appleid.apple.com/auth/keys", respReferesh.IDToken)
// fmt.Println("==============================")
// fmt.Println("RefreshToken")
// fmt.Println("==============================")
// fmt.Println("IDToken:", respReferesh.IDToken)
// fmt.Println("AccessToken:", respReferesh.AccessToken)
// fmt.Println("ExpiresIn:", respReferesh.ExpiresIn)
// fmt.Println("RefreshToken:", respReferesh.RefreshToken)
// fmt.Println("TokenType:", respReferesh.TokenType)
// fmt.Println("==============================")
// fmt.Println("Parse:")
// fmt.Println("userid:", userid)
// fmt.Println("email:", email)
// fmt.Println("nonce:", nonce)
return true, userid, email
}
func generateClientSecret(signingKey, teamID, clientID, keyID string) (string, error) {
block, _ := pem.Decode([]byte(signingKey))
if block == nil {
return "", errors.New("empty block after decoding")
}
privKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return "", err
}
// Create the Claims
now := time.Now()
claims := &jwt.StandardClaims{
Issuer: teamID,
IssuedAt: now.Unix(),
ExpiresAt: now.Add(time.Hour*24*180 - time.Second).Unix(), // 180 days
Audience: "https://appleid.apple.com",
Subject: clientID,
}
token := jwt.NewWithClaims(jwt.SigningMethodES256, claims)
token.Header["alg"] = "ES256"
token.Header["kid"] = keyID
return token.SignedString(privKey)
}
func verifyWebToken(ctx context.Context, reqBody Apple_WebValidationTokenRequest, result interface{}) error {
data := url.Values{}
data.Set("client_id", reqBody.ClientID)
data.Set("client_secret", reqBody.ClientSecret)
data.Set("code", reqBody.Code)
data.Set("redirect_uri", reqBody.RedirectURI)
data.Set("grant_type", "authorization_code")
req, err := http.NewRequestWithContext(ctx, "POST", "https://appleid.apple.com/auth/token", strings.NewReader(data.Encode()))
if err != nil {
return err
}
req.Header.Add("content-type", "application/x-www-form-urlencoded")
req.Header.Add("accept", "application/json")
req.Header.Add("user-agent", "go-signin-with-apple") // apple requires a user agent
client := &http.Client{}
res, err := client.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
return json.NewDecoder(res.Body).Decode(result)
}
func verifyRefreshToken(ctx context.Context, reqBody Apple_WebRefreshTokenRequest, result interface{}) error {
data := url.Values{}
data.Set("client_id", reqBody.ClientID)
data.Set("client_secret", reqBody.ClientSecret)
data.Set("grant_type", "refresh_token")
data.Set("refresh_token", reqBody.RefreshToken)
//return doRequest(ctx, c.client, &result, c.validationURL, data)
req, err := http.NewRequestWithContext(ctx, "POST", "https://appleid.apple.com/auth/token", strings.NewReader(data.Encode()))
if err != nil {
return err
}
req.Header.Add("content-type", "application/x-www-form-urlencoded")
req.Header.Add("accept", "application/json")
req.Header.Add("user-agent", "go-signin-with-apple") // apple requires a user agent
client := &http.Client{}
res, err := client.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
return json.NewDecoder(res.Body).Decode(result)
}