package core import ( "context" "crypto/rsa" "encoding/base64" "encoding/json" "errors" "fmt" "io" "math/big" "net" "net/http" "os" "strings" "sync/atomic" "text/template" "time" "unsafe" "repositories.action2quare.com/ayo/gocommon" "repositories.action2quare.com/ayo/gocommon/flagx" "repositories.action2quare.com/ayo/gocommon/logger" "repositories.action2quare.com/ayo/gocommon/session" "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 devflag = flagx.Bool("dev", false, "") var noauth = flagx.Bool("noauth", false, "") var authtype = flagx.String("auth", "on", "on|off|both") var ( CollectionLink = gocommon.CollectionName("link") CollectionWhitelist = gocommon.CollectionName("whitelist") CollectionService = gocommon.CollectionName("service") CollectionAccount = gocommon.CollectionName("account") CollectionFile = gocommon.CollectionName("file") CollectionBlock = gocommon.CollectionName("block") CollectionPlatformLoginToken = gocommon.CollectionName("platform_login_token") //-- 각 플랫폼에 로그인 및 권한 받아오는 과정에 사용하는 Key CollectionUserToken = gocommon.CollectionName("usertoken") CollectionGamepotUserInfo = gocommon.CollectionName("gamepot_userinfo") //-- 클라로부터 수집된 gamepot 정보 - server to server로 유효성이 검증되진 않았지만 수집은 한다. CollectionFirebaseUserInfo = gocommon.CollectionName("firebase_userinfo") //-- Firebase UserInfo ) const ( AuthPlatformSteamSDK = "steam" AuthPlatformFirebaseAuth = "firebase" AuthPlatformGoogle = "google" AuthPlatformMicrosoft = "microsoft" AuthPlatformApple = "apple" AuthPlatformTwitter = "twitter" AuthPlatformHybeim = "hybeim" ) const ( sessionTTL = time.Hour sessionTTLDev = 1 * time.Hour ) func SessionTTL() time.Duration { if *devflag { return sessionTTLDev } return sessionTTL } type maingateConfig struct { session.SessionConfig `json:",inline"` MustUseChecksum bool `json:"maingate_must_checksum"` Mongo string `json:"maingate_mongodb_url"` Autologin_ttl int64 `json:"autologin_ttl"` AccDelTTL int64 `json:"acc_del_ttl"` MaximumNumLinkAccount int64 `json:"maximum_num_link_account"` 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"` SteamAppId string `json:"steam_app_id"` SteamPublisherAuthKey string `json:"steam_publisher_authkey"` GlobalMaingateToken string `json:"maingate_api_token"` HybeImProjectIdstring string `json:"hybeim_projectid"` HybeImServiceIdstring string `json:"hybeim_serviceid"` HybeImAccessKey string `json:"hybeim_acesskey"` HybeImEndPoint string `json:"hybeim_Endpoint"` Firebase_Google_Analytics_JS_SDK_Config } type Firebase_Google_Analytics_JS_SDK_Config struct { FGA_apiKey string `json:"firebase_google_analytics_jssdk_apikey"` FGA_authDomain string `json:"firebase_google_analytics_jssdk_authdomain"` FGA_databaseURL string `json:"firebase_google_analytics_jssdk_databaseurl"` FGA_projectId string `json:"firebase_google_analytics_jssdk_projectid"` FGA_storageBucket string `json:"firebase_google_analytics_jssdk_storagebucket"` FGA_messagingSenderId string `json:"firebase_google_analytics_jssdk_messagingsenderid"` FGA_appId string `json:"firebase_google_analytics_jssdk_apiid"` FGA_measurementId string `json:"ffirebase_google_analytics_jssdk_measurementid"` } 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 = gocommon.ConfigModTime() } type firebaseClient struct { firebaseAppClient *auth.Client firebaseAppContext context.Context } // Maingate : type Maingate struct { mongoClient gocommon.MongoClient sessionProvider session.Provider //services servicelist serviceptr unsafe.Pointer admins unsafe.Pointer wl memberContainerPtr[string, *whitelistmember] bl memberContainerPtr[primitive.ObjectID, *blockinfo] tokenEndpoints map[string]string authorizationEndpoints map[string]string userinfoEndpoint map[string]string jwksUri map[string]string firebase *firebaseClient } var config maingateConfig // New : func New(ctx context.Context) (*Maingate, error) { if err := gocommon.LoadConfig(&config); err != nil { return nil, err } var admins globalAdmins if err := gocommon.LoadConfig(&admins); err == nil { admins.parse() } if len(config.SessionStorage) == 0 { return nil, errors.New("maingate_session_storage is missing") } if config.SessionTTL == 0 { config.SessionTTL = 3600 } mg := Maingate{ admins: unsafe.Pointer(&admins), 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 { return nil, err } if len(*authtype) == 0 { *authtype = "on" } if !*noauth && (*authtype == "on" || *authtype == "both") { if len(config.FirebaseAdminSDKCredentialFile) > 0 { opt := option.WithCredentialsFile(config.FirebaseAdminSDKCredentialFile) firebaseApp, err := firebase.NewApp(context.Background(), nil, opt) if err != nil { logger.Error("firebase admin error initializing app failed :", err) return nil, err } firebaseAppClient, err := firebaseApp.Auth(ctx) if err != nil { logger.Println("FirebaseAppClient error getting Auth client:", err) return nil, err } mg.firebase = &firebaseClient{ firebaseAppContext: ctx, firebaseAppClient: firebaseAppClient, } } } return &mg, nil } func (mg *Maingate) service() *serviceDescription { valptr := atomic.LoadPointer(&mg.serviceptr) return (*serviceDescription)(valptr) } 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 logger.ErrorWithCallStack(err) } if err := mg.discoverOpenIdConfiguration("google", "https://accounts.google.com/.well-known/openid-configuration"); err != nil { return logger.ErrorWithCallStack(err) } // redis에서 env를 가져온 후에 mg.mongoClient, err = gocommon.NewMongoClient(context, config.Mongo) if err != nil { return logger.ErrorWithCallStack(err) } if err = mg.mongoClient.MakeUniqueIndices(CollectionCouponUse, map[string]bson.D{ "idrounds": {{Key: "_id", Value: 1}, {Key: "rounds", Value: 1}}, }); err != nil { return logger.ErrorWithCallStack(err) } if err = mg.mongoClient.MakeUniqueIndices(CollectionLink, map[string]bson.D{ "platformuid": {{Key: "platform", Value: 1}, {Key: "uid", Value: 1}}, }); err != nil { return logger.ErrorWithCallStack(err) } if err = mg.mongoClient.MakeUniqueIndices(CollectionLink, map[string]bson.D{ "emailplatform": {{Key: "email", Value: 1}, {Key: "platform", Value: 1}}, }); err != nil { return logger.ErrorWithCallStack(err) } if err = mg.mongoClient.MakeIndices(CollectionAccount, map[string]bson.D{ "accid": {{Key: "accid", Value: 1}}, }); err != nil { return logger.ErrorWithCallStack(err) } if err = mg.mongoClient.MakeUniqueIndices(CollectionFile, map[string]bson.D{ "keyonly": {{Key: "key", Value: 1}}, }); err != nil { return logger.ErrorWithCallStack(err) } if err = mg.mongoClient.MakeExpireIndex(CollectionAccount, int32(config.AccDelTTL)); err != nil { return logger.ErrorWithCallStack(err) } if err = mg.mongoClient.MakeExpireIndex(CollectionLink, int32(config.AccDelTTL)); err != nil { return logger.ErrorWithCallStack(err) } // Delete대신 _ts로 expire시킴. pipeline에 삭제 알려주기 위함 if err = mg.mongoClient.MakeExpireIndex(CollectionWhitelist, 10); err != nil { return logger.ErrorWithCallStack(err) } if *devflag { // 에러 체크하지 말것 mg.mongoClient.DropIndex(CollectionBlock, "codeaccid") } if err = mg.mongoClient.MakeExpireIndex(CollectionBlock, int32(3)); err != nil { return logger.ErrorWithCallStack(err) } if err = mg.mongoClient.MakeUniqueIndices(CollectionPlatformLoginToken, map[string]bson.D{ "platformauthtoken": {{Key: "platform", Value: 1}, {Key: "key", Value: 1}}, }); err != nil { return logger.ErrorWithCallStack(err) } if err = mg.mongoClient.MakeExpireIndex(CollectionPlatformLoginToken, int32(config.SessionTTL+300)); err != nil { return logger.ErrorWithCallStack(err) } if err = mg.mongoClient.MakeUniqueIndices(CollectionUserToken, map[string]bson.D{ "platformusertoken": {{Key: "platform", Value: 1}, {Key: "userid", Value: 1}}, }); err != nil { return logger.ErrorWithCallStack(err) } if err = mg.mongoClient.MakeUniqueIndices(CollectionGamepotUserInfo, map[string]bson.D{ "gamepotuserid": {{Key: "gamepotuserid", Value: 1}}, }); err != nil { return logger.ErrorWithCallStack(err) } if err = mg.mongoClient.MakeUniqueIndices(CollectionFirebaseUserInfo, map[string]bson.D{ "firebaseuserid": {{Key: "firebaseuserid", Value: 1}}, }); err != nil { return logger.ErrorWithCallStack(err) } mg.sessionProvider, err = session.NewProviderWithConfig(context, config.SessionConfig) if err != nil { return logger.ErrorWithCallStack(err) } var preall []struct { Link string `bson:"link"` Id primitive.ObjectID `bson:"_id"` } if err = mg.mongoClient.FindAllAs(CollectionFile, nil, &preall, options.Find().SetProjection(bson.M{ "link": 1, })); err != nil { return logger.ErrorWithCallStack(err) } for _, pre := range preall { _, err := os.Stat(pre.Link) if !os.IsNotExist(err) { continue } logger.Println("saving files :", pre.Link) var fulldoc FileDocumentDesc err = mg.mongoClient.FindOneAs(CollectionFile, bson.M{ "_id": pre.Id, }, &fulldoc) if err != nil { return logger.ErrorWithCallStack(err) } err = fulldoc.Save() if err != nil { return logger.ErrorWithCallStack(err) } } var whites []*whitelistmember if err := mg.mongoClient.AllAs(CollectionWhitelist, &whites, options.Find().SetReturnKey(false)); err != nil { return logger.ErrorWithCallStack(err) } mg.wl.init(whites) var blocks []*blockinfo if err := mg.mongoClient.AllAs(CollectionBlock, &blocks); err != nil { return logger.ErrorWithCallStack(err) } logger.Println("allblocks :", blocks) mg.bl.init(blocks) go mg.wl.watchCollection(context, CollectionWhitelist, mg.mongoClient) go mg.bl.watchCollection(context, CollectionBlock, mg.mongoClient) return nil } var portptr = flagx.Int("port", 80, "") func (mg *Maingate) RegisterHandlers(ctx context.Context, serveMux *http.ServeMux, prefix string) error { var allServices []*serviceDescription if err := mg.mongoClient.AllAs(CollectionService, &allServices, options.Find().SetReturnKey(false)); err != nil { return logger.ErrorWithCallStack(err) } if len(allServices) > 0 { only := allServices[0] only.prepare(mg) only.mustUseChecksum = config.MustUseChecksum atomic.StorePointer(&mg.serviceptr, unsafe.Pointer(only)) } else { empty := serviceDescription{ Id: primitive.NewObjectID(), } if *devflag { addrs, err := net.InterfaceAddrs() if err != nil { return logger.ErrorWithCallStack(err) } ipaddr := "127.0.0.1" for _, addr := range addrs { if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { if ipnet.IP.To4() != nil && ipnet.IP.IsPrivate() { ipaddr = ipnet.IP.String() } } } empty.Divisions = map[string]*Division{ "default": { DivisionForUser: DivisionForUser{ Priority: 0, State: DivisionState_FullOpen, }, Urls: bson.M{ "warehouse": fmt.Sprintf("http://%s:%d/warehouse", ipaddr, *portptr), }, }, } } empty.prepare(mg) atomic.StorePointer(&mg.serviceptr, unsafe.Pointer(&empty)) filter := bson.M{"_id": empty.Id} _, _, err := mg.mongoClient.Update(CollectionService, filter, bson.M{ "$set": &empty, }, options.Update().SetUpsert(true)) if err != nil { return logger.ErrorWithCallStack(err) } } if *devflag { serveMux.HandleFunc(gocommon.MakeHttpHandlerPattern(prefix, "/"), func(w http.ResponseWriter, r *http.Request) { // mg.service()를 요청마다 불러야 함 mg.service().serveHTTP_dev(w, r) }) } else { pattern := gocommon.MakeHttpHandlerPattern(prefix, "/") logger.Println("pattern registered :", pattern) serveMux.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) { // mg.service()를 요청마다 불러야 함 mg.service().serveHTTP(w, r) }) } serveMux.HandleFunc(gocommon.MakeHttpHandlerPattern(prefix, "api/"), mg.api) configraw, _ := json.Marshal(config) var convertedConfig map[string]any if err := json.Unmarshal(configraw, &convertedConfig); err != nil { return logger.ErrorWithCallStack(err) } serveMux.HandleFunc(gocommon.MakeHttpHandlerPattern(prefix, "config"), func(w http.ResponseWriter, r *http.Request) { defer func() { s := recover() if s != nil { logger.Error(s) } }() if !*devflag { 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 } apitokenObj, _ := primitive.ObjectIDFromHex(apitoken) if mg.service().isValidToken(apitokenObj) { convertedConfig["divisions"] = mg.service().Divisions } } else { convertedConfig["divisions"] = mg.service().Divisions } enc := json.NewEncoder(w) enc.Encode(convertedConfig) }) if err := os.MkdirAll("static", os.ModePerm); err != nil { // 일반 엔드유저한테 오픈할 static 페이지 return logger.ErrorWithCallStack(err) } cfsx := http.FileServer(http.Dir("console")) pattern := gocommon.MakeHttpHandlerPattern(prefix, "console", "/") serveMux.Handle(pattern, http.StripPrefix(pattern, cfsx)) logger.Println("maingate console registered :", pattern) staticfs := http.FileServer(http.Dir("static")) pattern = gocommon.MakeHttpHandlerPattern(prefix, "static", "/") serveMux.Handle(pattern, http.StripPrefix(pattern, staticfs)) logger.Println("maingate static registered :", pattern) fbafs := http.FileServer(http.Dir("fba")) pattern = gocommon.MakeHttpHandlerPattern(prefix, "fba", "/") serveMux.Handle(pattern, http.StripPrefix(pattern, fbafs)) logger.Println("google_analytics static registered :", pattern) serveMux.HandleFunc(gocommon.MakeHttpHandlerPattern(prefix, "fba", "fb-ga.min.js"), mg.google_analytics_js) logger.Println("google_analytics.js static registered :", pattern) serveMux.HandleFunc(gocommon.MakeHttpHandlerPattern(prefix, "request_login_url", AuthPlatformGoogle), mg.platform_google_get_login_url) serveMux.HandleFunc(gocommon.MakeHttpHandlerPattern(prefix, "authorize", AuthPlatformGoogle), mg.platform_google_authorize) serveMux.HandleFunc(gocommon.MakeHttpHandlerPattern(prefix, "authorize_result", AuthPlatformGoogle), mg.platform_google_authorize_result) serveMux.HandleFunc(gocommon.MakeHttpHandlerPattern(prefix, "request_login_url", AuthPlatformMicrosoft), mg.platform_microsoft_get_login_url) serveMux.HandleFunc(gocommon.MakeHttpHandlerPattern(prefix, "authorize", AuthPlatformMicrosoft), mg.platform_microsoft_authorize) serveMux.HandleFunc(gocommon.MakeHttpHandlerPattern(prefix, "authorize_result", AuthPlatformMicrosoft), mg.platform_microsoft_authorize_result) serveMux.HandleFunc(gocommon.MakeHttpHandlerPattern(prefix, "request_login_url", AuthPlatformTwitter), mg.platform_twitter_get_login_url) serveMux.HandleFunc(gocommon.MakeHttpHandlerPattern(prefix, "authorize", AuthPlatformTwitter), mg.platform_twitter_authorize) serveMux.HandleFunc(gocommon.MakeHttpHandlerPattern(prefix, "authorize_result", AuthPlatformTwitter), mg.platform_twitter_authorize_result) serveMux.HandleFunc(gocommon.MakeHttpHandlerPattern(prefix, "request_login_url", AuthPlatformApple), mg.platform_apple_get_login_url) serveMux.HandleFunc(gocommon.MakeHttpHandlerPattern(prefix, "authorize", AuthPlatformApple), mg.platform_apple_authorize) serveMux.HandleFunc(gocommon.MakeHttpHandlerPattern(prefix, "authorize_result", AuthPlatformApple), mg.platform_apple_authorize_result) serveMux.HandleFunc(gocommon.MakeHttpHandlerPattern(prefix, "request_login_url", AuthPlatformFirebaseAuth), mg.platform_firebaseauth_get_login_url) serveMux.HandleFunc(gocommon.MakeHttpHandlerPattern(prefix, "authorize_sdk", AuthPlatformFirebaseAuth), mg.platform_firebaseauth_authorize_sdk) serveMux.HandleFunc(gocommon.MakeHttpHandlerPattern(prefix, "authorize_sdk", AuthPlatformSteamSDK), mg.platform_steamsdk_authorize) serveMux.HandleFunc(gocommon.MakeHttpHandlerPattern(prefix, "authorize_sdk", AuthPlatformHybeim), mg.platform_hybeim_authorize) go mg.watchServiceCollection(ctx, serveMux, prefix) go mg.watchFileCollection(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) GeneratePlatformLoginNonceKey() string { const allowed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" b := make([]byte, 52) for i := range b { b[i] = allowed[r.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 "", logger.ErrorWithCallStack(err) } //requestinfo := fmt.Sprintf("%s_%s", cookie.Value, host) //-- RemoteAddr체크는 로드밸런서 IP 찍히는 문제 때문에 제외한다. requestinfo := fmt.Sprintf("%s_", cookie.Value) return requestinfo, nil } func (mg *Maingate) setUserToken(info usertokeninfo) error { _, _, err := mg.mongoClient.Update(CollectionUserToken, bson.M{ "platform": info.platform, "userid": info.userid, }, bson.M{ "$set": bson.M{ "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 logger.ErrorWithCallStack(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, }) if err != nil { return info, err } if found == nil { return info, errors.New("user token not found :" + platform + " / " + userid + " / " + brinfo) } if found["brinfo"].(string) != brinfo { return info, errors.New("user brinfo is different :" + platform + " / " + userid + " / " + brinfo) } updatetime, ok := found["lastupdate"].(int64) if !ok || time.Now().Unix()-updatetime < config.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 AuthPlatformSteamSDK: success, userid, email = mg.platform_steamsdk_getuserinfo(info) case AuthPlatformHybeim: success, userid, email = mg.platform_hybeim_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) (string, string, error) { 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 provider, providerid, nil } 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 } func (mg *Maingate) google_analytics_js(w http.ResponseWriter, r *http.Request) { fgaconfig := Firebase_Google_Analytics_JS_SDK_Config{ FGA_apiKey: config.FGA_apiKey, FGA_authDomain: config.FGA_authDomain, FGA_databaseURL: config.FGA_databaseURL, FGA_projectId: config.FGA_projectId, FGA_storageBucket: config.FGA_storageBucket, FGA_messagingSenderId: config.FGA_messagingSenderId, FGA_appId: config.FGA_appId, FGA_measurementId: config.FGA_measurementId, } parsedTemplate, _ := template.ParseFiles("template/fb-ga.min.js") err := parsedTemplate.Execute(w, fgaconfig) if err != nil { logger.Error("Error executing template :", err) return } }