Files
maingate/core/service.go
2024-01-25 17:08:51 +09:00

1098 lines
30 KiB
Go

package core
import (
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"strconv"
"strings"
"time"
"repositories.action2quare.com/ayo/gocommon"
"repositories.action2quare.com/ayo/gocommon/logger"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo/options"
)
type blockinfo struct {
Start primitive.DateTime `bson:"start" json:"start"`
End primitive.DateTime `bson:"_ts" json:"_ts"`
Reason string `bson:"reason" json:"reason"`
Accid primitive.ObjectID `bson:"_id,omitempty" json:"_id,omitempty"`
}
type blockinfoWithStringId struct {
Reason string `bson:"reason" json:"reason"`
StrId string `bson:"id" json:"id"`
StartUnix int64 `bson:"start_unix" json:"start_unix"`
EndUnix int64 `bson:"end_unix" json:"end_unix"`
}
type whitelistmember struct {
Id primitive.ObjectID `bson:"_id,omitempty" json:"_id,omitempty"`
Email string `bson:"email" json:"email"`
Platform string `bson:"platform" json:"platform"`
Desc string `bson:"desc" json:"desc"`
ExpiredAt primitive.DateTime `bson:"_ts,omitempty" json:"_ts,omitempty"`
}
func (wh *whitelistmember) Key() string {
if strings.HasPrefix(wh.Email, "*@") {
// 도메인 전체 허용
return wh.Email[2:]
}
return wh.Email
}
func (wh *whitelistmember) Expired() bool {
// 얘는 Expired가 있기만 하면 제거된 상태
return wh.ExpiredAt != 0
}
func (bi *blockinfo) Key() primitive.ObjectID {
return bi.Accid
}
func (bi *blockinfo) Expired() bool {
return bi.End.Time().Unix() < time.Now().UTC().Unix()
}
type usertokeninfo struct {
platform string
userid string
token string //refreshtoken
secret string
brinfo string
accesstoken string // microsoft only
accesstoken_expire_time int64 // microsoft only
}
type DivisionStateName string
const (
DivisionState_Closed = DivisionStateName("closed")
DivisionState_Maintenance = DivisionStateName("maintenance")
DivisionState_RestrictedOpen = DivisionStateName("restricted")
DivisionState_FullOpen = DivisionStateName("open")
)
type Maintenance struct {
Notice string `bson:"notice" json:"notice"`
StartTimeUTC int64 `bson:"start_unixtime_utc" json:"start_unixtime_utc"`
link string
}
type DivisionForUser struct {
Priority int `bson:"priority" json:"priority"`
State DivisionStateName `bson:"state" json:"state"`
LockCreateChar bool `bson:"lockcreatechar" json:"lockcreatechar"`
Maintenance *Maintenance `bson:"maintenance,omitempty" json:"maintenance,omitempty"`
}
type Division struct {
DivisionForUser `bson:",inline" json:",inline"`
Url string `bson:"url" json:"url"`
}
type ServiceDescriptionSummary struct {
Id primitive.ObjectID `bson:"_id" json:"_id"`
ServiceCode string `bson:"code" json:"code"`
}
type serviceDescription struct {
ServiceDescriptionSummary `bson:",inline" json:",inline"`
Divisions map[string]*Division `bson:"divisions" json:"divisions"`
ServerApiTokens []primitive.ObjectID `bson:"api_tokens" json:"api_tokens"`
MaximumNumLinkAccount int64
VersionSplits map[string]string `bson:"version_splits" json:"version_splits"`
auths *gocommon.AuthCollection
wl *memberContainerPtr[string, *whitelistmember]
bl *memberContainerPtr[primitive.ObjectID, *blockinfo]
mongoClient gocommon.MongoClient
sessionTTL time.Duration
serviceCodeBytes []byte
getUserBrowserInfo func(r *http.Request) (string, error)
getUserTokenWithCheck func(platform string, userid string, brinfo string) (usertokeninfo, error)
updateUserinfo func(info usertokeninfo) (bool, string, string)
getProviderInfo func(platform string, uid string) (string, string, error)
divisionsSerialized []byte
serviceSerialized []byte
divisionsSplits map[string][]byte
}
func (sh *serviceDescription) isValidToken(apiToken primitive.ObjectID) bool {
if *devflag {
return true
}
if apiToken.IsZero() {
return false
}
for _, test := range sh.ServerApiTokens {
if test == apiToken {
return true
}
}
return false
}
func (sh *serviceDescription) readProfile(authtype string, id string, binfo string) (email string, err error) {
defer func() {
s := recover()
if s != nil {
logger.Error("readProfile failed :", authtype, id, s)
if errt, ok := s.(error); ok {
err = errt
} else {
err = errors.New(fmt.Sprint(s))
}
}
}()
userinfo, err := sh.getUserTokenWithCheck(authtype, id, binfo)
if err != nil {
return "", err
}
if len(userinfo.token) == 0 {
return "", errors.New("refreshtoken token not found")
}
//-- 토큰으로 모두 확인이 끝났으면 갱신한다.
ok, _, email := sh.updateUserinfo(userinfo)
if !ok {
return "", errors.New("updateUserinfo failed")
}
return email, nil
}
func (sh *serviceDescription) prepare(mg *Maingate) error {
divs := sh.Divisions
if len(sh.ServiceCode) == 0 {
sh.ServiceCode = hex.EncodeToString(sh.Id[6:])
}
if *noauth {
sh.ServiceCode = "000000000000"
}
divsForUsers := make(map[string]*DivisionForUser)
defaultDivNames := make(map[string]bool)
for dn, div := range divs {
if div.State != DivisionState_Closed {
defaultDivNames[dn] = true
}
divsForUsers[dn] = &div.DivisionForUser
if len(div.State) == 0 {
div.State = DivisionState_FullOpen
}
if div.State != DivisionState_FullOpen {
if div.Maintenance == nil {
div.Maintenance = &Maintenance{}
}
if len(div.Maintenance.link) == 0 {
if len(div.Maintenance.Notice) == 0 {
div.Maintenance.link = "https://www.action2quare.com"
} else if strings.HasPrefix(div.Maintenance.Notice, "http") {
div.Maintenance.link = div.Maintenance.Notice
} else {
var fd FileDocumentDesc
if err := mg.mongoClient.FindOneAs(CollectionFile, bson.M{
"key": div.Maintenance.Notice,
}, &fd, options.FindOne().SetProjection(bson.M{"link": 1})); err != nil {
logger.Println(err)
return err
}
div.Maintenance.link = fd.Link
logger.Println("div.Maintenance.link :", fd.Link)
}
}
} else {
div.Maintenance = nil
}
}
sh.divisionsSerialized, _ = json.Marshal(divs)
sh.divisionsSplits = make(map[string][]byte)
for ver, divnamesT := range sh.VersionSplits {
if ver == "default" {
continue
}
divnames := strings.Split(divnamesT, ",")
split := make(map[string]*DivisionForUser)
for _, divname := range divnames {
split[divname] = divsForUsers[divname]
// 스플릿 된 버전은 default에서 제거해야 한다.
delete(defaultDivNames, divname)
}
splitMarshaled, _ := json.Marshal(split)
sh.divisionsSplits[ver] = splitMarshaled
}
defaultsDivs := make(map[string]*DivisionForUser)
for divname := range defaultDivNames {
defaultsDivs[divname] = divsForUsers[divname]
}
defaultMarshaled, _ := json.Marshal(defaultsDivs)
sh.divisionsSplits["default"] = defaultMarshaled
sh.MaximumNumLinkAccount = mg.maingateConfig.MaximumNumLinkAccount
sh.mongoClient = mg.mongoClient
sh.auths = mg.auths
sh.sessionTTL = time.Duration(mg.SessionTTL * int64(time.Second))
sh.serviceCodeBytes, _ = hex.DecodeString(sh.ServiceCode)
sh.getUserBrowserInfo = mg.GetUserBrowserInfo
sh.getUserTokenWithCheck = mg.getUserTokenWithCheck
sh.updateUserinfo = mg.updateUserinfo
sh.getProviderInfo = mg.getProviderInfo
sh.wl = &mg.wl
sh.bl = &mg.bl
sh.serviceSerialized, _ = json.Marshal(sh)
logger.Println("service is ready :", sh.ServiceCode, string(sh.serviceSerialized))
return nil
}
func (sh *serviceDescription) link(w http.ResponseWriter, r *http.Request) {
defer func() {
s := recover()
if s != nil {
logger.Error(s)
}
}()
if r.Method != "GET" {
w.WriteHeader(http.StatusBadRequest)
return
}
queryvals := r.URL.Query()
//oldToken := queryvals.Get("otoken")
oldType := queryvals.Get("otype")
oldId := queryvals.Get("oid")
sk := queryvals.Get("sk")
//newToken := queryvals.Get("ntoken")
newType := queryvals.Get("ntype")
newId := queryvals.Get("nid")
oldAuth := sh.auths.Find(sk)
if oldAuth == nil {
// 잘못된 세션
logger.Println("link failed. session key is not valid :", sk)
w.WriteHeader(http.StatusBadRequest)
return
}
// fmt.Println("=================")
// fmt.Println(oldType)
// fmt.Println(oldId)
// fmt.Println("=================")
// fmt.Println(newType)
// fmt.Println(newId)
// fmt.Println("=================")
// fmt.Println(oldAuth.Platform)
// fmt.Println(oldAuth.Uid)
// fmt.Println("=================")
bfinfo, err := sh.getUserBrowserInfo(r)
if err != nil {
logger.Error("getUserBrowserInfo failed :", err)
w.WriteHeader(http.StatusBadRequest)
return
}
guestlink := (len(oldType) == 0)
if !guestlink {
_, err = sh.readProfile(oldType, oldId, bfinfo)
if err != nil {
logger.Println("readProfile(old) failed :", err)
w.WriteHeader(http.StatusBadRequest)
return
}
} else {
logger.Println("from guest acc to real acc link : ", oldId, bfinfo, newType, newId, bfinfo)
}
oldType, oldId, err = sh.getProviderInfo(oldType, oldId)
if err != nil {
logger.Println("getProviderInfo failed :", err)
w.WriteHeader(http.StatusBadRequest)
return
}
//if oldAuth.Token != oldToken || oldAuth.Uid != oldId || oldAuth.Platform != oldType {
if oldAuth.Uid != oldId || oldAuth.Platform != oldType {
logger.Println("link failed. session key is not correct :", *oldAuth, queryvals)
w.WriteHeader(http.StatusBadRequest)
return
}
email, err := sh.readProfile(newType, newId, bfinfo)
if err != nil {
logger.Println("readProfile(new) failed :", err)
w.WriteHeader(http.StatusBadRequest)
return
}
// if len(email) == 0 {
// logger.Println("link failed. email is missing :", r.URL.Query())
// w.WriteHeader(http.StatusBadRequest)
// return
// }
newType, newId, err = sh.getProviderInfo(newType, newId)
if err != nil {
logger.Println("getProviderInfo failed :", err)
w.WriteHeader(http.StatusBadRequest)
return
}
createtime := primitive.NewDateTimeFromTime(time.Now().UTC())
link, err := sh.mongoClient.FindOneAndUpdate(CollectionLink, bson.M{
"platform": newType,
"uid": newId,
}, bson.M{
"$setOnInsert": bson.M{
"create": createtime,
"email": email,
},
}, options.FindOneAndUpdate().SetReturnDocument(options.After).SetUpsert(true).SetProjection(bson.M{"_id": 1}))
if err != nil {
logger.Error("link failed. FindOneAndUpdate link err:", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
_, newid, err := sh.mongoClient.Update(CollectionAccount, bson.M{
"_id": link["_id"].(primitive.ObjectID),
}, bson.M{
"$setOnInsert": bson.M{
"accid": oldAuth.Accid,
"create": createtime,
},
}, options.Update().SetUpsert(true))
if err != nil {
logger.Error("link failed. Update ServiceName err :", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
// newid가 있어야 한다. 그래야 기존 서비스 계정이 없는 상태이다.
if newid == nil {
// 이미 계정이 있네?
logger.Println("link failed. already have service account :", r.URL.Query())
w.WriteHeader(http.StatusBadRequest)
return
}
if guestlink {
//기존 게스트 링크 삭제
link, err = sh.mongoClient.FindOneAndDelete(CollectionLink, bson.M{
"platform": oldType,
"uid": oldId,
}, options.FindOneAndDelete().SetProjection(bson.M{"_id": 1}))
if err == nil {
sh.mongoClient.Delete(CollectionAccount, bson.M{
"_id": link["_id"].(primitive.ObjectID),
})
}
}
logger.Println("link success :", r.URL.Query())
}
// == link된 계정을 해제 한다. but, 최소 1개 계정은 연결되어 있어야 한다.
func (sh *serviceDescription) unlink(w http.ResponseWriter, r *http.Request) {
defer func() {
s := recover()
if s != nil {
logger.Error(s)
}
}()
if r.Method != "GET" {
w.WriteHeader(http.StatusBadRequest)
return
}
queryvals := r.URL.Query()
sType := queryvals.Get("stype")
sId := queryvals.Get("sid")
sk := queryvals.Get("sk")
targetType := queryvals.Get("ttype")
authInfo := sh.auths.Find(sk)
if authInfo == nil {
// 잘못된 세션
logger.Println("linkinfo failed. session key is not valid :", sk)
w.WriteHeader(http.StatusBadRequest)
return
}
// fmt.Println("=================")
// fmt.Println(sType)
// fmt.Println(sId)
// fmt.Println("=================")
// fmt.Println(authInfo.Platform)
// fmt.Println(authInfo.Uid)
// fmt.Println("=================")
sType, sId, err := sh.getProviderInfo(sType, sId)
if err != nil {
logger.Println("getProviderInfo failed :", err)
w.WriteHeader(http.StatusBadRequest)
return
}
if authInfo.Uid != sId || authInfo.Platform != sType {
logger.Println("unlink failed. session key is not correct :", *authInfo, queryvals)
w.WriteHeader(http.StatusBadRequest)
return
}
accDocs, err := sh.mongoClient.FindAll(CollectionAccount, bson.M{
"accid": authInfo.Accid,
}, options.Find().SetProjection(bson.M{
"_id": 1,
}))
if err != nil {
logger.Error("unlink failed, fail to count accounts :", err)
w.WriteHeader(http.StatusInternalServerError)
}
if len(accDocs) <= 1 {
logger.Println("unlink failed. At least one link must be maintained. :", r.URL.Query())
w.WriteHeader(http.StatusBadRequest)
return
}
var ids primitive.A
for _, accDoc := range accDocs {
ids = append(ids, accDoc["_id"].(primitive.ObjectID))
}
link, err := sh.mongoClient.FindOneAndDelete(CollectionLink, bson.M{
"platform": targetType,
"_id": bson.M{"$in": ids},
}, options.FindOneAndDelete().SetProjection(bson.M{"_id": 1}))
if err != nil {
logger.Error("unlink failed. FindOneAndDelete link err:", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
preid, err := sh.mongoClient.FindOneAndDelete(CollectionAccount, bson.M{
"_id": link["_id"].(primitive.ObjectID),
}, options.FindOneAndDelete().SetProjection(bson.M{"_id": 1}))
if err != nil {
logger.Error("unlink failed. Delete ServiceName err :", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
if preid == nil {
logger.Println("unlink failed. service account not found:", r.URL.Query())
w.WriteHeader(http.StatusBadRequest)
return
}
logger.Println("unlink success :", r.URL.Query())
}
// == 연결된 계정 정보(숫자) 전달하는 API
func (sh *serviceDescription) linkinfo(w http.ResponseWriter, r *http.Request) {
defer func() {
s := recover()
if s != nil {
logger.Error(s)
}
}()
if r.Method != "GET" {
w.WriteHeader(http.StatusBadRequest)
return
}
queryvals := r.URL.Query()
sType := queryvals.Get("stype")
sId := queryvals.Get("sid")
sk := queryvals.Get("sk")
authInfo := sh.auths.Find(sk)
if authInfo == nil {
// 잘못된 세션
logger.Println("linkinfo failed. session key is not valid :", sk)
w.WriteHeader(http.StatusBadRequest)
return
}
// fmt.Println("=================")
// fmt.Println(sType)
// fmt.Println(sId)
// fmt.Println("=================")
// fmt.Println(authInfo.Platform)
// fmt.Println(authInfo.Uid)
// fmt.Println("=================")
sType, sId, err := sh.getProviderInfo(sType, sId)
if err != nil {
logger.Println("getProviderInfo failed :", err)
w.WriteHeader(http.StatusBadRequest)
return
}
//if oldAuth.Token != oldToken || oldAuth.Uid != oldId || oldAuth.Platform != oldType {
if authInfo.Uid != sId || authInfo.Platform != sType {
logger.Println("linkinfo failed. session key is not correct :", *authInfo, queryvals)
w.WriteHeader(http.StatusBadRequest)
return
}
platformName := "platform"
accDocs, err := sh.mongoClient.FindAll(CollectionAccount, bson.M{
"accid": authInfo.Accid,
}, options.Find().SetLimit(sh.MaximumNumLinkAccount).SetProjection(bson.M{
"_id": 1,
}))
if err != nil {
logger.Error("linkinfo failed. CountDocuments err :", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
var ids primitive.A
for _, accDoc := range accDocs {
ids = append(ids, accDoc["_id"].(primitive.ObjectID))
}
links, err := sh.mongoClient.FindAll(CollectionLink, bson.M{
"_id": bson.M{"$in": ids},
}, options.Find().SetLimit(sh.MaximumNumLinkAccount).SetProjection(bson.M{
platformName: 1,
}))
if err != nil {
logger.Error("linkinfo failed. FindAll returns err :", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
var linkstrs []string
for _, link := range links {
linkstrs = append(linkstrs, link[platformName].(string))
}
linkbytes, err := json.Marshal(linkstrs)
if err != nil {
logger.Error("linkinfo failed. json marshal fail :", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
logger.Println("linkinfo :", linkstrs)
w.Write(linkbytes)
}
// == 계정 이메일 조회
func (sh *serviceDescription) emailinfo(w http.ResponseWriter, r *http.Request) {
defer func() {
s := recover()
if s != nil {
logger.Error(s)
}
}()
if r.Method != "GET" {
w.WriteHeader(http.StatusBadRequest)
return
}
queryvals := r.URL.Query()
sk := queryvals.Get("sk")
authInfo := sh.auths.Find(sk)
if authInfo == nil {
logger.Println(" session key is not valid :", sk)
w.WriteHeader(http.StatusInternalServerError)
return
}
email := authInfo.Email
if strings.HasPrefix(email, "__dummy_") && strings.HasSuffix(email, "temp__") {
email = ""
}
if strings.HasSuffix(email, "@noauth.flag") || strings.HasSuffix(email, "@guest.flag") {
email = ""
}
// fmt.Println("=================")
// fmt.Println(email)
// fmt.Println("=================")
//logger.Println("Email :", email)
w.Write([]byte(fmt.Sprintf(`{"email":"%s"}`, email)))
}
func (sh *serviceDescription) authorize(w http.ResponseWriter, r *http.Request) {
defer func() {
s := recover()
if s != nil {
logger.Error(s)
}
}()
if r.Method != "GET" {
w.WriteHeader(http.StatusBadRequest)
return
}
queryvals := r.URL.Query()
authtype := queryvals.Get("type")
uid := queryvals.Get("id")
//accesstoken := queryvals.Get("token") //-- 이거 이제 받지마라
session := queryvals.Get("sk")
var email string
if !*noauth {
if len(authtype) > 0 {
//email, err := sh.readProfile(authtype, uid, accesstoken)
bfinfo, err := sh.getUserBrowserInfo(r)
if err != nil {
logger.Println("getUserBrowserInfo failed :", err)
w.WriteHeader(http.StatusBadRequest)
return
}
email, err = sh.readProfile(authtype, uid, bfinfo)
if err != nil {
logger.Println("readProfile failed :", err)
w.WriteHeader(http.StatusBadRequest)
return
}
newType, newId, err := sh.getProviderInfo(authtype, uid)
if err != nil {
logger.Println("getProviderInfo failed :", err)
w.WriteHeader(http.StatusBadRequest)
return
}
if authtype != newType || uid != newId {
logger.Printf("auth success ( redirect ) : %s->%s, %s->%s, %s, %s", authtype, newType, uid, newId, email, session)
authtype = newType
uid = newId
}
} else {
email = fmt.Sprintf("%s@guest.flag", uid)
}
} else {
email = fmt.Sprintf("%s@noauth.flag", uid)
}
//if len(session) == 0 && len(email) > 0 {
if len(session) == 0 {
// platform + id -> account id
createtime := primitive.NewDateTimeFromTime(time.Now().UTC())
link, err := sh.mongoClient.FindOneAndUpdate(CollectionLink, bson.M{
"platform": authtype,
"uid": uid,
}, bson.M{
"$setOnInsert": bson.M{
"create": createtime,
"email": email,
},
}, options.FindOneAndUpdate().SetReturnDocument(options.After).SetUpsert(true).SetProjection(bson.M{
"_id": 1,
"_ts": 1,
}))
if err != nil {
logger.Error("authorize failed :", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
linkid := link["_id"].(primitive.ObjectID)
newaccid := primitive.NewObjectID()
for i := 0; i < len(sh.serviceCodeBytes); i++ {
newaccid[i] ^= sh.serviceCodeBytes[i]
}
account, err := sh.mongoClient.FindOneAndUpdate(CollectionAccount, bson.M{
"_id": linkid,
}, bson.M{
"$setOnInsert": bson.M{
"accid": newaccid,
"create": createtime,
},
}, options.FindOneAndUpdate().SetReturnDocument(options.After).SetUpsert(true).SetProjection(bson.M{"accid": 1, "create": 1}))
if err != nil {
logger.Error("authorize failed. Update sh.ServiceName err:", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
accid := account["accid"].(primitive.ObjectID)
oldcreate := account["create"].(primitive.DateTime)
newaccount := oldcreate == createtime
var bi *blockinfo
if sh.bl.contains(accid, &bi) {
// 블럭된 계정. 블락 정보를 알려준다.
w.Header().Add("MG-ACCOUNTBLOCK-START", strconv.FormatInt(bi.Start.Time().Unix(), 10))
w.Header().Add("MG-ACCOUNTBLOCK-END", strconv.FormatInt(bi.End.Time().Unix(), 10))
w.Header().Add("MG-ACCOUNTBLOCK-REASON", bi.Reason)
w.WriteHeader(http.StatusUnauthorized)
return
}
newsession := primitive.NewObjectID()
expired := primitive.NewDateTimeFromTime(time.Now().UTC().Add(sh.sessionTTL))
newauth := gocommon.Authinfo{
Accid: accid,
ServiceCode: sh.ServiceCode,
Platform: authtype,
Uid: uid,
Email: email,
Sk: newsession,
Expired: expired,
//RefreshToken: queryvals.Get("rt"),
}
_, _, err = sh.mongoClient.UpsertOne(CollectionAuth, bson.M{"_id": newauth.Accid}, &newauth)
if err != nil {
logger.Error("authorize failed :", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
logger.Println("session created :", accid, authtype, uid, email, newsession)
output := map[string]any{
"sk": newsession.Hex(),
"expirein": sh.sessionTTL.Seconds(),
"newAccount": newaccount,
"accid": newauth.Accid.Hex(),
}
if *noauth {
output["noauth"] = true
}
if link["_ts"] != nil {
delts := link["_ts"].(primitive.DateTime)
if !delts.Time().IsZero() {
// 삭제된 계정. 삭제 되었다고 알려주자
w.Header().Add("MG-ACCOUNT-DELETED", "TRUE")
}
}
bt, _ := json.Marshal(output)
w.Write(bt)
} else if len(session) > 0 {
sessionobj, _ := primitive.ObjectIDFromHex(session)
if !sessionobj.IsZero() {
updated, _, err := sh.mongoClient.Update(CollectionAuth,
bson.M{
"sk": sessionobj,
},
bson.M{
"$currentDate": bson.M{
"_ts": bson.M{"$type": "date"},
},
}, options.Update().SetUpsert(false))
if err != nil {
logger.Error("update auth collection failed :", err)
return
}
if !updated {
// 세션이 없네?
logger.Println("authorize failed. session not exists in database :", session)
w.WriteHeader(http.StatusUnauthorized)
return
}
output := map[string]any{
"sk": session,
"expirein": sh.sessionTTL.Seconds(),
}
logger.Println("session updated :", authtype, uid, session)
authInfo := sh.auths.Find(session)
if authInfo == nil {
// 잘못된 세션
logger.Println("authorize failed. fail to find authInfo :", session)
w.WriteHeader(http.StatusBadRequest)
return
}
//혹시 삭제 된 계정 아닌지 확인해본다.
link, err := sh.mongoClient.FindOne(CollectionLink, bson.M{
"platform": authtype,
"uid": uid,
}, options.FindOne().SetProjection(bson.M{
"_ts": 1,
}))
if link["_ts"] != nil {
delts := link["_ts"].(primitive.DateTime)
if !delts.Time().IsZero() {
// 삭제된 계정. 삭제 되었다고 알려주자
w.Header().Add("MG-ACCOUNT-DELETED", "TRUE")
}
}
bt, _ := json.Marshal(output)
w.Write(bt)
} else {
logger.Println("authorize failed. sk is not valid hex :", session)
w.WriteHeader(http.StatusBadRequest)
return
}
} else {
logger.Println("authorize failed. id empty :", queryvals)
}
}
func (sh *serviceDescription) findVersionSplit(version string) []byte {
if len(version) > 0 {
for k, v := range sh.divisionsSplits {
if strings.HasPrefix(version, k) {
if version == k || version[len(k)] == '.' {
return v
}
}
}
}
return sh.divisionsSplits["default"]
}
func (sh *serviceDescription) delacc(w http.ResponseWriter, r *http.Request) {
defer func() {
s := recover()
if s != nil {
logger.Error(s)
}
}()
if r.Method != "GET" {
w.WriteHeader(http.StatusBadRequest)
return
}
queryvals := r.URL.Query()
sType := queryvals.Get("stype")
sId := queryvals.Get("sid")
sk := queryvals.Get("sk")
cancel := queryvals.Has("cancel")
authInfo := sh.auths.Find(sk)
if authInfo == nil {
// 잘못된 세션
logger.Println("delacc failed. session key is not valid :", sk)
w.WriteHeader(http.StatusBadRequest)
return
}
originAuthType := sType
sType, sId, err := sh.getProviderInfo(sType, sId)
if err != nil {
logger.Error("delacc failed. getProviderInfo err :", err)
w.WriteHeader(http.StatusBadRequest)
return
}
if authInfo.Uid != sId || authInfo.Platform != sType {
logger.Println("delacc failed. session key is not correct :", *authInfo, queryvals)
w.WriteHeader(http.StatusBadRequest)
return
}
linkidMap, err := sh.mongoClient.FindAll(CollectionAccount, bson.M{
"accid": authInfo.Accid,
}, options.Find().SetProjection(bson.M{
"_id": 1,
}))
if err != nil {
logger.Error("delacc failed. FindAll account err :", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
var linkidAry primitive.A
for _, linkid := range linkidMap {
linkidAry = append(linkidAry, linkid["_id"].(primitive.ObjectID))
}
delfilter := primitive.M{"_id": bson.M{"$in": linkidAry}}
var delop primitive.M
if !cancel {
curtime := primitive.NewDateTimeFromTime(time.Now().UTC())
delop = primitive.M{
"$set": primitive.M{"_ts": curtime},
}
if originAuthType == AuthPlatformFirebaseAuth {
sh.mongoClient.Delete(CollectionFirebaseUserInfo, bson.M{
"firebaseuserid": sId,
})
}
} else {
delfilter["platform"] = sType
targetLinkId, err := sh.mongoClient.FindAll(CollectionLink, delfilter, options.Find().SetProjection(bson.M{
"_id": 1,
}))
if len(targetLinkId) != 1 {
logger.Error("delacc failed. FindAll link err :", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
delfilter = primitive.M{"_id": targetLinkId[0]["_id"].(primitive.ObjectID)}
delop = primitive.M{
"$unset": primitive.M{"_ts": true},
}
}
updated, _, err := sh.mongoClient.Update(CollectionAccount, delfilter, delop, options.Update().SetUpsert(false))
if !updated || err != nil {
logger.Error("delacc failed. Update CollectionAccount timestamp err :", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
updated, _, err = sh.mongoClient.Update(CollectionLink, delfilter, delop, options.Update().SetUpsert(false))
if !updated || err != nil {
logger.Error("delacc failed. Update CollectionLink timestamp err :", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
logger.Println("delacc success :", linkidMap)
}
func (sh *serviceDescription) serveHTTP(w http.ResponseWriter, r *http.Request) {
defer func() {
s := recover()
if s != nil {
logger.Error(s)
}
}()
defer func() {
io.Copy(io.Discard, r.Body)
r.Body.Close()
}()
if strings.HasSuffix(r.URL.Path, "/auth") {
sh.authorize(w, r)
} else if strings.HasSuffix(r.URL.Path, "/link") {
sh.link(w, r)
} else if strings.HasSuffix(r.URL.Path, "/unlink") {
sh.unlink(w, r)
} else if strings.HasSuffix(r.URL.Path, "/linkinfo") {
sh.linkinfo(w, r)
} else if strings.HasSuffix(r.URL.Path, "/emailinfo") {
sh.emailinfo(w, r)
} else if strings.HasSuffix(r.URL.Path, "/delacc") {
sh.delacc(w, r)
} else if strings.HasSuffix(r.URL.Path, "/divs") {
// TODO : 세션키와 authtoken을 헤더로 받아서 accid 조회
queryvals := r.URL.Query()
sk := queryvals.Get("sk")
//if len(token) == 0 || len(sk) == 0 {
if len(sk) == 0 {
w.WriteHeader(http.StatusBadRequest)
return
}
// TODO : 각 서버에 있는 자산? 캐릭터 정보를 보여줘야 하나. 뭘 보여줄지는 프로젝트에 문의
// 일단 서버 종류만 내려보내자
// 세션키가 있는지 확인
if _, ok := sh.auths.IsValid(sk, ""); !ok {
logger.Println("sessionkey is not valid :", sk)
w.WriteHeader(http.StatusUnauthorized)
return
}
version := queryvals.Get("version")
w.Write(sh.findVersionSplit(version))
} else if strings.HasSuffix(r.URL.Path, "/addr") {
queryvals := r.URL.Query()
sk := queryvals.Get("sk")
//if len(token) == 0 || len(sk) == 0 {
if len(sk) == 0 {
w.WriteHeader(http.StatusBadRequest)
return
}
// TODO : 각 서버에 있는 자산? 캐릭터 정보를 보여줘야 하나. 뭘 보여줄지는 프로젝트에 문의
// 일단 서버 종류만 내려보내자
// 세션키가 있는지 확인
if _, ok := sh.auths.IsValid(sk, ""); !ok {
logger.Println("sessionkey is not valid :", sk)
w.WriteHeader(http.StatusUnauthorized)
return
}
divname := queryvals.Get("div")
divname = strings.Trim(divname, `"`)
div := sh.Divisions[divname]
if div != nil {
switch div.State {
case DivisionState_FullOpen:
w.Write([]byte(fmt.Sprintf(`{"service":"%s"}`, div.Url)))
case DivisionState_RestrictedOpen:
// 점검중이면 whitelist만 입장 가능
cell := sh.auths.QuerySession(sk, "")
if cell == nil {
logger.Println("sessionkey is not valid :", sk)
w.WriteHeader(http.StatusBadRequest)
return
}
wm := &whitelistmember{Email: cell.ToAuthinfo().Email, Platform: cell.ToAuthinfo().Platform}
if sh.wl.contains(wm.Key(), nil) {
// qa 권한이면 입장 가능
w.Write([]byte(fmt.Sprintf(`{"service":"%s"}`, div.Url)))
} else if div.Maintenance != nil {
// 권한이 없으므로 공지
w.Write([]byte(fmt.Sprintf(`{"notice":"%s"}`, div.Maintenance.link)))
} else {
logger.Println("div.Maintenance is nil :", divname)
}
case DivisionState_Maintenance:
// 점검중. 아무도 못들어감
if div.Maintenance != nil {
w.Write([]byte(fmt.Sprintf(`{"notice":"%s"}`, div.Maintenance.link)))
} else {
logger.Println("div.Maintenance is nil :", divname)
}
}
} else {
logger.Println("div is not found :", divname, sh.Divisions)
w.WriteHeader(http.StatusBadRequest)
}
} else {
logger.Println("??? :", r.URL.Path)
w.WriteHeader(http.StatusBadRequest)
}
}