898 lines
27 KiB
Go
898 lines
27 KiB
Go
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 (
|
|
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"
|
|
)
|
|
|
|
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"`
|
|
Mongo string `json:"maingate_mongodb_url"`
|
|
Autologin_ttl int64 `json:"autologin_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"`
|
|
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()
|
|
}
|
|
|
|
// 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
|
|
firebaseAppClient *auth.Client
|
|
firebaseAppContext context.Context
|
|
}
|
|
|
|
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 !*noauth {
|
|
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
|
|
}
|
|
|
|
mg.firebaseAppContext = ctx
|
|
mg.firebaseAppClient, err = firebaseApp.Auth(mg.firebaseAppContext)
|
|
if err != nil {
|
|
logger.Println("FirebaseAppClient error getting Auth client:", err)
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
// 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)
|
|
|
|
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,
|
|
},
|
|
|
|
Url: 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 {
|
|
serveMux.HandleFunc(gocommon.MakeHttpHandlerPattern(prefix, "/"), 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)
|
|
|
|
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 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
|
|
}
|
|
}
|