Files
maingate/core/maingate.go

947 lines
27 KiB
Go

package core
import (
"context"
"crypto/rsa"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"math/big"
"math/rand"
"net"
"net/http"
"os"
"strings"
"sync/atomic"
"time"
"unsafe"
"repositories.action2quare.com/ayo/gocommon"
"repositories.action2quare.com/ayo/gocommon/flagx"
"repositories.action2quare.com/ayo/gocommon/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 devflag = flagx.Bool("dev", false, "")
var noauth = flagx.Bool("noauth", false, "")
var (
CollectionLink = gocommon.CollectionName("link")
CollectionAuth = gocommon.CollectionName("auth")
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 mongoAuthCell struct {
src *gocommon.Authinfo
}
func (ac *mongoAuthCell) ToAuthinfo() *gocommon.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 gocommon.MongoClient, sessionTTL time.Duration) *gocommon.AuthCollection {
authcoll := gocommon.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) gocommon.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 maingateConfig struct {
Mongo string `json:"maingate_mongodb_url"`
SessionTTL int64 `json:"maingate_session_ttl"`
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"`
}
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 {
maingateConfig
mongoClient gocommon.MongoClient
auths *gocommon.AuthCollection
//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
}
// New :
func New(ctx context.Context) (*Maingate, error) {
var config maingateConfig
if err := gocommon.LoadConfig(&config); err != nil {
return nil, err
}
var admins globalAdmins
if err := gocommon.LoadConfig(&admins); err == nil {
admins.parse()
}
if config.SessionTTL == 0 {
config.SessionTTL = 3600
}
mg := Maingate{
maingateConfig: config,
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 {
logger.Error("mg.prepare() failed :", err)
return nil, err
}
if !*noauth {
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 {
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 err
}
if err := mg.discoverOpenIdConfiguration("google", "https://accounts.google.com/.well-known/openid-configuration"); err != nil {
return err
}
// redis에서 env를 가져온 후에
mg.mongoClient, err = gocommon.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(CollectionAccount, map[string]bson.D{
"accid": {{Key: "accid", Value: 1}},
}); err != nil {
return err
}
if err = mg.mongoClient.MakeUniqueIndices(CollectionFile, map[string]bson.D{
"keyonly": {{Key: "key", Value: 1}},
}); err != nil {
return err
}
// Delete대신 _ts로 expire시킴. pipeline에 삭제 알려주기 위함
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 *devflag {
if err = mg.mongoClient.DropIndex(CollectionBlock, "codeaccid"); 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)))
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 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 err
}
err = fulldoc.Save()
if err != nil {
return err
}
}
var whites []*whitelistmember
if err := mg.mongoClient.AllAs(CollectionWhitelist, &whites, options.Find().SetReturnKey(false)); err != nil {
return err
}
mg.wl.init(whites)
var blocks []*blockinfo
if err := mg.mongoClient.AllAs(CollectionBlock, &blocks); err != nil {
return err
}
mg.bl.init(blocks)
go watchAuthCollection(context, mg.auths, mg.mongoClient)
go mg.wl.watchCollection(context, CollectionWhitelist, mg.mongoClient)
go mg.bl.watchCollection(context, CollectionBlock, mg.mongoClient)
return nil
}
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 err
}
if len(allServices) > 0 {
only := allServices[0]
only.prepare(mg)
atomic.StorePointer(&mg.serviceptr, unsafe.Pointer(only))
} else {
empty := serviceDescription{
ServiceDescriptionSummary: ServiceDescriptionSummary{
Id: primitive.NewObjectID(),
},
}
if *devflag {
host, _ := os.Hostname()
addrs, err := net.InterfaceAddrs()
if err != nil {
return 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{
host: {
DivisionForUser: DivisionForUser{
Priority: 0,
State: DivisionState_FullOpen,
},
Url: fmt.Sprintf("http://%s/warehouse", ipaddr),
},
}
}
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 err
}
}
logger.Println("Service is registered :", mg.service().ServiceCode)
serveMux.HandleFunc(gocommon.MakeHttpHandlerPattern(prefix, mg.service().ServiceCode, "/"), func(w http.ResponseWriter, r *http.Request) {
mg.service().serveHTTP(w, r)
})
serveMux.HandleFunc(gocommon.MakeHttpHandlerPattern(prefix, "api/"), mg.api)
serveMux.HandleFunc(gocommon.MakeHttpHandlerPattern(prefix, "query/"), mg.query)
configraw, _ := json.Marshal(mg.maingateConfig)
var convertedConfig map[string]any
if err := json.Unmarshal(configraw, &convertedConfig); err != nil {
return 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 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)
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) 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
}
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) {
logger.Println("MG-X-API-TOKEN is invalid :", apitoken)
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) //-- 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 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 < 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 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
}