maingate 이전
This commit is contained in:
399
core/platformapple.go
Normal file
399
core/platformapple.go
Normal file
@ -0,0 +1,399 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"repositories.action2quare.com/ayo/go-ayo/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)
|
||||
}
|
||||
Reference in New Issue
Block a user