package core import ( "bytes" "encoding/json" "io" "net/http" "net/url" "time" "repositories.action2quare.com/ayo/gocommon/logger" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo/options" ) type Google_ValidationResponse struct { IDToken string `json:"id_token"` RefreshToken string `json:"refresh_token"` AccessToken string `json:"access_token"` TokenType string `json:"token_type"` Scope string `json:"scope"` ExpiresIn int `json:"expires_in"` } type Google_UserInfoResponse struct { Sub string `json:"sub"` Givenname string `json:"given_name"` Familyname string `json:"family_name"` Email string `json:"email"` Locale string `json:"locale"` } func (mg *Maingate) platform_google_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(AuthPlatformGoogle, existid, browserinfo) if err == nil { if info.token != "" { params := url.Values{} params.Add("id", existid) params.Add("authtype", AuthPlatformGoogle) http.Redirect(w, r, "actionsquare://login?"+params.Encode(), http.StatusSeeOther) return } } } sessionkey := mg.GeneratePlatformLoginNonceKey() nonce := mg.GeneratePlatformLoginNonceKey() mg.mongoClient.Delete(CollectionPlatformLoginToken, bson.M{ "platform": AuthPlatformGoogle, "key": sessionkey, }) _, _, err = mg.mongoClient.Update(CollectionPlatformLoginToken, bson.M{ "_id": primitive.NewObjectID(), }, bson.M{ "$setOnInsert": bson.M{ "platform": AuthPlatformGoogle, "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", config.GoogleClientId) params.Add("response_type", "code") params.Add("redirect_uri", config.RedirectBaseUrl+"/authorize/"+AuthPlatformGoogle) params.Add("scope", "https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email") params.Add("access_type", "offline") params.Add("prompt", "consent") params.Add("state", nonce) // 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 //fmt.Println(mg.authorizationEndpoints[AuthPlatformGoogle] + params.Encode()) //http.Redirect(w, r, "https://appleid.apple.com/auth/authorize?"+params.Encode(), http.StatusSeeOther) http.Redirect(w, r, mg.authorizationEndpoints[AuthPlatformGoogle]+"?"+params.Encode(), http.StatusSeeOther) } func (mg *Maingate) platform_google_authorize(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() code := r.URL.Query().Get("code") state := r.URL.Query().Get("state") // 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) // set cookie for storing token cookie2 := http.Cookie{ Name: "LoginFlowContext_code_state", Value: state, Expires: time.Now().Add(1 * time.Minute), SameSite: http.SameSiteLaxMode, Secure: true, Path: "/", } http.SetCookie(w, &cookie2) http.Redirect(w, r, config.RedirectBaseUrl+"/authorize_result/"+AuthPlatformGoogle, http.StatusSeeOther) //-- 바로 받으니까 쿠키 안와서 한번 더 Redirect 시킨다. } func (mg *Maingate) platform_google_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 } cookiestate, err := r.Cookie("LoginFlowContext_code_state") if err != nil { logger.Println("state not found", err) w.WriteHeader(http.StatusBadRequest) return } code := cookiecode.Value state := cookiestate.Value found, err := mg.mongoClient.FindOne(CollectionPlatformLoginToken, bson.M{ "key": cookie.Value, "platform": AuthPlatformGoogle, }) 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 } //================= params := url.Values{} params.Add("client_id", config.GoogleClientId) params.Add("redirect_uri", config.RedirectBaseUrl+"/authorize/"+AuthPlatformGoogle) params.Add("client_secret", config.GoogleClientSecret) params.Add("code", code) params.Add("grant_type", "authorization_code") var respReferesh Google_ValidationResponse acceestoken_expire_time := time.Now().Unix() content := params.Encode() resp, _ := http.Post(mg.tokenEndpoints[AuthPlatformGoogle], "application/x-www-form-urlencoded", bytes.NewBuffer([]byte(content))) if resp != nil { body, _ := io.ReadAll(resp.Body) json.Unmarshal(body, &respReferesh) } else { logger.Println("tokenEndpoints fail.") w.WriteHeader(http.StatusBadRequest) return } // 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("Scope:", respReferesh.Scope) // fmt.Println("==============================") if respReferesh.RefreshToken == "" { logger.Errorf("RefreshToken not found") w.WriteHeader(http.StatusBadRequest) return } acceestoken_expire_time = acceestoken_expire_time + (int64(respReferesh.ExpiresIn) * 3 / 4) //--- 3/4 이상 지나면 업데이트 한다. ms는 보통 3600초 1시간 userid, email, _ := JWTparseCode(mg.jwksUri[AuthPlatformGoogle], respReferesh.IDToken) if state == "" || state != found["nonce"] { logger.Errorf("nonce not match") return } if userid != "" && email != "" { var info usertokeninfo info.platform = AuthPlatformGoogle info.userid = userid info.token = respReferesh.RefreshToken info.brinfo = brinfo info.accesstoken = respReferesh.AccessToken info.accesstoken_expire_time = acceestoken_expire_time mg.setUserToken(info) params := url.Values{} params.Add("id", userid) params.Add("authtype", AuthPlatformGoogle) http.Redirect(w, r, "actionsquare://login?"+params.Encode(), http.StatusSeeOther) } else { http.Redirect(w, r, "actionsquare://error", http.StatusSeeOther) } } func (mg *Maingate) platform_google_getuserinfo(info usertokeninfo) (bool, string, string) { // fmt.Println(info.platform) // fmt.Println(info.userid) // fmt.Println(info.accesstoken) // fmt.Println(time.Now().Unix()) // fmt.Println(info.accesstoken_expire_time) //-- access token 갱신이 필요하다. -- userinfoEndpoint if time.Now().Unix() > info.accesstoken_expire_time { params := url.Values{} params.Add("client_id", config.GoogleClientId) params.Add("redirect_uri", config.RedirectBaseUrl+"/authorize/"+AuthPlatformGoogle) params.Add("client_secret", config.GoogleClientSecret) params.Add("scope", "https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email") params.Add("refresh_token", info.token) params.Add("grant_type", "refresh_token") var respReferesh Google_ValidationResponse acceestoken_expire_time := time.Now().Unix() content := params.Encode() resp, _ := http.Post(mg.tokenEndpoints[AuthPlatformGoogle], "application/x-www-form-urlencoded", bytes.NewBuffer([]byte(content))) if resp != nil { body, _ := io.ReadAll(resp.Body) json.Unmarshal(body, &respReferesh) // fmt.Println("==== accesstoken 업데이트 =====") // 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("==============================") acceestoken_expire_time = acceestoken_expire_time + (int64(respReferesh.ExpiresIn) * 3 / 4) //--- 3/4 이상 지나면 업데이트 한다. ms는 보통 3600초 1시간 userid, email, _ := JWTparseCode(mg.jwksUri[AuthPlatformGoogle], respReferesh.IDToken) if userid != "" && email != "" && info.userid == userid { info.token = respReferesh.RefreshToken info.accesstoken = respReferesh.AccessToken info.accesstoken_expire_time = acceestoken_expire_time mg.setUserToken(info) //-- accesstoken 업데이트 } else { logger.Println("JWTparseCode fail.") return false, "", "" } } else { logger.Println("tokenEndpoints fail.") return false, "", "" } } //================= req, _ := http.NewRequest("GET", mg.userinfoEndpoint[AuthPlatformGoogle], nil) req.Header.Add("Authorization", "Bearer "+info.accesstoken) client := http.Client{} resp, err := client.Do(req) if err != nil { logger.Error("authorize query failed :", err) return false, "", "" } defer resp.Body.Close() var respUserInfo Google_UserInfoResponse body, _ := io.ReadAll(resp.Body) json.Unmarshal(body, &respUserInfo) // fmt.Println(string(body)) // fmt.Println(respUserInfo.Sub) // fmt.Println(respUserInfo.Email) if respUserInfo.Sub != info.userid { logger.Println("userinfoEndpoint fail.") return false, "", "" } return true, info.userid, respUserInfo.Email //? }