959 lines
27 KiB
Go
959 lines
27 KiB
Go
package core
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rsa"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"html/template"
|
|
"io"
|
|
"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 {
|
|
logger.Println("FirebaseAppClient error getting Auth client:", 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
|
|
}
|