maingate 이전
This commit is contained in:
472
core/api.go
Normal file
472
core/api.go
Normal file
@ -0,0 +1,472 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"repositories.action2quare.com/ayo/go-ayo/common"
|
||||
"repositories.action2quare.com/ayo/go-ayo/logger"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
func (caller apiCaller) isGlobalAdmin() bool {
|
||||
if *noauth {
|
||||
return true
|
||||
}
|
||||
|
||||
email, ok := caller.userinfo["email"]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if _, ok := caller.admins[email.(string)]; ok {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (caller apiCaller) writeAccessableServices(w http.ResponseWriter) {
|
||||
services, editable := caller.getAccessableServices()
|
||||
for _, r := range editable {
|
||||
w.Header().Add("MG-X-SERVICE-EDITABLE", r)
|
||||
}
|
||||
|
||||
w.Write([]byte("{"))
|
||||
start := true
|
||||
for _, v := range services {
|
||||
if !start {
|
||||
w.Write([]byte(","))
|
||||
}
|
||||
w.Write([]byte(fmt.Sprintf(`"%s":`, v.ServiceName)))
|
||||
serptr := atomic.LoadPointer(&v.serviceSerialized)
|
||||
w.Write(*(*[]byte)(serptr))
|
||||
start = false
|
||||
}
|
||||
w.Write([]byte("}"))
|
||||
}
|
||||
|
||||
func (caller apiCaller) getAccessableServices() ([]*serviceDescription, []string) {
|
||||
allservices := caller.mg.services.all()
|
||||
v, ok := caller.userinfo["email"]
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
email := v.(string)
|
||||
_, admin := caller.admins[email]
|
||||
|
||||
var output []*serviceDescription
|
||||
var editable []string
|
||||
for _, desc := range allservices {
|
||||
if admin {
|
||||
output = append(output, desc)
|
||||
editable = append(editable, desc.ServiceName)
|
||||
} else if desc.isValidAPIUser("*", email) {
|
||||
output = append(output, desc)
|
||||
if desc.isValidAPIUser("service", email) {
|
||||
editable = append(editable, desc.ServiceName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(output, func(i, j int) bool {
|
||||
return output[i].ServiceName < output[j].ServiceName
|
||||
})
|
||||
|
||||
return output, editable
|
||||
}
|
||||
|
||||
func (caller apiCaller) isValidUser(service any, category string) (valid bool, admin bool) {
|
||||
if *noauth {
|
||||
return true, true
|
||||
}
|
||||
|
||||
v, ok := caller.userinfo["email"]
|
||||
if !ok {
|
||||
logger.Println("isVaidUser failed. email is missing :", caller.userinfo)
|
||||
return false, false
|
||||
}
|
||||
|
||||
email := v.(string)
|
||||
if _, ok := caller.admins[email]; ok {
|
||||
return true, true
|
||||
}
|
||||
|
||||
svcdesc := caller.mg.services.get(service)
|
||||
if svcdesc == nil {
|
||||
logger.Println("isVaidUser failed. service is missing :", service)
|
||||
return false, false
|
||||
}
|
||||
|
||||
return svcdesc.isValidAPIUser(category, email), false
|
||||
}
|
||||
|
||||
func (caller apiCaller) whitelistAPI(w http.ResponseWriter, r *http.Request) error {
|
||||
mg := caller.mg
|
||||
queryvals := r.URL.Query()
|
||||
if r.Method == "GET" {
|
||||
service := queryvals.Get("service")
|
||||
|
||||
if valid, _ := caller.isValidUser(service, "whitelist"); !valid {
|
||||
logger.Println("whitelistAPI failed. not vaild user :", r.Method, caller.userinfo)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(service) > 0 {
|
||||
|
||||
all, err := mg.mongoClient.FindAll(CollectionWhitelist, bson.M{
|
||||
"service": service,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(all) > 0 {
|
||||
allraw, _ := json.Marshal(all)
|
||||
w.Write(allraw)
|
||||
}
|
||||
} else {
|
||||
logger.Println("service param is missing")
|
||||
}
|
||||
} else if r.Method == "PUT" {
|
||||
body, _ := io.ReadAll(r.Body)
|
||||
var member whitelistmember
|
||||
if err := json.Unmarshal(body, &member); err != nil {
|
||||
return err
|
||||
}
|
||||
if valid, _ := caller.isValidUser(member.Service, "whitelist"); !valid {
|
||||
logger.Println("whitelistAPI failed. not vaild user :", r.Method, caller.userinfo)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return nil
|
||||
}
|
||||
|
||||
member.Expired = 0
|
||||
|
||||
_, _, err := mg.mongoClient.Update(CollectionWhitelist, bson.M{
|
||||
"_id": primitive.NewObjectID(),
|
||||
}, bson.M{
|
||||
"$set": &member,
|
||||
}, options.Update().SetUpsert(true))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if r.Method == "DELETE" {
|
||||
id := queryvals.Get("id")
|
||||
if len(id) == 0 {
|
||||
return errors.New("id param is missing")
|
||||
}
|
||||
idobj, err := primitive.ObjectIDFromHex(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, _, err = mg.mongoClient.Update(CollectionWhitelist, bson.M{
|
||||
"_id": idobj,
|
||||
}, bson.M{
|
||||
"$currentDate": bson.M{
|
||||
"_ts": bson.M{"$type": "date"},
|
||||
},
|
||||
}, options.Update().SetUpsert(false))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (caller apiCaller) serviceAPI(w http.ResponseWriter, r *http.Request) error {
|
||||
mg := caller.mg
|
||||
queryvals := r.URL.Query()
|
||||
if r.Method == "GET" {
|
||||
name := queryvals.Get("name")
|
||||
if len(name) > 0 {
|
||||
if valid, _ := caller.isValidUser(name, "*"); !valid {
|
||||
logger.Println("serviceAPI failed. not vaild user :", r.Method, caller.userinfo)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return nil
|
||||
}
|
||||
|
||||
if valid, admin := caller.isValidUser(name, "service"); valid || admin {
|
||||
w.Header().Add("MG-X-SERVICE-EDITABLE", name)
|
||||
}
|
||||
|
||||
serptr := atomic.LoadPointer(&mg.services.get(name).serviceSerialized)
|
||||
w.Write(*(*[]byte)(serptr))
|
||||
} else {
|
||||
caller.writeAccessableServices(w)
|
||||
}
|
||||
} else if r.Method == "POST" {
|
||||
body, _ := io.ReadAll(r.Body)
|
||||
var service serviceDescription
|
||||
if err := json.Unmarshal(body, &service); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if service.Id.IsZero() {
|
||||
if caller.isGlobalAdmin() {
|
||||
service.Id = primitive.NewObjectID()
|
||||
} else {
|
||||
logger.Println("serviceAPI failed. not vaild user :", r.Method, caller.userinfo)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return nil
|
||||
}
|
||||
} else if valid, _ := caller.isValidUser(service.Id, "service"); !valid {
|
||||
logger.Println("serviceAPI failed. not vaild user :", r.Method, caller.userinfo)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return nil
|
||||
}
|
||||
|
||||
filter := bson.M{"_id": service.Id}
|
||||
success, _, err := mg.mongoClient.Update(CollectionService, filter, bson.M{
|
||||
"$set": &service,
|
||||
}, options.Update().SetUpsert(true))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !success {
|
||||
logger.Println("serviceAPI failed. not vaild user :", caller.userinfo)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (caller apiCaller) accountAPI(w http.ResponseWriter, r *http.Request) error {
|
||||
mg := caller.mg
|
||||
queryvals := r.URL.Query()
|
||||
if r.Method == "GET" {
|
||||
service := queryvals.Get("service")
|
||||
if len(service) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if valid, _ := caller.isValidUser(service, "account"); !valid {
|
||||
logger.Println("accountAPI failed. not vaild user :", r.Method, caller.userinfo)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return nil
|
||||
}
|
||||
|
||||
var accdoc primitive.M
|
||||
if v := queryvals.Get("accid"); len(v) == 0 {
|
||||
email := queryvals.Get("email")
|
||||
platform := queryvals.Get("platform")
|
||||
if len(email) == 0 || len(platform) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
found, err := mg.mongoClient.FindOne(CollectionLink, bson.M{
|
||||
"email": email,
|
||||
"platform": platform,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if found == nil {
|
||||
return nil
|
||||
}
|
||||
if idobj, ok := found["_id"]; ok {
|
||||
svcdoc, err := mg.mongoClient.FindOne(common.CollectionName(service), bson.M{
|
||||
"_id": idobj,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if svcdoc != nil {
|
||||
found["accid"] = svcdoc["accid"]
|
||||
}
|
||||
|
||||
accdoc = found
|
||||
}
|
||||
} else {
|
||||
accid, err := primitive.ObjectIDFromHex(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
svcdoc, err := mg.mongoClient.FindOne(common.CollectionName(service), bson.M{
|
||||
"accid": accid,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
found, err := mg.mongoClient.FindOne(CollectionLink, bson.M{
|
||||
"_id": svcdoc["_id"],
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if found != nil {
|
||||
found["accid"] = accid
|
||||
}
|
||||
accdoc = found
|
||||
}
|
||||
|
||||
if accdoc != nil {
|
||||
accdoc["code"] = service
|
||||
delete(accdoc, "uid")
|
||||
delete(accdoc, "_id")
|
||||
|
||||
var bi blockinfo
|
||||
if err := mg.mongoClient.FindOneAs(CollectionBlock, bson.M{
|
||||
"code": service,
|
||||
"accid": accdoc["accid"],
|
||||
}, &bi); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !bi.Start.Time().IsZero() && bi.End.Time().After(time.Now().UTC()) {
|
||||
accdoc["blocked"] = bi
|
||||
}
|
||||
return json.NewEncoder(w).Encode(accdoc)
|
||||
}
|
||||
} else if r.Method == "POST" {
|
||||
var account struct {
|
||||
Code string
|
||||
Accid string
|
||||
Blocked blockinfo
|
||||
}
|
||||
|
||||
body, _ := io.ReadAll(r.Body)
|
||||
if err := json.Unmarshal(body, &account); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
accid, _ := primitive.ObjectIDFromHex(account.Accid)
|
||||
if !account.Blocked.Start.Time().IsZero() && account.Blocked.Start.Time().After(time.Now().UTC()) {
|
||||
if _, _, err := mg.mongoClient.Update(CollectionBlock, bson.M{
|
||||
"code": account.Code,
|
||||
"accid": accid,
|
||||
}, bson.M{
|
||||
"$set": account.Blocked,
|
||||
}, options.Update().SetUpsert(true)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var errApiTokenMissing = errors.New("mg-x-api-token is missing")
|
||||
|
||||
func (caller apiCaller) configAPI(w http.ResponseWriter, r *http.Request) error {
|
||||
mg := caller.mg
|
||||
apitoken := r.Header.Get("MG-X-API-TOKEN")
|
||||
if len(apitoken) == 0 {
|
||||
return errApiTokenMissing
|
||||
}
|
||||
|
||||
if _, exists := mg.apiTokenToService.get(apitoken); !exists {
|
||||
return fmt.Errorf("mg-x-api-token is not valid : %s", apitoken)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var noauth = flag.Bool("noauth", false, "")
|
||||
|
||||
type apiCaller struct {
|
||||
userinfo map[string]any
|
||||
admins map[string]bool
|
||||
mg *Maingate
|
||||
}
|
||||
|
||||
func (mg *Maingate) api(w http.ResponseWriter, r *http.Request) {
|
||||
defer func() {
|
||||
s := recover()
|
||||
if s != nil {
|
||||
logger.Error(s)
|
||||
}
|
||||
}()
|
||||
|
||||
defer func() {
|
||||
io.Copy(io.Discard, r.Body)
|
||||
r.Body.Close()
|
||||
}()
|
||||
|
||||
var userinfo map[string]any
|
||||
|
||||
if !*noauth {
|
||||
authheader := r.Header.Get("Authorization")
|
||||
if len(authheader) == 0 {
|
||||
logger.Println("Authorization header is not valid :", authheader)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
req, _ := http.NewRequest("GET", "https://graph.microsoft.com/oidc/userinfo", nil)
|
||||
req.Header.Add("Authorization", authheader)
|
||||
client := &http.Client{}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
logger.Println("graph microsoft api call failed :", err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
raw, _ := io.ReadAll(resp.Body)
|
||||
if err = json.Unmarshal(raw, &userinfo); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if _, expired := userinfo["error"]; expired {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ptr := atomic.LoadPointer(&mg.admins)
|
||||
adminsptr := (*globalAdmins)(ptr)
|
||||
|
||||
if adminsptr.modtime != common.ConfigModTime() {
|
||||
var config globalAdmins
|
||||
if err := common.LoadConfig(&config); err == nil {
|
||||
config.parse()
|
||||
adminsptr = &config
|
||||
atomic.StorePointer(&mg.admins, unsafe.Pointer(adminsptr))
|
||||
}
|
||||
}
|
||||
|
||||
logger.Println("api call :", r.URL.Path, r.Method, r.URL.Query(), userinfo)
|
||||
caller := apiCaller{
|
||||
userinfo: userinfo,
|
||||
admins: adminsptr.emails,
|
||||
mg: mg,
|
||||
}
|
||||
|
||||
var err error
|
||||
if strings.HasSuffix(r.URL.Path, "/service") {
|
||||
err = caller.serviceAPI(w, r)
|
||||
} else if strings.HasSuffix(r.URL.Path, "/whitelist") {
|
||||
err = caller.whitelistAPI(w, r)
|
||||
} else if strings.HasSuffix(r.URL.Path, "/config") {
|
||||
err = caller.configAPI(w, r)
|
||||
} else if strings.HasSuffix(r.URL.Path, "/account") {
|
||||
err = caller.accountAPI(w, r)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
}
|
||||
}
|
||||
959
core/maingate.go
Normal file
959
core/maingate.go
Normal file
@ -0,0 +1,959 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rsa"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"log"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"repositories.action2quare.com/ayo/go-ayo/common"
|
||||
"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"
|
||||
|
||||
firebase "firebase.google.com/go"
|
||||
"firebase.google.com/go/auth"
|
||||
|
||||
"google.golang.org/api/option"
|
||||
)
|
||||
|
||||
var (
|
||||
CollectionLink = common.CollectionName("link")
|
||||
CollectionAuth = common.CollectionName("auth")
|
||||
CollectionWhitelist = common.CollectionName("whitelist")
|
||||
CollectionService = common.CollectionName("service")
|
||||
CollectionBlock = common.CollectionName("block")
|
||||
CollectionPlatformLoginToken = common.CollectionName("platform_login_token") //-- 각 플랫폼에 로그인 및 권한 받아오는 과정에 사용하는 Key
|
||||
CollectionUserToken = common.CollectionName("usertoken")
|
||||
CollectionGamepotUserInfo = common.CollectionName("gamepot_userinfo") //-- 클라로부터 수집된 gamepot 정보 - server to server로 유효성이 검증되진 않았지만 수집은 한다.
|
||||
CollectionFirebaseUserInfo = common.CollectionName("firebase_userinfo") //-- Firebase UserInfo
|
||||
)
|
||||
|
||||
const (
|
||||
AuthPlatformFirebaseAuth = "firebase"
|
||||
AuthPlatformGamepot = "gamepot"
|
||||
AuthPlatformGoogle = "google"
|
||||
AuthPlatformMicrosoft = "microsoft"
|
||||
AuthPlatformApple = "apple"
|
||||
AuthPlatformTwitter = "twitter"
|
||||
)
|
||||
|
||||
const (
|
||||
sessionTTL = time.Hour
|
||||
sessionTTLDev = 1 * time.Hour
|
||||
)
|
||||
|
||||
func SessionTTL() time.Duration {
|
||||
if *common.Devflag {
|
||||
return sessionTTLDev
|
||||
}
|
||||
|
||||
return sessionTTL
|
||||
}
|
||||
|
||||
type mongoAuthCell struct {
|
||||
src *common.Authinfo
|
||||
}
|
||||
|
||||
func init() {
|
||||
if *common.Devflag {
|
||||
hostname, _ := os.Hostname()
|
||||
CollectionLink = common.CollectionName(fmt.Sprintf("%s-%s", hostname, string(CollectionLink)))
|
||||
CollectionAuth = common.CollectionName(fmt.Sprintf("%s-%s", hostname, string(CollectionAuth)))
|
||||
CollectionWhitelist = common.CollectionName(fmt.Sprintf("%s-%s", hostname, string(CollectionWhitelist)))
|
||||
CollectionService = common.CollectionName(fmt.Sprintf("%s-%s", hostname, string(CollectionService)))
|
||||
CollectionBlock = common.CollectionName(fmt.Sprintf("%s-%s", hostname, string(CollectionBlock)))
|
||||
CollectionPlatformLoginToken = common.CollectionName(fmt.Sprintf("%s-%s", hostname, string(CollectionPlatformLoginToken)))
|
||||
CollectionUserToken = common.CollectionName(fmt.Sprintf("%s-%s", hostname, string(CollectionUserToken)))
|
||||
CollectionGamepotUserInfo = common.CollectionName(fmt.Sprintf("%s-%s", hostname, string(CollectionGamepotUserInfo)))
|
||||
CollectionFirebaseUserInfo = common.CollectionName(fmt.Sprintf("%s-%s", hostname, string(CollectionFirebaseUserInfo)))
|
||||
}
|
||||
}
|
||||
|
||||
func (ac *mongoAuthCell) ToAuthinfo() *common.Authinfo {
|
||||
if ac.src == nil {
|
||||
logger.Error("mongoAuthCell ToAuthinfo failed. ac.src is nil")
|
||||
}
|
||||
return ac.src
|
||||
}
|
||||
|
||||
func (ac *mongoAuthCell) ToBytes() []byte {
|
||||
bt, _ := json.Marshal(ac.src)
|
||||
return bt
|
||||
}
|
||||
|
||||
func makeAuthCollection(mongoClient common.MongoClient, sessionTTL time.Duration) *common.AuthCollection {
|
||||
authcoll := common.MakeAuthCollection(sessionTTL)
|
||||
authcoll.SessionRemoved = func(sk string) {
|
||||
skid, _ := primitive.ObjectIDFromHex(sk)
|
||||
mongoClient.Delete(CollectionAuth, bson.M{
|
||||
"sk": skid,
|
||||
})
|
||||
}
|
||||
authcoll.QuerySession = func(sk string, token string) common.AuthinfoCell {
|
||||
skid, _ := primitive.ObjectIDFromHex(sk)
|
||||
var outcell mongoAuthCell
|
||||
err := mongoClient.FindOneAs(CollectionAuth, bson.M{
|
||||
"sk": skid,
|
||||
}, &outcell.src, options.FindOne().SetHint("skonly"))
|
||||
|
||||
if err != nil {
|
||||
logger.Error("QuerySession failed :", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
if outcell.src == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &outcell
|
||||
}
|
||||
|
||||
return authcoll
|
||||
}
|
||||
|
||||
type apiTokenMap struct {
|
||||
sync.Mutex
|
||||
tokenToService map[string]string
|
||||
}
|
||||
|
||||
func (tm *apiTokenMap) add(token string, serviceCode string) {
|
||||
tm.Lock()
|
||||
defer tm.Unlock()
|
||||
|
||||
tm.tokenToService[token] = serviceCode
|
||||
}
|
||||
|
||||
func (tm *apiTokenMap) remove(token string) {
|
||||
tm.Lock()
|
||||
defer tm.Unlock()
|
||||
|
||||
delete(tm.tokenToService, token)
|
||||
}
|
||||
|
||||
func (tm *apiTokenMap) get(token string) (code string, exists bool) {
|
||||
tm.Lock()
|
||||
defer tm.Unlock()
|
||||
|
||||
code, exists = tm.tokenToService[token]
|
||||
return
|
||||
}
|
||||
|
||||
type maingateConfig struct {
|
||||
Mongo string `json:"maingate_mongodb_url"`
|
||||
SessionTTL int64 `json:"maingate_session_ttl"`
|
||||
Autologin_ttl int64 `json:"autologin_ttl"`
|
||||
RedirectBaseUrl string `json:"redirect_base_url"`
|
||||
GoogleClientId string `json:"google_client_id"`
|
||||
GoogleClientSecret string `json:"google_client_secret"`
|
||||
TwitterOAuthKey string `json:"twitter_oauth_key"`
|
||||
TwitterOAuthSecret string `json:"twitter_oauth_secret"`
|
||||
TwitterCustomerKey string `json:"twitter_customer_key"`
|
||||
TwitterCustomerSecret string `json:"twitter_customer_secret"`
|
||||
AppleCientId string `json:"apple_client_id"`
|
||||
ApplePrivateKey string `json:"apple_privatekey"`
|
||||
AppleServiceId string `json:"apple_service_id"`
|
||||
AppleTeamId string `json:"apple_team_id"`
|
||||
AppleKeyId string `json:"apple_key_id"`
|
||||
MicrosoftClientId string `json:"microsoft_client_id"`
|
||||
MicrosoftClientSecret string `json:"microsoft_client_secret"`
|
||||
GamepotProjectId string `json:"gamepot_project_id"`
|
||||
GamepotLoginCheckAPIURL string `json:"gamepot_logincheckapi_url"`
|
||||
FirebaseAdminSDKCredentialFile string `json:"firebase_admin_sdk_credentialfile"`
|
||||
}
|
||||
|
||||
type globalAdmins struct {
|
||||
Admins []string `json:"maingate_global_admins"`
|
||||
emails map[string]bool
|
||||
modtime time.Time
|
||||
}
|
||||
|
||||
func (ga *globalAdmins) parse() {
|
||||
parsed := make(map[string]bool)
|
||||
for _, admin := range ga.Admins {
|
||||
parsed[admin] = true
|
||||
}
|
||||
ga.emails = parsed
|
||||
ga.modtime = common.ConfigModTime()
|
||||
}
|
||||
|
||||
type servicelist struct {
|
||||
services unsafe.Pointer
|
||||
}
|
||||
|
||||
func (sl *servicelist) init(total []*serviceDescription) error {
|
||||
next := make(map[string]*serviceDescription)
|
||||
for _, service := range total {
|
||||
next[service.ServiceName] = service
|
||||
}
|
||||
|
||||
atomic.StorePointer(&sl.services, unsafe.Pointer(&next))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sl *servicelist) add(s *serviceDescription) {
|
||||
ptr := atomic.LoadPointer(&sl.services)
|
||||
src := (*map[string]*serviceDescription)(ptr)
|
||||
|
||||
next := map[string]*serviceDescription{}
|
||||
for k, v := range *src {
|
||||
next[k] = v
|
||||
}
|
||||
next[s.ServiceName] = s
|
||||
atomic.StorePointer(&sl.services, unsafe.Pointer(&next))
|
||||
}
|
||||
|
||||
func (sl *servicelist) get(sn any) *serviceDescription {
|
||||
ptr := atomic.LoadPointer(&sl.services)
|
||||
src := *(*map[string]*serviceDescription)(ptr)
|
||||
|
||||
switch sn := sn.(type) {
|
||||
case string:
|
||||
return src[sn]
|
||||
|
||||
case primitive.ObjectID:
|
||||
for _, desc := range src {
|
||||
if desc.Id == sn {
|
||||
return desc
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sl *servicelist) all() map[string]*serviceDescription {
|
||||
ptr := atomic.LoadPointer(&sl.services)
|
||||
src := (*map[string]*serviceDescription)(ptr)
|
||||
|
||||
next := map[string]*serviceDescription{}
|
||||
for k, v := range *src {
|
||||
next[k] = v
|
||||
}
|
||||
|
||||
return next
|
||||
}
|
||||
|
||||
func (sl *servicelist) remove(uid primitive.ObjectID) (out *serviceDescription) {
|
||||
ptr := atomic.LoadPointer(&sl.services)
|
||||
src := (*map[string]*serviceDescription)(ptr)
|
||||
|
||||
next := map[string]*serviceDescription{}
|
||||
var targetkey string
|
||||
out = nil
|
||||
for k, v := range *src {
|
||||
next[k] = v
|
||||
if v.Id == uid {
|
||||
targetkey = k
|
||||
out = v
|
||||
}
|
||||
}
|
||||
delete(next, targetkey)
|
||||
atomic.StorePointer(&sl.services, unsafe.Pointer(&next))
|
||||
return
|
||||
}
|
||||
|
||||
// Maingate :
|
||||
type Maingate struct {
|
||||
maingateConfig
|
||||
|
||||
mongoClient common.MongoClient
|
||||
|
||||
auths *common.AuthCollection
|
||||
services servicelist
|
||||
admins unsafe.Pointer
|
||||
apiTokenToService apiTokenMap
|
||||
tokenEndpoints map[string]string
|
||||
authorizationEndpoints map[string]string
|
||||
userinfoEndpoint map[string]string
|
||||
jwksUri map[string]string
|
||||
webTemplate map[string]*template.Template
|
||||
firebaseAppClient *auth.Client
|
||||
firebaseAppContext context.Context
|
||||
}
|
||||
|
||||
// New :
|
||||
func New(ctx context.Context) (*Maingate, error) {
|
||||
var config maingateConfig
|
||||
if err := common.LoadConfig(&config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var admins globalAdmins
|
||||
if err := common.LoadConfig(&admins); err == nil {
|
||||
admins.parse()
|
||||
}
|
||||
|
||||
if config.SessionTTL == 0 {
|
||||
config.SessionTTL = 3600
|
||||
}
|
||||
|
||||
mg := Maingate{
|
||||
maingateConfig: config,
|
||||
services: servicelist{},
|
||||
admins: unsafe.Pointer(&admins),
|
||||
apiTokenToService: apiTokenMap{
|
||||
tokenToService: make(map[string]string),
|
||||
},
|
||||
tokenEndpoints: make(map[string]string),
|
||||
authorizationEndpoints: make(map[string]string),
|
||||
userinfoEndpoint: make(map[string]string),
|
||||
jwksUri: make(map[string]string),
|
||||
}
|
||||
|
||||
err := mg.prepare(ctx)
|
||||
if err != nil {
|
||||
logger.Error("mg.prepare() failed :", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
opt := option.WithCredentialsFile(mg.FirebaseAdminSDKCredentialFile)
|
||||
firebaseApp, err := firebase.NewApp(context.Background(), nil, opt)
|
||||
if err != nil {
|
||||
logger.Error("firebase admin error initializing app failed :", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mg.firebaseAppContext = ctx
|
||||
mg.firebaseAppClient, err = firebaseApp.Auth(mg.firebaseAppContext)
|
||||
if err != nil {
|
||||
log.Fatalf("error getting Auth client: %v\n", err)
|
||||
}
|
||||
|
||||
return &mg, nil
|
||||
}
|
||||
|
||||
func (mg *Maingate) Destructor() {
|
||||
logger.Println("maingate.Destructor")
|
||||
mg.mongoClient.Close()
|
||||
}
|
||||
|
||||
func (mg *Maingate) discoverOpenIdConfiguration(name string, url string) error {
|
||||
// microsoft open-id configuration
|
||||
discover, err := http.Get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer discover.Body.Close()
|
||||
body, err := io.ReadAll(discover.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var endpoints map[string]any
|
||||
if err := json.Unmarshal(body, &endpoints); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if tokenEndpoint, ok := endpoints["token_endpoint"].(string); ok {
|
||||
mg.tokenEndpoints[name] = tokenEndpoint
|
||||
if !ok {
|
||||
return fmt.Errorf("token_endpoint is missing. %s %s", name, url)
|
||||
}
|
||||
}
|
||||
|
||||
if authorizationEndpoint, ok := endpoints["authorization_endpoint"].(string); ok {
|
||||
mg.authorizationEndpoints[name] = authorizationEndpoint
|
||||
if !ok {
|
||||
return fmt.Errorf("authorization_endpoint is missing. %s %s", name, url)
|
||||
}
|
||||
}
|
||||
|
||||
if userinfoEndpoint, ok := endpoints["userinfo_endpoint"].(string); ok {
|
||||
mg.userinfoEndpoint[name] = userinfoEndpoint
|
||||
if !ok {
|
||||
return fmt.Errorf("userinfo_endpoint is missing. %s %s", name, url)
|
||||
}
|
||||
}
|
||||
|
||||
if jwksUri, ok := endpoints["jwks_uri"].(string); ok {
|
||||
mg.jwksUri[name] = jwksUri
|
||||
if !ok {
|
||||
return fmt.Errorf("jwks_uri is missing. %s %s", name, url)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (mg *Maingate) prepare(context context.Context) (err error) {
|
||||
if err := mg.discoverOpenIdConfiguration(AuthPlatformMicrosoft, "https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := mg.discoverOpenIdConfiguration("google", "https://accounts.google.com/.well-known/openid-configuration"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mg.webTemplate = make(map[string]*template.Template)
|
||||
mg.webTemplate[AuthPlatformGamepot] = template.Must(template.ParseFiles("www/gamepot.html"))
|
||||
|
||||
// redis에서 env를 가져온 후에
|
||||
mg.mongoClient, err = common.NewMongoClient(context, mg.Mongo, "maingate")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = mg.mongoClient.MakeUniqueIndices(CollectionAuth, map[string]bson.D{
|
||||
"skonly": {{Key: "sk", Value: 1}},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = mg.mongoClient.MakeUniqueIndices(CollectionLink, map[string]bson.D{
|
||||
"platformuid": {{Key: "platform", Value: 1}, {Key: "uid", Value: 1}},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = mg.mongoClient.MakeUniqueIndices(CollectionLink, map[string]bson.D{
|
||||
"emailplatform": {{Key: "email", Value: 1}, {Key: "platform", Value: 1}},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = mg.mongoClient.MakeIndices(CollectionWhitelist, map[string]bson.D{
|
||||
"service": {{Key: "service", Value: 1}},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = mg.mongoClient.MakeExpireIndex(CollectionWhitelist, 10); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = mg.mongoClient.MakeExpireIndex(CollectionAuth, int32(mg.SessionTTL+300)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = mg.mongoClient.MakeUniqueIndices(CollectionBlock, map[string]bson.D{
|
||||
"codeaccid": {{Key: "code", Value: 1}, {Key: "accid", Value: 1}},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = mg.mongoClient.MakeExpireIndex(CollectionBlock, int32(3)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = mg.mongoClient.MakeUniqueIndices(CollectionPlatformLoginToken, map[string]bson.D{
|
||||
"platformauthtoken": {{Key: "platform", Value: 1}, {Key: "key", Value: 1}},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = mg.mongoClient.MakeExpireIndex(CollectionPlatformLoginToken, int32(mg.SessionTTL+300)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = mg.mongoClient.MakeUniqueIndices(CollectionUserToken, map[string]bson.D{
|
||||
"platformusertoken": {{Key: "platform", Value: 1}, {Key: "userid", Value: 1}},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = mg.mongoClient.MakeUniqueIndices(CollectionGamepotUserInfo, map[string]bson.D{
|
||||
"gamepotuserid": {{Key: "gamepotuserid", Value: 1}},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = mg.mongoClient.MakeUniqueIndices(CollectionFirebaseUserInfo, map[string]bson.D{
|
||||
"firebaseuserid": {{Key: "firebaseuserid", Value: 1}},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mg.auths = makeAuthCollection(mg.mongoClient, time.Duration(mg.SessionTTL*int64(time.Second)))
|
||||
|
||||
go watchAuthCollection(context, mg.auths, mg.mongoClient)
|
||||
go mg.watchWhitelistCollection(context)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func whitelistKey(email string) string {
|
||||
if strings.HasPrefix(email, "*@") {
|
||||
// 도메인 전체 허용
|
||||
return email[2:]
|
||||
}
|
||||
|
||||
return email
|
||||
}
|
||||
|
||||
func (mg *Maingate) RegisterHandlers(ctx context.Context, serveMux *http.ServeMux, prefix string) error {
|
||||
var allServices []*serviceDescription
|
||||
logger.Println(CollectionService)
|
||||
if err := mg.mongoClient.FindAllAs(CollectionService, bson.M{}, &allServices, options.Find().SetReturnKey(false)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, service := range allServices {
|
||||
if err := service.prepare(mg); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
logger.Println("RegisterHandlers...")
|
||||
|
||||
mg.services.init(allServices)
|
||||
for _, service := range allServices {
|
||||
if service.Closed {
|
||||
continue
|
||||
}
|
||||
|
||||
logger.Println("ServiceCode:", service.ServiceCode)
|
||||
serveMux.Handle(common.MakeHttpHandlerPattern(prefix, service.ServiceCode, "/"), service)
|
||||
}
|
||||
|
||||
serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "api", "/"), mg.api)
|
||||
serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "query", "/"), mg.query)
|
||||
|
||||
configraw, _ := json.Marshal(mg.maingateConfig)
|
||||
serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "config"), func(w http.ResponseWriter, r *http.Request) {
|
||||
apitoken := r.Header.Get("MG-X-API-TOKEN")
|
||||
if len(apitoken) == 0 {
|
||||
logger.Println("MG-X-API-TOKEN is missing")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
_, exists := mg.apiTokenToService.get(apitoken)
|
||||
if !exists {
|
||||
logger.Println("MG-X-API-TOKEN is invalid :", apitoken)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
w.Write(configraw)
|
||||
})
|
||||
|
||||
fsx := http.FileServer(http.Dir("./console"))
|
||||
serveMux.Handle(common.MakeHttpHandlerPattern(prefix, "console", "/"), http.StripPrefix("/console/", fsx))
|
||||
|
||||
serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "request_login_url", AuthPlatformGoogle), mg.platform_google_get_login_url)
|
||||
serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "authorize", AuthPlatformGoogle), mg.platform_google_authorize)
|
||||
serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "authorize_result", AuthPlatformGoogle), mg.platform_google_authorize_result)
|
||||
|
||||
serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "request_login_url", AuthPlatformMicrosoft), mg.platform_microsoft_get_login_url)
|
||||
serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "authorize", AuthPlatformMicrosoft), mg.platform_microsoft_authorize)
|
||||
serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "authorize_result", AuthPlatformMicrosoft), mg.platform_microsoft_authorize_result)
|
||||
|
||||
serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "request_login_url", AuthPlatformTwitter), mg.platform_twitter_get_login_url)
|
||||
serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "authorize", AuthPlatformTwitter), mg.platform_twitter_authorize)
|
||||
serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "authorize_result", AuthPlatformTwitter), mg.platform_twitter_authorize_result)
|
||||
|
||||
serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "request_login_url", AuthPlatformApple), mg.platform_apple_get_login_url)
|
||||
serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "authorize", AuthPlatformApple), mg.platform_apple_authorize)
|
||||
serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "authorize_result", AuthPlatformApple), mg.platform_apple_authorize_result)
|
||||
|
||||
serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "request_login_url", AuthPlatformGamepot), mg.platform_gamepot_get_login_url)
|
||||
serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "authorize", AuthPlatformGamepot), mg.platform_gamepot_authorize)
|
||||
serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "authorize_sdk", AuthPlatformGamepot), mg.platform_gamepot_authorize_sdk)
|
||||
|
||||
serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "request_login_url", AuthPlatformFirebaseAuth), mg.platform_firebaseauth_get_login_url)
|
||||
serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "authorize_sdk", AuthPlatformFirebaseAuth), mg.platform_firebaseauth_authorize_sdk)
|
||||
|
||||
go mg.watchServiceCollection(ctx, serveMux, prefix)
|
||||
|
||||
// fsx := http.FileServer(http.Dir("console"))
|
||||
// serveMux.Handle("/console/", http.StripPrefix("/console/", fsx))
|
||||
// logger.Println("console file server open")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mg *Maingate) query(w http.ResponseWriter, r *http.Request) {
|
||||
defer func() {
|
||||
s := recover()
|
||||
if s != nil {
|
||||
logger.Error(s)
|
||||
}
|
||||
}()
|
||||
|
||||
defer func() {
|
||||
io.Copy(io.Discard, r.Body)
|
||||
r.Body.Close()
|
||||
}()
|
||||
|
||||
queryvals := r.URL.Query()
|
||||
sk := queryvals.Get("sk")
|
||||
|
||||
if len(sk) == 0 {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
info := mg.auths.Find(sk)
|
||||
if info == nil {
|
||||
logger.Println("session key is not valid :", sk)
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
apitoken := r.Header.Get("MG-X-API-TOKEN")
|
||||
if len(apitoken) == 0 {
|
||||
logger.Println("MG-X-API-TOKEN is missing")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
servicecode, exists := mg.apiTokenToService.get(apitoken)
|
||||
if !exists {
|
||||
logger.Println("MG-X-API-TOKEN is invalid :", apitoken)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if info.ServiceCode != servicecode {
|
||||
logger.Println("session is not for this service :", info.ServiceCode, servicecode)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
bt, _ := json.Marshal(info)
|
||||
w.Write(bt)
|
||||
}
|
||||
|
||||
func (mg *Maingate) GeneratePlatformLoginNonceKey() string {
|
||||
const allowed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
b := make([]byte, 52)
|
||||
for i := range b {
|
||||
b[i] = allowed[rand.Intn(len(allowed))]
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func (mg *Maingate) GetUserBrowserInfo(r *http.Request) (string, error) {
|
||||
//=========== 브라우저 검증쪽은 앞으로 빼자
|
||||
host, _, err := net.SplitHostPort(r.RemoteAddr)
|
||||
if err != nil {
|
||||
logger.Error("Error: RemoteAddr Split Error :", err)
|
||||
}
|
||||
|
||||
cookie, err := r.Cookie("ActionSquareSessionExtraInfo")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
requestinfo := fmt.Sprintf("%s_%s", cookie.Value, host)
|
||||
return requestinfo, nil
|
||||
}
|
||||
|
||||
func (mg *Maingate) setUserToken(info usertokeninfo) error {
|
||||
|
||||
mg.mongoClient.Delete(CollectionUserToken, bson.M{
|
||||
"platform": info.platform,
|
||||
"userid": info.userid,
|
||||
})
|
||||
_, _, err := mg.mongoClient.Update(CollectionUserToken, bson.M{
|
||||
"_id": primitive.NewObjectID(),
|
||||
}, bson.M{
|
||||
"$setOnInsert": bson.M{
|
||||
"platform": info.platform,
|
||||
"userid": info.userid,
|
||||
"token": info.token,
|
||||
"secret": info.secret,
|
||||
"brinfo": info.brinfo,
|
||||
"lastupdate": time.Now().Unix(),
|
||||
"accesstoken": info.accesstoken,
|
||||
"accesstoken_expire_time": info.accesstoken_expire_time,
|
||||
},
|
||||
}, options.Update().SetUpsert(true))
|
||||
return err
|
||||
}
|
||||
|
||||
func (mg *Maingate) getUserTokenWithCheck(platform string, userid string, brinfo string) (usertokeninfo, error) {
|
||||
|
||||
var info usertokeninfo
|
||||
|
||||
found, err := mg.mongoClient.FindOne(CollectionUserToken, bson.M{
|
||||
"platform": platform,
|
||||
"userid": userid,
|
||||
"brinfo": brinfo,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return info, err
|
||||
}
|
||||
if found == nil {
|
||||
return info, errors.New("user token not found: " + platform + " / " + userid + " / " + brinfo)
|
||||
}
|
||||
|
||||
updatetime, ok := found["lastupdate"].(int64)
|
||||
|
||||
if !ok || time.Now().Unix()-updatetime < mg.maingateConfig.Autologin_ttl {
|
||||
info.platform = platform
|
||||
info.userid = userid
|
||||
info.brinfo = brinfo
|
||||
|
||||
token := found["token"]
|
||||
if token != nil {
|
||||
info.token = token.(string)
|
||||
}
|
||||
|
||||
secret := found["secret"]
|
||||
if secret != nil {
|
||||
info.secret = secret.(string)
|
||||
}
|
||||
|
||||
accesstoken := found["accesstoken"]
|
||||
if accesstoken != nil {
|
||||
info.accesstoken = accesstoken.(string)
|
||||
}
|
||||
|
||||
accesstoken_expire_time := found["accesstoken_expire_time"]
|
||||
if accesstoken_expire_time != nil {
|
||||
info.accesstoken_expire_time = accesstoken_expire_time.(int64)
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
return info, errors.New("session is expired")
|
||||
}
|
||||
|
||||
func (mg *Maingate) updateUserinfo(info usertokeninfo) (bool, string, string) {
|
||||
var success bool
|
||||
var userid, email string
|
||||
success = false
|
||||
userid = ""
|
||||
email = ""
|
||||
|
||||
switch info.platform {
|
||||
case AuthPlatformApple:
|
||||
success, userid, email = mg.platform_apple_getuserinfo(info.token)
|
||||
case AuthPlatformTwitter:
|
||||
success, userid, email = mg.platform_twitter_getuserinfo(info.token, info.secret)
|
||||
case AuthPlatformMicrosoft:
|
||||
success, userid, email = mg.platform_microsoft_getuserinfo(info)
|
||||
case AuthPlatformGoogle:
|
||||
success, userid, email = mg.platform_google_getuserinfo(info)
|
||||
case AuthPlatformGamepot:
|
||||
success, userid, email = mg.platform_gamepot_getuserinfo(info)
|
||||
case AuthPlatformFirebaseAuth:
|
||||
success, userid, email = mg.platform_firebase_getuserinfo(info)
|
||||
}
|
||||
|
||||
if !success {
|
||||
return false, "", ""
|
||||
}
|
||||
|
||||
if info.userid != userid {
|
||||
logger.Error("userinfo / id is not match. :", info.userid, " / ", userid)
|
||||
return false, "", ""
|
||||
}
|
||||
|
||||
mg.setUserToken(info)
|
||||
|
||||
return success, userid, email
|
||||
|
||||
}
|
||||
|
||||
func (mg *Maingate) getProviderInfo(platform string, uid string) (error, string, string) {
|
||||
provider := ""
|
||||
providerid := ""
|
||||
|
||||
switch platform {
|
||||
case AuthPlatformFirebaseAuth: // Fireabase 통해서 로그인 하는 경우, 원래 제공하는 Google 혹은 Apple쪽 ID와 일치 시킨다
|
||||
found, err := mg.mongoClient.FindOne(CollectionFirebaseUserInfo, bson.M{
|
||||
"firebaseuserid": uid,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err, "", ""
|
||||
}
|
||||
if found == nil {
|
||||
return errors.New("firebase info not found: " + uid), "", ""
|
||||
}
|
||||
provider = found["firebaseprovider"].(string)
|
||||
providerid = found["firebaseproviderId"].(string)
|
||||
if provider == "" || providerid == "" {
|
||||
return errors.New("getProviderInfo - firebase info not found: " + provider + " / " + providerid), "", ""
|
||||
}
|
||||
default:
|
||||
provider = platform
|
||||
providerid = uid
|
||||
}
|
||||
|
||||
if provider == "" || providerid == "" {
|
||||
return errors.New("getProviderInfo - provider info not found: " + provider + " / " + providerid), "", ""
|
||||
}
|
||||
|
||||
return nil, provider, providerid
|
||||
|
||||
}
|
||||
|
||||
var errKeyNotFound = errors.New("key not found")
|
||||
|
||||
func downloadSigningCert(keyurl string, kid string, alg string) (rsa.PublicKey, error) {
|
||||
|
||||
var publicKey rsa.PublicKey
|
||||
|
||||
resp, err := http.Get(keyurl) // GET 호출
|
||||
if err != nil {
|
||||
return rsa.PublicKey{}, err
|
||||
}
|
||||
defer func() {
|
||||
io.Copy(io.Discard, resp.Body)
|
||||
resp.Body.Close()
|
||||
}()
|
||||
|
||||
// 결과 출력
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return rsa.PublicKey{}, err
|
||||
}
|
||||
|
||||
var parseddata map[string]interface{}
|
||||
err = json.Unmarshal([]byte(data), &parseddata)
|
||||
if err != nil {
|
||||
return rsa.PublicKey{}, err
|
||||
}
|
||||
|
||||
mapKeys, keysok := parseddata["keys"]
|
||||
if !keysok {
|
||||
return rsa.PublicKey{}, errKeyNotFound
|
||||
}
|
||||
|
||||
keylist, keyok := mapKeys.([]interface{})
|
||||
if !keyok {
|
||||
return rsa.PublicKey{}, errKeyNotFound
|
||||
}
|
||||
|
||||
for _, key := range keylist {
|
||||
alg_, alg_ok := key.(map[string]interface{})["alg"]
|
||||
kty_, kty_ok := key.(map[string]interface{})["kty"]
|
||||
|
||||
if !alg_ok && kty_ok {
|
||||
alg_ = kty_
|
||||
alg_ok = kty_ok
|
||||
}
|
||||
|
||||
kid_, kid_ok := key.(map[string]interface{})["kid"]
|
||||
nkey_, nkey_ok := key.(map[string]interface{})["n"]
|
||||
ekey_, ekey_ok := key.(map[string]interface{})["e"]
|
||||
if alg_ok && kid_ok && nkey_ok && ekey_ok {
|
||||
|
||||
if (alg_.(string) == alg || (alg == "RS256" && alg_ == "RSA")) && kid_.(string) == kid {
|
||||
decode_nkey_, err := base64.RawURLEncoding.DecodeString(nkey_.(string))
|
||||
if err != nil {
|
||||
return rsa.PublicKey{}, errors.New("n key decode fail")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return rsa.PublicKey{}, errors.New("e key decode fail")
|
||||
}
|
||||
if ekey_.(string) != "AQAB" && ekey_.(string) != "AAEAAQ" {
|
||||
return rsa.PublicKey{}, errors.New("e key is not AQAB or AAEAAQ")
|
||||
}
|
||||
|
||||
publicKey = rsa.PublicKey{
|
||||
N: new(big.Int).SetBytes(decode_nkey_),
|
||||
E: 65537,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return publicKey, nil
|
||||
}
|
||||
|
||||
func JWTparseCode(keyurl string, code string) (string, string, string) {
|
||||
|
||||
parts := strings.Split(string(code), ".")
|
||||
if len(parts) != 3 {
|
||||
logger.Error("ErrCannotDecode:", len(parts))
|
||||
return "", "", ""
|
||||
}
|
||||
decoded, err := base64.RawURLEncoding.DecodeString(parts[0])
|
||||
if err != nil {
|
||||
logger.Error("ErrInvalidTokenPart:", string(decoded))
|
||||
return "", "", ""
|
||||
}
|
||||
|
||||
var parseddata map[string]string
|
||||
err = json.Unmarshal(decoded, &parseddata)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
kid, kidok := parseddata["kid"]
|
||||
alg, algok := parseddata["alg"]
|
||||
|
||||
if !kidok {
|
||||
logger.Error("ErrorHeader_kid_not_found")
|
||||
return "", "", ""
|
||||
}
|
||||
if !algok {
|
||||
logger.Error("ErrorHeader_alg_not_found")
|
||||
return "", "", ""
|
||||
}
|
||||
|
||||
publicKey, err := downloadSigningCert(keyurl, kid, alg)
|
||||
if err != nil {
|
||||
logger.Error("ErrorHeader_alg_not_found:", err)
|
||||
return "", "", ""
|
||||
}
|
||||
|
||||
token, err := jwt.Parse(code, func(token *jwt.Token) (interface{}, error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
|
||||
return nil, errors.New("Unexpected signing method:" + token.Header["alg"].(string))
|
||||
}
|
||||
|
||||
return &publicKey, nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
if err := err.(*jwt.ValidationError); err != nil {
|
||||
if err.Errors == jwt.ValidationErrorExpired {
|
||||
logger.Error("ValidationErrorExpired:", err)
|
||||
return "", "", ""
|
||||
}
|
||||
logger.Error("JWT Validation Error:", err)
|
||||
return "", "", ""
|
||||
}
|
||||
}
|
||||
|
||||
claims, ok := token.Claims.(jwt.MapClaims)
|
||||
if !ok {
|
||||
logger.Error("JWT token.Claims Fail: - MapClaims")
|
||||
return "", "", ""
|
||||
}
|
||||
|
||||
// for claim := range claims {
|
||||
// fmt.Println(claim, claims[claim])
|
||||
// }
|
||||
|
||||
nonce_, nonchk := claims["nonce"]
|
||||
nonce := ""
|
||||
if nonchk {
|
||||
nonce = nonce_.(string)
|
||||
}
|
||||
|
||||
email_, emailchk := claims["email"]
|
||||
email := ""
|
||||
if emailchk {
|
||||
email = email_.(string)
|
||||
}
|
||||
|
||||
//--- nonce 체크 필요하다.
|
||||
return claims["sub"].(string), email, nonce
|
||||
}
|
||||
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)
|
||||
}
|
||||
269
core/platformfirebaseauth.go
Normal file
269
core/platformfirebaseauth.go
Normal file
@ -0,0 +1,269 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"repositories.action2quare.com/ayo/go-ayo/logger"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
func (mg *Maingate) platform_firebaseauth_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")
|
||||
withSDK := r.URL.Query().Get("withSDK")
|
||||
|
||||
// 무조껀 SDK 있어야됨
|
||||
if withSDK != "1" {
|
||||
return
|
||||
}
|
||||
|
||||
//fmt.Println("existid =>", existid)
|
||||
if existid != "" {
|
||||
//기존 계정이 있는 경우에는 그 계정 부터 조회한다.
|
||||
info, err := mg.getUserTokenWithCheck(AuthPlatformFirebaseAuth, existid, browserinfo)
|
||||
if err == nil {
|
||||
if info.token != "" {
|
||||
params := url.Values{}
|
||||
params.Add("id", existid)
|
||||
params.Add("authtype", AuthPlatformFirebaseAuth)
|
||||
if withSDK == "1" {
|
||||
w.Write([]byte("?" + params.Encode()))
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sessionkey := mg.GeneratePlatformLoginNonceKey()
|
||||
nonce := mg.GeneratePlatformLoginNonceKey()
|
||||
|
||||
mg.mongoClient.Delete(CollectionPlatformLoginToken, bson.M{
|
||||
"platform": AuthPlatformFirebaseAuth,
|
||||
"key": sessionkey,
|
||||
})
|
||||
|
||||
_, _, err = mg.mongoClient.Update(CollectionPlatformLoginToken, bson.M{
|
||||
"_id": primitive.NewObjectID(),
|
||||
}, bson.M{
|
||||
"$setOnInsert": bson.M{
|
||||
"platform": AuthPlatformFirebaseAuth,
|
||||
"key": sessionkey,
|
||||
"nonce": nonce,
|
||||
"brinfo": browserinfo,
|
||||
},
|
||||
}, options.Update().SetUpsert(true))
|
||||
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
logger.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
if withSDK == "1" {
|
||||
params := url.Values{}
|
||||
params.Add("nonce", nonce)
|
||||
w.Write([]byte("?" + params.Encode()))
|
||||
}
|
||||
}
|
||||
|
||||
type FirebaseSDKAuthInfo struct {
|
||||
MemberId string `json:"id"`
|
||||
Code string `json:"code"`
|
||||
State string `json:"state"`
|
||||
Nickname string `json:"nickname"`
|
||||
Provider string `json:"provider"`
|
||||
ProviderId string `json:"providerId"`
|
||||
Email string `json:"email"`
|
||||
PhotoUrl string `json:"photourl"`
|
||||
PhoneNumber string `json:"phonenumber"`
|
||||
}
|
||||
|
||||
func (mg *Maingate) platform_firebaseauth_authorize_sdk(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
defer r.Body.Close()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
var authinfo FirebaseSDKAuthInfo
|
||||
|
||||
err = json.NewDecoder(r.Body).Decode(&authinfo)
|
||||
if err != nil {
|
||||
logger.Println("authinfo decoding fail:", err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
bSuccess, Result := mg.platform_firebaseauth_authorize_raw(w, brinfo, authinfo.Code, authinfo.State,
|
||||
cookie.Value, authinfo.MemberId, authinfo.Nickname, authinfo.Provider, authinfo.ProviderId, authinfo.Email, authinfo.PhotoUrl, authinfo.PhoneNumber)
|
||||
|
||||
if bSuccess {
|
||||
w.Write([]byte("?" + Result))
|
||||
//http.Redirect(w, r, "actionsquare://login?"+Result, http.StatusSeeOther)
|
||||
} else {
|
||||
http.Redirect(w, r, "actionsquare://error", http.StatusSeeOther)
|
||||
}
|
||||
}
|
||||
|
||||
func (mg *Maingate) platform_firebaseauth_authorize_raw(w http.ResponseWriter, brinfo, code, state, cookieSessionKey, memberId, nickname, provider, providerId, email, photourl, phonenumber string) (bool, string) {
|
||||
|
||||
found, err := mg.mongoClient.FindOne(CollectionPlatformLoginToken, bson.M{
|
||||
"platform": AuthPlatformFirebaseAuth,
|
||||
"key": cookieSessionKey,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
logger.Println("LoginFlowContext_SessionKey find key :", err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return false, ""
|
||||
}
|
||||
|
||||
if found == nil {
|
||||
logger.Println("LoginFlowContext_SessionKey not found")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return false, ""
|
||||
}
|
||||
|
||||
if cookieSessionKey != found["key"] {
|
||||
logger.Println("LoginFlowContext_SessionKey key not match")
|
||||
logger.Println(cookieSessionKey)
|
||||
logger.Println(found["key"])
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return false, ""
|
||||
}
|
||||
|
||||
if state != found["nonce"] {
|
||||
logger.Println("LoginFlowContext_SessionKey nonce not match")
|
||||
logger.Println(state)
|
||||
logger.Println(found["nonce"])
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return false, ""
|
||||
}
|
||||
|
||||
if brinfo != found["brinfo"] { //-- 로그인 시작점과 인증점의 브라우저 혹은 접속지 정보가 다르다?
|
||||
logger.Println("LoginFlowContext_SessionKey brinfo not match ")
|
||||
logger.Println(brinfo)
|
||||
logger.Println(found["brinfo"])
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return false, ""
|
||||
}
|
||||
|
||||
_, err = mg.firebaseAppClient.VerifyIDToken(mg.firebaseAppContext, code)
|
||||
if err != nil {
|
||||
log.Println("error verifying ID token:", err)
|
||||
return false, ""
|
||||
}
|
||||
|
||||
// log.Println("Verified ID token: ", token)
|
||||
// log.Println("Verified ID token: ", token.UID)
|
||||
|
||||
acceestoken_expire_time := time.Date(2999, 1, int(time.January), 0, 0, 0, 0, time.UTC).Unix()
|
||||
|
||||
if memberId != "" && provider != "" && providerId != "" {
|
||||
var info usertokeninfo
|
||||
info.platform = AuthPlatformFirebaseAuth
|
||||
info.userid = memberId
|
||||
|
||||
info.token = code
|
||||
info.brinfo = brinfo
|
||||
info.accesstoken = ""
|
||||
info.accesstoken_expire_time = acceestoken_expire_time
|
||||
mg.setUserToken(info)
|
||||
|
||||
mg.mongoClient.Delete(CollectionFirebaseUserInfo, bson.M{
|
||||
"firebaseuserid": info.userid,
|
||||
})
|
||||
|
||||
_, _, err := mg.mongoClient.Update(CollectionFirebaseUserInfo, bson.M{
|
||||
"_id": primitive.NewObjectID(),
|
||||
}, bson.M{
|
||||
"$setOnInsert": bson.M{
|
||||
"firebaseuserid": memberId,
|
||||
"firebasenickname": nickname,
|
||||
"firebaseprovider": provider,
|
||||
"firebaseproviderId": providerId,
|
||||
"firebaseemail": email,
|
||||
"firebasephotourl": photourl,
|
||||
"firebasephonenumber": phonenumber,
|
||||
"updatetime": time.Now(),
|
||||
},
|
||||
}, options.Update().SetUpsert(true))
|
||||
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
}
|
||||
|
||||
params := url.Values{}
|
||||
params.Add("id", memberId)
|
||||
params.Add("authtype", AuthPlatformFirebaseAuth)
|
||||
return true, params.Encode()
|
||||
}
|
||||
return false, ""
|
||||
}
|
||||
|
||||
func (mg *Maingate) platform_firebase_getuserinfo(info usertokeninfo) (bool, string, string) {
|
||||
|
||||
found, err := mg.mongoClient.FindOne(CollectionFirebaseUserInfo, bson.M{
|
||||
"firebaseuserid": info.userid,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
return false, "", ""
|
||||
}
|
||||
if found == nil {
|
||||
logger.Error(errors.New("firebase info not found: " + info.userid))
|
||||
return false, "", ""
|
||||
}
|
||||
|
||||
_, err = mg.firebaseAppClient.VerifyIDToken(mg.firebaseAppContext, info.token)
|
||||
if err != nil {
|
||||
log.Println("error verifying ID token:", err)
|
||||
return false, "", ""
|
||||
}
|
||||
|
||||
tempEmail := found["firebaseemail"].(string)
|
||||
|
||||
return true, info.userid, tempEmail
|
||||
|
||||
}
|
||||
344
core/platformgamepot.go
Normal file
344
core/platformgamepot.go
Normal file
@ -0,0 +1,344 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"repositories.action2quare.com/ayo/go-ayo/logger"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
type GamepotTemplate struct {
|
||||
RedirectBaseUrl string
|
||||
State string
|
||||
}
|
||||
|
||||
type Gamepot_LoginValidationResponse struct {
|
||||
Message string `json:"message"`
|
||||
Status int `json:"status"`
|
||||
}
|
||||
|
||||
func (mg *Maingate) platform_gamepot_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")
|
||||
withSDK := r.URL.Query().Get("withSDK")
|
||||
|
||||
//fmt.Println("existid =>", existid)
|
||||
if existid != "" {
|
||||
//기존 계정이 있는 경우에는 그 계정 부터 조회한다.
|
||||
info, err := mg.getUserTokenWithCheck(AuthPlatformGamepot, existid, browserinfo)
|
||||
if err == nil {
|
||||
if info.token != "" {
|
||||
params := url.Values{}
|
||||
params.Add("id", existid)
|
||||
params.Add("authtype", AuthPlatformGamepot)
|
||||
if withSDK == "1" {
|
||||
w.Write([]byte("?" + params.Encode()))
|
||||
} else {
|
||||
http.Redirect(w, r, "actionsquare://login?"+params.Encode(), http.StatusSeeOther)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sessionkey := mg.GeneratePlatformLoginNonceKey()
|
||||
nonce := mg.GeneratePlatformLoginNonceKey()
|
||||
|
||||
mg.mongoClient.Delete(CollectionPlatformLoginToken, bson.M{
|
||||
"platform": AuthPlatformGamepot,
|
||||
"key": sessionkey,
|
||||
})
|
||||
|
||||
_, _, err = mg.mongoClient.Update(CollectionPlatformLoginToken, bson.M{
|
||||
"_id": primitive.NewObjectID(),
|
||||
}, bson.M{
|
||||
"$setOnInsert": bson.M{
|
||||
"platform": AuthPlatformGamepot,
|
||||
"key": sessionkey,
|
||||
"nonce": nonce,
|
||||
"brinfo": browserinfo,
|
||||
},
|
||||
}, options.Update().SetUpsert(true))
|
||||
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
logger.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
if withSDK == "1" {
|
||||
params := url.Values{}
|
||||
params.Add("nonce", nonce)
|
||||
w.Write([]byte("?" + params.Encode()))
|
||||
} else {
|
||||
var templateVar GamepotTemplate
|
||||
templateVar.RedirectBaseUrl = mg.RedirectBaseUrl
|
||||
templateVar.State = nonce
|
||||
mg.webTemplate[AuthPlatformGamepot].Execute(w, templateVar)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (mg *Maingate) platform_gamepot_authorize(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
r.ParseForm()
|
||||
|
||||
code := r.Form.Get("code")
|
||||
state := r.Form.Get("state")
|
||||
|
||||
gamepotmemberId := r.Form.Get("id")
|
||||
gamepotnickname := r.Form.Get("nickname")
|
||||
gamepotprovider := r.Form.Get("provider")
|
||||
gamepotproviderId := r.Form.Get("providerId")
|
||||
// gamepotverify := r.Form.Get("verify")
|
||||
// gamepotagree := r.Form.Get("agree")
|
||||
|
||||
bSuccess, Result := mg.platform_gamepot_authorize_raw(w, brinfo, code, state, cookie.Value, gamepotmemberId, gamepotnickname, gamepotprovider, gamepotproviderId)
|
||||
|
||||
if bSuccess {
|
||||
http.Redirect(w, r, "actionsquare://login?"+Result, http.StatusSeeOther)
|
||||
} else {
|
||||
http.Redirect(w, r, "actionsquare://error", http.StatusSeeOther)
|
||||
}
|
||||
}
|
||||
|
||||
type GamePotSDKAuthInfo struct {
|
||||
Code string `json:"code"`
|
||||
State string `json:"state"`
|
||||
MemberId string `json:"id"`
|
||||
Nickname string `json:"nickname"`
|
||||
Provider string `json:"provider"`
|
||||
ProviderId string `json:"providerId"`
|
||||
// Verify string `json:"verify"`
|
||||
// Agree string `json:"agree"`
|
||||
}
|
||||
|
||||
func (mg *Maingate) platform_gamepot_authorize_sdk(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
defer r.Body.Close()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
var authinfo GamePotSDKAuthInfo
|
||||
|
||||
err = json.NewDecoder(r.Body).Decode(&authinfo)
|
||||
if err != nil {
|
||||
logger.Println("authinfo decoding fail:", err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
bSuccess, Result := mg.platform_gamepot_authorize_raw(w, brinfo, authinfo.Code, authinfo.State,
|
||||
cookie.Value, authinfo.MemberId, authinfo.Nickname, authinfo.Provider, authinfo.ProviderId)
|
||||
|
||||
if bSuccess {
|
||||
w.Write([]byte("?" + Result))
|
||||
//http.Redirect(w, r, "actionsquare://login?"+Result, http.StatusSeeOther)
|
||||
} else {
|
||||
http.Redirect(w, r, "actionsquare://error", http.StatusSeeOther)
|
||||
}
|
||||
}
|
||||
|
||||
func (mg *Maingate) platform_gamepot_authorize_raw(w http.ResponseWriter, brinfo, code, state, cookieSessionKey, gamepotmemberId, gamepotnickname, gamepotprovider, gamepotproviderId string) (bool, string) {
|
||||
|
||||
found, err := mg.mongoClient.FindOne(CollectionPlatformLoginToken, bson.M{
|
||||
"platform": AuthPlatformGamepot,
|
||||
"key": cookieSessionKey,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
logger.Println("LoginFlowContext_SessionKey find key :", err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return false, ""
|
||||
}
|
||||
|
||||
if found == nil {
|
||||
logger.Println("LoginFlowContext_SessionKey not found")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return false, ""
|
||||
}
|
||||
|
||||
if cookieSessionKey != found["key"] {
|
||||
logger.Println("LoginFlowContext_SessionKey key not match")
|
||||
logger.Println(cookieSessionKey)
|
||||
logger.Println(found["key"])
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return false, ""
|
||||
}
|
||||
|
||||
if state != found["nonce"] {
|
||||
logger.Println("LoginFlowContext_SessionKey nonce not match")
|
||||
logger.Println(state)
|
||||
logger.Println(found["nonce"])
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return false, ""
|
||||
}
|
||||
|
||||
if brinfo != found["brinfo"] { //-- 로그인 시작점과 인증점의 브라우저 혹은 접속지 정보가 다르다?
|
||||
logger.Println("LoginFlowContext_SessionKey brinfo not match ")
|
||||
logger.Println(brinfo)
|
||||
logger.Println(found["brinfo"])
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return false, ""
|
||||
}
|
||||
|
||||
//=================
|
||||
params := url.Values{}
|
||||
params.Add("projectId", mg.GamepotProjectId)
|
||||
params.Add("memberId", gamepotmemberId)
|
||||
params.Add("token", code)
|
||||
|
||||
var respLoginCheck Gamepot_LoginValidationResponse
|
||||
content := params.Encode()
|
||||
resp, _ := http.Post(mg.GamepotLoginCheckAPIURL, "application/json", bytes.NewBuffer([]byte(content)))
|
||||
if resp != nil {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
json.Unmarshal(body, &respLoginCheck)
|
||||
} else {
|
||||
logger.Println("gamepot logincheck fail.")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return false, ""
|
||||
}
|
||||
|
||||
// fmt.Println("==============================")
|
||||
// fmt.Println("respLoginCheck.Status:", respLoginCheck.Status)
|
||||
// fmt.Println("respLoginCheck.Message:", respLoginCheck.Message)
|
||||
// fmt.Println("==============================")
|
||||
|
||||
if respLoginCheck.Status != 0 {
|
||||
logger.Errorf("gamepot login fail:", respLoginCheck.Message)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return false, ""
|
||||
}
|
||||
|
||||
acceestoken_expire_time := time.Date(2999, 1, int(time.January), 0, 0, 0, 0, time.UTC).Unix()
|
||||
|
||||
if gamepotmemberId != "" && gamepotprovider != "" && gamepotproviderId != "" {
|
||||
var info usertokeninfo
|
||||
info.platform = AuthPlatformGamepot
|
||||
info.userid = gamepotmemberId
|
||||
//== memberid 제외하고는 모두 client로 부터 온 값이기 때문에 유효성이 확인된 값이 아니다. 하지만, 참조용으로 사용은 한다.
|
||||
// 정확한 정보는 gamepotid를 gamepot dashboard에서 조회해서 확인 할 수 밖에 없다.
|
||||
info.token = gamepotprovider + "-" + gamepotproviderId
|
||||
info.brinfo = brinfo
|
||||
info.accesstoken = ""
|
||||
info.accesstoken_expire_time = acceestoken_expire_time
|
||||
mg.setUserToken(info)
|
||||
|
||||
mg.mongoClient.Delete(CollectionGamepotUserInfo, bson.M{
|
||||
"gamepotuserid": info.userid,
|
||||
})
|
||||
|
||||
_, _, err := mg.mongoClient.Update(CollectionGamepotUserInfo, bson.M{
|
||||
"_id": primitive.NewObjectID(),
|
||||
}, bson.M{
|
||||
"$setOnInsert": bson.M{
|
||||
"gamepotuserid": gamepotmemberId,
|
||||
"gamepotnickname": gamepotnickname,
|
||||
"gamepotprovider": gamepotprovider,
|
||||
"gamepotproviderId": gamepotproviderId,
|
||||
// "gamepotverify": gamepotverify,
|
||||
// "gamepotagree": gamepotagree,
|
||||
"updatetime": time.Now(),
|
||||
},
|
||||
}, options.Update().SetUpsert(true))
|
||||
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
}
|
||||
|
||||
params := url.Values{}
|
||||
params.Add("id", gamepotmemberId)
|
||||
params.Add("authtype", AuthPlatformGamepot)
|
||||
return true, params.Encode()
|
||||
}
|
||||
return false, ""
|
||||
}
|
||||
|
||||
func (mg *Maingate) platform_gamepot_getuserinfo(info usertokeninfo) (bool, string, string) {
|
||||
|
||||
found, err := mg.mongoClient.FindOne(CollectionGamepotUserInfo, bson.M{
|
||||
"gamepotuserid": info.userid,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
return false, "", ""
|
||||
}
|
||||
if found == nil {
|
||||
logger.Error(errors.New("gamepot info not found: " + info.userid))
|
||||
return false, "", ""
|
||||
}
|
||||
|
||||
gamepotprovider := found["gamepotprovider"].(string)
|
||||
gamepotproviderId := found["gamepotproviderId"].(string)
|
||||
|
||||
if gamepotprovider+"-"+gamepotproviderId != info.token {
|
||||
logger.Println("gamepot info not match..") //-- token은 플랫폼종류+플랫폼ID로 구성했는데... 검증할 방법이 없어서 client로 부터 온값을 쓴다. 그래도 유저가 조작하지 않는 이상 일치해야 된다.
|
||||
logger.Println(info.token)
|
||||
logger.Println(gamepotprovider + "-" + gamepotproviderId)
|
||||
return false, "", ""
|
||||
}
|
||||
|
||||
tempEmail := info.userid + "@gamepot" //-- 게임팟은 email을 안줘서 일단 gamepotid기준으로 임시값을 할당한다.
|
||||
|
||||
return true, info.userid, tempEmail
|
||||
|
||||
}
|
||||
361
core/platformgoogle.go
Normal file
361
core/platformgoogle.go
Normal file
@ -0,0 +1,361 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"repositories.action2quare.com/ayo/go-ayo/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", mg.GoogleClientId)
|
||||
params.Add("response_type", "code")
|
||||
params.Add("redirect_uri", mg.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, mg.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", mg.GoogleClientId)
|
||||
params.Add("redirect_uri", mg.RedirectBaseUrl+"/authorize/"+AuthPlatformGoogle)
|
||||
params.Add("client_secret", mg.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", mg.GoogleClientId)
|
||||
params.Add("redirect_uri", mg.RedirectBaseUrl+"/authorize/"+AuthPlatformGoogle)
|
||||
params.Add("client_secret", mg.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 //?
|
||||
|
||||
}
|
||||
340
core/platformmicrosoft.go
Normal file
340
core/platformmicrosoft.go
Normal file
@ -0,0 +1,340 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"repositories.action2quare.com/ayo/go-ayo/logger"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
type Microsoft_ValidationResponse struct {
|
||||
IDToken string `json:"id_token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
AccessToken string `json:"access_token"`
|
||||
TokenType string `json:"token_type"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
}
|
||||
|
||||
type Microsoft_UserInfoResponse struct {
|
||||
Sub string `json:"sub"`
|
||||
Givenname string `json:"givenname"`
|
||||
Familyname string `json:"familyname"`
|
||||
Email string `json:"email"`
|
||||
Locale string `json:"locale"`
|
||||
}
|
||||
|
||||
func (mg *Maingate) platform_microsoft_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(AuthPlatformMicrosoft, existid, browserinfo)
|
||||
if err == nil {
|
||||
if info.token != "" {
|
||||
params := url.Values{}
|
||||
params.Add("id", existid)
|
||||
params.Add("authtype", AuthPlatformMicrosoft)
|
||||
http.Redirect(w, r, "actionsquare://login?"+params.Encode(), http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sessionkey := mg.GeneratePlatformLoginNonceKey()
|
||||
nonce := mg.GeneratePlatformLoginNonceKey()
|
||||
|
||||
mg.mongoClient.Delete(CollectionPlatformLoginToken, bson.M{
|
||||
"platform": AuthPlatformMicrosoft,
|
||||
"key": sessionkey,
|
||||
})
|
||||
|
||||
_, _, err = mg.mongoClient.Update(CollectionPlatformLoginToken, bson.M{
|
||||
"_id": primitive.NewObjectID(),
|
||||
}, bson.M{
|
||||
"$setOnInsert": bson.M{
|
||||
"platform": AuthPlatformMicrosoft,
|
||||
"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.MicrosoftClientId)
|
||||
params.Add("response_type", "code")
|
||||
params.Add("redirect_uri", mg.RedirectBaseUrl+"/authorize/"+AuthPlatformMicrosoft)
|
||||
params.Add("response_mode", "query")
|
||||
params.Add("scope", "openid offline_access https://graph.microsoft.com/mail.read")
|
||||
|
||||
params.Add("nonce", 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[AuthPlatformMicrosoft] + params.Encode())
|
||||
|
||||
//http.Redirect(w, r, "https://appleid.apple.com/auth/authorize?"+params.Encode(), http.StatusSeeOther)
|
||||
http.Redirect(w, r, mg.authorizationEndpoints[AuthPlatformMicrosoft]+"?"+params.Encode(), http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func (mg *Maingate) platform_microsoft_authorize(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
defer r.Body.Close()
|
||||
code := r.URL.Query().Get("code")
|
||||
|
||||
// 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/"+AuthPlatformMicrosoft, http.StatusSeeOther) //-- 바로 받으니까 쿠키 안와서 한번 더 Redirect 시킨다.
|
||||
}
|
||||
|
||||
func (mg *Maingate) platform_microsoft_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": AuthPlatformMicrosoft,
|
||||
})
|
||||
|
||||
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", mg.MicrosoftClientId)
|
||||
params.Add("redirect_uri", mg.RedirectBaseUrl+"/authorize/"+AuthPlatformMicrosoft)
|
||||
params.Add("code", code)
|
||||
params.Add("scope", "openid offline_access https://graph.microsoft.com/mail.read")
|
||||
params.Add("grant_type", "authorization_code")
|
||||
|
||||
params.Add("client_secret", mg.MicrosoftClientSecret)
|
||||
|
||||
var respReferesh Microsoft_ValidationResponse
|
||||
acceestoken_expire_time := time.Now().Unix()
|
||||
content := params.Encode()
|
||||
resp, _ := http.Post(mg.tokenEndpoints[AuthPlatformMicrosoft], "application/x-www-form-urlencoded", bytes.NewBuffer([]byte(content)))
|
||||
if resp != nil {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
// w.Write(body)
|
||||
//json.NewDecoder(resp.Body).Decode(respReferesh)
|
||||
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("==============================")
|
||||
|
||||
acceestoken_expire_time = acceestoken_expire_time + (int64(respReferesh.ExpiresIn) * 3 / 4) //--- 3/4 이상 지나면 업데이트 한다. ms는 보통 3600초 1시간
|
||||
|
||||
userid, _, nonce := JWTparseCode(mg.jwksUri[AuthPlatformMicrosoft], respReferesh.IDToken)
|
||||
|
||||
if nonce == "" || nonce != found["nonce"] {
|
||||
logger.Errorf("nonce not match")
|
||||
return
|
||||
}
|
||||
|
||||
if userid != "" {
|
||||
var info usertokeninfo
|
||||
info.platform = AuthPlatformMicrosoft
|
||||
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", AuthPlatformMicrosoft)
|
||||
http.Redirect(w, r, "actionsquare://login?"+params.Encode(), http.StatusSeeOther)
|
||||
} else {
|
||||
http.Redirect(w, r, "actionsquare://error", http.StatusSeeOther)
|
||||
}
|
||||
}
|
||||
|
||||
func (mg *Maingate) platform_microsoft_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", mg.MicrosoftClientId)
|
||||
params.Add("redirect_uri", mg.RedirectBaseUrl+"/authorize/"+AuthPlatformMicrosoft)
|
||||
params.Add("refresh_token", info.token)
|
||||
params.Add("scope", "openid offline_access https://graph.microsoft.com/mail.read")
|
||||
params.Add("grant_type", "refresh_token")
|
||||
|
||||
params.Add("client_secret", mg.MicrosoftClientSecret)
|
||||
|
||||
var respReferesh Microsoft_ValidationResponse
|
||||
acceestoken_expire_time := time.Now().Unix()
|
||||
|
||||
content := params.Encode()
|
||||
resp, _ := http.Post(mg.tokenEndpoints[AuthPlatformMicrosoft], "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, _, _ := JWTparseCode(mg.jwksUri[AuthPlatformMicrosoft], respReferesh.IDToken)
|
||||
if userid != "" && 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[AuthPlatformMicrosoft], 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 Microsoft_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 //?
|
||||
|
||||
}
|
||||
417
core/platformtwitter.go
Normal file
417
core/platformtwitter.go
Normal file
@ -0,0 +1,417 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"repositories.action2quare.com/ayo/go-ayo/logger"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
func (mg *Maingate) platform_twitter_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(AuthPlatformTwitter, existid, browserinfo)
|
||||
if err == nil {
|
||||
if info.token != "" {
|
||||
params := url.Values{}
|
||||
params.Add("id", existid)
|
||||
params.Add("authtype", AuthPlatformTwitter)
|
||||
http.Redirect(w, r, "actionsquare://login?"+params.Encode(), http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sessionkey := mg.GeneratePlatformLoginNonceKey()
|
||||
nonce := mg.GeneratePlatformLoginNonceKey()
|
||||
|
||||
auth_token, auth_secret := parse_TwitterOAuthToken(mg.CallTwitterAPI_WithAPPKey("https://api.twitter.com/oauth/request_token", "POST", nonce))
|
||||
|
||||
if auth_token == "" || auth_secret == "" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
} else {
|
||||
|
||||
mg.mongoClient.Delete(CollectionPlatformLoginToken, bson.M{
|
||||
"platform": AuthPlatformTwitter,
|
||||
"key": sessionkey,
|
||||
})
|
||||
|
||||
_, _, err := mg.mongoClient.Update(CollectionPlatformLoginToken, bson.M{
|
||||
"_id": primitive.NewObjectID(),
|
||||
}, bson.M{
|
||||
"$setOnInsert": bson.M{
|
||||
"platform": AuthPlatformTwitter,
|
||||
"key": sessionkey,
|
||||
"token": auth_token,
|
||||
"secret": auth_secret,
|
||||
"nonce": nonce,
|
||||
"brinfo": browserinfo,
|
||||
},
|
||||
}, options.Update().SetUpsert(true))
|
||||
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
logger.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
params := url.Values{}
|
||||
params.Add("oauth_token", auth_token)
|
||||
http.Redirect(w, r, "https://api.twitter.com/oauth/authorize?"+params.Encode(), http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func (mg *Maingate) platform_twitter_authorize(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// defer r.Body.Close()
|
||||
// body, _ := io.ReadAll(r.Body)
|
||||
// bodyString := string(body)
|
||||
|
||||
// oauth_token := r.URL.Query().Get("oauth_token")
|
||||
// oauth_verifier := r.URL.Query().Get("oauth_verifier")
|
||||
// fmt.Println("bodyString")
|
||||
// fmt.Println(bodyString)
|
||||
// fmt.Println("=======================")
|
||||
// fmt.Println("Req: %s %s", r.URL.Host, r.URL.Path)
|
||||
// fmt.Println(oauth_token, oauth_verifier)
|
||||
// fmt.Println("=======================")
|
||||
|
||||
// set cookie for storing token
|
||||
cookie := http.Cookie{
|
||||
Name: "LoginFlowContext_code",
|
||||
Value: r.URL.Query().Encode(),
|
||||
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/"+AuthPlatformTwitter, http.StatusSeeOther) //-- 바로 받으니까 쿠키 안와서 한번 더 Redirect 시킨다.
|
||||
}
|
||||
|
||||
func (mg *Maingate) platform_twitter_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": AuthPlatformTwitter,
|
||||
})
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
urlvalue, err := url.ParseQuery(code)
|
||||
if err != nil {
|
||||
logger.Println("Token parse error")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
//===
|
||||
|
||||
oauth_token := urlvalue.Get("oauth_token")
|
||||
oauth_verifier := urlvalue.Get("oauth_verifier")
|
||||
if oauth_token == "" || oauth_verifier == "" {
|
||||
w.WriteHeader(400)
|
||||
return
|
||||
}
|
||||
|
||||
userid, token, secret := getTwitterAccessToken("https://api.twitter.com/oauth/access_token?oauth_token=" + oauth_token + "&oauth_verifier=" + oauth_verifier)
|
||||
|
||||
if userid != "" && token != "" && secret != "" {
|
||||
|
||||
var info usertokeninfo
|
||||
info.platform = AuthPlatformTwitter
|
||||
info.userid = userid
|
||||
info.token = token
|
||||
info.secret = secret
|
||||
info.brinfo = brinfo
|
||||
|
||||
mg.setUserToken(info)
|
||||
params := url.Values{}
|
||||
params.Add("id", userid)
|
||||
params.Add("authtype", AuthPlatformTwitter)
|
||||
http.Redirect(w, r, "actionsquare://login?"+params.Encode(), http.StatusSeeOther)
|
||||
} else {
|
||||
http.Redirect(w, r, "actionsquare://error", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (mg *Maingate) platform_twitter_getuserinfo(token, secret string) (bool, string, string) {
|
||||
|
||||
result := mg.CallTwitterAPI("https://api.twitter.com/2/users/me", "GET", token, secret, mg.GeneratePlatformLoginNonceKey())
|
||||
|
||||
var TwitterUserInfo struct {
|
||||
Data struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Username string `json:"username"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
err := json.Unmarshal([]byte(result), &TwitterUserInfo)
|
||||
if err != nil {
|
||||
logger.Error("twitter userinfo api unmarshal fail :", result)
|
||||
return false, "", ""
|
||||
}
|
||||
|
||||
// fmt.Println("=====================")
|
||||
// fmt.Println(result)
|
||||
// fmt.Println(TwitterUserInfo.Data.Id)
|
||||
// fmt.Println(TwitterUserInfo.Data.Name)
|
||||
// fmt.Println(TwitterUserInfo.Data.Username)
|
||||
// fmt.Println("=====================")
|
||||
|
||||
return true, TwitterUserInfo.Data.Id, ""
|
||||
}
|
||||
|
||||
func (mg *Maingate) CallTwitterAPI_WithAPPKey(requesturl, method, nonce string) string {
|
||||
return mg.CallTwitterAPI(requesturl, method, mg.TwitterOAuthKey, mg.TwitterOAuthSecret, nonce)
|
||||
}
|
||||
|
||||
func (mg *Maingate) CallTwitterAPI(requesturl, method, oauth_token, oauth_secret, nonce string) string {
|
||||
vals := url.Values{}
|
||||
|
||||
if method == "GET" {
|
||||
splited := strings.Split(requesturl, "?")
|
||||
if len(splited) > 1 {
|
||||
parameter := splited[1]
|
||||
args := strings.Split(parameter, "&")
|
||||
for _, arg := range args {
|
||||
tempsplited := strings.Split(arg, "=")
|
||||
if len(tempsplited) == 2 {
|
||||
vals.Add(tempsplited[0], tempsplited[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//vals.Add("oauth_callback", "actionclient://callback")
|
||||
//vals.Add("oauth_callback", "http://127.0.0.1:7770/auth")
|
||||
|
||||
vals.Add("oauth_callback", mg.RedirectBaseUrl+"/authorize/"+AuthPlatformTwitter)
|
||||
vals.Add("oauth_consumer_key", mg.TwitterCustomerKey)
|
||||
vals.Add("oauth_token", oauth_token)
|
||||
vals.Add("oauth_signature_method", "HMAC-SHA1")
|
||||
vals.Add("oauth_timestamp", strconv.Itoa(int(time.Now().Unix())))
|
||||
vals.Add("oauth_nonce", nonce)
|
||||
vals.Add("oauth_version", "1.0")
|
||||
|
||||
parameterString := strings.Replace(vals.Encode(), "+", "%20", -1)
|
||||
signatureBase := strings.ToUpper(method) + "&" + url.QueryEscape(strings.Split(requesturl, "?")[0]) + "&" + url.QueryEscape(parameterString)
|
||||
signingKey := url.QueryEscape(mg.TwitterCustomerSecret) + "&" + url.QueryEscape(oauth_secret)
|
||||
signature := calculateTwitterSignature(signatureBase, signingKey)
|
||||
|
||||
headerString := "OAuth oauth_callback=\"" + url.QueryEscape(vals.Get("oauth_callback")) + "\", oauth_consumer_key=\"" + url.QueryEscape(vals.Get("oauth_consumer_key")) + "\", oauth_nonce=\"" + url.QueryEscape(vals.Get("oauth_nonce")) +
|
||||
"\", oauth_signature=\"" + url.QueryEscape(signature) + "\", oauth_signature_method=\"" + url.QueryEscape(vals.Get("oauth_signature_method")) +
|
||||
"\", oauth_timestamp=\"" + url.QueryEscape(vals.Get("oauth_timestamp")) + "\", oauth_token=\"" + url.QueryEscape(vals.Get("oauth_token")) +
|
||||
"\", oauth_version=\"" + url.QueryEscape(vals.Get("oauth_version")) + "\""
|
||||
|
||||
client := &http.Client{}
|
||||
|
||||
req, err := http.NewRequest(method, requesturl, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.Header.Add("Content-Length", "0")
|
||||
req.Header.Add("Accept-Encoding", "application/json")
|
||||
req.Header.Add("Host", "api.twitter.com")
|
||||
req.Header.Add("Accept", "*/*")
|
||||
req.Header.Add("Authorization", headerString)
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
strBody := ""
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err == nil {
|
||||
strBody = string(respBody)
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
logger.Error("Error: ", resp.StatusCode, err, strBody)
|
||||
return "error"
|
||||
}
|
||||
|
||||
return strBody
|
||||
}
|
||||
|
||||
func getTwitterAccessToken(requesturl string) (string, string, string) {
|
||||
|
||||
client := &http.Client{}
|
||||
|
||||
req, err := http.NewRequest("POST", requesturl, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.Header.Add("Content-Length", "0")
|
||||
req.Header.Add("Accept-Encoding", "application/json")
|
||||
req.Header.Add("Host", "api.twitter.com")
|
||||
req.Header.Add("Accept", "*/*")
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
return "", "", ""
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
strBody := ""
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err == nil {
|
||||
strBody = string(respBody)
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
logger.Error("Error: ", resp.StatusCode, err, strBody)
|
||||
return "", "", ""
|
||||
}
|
||||
|
||||
params := strings.Split(strBody, "&")
|
||||
oauth_token := ""
|
||||
oauth_secret := ""
|
||||
user_id := ""
|
||||
//screen_name := ""
|
||||
for _, param := range params {
|
||||
parsedStr := strings.Split(param, "=")
|
||||
if len(parsedStr) == 2 {
|
||||
if parsedStr[0] == "oauth_token" {
|
||||
oauth_token = parsedStr[1]
|
||||
}
|
||||
if parsedStr[0] == "oauth_token_secret" {
|
||||
oauth_secret = parsedStr[1]
|
||||
}
|
||||
if parsedStr[0] == "user_id" {
|
||||
user_id = parsedStr[1]
|
||||
}
|
||||
// if parsedStr[0] == "screen_name" {
|
||||
// screen_name = parsedStr[1]
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
// ret := ""
|
||||
|
||||
// if oauth_token != "" && oauth_secret != "" && user_id != "" && screen_name != "" {
|
||||
// ret = "{ \"AuthToken\": \"" + oauth_token + "\", \"UserId\": \"" + user_id + "\", \"Screen_name\": \"" + screen_name + "\" }"
|
||||
// }
|
||||
// return ret, user_id, oauth_token, oauth_secret
|
||||
return user_id, oauth_token, oauth_secret
|
||||
}
|
||||
|
||||
func calculateTwitterSignature(base, key string) string {
|
||||
hash := hmac.New(sha1.New, []byte(key))
|
||||
hash.Write([]byte(base))
|
||||
signature := hash.Sum(nil)
|
||||
return base64.StdEncoding.EncodeToString(signature)
|
||||
}
|
||||
|
||||
func parse_TwitterOAuthToken(strBody string) (string, string) {
|
||||
params := strings.Split(strBody, "&")
|
||||
oauth_token := ""
|
||||
oauth_secret := ""
|
||||
for _, param := range params {
|
||||
parsedStr := strings.Split(param, "=")
|
||||
|
||||
if len(parsedStr) == 2 {
|
||||
if parsedStr[0] == "oauth_token" {
|
||||
oauth_token = parsedStr[1]
|
||||
}
|
||||
if parsedStr[0] == "oauth_token_secret" {
|
||||
oauth_secret = parsedStr[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return oauth_token, oauth_secret
|
||||
}
|
||||
612
core/service.go
Normal file
612
core/service.go
Normal file
@ -0,0 +1,612 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"repositories.action2quare.com/ayo/go-ayo/logger"
|
||||
|
||||
"repositories.action2quare.com/ayo/go-ayo/common"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
type blockinfo struct {
|
||||
Start primitive.DateTime
|
||||
End primitive.DateTime `bson:"_ts"`
|
||||
Reason string
|
||||
}
|
||||
|
||||
type whitelistmember struct {
|
||||
Service string
|
||||
Email string
|
||||
Platform string
|
||||
Desc string
|
||||
Expired primitive.DateTime `bson:"_ts,omitempty" json:"_ts,omitempty"`
|
||||
}
|
||||
|
||||
type whitelist struct {
|
||||
emailptr unsafe.Pointer
|
||||
working int32
|
||||
}
|
||||
|
||||
type usertokeninfo struct {
|
||||
platform string
|
||||
userid string
|
||||
token string //refreshtoken
|
||||
secret string
|
||||
brinfo string
|
||||
accesstoken string // microsoft only
|
||||
accesstoken_expire_time int64 // microsoft only
|
||||
}
|
||||
|
||||
func (wl *whitelist) init(total []whitelistmember) {
|
||||
next := make(map[string]*whitelistmember)
|
||||
for _, member := range total {
|
||||
next[whitelistKey(member.Email)] = &member
|
||||
}
|
||||
|
||||
atomic.StorePointer(&wl.emailptr, unsafe.Pointer(&next))
|
||||
atomic.StoreInt32(&wl.working, 1)
|
||||
}
|
||||
|
||||
func (wl *whitelist) add(m *whitelistmember) {
|
||||
ptr := atomic.LoadPointer(&wl.emailptr)
|
||||
src := (*map[string]*whitelistmember)(ptr)
|
||||
|
||||
next := map[string]*whitelistmember{}
|
||||
for k, v := range *src {
|
||||
next[k] = v
|
||||
}
|
||||
next[whitelistKey(m.Email)] = m
|
||||
atomic.StorePointer(&wl.emailptr, unsafe.Pointer(&next))
|
||||
}
|
||||
|
||||
func (wl *whitelist) remove(email string) {
|
||||
ptr := atomic.LoadPointer(&wl.emailptr)
|
||||
src := (*map[string]*whitelistmember)(ptr)
|
||||
|
||||
next := make(map[string]*whitelistmember)
|
||||
for k, v := range *src {
|
||||
next[k] = v
|
||||
}
|
||||
delete(next, whitelistKey(email))
|
||||
atomic.StorePointer(&wl.emailptr, unsafe.Pointer(&next))
|
||||
}
|
||||
|
||||
func (wl *whitelist) isMember(email string, platform string) bool {
|
||||
if atomic.LoadInt32(&wl.working) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
ptr := atomic.LoadPointer(&wl.emailptr)
|
||||
src := *(*map[string]*whitelistmember)(ptr)
|
||||
|
||||
if member, exists := src[whitelistKey(email)]; exists {
|
||||
return member.Platform == platform
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type serviceDescription struct {
|
||||
// sync.Mutex
|
||||
Id primitive.ObjectID `bson:"_id"`
|
||||
ServiceName string `bson:"service"`
|
||||
Divisions map[string]any `bson:"divisions"`
|
||||
ServiceCode string `bson:"code"`
|
||||
UseWhitelist bool `bson:"use_whitelist"`
|
||||
Closed bool `bson:"closed"`
|
||||
ServerApiTokens []primitive.ObjectID `bson:"api_tokens"`
|
||||
ApiUsers map[string][]string `bson:"api_users"`
|
||||
|
||||
auths *common.AuthCollection
|
||||
wl whitelist
|
||||
mongoClient common.MongoClient
|
||||
sessionTTL time.Duration
|
||||
closed int32
|
||||
serviceCodeBytes []byte
|
||||
getUserBrowserInfo func(r *http.Request) (string, error)
|
||||
getUserTokenWithCheck func(platform string, userid string, brinfo string) (usertokeninfo, error)
|
||||
updateUserinfo func(info usertokeninfo) (bool, string, string)
|
||||
getProviderInfo func(platform string, uid string) (error, string, string)
|
||||
|
||||
apiUsers unsafe.Pointer
|
||||
divisionsSerialized unsafe.Pointer
|
||||
serviceSerialized unsafe.Pointer
|
||||
}
|
||||
|
||||
func (sh *serviceDescription) readProfile(authtype string, id string, binfo string) (email string, err error) {
|
||||
defer func() {
|
||||
s := recover()
|
||||
if s != nil {
|
||||
logger.Error("readProfile failed :", authtype, id, s)
|
||||
if errt, ok := s.(error); ok {
|
||||
err = errt
|
||||
} else {
|
||||
err = errors.New(fmt.Sprint(s))
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
userinfo, err := sh.getUserTokenWithCheck(authtype, id, binfo)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if userinfo.token == "" {
|
||||
return "", errors.New("refreshtoken token not found")
|
||||
}
|
||||
|
||||
//-- 토큰으로 모두 확인이 끝났으면 갱신한다.
|
||||
ok, _, email := sh.updateUserinfo(userinfo)
|
||||
|
||||
if !ok {
|
||||
return "", errors.New("updateUserinfo failed")
|
||||
}
|
||||
|
||||
return email, nil
|
||||
}
|
||||
|
||||
func (sh *serviceDescription) prepare(mg *Maingate) error {
|
||||
div := sh.Divisions
|
||||
if len(sh.ServiceCode) == 0 {
|
||||
sh.ServiceCode = hex.EncodeToString(sh.Id[6:])
|
||||
}
|
||||
|
||||
divmarshaled, _ := json.Marshal(div)
|
||||
devstr := string(divmarshaled)
|
||||
sh.divisionsSerialized = unsafe.Pointer(&devstr)
|
||||
|
||||
sh.mongoClient = mg.mongoClient
|
||||
sh.auths = mg.auths
|
||||
sh.sessionTTL = time.Duration(mg.SessionTTL * int64(time.Second))
|
||||
sh.wl = whitelist{}
|
||||
sh.serviceCodeBytes, _ = hex.DecodeString(sh.ServiceCode)
|
||||
sh.getUserBrowserInfo = mg.GetUserBrowserInfo
|
||||
sh.getUserTokenWithCheck = mg.getUserTokenWithCheck
|
||||
sh.updateUserinfo = mg.updateUserinfo
|
||||
sh.getProviderInfo = mg.getProviderInfo
|
||||
|
||||
if sh.Closed {
|
||||
sh.closed = 1
|
||||
} else {
|
||||
sh.closed = 0
|
||||
}
|
||||
|
||||
if sh.UseWhitelist {
|
||||
var whites []whitelistmember
|
||||
if err := mg.mongoClient.FindAllAs(CollectionWhitelist, bson.M{
|
||||
"$or": []bson.M{{"service": sh.ServiceName}, {"service": sh.ServiceCode}},
|
||||
}, &whites, options.Find().SetReturnKey(false)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sh.wl.init(whites)
|
||||
} else {
|
||||
sh.wl.working = 0
|
||||
}
|
||||
|
||||
if len(sh.ApiUsers) == 0 {
|
||||
sh.ApiUsers = map[string][]string{
|
||||
"service": {},
|
||||
"whitelist": {},
|
||||
"account": {},
|
||||
}
|
||||
}
|
||||
parsedUsers := make(map[string]map[string]bool)
|
||||
for cat, users := range sh.ApiUsers {
|
||||
catusers := make(map[string]bool)
|
||||
for _, user := range users {
|
||||
catusers[user] = true
|
||||
}
|
||||
parsedUsers[cat] = catusers
|
||||
}
|
||||
|
||||
sh.apiUsers = unsafe.Pointer(&parsedUsers)
|
||||
for _, keyid := range sh.ServerApiTokens {
|
||||
mg.apiTokenToService.add(keyid.Hex(), sh.ServiceCode)
|
||||
}
|
||||
|
||||
bt, _ := json.Marshal(sh)
|
||||
atomic.StorePointer(&sh.serviceSerialized, unsafe.Pointer(&bt))
|
||||
|
||||
logger.Println("service is ready :", sh.ServiceName, sh.ServiceCode, sh.UseWhitelist, string(divmarshaled))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sh *serviceDescription) link(w http.ResponseWriter, r *http.Request) {
|
||||
defer func() {
|
||||
s := recover()
|
||||
if s != nil {
|
||||
logger.Error(s)
|
||||
}
|
||||
}()
|
||||
|
||||
if r.Method != "GET" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
queryvals := r.URL.Query()
|
||||
//oldToken := queryvals.Get("otoken")
|
||||
oldType := queryvals.Get("otype")
|
||||
oldId := queryvals.Get("oid")
|
||||
sk := queryvals.Get("sk")
|
||||
//newToken := queryvals.Get("ntoken")
|
||||
newType := queryvals.Get("ntype")
|
||||
newId := queryvals.Get("nid")
|
||||
|
||||
oldAuth := sh.auths.Find(sk)
|
||||
if oldAuth == nil {
|
||||
// 잘못된 세션
|
||||
logger.Println("link failed. session key is not valid :", sk)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// fmt.Println("=================")
|
||||
// fmt.Println(oldType)
|
||||
// fmt.Println(oldId)
|
||||
// fmt.Println("=================")
|
||||
// fmt.Println(newType)
|
||||
// fmt.Println(newId)
|
||||
// fmt.Println("=================")
|
||||
// fmt.Println(oldAuth.Platform)
|
||||
// fmt.Println(oldAuth.Uid)
|
||||
// fmt.Println("=================")
|
||||
|
||||
//if oldAuth.Token != oldToken || oldAuth.Uid != oldId || oldAuth.Platform != oldType {
|
||||
if oldAuth.Uid != oldId || oldAuth.Platform != oldType {
|
||||
logger.Println("link failed. session key is not correct :", *oldAuth, queryvals)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
bfinfo, err := sh.getUserBrowserInfo(r)
|
||||
if err != nil {
|
||||
logger.Error("getUserBrowserInfo failed :", err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = sh.readProfile(oldType, oldId, bfinfo)
|
||||
if err != nil {
|
||||
logger.Error("readProfile(old) failed :", err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
email, err := sh.readProfile(newType, newId, bfinfo)
|
||||
if err != nil {
|
||||
logger.Error("readProfile(new) failed :", err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// if len(email) == 0 {
|
||||
// logger.Println("link failed. email is missing :", r.URL.Query())
|
||||
// w.WriteHeader(http.StatusBadRequest)
|
||||
// return
|
||||
// }
|
||||
|
||||
if !sh.wl.isMember(email, newType) {
|
||||
logger.Println("link failed. not whitelist member :", r.URL.Query(), email)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
err, newType, newId = sh.getProviderInfo(newType, newId)
|
||||
if err != nil {
|
||||
logger.Error("getProviderInfo failed :", err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
}
|
||||
|
||||
createtime := primitive.NewDateTimeFromTime(time.Now().UTC())
|
||||
link, err := sh.mongoClient.FindOneAndUpdate(CollectionLink, bson.M{
|
||||
"platform": newType,
|
||||
"uid": newId,
|
||||
}, bson.M{
|
||||
"$setOnInsert": bson.M{
|
||||
"create": createtime,
|
||||
"email": email,
|
||||
},
|
||||
}, options.FindOneAndUpdate().SetReturnDocument(options.After).SetUpsert(true).SetProjection(bson.M{"_id": 1}))
|
||||
if err != nil {
|
||||
logger.Error("link failed. FindOneAndUpdate link err:", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
_, newid, err := sh.mongoClient.Update(common.CollectionName(sh.ServiceName), bson.M{
|
||||
"_id": link["_id"].(primitive.ObjectID),
|
||||
}, bson.M{
|
||||
"$setOnInsert": bson.M{
|
||||
"accid": oldAuth.Accid,
|
||||
"create": createtime,
|
||||
},
|
||||
}, options.Update().SetUpsert(true))
|
||||
if err != nil {
|
||||
logger.Error("link failed. Update ServiceName err :", err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// newid가 있어야 한다. 그래야 기존 서비스 계정이 없는 상태이다.
|
||||
if newid == nil {
|
||||
// 이미 계정이 있네?
|
||||
logger.Println("link failed. already have service account :", r.URL.Query())
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
logger.Println("link success :", r.URL.Query())
|
||||
}
|
||||
|
||||
func (sh *serviceDescription) isValidAPIUser(category string, email string) bool {
|
||||
ptr := atomic.LoadPointer(&sh.apiUsers)
|
||||
catusers := *(*map[string]map[string]bool)(ptr)
|
||||
|
||||
if category == "*" {
|
||||
for _, users := range catusers {
|
||||
if _, ok := users[email]; ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
} else if users, ok := catusers[category]; ok {
|
||||
if _, ok := users[email]; ok {
|
||||
return true
|
||||
}
|
||||
|
||||
logger.Println("isValidAPIUser failed. email is not allowed :", category, email, users)
|
||||
}
|
||||
|
||||
logger.Println("isValidAPIUser failed. category is missing :", category)
|
||||
return false
|
||||
}
|
||||
|
||||
func (sh *serviceDescription) authorize(w http.ResponseWriter, r *http.Request) {
|
||||
defer func() {
|
||||
s := recover()
|
||||
if s != nil {
|
||||
logger.Error(s)
|
||||
}
|
||||
}()
|
||||
|
||||
if r.Method != "GET" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
queryvals := r.URL.Query()
|
||||
authtype := queryvals.Get("type")
|
||||
uid := queryvals.Get("id")
|
||||
//accesstoken := queryvals.Get("token") //-- 이거 이제 받지마라
|
||||
session := queryvals.Get("sk")
|
||||
|
||||
//email, err := sh.readProfile(authtype, uid, accesstoken)
|
||||
bfinfo, err := sh.getUserBrowserInfo(r)
|
||||
if err != nil {
|
||||
logger.Error("getUserBrowserInfo failed :", err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
email, err := sh.readProfile(authtype, uid, bfinfo)
|
||||
if err != nil {
|
||||
logger.Error("readProfile failed :", err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if !sh.wl.isMember(email, authtype) {
|
||||
logger.Println("auth failed. not whitelist member :", sh.ServiceCode, authtype, uid, email)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
logger.Println("auth success :", authtype, uid, email, session)
|
||||
|
||||
err, newType, newId := sh.getProviderInfo(authtype, uid)
|
||||
if err != nil {
|
||||
logger.Error("getProviderInfo failed :", err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if authtype != newType || uid != newId {
|
||||
authtype = newType
|
||||
uid = newId
|
||||
logger.Println("auth success ( redirect ) :", authtype, uid, email, session)
|
||||
}
|
||||
|
||||
//if len(session) == 0 && len(email) > 0 {
|
||||
if len(session) == 0 {
|
||||
// platform + id -> account id
|
||||
createtime := primitive.NewDateTimeFromTime(time.Now().UTC())
|
||||
link, err := sh.mongoClient.FindOneAndUpdate(CollectionLink, bson.M{
|
||||
"platform": authtype,
|
||||
"uid": uid,
|
||||
}, bson.M{
|
||||
"$setOnInsert": bson.M{
|
||||
"create": createtime,
|
||||
"email": email,
|
||||
},
|
||||
}, options.FindOneAndUpdate().SetReturnDocument(options.After).SetUpsert(true).SetProjection(bson.M{"_id": 1}))
|
||||
if err != nil {
|
||||
logger.Error("authorize failed :", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
linkid := link["_id"].(primitive.ObjectID)
|
||||
newaccid := primitive.NewObjectID()
|
||||
for i := 0; i < len(sh.serviceCodeBytes); i++ {
|
||||
newaccid[i] ^= sh.serviceCodeBytes[i]
|
||||
}
|
||||
account, err := sh.mongoClient.FindOneAndUpdate(common.CollectionName(sh.ServiceName), bson.M{
|
||||
"_id": linkid,
|
||||
}, bson.M{
|
||||
"$setOnInsert": bson.M{
|
||||
"accid": newaccid,
|
||||
"create": createtime,
|
||||
},
|
||||
}, options.FindOneAndUpdate().SetReturnDocument(options.After).SetUpsert(true).SetProjection(bson.M{"accid": 1, "create": 1}))
|
||||
if err != nil {
|
||||
logger.Error("authorize failed. Update sh.ServiceName err:", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
accid := account["accid"].(primitive.ObjectID)
|
||||
oldcreate := account["create"].(primitive.DateTime)
|
||||
newaccount := oldcreate == createtime
|
||||
|
||||
var bi blockinfo
|
||||
if err := sh.mongoClient.FindOneAs(CollectionBlock, bson.M{
|
||||
"code": sh.ServiceCode,
|
||||
"accid": accid,
|
||||
}, &bi); err != nil {
|
||||
logger.Error("authorize failed. find blockinfo in CollectionBlock err:", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if !bi.Start.Time().IsZero() {
|
||||
now := time.Now().UTC()
|
||||
if bi.Start.Time().Before(now) && bi.End.Time().After(now) {
|
||||
// block됐네?
|
||||
// status는 정상이고 reason을 넘겨주자
|
||||
json.NewEncoder(w).Encode(map[string]any{
|
||||
"blocked": bi,
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
newsession := primitive.NewObjectID()
|
||||
expired := primitive.NewDateTimeFromTime(time.Now().UTC().Add(sh.sessionTTL))
|
||||
newauth := common.Authinfo{
|
||||
Accid: accid,
|
||||
ServiceCode: sh.ServiceCode,
|
||||
Platform: authtype,
|
||||
Uid: uid,
|
||||
//Token: accesstoken,
|
||||
Sk: newsession,
|
||||
Expired: expired,
|
||||
//RefreshToken: queryvals.Get("rt"),
|
||||
}
|
||||
|
||||
_, _, err = sh.mongoClient.UpsertOne(CollectionAuth, bson.M{"_id": newauth.Accid}, &newauth)
|
||||
if err != nil {
|
||||
logger.Error("authorize failed :", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
output := map[string]any{
|
||||
"sk": newsession.Hex(),
|
||||
"expirein": sh.sessionTTL.Seconds(),
|
||||
"newAccount": newaccount,
|
||||
"accid": newauth.Accid.Hex(),
|
||||
}
|
||||
bt, _ := json.Marshal(output)
|
||||
w.Write(bt)
|
||||
} else if len(session) > 0 {
|
||||
sessionobj, _ := primitive.ObjectIDFromHex(session)
|
||||
if !sessionobj.IsZero() {
|
||||
updated, _, err := sh.mongoClient.Update(CollectionAuth,
|
||||
bson.M{
|
||||
"sk": sessionobj,
|
||||
},
|
||||
bson.M{
|
||||
"$currentDate": bson.M{
|
||||
"_ts": bson.M{"$type": "date"},
|
||||
},
|
||||
}, options.Update().SetUpsert(false))
|
||||
if err != nil {
|
||||
logger.Error("update auth collection failed")
|
||||
logger.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
if !updated {
|
||||
// 세션이 없네?
|
||||
logger.Println("authorize failed. session not exists in database :", session)
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
output := map[string]any{
|
||||
"sk": session,
|
||||
"expirein": sh.sessionTTL.Seconds(),
|
||||
}
|
||||
bt, _ := json.Marshal(output)
|
||||
w.Write(bt)
|
||||
} else {
|
||||
logger.Println("authorize failed. sk is not valid hex :", session)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
logger.Println("authorize failed. id empty :", queryvals)
|
||||
}
|
||||
}
|
||||
|
||||
func (sh *serviceDescription) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
defer func() {
|
||||
s := recover()
|
||||
if s != nil {
|
||||
logger.Error(s)
|
||||
}
|
||||
}()
|
||||
|
||||
defer func() {
|
||||
io.Copy(io.Discard, r.Body)
|
||||
r.Body.Close()
|
||||
}()
|
||||
|
||||
if atomic.LoadInt32(&sh.closed) != 0 {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
if strings.HasSuffix(r.URL.Path, "/auth") {
|
||||
sh.authorize(w, r)
|
||||
} else if strings.HasSuffix(r.URL.Path, "/link") {
|
||||
sh.link(w, r)
|
||||
} else {
|
||||
// TODO : 세션키와 authtoken을 헤더로 받아서 accid 조회
|
||||
queryvals := r.URL.Query()
|
||||
//token := queryvals.Get("token")
|
||||
token := "" // 더이상 쓰지 않는다.
|
||||
sk := queryvals.Get("sk")
|
||||
|
||||
//if len(token) == 0 || len(sk) == 0 {
|
||||
if len(sk) == 0 {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO : 각 서버에 있는 자산? 캐릭터 정보를 보여줘야 하나. 뭘 보여줄지는 프로젝트에 문의
|
||||
// 일단 서버 종류만 내려보내자
|
||||
// 세션키가 있는지 확인
|
||||
if _, ok := sh.auths.IsValid(sk, token); !ok {
|
||||
logger.Println("sessionkey is not valid :", sk, token)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
divstrptr := atomic.LoadPointer(&sh.divisionsSerialized)
|
||||
divstr := *(*string)(divstrptr)
|
||||
w.Write([]byte(divstr))
|
||||
}
|
||||
}
|
||||
330
core/watch.go
Normal file
330
core/watch.go
Normal file
@ -0,0 +1,330 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"repositories.action2quare.com/ayo/go-ayo/logger"
|
||||
|
||||
"repositories.action2quare.com/ayo/go-ayo/common"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
type authPipelineDocument struct {
|
||||
OperationType string `bson:"operationType"`
|
||||
DocumentKey struct {
|
||||
Id primitive.ObjectID `bson:"_id"`
|
||||
} `bson:"documentKey"`
|
||||
Authinfo *common.Authinfo `bson:"fullDocument"`
|
||||
}
|
||||
|
||||
type servicePipelineDocument struct {
|
||||
OperationType string `bson:"operationType"`
|
||||
DocumentKey struct {
|
||||
Id primitive.ObjectID `bson:"_id"`
|
||||
} `bson:"documentKey"`
|
||||
Service *serviceDescription `bson:"fullDocument"`
|
||||
}
|
||||
|
||||
type whilelistPipelineDocument struct {
|
||||
OperationType string `bson:"operationType"`
|
||||
DocumentKey struct {
|
||||
Id primitive.ObjectID `bson:"_id"`
|
||||
} `bson:"documentKey"`
|
||||
Member *whitelistmember `bson:"fullDocument"`
|
||||
}
|
||||
|
||||
func (mg *Maingate) watchWhitelistCollection(parentctx context.Context) {
|
||||
defer func() {
|
||||
s := recover()
|
||||
if s != nil {
|
||||
logger.Error(s)
|
||||
}
|
||||
}()
|
||||
|
||||
matchStage := bson.D{
|
||||
{
|
||||
Key: "$match", Value: bson.D{
|
||||
{Key: "operationType", Value: bson.D{
|
||||
{Key: "$in", Value: bson.A{
|
||||
"update",
|
||||
"insert",
|
||||
}},
|
||||
}},
|
||||
},
|
||||
}}
|
||||
projectStage := bson.D{
|
||||
{
|
||||
Key: "$project", Value: bson.D{
|
||||
{Key: "documentKey", Value: 1},
|
||||
{Key: "operationType", Value: 1},
|
||||
{Key: "fullDocument", Value: 1},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var stream *mongo.ChangeStream
|
||||
var err error
|
||||
var ctx context.Context
|
||||
|
||||
for {
|
||||
if stream == nil {
|
||||
stream, err = mg.mongoClient.Watch(CollectionWhitelist, mongo.Pipeline{matchStage, projectStage})
|
||||
if err != nil {
|
||||
logger.Error("watchWhitelistCollection watch failed :", err)
|
||||
time.Sleep(time.Minute)
|
||||
continue
|
||||
}
|
||||
ctx = context.TODO()
|
||||
}
|
||||
|
||||
changed := stream.TryNext(ctx)
|
||||
if ctx.Err() != nil {
|
||||
logger.Error("watchServiceCollection stream.TryNext failed. process should be restarted! :", ctx.Err().Error())
|
||||
break
|
||||
}
|
||||
|
||||
if changed {
|
||||
var data whilelistPipelineDocument
|
||||
if err := stream.Decode(&data); err == nil {
|
||||
ot := data.OperationType
|
||||
switch ot {
|
||||
case "insert":
|
||||
// 새 화이트리스트 멤버
|
||||
if svc := mg.services.get(data.Member.Service); svc != nil {
|
||||
svc.wl.add(data.Member)
|
||||
}
|
||||
case "update":
|
||||
if svc := mg.services.get(data.Member.Service); svc != nil {
|
||||
if data.Member.Expired != 0 {
|
||||
logger.Println("whitelist member is removed :", *data.Member)
|
||||
svc.wl.remove(data.Member.Email)
|
||||
} else {
|
||||
logger.Println("whitelist member is updated :", *data.Member)
|
||||
svc.wl.add(data.Member)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.Error("watchServiceCollection stream.Decode failed :", err)
|
||||
}
|
||||
} else if stream.Err() != nil || stream.ID() == 0 {
|
||||
logger.Error("watchServiceCollection stream error :", stream.Err())
|
||||
stream.Close(ctx)
|
||||
stream = nil
|
||||
} else {
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (mg *Maingate) watchServiceCollection(parentctx context.Context, serveMux *http.ServeMux, prefix string) {
|
||||
defer func() {
|
||||
s := recover()
|
||||
if s != nil {
|
||||
logger.Error(s)
|
||||
}
|
||||
}()
|
||||
|
||||
matchStage := bson.D{
|
||||
{
|
||||
Key: "$match", Value: bson.D{
|
||||
{Key: "operationType", Value: bson.D{
|
||||
{Key: "$in", Value: bson.A{
|
||||
"delete",
|
||||
"insert",
|
||||
"update",
|
||||
"replace",
|
||||
}},
|
||||
}},
|
||||
},
|
||||
}}
|
||||
projectStage := bson.D{
|
||||
{
|
||||
Key: "$project", Value: bson.D{
|
||||
{Key: "operationType", Value: 1},
|
||||
{Key: "fullDocument", Value: 1},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var stream *mongo.ChangeStream
|
||||
var err error
|
||||
var ctx context.Context
|
||||
|
||||
for {
|
||||
if stream == nil {
|
||||
stream, err = mg.mongoClient.Watch(CollectionService, mongo.Pipeline{matchStage, projectStage}, options.ChangeStream().SetFullDocument(options.UpdateLookup))
|
||||
if err != nil {
|
||||
logger.Error("watchServiceCollection watch failed :", err)
|
||||
time.Sleep(time.Minute)
|
||||
continue
|
||||
}
|
||||
ctx = context.TODO()
|
||||
}
|
||||
|
||||
changed := stream.TryNext(ctx)
|
||||
if ctx.Err() != nil {
|
||||
logger.Error("watchServiceCollection stream.TryNext failed. process should be restarted! :", ctx.Err().Error())
|
||||
break
|
||||
}
|
||||
|
||||
if changed {
|
||||
var data servicePipelineDocument
|
||||
if err := stream.Decode(&data); err == nil {
|
||||
ot := data.OperationType
|
||||
switch ot {
|
||||
case "insert":
|
||||
// 새 서비스 추가됨
|
||||
if err := data.Service.prepare(mg); err != nil {
|
||||
logger.Error("service cannot be prepared :", data.Service, err)
|
||||
} else {
|
||||
logger.Println("service is on the board! :", data.Service)
|
||||
mg.services.add(data.Service)
|
||||
serveMux.Handle(common.MakeHttpHandlerPattern(prefix, data.Service.ServiceCode, "/"), data.Service)
|
||||
}
|
||||
|
||||
case "replace":
|
||||
fallthrough
|
||||
|
||||
case "update":
|
||||
data.Service.prepare(mg)
|
||||
if old := mg.services.get(data.Service.ServiceName); old != nil {
|
||||
logger.Printf("service is changed : %v", data.Service)
|
||||
|
||||
atomic.SwapPointer(&old.divisionsSerialized, data.Service.divisionsSerialized)
|
||||
atomic.SwapPointer(&old.apiUsers, data.Service.apiUsers)
|
||||
atomic.SwapPointer(&old.serviceSerialized, data.Service.serviceSerialized)
|
||||
|
||||
for _, token := range old.ServerApiTokens {
|
||||
mg.apiTokenToService.remove(token.Hex())
|
||||
}
|
||||
|
||||
for _, token := range data.Service.ServerApiTokens {
|
||||
mg.apiTokenToService.add(token.Hex(), data.Service.ServiceCode)
|
||||
}
|
||||
|
||||
if data.Service.UseWhitelist {
|
||||
atomic.StoreInt32(&old.wl.working, 1)
|
||||
} else {
|
||||
atomic.StoreInt32(&old.wl.working, 0)
|
||||
}
|
||||
|
||||
old.Closed = data.Service.Closed
|
||||
if old.Closed {
|
||||
atomic.StoreInt32(&old.closed, 1)
|
||||
} else {
|
||||
atomic.StoreInt32(&old.closed, 0)
|
||||
}
|
||||
atomic.SwapPointer(&old.wl.emailptr, data.Service.wl.emailptr)
|
||||
|
||||
old.Divisions = data.Service.Divisions
|
||||
} else if !data.Service.Closed {
|
||||
if err := data.Service.prepare(mg); err != nil {
|
||||
logger.Error("service cannot be prepared :", data.Service, err)
|
||||
} else {
|
||||
logger.Println("service is on the board! :", data.Service)
|
||||
mg.services.add(data.Service)
|
||||
serveMux.Handle(common.MakeHttpHandlerPattern(prefix, data.Service.ServiceCode, "/"), data.Service)
|
||||
}
|
||||
}
|
||||
case "delete":
|
||||
if deleted := mg.services.remove(data.DocumentKey.Id); deleted != nil {
|
||||
logger.Println("service is closed :", data.Service)
|
||||
atomic.AddInt32(&deleted.closed, 1)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.Error("watchServiceCollection stream.Decode failed :", err)
|
||||
}
|
||||
} else if stream.Err() != nil || stream.ID() == 0 {
|
||||
logger.Error("watchServiceCollection stream error :", stream.Err())
|
||||
stream.Close(ctx)
|
||||
stream = nil
|
||||
} else {
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func watchAuthCollection(parentctx context.Context, ac *common.AuthCollection, mongoClient common.MongoClient) {
|
||||
defer func() {
|
||||
s := recover()
|
||||
if s != nil {
|
||||
logger.Error(s)
|
||||
}
|
||||
}()
|
||||
|
||||
matchStage := bson.D{
|
||||
{
|
||||
Key: "$match", Value: bson.D{
|
||||
{Key: "operationType", Value: bson.D{
|
||||
{Key: "$in", Value: bson.A{
|
||||
"delete",
|
||||
"insert",
|
||||
"update",
|
||||
}},
|
||||
}},
|
||||
},
|
||||
}}
|
||||
projectStage := bson.D{
|
||||
{
|
||||
Key: "$project", Value: bson.D{
|
||||
{Key: "documentKey", Value: 1},
|
||||
{Key: "operationType", Value: 1},
|
||||
{Key: "fullDocument", Value: 1},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var stream *mongo.ChangeStream
|
||||
var err error
|
||||
var ctx context.Context
|
||||
|
||||
for {
|
||||
if stream == nil {
|
||||
stream, err = mongoClient.Watch(CollectionAuth, mongo.Pipeline{matchStage, projectStage})
|
||||
if err != nil {
|
||||
logger.Error("watchAuthCollection watch failed :", err)
|
||||
time.Sleep(time.Minute)
|
||||
continue
|
||||
}
|
||||
ctx = context.TODO()
|
||||
}
|
||||
|
||||
changed := stream.TryNext(ctx)
|
||||
if ctx.Err() != nil {
|
||||
logger.Error("watchAuthCollection stream.TryNext failed. process should be restarted! :", ctx.Err().Error())
|
||||
break
|
||||
}
|
||||
|
||||
if changed {
|
||||
var data authPipelineDocument
|
||||
if err := stream.Decode(&data); err == nil {
|
||||
ot := data.OperationType
|
||||
switch ot {
|
||||
case "insert":
|
||||
ac.AddRaw(&mongoAuthCell{src: data.Authinfo})
|
||||
case "update":
|
||||
ac.AddRaw(&mongoAuthCell{src: data.Authinfo})
|
||||
case "delete":
|
||||
ac.RemoveByAccId(data.DocumentKey.Id)
|
||||
}
|
||||
} else {
|
||||
logger.Error("watchAuthCollection stream.Decode failed :", err)
|
||||
}
|
||||
} else if stream.Err() != nil || stream.ID() == 0 {
|
||||
logger.Error("watchAuthCollection stream error :", stream.Err())
|
||||
stream.Close(ctx)
|
||||
stream = nil
|
||||
} else {
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user